本篇重点介绍处理字符和字符串的库函数的使用和注意事项
本篇重点
- 本篇重点介绍处理字符和字符串的库函数的使用和注意事项
- 前言:
- 求字符串长度
- strlen
- 拷贝字符串函数
- strcpy
- (追加)连接字符串函数
- strcat
- 比较两个字符串函数
- strcmp
- 对上面改进字符串函数介绍
- strncpy
- strncat
- strncmp
- 查找子字符串函数
- strstr
- 将字符串拆分为标记的函数
- strtok
- 返回错误信息的函数
- strerror
- perror
- 字符分类函数
- islower
- isupper
- 归纳总结
- 字符转换函数:
- tolower
- toupper
- 针对内存的函数
- memset
- memcpy
- memmove
- memcmp
前言:
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常要放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数
求字符串长度
strlen
注意点:
1. 字符串将’\0’作为结束标志,strlen函数返回的是在’\0’之前出现的字符个数但不包含‘\0’。
2. 参数指向的字符串必须要有’\0’,以’\0’为结束标志
3. 注意函数的返回值是size_t ,是无符号整数
4. 最好理解strlen函数的模拟实现
在这里我会展示3种方法来模拟实现strlen函数。
strlen函数模拟实现:
第一种:暴力法死算
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str);
int count = 0;
while (*str!='\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int ret = my_strlen(arr);
printf("%d", ret);
return 0;
}
第二种:指针-指针=指针之间元素的个数
size_t my_strlen(const char* str)
{
assert(str);
char* start = str;
while (*str != 0)
{
str++;
}
return str - start;
}
int main()
{
char arr[] = "abcdef";
int ret = my_strlen(arr);
printf("%d", ret);
return 0;
}
第三种:递归法
size_t my_strlen(const char* str)
{
assert(str);
if (*str != '\0')
{
return 1 + my_strlen(str + 1);
}
else
return 0;
}
int main()
{
char arr[] = "abcdef";
int ret = my_strlen(arr);
printf("%d", ret);
return 0;
}
还有一个题目来考考你:涉及上面的注意点喔
#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;
}
结果:
为什么呢?
计算str1的字符串大小应该为6,计算str2的字符串大小应该是3,3-6应该小于0,应该打印str1>str2才对呢。
是不是忘记了什么?strlen函数的返回值是什么呢?是size_t类型的,两个无符号数相减,得到的也是无符号数,所以-3就被当作无符号数来看啦,这将是一个非常大的数。所以肯定大于0。
拷贝字符串函数
strcpy
注意点:
1. 将源指向的C字符串复制到目标指向的数组中,包括
终止空字符(并在该点停止)
2. 源字符串必须以’\0’结束
3. 会将源字符串的’\0’拷贝到目的空间
4. 目标空间要足够大,以确保源字符串能存放进去
5. 目标空间必须可变,不能是常量
6. 要理解strcpy模拟实现
strcpy函数模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);//断言判断dest和src指针不为空指针
char* start = dest;//记录dest的起始位置,因为最后还要将目标空间的起始地址返回
while (*dest++ = *src++)
{
;//将源字符串全部拷贝到目的空间里,当*src为'\0'时,赋值给dest后该表达式为假,跳出循环,全部拷贝成功
}
return start;//将起始地址传回
}
int main()
{
char arr1[20] = { 0 };//目标空间要足够大
char arr2[] = "abcdef";
char *ret=my_strcpy(arr1, arr2);
printf("%s", ret);
return 0;
}
结果:
(追加)连接字符串函数
strcat
注意点:
1. 将源字符串的副本追加到目标字符串。终止的空字符
in destination被源的第一个字符覆盖,并且包含一个空字符
在由目的地中的二者串联形成的新字符串的末尾。
2. 源字符串必须以’\0’结束
3. 目标空间必须要足够大,能容纳源字符串
4. 目的空间要能修改
5. 该函数不能用于字符串给自己追加
strcat函数模拟实现:
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);//断言判断
char* start = dest;
//首先要找到目的空间的'\0'
while (*dest!='\0')
{
dest++;
}
//再将字符串追加到'\0'的后面,包括'\0'
while (*dest++ == *src++)
{
;
}
return start;//最后返回目标空间的起始地址
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
char* ret =my_strcat(arr1, arr2);
printf("%s",ret );
return 0;
}
结果:
比较两个字符串函数
strcmp
注意点:
1. 此函数开始比较每个字符串的第一个字符。如果它们相等,它继续使用,直到字符不同或终止达到空字符。
2. 标志规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数
3. 比较的是相同位上字符的ASCII 码值
模拟实现strcmp函数
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
//先比较第一个字符,如果相同则比下一个,后面的类似。
//直到相同位的字符不一样比较字符ASCII码值
while (*str1 == *str2)
{
if (*str1 == '\0')//在这个循环里,如果str1和str2中有一个等于0则表示两个字符串是相等的,因为在str1==str2的条件下,又等于0
{//肯定是相等的
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abd";
int ret=my_strcmp(arr1, arr2);
if (ret > 0)
{
printf("arr1>arr2");
}
else if (ret < 0)
{
printf("arr1<arr2");
}
else
printf("arr1=arr2");
return 0;
}
结果:
还有一个注意点:
- 在VS编译器环境下strcmp比较两个字符串时,第一个字符串大于第二个字符串返回的是1,第一个字符串小于第二字符串返回的是-1,相同时返回是0,但在不同的环境下,strcmp函数的返回的值可能不是1,-1,所以不能一概而论。
比如这题这样写你觉得合适吗?
int main()
{
char arr1[] = "abc";
char arr2[] = "abd";
int ret=my_strcmp(arr1, arr2);
if (ret==1)
{
printf("arr1>arr2");
}
else if (ret==-1)
{
printf("arr1<arr2");
}
else
printf("arr1=arr2");
return 0;
}
虽然也能算出来,但在不同的环境下,就不一定能算出来了所以最好还是写成大于0,小于0的形式比较保险。
对上面改进字符串函数介绍
strncpy
是对strcpy函数的改进版,增加了可以拷贝几个字符的功能,其他都一样。
注意点:
1. 将源的前num个字符复制到目标。如果源C字符串的结尾
(由空字符发出信号)在num个字符被复制之前被发现,
目标用零填充,直到总共写入num个字符。
2. 拷贝num个字符从源字符串到目的空间
3. 如果源字符串的长度小于num,则拷贝源字符串后,在目标的后面追加0,直到num为止。
strncat
是对strcat函数的改进版,增加了可以追加几个字符的功能,其他的都一样。
注意点:
将源的前num个字符追加到目标,加上终止的空字符。
如果源代码中的C字符串的长度小于num,则仅复制终止空字符之前的内容。。
strncmp
是strcmp函数的改进版,可以选择比较前num个字符的大小
注意点:
比较出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
查找子字符串函数
strstr
注意点:
1. 返回值是指向str1中首次出现str2所指向的代表的字符的地址,如果序列在str1中不存在,则返回NULL指针
2. 是查找str1中是否有str2这个字符子串。
例子:
int main()
{
char str[] = "This is a simple string";
char* ret;
ret = strstr(str, "simple");
//strstr返回的是在str数组中首次出现"simple"的指针,如果没有出现就返回NULL;
printf("%s", ret);
return 0;
}
结果:
模拟实现strstr函数:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (*str2 == '\0')
{
return str1;
}
char* s1 = NULL;
char* s2 = NULL;
char* cp = str1;
//
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cp;
}
cp++;
}
return NULL;
}
int main()
{
char arr1[] = "abbbcd";
char arr2[] = "bc";
char* ret = my_strstr(arr1, arr2);
printf("%s", ret);
return 0;
}
结果:
将字符串拆分为标记的函数
strtok
注意点:
- 参数 deli是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定了要一个字符串,它必须包含0个或多个由deli字符串中一个或者多个分隔符分割的标志
- strtok函数找到str中的一个标记,会将其用’\0’替换,返回一个指向这个标记的指针。(strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容)
- strtok函数的第一个参数不为NULL,函数将会找到str中的第一个标记,strtok函数将保存它在字符串中的位置
- strtok的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回NULL指针
例子:
#include <string.h>
int main()
{
char arr1[] = "xiao tao lailo@qq.com";//要分割的字符串C
char arr2[] = "@.";//分割符形成的集合分割符@ 和 分割符 .
char arr3[100] = { 0 };
strcpy(arr3, arr1);//将数据临时拷贝一份,处理arr1中的内容
char*ret=strtok(arr3, arr2);//第一次要将完整的要分割的字符串作为参数
printf("%s\n", ret);
ret = strtok(NULL, arr2);//第二次就不需要字符串了,只需要NULL作为参数就可以
printf("%s\n", ret);
ret = strtok(NULL, arr2);//第三次也是一样
printf("%s\n", ret);
return 0;
}
也可以把分割的过程写成一个循环,因为就第一次不同,后面的都是一样的可以写成下面这样:
#include <string.h>
int main()
{
char arr1[] = "xiao tao lailo@qq.com";//要分割的字符串C
char arr2[] = "@.";//分割符形成的集合分割符@ 和 分割符 .
char arr3[100] = { 0 };
strcpy(arr3, arr1);//将数据临时拷贝一份,处理arr1中的内容
//char*ret=strtok(arr3, arr2);//第一次要将完整的要分割的字符串作为参数
//printf("%s\n", ret);
//ret = strtok(NULL, arr2);//第二次就不需要字符串了,只需要NULL作为参数就可以
//printf("%s\n", ret);
//ret = strtok(NULL, arr2);//第三次也是一样
//printf("%s\n", ret);
char* ret = NULL;
for (ret = strtok(arr3, arr2); ret != NULL; ret = strtok(NULL, arr2))
{
printf("%s\n", ret);
}
return 0;
}
返回错误信息的函数
strerror
注意点:这个函数的功能是,返回错误码,所对应的错误信息
例子:
#include <errno.h>//使用该库函数需要引用头文件
#include <string.h>
int main()
{
FILE* pFile = fopen("unexist.txt", "r");//打开一个不存在的文件
if (pFile == NULL)//打开失败会进行报错
{
printf("%s", strerror(errno));//错误信息打印
}
else
fclose(pFile);
return 0;
}
结果:
perror
这个相比较上面的更方便些,因为这个直接可以打印报错
例子:
#include <string.h>
int main()
{
FILE* pFile = fopen("unexist.txt", "r");//打开一个不存在的文件
if (pFile == NULL)//打开失败会进行报错
{
perror("fopen");//参数是在哪个过程会报错呢就写哪个过程。
}
else
fclose(pFile);
return 0;
}
结果:
字符分类函数
islower
判断是否是小写字母函数
返回值:
是—返回非0,不是返回0.
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
int ret1 = islower('A');
int ret2 = islower('a');
printf("%d\n", ret1);//不是小写字母返回0
printf("%d\n", ret2);//是小写字母返回非0
return 0;
}
isupper
1.检查字符是否为大写字母
2.返回值:
是大写字母就返回非0,不是就返回0.
3.例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
int ret1 = isupper('A');//是大写字母返回非0
int ret2 = isupper('a');//不是大写字母返回0
printf("%d\n", ret1);
printf("%d\n", ret2);
return 0;
}
归纳总结
以上字符分类函数的结构都是一样的,返回值也是差不多的,如果是真就返回非0的数,如果是假就返回0。
都是只有一个参数,一个整数返回值。参数取决于是分类什么。
字符转换函数:
tolower
例子1:
#include <ctype.h>
int main()
{
int ret=tolower('A');//将大写转换为小写
printf("%c", ret);
return 0;
}
例子2:
#include <ctype.h>
int main()
{
int i = 0;
char arr[] = "Xiao Tao Lai Lo";
char c;
while (arr[i])
{
c = arr[i];
if (isupper(c))//如果是大写
{
c = tolower(c);//转换成小写
}
putchar(c);//输出
i++;
}
return 0;
}
结果:
toupper
将小写字母转换为大写字母
例子:
#include <ctype.h>
int main()
{
int ret=toupper('a');//将小写转换为大写
printf("%c", ret);
return 0;
}
以上函数都是针对字符或者字符串的,下面将介绍可以改变各种类型的函数————内存函数
针对内存的函数
memset
注意点:
1. 这是内存设置函数
2. 以字节为单位来设置内存中的数据
例子:
#include <string.h>
int main()
{
char arr[] = "xiao tao lai lo";
memset(arr, 'x', 4);//第一个参数是要指向设置的内存块的指针
//第二个参数是要设置的值
//第三个参数是要设置多少字节数
printf("%s\n", arr);
memset(arr + 5, 'y', 3);
printf("%s\n", arr);
return 0;
}
注意:这个是以字节为单位改变内存的
int main()
{
int arr[10] = { 0 };
memset(arr, '1', 40);//要将arr这个内存块40个字节全部设置成1可以吗?
return 0;
}
可是通过监测分析arr中每个数都是一个很大的数字这是为什么呢?
这是因为memset是以字节为单位来改变内存的,它每次修改一个字节,也就是每次将一个字节修改为1,所以一个int类型4个字节,就改成了4个1了。
一开始内存中都是0,使用memset函数后看内存中是什么样子呢?
所以在使用memset函数时要注意这点,不然容易出错。一般memset函数用于将一个变量初始化为0,比较容易。
memcpy
例子:
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 40);//参数1是要拷贝的目标空间,参数2是拷贝的源数据,参数3是拷贝的数量
return 0;
}
我们可以来深入的理解这个函数,来模拟实现下
memcpy函数模拟实现:
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;//记录一下目标空间的起始地址,最后返回要用
//由于void*类型无法使用我们需要把它强制类型转换为char*
while (num--)//循环num次
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
//注意这里不能写成(char*)dest++这种形式,因为强制类型转换的只是暂时的,++后还是void*类型的
//但是可以++(char*)dest这样写
}
return ret;//返回目标空间的起始地址
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 40);
return 0;
}
注意点2:
该函数要求要拷贝的空间与源数据的空间不能由重叠
举个例子:
它要求使用memmove这个函数来处理有重叠部分。其实在VS环境下,memove的功能与memcpy功能是差不多的都可以处理
但有的环境不可以,所以保险起见,当处理有重叠部分的需要使用memove函数,接下来就介绍memove函数
memmove
这个功能结构都是与memcpy是一样的,只不过更全面可以用于重叠部分的拷贝
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr, arr + 2, 20);
//要求将 3 4 5 6 7拷贝到 1 2 3 4 5内容去,
return 0;
}
那怎么实现重叠部分的拷贝的呢?
分情况讨论下,1.当目标空间在源数据空间的左边时
2.当目标空间在源数据的右边时
所以总结下,当目的空间地址小于源数据空间时,从前往后交换
当目的空间地址大于源数据空间时,从后往前交换
模拟实现下这个memmove函数吧
void* my_memmove(void* dest, void* src, size_t num)
{
void* ret = dest;//记录一下目标空间的起始地址,最后返回要用
if (dest < src)
{
//从前往后交换,跟memcpy一样直接复制过来就可以
while (num--)//循环num次
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
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, 20);
//要求将 1 2 3 4 5拷贝到 3 4 5 6 7内容去,
return 0;
}
memcmp
返回值:
例子:
int main()
{
int arr1[] = { 1,2,3 };
int arr2[] = { 1,2,4 };
int ret=memcmp(arr1, arr2, 8);//前两个都一样所以最后结果应该为0
printf("%d", ret);
return 0;
}
int main()
{
int arr1[] = { 1,2,3 };
int arr2[] = { 1,2,4 };
int ret=memcmp(arr1, arr2, 12);//前两个都一样,但第三个不同进行比较,3小于4,所以最好结果为<0
printf("%d", ret);
return 0;
}