提示:本篇重点介绍字符和字符串的库函数的使用和注意事项
文章目录
🌟前言
大家好,我是王云朵😎
本篇我来总结一下C语言中的字符和字符串所能用到的一些函数
C语言中对字符和字符串的处理很是繁琐,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串
中或字符数组
中。
字符串常量
适用于那些对它不做修改的字符串函数。
提示:以下是本篇文章正文内容,下面案例可供参考
一、函数类型
- 求字符串长度
strlen - 长度不 受限制的字符串函数
strcpy
strcat
strcmp - 长度受限制的字符串函数介绍
strncpy
strncat
strncmp - 字符串查找
strstr
strtok - 错误信息报告
strerror - 字符操作
- 内存操作函数
memcpy
memmove
memcmp
memset
二、函数介绍
1.求字符串长度
strlen
size_t strlen( const char * str )
定义
:Get the length of a string.
翻译
:获取字符串的长度。
- 字符串以’ \0 ‘作为结束标志,strlen函数返回的是在字符串中’ \0 ‘前面出现的字符个数(不包括’ \0 ')
- 参数指向的字符串必须要以’ \0 '结束。
- 注意函数的返回值为size_t,是无符号的(易错)。
注意一:
下面的结果是6,因为我们的char* str = "abcdef"其实里面在’f’的后面默认有一个字符’\0’,所以就会自动停止,只计算前面’\0’前面的6个字符。
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
int len = strlen("abcdef");
printf("%d",len);
return 0;
}
注意二:
看下面应该就明白结果为什么是23了,这里的23是一个随机数,因为我们在数组里定义字符的时候没有定义’\0’,而strlen计算长度的时候要找到’\0’才停止,所以这里就会出现随机值
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
int len = strlen(arr);
printf("%s",arr);
return 0;
}
注意三:函数的返回值是无符号的(特别容易错)
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char* str1 = "abc";
char* str2 = "abcdef";
if (strlen(str1) - strlen(str2)>0)
{
printf("hehe\0");
}
else
{
printf("haha\0");
}
return 0;
}
运行结果
为什么运行结果是 hehe 而不是 haha 呢?
因为我们所使用的库函数strlen返回类型是size_t ,那size_t是什么呢
我们查看定义
size_t其实使用typedef定义的,typedef把无符号整型unsigned int 重命名成size_t,其实就是size_t就是unsigned int ,那么刚刚的问题就解决啦。
正常str1的长度就是3,str2的长度就是6 ,而偏偏这里是strlen的返回类型是无符号整型,所以3-6=-3 我们就把-3当成了无符号数,而一个无符号数以补码的形式放在内存中一定是大于0的,所以代码打印结果是hehe
模拟strlen函数的实现
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
int my_strlen(const char*str)//由于传过来的是字符'a'的地址,所以
//我们用char类型的指针接受,而我们这里只是求长度,
//不会改变字符串长度,所以我们加const,更加完美
{
int count = 0;//由于要计算字符的数量,所以这里建一个计数器
assert(str != NULL);
while (*str)//从第一个字符开始如果字符的ASCII码不为0那么就进入循环进行计数
{
count++;
str++;//每计数一次,指针往后挪一次,知道指针指向'\0',这时再进入循环的时候循环终止
}
return count;
}
int main()
{
int len = my_strlen("abcdef");//传参的是我们传的实际是'a'的地址
printf("%d", len);
return 0;
}
这里我们自己的my_strlen的返回类型为什么是int呢,这样更容易理解,出错少。而数据库里为什么是unsigned int呢 ,因为字符串数量不会出现负数 最少也是0。
2.长度不受限制的字符串函数
strcpy
char*strcpy ( char* destination , const char* source );
定义
:Copies the C string pointed be source into the array pointed by destination,including the terminating null character (and stopping at that point).
翻译
:将源指向的C字符串复制到目标指向的数组中,包括终止的空字符(并在该点停止)。
- 源字符串必须以’\0’结束。
- 会将源字符串中的’\0’拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
- 学会strcpy函数的模拟实现。
注意一:
源字符arr2必须有字符’\0’,而且strcpy函数会将’\0’拷贝过去,打印字符串的时候遇到’\0’才会停止。不然就会像报错崩溃。
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char arr1[] = "abcdef";
char arr2[] = "ABC";
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char arr1[] = "abcdef";
char arr2[] = {'A','B','C'};
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
注意二:
这里要特别强调目标函数的空间一定要大而且特别是要比源函数空间大,不然就会这样报错,因为如果源函数空间大拷贝到空间小的目标函数里,就会非法访问空间。
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char arr1[] = "abc";
char arr2[] = "ABCDEF";
printf("拷贝前:%s\n", arr1);
strcpy(arr1, arr2);
printf("拷贝后:%s\n", arr1);
return 0;
}
注意三:目标空间必须是可以变化的,这样才能够进行拷贝,不然的话就会像下面一样
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char* arr1 = "abcdef";
char arr2[] = "ABC";
printf("拷贝前:%s\n", arr1);
strcpy(arr1, arr2);
printf("拷贝后:%s\n", arr1);
return 0;
}
学会strcpy函数的模拟实现。
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"assert.h"
char* my_strcpy(char* dest, const char* src)
{
assert(dest != NULL);//保证指针不为空
assert(src != NULL);
char* ret = dest;//将目标函数的起始地址传给ret,因为下面的循环会改变dest的地址
while (*dest++ = *src++)
{
;
}
return ret;//拷贝完成,直接将目标函数的起始地址传出去就OK了
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "ABC";
printf("拷贝前:%s\n", arr1);
my_strcpy(arr1, arr2);
printf("拷贝后:%s\n", arr1);
return 0;
}
strcat
char* strcat ( char * destination , const char * source );
定义
:Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.
翻译
:将源字符串的副本附加到目标字符串。目标中终止的空字符被源的第一个字符覆盖,空字符包含在由目标中的两个字符串联而成的新字符串的末尾。
- 源字符串必须以’\0’结束
- 目标空间必须有足够的大,能容纳下源字符串的内容
- 目标空间必须可修改
- 字符串自己给自己追加,如何?
注意一:
根据前面的字符串介绍,这里跟上面道理几乎一模一样,有一点不同是这里的追加实际上是找到目标函数的’\0’,然后从’\0’开始拷贝。
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"assert.h"
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "ABC";
printf("拷贝前:%s\n", arr1);
strcat(arr1, arr2);
printf("拷贝后:%s\n", arr1);
return 0;
}
那怎么才能肯定这里也是把源字符串的’/0’也拷贝过去了呢,因为目标函数本身就有很多’\0’,接下来看例子
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"assert.h"
int main()
{
char arr1[20] = "abcdef\0xxxxxxxxxx";
char arr2[] = "ABC";
printf("追加前:%s\n", arr1);
strcat(arr1, arr2);
printf("追加后:%s\n", arr1);
return 0;
}
注意二:
此函数不能自己追加自己,因为追加的时候我们是要把源字符串的’\0’也给追加上去,而自己追加自己的时候,我们追加的第一个字符就把’\0’给替换了,而进行到尾声的时候源字符串就找不到’\0’了,于是乎,崩溃
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"assert.h"
int main()
{
char arr[20] = "abcdef";
printf("追加前:%s\n", arr);
strcat(arr, arr);
printf("追加后:%s\n", arr);
return 0;
}
学会strcat函数的模拟实现。
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"assert.h"
char* my_strcat(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* ret = dest;
while (*dest != '\0')//我们先找到目标函数'\0'的地址,
//然后下面再开始追加也就是从'\0'开始拷贝
{
dest++;
}
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "ABC";
printf("追加前:%s\n", arr1);
my_strcat(arr1, arr2);
printf("追加后:%s\n", arr1);
return 0;
}
strcmp
int strcmp ( const char * str1, const char * str2 );
定义
:This function starts comparing the first character of each string. If they are equal to each
other, it continues with the following pairs until the characters differ or until a terminating
null-character is reached.
翻译
:此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用下一对,直到字符不同或到达终止空字符
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
- 那么如何判断两个字符串?
注意一:
这里的比较字符串其实比较的是单个字符的ASCII码大小
看下面的结果,如果两个字符串比较字符的时候相等,那么就比较下一对,直到不相等的时候分出胜负,返回数字
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqwe";
int ret = strcmp(arr1, arr2);
printf("%d", ret);
return 0;
}
注意二:学会strcmp函数的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"assert.h"
int my_strcmp(const char* arr1, const char* arr2)
{
assert(arr1 && arr2);//只要有一个为0那么就警告
while (*arr1 == *arr2)
{
if (*arr1 == '\0')
{
return 0;
}
arr1++;
arr2++;
}
return *arr1 - *arr2;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqwe";
int ret = my_strcmp(arr1, arr2);
printf("%d", ret);
return 0;
}
3.长度受限制的字符串函数介绍
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
定义
:Copies the first num characters of source to destination. If the end of the source C string(which is signaled by a null-character) is found before num characters have been copied,destination is padded with zeros until a total of num characters have been written to it.
翻译
:将源的第一个num字符复制到目标。如果在复制num字符之前找到源C字符串(由空字符表示)的结尾,则会用零填充目标,直到总共写入num字符。
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
注意:
这里比上面的strcpy多了一个限制数字numnum<=目标字符串时
源字符串长度<num,补’\0’直到num
源字符串长度>=num,补一个’\0’
num>目标字符串时
源字符串长度<num,补’\0’直到填满目标字符串
源字符串长度>num,警告,崩溃,非法访问空间
strncat
char * strncat ( char * destination, const char * source, size_t num );
定义
: Appends the first num characters of source to destination, plus a terminating null-character.
If the length of the C string in source is less than num, only the content up to the terminatingnull-character is copied.
翻译
:将源的第一个num字符加上终止的null字符追加到目标。如果source中的C字符串长度小于num,则只复制到terminatingnull字符之前的内容。
注意:
这里比上面的strcat多了一个限制数字numnum<目标空间时
若源字符串长度<num,补’\0’,直到达到num
若源字符串长度>=num,补一个’\0’
num>目标空间时
若源字符串长度<num,补’\0’,直到填满空间
若源字符串长度>=num,警告,崩溃,非法访问空间
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
这里比上面的strcmp多了一个限制数字num,也就是限制了比较到哪一位,其余操作跟strcmp一样
4.字符串查找
strstr
char * strstr ( const char *str1, const char * str2);
定义
:Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
翻译
:返回指向str1中第一个出现的str2的指针,如果str2不是str1的一部分,则返回空指针。
注意一:
我们在str1中找到str2的话,我们会继续把str2在str1后面的字符串继续输出直至’\0’
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char arr1[] = "abcdef";
char arr2[] = "bc";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s", ret);
}
return 0;
}
注意二:若str1中存在多条str2子链,则从第一条开始输出直至’\0’
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char arr1[] = "abcdebcf";
char arr2[] = "bc";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s", ret);
}
return 0;
}
学会strstr函数的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"assert.h"
char* my_strstr(const char* p1, const char* p2)
{
assert(p1 && p2);//断言,保障p1,p2不是空指针
char* s1; char* s2;//定义两个指针,用来移动找到相同字符
char* cur = (char*)p1;//定义一个指针,用来返回找到字符时的那个地址
if (!*p2)//先判断p2的内容是不是空格,因为空格不会是p1的子集
return ((char*)p1);//如果p2是空格,则直接返回p1
while (*cur)
{
s1 = (char*)p1;
s2 = (char*)p2;
while (*s1 && *s2 && !(*s1 - *s2))//当s1,s2相等且不为'\0'时,
//都进入循环自增往后寻找字符是否还相等
s1++; s2++;
if (!*s2)//如果s2已经到达了'\0'的位置,说明p2是p1的子集,返回cur的地址
return cur;
cur++;//在没找到子集的时候,cur自增,从s1的下一个字符开始寻找
}
return NULL;//遍历循环后没有找到子集,返回NULL
}
int main()
{
char arr1[] = "abcdebcf";
char arr2[] = "bc";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s", ret);
}
return 0;
}
strtok
char * strtok ( char * str, const char * sep );
- sep参数是个字符串,定义了用作分隔符的字符集合。
- 第一个参数指定一个字符串,它包含了0个或者多个有sep字符中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用’\0’结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回NULL指针。
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include"stdio.h"
#include"string.h"
int main()
{
char arr[] = "codenange@qq.com";
char* p = "@.";
char buf[1024] = { 0 };
strcpy(buf, arr);
char* ret = NULL;
for (ret=strtok(arr,p);ret!=NULL;ret=strtok(NULL,p))
{
printf("%s\n", ret);
}
return 0;
}
5.错误信息报告
strerror
char * strerror ( int errnum );
- 返回错误码所对应的错误信息
错误码 | 错误信息 |
---|---|
0 | No error |
1 | Operation not permitted |
2 | No such file or directory |
··· | ··· |
errno是一个全局的错误码的变量
当C语言的库函数在执行过程中,发生了错误,就会把对应的错误码,赋值到errno中
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<errno.h>//引用errno时用的头文件
#include<string.h>
int main()
{
FILE* pf = fopen("test.txt","r");//打开文件test.txt 这里没有在工程中创建该文件夹
if (pf == NULL)//因为没有创建文件夹,所以会打开失败,打开失败的时候会传入空指针
{
printf("%s\n",strerror(errno));//这里打印出失败的原因
}
else
{
printf("open file success");
}
return 0;
}
6.字符操作
字符分类函数:
函数 | 如果它的参数符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格’ ‘,换页’\f’,换行’\n’,回车’\r’,制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数字0~9 |
isxidigit | 十六进制数字,包括所有十进制数字,小写字母a ~ f,大写字母A ~ F |
islower | 小写字母a ~ z |
isupper | 大写字母A ~ Z |
isalpha | 字母a ~ z或A ~ Z |
isalnum | 字母或者数字,a ~ z,A ~ Z,0 ~ 9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
字符转换函数:
int tolower ( int c );
int toupper ( int c );
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char ch1 = 'A';
char ch2 = 'b';
printf("%c\n", tolower(ch1));//把大写转换成小写
printf("%c\n", toupper(ch2));//把小写转换成大写
return 0;
}
7.内存操作函数
memcpy
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到’\0’的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = {0};
int arr2[] = {1,2,3,4,5,6};
memcpy(arr1, arr2, sizeof(arr2));
return 0;
}
学会memcpy函数的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void*dest,const void*src,size_t num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
return ret;
}
int main()
{
int arr1[10] = {0};
int arr2[] = {1,2,3,4,5,6};
my_memcpy(arr1, arr2, sizeof(arr2));
return 0;
}
注意:我们模拟的my_memcpy有个不足的地方,看下面例子
代码如下(示例):
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10};
my_memcpy(arr+2, arr, 16);//拷贝四个元素,每个元素4个字节,总共16个字节
return 0;
}
可以看到 我们期盼的结果应该是1 2 1 2 3 4 7 8 9 10而实际输出是1 2 1 2 1 2 7 8 9 10
在C语言标准下,memcpy不负责重叠部分的拷贝,但是本身memcpy是可以完成这项任务的,我们上面模拟的my_memcpy只完成了60分的任务,而库函数memcpy是100分。
memmove
void * memmove ( void * destination, const void * source, size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
memmove(arr+2, arr,16 );//拷贝四个元素,每个元素4个字节,总共16个字节
return 0;
}
学会memmove函数的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest&&src);
char* ret = dest;
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
}
else
{
while(num--)
{
*((char*)dest+num) = *((char*)src+num);
}
}
return ret;
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
my_memmove(arr+2, arr, 16);
return 0;
}
memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
- 比较从ptr1和ptr2指针开始的num个字节
该函数与strncmp操作一样
memset
void *memset( void *dest, int c, size_t count );
- 定义:Sets buffers to a specified character.
翻译:将内存块设置为指定的字符
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr[10];
memset(arr, '#',10);
return 0;
}
有个易错点要注意memset的第三个参数是字节数,看下面例子
代码如下(示例):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = {0};
memset(arr, '#',10);
return 0;
}
🌟总结
🤗作者水平有限,如有总结不对的地方,欢迎留言或者私信!
💕如果你觉得这篇文章还不错的话,那么点赞👍、评论💬、收藏🤞就是对我最大的支持!
🌟你知道的越多,你不知道越多,我们下期见!