C++ <cstring>字符串库函数的自定义实现

字符串处理函数包括几大类可以满足对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复制给s1strncpystrcpy一样,只不过它限制了复制的字符的个数,最多复制n个字符。如果n过小,那么strncpy就不能复制末尾的空字符,如果n比源字符串长度大,strncpy在遇到空字符后会不断向目的字符串追加空字符,直到达到n个。同时strcpystrncpy在源和目的重叠时也会有问题的。
  • memcpy函数从字节数组s2s1复制n个字节。如果源和目的有重叠,那么使用memcpy会有问题。memmove函数可以在源和目的重合时正常处理,在其他方面与memcpy相同。
  • memcpymemmovestrncpy函数可用于包括字符在内的任何内存块,而strcpy函数只适合字符串,它会持续复制字符,直到遇到源字符中的空字符为止。

拼接函数

char *strcat(char *s1, const char *s2);
char *strncat(char *s1, const char *s2, size_t n);
  • strcat函数将它的第二个参数s2追加到第一个参数s1的末尾。s1s2必须都是以空字符结尾的字符串。strcat会用s2的第一个字符覆盖s1的空字符,并在拼接字符串的后边添加空字符。
  • strncatstrcat功能相同,只是限制了从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。三个函数的主要区别是在于何时结束比较,如果第一个不同的字符在memcmpstrncmp的范围n之内,则三者相同。否则,strcmp在遇到空字符停止比较,memcmp不关心空字符,在比较的字节数达到n个时停止比较,strncmp结合了上述两个函数的特点,在达到n个字符或遇到空字符时停止比较。
  • stricmp,strnicmpstrcmp,strncmp功能相似,只不过以大小写不敏感的方式进行比较。
  • strcrollstrcmp功能相似,只不过比较结果依赖于本地化设置(根据不同的地点比较结果不同)。然而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的指针,若未找到,则返回空指针。 
  • strrchrstrchr类似,只是从字符串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的错误码时,会返回一个指向描述这种错误的字符串的指针。
    原文作者:Hann Yang
    原文地址: https://blog.csdn.net/boysoft2002/article/details/113730868
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞