字符串处理函数包括几大类可以满足对char*字符串大部分操作,需要包括头文件<cstring>或者<string.h>。我是更喜欢用string类操作字符串的,只是我家小朋友刚开始学指针,而字符串操作是非常适合练基本功的。所以选几种操作讲讲,看它们如果不用库函数是怎么实现的:
声明、串长、复制
#include <iostream> #include <cstring> #include <assert.h> using namespace std; int strLen(const char *s) { if (NULL==s) throw "Invalid argument"; //assert(s!=NULL); //或者用<assert.h>库函数assert() int i=0; while(*s++!='\0') i++; //两种循环都可以 //for(i=0;*s!='\0';++s) i++; return i; } //甚至可以不用中间变量来求字串长,网传是一道某大公司的面试题 //if (*s) return (1+strlen(s+1)); else return 0; //或直接三目操作符:return (*s)?(1+strlen(s+1)):0; //酱紫的递归计算巨长的字串来估计比较耗内存的 int strLen1(const char *s) { const char *t; for (t=s; *t!='\0'; ++t); return t-s; } char *strCpy(char *dest,const char *src) { assert(NULL!=dest); assert(NULL!=src); //两个条件可以用||合并,但不知错在哪个条件 while (*src!='\0'){ *dest=*src; dest++; src++; } *dest=*src; //此时等价于*dest='\0'; //while ((*dest++ = *src++)!='\0'); //可合并为一行 return dest; } char *strCpy1(char *dest,const char *src) { char *p=dest; cout<<"begin copying:"<<endl; while (*src!='\0'){ cout<<*src<<endl; *dest++=*src++; } *dest='\0'; cout<<"end!"<<endl; return p; } int main() { int len=0; char aChar; const char *str = "hello"; // 声明和赋值 //const 不能省,否则有警告提示,但能通过编译。警告内容如下: //[Warning] deprecated conversion from string constant to 'char*' [-Wwrite-strings] //可以用函数malloc()来分配空间,指定一个字串大小StringLength //char *s=(char*)malloc(StringLength*sizeof(char)); cout<<str<<endl<<endl; cout<<"测试:sizeof()、strlen()"<<endl; const char msg1[] = "hello,world!hello,world!"; len = strlen(msg1); //<cstring>库函数 cout<<sizeof(msg1)<<"|"<<len<<endl; //sizeof 与 strlen 的区别,此时sizeof的值是数组的大小 len = strLen(msg1); //有大写字母的为自定义函数,以下同 cout<<sizeof(msg1)<<"|"<<len<<endl<<endl; //定义和赋值分开进行 const char *msg; msg = "hello,world!hello,world!"; len = strlen(msg); //<cstring>库函数 cout<<sizeof(msg)<<"|"<<len<<endl; //此时sizeof的值是指针的大小:64位系统是8;32位系统是4 len = strLen(msg); //有大写字母的为自定义函数,以下同 cout<<sizeof(msg)<<"|"<<len<<endl<<endl; //结论:sizeof不适合计算字符串长度 cout<<"测试:移动指针"<<endl; cout<<msg<<endl; *msg++;*msg++; //向后移动指针,对比显示的内容 aChar=*msg; cout<<&msg<<endl; //取地址 cout<<*msg<<endl; cout<<aChar<<endl<<endl; *msg--;*msg--; //向前移动指针,对比显示的内容 aChar=*msg; cout<<&msg<<endl; cout<<*msg<<endl; cout<<aChar<<endl; cout<<*(msg+0)<<"="<<msg[0]<<endl; cout<<*(msg+1)<<"="<<msg[1]<<endl; cout<<*(msg+2)<<"="<<msg[2]<<endl; cout<<*(msg+3)<<"="<<msg[3]<<endl; cout<<*(msg+4)<<"="<<msg[4]<<endl; cout<<*(msg+5)<<"="<<msg[5]<<endl; cout<<"["<<*(msg+strlen(msg))<<"]<-NULL/\'0\'"<<endl; cout<<msg<<endl<<endl; cout<<"测试:strcpy()"<<endl; for (int i=0;i<12;i++) *msg++; //源字串的长度必须不大于目标字串长度 cout<<msg<<endl; char dest[21]="ABCDEFGHIJKLMNOPQRST"; //20个字母+'\0' cout<<"["<<dest<<"]:"<<sizeof(dest)<<"|"<<strlen(dest)<<endl; strcpy(dest,msg); //sizeof值未变,strlen值变小 cout<<"["<<dest<<"]:"<<sizeof(dest)<<"|"<<strlen(dest)<<endl; strCpy(dest,msg); cout<<"["<<dest<<"]:"<<sizeof(dest)<<"|"<<strlen(dest)<<endl; strCpy1(dest,msg); cout<<"["<<dest<<"]:"<<sizeof(dest)<<"|"<<strlen(dest)<<endl<<endl; cout<<"测试:strncpy()"<<endl; char dest1[15]="abcdefghijklmn"; strncpy(dest1,msg,strlen(msg)); cout<<dest1<<endl; char dest2[10]={0}; //设一个最大长度可为10的空串 cout<<dest2<<"|"<<endl; strncpy(dest2,msg,strlen(dest2)); //没复制到内容 cout<<dest2<<"|"<<endl; strncpy(dest2,msg,sizeof(dest2)); //只能复制到10个字符 cout<<dest2<<endl; strncpy(dest2,msg,strlen(msg)); //可以得到12个字符,但后2个越界 cout<<dest2<<"|"<<sizeof(dest2)<<"|"<<strlen(dest2)<<endl; //结论:dest必须有足够的空间来容纳source的字符长度+'\0';相比strcpy更容易溢出出错弃用,建议用strncpy return 0; }
测试结果:
hello 测试:sizeof()、strlen() 25|24 25|24 8|24 8|24 测试:移动指针 hello,world!hello,world! 0x22fe08 l l 0x22fe08 h h h=h e=e l=l l=l o=o ,=, []<-NULL/'\0' hello,world!hello,world! 测试:strcpy() hello,world! [ABCDEFGHIJKLMNOPQRST]:21|20 [hello,world!]:21|12 [hello,world!]:21|12 begin copying: h e l l o , w o r l d ! end! [hello,world!]:21|12 测试:strncpy() hello,world!mn | | hello,worl hello,world!|10|12 -------------------------------- Process exited after 1.042 seconds with return value 0 请按任意键继续. . .
指针减法运算:
两个指针相减的结果的类型是ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位)与数组中存储的元素的类型无关,因为减法运算的结果已经将地址的差值除以该类型占用内存的长度。例如,如果p1指向array[i],而p2指向array[j],那么p2-p1的值就是j-i的值。
另外:void*类型的指针之间不能进行减法运算!指针可以加减一个整数表示指针偏移,但指针之间只有减法,没有加、乘、除法。
题外话:递归求串长的最大长度
测试:见如下代码maxSize增加到一定大比如说70000,递归法的strLen()就不行了,库函数和另外两种方法都没问题。估计maxSize的值在不同机器不同平台上可能会有不同;也有可能同一环境下不同时时间点都有可以不同。
#include<iostream> #include<cstring> int strLen(const char *s) { if (*s) return (1+strLen(s+1)); else return 0; } int strLEN1(const char *s) { int i=0; while(*s++!='\0') i++; return i; } int strLEN2(const char *s) { const char *t=s; while(*t) *t++; return t-s; } int main(void) { int maxSize=43210; char *str=(char*)malloc(maxSize+1); for (int i=0;i<maxSize;i++) *(str+i)=(i%10)+'0'; std::cout<<str<<std::endl; std::cout<<"{"<<strlen(str)<<"}"<<std::endl; std::cout<<"["<<strLEN1(str)<<"]"<<std::endl; std::cout<<"["<<strLEN2(str)<<"]"<<std::endl; std::cout<<"("<<strLen(str)<<")"<<std::endl; return 0; }
连接、比较
#include <iostream> #include <cstring> using namespace std; char *strCat(char *dest,const char *src) { char *p=dest; while (*dest) *dest++; //与strCpy比只多此行,目标串指针移到尾部然后复制源串 while ((*dest++ = *src++)!='\0'); return p; } char *strnCat(char *dest,const char *src,int n) { char *p=dest; if (n>0){ while (*dest) *dest++; while ((*dest++ = *src++)!='\0') if (!--n) break; //与strCat比,增加条件--n==0退出 *dest='\0'; } return p; //n非正整数直接返回目标串 } int strCmp(const char *s1,const char *s2) { int ret=0; while (!(ret=*s1++-*s2) && *s2++); if (ret<0) ret=-1; else if (ret>0) ret=1; return(ret); } int main() { char str1[20] = "hello"; const char *str2 = ",world!"; cout<<"测试:strcat()"<<endl; cout<<"str1:"<<str1<<endl; cout<<strcat(str1,str2)<<endl; cout<<"str1:"<<str1<<endl<<endl; cout<<"测试:strCat()"<<endl; str1[5]='\0'; //复原str1 cout<<"str1:"<<str1<<endl; cout<<strCat(str1,str2)<<endl; cout<<"str1:"<<str1<<endl<<endl; cout<<"测试:strncat(s1,s2,i) i=0~8"<<endl; for (int i=0;i<9;i++){ str1[5]='\0'; cout<<"str1:"<<str1<<endl; cout<<strncat(str1,str2,i)<<endl; cout<<"str1:"<<str1<<endl<<endl; } cout<<"测试:strnCat(s1,s2,i) i=0~8"<<endl; for (int i=0;i<9;i++){ str1[5]='\0'; cout<<"str1:"<<str1<<endl; cout<<strnCat(str1,str2,i)<<endl; cout<<"str1:"<<str1<<endl<<endl; } //注意:目标串的空间 必须可容纳 目标串+源串的长度 + 1('\0') 个字符 cout<<"测试:strcmp()"<<endl; cout<<"str1>str2"<<"|"<<strcmp(str1,str2)<<endl; cout<<"str1=str1"<<"|"<<strcmp(str1,str1)<<endl; cout<<"str2<str1"<<"|"<<strcmp(str2,str1)<<endl<<endl; cout<<"测试:strCmp()"<<endl; cout<<"str1>str2"<<"|"<<strCmp(str1,str2)<<endl; cout<<"str1=str1"<<"|"<<strCmp(str1,str1)<<endl; cout<<"str2<str1"<<"|"<<strCmp(str2,str1)<<endl; return 0; }
测试结果:
测试:strcat() str1:hello hello,world! str1:hello,world! 测试:strCat() str1:hello hello,world! str1:hello,world! 测试:strncat(s1,s2,i) i=0~8 str1:hello hello str1:hello str1:hello hello, str1:hello, str1:hello hello,w str1:hello,w str1:hello hello,wo str1:hello,wo str1:hello hello,wor str1:hello,wor str1:hello hello,worl str1:hello,worl str1:hello hello,world str1:hello,world str1:hello hello,world! str1:hello,world! str1:hello hello,world! str1:hello,world! 测试:strnCat(s1,s2,i) i=0~8 str1:hello hello str1:hello str1:hello hello, str1:hello, str1:hello hello,w str1:hello,w str1:hello hello,wo str1:hello,wo str1:hello hello,wor str1:hello,wor str1:hello hello,worl str1:hello,worl str1:hello hello,world str1:hello,world str1:hello hello,world! str1:hello,world! str1:hello hello,world! str1:hello,world! 测试:strcmp() str1>str2|1 str1=str1|0 str2<str1|-1 测试:strCmp() str1>str2|1 str1=str1|0 str2<str1|-1 -------------------------------- Process exited after 1.034 seconds with return value 0 请按任意键继续. . .
结论:C语言风格字符串使用麻烦,需要自己分配空间,就连最简单的字串连接操作还要担心是否越界,而string类只要用“加法+”就行了。所以不建议用<cstring>字符串,强烈推荐使用string类,头文件<string>,Dev-C++中可以不用#include。后者字符串操作函数比较丰富,且使用方便:
strlen(s) <=> s.length() 或 s.size()、strcat(s1,s2) <=> s1.append(s2) 、strncpy(s1,s2,n) <=> s1=s2.substr(0,n)等等。还与C字符串可以双向转换:
#include <iostream> int main(void) { //C字符串 转 string类 const char *s1 = "hello"; std::string s2 = std::string(s1); std::cout<<s2<<std::endl; //string类 转 C字符串 std::string s3 = "world"; char *s4 = (char*) s3.data(); //或 .c_str() std::cout<<s4<<std::endl; return 0; }
附录一:
string类
【以下内容来源:百度百科】
string是以char作为模板参数的模板类实例,把字符串的内存管理责任由string负责而不是由编程者负责,大大减轻了C语言风格的字符串的麻烦。
std::basic_string提供了大量的字符串操作函数,如比较、连接、搜索、替换、获得子串等。
std::basic_string属于C++ STL容器类,用户自定义的类也可以作为它的模板参数,因此也适用C++ STL Algorithm库。
string本质上是以字符作为元素的vector特化版本;不存在0字符结尾这个概念,能装入'\0'这种数据。
成员函数
下面列出所有成员函数,其中string是std::basic_string<T>的简写:
构造表示
string::string(构造)
string::~string(析构)
string::operator=- 赋值
string::assign–赋值
string::get_allocator–获得内存分配器
字符访问
string::at–访问特定字符,带边界检查
string::operator[]–访问特定字符
string::front–访问第一个字符
string::back–访问最后一个字符
string::data–访问基础数组,C++11 后与 c_str() 完全相同
string::c_str–返回对应于字符串内容的 C 风格零结尾的只读字符串
string::substr–以子串构造一个新串;参数为空时取全部源串
迭代器
string::begin–获得指向开始位置的迭代器
string::end–获得指向末尾的迭代器
string::rbegin–获得指向末尾的逆向迭代器
string::rend–获得指向开始位置的逆向迭代器
string::cbegin–获得指向开始位置的只读迭代器
string::cend–获得指向末尾的只读迭代器
string::crbegin–获得指向末尾的逆向只读迭代器
string::crend–获得指向开始位置的逆向只读迭代器
容量
string::empty–检查是否为空
string::size–返回数据的字符长度
string::length–返回数据的字符长度,与 size() 完全相同
string::max_size–返回可存储的最大的字节容量,在 32 位 Windows 上大概为 43 亿字节。
string::reserve–改变 string 的字符存储容量,实际获得的存储容量不小于 reserve 的参数值。
string::capacity–返回当前的字符存储容量
string::shrink_to_fit(C++11新增)–降低内存容量到刚好
修改器
string::clear–清空内容
string::insert–插入字符或字符串。目标 string 中的插入位置可用整数值或迭代器表示。如果参数仅为一个迭代器,则在其所指位置插入0值。
string::erase–删除 1 个或 1 段字符
string::push_back–追加 1 个字符
string::pop_back–删除最后 1 个字符,C++11 标准引入
string::append–追加字符或字符串
string::operator+=–追加,只有一个参数——字符指针、字符或字符串;不像 append() 一样可以追加参数的子串或若干相同字
string::copy–拷贝出一段字符到 C 风格字符数组;有溢出危险
string::resize–改变(增加或减少)字符串长度;如果增加了字符串长度,新字符缺省为 0 值
string::swap–与另一个 string 交换内容
string::replace–替换子串;如果替换源数据与被替换数据的长度不等,则结果字符串的长度发生改变
搜索
string::find–前向搜索特定子串的第一次出现
string::rfind–从尾部开始,后向搜索特定子串的第一次出现
string::find_first_of–搜索指定字符集合中任意字符在 *this 中的第一次出现
string::find_last_of–搜索指定字符集合中任意字符在 *this 中的最后一次出现
string::find_first_not_of–*this 中的不属于指定字符集合的首个字符
string::find_last_not_of–*this 中的不属于指定字符集合的末个字符
string::compare–与参数字符串比较
常量值
- string::npos–表示“未找到”,值为static const unsigned -1
非成员的有关的全局函数
std::operator+–字符串连接
std::operator!=–不等比较
std::operator==–相等比较
std::operator<–小于比较
std::operator<=–小于等于比较
std::operator>–大于比较
std::operator>=–大于等于比较
std::operator<<–字符串内容写到输出流中
std::operator>>–从输入流中读取一个字符串
std::getline–从istream中读入一行或一段字符到string中
std::swap–交换两个string的内容。是std::swap算法针对std::basic_string的特化版本
std::stoi–字符串转为整形
std::stol–字符串转为长整形
std::stoll–字符串转为长长整形
std::stoul–字符串转为无符号长整形
std::stoull–字符串转为无符号长长整形
std::stof–字符串转为单精度浮点形
std::stod–字符串转为双精度浮点形
std::stold–字符串转为长双精度浮点形
std::to_string–整型、无符号整型、浮点型转化为string
std::to_wstring–整型、无符号整型、浮点型转化为wstring
附录二:
复制函数
char *strcpy(char *s1, const char *s2); char *strncpy(char *s1, const char *s2, size_t n); void *memcpy(void *s1, const void *s2, size_t n); void *memmove(void *s1, const void *s2, size_t n);
strcpy,strcpy,memcpy,memmove的第一个参数都为复制的dest,而第二个参数都为复制的源source。
strcpy将一个以空字符结尾的字符串s2复制给s1。strncpy跟strcpy一样,只不过它限制了复制的字符的个数,最多复制n个字符。如果n过小,那么strncpy就不能复制末尾的空字符,如果n比源字符串长度大,strncpy在遇到空字符后会不断向目的字符串追加空字符,直到达到n个。同时strcpy和strncpy在源和目的重叠时也会有问题的。
memcpy函数从字节数组s2向s1复制n个字节。如果源和目的有重叠,那么使用memcpy会有问题。memmove函数可以在源和目的重合时正常处理,在其他方面与memcpy相同。
memcpy、memmove和strncpy函数可用于包括字符在内的任何内存块,而strcpy函数只适合字符串,它会持续复制字符,直到遇到源字符中的空字符为止。
拼接函数
1. char *strcat(char *s1, const char *s2); 2. char *strncat(char *s1, const char *s2, size_t n);
strcat
函数将它的第二个参数s2
追加到第一个参数s1
的末尾。s1
和s2
必须都是以空字符结尾的字符串。strcat
会用s2的第一个字符覆盖s1
的空字符,并在拼接字符串的后边添加空字符。strncat
与strcat
功能相同,只是限制了从s2
中取出拼接到s1
的字符个数。
比较函数
int memcmp(const void *s1, const void *s2, size_t n); int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); int stricmp(const char *s1, const char *s2); int strnicmp(const char *s1, const char *s2, size_t n); int strcoll(const char *s1, const char *s2); size_t strxfrm(char *s1, const char *s2, size_t n);
memcmp,strcmp,strncmp,stricmp,strnicmp函数以指向字符(字节)数组的指针为参数,逐个比较两个字符(字节)数组的每个字符。根据比较结束时第一个字符(字节)数组中的字符(字节)是小于、等于或大于第二个字符(字节)数组中的字符(字节)而返回-1,0或1。三个函数的主要区别是在于何时结束比较,如果第一个不同的字符在memcmp和strncmp的范围n之内,则三者相同。否则,strcmp在遇到空字符停止比较,memcmp不关心空字符,在比较的字节数达到n个时停止比较,strncmp结合了上述两个函数的特点,在达到n个字符或遇到空字符时停止比较。
stricmp,strnicmp和strcmp,strncmp功能相似,只不过以大小写不敏感的方式进行比较。
strcroll和strcmp功能相似,只不过比较结果依赖于本地化设置(根据不同的地点比较结果不同)。然而strcoll函数的速度不是很快,当这是个问题或者希望在改变本地设置而不影响比较结果的话,可以使用strxfrm函数,strxfrm将第二个参数进行本地化转换,并将转换结果放在第一个参数,参数n限制了转换的字符个数。
搜索函数
void *memchr(const void *s, int c, size_t n); char *strchr(const char *s, int c); char *strrchr(const char *s, int c); char *strpbrk(const char *s1, const char *s2); size_t strcspn(const char *s1, const char *s2); size_t strspn(const char *s1, const char *s2); char *strstr(const char *s1, const char *s2); char *strtok(char *s1, const char *s2);
strchr函数在字符串s中搜索字符c,它会返回一个指向s中第一个字符c的指针,如果没找到,则返回空指针。当遇到空字符时停止搜索。
memchr函数在搜索了n个字符后停止搜索,返回第一个字符c的指针,若未找到,则返回空指针。
strrchr与strchr类似,只是从字符串s的空字符开始,反向搜索字符c。如果找到,则返回反向第一个字符c的地址,若未找到返回空指针。
strpbrk函数从s1中寻找与s2中任意一个字符匹配的第一个字符,并返回指向它的指针。若找不到,则返回空。
strspn函数从字符串s1中搜索字符集s2,并返回字符组中第一个不属于给定字符集中的字符的下标,而strcspn函数返回第一个属于给定字符集中的字符的下标。
strstr函数在字符串s1中搜索字符串s2,返回找到的第一处匹配子串的指针,如果找不到,则返回空。
strtok函数在s1中搜索,查找一个非空字符序列(称作记号),这个序列不包括s2中指定的字符。将找到的记号后面的那个字符替换为一个空字符标记该记号的末尾,然后返回一个指向该记号的首字符的指针。使用
strtok(NULL,s2)就可以继续上一次的strtok函数调用,直到其返回一个空指针为止。
其他函数
1. void *memset(void *s, int c, size_t n); 2. size_t strlen(const char *s); 3. char *strerror(int errnum);
memset
函数将一个字符的多个副本存储到指定的内存区域。strlen
返回字符串的长度,不包括字符串末尾的空字符。strerror
当输入存储在errno
的错误码时,会返回一个指向描述这种错误的字符串的指针。