C语言字符函数和字符串函数+内存操作函数
目录
本章重点
0、前言
1、函数介绍
1.1strlen
1.1.1strlen使用
1.1.2strlen的模拟实现
1.1.3 assert介绍
1.2strcpy
1.2.1strcpy的使用
1.2.2strcpy的模拟实现
1.3strcat
1.3.1strcat的使用
1.3.2strcat的模拟实现
1.4strcmp
1.1.1strcmp的使用
1.4.2strcmp的模拟实现
1.5strncpy
1.5.1strncpy的使用
1.5.2strncpy的模拟实现
1.6strncat
1.6.1strncat的使用
1.6.2strncat的模拟实现
1.7strncmp
1.7.1strncmp的使用
1.7.2strncmp的模拟实现
1.8strstr
1.8.1strstr的使用
1.8.2strstr的模拟实现
1.9strtok
1.9strtok的使用
1.10strerror
1.10.1strerror使用
1.11字符分类函数
1.12字符转换函数
1.13memcpy
1.13.1memcpy的使用
1.13.2momcpy的模拟实现
1.14mommove
1.14.1memmove的使用
1.14.2memmove的模拟实现
1.15memcmp
1.15.1memcmp的使用
1.15.2memcmp的模拟实现
1.16memset
本章重点
重点介绍处理字符和字符串的库函数的使用和注意事项
- 求字符串长度
- strlen
- 长度不受限制的字符串函数
- strcpy
- strcat
- strcmp
- 长度受限制的字符串函数介绍
- strncpy
- strncat
- strncmp
- 字符串查找
- strstr
- strtok
- 错误信息报告
- strerror
- 字符操作
- 内存操作函数
- memcpy
- memmove
- memset
- memcmp
0、前言
C语言中对字符和字符串的处理很繁琐,但是C语言本身是没有字符串类型的,字符串通常在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修饰的字符串函数。
1、函数介绍
1.1strlen
1.1.1strlen使用
size_t strlen (const char * str);
- 字符串已经'\0'作为结束标志,strlen函数返回的是在字符串中‘\0’前面出现的字符个数(不包含‘\0’)。
- 参数指向的字符串必须要以‘\0’结束。
- 注意函数的返回为 size_t( unsigned int),是无符号的(易错) 【因为是无符号的所以相减不会出现负值,不能通过减法来判断两个字符串长度的大小】。
- 学会strlen函数的模拟实现
注:如果所定义的字符串中没有放 \0 出现的是随机值。所以在传字符串时候一定要有‘\0’。
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
int main() {char arr[] = "abcdef";//abcdef\0int len = strlen(arr);printf("%d\0 ", len);return 0;
}
注意:下面比较两字符字符串长度大小的方法是错误的。
解析:很明显可以看出arr字符串的长度为6,arr1字符串的长度为3。3 - 6 = -3 < 0,应该打印arr1 < arr。但是strlen返回size_t (unsigned int)类型,无符号整形不会出现负数,所以很好理解这种方法为何出错啦。接下来进行改正。
1.1.2strlen的模拟实现
方法一:计数器方法
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
#include<assert.h>
size_t my_strlen(char* str) {assert(str);//size_t count = 0;while (*str != '\0') {count += 1;str++;}return count;
}
int main() {char arr[] = "abcdef";size_t n = my_strlen(arr);printf("%d \n", n);return 0;
}
方法二:指针 - 指针
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
#include<assert.h>
size_t my_strlen(char* str) {assert(str);//char* p = str;while (*str != '\0') {str++;}return str - p;
}
int main() {char arr[] = "abcdef";size_t n = my_strlen(arr);printf("%d \n", n);return 0;
}
方法三:递归的方法
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
#include<assert.h>
size_t my_strlen(char* str) {assert(str);//if (*str != '\0') {return my_strlen(str += 1) + 1;}return 0;
}
int main() {char arr[] = "abcdef";size_t n = my_strlen(arr);printf("%d \n", n);return 0;
}
1.1.3 assert介绍
上面代码使用了assert下面简单介绍一下
assert
是一个宏,用于在运行时检查一个条件是否为真,如果条件不满足,则运行时将终止程序的执行并输出一条错误信息。
1.2strcpy
1.2.1strcpy的使用
char* strcpy(char* destination【目标空间】, const char* source【原字符串】);
- 原字符串必须以‘\0’结束。
- 会将原字符串中的‘\0’拷贝到目标空间。
- 目标空间必须足够大,以确保能存放原字符串。
- 目标空间必须可变。(变量,比如常量字符串不可改。)
- 学会模拟实现。
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
#include<assert.h>
int main() {char name[20] = { 0 };char arr[] = "zhangsan";strcpy(name, arr);//name = arr;这种写法是错误的,arr数组名是地址,地址是一个常量值,不能被赋值printf("%s", name);return 0;
}
1.2.2strcpy的模拟实现
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
#include<assert.h>
char* my_strcpy(char* dest, char* src) {assert(dest && src);char* p = dest;while (*dest++ = *src++);//简化版本return p;
}
int main(){char name[20] = { 0 };char arr[] = "zhangsan";char*p = my_strcpy(name, arr); // 把arr复制到name中printf("%s\n", p);return 0;
}
1.3strcat
1.3.1strcat的使用
char* strcat(char* destination【目标空间】, const char* source【原字符串】);
- 原字符串必须以‘\0’结束。
- 目标空间必须足够的大,能容纳下字符串的内容。
- 目标空间必须可修改。
- 字符串自己给自己追加,如何?【死循环】
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
int main(){char arr[20] = "hello ";strcat(arr, "world");printf("%s\n", arr);
}
1.3.2strcat的模拟实现
#include<stdio.h>
#include<string.h> //strlen函数的头文件。
#include<assert.h>
char* my_strcat(char* dest, const char* src) {assert(dest && src);char* p = dest;while (*dest != '\0') {dest++;}while (*dest++ = *src++);return p;
}
int main(){char arr[20] = "hello ";char* ans = my_strcat(arr, "world");printf("%s\n", ans);
}
1.4strcmp
1.1.1strcmp的使用
int strcmp(conts char* str1, conts char* str2);
注意:这里比较的是两个字符串,不是比较两个字符串的长度
#include<stdio.h>
#include<string.h> //strcmp函数的头文件。
#include<assert.h>
int main(){char arr1[] = "abdc";char arr2[] = "abcdef";int ant = strcmp(arr1, arr2);if (ant > 0) {printf("arr1 > arr2");}else if (ant == 0) {printf("arr1 == arr2");}else {printf("arr1 < arr2");}
}
1.4.2strcmp的模拟实现
#include<stdio.h>
#include<string.h> //strcmp函数的头文件。
#include<assert.h>
int my_strcmp(const char* str1, const char* str2) {assert(str1 && str2);while (*str1 == *str2) {if (*str1 == '\0') {return 0;}str1++;str2++;}return *str1 - *str2;
}
int main() {char arr1[] = "abcdef";char arr2[] = "abcdef";int ans = my_strcmp(arr1, arr2);if (ans > 0) {printf("arr1 > arr2\n");}else if (ans == 0) {printf("arr1 == arr2\n");}else {printf("arr1 < arr2\n");}return 0;
}
1.5strncpy
1.5.1strncpy的使用
char* strncpy(char* destination【目标空间】, const char* source【原字符串】, size_t num);
只拷贝num个字符。
#include<stdio.h>
#include<string.h> //strncpy函数的头文件。
#include<assert.h>
int main() {char arr1[] = "aaaaaaa";//共7个字符char arr2[] = "bbbbbbbbb";//共9个字符strncpy(arr1, arr2, 5);printf("%s\n", arr1);return 0;
}
1.5.2strncpy的模拟实现
代码1:
#include<stdio.h>
#include<string.h> //strncpy函数的头文件。
#include<assert.h>
char* my_strncpy(char* str1, const char* str2, int num) {assert(str1 && str2);char* p = str1;while (num > 0) {if (*str2 == '\0') {*str1 = '\0';str1++;}else {*str1 = *str2;str1++;str2++;}num--;}return p;
}
int main() {char arr1[] = "aaaaaaa";//共7个字符char arr2[] = "bbb";//共9个字符int n = 0;scanf("%d", &n);char* ant = my_strncpy(arr1, arr2, n);printf("%s\n", ant);/*int i = 0;for (i = 0; i < 7; i++) {if (*ant == '\0')printf("*");elseprintf("%c", *ant);ant++;}*/return 0;
}
代码2:
#include<stdio.h>
#include<string.h> //strncpy函数的头文件。
#include<assert.h>
char* my_strncpy(char* str1, const char* str2, int sum) {assert(str1 && str2);char* p = str1;while (*str2 != '\0' && (*str1++ = *str2++) && (sum--));while (sum > 0) {*(++str1) = '\0';sum--;}return p;
}
int main() {char arr1[20] = "abc";char arr2[] = "dd";char* ans = my_strncpy(arr1,arr2,10);printf("%s\n", ans);return 0;
}
1.6strncat
1.6.1strncat的使用
char* strncat(char* destination【目标空间】, const char* source【原字符串】,size_t sum);
追加在字符串后面追加sum个字符。
- 目标空间要足够大。
#include<stdio.h>
#include<string.h> //strncat函数的头文件。
#include<assert.h>
int main() {char arr1[20] = "abc";char arr2[] = "ddddd";strncat(arr1,arr2,3);printf("%s\n", arr1);return 0;
}
如果所追加的字符个数大于所追加的字符长度,则只是把所追加的字符串追加完并加’\0’。
比如:char arr[20] = "ab"; strncat(arr, "ddd", 6);则我们知道所追加的字符串只有3个字符要追加的个数为6个。那么arr会变成什么?答案abddd\0
1.6.2strncat的模拟实现
#include<stdio.h>
#include<string.h> //strncat函数的头文件。
#include<assert.h>
char* my_strncat(char* str1, const char* str2, int sum) {char* p = str1;while (*str1 != '\0') {str1++;}while (sum > 0 && (*str1++ = *str2++)) {//*str1++ = *str2++;sum--;}return p;}
int main() {char arr1[20] = "abc";char arr2[] = "ddddd";char* ans = my_strncat(arr1,arr2,10);printf("%s\n", ans);return 0;
}
1.7strncmp
1.7.1strncmp的使用
int strncmp(conts char* str1, conts char* str2, size_t num);
#include<stdio.h>
#include<string.h> //strncmp函数的头文件。
#include<assert.h>
int main() {char arr1[20] = "abcdef";char arr2[20] = "abc";int ans = strncmp(arr1,arr2,3);if (ans == 0) {printf("==\n");}else if (ans > 0) {printf(">\n");}else {printf("<\n");}return 0;
}
1.7.2strncmp的模拟实现
#include<stdio.h>
#include<string.h> //strncmp函数的头文件。
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, int sum) {assert(str1 && str2);while (sum > 0) {sum--;if (*str1 == *str2) {str1++;str2++;}else if ((*str1 == '\0') || (*str2 == '\0')) {return 0;}else {break;}}if ((*str1 == '\0') || (*str2 == '\0')) {return 0;}else {return *str1 - *str2;}
}
int main() {char arr1[20] = "abcdff";char arr2[20] = "abcd";int ans = my_strncmp(arr1,arr2,4);if (ans == 0) {printf("==\n");}else if (ans > 0) {printf(">\n");}else {printf("<\n");}return 0;
}
1.8strstr
1.8.1strstr的使用
在一个字符串中找子字符串。
const char* strstr(const char* str1, const char* str2);
如果找到返回子串在主串的地址,如果找不到返回空(NULL)。
#include<stdio.h>
#include<string.h> //strstr函数的头文件。
#include<assert.h>
int main() {char arr1[20] = "zzabcdff";char arr2[20] = "abcd";char* p = strstr(arr1, arr2); //在arr1中找arr2if (p == NULL) {printf("子串没找到\n");}else {printf("%s\n", p);}return 0;
}
1.8.2strstr的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h> //strstr函数的头文件。
#include<assert.h>
const char* my_strstr(const char* str1, const char* str2) {assert(str1 && str2);const char* s1 = str1;const char* s2 = str2;const char* p = str1;while (*s1 != '\0') {while (*s1 != '\0' && *s2!= '\0' && * s1 == *s2) { //字符逐个比较s1++;s2++;}if (*s2 == '\0') { //如果s2子串走到了\0 说明前面的字符匹配成功。return p;}//如果没有匹配成功继续从下一个字符下一个匹配p++;s1 = p;s2 = str2;}return NULL; //如果循环结束说明没有匹配成功,返回NULL
}
int main() {const char arr1[20] = "zzabbbcdff";const char arr2[20] = "ff";const char* p = my_strstr(arr1, arr2); //在arr1中找arr2if (p == NULL) {printf("子串没找到\n");}else {printf("%s\n", p);}return 0;
}
注意:我们写的代码效率较低。KMP算法,这个算法也是在字符串中找子串,但是实现起来比较复杂难以理解。
1.9strtok
1.9strtok的使用
char* strtok (char * str, const chtr * sep);
- sep参数是个字符串,定义了用作分隔符的字符集合。
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中分隔符分割符的标记。
- strtok函数找到str中的下一个标记,并将其用\0结尾【分隔符被改为 \0 】,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多标记,则返回NULL指针。
-
#include<stdio.h> #include<string.h> //strtok函数的头文件。 #include<assert.h> int main() {const char arr1[20] = "@.";char arr2[20] = "ff.gg@aa";char* ant = strtok(arr2, arr1);printf("%s\n", ant);ant = strtok(NULL, arr1);printf("%s\n", ant);ant = strtok(NULL, arr1);printf("%s\n", ant);return 0; }
有时候我们并不知道字符串被分隔符分割几次,所以我们并不知道调用多少次strtok函数,所以下面的技巧很好的解决了这个问题。
#include<stdio.h>
#include<string.h> //strtok函数的头文件。
#include<assert.h>
int main() {const char arr1[20] = "@.";char arr2[20] = "ff.gg@aa";char* ret = NULL;//利用for的特点for (ret = strtok(arr2, arr1);ret != NULL; ret = strtok(NULL, arr1)) {printf("%s\n", ret);}return 0;
}
1.10strerror
1.10.1strerror使用
#include<errno.h> //头文件。
char * strerror(int errnum);
返回错误码,所对应的错误信息。
C语言的库函数,在执行失败的时候,都会设置错误码; 0 1 2 等每个数字对应不同的错误情况。
#include<stdio.h>
#include<string.h> //strtok函数的头文件。
#include<errno.h> //strerror()头文件
int main() {FILE* pf = fopen("test.txt", "r");if (pf == NULL) { //如果为空说明打开失败printf("%s\n", strerror(errno)); //errno—C语言设置的一个全局的错误码存放的变量}return 0;
}
1.11字符分类函数
上面的函数用法是一模一样的。都需要头文件这里就不挨个介绍啦。
1.12字符转换函数
字符大小写转换,tolower将大写转换小写。toupper转换为大写。
1.13memcpy
1.13.1memcpy的使用
1.13.2momcpy的模拟实现
#include<stdio.h>
#include<string.h> //memcpy函数的头文件。
#include<assert.h>
void * my_memcpy(void* dest, const void* src,size_t num) {assert(dest && src);void* p = dest; //保留首地址,方便返回。while(num > 0){*(char*)dest = *(char*)src; //一个字节一个字节的拷贝dest = (char*)dest + 1;src = (char*)src + 1;num -= 1;}return p;
}
int main() {int arr1[5] = { 1,2,3,4,5 };int arr2[10] = {0};void* p = my_memcpy(arr2, arr1, 20); //函数的第三个字符表示拷贝的字节数。int sz = sizeof(arr2) / sizeof(arr2[2]);int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr2[i]);}printf("\n");return 0;
}
- memcmp 负则拷贝两块独立空间中的数据。
- 重叠内存的拷贝,是怎么做的呢?memmove函数。
1.14mommove
1.14.1memmove的使用
void *memmove(void *dest, const void *src, size_s count);
1.14.2memmove的模拟实现
#include<stdio.h>
#include<string.h> //memmove函数的头文件。
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num) {//函数的第三个参数表示拷贝的字节数assert(dest && src);void* p = dest;if (dest < src){while (num--) {*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{while(num--){*((char*)dest + num) = *((char*)src + num); //从后往前覆盖}}return p;
}
int main() {int arr1[20] = { 1,2,3,4,5,6,7,8,9,10 };my_memmove(arr1 + 2, arr1, 20);//打印int i = 0;for (i = 0; i < 10; i++) {printf("%d ", arr1[i]);}printf("\n");return 0;
}
1.15memcmp
1.15.1memcmp的使用
比较两块内存中的数据是否相同。
#include<stdio.h>
#include<string.h> //memmove函数的头文件。
#include<assert.h>
int main() {int arr1[20] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[] = { 1, 2, 3, 5 };int tmp = memcmp(arr1, arr2, 16);//第三个参数单位是字节if (tmp > 0) {printf("arr1 > arr2\n");}else if (tmp < 0) {printf("arr1 < arr2\n");}else {printf("arr1 == arr2\n");}return 0;
}
1.15.2memcmp的模拟实现
#include<stdio.h>
#include<string.h> //memcmp函数的头文件。
#include<assert.h>
int my_memcmp(const void* ptr1, const void* ptr2, size_t num) {assert(ptr1 && ptr2);while(num--){if (*((char*)ptr1) == *((char*)ptr2)) {ptr1 = (char*)ptr1 + 1;ptr2 = (char*)ptr2 + 1;}else {return *((char*)ptr1) - *((char*)ptr2);}}return 0;
}
int main() {int arr1[20] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[] = { 1, 2, 3 };int tmp = my_memcmp(arr1, arr2, 9);//第三个参数单位是字节if (tmp > 0) {printf("arr1 > arr2\n");}else if (tmp < 0) {printf("arr1 < arr2\n");}else {printf("arr1 == arr2\n");}return 0;
}
1.16memset
void* memset(void * ptr, int value, size_t num);
ptr设置填充空间的起始地址。
value设置填充的值。
num设置填充的字节数。
#include<stdio.h>
#include<string.h> //memset函数的头文件。
int main() {char arr1[20] = "hello world";memset(arr1, 'x', 5);//第三个参数单位是字节printf("%s", arr1);return 0;
}
memset在设置填充时候是一个字节一个字节设置的。