这篇博客将会简单整理并重点介绍处理字符和各类字符串的库函数的使用和注意事项,如果你觉得多少对你有点帮助,可以点赞收藏支持一波哦,欢迎大佬批评指正!!!
零. 前言
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数。
一. 长度不受限制的字符串函数
1.1 strlen(求字符串长度)
形式:size_t strlen ( const char * str );
返回值:该函数的返回值为字符串的长度,不包括串结束符’\0’。
使用strlen函数时必须知道的几个关键点**:**
1.strlen()括号里的参数类型必须是字符指针(char*);
2.字符串末尾自带一个"\0',且以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' );
3.参数(括号里的内容)指向的字符串必须要以 '\0' 结束;
4.注意函数的返回值为size_t,是无符号的。
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "China";
printf("%d\n", strlen(arr));
return 0;
}
复制代码
输出结果:
易错题点1:字符数组内容的区别(忘记字符串末尾自带的'\0')
例题1:
//该程序输出结果是什么?
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a','b','c' };
printf("strlen(arr1)=%d\n", strlen(arr1));
printf("strlen(arr2)=%d\n", strlen(arr2));
return 0;
}
复制代码
输出结果:
解析如下:
****易错题点2:strlen函数返回值的类型(****strlen函数的返回值为无符号整数(size_t)
例题2:
//该程序输出结果是什么?
#include<string.h>
#include <stdio.h>
int main()
{
const char* str1 = "abc";
const char* str2 = "abcdef";
if (strlen(str1) - strlen(str2) > 0)
{
printf("str1>str2\n");
}
else
{
printf("srt1<str2\n");
}
return 0;
}
复制代码
输出结果:
解析如下:
输出结果:
模拟实现strlen函数:
//模拟实现strlen函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<string.h>
size_t my_strlen(const char* p, int len)
{
assert(p != NULL);
while (*(p + len++));
return len-1 ;
}
int main()
{
size_t len = 0;
char arr[100];
gets_s(arr);
len = my_strlen(arr, len);
printf("len=%u\n",len );
return 0;
}
复制代码
1.2 strcpy(字符串拷贝(复制)函数)
形式:char* strcpy(char * destination, const char * source );
语法:strcpy(字符数组1,字符串2)
返回值:该函数返回字符数组1的首地址
语义:strcpy是字符串拷贝函数的关键字;字符数组1为目标字符数组名,保存复制后的结果,字符串2可以是字符串或字符数组名,是源字符串,表示将字符串2的所有字符包括字符结束符’\0’依次复制到字符数组1中去。操作后字符串2的字符覆盖字符数组1中的原来字符。
使用strcpy函数时必须知道的几个关键点**:**
1.源字符串必须以 '\0' 结束;
2.会将源字符串中的 '\0' 拷贝到目标空间
3.目标空间必须足够大,能容纳下源字符串的内容;
4.目标空间必须可修改。
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "xxxxxxxxxxxxx";
char arr2[] = "China";
printf("%s\n", strcpy(arr1, arr2));
return 0;
}
复制代码
输出结果:
易错点1:目标空间必须可修改(不能是常量字符串)
例题1:
//arr的值能拷贝到p当中吗?
#include<stdio.h>
#include<string.h>
int main()
{
const char* p = "#############";
char arr[] = "hello";
strcpy(p, arr);
return 0;
}
复制代码
答案是不能,因为char* p为常量字符串,里面的内容无法改变(字符指针char*的内容都是常量字符串)
易错点2:源字符数组要有'\0'
例题2:
//arr2的内容能拷贝到arr1里面去吗?
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "xxxxxxxxxxxxx";
char arr2[] = {'a','b','c'};
printf("%s\n", strcpy(arr1, arr2));
return 0;
}
复制代码
**答案是能,但是因为arr2后面没有加'\0',所以abc打印之后会继续打印(内容不定),直到找到'\0'为止.
**
输出结果:
模拟实现strlen函数:
//模拟实现strlen函数
//模拟实现字符串拷贝函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* scr)
{
//把str指向的内容拷贝进dest指向的空间中
//本质上,希望dest指向的内容被修改,而str指向的内容不应该被修改
//const char* scr:防止字符串arr2的内容改变
//const在*的左边,表示修饰指针指向的内容,则指针指向的内容无法改变
//其具体用法会在const的介绍中详细介绍
char* ret = dest;
assert(scr != NULL);
assert(dest != NULL);
//assert():断言,若括号内的条件不满足,则会报错
//报错时在末尾会提供报错的具体代码的行号
while (*(dest++) = *(scr++));
//当*dest='\0'时,'\0'的ASCLL值为0.括号内值为假,则跳出循环
return ret;
}
int main()
{
char arr1[20] = "xxxxxxxxxxxxx";
char arr2[] = "hello";
//strcpy(目的地,源头)
printf("%s\n", my_strcpy(arr1, arr2));//链式访问
return 0;
}
复制代码
输出结果:
1.3strcat(字符串追加(连接)函数)
形式:char * strcat ( char * destination, const char * source );
语法:strcat(字符数组1,字符数组2)
返回值:该函数返回字符数组1的首地址。
语义:strcat是字符串连接函数的关键字;字符数组1是放置在前面的字符串,字符数组2是连接在字符串1之后的字符串,连接时覆盖字符串1后面的’\0’,将整个的字符串2都连接到字符串1的后面。
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "China";
char arr2[] = " NO.1";
printf("%s\n", strcat(arr1, arr2));
return 0;
}
复制代码
输出结果:
使用strcpy函数时必须知道的几个关键点**:**
1.源字符串必须以 '\0' 结束。
2.会将源字符串中的 '\0' 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可变
接下来让我们思考几个问题: strcat(arr1,arr2)连接两个字符串时,arr2中的'\0'是否会一起带到arr1中来?arr1和arr2连接的过程是怎样的?通过下面的代码可以让我们做个小测试:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[50] ="hello \0#########";
//在arr1中主动放一个\0,若arr2中的\0也会被跟着到arr1中来,则输出的结果就会是:
//hell0 world(说明第一个\0被覆盖了) char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
复制代码
输出结果到底如何?
调试结果:
跟据调试结果我们可以得出如下结论:
strcat函数在使用时,源字符串会自动寻找目标空间的'\0', 然后源字符串的第一个字符将覆盖掉这个'\0',接着将源字符串的内容以此拷贝进去,包括源字符串的'\0',最后打印字符串时是以拷贝过去的'\0'作为结束标志。
模拟实现strcat函数:
//模拟实现strcat函数
//模拟实现字符串连接函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest != NULL && src != NULL);
char* ret = dest;//将目标空间的起始地址存在ret中
while (*dest)//1.找目标字符串中的\0
{
dest++;
}
while (*dest++ = *src++);//2.追加源字符串,包括\0
return ret;//返回目标空间的起始地址
}
int main()
{
char arr1[50] = "China ";
char arr2[] = "NO.1";
printf("%s\n", my_strcat(arr1, arr2));//链式访问
return 0;
}
复制代码
**输出结果:
**
注:strcat无法自己追加自己(strcat(arr1,arr1)为错误的写法),但strncat可以,strncat具体是个什么东西,以及到底是怎么实现的,在下面会做专门的讲解。
1.4 strcmp(字符串比较函数)
形式:int strcmp ( const char * str1, const char * str2 );
语法:strcmp (字符串1,字符串2)
返回值:
Ø若字符串1=字符串2,则函数值为0;
Ø若字符串1>字符串2,则函数值为一个正整数(正整数的值由编译器决定);
Ø若字符串1<字符串2,则函数值为一个负整数(负整数的值由编译器决定)。
语义:
Ø将两个字符串自左至右逐个字符相比,直到出现不同的字符或遇到’\0’为止。
Ø若全部字符都相同,则认为两个字符串相等。
Ø若出现不相同的字符,则以第一对不相同的ASCII码的大小字符的比较结果为准。
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[50] = "abce";
char arr2[] = "abcd";
printf("%d\n", strcmp(arr1, arr2));
return 0;
}
复制代码
输出结果:
使用strcmp函数时必须知道的关键点**:**
strcmp函数进行比较时,是一个一个查找比较,如果此时发现两个字符不同,则比较其ASCLL值,最后的比较结果决定了strcmp函数的返回值,比如说:
char* p="abcdg"和char* q="abcdekkkkkkk"
复制代码
虽然看起来q的总ASCLL值要比q大不少,但是用strcmp进行比较的结果也会是p>q(因为比较到g和e时,g的ASCLL值要大于e的ASCLL值):
#include<stdio.h>
#include<string.h>
int main()
{
const char* p ="abcdg";
const char* q = "abcdekkkkkkk";
printf("%d\n", strcmp(p, q));
return 0;
}
复制代码
输出结果:
模拟实现strcmp函数
//模拟实现strcmp函数
//模拟实现字符串比较函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>//断言
int my_strcmp(const char* p, const char* q)
{
assert(p != NULL && q != NULL);
while (*p == *q)//找不同的字符
{
if (*p == '\0')
{
return 0;
}
p++;
q++;
}
return *p - *q;//若q的ASCLL值大,则返回一个正数,反之则返回一个负数
}
int main()
{
const char *p ="abcde";//p里面存的是首字符a的地址
const char* q = "abcdf";//q里面存的是首字符a的地址
int ret = my_strcmp(p, q);
if (ret > 0)
{
printf("p>q\n");//字符串p大于字符串q
}
else if (ret < 0)
{
printf("p<q\n");//字符串p小于字符串q
}
else
{
printf("p==q\n");//字符串p等于字符串q
}
return 0;
}
复制代码
ps:这个程序中的一些代码其实可以适当简化,但是为了看得更清晰,所以我把步骤写详细了一点。
输出结果:
刚刚我们讲的strlen,strcpy,strcat,strcmp函数,都属于长度不受限制的字符串函数,接下来我们要讲的strncpy,strncat,strncmp函数,都属于长度受限制的字符串函数,那么两者之间到底有什么区别呢?
-------------------------------------------分割线----------------------------------------------
二.长度受限制的字符串函数
2.1 strncpy(字符串拷贝函数)
形式:char * strncpy ( char * destination, const char * source, size_t num );
num指的是拷贝字符的个数
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "abcdef";
char arr2[10]="opq";
strncpy(arr1, arr2, 2);
//将arr2中的前两个字符拷贝到arr1中
printf("%s\n",arr1);
return 0;
}
复制代码
输出结果:
注:如果num大于源字符串的字符个数(包括\0),则会将源字符串的所有内容(不包括\0后面的)拷贝进目标字符串中,num的值比源字符串的字符个数多多少则会补多少\0。
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "abcdefghijk";
char arr2[]="qwer";
strncpy(arr1, arr2, 8);
//将arr2中的前两个字符拷贝到arr1中
printf("%s\n",arr1);
return 0;
}
复制代码
输出结果:
调试结果:
模拟实现strncpy函数:
//模拟实现strncpy函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* source, int num)
{
assert(dest != NULL && source != NULL);
char* start = dest;//将arr1首地址存在start当中
while (num-- && (*dest++ = *source++));//拷贝,当num==0时跳出循环
if (num > 0)//补充\0
{
while (num--)
*dest++ = '\0';
}
return start;//返回arr1的首地址
}
int main()
{
char arr1[20] = "abcdefghijk";
char arr2[]="qwer";
printf("%s\n", my_strncpy(arr1, arr2, 8));
return 0;
}
复制代码
输出结果:
2.2 strncat(字符串连接函数)
形式:char * strncat ( char * destination, const char * source, size_t num );
num为连接字符的个数
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "hello ";
char arr2[]="world";
strncat(arr1, arr2, 3);
//将arr2中的前3个字符连接在arr1后
printf("%s\n", arr1);
return 0;
}
复制代码
输出结果:
注:当num的值大于源字符串的字符个数(包括\0)时,则会将源字符串的所有内容(不包括\0后面的)都连接到目标字符串中,
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "hello ";
char arr2[]="world";
strncat(arr1, arr2, 8);//num大于5
printf("%s\n", arr1);
return 0;
}
复制代码
输出结果:
调试结果:
模拟实现strncat函数:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strncat(char* dest, char* source, int num)
{
assert(dest != NULL && source != NULL);
char* start = dest;//将目标字符串的首地址储存在start中
while (*dest++);
dest--;//让dest指向目标字符串中的\0
while (num-- && (*dest++ = *source++) );
//num决定连接到目标字符串的个数
if (num > 0) return start;
//当num大于源字符串字符个数时,返回目标字符串的首地址
//当num小于源字符串的字符个数(不包括\0)时,在目标字符串末尾主动放一个\0
*(dest + 1) = '\0';
return start;
}
int main()
{
char arr1[20] = "hello ";
char arr2[]="world";
my_strncat(arr1, arr2, 8);
printf("%s\n",arr1);
return 0;
}
复制代码
输出结果:
2.3 strncmp(字符串比较函数)
形式:int strncmp ( const char * str1, const char * str2, size_t num );
num为源字符串跟目标字符串进行比较的字符个数
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "abcdefeghi";
char arr2[]="abcdd";
int ret = strncmp(arr1, arr2, 8);
printf("%d\n", ret);
return 0;
}
复制代码
输出结果:
-------------------------------------------分割线----------------------------------------------
三.字符串查找函数
注:有*号的只需了解就行
3.1 strstr(判断源字符串是否为目标字符串的子串)
形式:char * strstr ( const char *str2, const char * str1);
语法:strstr(arr1,arr2)
返回值:返回第一次出现的源字符串的地址
语义:判断源字符串是否为目标字符串的子串,若找到了,则返回子串的地址,
若找不到,则返回一个空指针(NULL)
注:子串的意思不是目标字符串有源字符串的内容就行,而是内容要跟源字符串一模一样,且不能分散开,比如“abcd”是“abcdefg"的子串,而”ade"则不是“abcdefg"的子串。
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "abcdefeghi";
char arr2[] = "cde";
char arr3[] = "abced";
char arr4[] = "aeh";
printf("%s\n", strstr(arr1, arr2));
printf("%s\n", strstr(arr1, arr3));
printf("%s\n", strstr(arr1, arr4));
return 0;
}
复制代码
输出结果:
模拟实现strstr函数:
//模拟实现strstr函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strstr(const char* dest,const char* source)
{
assert(dest != NULL && source != NULL);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = dest;
if (*source == '\0')
{
return (char*)dest;
//将const char* 类型的dest强制类型转换为返回值的类型(char*)
}//防止传过来的source内容为空
while (*dest)
{
s1 = cp;
s2 = source;
while (*s1 && *s2 && (*s1 == *s2))
{
//*s1 && *s2:当s1或s2为\0时,则跳出循环
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cp;
//将const char*类型的cp强制类型转换为返回值的类型(char*)
}
cp++;
}
return NULL;//找不到则返回空指针
}
int main()
{
char arr1[20] = "abbbcdefg";
char arr2[]="abbc";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("找不到\n");
}
else
{
printf("找到了:%s\n", ret);
}
return 0;
}
复制代码
输出结果:
*3.2 strtok(字符串分割)
形式:char * strtok ( char * str, const char * sep );
语法: strtok(arr1,arr2)
返回值:分割字符串的起始地址
语义:
- sep参数是字符串,定义了用作分隔符的字符集合**(例如: char* p="@. ")**
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
- 当strtok函数的第一个参数不为 NULL 时,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- 当strtok函数的第一个参数为 NULL 时,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。 如果字符串中不存在更多的标记,则返回 NULL 指针。
写法一 ( 在str字符串中分隔符较少时使用 ) :
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "1255416747@qq.com";
const char* p = "@.";//分隔符
char tmp[30] = { 0 };
strcpy(tmp, arr);
//为了防止arr的内容被改变,将arr的内容赋值到tmp当中
char* ret = strtok(tmp, p);
printf("%s\n", ret);//返回1255416747的首地址
//第一次:strtok的第一个参数不为NULL,将@改为\0
ret = strtok(NULL, p);
printf("%s\n", ret);//返回qq的首地址
//第二次:strtok从NULL (\0) 开始,将 . 改为\0
ret = strtok(NULL, p);
printf("%s\n", ret);//返回com的首地址
//第三次:strtok从NULL开始,以末尾的\0结束
return 0;
}
复制代码
输出结果:
写法二(巧妙运用for循环,任何情景下都可以使用):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "1255416747@qq.com";
const char* p = "@.";//分隔符
char tmp[20] = { 0 };
strcpy(tmp, arr);
char* ret = NULL;
for (ret = strtok(tmp, p); ret != NULL; ret = strtok(NULL, p))
{
//ret!=NULL : 当ret为空指针(\0)时,跳出循环
printf("%s\n", ret);
}
return 0;
}
复制代码
输出结果: