C语言字符函数和字符串函数+内存运行函数
目录
本章重点
重点介绍处理字符和字符串的库函数的使用和注意事项
- 求字符串长度
- 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
#include //strlen函数的头文件。
int main() {
char arr[] = "abcdef";//abcdef\0
int 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
#include //strlen函数的头文件。
#include
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
#include //strlen函数的头文件。
#include
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
#include //strlen函数的头文件。
#include
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
#include //strlen函数的头文件。
#include
int main() {
char name[20] = { 0 };
char arr[] = "zhangsan";
strcpy(name, arr);
//name = arr;这种写法是错误的,arr数组名是地址,地址是一个常量值,不能被赋值
printf("%s", name);
return 0;
}
1.2.2strcpy的模拟实现
#include
#include //strlen函数的头文件。
#include
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
#include //strlen函数的头文件。
int main(){
char arr[20] = "hello ";
strcat(arr, "world");
printf("%s\n", arr);
}
1.3.2strcat的模拟实现
#include
#include //strlen函数的头文件。
#include
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
#include //strcmp函数的头文件。
#include
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
#include //strcmp函数的头文件。
#include
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
#include //strncpy函数的头文件。
#include
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
#include //strncpy函数的头文件。
#include
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("*");
else
printf("%c", *ant);
ant++;
}*/
return 0;
}
代码2:
#include
#include //strncpy函数的头文件。
#include
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
#include //strncat函数的头文件。
#include
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
#include //strncat函数的头文件。
#include
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
#include //strncmp函数的头文件。
#include
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
#include //strncmp函数的头文件。
#include
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
#include //strstr函数的头文件。
#include
int main() {
char arr1[20] = "zzabcdff";
char arr2[20] = "abcd";
char* p = strstr(arr1, arr2); //在arr1中找arr2
if (p == NULL) {
printf("子串没找到\n");
}
else {
printf("%s\n", p);
}
return 0;
}
1.8.2strstr的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include
#include //strstr函数的头文件。
#include
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中找arr2
if (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#include //strtok函数的头文件。 #include 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
#include //strtok函数的头文件。
#include
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
#include //strtok函数的头文件。
#include //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
#include //memcpy函数的头文件。
#include
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
#include //memmove函数的头文件。
#include
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
#include //memmove函数的头文件。
#include
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
#include //memcmp函数的头文件。
#include
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
#include //memset函数的头文件。
int main() {
char arr1[20] = "hello world";
memset(arr1, 'x', 5);//第三个参数单位是字节
printf("%s", arr1);
return 0;
}
memset在设置填充时候是一个字节一个字节设置的。
浙公网安备 33010602011771号