重点介绍处理字符和字符串的库函数的使用和注意事项
0. 前言
C语言对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在
常量字符串
中或者字符数组
中.
常量字符串
适用于那些对它不做修改的字符串函数.
1. 字符串函数
字符串函数分为长度不受限制的函数 和 长度受限制的函数;长度受限制的函数相对更加安全.
1.1 strlen
1.1.1 介绍
size_t strlen(const char* str);
返回给定字符串的长度,即首元素为
str
所指向的字符数组直到而不包含首个空字符\0
的字符数.
若
str
所指向的字符数组中无空字符,则行为未定义.
参数
str
- 指向要所求字符串的首元素地址
返回值
- 字符串
str
的长度
注意事项
- 字符串要以
\0
作为结束标志,strlen
函数返回的是首地址和\0
之间(不包括空白字符)的字符个数. - 参数指向的字符串必须要以
\0
结束 - 注意函数的返回值是
size_t
,是无符号的. (易错)
#include <stdio.h>
#include <string.h>
int main(void)
{
const char* str1 = "abcde";
const char* str2 = "abcd";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str1 < str2");
}
else
{
printf("str1 > str2");
}
return 0;
}
-
程序运行结果如下:
-
str1
的长度是5
,str2
的长度是4
,事实应该是str1 > str2
;但是两个数都是size_t
类型的,是无符号整数.两个无符号整数进行运算,得到的也是无符号整数;计算结果-1
的补码是0xFFFF
,表示无符号整数2^32 - 1
,这个数是大于0的,所以判断为真,打印str1 < str2
.
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
const char* str1 = "abcdef";
int ret = strlen(str1);
printf("%d\n", ret);
return 0;
}
程序运行结果如下:
1.1.2 模拟实现
- 循环(计数器方式)
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str);
size_t count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
- 指针-指针
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str);
char* pstr = str;
while (*pstr)
{
pstr++;
}
return pstr - str;
}
- 递归(不能创建临时变量计数器)
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str);
if (!*str)
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
1.2 strcpy
1.2.1 介绍
char* strcpy(char* destination, const char* source);
将
source
所指向的字符串拷贝到destination
指向的空间内,包括最后的空白符\0
.
若
destination
所指空间不够存储source
的内容,则行为未定义.
若拷贝重叠的字符串,则行为未定义.
参数
destination
- 指向要写入的字符数组的指针source
- 指向复制来源的空终止字节字符串的指针
返回值(返回值方便链式访问)
destination
- 指向拷贝后的字符数组的指针
注意事项
socuce
源字符串必须以空字符\0
为结尾,同时会将\0
也拷入destination
目标字符串末尾.destination
目标空间必须足够大且可变.
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[20] = { 0, };
char str2[] = "hello world";
printf("%s\n", strcpy(str1, str2));
printf("%s\n", str1);
return 0;
}
程序运行结果如下:
1.2.2 模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
1.3 strcat
1.3.1 介绍
char* strcat(char* destination, const char* source);
将
source
所指向的空终止字符字符串复制到destination
所指向的空终止字符字符串的结尾.source[0]
替换destination
的空终止字符.
若
destination
所指向的空间存不下两块空间的内容,则行为未定义.
若字符串重叠,则行为未定义.(
source[0]
将destination
的空终止字符替换了,会陷入死循环中)
参数
destination
- 指向要后附的字符数组的指针source
- 指向复制来源的空终止字节字符串的指针
返回值
destination
- 指向后附后的字符数组的指针
注意事项
source
源字符串必须以空白符\0
结尾.destination
目标空间必须足够大且可修改.
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[20] = "hello ";
char str2[20] = "world!";
printf("%s\n", strcat(str1, str2));
printf("%s\n", str1);
return 0;
}
程序运行结果如下:
1.3.2 模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//先找到dest的空终止字符位置
while (*dest)
{
dest++;
}
//后附
while (*dest++ = *src++)
{
;
}
return ret;
}
1.4 strcmp
1.4.1 介绍
int strcmp(const char* str1, const char* str2);
根据ASCII码,比较字符串
str1
和字符串str2
的大小
从两个字符串首地址的字符开始比较,直至出现
\0
或者有字符不相等
参数
str1
- 被比较的字符串1str2
- 被比较的字符串2
返回值
-
返回表明两个字符串大小关系的整数
<0 第一个不匹配的字符在 str1
中的值小于在str2
中的值0 两个字符串完全相等 <0 第一个不匹配的字符在 str1
中的值大于在str2
中的值
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
const char* str1 = "abcdef";
const char* str2 = "abce";
int ret = strcmp(str1, str2);
if (ret > 0)
{
printf("str1 > str2");
}
else if (0 == ret)
{
printf("str1 equals str2");
}
else
{
printf("str1 < str2");
}
return 0;
}
程序运行结果如下:
1.4.2 模拟实现
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (str1 && str2 &&*str1 == *str2)
{
if (!*str1)
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
1.5 strncpy(受长度限制)
1.5.1 介绍
char* strcpy(char* destination, const char* source, size_t num);
在
strcpy
的基础上加上了长度的限制,如果长度限制比源字符串的长度要长, 则添加超出长度个空字符\0
.如果长度限制比原字符串的长度要短,则只拷贝字符,不会添加\0
参数
destination
- 指向要写入的字符数组的指针source
- 指向复制来源的空终止字节字符串的指针num
- 规定要拷贝的源字符串的字符个数
返回值(返回值方便链式访问)
destination
- 指向拷贝后的字符数组的指针
注意事项
- 若
num > strlen(source)
,则末尾补上num - strlen(source)
个空字符\0
. - 若
num < strlen(source)
,则源字符串的\0
不拷入目标空间. num
需要比目标空间少一个,用来存放\0
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[20] = "hello ";
char str2[20] = "world";
printf("%s\n", strncpy(str1, str2, 2));
printf("%s\n", str1);
return 0;
}
程序运行结果如下:
1.5.2 模拟实现
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
while (num && (*dest++ = *src++))
{
num--;
}
if (num)
{
while (num--)
{
*dest++ = '\0';
}
}
return ret;
}
1.6 strncat(有长度限制)
1.6.1 介绍
char* strncat(char* destination, const char* source, size_t num);
在
strcat
的基础上,加上了长度限制.后附源字符串
的num
个字符后,并追加一个空终止字符\0
.
参数
destination
- 指向要后附的字符数组的指针source
- 指向复制来源的空终止字节字符串的指针num
- 规定要后附的源字符串的字符个数
返回值destination
- 指向后附后的字符数组的指针
注意事项
- 因为 strncat 需要在每次调用时找到
destination
的结尾,故用strncat
将多个字符串连接成一体是低效的. - 若
strlen(source) < num
,则追加\0
,直到num
个 - 后附后的长度需要比目标空间少一个,用来存放
\0
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[20] = "hello ";
char str2[20] = "world";
int num = 2;
if (num + strlen(str1) < 20)
{
printf("%s\n", strncat(str1, str2, num));
printf("%s\n", str1);
}
return 0;
}
程序运行结果如下:
1.6.2 模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
while (*dest)
{
dest++;
}
while (num && (*dest++ = *src++))
{
num--;
}
*dest = '\0';
return ret;
}
1.7 strncmp(有长度限制)
1.7.1 介绍
int strncmp(const char* str1, const char* str2, size_t num);
在
strcmp
的基础上,比较到出现到num
个字符全部比较晚,如果有字符串结束,直接结束
参数
str1
- 被比较的字符串1str2
- 被比较的字符串2num
- 要比较的字符个数
返回值
-
返回表明两个字符串大小关系的整数
<0 第一个不匹配的字符在 str1
中的值小于在str2
中的值0 两个字符串完全相等 <0 第一个不匹配的字符在 str1
中的值大于在str2
中的值
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[20] = "abcdef";
char str2[20] = "abcd";
int ret = strncmp(str1, str2, 4);
if (ret > 0)
{
printf("str1 > str2");
}
else if (0 == ret)
{
printf("str1 equals str2");
}
else
{
printf("str1 < str2");
}
return 0;
}
程序运行结果如下:
1.7.2 模拟实现
#include <stdio.h>
#include <assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
while (--num && (*str1 == *str2))
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
1.8 strstr
1.8.1 介绍
char* strstr(const char* str, const char* target);
查找
str2
在str1
的首次出现位置,若不存在,则返回一个空指针NULL
参数
str
- 指向要检验的空终止字节字符串的指针
target
- 指向要查找的空终止字节字符串的指针
返回值
指向str
中寻获子串的首个字符的指针,或若找不到该字符则为空指针.若 target
指向空字符串,则返回 str
.
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "This is a simple string";
char* pch;
pch = strstr(str, "simple");
strncpy(pch, "sample", 6);
puts(str);
return 0;
}
程序运行结果如下:
1.8.2 模拟实现
这里给出暴力求解的方法思路
- 定义指针变量
pstr
,ptar
用来遍历两个字符串,定义sp
用来存放每次第一个字符匹配时的位置,方便返回继续查找或者返回结果 - 在没有遍历到
str
空白终止符之前,依次判断两串第一个字符是否一致,若一致,用sp
记录这个位置,同时继续判断接下来的字符 - 若遇到不一致的字符,返回到
sp
,继续2中的操作;若遇到target
的空白终止符,返回sp
,代表已经找到.
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str, const char* target)
{
assert(str && target);
char* pstr = str;
char* ptar = target;
char* sp = pstr;
while (*sp)
{
//回到上次位置下一个
pstr = sp;
ptar = target;
while (*pstr == *ptar)
{
pstr++;
ptar++;
if (*ptar == '\0')
{
return sp;
}
}
sp++;
}
return NULL;
}
1.9 strok
1.9.1 介绍
char* strtok (char* str, const char* sep);
寻找
str
所指向的空终止字节的字符串中的下一个记号,由sep
所指向的空终止字节字符串鉴别分割字符.
此函数被设计调用多次,以从同一个字符串获取相应的字符串
- 若
str
不是空指针,则当作strok
对str
的首次调用- 若找不到
sep
对应的字符,则表示str
中没有,则函数返回空指针 - 若找到了
sep
对应的字符- 将该位置的字符改为
\0
,并返回str
- 将该位置下一位置存入程序中的静态变量中
- 将该位置的字符改为
- 若找不到
- 若
str
是空指针,则把静态变量中存储的位置当作str
进行上述操作
参数
str
- 指向要记号化的空终止字节字符串的指针sep
- 指向标识分隔符的空终止字节字符串的指针
返回值
- 指向下个记号起始的指针,或若无更多记号则为空指针.
注意事项
-
此函数是破坏性的:它写入
'\0'
字符于字符串str
的元素.特别是,字符串字面量不能用作 strtok 的首参数.可以先将要读取的字符串拷贝一份,让函数对拷贝的字符串进行处理. -
每次对 strtok 的调用都会修改静态对象.
使用示例
#include <stdio.h>
#include <string.h>
int main(void)
{
char* p = "hello@1212.com";
char str[30];
char sep[] = { '@', "." };
char* ret = NULL;
strcpy(str, p); //将数据拷贝一份, 处理str数组中的内容
for (ret = strtok(str, sep); ret != NULL; ret = strtok(NULL, sep))
{
puts(ret);
}
return 0;
}
程序运行结果如下:
1.10 strerror
1.10.1 介绍
char* strerror(int errnum);
返回错误码,所对应的错误信息
在程序中有一个全局变量errno
用来存放当前程序的错误码,通过调用strerror
函数,传入errno
,能得到错误码对应字符串的首字符地址.
#include <stdio.h>
#include <string.h>
#include <errno.h> //必须包含的头文件
int main(void)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
程序运行如下:
可以看到不同errno
对应的程序错误提示.
例如打开文件,没有对应文件也会提示错误.
#include <stdio.h>
#include <string.h>
#include <errno.h> //必须包含的头文件
int main(void)
{
FILE* pFile = fopen("1.txt", "r");
if (pFile == NULL)
{
printf("错误为: %s\n", strerror(errno));
}
free(pFile);
return 0;
}
程序运行结果如下:
程序运行出错,可以通过这个函数来简单判断出现了什么错误,以便进行进一步的分析和解决
2. 字符函数
头文件<ctype.h>
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母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 | 任何可打印字符,包括图形字符和空白字符 |
toupper | 将字母转换为大写 |
tolower | 将字母转换为小写 |
3. 内存函数
3.1 memcpy
3.1.1 介绍
void* memcpy(void char* destination, const char* source, size_t num);
从
source
所指向的内存位置复制num
个字符到destination
所指向的内存位置
若对象重叠,则行为未定义
若destintaton
或source
为非法或空指针,则行为未定义
参数
destintaton
- 指向要复制的对象的指针source
- 指向复制来源对象的指针num
- 复制的字节数
返回值
destintaton
注意事项
- C语言标准规定
memcpy
只需满足未重叠的空间的拷贝,但是在具体编译器实现(例如VS
)可能也满足了重叠空间的拷贝 - 相比
memmove
,memcpy
在拷贝未重叠空间中更加高效
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0, };
memcpy(arr2, arr1, sizeof(int) * 10);
int i = 0;
for (i = 0; i < 20; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
程序运行结果如下:
3.1.2 模拟实现
#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* target, size_t num)
{
void* ret = dest;
assert(dest && target);
while (num--)
{
*(char*)dest = *(char*)target;
dest = (char*)dest + 1;
target = (char*)target + 1;
}
return ret;
}
3.2 memmove
3.2.1 介绍
void* memmove(void char* destination, const char* source, size_t num);
和
memcpy
的差别就是,memmove
是可以处理重叠的内存空间的.
参数
destintaton
- 指向要复制的对象的指针source
- 指向复制来源对象的指针num
- 复制的字节数
返回值
destintaton
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[20] = "1234567890";
memmove(str + 2, str + 5, sizeof(char) * 5); //将第六个开始的五个字符,移动到第三个元素的位置上
puts(str);
return 0;
}
程序运行结果如下:
3.2.2 模拟实现
#include <stdio.h>
#include <assert.h>
char* my_memmove(char* dest, const char* target, size_t num)
{
char* ret = dest;
assert(dest && target);
if (dest < target)//前->后
{
while (num--)
{
*(char*)dest = *(char*)target;
dest = (char*)dest + 1;
target = (char*)target + 1;
}
}
else //后->前
{
while (num--)
{
*((char*)dest + num) = *((char*)target + num);
dest = (char*)dest + 1;
target = (char*)dest + 1;
}
}
}
3.3 memset
3.3.1 介绍
void* memset(void* destination, int value, size_t num);
将
destination
指向的前num
个字节空间填充value
.
参数
destination
- 指向要填充的对象的指针value
- 填充字节num
- 要填充的字节数
返回值
destination
注意事项
- 填充的是1字节的数据,如果
value == 1
, 那么指定空间会存放01
而不是11
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "xxxxxxxxxx";
memset(str, 'y', 5);
puts(str);
return 0;
}
程序运行结果如下:
3.3.2 模拟实现
#include <stdio.h>
#include <assert.h>
void* my_memset(void* dest, int value, size_t num)
{
assert(dest);
void* ret = dest;
while (num--)
{
*(char*)dest = value;
dest = (char*)dest + 1;
}
return ret;
}
3.4 memcmp
int memcmp(const void* ptr1, const void* ptr2, size_t num);
比较
ptr1
和ptr2
开始指向的num
个字节的值
参数
ptr1
- 指向被比较的空间1ptr2
- 指向被比较的空间2- `num - 比较的字节数
返回值
-
返回表明两个空间字节内容大小关系的整数
<0 第一个不匹配的字节空间在 ptr1
中的值小于在ptr2
中的值0 两块空间完全相等 <0 第一个不匹配的空间在 ptr1
中的值大于在ptr2
中的值
使用实例
#include <stdio.h>
#include <string.h>
int main(void)
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 1,2,155,0,0 };
int ret = memcmp(arr1, arr2, sizeof(int) * 3);
if (ret > 0)
{
puts(">");
}
else if (ret < 0)
{
puts("<");
}
else
{
puts("==");
}
return 0;
}
程序运行结果如下:
本章完.