一、提及字符串,首先需要了解字符串的基本概念:
字符串(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
*/