目录
0.前言
1. 函数介绍
1.1 strlen
1.2 strcpy
1.3 strcat
1.4 strcmp
1.5 strncpy
1.6 strncat
1.7 strncmp
1.8 strstr
1.9 strtok
1.10 strerror
1.11 字符分类函数
1.12 memcpy
1.13 memmove
1.14 memcmp
1.15 memset
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数。
求字符串长度
size_t strlen ( const char * str );
基本使用方式:
#include
#include
int main()
{
char arr[] = "hello bit";
int len = strlen(arr); //string length
printf("len = %d", len); //len = 9
return 0;
}
注:
① 字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )
② 参数指向的字符串必须要以 '\0' 结束 (不然得到的是随机值)
③ 注意函数的返回值为size_t (unsigned int) ,是无符号的,即字符串长度不可能为负( 易错点 ),表达式中包含无符号整数时,表达式的结果也是无符号整数,这样就可能出现问题
看这样一段代码:
//达不到预期效果的代码
int main()
{
char* p1 = "abc";
char* p2 = "abcdef";
if (strlen(p1) - strlen(p2) > 0)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
//执行结果为 hehe
}
//修改后的代码
int main()
{
char* p1 = "abc";
char* p2 = "abcdef";
if (strlen(p1) > strlen(p2) )
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
//执行结果为 haha
}
strlen(p1) - strlen(p2) 理论上讲应该为 -3,而当看作无符号数时,-3则成了一个很大的正数
解决办法:1.在用strlen计算之前先强制转换成 int 类型;2.不用加减,直接用> <比较大小
④ 模拟实现:
#include
#include
#include
//方法一:
//size_t 就相当于unsigned int,定义无符号整型时可以直接使用
size_t my_strlen1(const char* str) //用const修饰*str, 使该指针指向的内容不能被修改了
{
assert(str != NULL);
int count = 0;//计数器
while (*str != '\0')
{
count++;
str++;
}
return count;
}
//方法二:
//不能使用计数器的版本(递归)
//my_strlen("hello");
//1 + my_strlen(ello);
//1+1+my_strlen(llo);
//1+1+1+my_strlen(lo);
//1+1+1+1+my_strlen(o);
//5+my_strlen('\0');
//5 + 0 = 5
size_t my_strlen2(const char* str) //用const修饰*str, 使该指针指向的内容不能被修改了
{
assert(str != NULL);
if (*str != '\0')
{
return 1 + my_strlen2(str + 1);
}
else
{
return 0;
}
}
//方法三:
//指针减指针的方法
size_t my_strlen3(const char* str)
{
assert(str != NULL);
const char* start = str; //const的目的:把受保护的数据,交给一个"安全的"指针
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
char arr[] = "hello";
int len = my_strlen3(arr);
printf("len = %d", len);
return 0;
}
字符串拷贝
char* strcpy(char * destination, const char * source );
基本使用方式:
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello bit";
strcpy(arr1, arr2); //arr1为目标字符串,arr2为源字符串
return 0;
}
① 源字符串必须以 '\0' 结束。
如,上述示例中如果这样定义arr2 :
char arr2[] = {'b','i','t'};
则会出现问题,因为strcpy函数会把源字符串的 '\0' 也拷贝进入目标字符数组,如果定义时不加'\0'
系统会继续往后拷贝直到找到'\0'为止,此时不仅达不到目标效果,且还有越界访问的风险
② 会将源字符串中的 '\0' 拷贝到目标空间。
③ 目标空间必须足够大,以确保能存放源字符串。
④ 目标空间必须可变。
如果目标空间不能修改,则达不到目的,如:
char* arr1 = "xxxxxxxxxx"; //常量字符串,不能被修改
⑤ 模拟实现 :
//模拟实现strcpy
//设置char*为返回类型,便于函数的链式访问(嵌套调用)
char* my_strcpy(char* dest,const char* src)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello bit";
my_strcpy(arr1, arr2);
printf("%s\n", my_strcpy(arr1, arr2));//链式访问
return 0;
}
字符串追加/连接
char * strcat ( char * destination, const char * source );
基本使用方式:
int main()
{
char arr[20] = "hello ";
strcat(arr, "world");
printf("%s\n", arr); //hello world
return 0;
}
① 源字符串必须以 '\0' 结束。(因为strcat是从源字符串的 ‘\0’ 处开始向后追加)
② 目标空间必须有足够的大,能容纳下源字符串的内容。
③ 目标空间必须可修改。
④ 字符串自己给自己追加,会怎么样?
追加时,追加字符串的第一个字符会取代源字符串的'\0',也就是说,在进行拷贝时,本该作为结束标志的'\0'丢失了,代码会一直循环追加操作,直到超出字符数组的范围,越界访问,程序会崩溃。
⑤ 模拟实现:
//模拟实现strcat
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(*dest && *src);
//1.找目的地空间的'\0'
while (*dest) //当*dest != '\0'为真
{
dest++;
}
//2.拷贝数据
while (*dest++ = *src++) //由'w'替代arr中'\0'的位置,依次向后拷贝
{
;
}
return ret;
}
int main()
{
char arr[20] = "hello ";
printf("%s\n", my_strcat(arr, "world")); //hello world
return 0;
}
字符串比较
int strcmp ( const char * str1, const char * str2 );
① 该函数如何评判两字符串的大小?
从首字符开始比较两者对应字符ASCII码值,若不相等,则ASCII码值大的字符所在字符串大于另一字符串,若相等则比较下一个字符,若所有字符串均相等,则两个字符串相等
② 判断后的返回值是什么?
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
基本使用方式:
int main()
{
int ret1 = strcmp("abc", "awe");
int ret2 = strcmp("abc", "abc");
int ret3 = strcmp("abc", "abb");
printf("%d %d %d\n", ret1, ret2, ret3); //-1 0 1
return 0;
}
③ 模拟实现:
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
return 0;
s1++;
s2++;
}
return *s1 - *s2;
/*if (*s1 > *s2) //vs的实现方式
{
return 1;
}
else
{
return -1;
}*/
}
int main()
{
printf("%d\n", my_strcmp("abc", "abb"));
return 0;
}
以上strcpy、strcat、strcmp函数均为长度不受限制的字符串操作函数,即该函数在使用的时候不会考虑给定空间是否够大,它们只会找'\0',所以在使用时可能经常会出现警告或者错误
所以接下来介绍的是长度受限制的字符串操作函数:strncpy、strncat、strncmp
字符串拷贝
char * strncpy ( char * destination, const char * source, size_t num );
可见,相比strcpy,strncpy多了一个参数 : size_t num 用于对拷贝字符的数量进行限制:
① 拷贝num个字符从源字符串到目标空间。
② 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
例如:
int main()
{
char arr[10] = "aaaaaaaaa";
strncpy(arr, "hello", 7);
return 0;
}
调试窗口可以看到:
基本使用方式:
int main()
{
char arr1[20] = { 0 };
char arr2[20] = { 0 };
char arr3[] = "hello";
strncpy(arr1, arr3, 3);//3 表示只拷贝3个字符进arr1
strncpy(arr2, arr3, 7);//7 超出了arr3的字符串长度,则会在后面补'\0'
printf("%s\n", arr1); //hel
printf("%s\n", arr2); //hello
return 0;
}
字符串追加/连接
char * strncat ( char * destination, const char * source, size_t num );
num表示追加几个字符
① 从目标字符串的第一个'\0'处开始追加,追加num个字符
② 追加结束后会自动补'\0'
基本使用方式:
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "qwer";
strncat(arr1, arr2, 4);
printf("%s\n", arr1);
return 0;
}
练习:判断一个字符串是否可由另一个字符串左旋得到
//字符串左旋操作
void left_move(char* str, int n)
{
int i = 0;
int len = strlen(str);
char tmp = *str;
int j = 0;
for (i = 0; i < n; i++)
{
//旋转一次操作:
for (j = 0; j < len - 1; j++)
{
*(str + j) = *(str + j + 1);//从第二个字符开始前移
}
*(str + len - 1) = tmp; //第一个字符放到最后去
}
}
暴力方式 : 每一次旋转后都比较一次
//int main()
//{
// char arr1[] = "abcdef";
// char arr2[] = "cdefab";
// int i = 0;
// int len = strlen(arr1);
//
// for (i = 0; i < len; i++)
// {
// left_move(arr1, 1);
// if (strcmp(arr1, arr2) == 0)
// {
// printf("YES\n");
// break;
// }
// }
// if (i == len) //每一趟比较均不相等,则说明不是旋转得到的
// {
// printf("NO\n");
// }
//
// return 0;
//}
//方法二 :
//在arr1后再追加一次arr1 : abcdefabcdef
//那么所有左旋转的结果,都被包含在上述字符串的子串中
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "cdefab";
if (strlen(arr1) != strlen(arr2)) //如果两字符串长度都不等,则肯定无法通过旋转得到
{
printf("NO\n");
return 0;
}
strncat(arr1, arr1, 6);//arr1追加自己
char* ret = strstr(arr1, arr2); //strstr : 寻找子字符串,若找到子串则返回子串的起始位置,若不是子串则返回空指针
if (ret == NULL)
{
printf("NO\n");
}
else
{
printf("YES\n");
}
return 0;
}
字符串比较
int strncmp ( const char * str1, const char * str2, size_t num );
比较两字符串的前num个字符
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完
基本使用方式:
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
int ret = strncmp(arr1, arr2, 5);//比较前5个字符
printf("%d\n", ret); //0
return 0;
}
寻找字符串的子串
char * strstr ( const char *str1, const char * str2);
寻找子字符串,若找到子串则返回子串的起始位置,若不是子串则返回空指针
基本使用方式:
int main()
{
char arr[] = "abcdefabcdef";
char* ret = strstr(arr, "cd");
if (ret != NULL)
{
printf("%s\n", ret); //cdefabcdef
}
return 0;
}
模拟实现:
//找到子串返回字串起始位置, 找不到返回空指针
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2); //保证str1和str2不为空指针
//建两个指针,分别指向起始位置,用于保存 下一次尝试匹配的位置
const char* s1 = str1;
const char* s2 = str2;
//用于保存 : 下一次开始匹配的位置
const char* cp = str1;
if (*s2 == '\0') //strstr函数规定如果str2为空字符串,则返回str1的地址
{
return (char*) str1;
}
while (*cp) //*cp != '\0'说明字符串中还有字符没进行查找
{
//匹配查找一次的操作
s1 = cp;
s2 = str2;
while (*s1 && *s2 && *s1 == *s2) //*s1 != '\0',*s2 != '\0'且s1 == s2时向后判断
{
s1++;
s2++;
}
//之后分别处理三种跳出循环的情况
if (*s2 == '\0') //说明匹配成功了//返回此时的cp地址
{
return (char*) cp; //co是由const char*修饰的,而该函数返回类型又是char*,即把安全的指针赋给了不安全的,会报警告。所以此时强制类型转换成char*
}
cp++; //s1 != s2时的处理
}
return NULL; //s1 == '\0'说明没找到子串返回空指针
}
int main()
{
char* str1 = "abcdef";
char* str2 = "cde";
char* ret = my_strstr(str1, str2);
printf("%s\n", ret);
return 0;
}
分解字符串
char * strtok ( char * str, const char * sep );
① sep参数是个字符串,定义了用作分隔符的字符集合。
② 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
③ strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。
(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
④ strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
⑤ strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
⑥ 如果字符串中不存在更多的标记,则返回NULL 指针
int main()
{
char arr1[] = "[email protected]";
char arr2[] = "@.";
char tmp[30] = { 0 };
//由于strtok会修改原字符串,所以我们先拷贝,只对拷贝的内容进行修改
strcpy(tmp, arr1);
char* a = strtok(tmp, arr2);
//第一次调用
//由于tmp不为空指针, 则会开始向后找第一个标记'@',并将其修改为'\0'
//同时: 1. 会返回: 首字符'z'的地址 2.会记住@的位置(作为下次调用的起始位置)
printf("%s\n", a); //zqf
char* b = strtok(NULL, arr2);
//第二次调用
//同一个字符串中被保存的位置开始,查找下一个标记
//返回q的地址,记住'.'的位置
printf("%s\n", b);//XJTU
char* c = strtok(NULL, arr2);
printf("%s\n", c); //com
//第三次调用
//从被保存的位置开始,查找下一个标记,找到'\0'
//返回c的地址
//如果再调用也只能返回空指针了
//注 : 如果这样定义char arr1[] = "zqf@\0qq.com";
//找到第一个'\0'后,如果再调用strtok函数,该函数也不会继续访问内存中的剩余字符,而是返回空指针
return 0;
}
基本使用方式:
int main()
{
char arr1[] = "[email protected]";
char tmp[30] = { 0 };
strcpy(tmp, arr1);
char arr2[] = "@.";
char* p = NULL;
for (p = strtok(tmp, arr2); p != NULL; p = strtok(NULL, arr2))
{
printf("%s\n", p);
}
return 0;
}
获得错误的描述字符串
char * strerror ( int errnum );
基本使用方法:
int main()
{
//当调用库函数,发生错误的时候,就会有些错误码
//错误码会放在errno这个全局变量中(如果没有错误则errno默认为0)
//需要头文件 #include
//正确的情况:
printf("%d\n", errno); // errno 没有错误默认为0
printf("%s\n", strerror(errno)); //No error 没有错误
//错误的情况:
//文件操作
//打开文件
FILE* pf = fopen("test.tst", "r");
//打开一个文件, 传参为1.文件名, 2.打开形式
// (此处为"读"的方式打开,如果文件不存在则会打开失败,如果是以"写"的方式打开,文件不存在时会创建一个该文件)
// 当打开成功,则会返回一个有效的指针,打开失败则返回空指针
//并且用FILE定义一个结构体变量接受fopen的返回值
if (pf == NULL)
{
printf("%s\n", strerror(errno)); //No such file or directory
perror("91班打印错误信息"); //
}
else
{
printf("打开成功\n");
fclose(pf); //关掉打开的文件
pf = NULL;
}
return 0;
}
函数 | 如果他的参数符合下列条件就返回真 |
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 | 任何可打印字符,包括图形字符和空白字符 |
以tolower函数为例:
int main()
{
char ch = 'A';//65 'a'为97
//printf("%c\n", ch + 32);
//
printf("%c\n", tolower(ch));
//注意 : tolower(ch)返回值是小写字母的ASCII码值,而不是把ch从大写改变成了小写
//需要头文件 #include
//把一个字符串改成全小写
char str[] = "Test String.\n";
char c;
int i = 0;
while (str[i]) //没遇到'\0'就转换并打印
{
c = str[i];
if (isupper(c))
{
c = tolower(c);
}
putchar(c);
i++;
}
return 0;
}
内存拷贝(内存不重叠部分)
void * memcpy ( void * destination, const void * source, size_t num );
num表示需要拷贝的字节
① 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。(一个字节一个字节的拷贝过去)
② 目标空间必须足够大且可修改。
③ 源字符串不需要有 '\0' 且该函数在遇到 '\0' 的时候并不会停下来。
④ 模拟实现:
void* my_memcpy(void* dest, const void* src, size_t count)
{
void* ret = dest;
while (count--)
{
//拷贝一个字节
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char* )src + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[10] = { 0 };
//对于整型数组,肯定不能用strcpy进行拷贝 :
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 小端在内存中的存储
//strcpy遇到00就停止了
//此时则需要内存拷贝
my_memcpy(arr2, arr1, sizeof(arr1));
return 0;
}
内存拷贝(两内存块间可重叠)
void * memmove ( void* destination, const void * source, size_t num );
① 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的,如果源空间和目标空间出现重叠,就得使用memmove函数处理。
② 模拟实现:
void* my_memmove(void* dest, const void* src, size_t count)
{
void* ret = dest;
assert(dest && src);
//当两块空间未重叠的时候,怎样拷贝都无所谓
//只有空间重叠时需要考虑拷贝的方式:
if (dest < src)//当dest在src左边的时候,src应该从前向后开始拷贝
{
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else//反之,src从后向前拷贝
{
while (count--)
{
*((char*)dest + count) = *((char*)src + count);
}
}
return ret;
}
int main()
{
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//此时我想把1 2 3 4 拷贝到3 4 5 6的位置上去
my_memmove(arr1 + 2, arr1, 16);
return 0;
}
dest 在 src 左边时:从前向后拷贝
dest 在 src 右边时:从后向前拷贝
基本使用方式:
int main()
{
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//此时我想把1 2 3 4 拷贝到3 4 5 6的位置上去
memmove(arr1 + 2, arr1, 16);
return 0;
}
内存比较
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
比较从 ptr1 和 ptr2 指针开始的num个字节 ,返回类型和strcmp一个道理
基本使用方法:
int main()
{
int arr1[] = { 1, 2, 3, 4, 5};
int arr2[] = { 1, 2, 3, 3, 3};
int ret = memcmp(arr1, arr2, 20);
printf("%d\n", ret); // 1
return 0;
}
初始化函数
void * memset ( void *dest, int value, size_t num );
将某一块内存中的内容(从dest开始到dest+num的全部字节)全部设置为指定的值, 该函数通常为新申请的内存做初始化工作。
基本使用方式:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
memset(arr, 0, sizeof(arr));
return 0;
}
注意,该函数是按照给定字节进行修改,如果上述函数这样使用:
memset(arr, 1, sizeof(arr));
并不是把数组的每一个元素都置为1,而是把每一个字节修改成了 01
如果对本文涉及的代码有需要,可进入我的gitee主页进行下载学习