C语言 -- 常见字符串处理函数总结

C语言常用字符串函数

一、基础知识

为了更好的理解后面提到的一些字符串处理函数,我们先补充一些基础知识。假如你已经掌握,可以直接跳过,进入第二部分。

1. 常量指针与指针常量

先来看一个例子:

int a = 100; const int * p = &a;  //常量指针,指向的值不可更改,但指向的地址可以更改 
int const * p = &a;  //与上式等价 
int * const p = &a;  //指针常量,指向的地址不可以更改,但指向的值可更改

那么该怎么区分这两种形式呢?从左往右看,跳过类型,看const修饰哪个字符。如果是*, 说明指针指向的值不能改变,如果是指针变量,说明指针的指向不能改变,指针的值不能修改。这个原则你可以通俗理解成 “就近原则”。
我们利用这个规则对上面提到的例子进行分析:

int a = 100; 
const (int) * p = &a;  
(int) const * p = &a;  
//将类型int隐去,则可以看到这两种形式,const修饰的都是*p,即指针变量p所指 向的值不能被改变,但p的值可以改变。
 (int) * const p = &a;  
//将类型int隐去,const修饰指针变量p,p存储的是什么,是变量a的地址。p的值 不能改变,但是p指向的变量的值可以改变。

2. 字符串的存储区

C语言中字符串一般存储在两个地方:数据常量区栈区
我们还是先来看一段代码说明:

//存储在栈区的字符串,存放与字符数组str1中,我们可以通过str1指针修改其值,但是str1本身是一个常来常量我们不能对其进行修改。类似于我们前面定义char *const str1; 
char str1[] = "hello world"; 
str1[1] = 'm'; 
char str4[] = "hello world"; 
//存储在常量区的字符串,即该指针执行我们不可以通过str2指针修改其值。类似与前面提到的定义:const char *str2或者char const *str2; 
char *str2 = "hello world"; 
char *str3 = "hello world"; 
//指针变量str2和str3指向同一块数据常量区,所以他们的地址是一样的。而str1,str4是两个不同的自动给变量,存储在栈区,所以他们的值(地址)应该是不同的。
printf("str2 = %p\n",str2); 
printf("str3 = %p\n",str3); 
printf("str1 = %p\n",str1); 
printf("str4 = %p\n",str4);

运行结果如下:
在这里插入图片描述
上面两行代码中,我们将str2与str3指向的内存地址分别打印出来,发现他们的值是一样的,为什么呢,这是因为常量区内存的值是只读的,我们即便声明两个不同的变量,只要他们的值是相同的,那么两个变量指向的就是同一块内存区域

这里值得注意的是,在c++中,字符串指针与c语言中稍有区别,c++中直接将字符串指针做了增强处理,因为c++中规定字符串指针必须用const修饰,例如在c++中这样定义,编译器会直接报错:

char* str = "hello world";  //直接报错 
const char * str = "hello world";  //正确

而在实际开发过程中,我们使用字符串一般使用数组形式,不太建议使用指针字符串形式,也即:

char str[] = "hello world";  //建议使用 
char* str = "hello world";  //不建议使用

3. 字符串长度

我们还从一个小例子来看:
char str1[] = "hello"; 
char* str2 = "hello"; 
printf(" sizeof(str1) = %ld\n", sizeof(str1));  //输出结果为 6 
printf(" sizeof(str2) = %ld\n", sizeof(str2));  //输出结果为 4 或者 8 (由编译器决定,32位的机器中,该值为4,64位的机器中,该值为8)

运行结果为:
在这里插入图片描述
这里的str2我们定义的是一个字符串指针,它指向一个常量区的字符串,而sizeof操作符操作这个指针的时候,实际上计算的是这个指针所占用的存储空间的大小。因为我本地的机器为64位,所以这里的sizeof(str2)值为8,即每个指针变量需要8个字节来存储(机器位数,其实也就代表着机器的寻址能力)。

下面我们来分析为什么str1的长度为6,我们明明只存进去5个字符。不应该是 5 才对吗?这是因为在声明一个字符串的时候,系统会自动为你的结尾添加上一个以 ‘\0’ 为结尾的结束字符,内存模型如下:
在这里插入图片描述
所以我们以后再遇到字符串的时候,一定要再心里给’\0’留个位置,这很重要。

那么我们怎么计算str2字符串的长度呢?别急,接着往下看。

二、字符串处理函数

1. 字符串长度计算

所需头文件
	#include <string.h>
函数原型
	size_t strlen(const char *s);
功能
	计算字符串的长度
参数
	s:传入的字符串指针
返回值
	字符串长度

strlen()比较简单,但有以下几点需要说明一下:

  • strlen 计算字符串也是以’\0’为结束的。strlen() 函数会不断判断当前字符是否为 ‘\0’,如果是的话,立马结束。
  • strlen计算的长度是不含’\0’的,其返回的长度只包含’\0’以前的字符个数。这点注意和sizeof()区分,sizeof计算的字符数组的长度是包含’\0’的。

下面来看一个例子:

 #include <stdio.h>
 #include <string.h>

 int main(void)
 {
     char str1[] = "hello";
     char* str2 = "hello";
     char str3[] = {'h','e','\0', 'l','l','0'};
     char str4[] = {'h', 'e', 'l','l','o', '\0'};

     printf("strlen(str1) = %ld\n", strlen(str1));
     printf("sizeof(str1) = %ld\n", sizeof(str1));

     printf("strlen(str2) = %ld\n", strlen(str2));

     printf("strlen(str3) = %ld\n", strlen(str3));
     printf("sizeof(str3) = %ld\n", sizeof(str3));
	 
     printf("strlen(str4) = %ld\n", strlen(str4));
     printf("sizeof(str4) = %ld\n", sizeof(str4));
	
	return 0}

运行结果如下:
在这里插入图片描述
可以看到,strlen()计算字符串长度时,遇到’\0’就结束,且其计算的字符串长度时不含’\0’的

2. 字符串复制

所需头文件
	#include <string.h>
函数原型
	char *strcpy(char *dest, const char *src);
功能
	把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去
参数
	dest:目的字符串首地址
	src:源字符首地
返回值
	成功:返回dest字符串的首地址
	失败:NULL

先来看一个小例子:

#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[] = "hello\0world";
	char* str2 = "hello";
	char str3[] = {'h','e','\0', 'l','l','0'};
	char str[3][16];

	//strcpy(&str[0][0],str1);
	//strcpy(&str[1][0],str2);
	//strcpy(&str[2][0],str3);
	strcpy(str[0],str1);
	strcpy(str[1],str2);
	strcpy(str[2],str3);

	printf("str[0] = %s\n",str[0]);
	printf("str[1] = %s\n",str[1]);
	printf("str[2] = %s\n",str[2]);

	return 0;
}

输出结果如下:
在这里插入图片描述
其实现的内存模型如下:
在这里插入图片描述

对于strcpy()我们有以下几点需要注意:
1. strcpy()会将src的’\0’也复制到dest的末尾。
2. 由于是逐个拷贝,意味着哪怕在字符串的中间遇到了 ‘\0’ 字符,也会结束拷贝。
3. 必须保证 dest 所指向的内存空间足够大,否则可能会造成缓冲溢出的错误。如数组访问越界,踩内存等。
4. 由于本身strcpy函数是一个非安全函数,所以我们使用的时候一定要注意。要想成为一个C语言高手,对于这些细节必须要特别注意。

所需头文件
	#include <string.h>
函数原型
	char *strncpy(char *dest, const char *src);
功能
	把src指向字符串的前n个字符复制到dest所指向的空间中,拷贝是否提前结束看指定的长度是否包含'\0'。
参数
	dest:目的字符串首地址
	src:源字符首地
	n:指定需要拷贝字符串个数
返回值
	成功:返回dest字符串的首地址
	失败:NULL

这个函数与strcpy()类似,我们先来看一个例子:

#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[] = "hello\0world";
	char* str2 = "hello";
	char str3[] = {'h','e','\0', 'l','l','o'};
	char str[3][16];

	strncpy(str[0],str1,12);
	strncpy(str[1],str2,4);
	strncpy(str[2],str3,sizeof(str[2]));

	printf("str[0] = %s\n",str[0]);
	printf("str[1] = %s\n",str[1]);
	printf("str[2] = %s\n",str[2]);

	printf("\n");

	return 0;
}

输出如下:
在这里插入图片描述

对于strncpy()函数,和strcpy()用法基本一样,但是有以下几点需要注意以下:
1. strncpy遇到’\0’或到达指定长度就停止拷贝,如果src长度小于n,则拷贝到src的最后一位即’\0’为止
2. 如果src的前n个字节里没有’\0’则dest将不会以’\0’结尾。
3. strncpy()相对与strcpy()更安全点儿,即我们可以指定拷贝长度。

3. 字符串拼接

所需头文件
	#include <string.h>
函数原型
	char *strcat(char *dest, const char *src);
功能
	将src字符串连接到dest的尾部,‘\0’也会追加过去
参数
	dest:目的字符串首地址
	src:源字符首地
返回值
	成功:返回dest字符串的首地址
	失败:NULL

strcat是一个字符串拼接函数,src的’\0’也会被追加过去。下面来看一个小例子:

#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[32] = "hello";
	char str2[] = "world";
	char *tmp_str = strcat(str1,str2);
	printf("tmp_str = %s\n",tmp_str);

	return 0;
}

输出结果为:
在这里插入图片描述

同样的dest要有足够的存储空间来接收,否则会报错。

所需头文件
	#include <string.h>
函数原型
	char *strncat(char *dest, const char *src,size_t n);
功能
	将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
参数
	dest:目的字符串首地址
	src:源字符首地
	n:指定需要追加字符串个数
返回值
	成功:返回dest字符串的首地址
	失败:NULL

strncat与strcat用法和功能基本类似,不同的是strncat可以指定连接src字节数。
同样的,我们来看一个小例子:

#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[16] = "hello";
	char str2[] = "world";
	char *tmp_str = strncat(str1,str2,4);
	printf("tmp_str = %s\n",tmp_str);

	return 0;
}

运行结果为:
在这里插入图片描述

对于strcat()比较简单,但是有以下几点需要注意一下:
1. strcat()返回dest的首地址,即连接后的字符串。
2. dest要有足够的缓存空间,否则会报错,造成缓冲溢出。
3. strcat()会覆盖dest原字符串末尾的’\0’,并且会将src末尾的’\0’也连接到新字符串的末尾。

4. 字符串比较函数

所需头文件
	#include <string.h>
函数原型
	int strcmp(char *s1, const char *s2);
功能
	比较字符串s1和s2,自左向右逐个按照ASCII码值进行比较,直到出现不同的字符或遇’\0’为止。

参数
	s1:字符串1的首地址
	s2:字符串2的首地址
返回值
	如果返回值 < 0,则表示 s1 小于 s2。
	如果返回值 > 0,则表示 s1 大于 s2。
	如果返回值 = 0,则表示 s1 等于 s2

需要注意的是strcmp()只能比较字符串,其他形式参数不能比较。

int main(void)
{
	char sr1[] = "English";
	char sr2[] = "ENGLISH";
	char sr3[] = "english";
	char sr4[] = "English";

	//strcmp()只能比较字符串, 其他形式的参数不能比较
	printf("strcmp(str1, str2) : %d\n", strcmp(sr1, sr2));
	printf("strcmp(str1, str3) : %d\n", strcmp(sr1, sr3));
	printf("strcmp(str1, str4) : %d\n", strcmp(sr1, sr4));
	printf("strcmp(&sr1[2], \"glish\"):%d\n", strcmp(&sr1[2], "glish"));

	return 0;
}

运行结果如下:
在这里插入图片描述
strcmp()会逐个字符的比较s1和s2中的每个字符,如果全部相同,则返回0。如果出现不相同的字符,则比较不相同的两个字符,然后返回字符串s1中对应的字符串的ASCLL码值减去s2中字符串中对应的字符的ASCLL码值。

所需头文件
	#include <string.h>
函数原型
	int strncmp(char *s1, const char *schar *s2, size_t n);
功能
	比较字符串s1和s2,自左向右逐个按照ASCLL码值比较s1和s2的前n个字符,直到出现不同的字符或遇’\0’为止。
参数
	s1:字符串1的首地址
	s2:字符串2的首地址
	n:指定比较的字符个数
返回值
	如果返回值 < 0,则表示 s1 小于 s2。
	如果返回值 > 0,则表示 s1 大于 s2。
	如果返回值 = 0,则表示 s1 等于 s2

strncmp和strcmp用法基本一致。先看个简单的小例子,看下怎么用。

#include <stdio.h>
#include <string.h>
  
int main(void)
{
	char sr1[] = "English";
	char sr2[] = "EngLISH";

	//strncmp()也只能比较字符串, 其他形式的参数不能比较
	printf("strncmp(str1, str2,3) : %d\n", strncmp(sr1, sr2,3));
	printf("strncmp(str1, str2,5) : %d\n", strncmp(sr1, sr2,5));
	printf("strncmp(str2, str1,5) : %d\n", strncmp(sr2, sr1,5));

	return 0;
}

运行结果如下:
在这里插入图片描述
strcmp()和strncmp()两个接口的用法也比较简单,一般是用来比较两个字符串是否相等。用法细节前面都有表述,这里不做赘述。

5. 格式化字符

所需头文件
	#include <stdio.h>
函数原型
	int sprintf(char *str, const char *format, ...);
功能
	根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0' 为止。
参数
	str:字符串首地址
	format:字符串格式,用法和printf()一样
	...:表示该函数是变参的
返回值
	成功:实际格式化的字符个数
	失败: - 1

话不多说,先看例子:

#include <stdio.h>
#include <string.h>
  
int main(void)
{
	char dst[100] = {0x0};
	int a = 10;
	char src[] = "hello";
	int len = sprintf(dst,"a=%d,\nsrc=%s", a, src);
	printf("%s\n", dst);
	printf("len = %d\n", len);

	return 0;
}

输出结果为:
在这里插入图片描述

sprintf()用于格式化输出字符串,返回实际格式化的字符的个数,我们这里看本来应该返回14的,可实际返回的是15,因为它将’\0’计算进去了。即它实际返回的值为转换的字符个数加上’\0’字符
sprintf()函数是的参数是可变的,即属于可变参函数接口。
sprintf()使用的还是比较多的,比如我们打造日志系统的时候,需要将相关信息格式存储。

所需头文件
	#include <stdio.h>
函数原型
	int sscanf(const char *str, const char *format, ...);
功能
	从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数
	str:指定的字符串首地址
	format:字符串格式,用法和scanf()一样
	...:表示该函数是变参的
返回值
	成功:参数数目,成功转换的值的个数
	失败: - 1
#include <stdio.h>
#include <string.h>

int main(void)
{
	char src[] = "a=10, b=20";
	int a;
	int b;
	sscanf(src, "a=%d,  b=%d", &a, &b);
	printf("a = %d, b = %d\n", a, b);

	return 0;
}

输出如下:
在这里插入图片描述
sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源。sscanf()的用法也较为广泛,即从指定的字符串中解析出我们想要的格式化的数据。

6. 字符串查找

所需头文件
	#include <string.h>
函数原型
	char *strchr(const char *s, char c);
功能
	在字符串s中查找字母c出现的位置
参数
	s:字符串首地址
	c:匹配字母(字符)
返回值
	成功:返回第一次出现的c地址(注意是地址,不是字符数组索引)
	失败:NULL

我们来看一个例子,加深理解:

#include <stdio.h>
#include <string.h>

int main(void)
{
	char src[] = "ddda123abcd";
	char *p = strchr(src, 'a');
	printf("p = %s\n", p);

	return 0;
}

输出如下:
在这里插入图片描述

注意,这里strchr返回第一次出现字符c的指针。

所需头文件
	#include <string.h>
函数原型
	char *strstr(const char *haystack, const char *needle);
功能
	在字符串haystack中查找字符串needle出现的位置
参数
	haystack:源字符串首地址
	needle:匹配字符串首地址
返回值
	成功:返回第一次出现的needle地址
	失败:NULL

strstr()函数是字符串中寻找指定的字符串。来看一个例子:

#include <stdio.h>
#include <string.h>

int main(void)
{
	char src[] = "ddda123abcd";
	char *p = strstr(src, "123");
	printf("p = %s\n", p);

	return 0;
}

输出为:
在这里插入图片描述
strstr()同strchr()不同的是,它查找的是字符串。其他的比较简单,这里不再赘述。

7. 字符串分割

所需头文件
	#include <string.h>
函数原型
	char *strtok(char *str, const char *delim);
功能
	将字符串分割成一个个片段,strtok()在参数str的字符串中发现参数delim中包含的分割		字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0,该函数会破坏原有字符串。首次调用strtok()时候需要传入需要源字符串的地址,再次调用时,把str设为NULL,会继续向后进行分隔,直到字符串末尾。
参数
	str:指向欲分割的字符串
	delim:分割字符串中包含的所有字符
返回值
	成功:分割后字符串首地址
	失败或查到到末尾时返回NULL
	查找不到delim中的字符时,返回当前strtok的字符串指针。所有delim中包含的字符都会被滤掉,并将被滤掉的地方设为一处分割的节点。

有一点需要注意的是,strtok()对字符串进行分割时会破坏源字符串的内容。

#include <stdio.h>
#include <string.h>

int main(void)
{
       char src[] = "ddda123abcd";
       char *p = strtok(src, "123");
       printf("p = %s\n", p);
  
      return 0;
}

输出为:
在这里插入图片描述

strtok()会将源字符串中出现指定字符串中任一字符的地方替换为’\0’,然后返回。
为了验证上述说法,我们改变需要查找的字符串的顺序。

#include <stdio.h>
#include <string.h>

int main(void)
{
       char src[] = "ddda123abcd";
       char *p = strtok(src, "321");
       printf("p = %s\n", p);
  
      return 0;
}

运行结果为:
在这里插入图片描述
需要注意的是,使用该函数进行字符串分割时,会破坏被分解字符串的完整,调用前和调用后的s已经不一样了。

第一次分割之后,原字符串str是分割完成之后的第一个字符串,剩余的字符串存储在一个静态变量中,因此多线程同时访问该静态变量时,则会出现错误。

一般不建议使用,如果要保持原字符串的完整,可以使用strchr和sscanf的组合等。

在网络编程中我们有时会用strtok()来分隔IP地址,下面给出一个应用的例子:

#include <stdio.h>
#include <string.h>
  
int main(void)
{
	char src[] = "192.168.1.24";
	int a[4],i;
	char *p = strtok(src, ".");
	for(i = 0;i < sizeof(a) && p != NULL;i++)
	{
		printf("p = %s\n", p);
		sscanf(p,"%d",&a[i]);
		printf("a[%d] = %d\n",i,a[i]);
		p = strtok(NULL,"."); //注意,这里再次调用的时候传入的NULL
	}

	return 0;
}

运行结果为:
在这里插入图片描述

三、总结

日常开发中常用到的一些字符串处理函数已经整理如上了,要成为C语言高手,这些基础内容需要我们用心去掌握。

这里总结的只是一些常用的字符串处理函数,后续也会陆续补充一些进来,以飨读者,敬请关注。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值