数据结构精录&总结Episode.4 数据结构入门之字符串详解(基于Visual C++)

今天突然发现CSDN上的编辑器分为富文本编辑器(也就是低端版)和Markdown编辑器(也就是高端版)两种,感觉富文本编辑器简介而功能强大,另一个则是全屏按钮+排版很鸡肋啊。

大约是中午左右我们C++数据结构课程的老师在微信群中发布了一条很严肃的消息,大意是由于当前诸如CSDN、Github、知乎等等这些平台的越来越发达,很多同学在学习的同时也受到了这些大量的泥沙俱下的信息的干扰。半学期的编程任务中,很多同学甚至没有使用C++的类的概念进行编写,也有同学没有认真完成作业。出于这个角度,我打算将更新文章中有关北理乐学平台数据结构试题解答的上传频率降低。我相信很久以后一定都还会有学弟学妹来翻看我博文中的代码,当然都是可以直接白嫖,但是对自己负责永远是没错的!


一、提及字符串,首先需要了解字符串的基本概念:

字符串(string)是一组由16位值组成的不可变的有序序列,每个字符通常来自Unicode字符集。字符串的长度是其所含的16位值的个数。其中我们判断两个字符串相等,需要判断所含的对应位上的16位数完全相等。

此外,在学习@7090关于字符串的博文时,我还了解到三点经常容易被我们忽视or误解的字符串知识:

1:释放数组指针记得是使用  delete [] pia,虽然早就知道这一点,但是在项目中有时候还是忘记数组指针的释放方法,很轻易的就使用 delete pia进行指针的释放,造成内存泄露了,对于多维数组pia[][],或者类似形式的int** pia = new int[rows][cols],要进行如下形式的资源释放

for (int i = 0 ;i < rows;i++)  

{  

 delete [] pia[i];  

}  

delete [] pia;  

2:c++中c语言风格的字符串都是以'\0'标志结束的,你创建下面一个数组也是可以的

char c[] = {'c','+','+'}; 

3:关于动态分配的数组的初始化,如果数组元素是类类型,比如string(曾经混淆string是否是类类型),将调用该类的默认构造方法进行初始化,如果元素是基本数据类型,也就是内置类型,则无初始化。例如:

string *s = new string[10];  //调用默认构造方法进行初始化。默认为""  

int *i = new int[10];        //没有初始化  

二、提及字符串,我们不得不学习C++中相对于C语言,string这个预设好的强大的字符串类:

1、string::size_type

字符串的size()成员函数应该似乎返回整型数值,但事实上,str.size()返回是string::size_type类型的值。

string类型和其他许多库类型都定义了一些配套类型(companion type)。通过这些配套类型,库函数的使用就与机器无关(machine-independent)。

size_type与unsigned型(unsigned int  或 unsigned long)具有相同含义,而且保证足够大的能够存储任意的string对象的长度。

string::size_type它在不同的机器上,长度是可以不同的,并非固定的长度。但只要你使用了这个类型,就使得你的程序适合这个机器。与实际机器匹配。

注意:不要把size的返回值赋给一个int变量,一是unsigned 和 signed的大小问题,二是有些机器上int变量的表示范围太小.因此,为了避免溢出,保存一个string对象size的最安全的方法就是使用标准库类型string::size_type.

 

【P.S.string对象的索引也应为size_type类型。

string::npos表示size_type的最大值,用来表示不存在的位置。

find()成员函数的返回值为size_type

返回size_t或size_type的有sizeof(),strlen(),size()】

 

 2、string::find()方法和string::nopos静态常量  (noposition)

string::find()函数:是一个字符或字符串查找函数,该函数有唯一的返回类型string::size_type,即一个无符号整形类型,可能是整数也可能是长整数如果查找成功,返回按照查找规则找到的第一个字符或者子串的位置;如果查找失败,返回string::npos,即-1(当然打印出的结果不是-1,而是一个很大的数值,那是因为它是无符号的)。

 

string::npos静态成员常量:是对类型为size_t的元素具有最大可能的值。当这个值在字符串成员函数中的长度或者子长度被使用时,该值表示“直到字符串结尾”。作为返回值他通常被用作表明没有匹配。

string::npos是这样定义的:static const size_type npos = -1;

 

因为string::size_type描述的是size,故需为无符号整数型类别。因为缺省配置为size_t作为size_type,于是-1被转换为无符号整数类型,npos也就成为了该类别的最大无符号值。不过实际值还是取决于size_type的实际定义类型,即无符号整型(unsigned int)的-1与无符号长整型(unsigned long)的-1是不同的。

3、push_back()方法

string中的push_back函数,作用是字符串之后插入一个字符。字符串末尾加单个字符

 C++ 中的vector头文件里面就有这个push_back函数,在vector类中作用为在vector尾部加入一个数据。

 

有关字符串的知识,还有很多很多,但是我们最常用的算法,一是利用数据结构中类似于链表和队列的方式,用结构体来定义新的字符串封装样式。二是活用字符串的模式匹配。以下代码详细总结了有关字符串、字符串操作函数、字符串模式匹配BF、KMP和改进KMP算法的知识,详细概念及结论均在注释中给出:

// 第四章 字符串.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。编写—JoeyBG,部分算法及程序表达还有不完善之处,敬请谅解!
//

#include 
#include
#include
#include
#include
#include
using namespace std;

#define Maxsize 100

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

/*
【C与C++中的字符串知识点及异同】

对于C语言,需要区分C字符串和C字符数组。
C字符串:以字符NULL(‘\0’)结尾的字符数组;
C字符数组:数组元素类型为字符类型。
C字符串的的初始化:char str[] = "hello";;
C字符串的相关操作,利用“string”中定义的字符串处理函数。
比如,strcpy(str,"world");//字符串的赋值等。
对于C++字符串,需要注意的是字符串封装成了一种数据类型string,可以直接声明变量并进行赋值等字符串操作。

C字符串的输入操作:字体修改方法
1、使用输入操作符cin来填充字符串变量,char str[20];cin>>str;
注意:以这种方式来读取字符串时,会忽略最初的空白字符(空格、制表符和换行符),而且输入会在下一个空格或换行符处停止;
2、使用预定义函数getline获取整行输入(包括空格)。char str[20]; getline(str,20);遇到行结束的时候输入才会停止;
注意:getline 函数有两个参数,第一个参数用于接收输入的C字符串变量;第二个参数用于规定getline最多能接收的字符个数。

C++string类的输入操作:
1、使用输入操作符cin, string str; cin>>str;
2、使用getline函数, string str; getline(cin,str);
3、返回字符串的长度,str.length(str);

C字符串和C++string对象之间的转换:
1、C字符串存储到string类型的变量中,例如:
char str[] = "hello";
string mystr;
mystr = str;
2、C++string对象不能自动的转换为C字符串,需要进行显示的类型转换,需要用到string类的成员函数c_str() , strcpy ( a, b.c_str() );

字符串到数字的转换:
atoi函数获取一个C字符串参数,返回对应的int值。如果参数不与一个int值对应,atoi就会返回0。atoi函数在文件为cstdlib的库中。
atoi ( "1234" );//返回整数1234
atoi ( "#123" );//返回0
*/

//C语言中的字符串操作函数可一同用于C++,而且再iostream或者stdio.h头文件中均有囊括。因为无法重载只由不同返回类型决定的函数,故以下均给出注释
/*
char* strcpy(char* pStrDest, const char* pStrSource)
{
	assert(NULL != pStrDest && NULL != pStrSource);
	char* strTemp = pStrDest;
	while ((*pStrDest++ = *pStrSource++) != '\0');

	return strTemp;
}//重载函数strcpy,拷贝pStrSource到pStrDest,并返回pStrDest地址(源和目标位置重叠情况除外)

int strcmp(const char* pStrA, const char* pStrB)
{
	assert(NULL != pStrA && NULL != pStrB);
	while (*pStrA && *pStrB && *pStrA == *pStrB)
	{
		++pStrA;
		++pStrB;
	}

	return (*pStrA - *pStrB);
}//重载函数strcmp,目的是比较字符串pStrA和pStrB并返回bool真值

char* strstr(const char* pStrSource, const char* pStrSearch)
{
	assert(pStrSource != NULL && pStrSearch != NULL);
	const char* strTempSource = pStrSource;
	const char* strTempSearch = pStrSearch;
	for (; *pStrSource != '\0'; ++pStrSource)
	{
		for (strTempSource = pStrSource, strTempSearch = pStrSearch;
			*strTempSearch != '\0' && *strTempSearch == *strTempSource;
			++strTempSource, ++strTempSearch);

		if (*strTempSearch == '\0')
		{
			return (char*)pStrSource;
		}
	}

	return (char*)NULL;
}//重载函数strstr,实现查找要求字串是否出现在母串中并返回头指针

char* strDelChar(char* pStrSource, const char chDel)
{
	assert(NULL != pStrSource && !isspace(chDel));
	char* pTempStrA, * pTempStrB;
	pTempStrA = pTempStrB = pStrSource;

	while (*pTempStrB++)
	{
		if (*pTempStrB != chDel)
		{
			*pTempStrA++ = *pTempStrB;
		}
	}
	*pTempStrA = '\0';

	return pStrSource;
}//字符串删除函数,这里删除掉字符串中与给定字符相同的位并用NULL填充

char* strrev(char* pStrSource)
{
	assert(NULL != pStrSource);

	char* pStrStart, * pStrEnd;
	pStrStart = pStrEnd = pStrSource;
	while (*pStrEnd != '\0')
	{
		++pStrEnd;
	}

	char chTemp;
	for (--pStrEnd, pStrStart; pStrEnd < pStrStart; ++pStrStart, --pStrEnd)
	{
		chTemp = *pStrStart;
		*pStrStart = *pStrEnd;
		*pStrEnd = chTemp;
	}

	return pStrSource;
}//字符串反序函数,将字符串的顺序逆置

void* memmove(void* pStrTo, const void* pStrFrom, size_t count)
{
	assert(NULL != pStrTo && NULL != pStrFrom);

	void* pStrRet = pStrTo;

	)
	{
	//内存块不重叠情况
	while (count--)
	{
		*pStrTo++ = *pStrFrom++;
	}
	}
	else
	{
	//内存块重叠情况
	char* pStrDest = (char*)pStrTo;
	char* pStrSource = (char*)pStrFrom;
	pStrDest = pStrDest + count - ;
	pStrSource = pStrSource + count - ;
	while (count--)
	{
		*pStrDest-- = *pStrSource--;
	}
	}

	return pStrRet;
}//字符串内存块拷贝函数,这里类似于字符串拷贝函数,唯一不同的是一个拷贝内容,一个既拷贝内容同时也拷贝内存空间

int strlen(const char* pStrSource)
{
	assert(NULL != pStrSource);
	int iLen = 0;
	while (*pStrSource++ != '\0')
	{
		++iLen;
	}

	return iLen;
}//字符串长度检测函数,检查字符串的长度,这里sizeof会包含NULL但strlen是不包含的,sizeof=strlen+1,从而以下省略sizeof的代码
*/

typedef struct
{
	char* ch;
	int length;
}LinkString;//利用数据结构所学的结构体方式定义字符串,包括字符串头指针和长度信息的封装

typedef char SString[Maxsize + 1];//利用常用字符方式定义字符串,不同的是长度先+1,0号单元存放串的长度

//模式匹配的三大算法:BF、KMP和改进KMP算法。其中子串的定位运算称作串的模式匹配或者串匹配,这是基础字符串操作中最有研究价值的一个东西
int Index_BF(SString S, SString T, int pos)
{ 	
	int i = pos, j = 1, sum = 0;
	while (i <= S[0] && j <= T[0])
	{
		sum++;
		if (S[i] == T[j]) // 如果相等,则继续比较后面的字符
		{
			i++;
			j++;
		}
		else
		{
			i = i - j + 2; //i退回到上一轮开始比较的下一个字符
			j = 1;  //j退回到第1个字符
		}
	}
	cout << "一共比较了" << sum << "次" << endl;
	if (j > T[0]) // 匹配成功
		return i - T[0];
	else
		return 0;
}//模式匹配BF算法函数。求T在主串S中第pos个字符之后第一次出现的位置。其中,T非空,1≤pos≤s[0],s[0]存放S串的长度。BF算法的时间复杂度最好为O(m+n),最坏为O(mn),即整体为O(mn)。

void get_next(SString T, int next[])
{
	int j = 1, k = 0;
	next[1] = 0;
	while (j < T[0])
	{

		if (k == 0 || T[j] == T[k])
			next[++j] = ++k;
		else
			k = next[k];
	}
	cout << "-----next[]-------" << endl;
	for (j = 1; j <= T[0]; j++)
		cout << next[j] << "  ";
	cout << endl;
}//普通KMP算法中计算next函数

void get_next2(SString T, int next[])
{
	int j = 1, k = 0;
	next[1] = 0;
	while (j < T[0])
	{

		if (k == 0 || T[j] == T[k])
		{
			j++;
			k++;
			if (T[j] == T[k])
				next[j] = next[k];
			else
				next[j] = k;
		}
		else
			k = next[k];
	}
	cout << "-----next[]-------" << endl;
	for (j = 1; j <= T[0]; j++)
		cout << next[j] << "  ";
	cout << endl;
}//改进KMP算法,也就是计算next函数值的改进算法

int Index_KMP(SString S, SString T, int pos, int next[])
{ 	
	int i = pos, j = 1, sum = 0;
	while (i <= S[0] && j <= T[0])
	{
		sum++;
		if (j == 0 || S[i] == T[j]) // 继续比较后面的字符
		{
			i++;
			j++;
		}
		else
			j = next[j]; // 模式串向右移动
	}
	cout << "一共比较了" << sum << "次" << endl;
	if (j > T[0]) // 匹配成功
		return i - T[0];
	else
		return 0;
}//模式匹配KMP算法,利用模式串T的next函数求T在主串S中第pos个字符之后的位置的KMP算法。其中,T非空,1≤pos≤StrLength(S)。算法时间复杂度O(n+m)

//通常情况下BF算法的复杂度也应该是O(m+n)或多点,所以如果主串和字串的匹配部分较多,KMP算法首选。匹配部分较少,BF算法首选。这需要主观地稍作判断

int main()
{
	SString S, T;
	char str[100];
	cout << "数据结构精录&总结 Episode.4 字符串—编写&调试:JoeyBG,算法仍有诸多不足之处敬请见谅!" << endl;
	cout << "--------------------------------------------------------------------------------------------" << endl;
	cout << endl;
	cout << "输入串S(输入时字符串的第一项为字符串的长度,从第二项开始才为字符串本身):" << "  ";
	cin >> S;//键入一个字符串,这个字符串将作为S
	cout << "输入串T(输入时字符串的第一项为字符串的长度,从第二项开始才为字符串本身):" << "  ";
	cin >> T;//其中我们要求第一项代表的是字符串的长度,所以以下需要进行一个检测
	if (isdigit(S[0]) == 0 || isdigit(T[0]) == 0)
	{
		cout << "第一项应当是声明字符串长度的数字!" << endl;
		cout << "输入有误,程序自动退出" << endl;
		exit(0);
	}//利用isdigit函数实现判断是否第一项字符是输入的数字字符
	else if (((int)S[0]-48) != (strlen(S) - 1) || ((int)T[0]-48) != (strlen(T) - 1))
	{
		cout << "输入字符串长度与事实不符" << endl;
		cout << "程序自动退出" << endl;
		exit(0);
	}//利用atoi函数的内在原理,将首项字符串长度符号取ASCII码再减48得到真实整数值
	int* p = new int[T[0] + 1]; //生成T的next数组
	cout << endl;
	cout << "BF算法运行结果:" << endl;
	cout << "主串和子串在第" << Index_BF(S, T, 1) << "个字符处首次匹配\n";
	cout << endl;
	cout << "KMP算法运行结果:" << endl;
	get_next(T, p);
	cout << "主串和子串在第" << Index_KMP(S, T, 1, p) << "个字符处首次匹配\n";
	cout << endl;
	cout << "改进的KMP算法运行结果:" << endl;
	get_next2(T, p);
	cout << "主串和子串在第" << Index_KMP(S, T, 1, p) << "个字符处首次匹配\n";
	return 0;
}//程序算法调用实现的主函数


/*
参考资料:
1、C/C++知识点--字符串函数:https://www.bbsmax.com/A/8Bz8X18Vzx/
2、何梦吉他:C++string类型字符串相关知识点,https://www.cnblogs.com/hemengjita/p/12747173.html
3、陈小玉:趣学数据结构,人民邮电出版社,2019.09
*/

 

你可能感兴趣的:(数据结构精录&总结)