字符串处理函数包括几大类可以满足对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
函数只适合字符串,它会持续复制字符,直到遇到源字符中的空字符为止。
拼接函数
char *strcat(char *s1, const char *s2);
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
函数调用,直到其返回一个空指针为止。
其他函数
void *memset(void *s, int c, size_t n);
size_t strlen(const char *s);
char *strerror(int errnum);
memset
函数将一个字符的多个副本存储到指定的内存区域。strlen
返回字符串的长度,不包括字符串末尾的空字符。strerror
当输入存储在errno
的错误码时,会返回一个指向描述这种错误的字符串的指针。