重点:
重点介绍处理字符和字符串的库函数的使用和注意事项
1、求字符串长度
strlen
2、长度不受限制的字符串函数
strcpy
strcat
strcmp
1、函数介绍
1.1 strlen
函数原型:size_t strlen (const char *str);
1、字符串以‘\0’作为结束标志,strlen函数返回的是(从参数给的地址开始)在字符串中‘\0’前面出现的字符个数(不包含‘\0’)
2、参数指向的字符串必须要以‘\0’结束(strlen的正常使用,要有‘\0’,否则返回值为一个随机值)
3、注意函数的返回值为size_t,是无符号的(易错)
4、模拟实现strlen函数
讲解:
(1)strlen函数的返回类型和参数类型为什么这么设计?
①返回类型:strlen是求字符串长度的,求出的长度是不可能为负数的,所以返回类型设置为size_t也是合情合理的。(typedef unsigned int size_t)
②参数类型:因为指针+1向后跳过一个字符且为了程序的健壮性(指针指向的内容是不变的,但指针变量本身可以修改),所以参数类型设置为const char* str。
(2)模拟实现strlen函数:
分析:
strlen函数是统计字符串长度的,统计的是字符串‘\0’之前的字符个数;模拟strlen函数,那函数的返回类型,参数类型一致。
实现统计字符串长度的方式有三种:
①计算器实现:while循环判断字符是否不为‘\0’,不为‘\0’,计算器count加1,为‘\0’则统计结束。
②递归实现:递归的思想就是大事化小——把一个大型问题层层转化为与原问题相似的小问题来求解,递归要有两个必要条件,条件一 ——存在限制条件,递归一次跳过一个字符,条件二 ——递归出口,当字符为‘\0’是,开始回归。
③指针 - 指针实现:指针减指针的绝对值,得到两个指针之间的元素个数(注:两个指针要指向同一块空间)。
代码实例:
#include<stdio.h>
#include<assert.h>
//计算器实现
size_t my_strlen1(const char* str)
{
//断言指针的有效性
assert(str != NULL);
size_t count = 0;
//统计从参数给的地址开始在字符串中'\0'前面出现的字符个数
while (*str++ != '\0')
{
count++;
}
return count;
}
//递归实现
size_t my_strlen2(const char* str)
{
assert(str != NULL);
if (*str != '\0')
{
return 1 + my_strlen2(1 + str);//每一次递归趋于递归出口的语句
}
else
{
return 0;//递归出口
}
}
//指针 - 指针实现(指针-指针,得到两个指针之间的元素个数)
size_t my_strlen3(const char* str)
{
assert(str != NULL);
//首字符的地址
const char* start = str;
//‘\0’字符的地址
while (*str)
{
str++;
}
//‘\0’字符的地址 - 首字符的地址,得到字符串的长度
return str - start;
}
int main()
{
char arr[] = "abcdef";
size_t len1 = my_strlen1(arr);
size_t len2 = my_strlen2(arr);
size_t len3 = my_strlen3(arr);
printf("%d %d %d\n", len1, len2, len3);
return 0;
}
(3)注意函数的返回值为size_t,是无符号的
下面代码的运行结果是什么?
#include <stdio.h>
#include<string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");
}
return 0;
}
运行结果:
为什么呢?str2的长度是3,str1的长度是6, 3 - 6 = -3执行else语句打印str1 > str2,当你这么想就错了,因为 strlen函数的返回类型是size_t,为无符号整形,无符号整形在内存中全为数值位,所以-3是一个很大的整数,所以条件为真打印str2 > str1。
1.2 strcpy(string copy——字符串拷贝)
函数原型:char* strcpy(char* destination, const char* source);
1、把source指向的C字符串拷贝到destination指向的数组中,包括结束的‘\0’字符(并在该点停止);
2、源字符串必须以‘\0’结束(因为strcpy拷贝到‘\0’字符,才会停止);
3、目标空间必须足够大,以确保能存放源字符串(避免溢出。程序崩溃);
4、目标空间必须可变(例:字符数组的空间可变,常量字符串的空间不可变);
5、学会模拟实现。
6、source和destination所指向的内存区域不可以重叠。
讲解:
(1)strcpy函数的返回类型和参数类型为什么这么设置呢?
①返回类型:返回目标空间的起始地址——char*(函数有返回值,可以链式访问)
②参数类型:要将源字符串拷贝到目标空间中,所以要两个字符指针变量分别接收源字符串和目标空间的起始地址;为了程序的健壮性源字符串只是被拷贝,源字符串内容不变,所以使用const修饰(const如果放在*的左边,const修饰的是指针指向的内容;const如果放在*的右边,const修饰的是指针变量本身)。
(2)strcpy拷贝到‘\0’字符,才会停止。
代码实例:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "##################";
char arr2[] = "abc\0def";
//将arr2拷贝到arr1中
strcpy(arr1, arr2);
//打印拷贝后的arr1
printf(arr1);
return 0;
}
F10开始调试,观察拷贝后目标空间内存的变化:
(3)模拟实现strcpy
分析:
strcpy是拷贝字符串的,把源指向的C字符串拷贝到目标空间指向的数组中,包括结束的‘\0’字;模拟strcpy函数,那函数的返回类型,参数类型一致。
函数体的实现:
①定义一个char* ret变量存储目标空间的起始地址
②将src指向的字符串拷贝到dest指向的数组中,while循环一次拷贝src一个字符到dest中,直到拷贝‘\0’结束。
③返回目标空间起始地址
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
//断言指针的有效性
assert(dest && src);
//存放目标空间的起始地址
char* ret = dest;
//将src所指向内容拷贝到dest所指向数组(拷贝到'\0'才停止)
//*dest++ = *src++
//等价于
//*dest = *src
//dest++;src++
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
//将arr2拷贝到arr1
char arr1[] = "####################";
char arr2[] = "abc";
//链式访问:注有返回值才可以
printf("%s\n", my_strcpy(arr1, arr2));
return 0;
}
1.3 strcat
函数原型:char* strcat(char* destination, const char* source );
1、将源字符串的副本(包括‘\0’)追加到目标空间,目标空间中的‘\0’字符被源字符串的第一个字符覆盖。
2、目标空间字符串必须有‘\0’(因为strcat追加,从目标字符串的‘\0’开始)
3、源字符串必须以‘\0’结束(因为strcat追加,直到追加到源字符串的‘\0’才停止,包含‘\0’)
4、目标空间必须足够大,能容纳下源字符串的内容(避免溢出,程序崩溃)
5、目标空间必须可修改
6、字符串自己给自己追加,如何?(不可以,因为目标中的‘\0’字符被源的第一个字符覆盖,源字符串就没有了‘\0’,一直追加,直至空间溢出,程序崩溃)
7、source和destination所指向的内存区域不可以重叠。
讲解:
(1)函数的返回类型与参数为什么这样设计?
①返回类型:返回目标空间的起始地址——char*(函数有返回值,可以链式访问)
②参数:要给目标空间追加字符串,就需要两个字符指针变量分别接收目标空间的起始地址和追加字符串的起始地址;为了程序的健壮性,使用const修饰源指向的内容。
(2)模拟实现strcat:
分析:
strcat是追加字符串的,把源指向的C字符串的副本(包括\0)追加到目标空间中,从目标空间的\0开始追加;模拟strcat函数,那函数的返回类型和参数类型一致。
函数体的实现:
①定义一个char* ret变量存储目标空间的起始地址
②找到目标空间的\0
③将源字符串追加到目标空间中,直到追加到源字符串的\0才停止(包括\0)。
④返回目标空间的起始地址
//模拟实现strcat
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
//断言指针的有效性
assert(dest && src);
//存放目标空间的起始地址
char* ret = dest;
//找到目标空间的\0
while (*dest)
{
dest++;
}
//追加到源字符串的\0
while (*dest++ = *src++)
{
;
}
//返回目标空间的起始地址
return ret;
}
int main()
{
char arr[20] = "hello ";
//在arr后追加word
my_strcat(arr, "word");
//打印追加后的arr
printf("%s\n", arr);
return 0;
}
1.4 strcmp
函数原型:int strcmp(const char* str1,const char* str2);
1、比较字符串的大小,比较的是两个字符串对应位置字符的ASCII码值(自左向右),如果相等,则继续比较,直到出现不同的字符或遇到‘\0’才停止。
2、标准规定:
①第一个字符串大于第二个字符串,则返回大于0的数字
②第一个字符串等于第二个字符串,则返回0
③第一个字符串小于第二个字符串,则返回小于0的数字
讲解:
(1)返回类型和参数为什么这么设计?
①返回类型:返回有正有负的整数——int(VS环境下:大于返回1,等于返回0,小于返回-1)
②参数:比较两个字符串的大小,就需要知道两个字符串的起始地址;为了程序的健壮性,都使用const修饰指针指向的内容。
(2)模拟实现strcmp
分析:
strcmp是比较字符串大小的,比较的是对应位置字符的ASCII值,如果相等,则继续比较,直到字符不同或遇到\0字符。(等于返回0,大于返回1,小于返回-1)
函数体实现:
①相等:while循环比较对应位置的字符是否相等,相等注意特殊情况——先判断是否遇到\0字符,遇到直接返回0,否则两个字符串的地址都向后跳过一个字符,继续比较;不相等则退出循环
②不相等:如果*str1 > *str2,则返回1,否则返回-1
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
//断言指针的有效性
assert(str1 && str2);
//比较对应字符的ASCII值:
//1、相等:
while (*str1 == *str2)
{
//注意判断是否遇到终止\0字符——是则直接返回0,否则都向后跳一个字符
if (*str1 == '\0')//循环条件时相等,所以如果一个为0,两个也为0
{
return 0;
}
else
{
str1++;
str2++;
}
}
//2、不相等:大于返回1,小于返回-1
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abq";
char arr2[] = "abc";
//arr1与arr2比较
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
总结:
strcpy:拷贝字符串函数
strcat:追加字符串函数
strcmp:比较字符串函数
这些函数都是长度不受限制的字符串函数,在使用时都要遇到\0才停止,所以函数不安全。
C语言中对齐进行了优化:长度受限制的字符串函数:strncpy,strncat,strncmp——他们与原先的函数相比,都加一个参数num,能控制拷贝、追加、比较的字符个数。
今天就到这结束,后续更新strncpy,strncat,strncmp,strstr,strtok,strerror等库函数。