目录
[1.1 strlen](#1.1 strlen)
[1.2 strcpy](#1.2 strcpy)
[1.3 strcat](#1.3 strcat)
[1.4 strcmp](#1.4 strcmp)
[1.5 strncpy](#1.5 strncpy)
[1.6 strncat](#1.6 strncat)
[1.7 strncmp](#1.7 strncmp)
[1.8 strstr](#1.8 strstr)
[1.9 strtok](#1.9 strtok)
[1.10 strerror](#1.10 strerror)
[1.11 memcpy](#1.11 memcpy)
[1.12 memmove](#1.12 memmove)
[1.13 memset](#1.13 memset)
[1.14 memcmp](#1.14 memcmp)
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数。
1.函数介绍
字符串函数:
1.1 strlen

字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
strlen函数参数实际上是字符串首元素的地址
cpp#include <stdio.h> #include <string.h> int main() { const char* str = "abcdef"; size_t len1 = strlen("abcdef"); size_t len2 = strlen(str); printf("%d\n", len1); printf("%d\n", len2); return 0; }
注意函数的返回值为size_t,是无符号的( 易错 )
cpp#include <stdio.h> int main() { const char* str1 = "abcdef"; const char* str2 = "bbb"; //注意:两无符号整型的差也是无符号整型 //-3补码:11111111111111111111111111111101 // 作为无符号整型时是极大的数字 // 3 - 6 =-3 if (strlen(str2) - strlen(str1) > 0) { printf("str2>str1\n"); } else { printf("srt1>str2\n"); } return 0; }
模拟实现strlen
cpp//1.计数器 size_t my_strlen(char* str) { int count = 0;//创建了临时变量记录长度 while (*str != '\0') { count++; str++; } return count; } int main() { char arr[] = "abc"; size_t len = my_strlen(arr);//注意:数组名实际上是首元素的地址 printf("%zd\n", len); return 0; } //使用assert断言以及const对计数器实现模拟strlen函数进行改造 size_t my_strlen(const char* str) {//如果不写返回类型,编译器默认返回int型 assert(str); //str不为空指针 size_t count = 0; while (*str) { count++; str++; } return count; } int main() { char arr[] = "abcdef"; printf("%zd\n", my_strlen(arr)); return 0; } //2.递归 size_t my_strlen(char* str) { if (*str == '\0') { return 0; } else return 1 + my_strlen(str + 1); //注意:不能传参str++(先使用后++);最好不要使用带有副作用的参数:++str:str本身的值也会被改变 } int main() { char arr[] = "abc"; size_t len = my_strlen(arr);//注意:数组名实际上是首元素的地址 printf("%zd\n", len); return 0; } //3.指针-指针 #include <stdio.h> size_t my_strlen(char* str) { char* start = str; while (*str != '\0') { str++; } return str - start; } int main() { char arr[] = "abcdef"; size_t len = my_strlen(arr); printf("%zd\n", len); return 0; }
1.2 strcpy

Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
源字符串必须以 '\0' 结束。
会将源字符串中的 '\0' 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串------程序员自己保证
目标空间必须可变。
cpp#include <stdio.h> #include <string.h> int main() { //目标空间必须可以修改 char* dest = "abcdef"; char* arr[] = "HELLO"; strcpy(dest, arr); //error:发生访问冲突:常量字符串不能修改 printf("%s\n", dest); return 0; }
模拟实现strcpy
cpp#include <stdio.h> #include <assert.h> char* my_strcpy(char* dest, char* src) { assert(dest != NULL); assert(src != NULL); char* ret = dest; while (*dest++ = *src++); return ret; } int main() { char arr1[10] = { 0 }; char arr2[] = "hello c"; printf("%s\n", my_strcpy(arr1, arr2)); return 0; }
1.3 strcat

strcat的使用:
- 找到arr1的末尾(\0)
- 再把arr2的内容追加到arr1后边
Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.
源字符串必须以 '\0' 结束。(保证能找到目标空间的末尾)
目标空间必须有足够的大,能容纳下源字符串的内容
cpp#include <stdio.h> #include <string.h> int main() { //char arr1[] = "abc";//err char arr1[20] = "abc"; char arr2[] = "def"; strcat(arr1, arr2); printf("%s\n",arr1); return 0; }
下标越界
目标空间必须可修改
模拟实现
cpp#include <assert.h> #include <stdio.h> //模拟实现strcat //strcat函数返回的是目标空间的起始地址 char* my_strcat(char* dest, const char* src) { char* ret = dest; assert(dest && src); //1.找到目标空间的起始地址 while (*dest != '\0') { dest++; } //2.数据追加 //相当于strcpy while (*dest++ == *src++); return ret; } int main() { char arr1[20] = "abc"; char arr2[] = "def"; my_strcat(arr1, arr2); printf("%s\n", arr1); return 0; }
字符串自己给自己追加,如何?
注意:给自己追加可能会覆盖源字符串的'\0',导致源字符串不以'\0'结尾,但是否覆盖取决于库函数的具体实现,c语言没有规定标准的库函数实现,所以尽量不要给自己追加
1.4 strcmp

- This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.
- 标准规定:
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
注意:模拟实现
cpp#include <assert.h> #include <stdio.h> //strcmp的模拟实现 int my_strcmp(const char* str1,const char* str2) {//只比较不改变 assert(str1 && str2); while (*str1 == *str2) { if (*str1 == '\0') return 0; str1++; str2++; } if (*str1 > *str2) return 1; else return -1; //VS环境下返回的数字是1,-1 } //注意:可以改成 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[] = "abe"; char arr2[] = "abd"; //使用strcmp比较字符串大小 if (my_strcmp(arr1, arr2) > 0) { printf(">\n"); } else if (my_strcmp(arr1, arr2) < 0) { printf("<\n"); } else printf("=\n"); return 0; }
长度不受限制的字符函数:strcpy,strcat,strcmp
长度受限制的字符函数: strncpy,strncat,strncmp(相对安全)
1.5 strncpy

Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.
拷贝num个字符从源字符串到目标空间(只拷贝num个字符,不会自动补充'\0')
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个
cpp#include <string.h> int main() { char arr1[20] = "xxxxxxxxxxxxx"; char arr2[] = "abc"; strncpy(arr1, arr2, 6); return 0; }
1.6 strncat

Appends the first num characters of source to destination, plus a terminating null-character.注意:加上'\0'
cpp#include <string.h> int main() { char arr1[10] = "abc\0xxxx"; char arr2[] = "eee"; strncat(arr1, arr2, 2); return 0; }
If the length of the C string in source is less than num, only the content up to the terminating null-character is copied.(如果源字符串的长度小于num,就按照源字符串的长度追加)
1.7 strncmp

比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
1.8 strstr
在字符串中找字符串

strstr会返回str1中str2第一次出现的位置,如果str1中没有str2,就返回NULL
strstr的使用:
cpp
#include <stdio.h>
#include <string.h>
int main() {
char arr1[] = "abcdefg";
char arr2[] = "def";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
printf("找不到\n");
else
printf("%s\n", ret);
return 0;
}

strstr的模拟实现:
cpp
#include <assert.h>
#include <stdio.h>
//strstr的模拟实现
const char* my_strstr(const char* str1, const char* str2) {
//创建匹配指针
const char* cp;
const char* s1;
const char* s2;
//空指针不能接引用
assert(str1 && str2);
if (*str2 == '\0') {
return str1;
}
cp = str1;
while (*cp) {
s1 = cp;
s2 = str2;
//匹配的条件
/*if (*s1 && *s2 && *s1 == *s2) {
s1++;
s2++;
}*/
//注意:判断是否匹配应该是个循环,否则无论是否匹配cp都++
// 正确的是cp++一次,判断整个字符串是否匹配,不相等时cp++,不相等是因为str2到末尾时返回cp
while (*s1 && *s2 && *s1 == *s2) {
s1++;
s2++;
}
if (*s2 == '\0') {
return cp;
}
cp++;
}
return NULL;
}
int main() {
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
const char* ret = my_strstr(arr1, arr2);
if (ret == NULL) {
printf("没有找到\n");
}
else
printf("%s\n", ret);
return 0;
}
注意:
- 创建用来遍历的指针
- 空指针不能解引用
- str2为空字符串的情况
- 指针的使命(作用)
- 循环的条件
- 判断匹配的条件(没有到字符串末尾且对应相等)
- 不匹配时的两种情况
- 函数的返回值
注意:上述strstr的模拟实现是一种暴力求解方式,更高效的算法:KMP
1.9 strtok

- sep参数是个字符串,定义了用作分隔符的字符集合(相同的字符只需要出现一次,且没有顺序)
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
cpp
#include <stdio.h>
#include <string.h>
//strtok函数
int main() {
char arr1[] = "[email protected]@hehe";
char arr2[] = "234.4342.235";//IP地址,点分十进制
char buf1[200] = { 0 };
strcpy(buf1, arr1);
//使用strtok时用的是源字符串的临时拷贝
char buf2[200] = { 0 };
strcpy(buf2, arr2);
char* p1 = "@.";
char* s = NULL;
for (s = strtok(buf1, p1); s != NULL; s = strtok(NULL, p1)) {//[email protected]@hehe
printf("%s\n", s);
}
//strtok只有第一次使用需要传非空指针,for循环的初始化部分只执行一次,将strtok的第一次调用放在初始化部分
//strtok可以调用多次,但第一次调用和之后调用不一样
char* p2 = ".";
char* s = NULL;
for (s = strtok(buf2, p2); s != NULL; s = strtok(NULL, p2)) {//234.4342.235
printf("%s\n", s);
}
return 0;
}
1.10 strerror

strerror将错误码翻译为错误信息,返回错误信息的起始地址
(无论是使用库函数,还是在软件设计过程中发生错误时,都会产生错误码)
c语言中使用库函数的时候,如果发生错误,就会将错误码放在errno的变量中,errno是一个全局变量,可以直接使用

打开文件的例子
fopen以读的形式打开文件:如果文件存在,打开成功,如果文件不存在,打开失败
cpp
#include <stdio.h>
#include <string,h>
int main() {
FILE* pf = fopen("add.txt", "r");//以读的形式打开add.txt
if (pf == NULL) {
printf("打开文件失败,原因是:%s\n", strerror(errno));
return 1;
}
else
printf("打开文件成功\n");
return 0;
}
注意:不要隐藏文件的扩展名
cpp
#include <stdio.h>
#include <string.h>
//perror 头文件stdio.h
// 直接打印错误信息所对应的错误码
// printf+strerror
// perror在打印的时候:自定义信息: xxxxxxxxxxxx(自定义信息:(空格)(错误码错对应的信息)
// 方便,但是功能单一,无法实现单纯的获得错误码,而是直接打印
int main() {
FILE* pf = fopen("add.txt", "r");//以读的形式打开add.txt
if (pf == NULL) {
perror("打开文件失败");
return 1;
}
else
printf("打开文件成功\n");
return 0;
}

字符函数:
- 字符分类函数
- 字符转换函数
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格'',换页'R',换行'N',回车'V',制表符'N'或者垂直制表符'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 | 任何可打印字符,包括图形字符和空白字符 |
[字符分类函数] |
eg:islower

cpp
#include <stdio.h>
#include <ctype.h>
//islower
int main() {
int ret = islower('a');
printf("%d\n", ret);
return 0;
}
cpp
//islower
// 之前判断小写字母的方法
int main() {
char ch = 'w';
if (ch >= 'a' && ch <= 'z') {
printf("是小写字母\n");
}
else
printf("非小写\n");
return 0;
}
可以写成:
cpp
int main() {
char ch = 'A';
if (islower(ch)) {
printf("是小写字母\n");
}
else
printf("非小写\n");
return 0;
}
字符转换函数:
int tolower ( int c );
int toupper ( int c );
注意:参数与返回值的类型都是int,字符数据可以以ASCII码值形式存入int型中,但int型不能存入char中


练习:
cpp
#include <stdio.h>
#include <ctype.h>
int main() {
//Test String.\n
char arr[] = "Test String.\n";
char* p = arr;
while (*p) {
*p = tolower(*p);
//注意:转换以后要赋值才能改变*p
//注意:tolower只能处理大写字母,小写字母(其他)不做改变
p++;
}
printf("%s", arr);
return 0;
}
内存相关函数:
1.11 memcpy

头文件:string.h
注意:参数的指针类型不为具体的类型, 可以接受任意类型
模拟实现memcpy:
cpp
#include <assert.h>
//模拟实现
//注意:memcpy可以操作多个类型,用void*接收;
void* my_memcpy(void* dest, const void* src, size_t sz) {
assert(dest && src);
void* ret = dest;
while (sz--) {
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;//强制类型转换是临时的
src = (char*)src + 1;
//注意两种写法:
//1.
/*(char*)dest++;
(char*)src++;*/
//++的优先级高于强制类型转化,void*类型的指针不能直接进行+-操作,不正确
//2.
/*++(char*)dest;
++(char*)src;*/
//c++下无法编译
}
return ret;
}
int main() {
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5 };
//把arr2中前5个整数的数据拷贝到arr1中
my_memcpy(arr1, arr2, 20);
return 0;
}
memcpy是否能拷贝数组的数据给自身?
注意:不重叠内存的拷贝,可以使用memcpy;重叠内存的拷贝,可以使用memmove
1.12 memmove

cpp
#include <string.h>
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr + 2, arr, 20);
return 0;
}

事实上,VS环境下使用memcpy也可以实现重叠部分的拷贝:
标准值规定,memcpy来实现按不重叠的内存拷贝(只需要达成60分),重叠的内存拷贝,有memmove来实现;但是,当前的VS2022这个环境中memcpy也能实现重叠,当前环境下的memcpy100分
memmove的模拟实现:

注意:如果src与dest的重叠部分在src的前面,就从前往后拷贝;如果在src的后面,就从后往前拷贝;总之,重叠部分的数据要先拷贝,否则会被覆盖

可以有两种拷贝方法:
1.dest<src:从前往后;dest>src:从后往前
2.src<dest<(char*)src+sz:从后向前;else:从前向后
这里方法一更简单,使用该方法进行模拟:
cpp
#include <assert.h>
#include <stdio.h>
//memmove的模拟实现
void* my_memmove(void* dest, const void* src, size_t sz) {
assert(dest && src);
void* ret = dest;
if (dest < src) {
int i = 0;
for (i = 0; i < sz; i++) {
*(char*)src = *(char*)dest;
src = (char*)src + 1;//注意:强制类型转换是临时的,下一次使用仍要强转
dest = (char*)dest + 1;
}
}
else {
while (sz--) {
*((char*)dest + sz) = *((char*)src + sz);
}
}
return ret;
}
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
1.13 memset

注意:memset是以字节为单位来设置内存
cpp
#include <string.h>
#include <stdio.h>
int main() {
char arr[] = "hello haodan";
memset(arr + 6, 'x', 3);
printf("%s\n", arr);
return 0;
}
cpp
#include <string.h>
int main() {
int arr[10] = { 0 };
memset(arr, 1, 40);
//数组会变成10个1吗?
//不会:memset以字节为单位设置内存,而不是以整型为单位设置
//memset不能将元素变为全1,但是可以设置为全0(每个字节都为0)
//memset更适合设置字符
return 0;
}
1.14 memcmp

以字节为单位比较
cpp
#include <string.h>
#include <stdio.h>
int main() {
int arr1[] = { 1,2,3,4,5 };
//第13个字节:04
int arr2[] = { 1,2,3,7 };
//第13个字节:07
int arr3[] = { 1,2,3,0x11223304 };
//第13个字节:04
printf("%d\n", memcmp(arr1, arr2, 13));
//返回小于0的数字
printf("%d\n", memcmp(arr1, arr3, 13));
//返回0
return 0;
}