目录
1. 字符串
字符串(character string)是一个或多个字符的序列,以'\0'结尾。
1.1 字符串常量
用双引号引起来的内容称为字符串字面量(string literal),也叫作字符串常量(string constant)。如,"abcd"。
字符串常量的末尾会自动加上一个'\0','\0'是字符串结束的标志。字符串常量中间也可以有'\0',但这样的字符串常量不是字符串。如,字符串常量"abcd"是字符串,字符串常量"abcd\0def"不是字符串,第一个'\0'的出现就代表字符串已经结束了。
#include <stdio.h>
#include <string.h>
int main()
{
printf("%d\n", sizeof("abcd"));//5
printf("%d\n", sizeof("abcd\0def"));//9
printf("%d\n", strlen("abcd"));//4
printf("%d\n", strlen("abcd\0def"));//4
return 0;
}
字符串常量不可以修改。
1.2 字符数组
可以使用字符数组存储字符串。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcd";
char arr2[] = { 'a', 'b', 'c', 'd' };//没有'\0'不构成字符串
char arr3[] = { 'a', 'b', 'c', 'd', '\0' };
printf("%s\n", arr1);//abcd
printf("%s\n", arr2);
printf("%s\n", arr3);//abcd
printf("%d\n", strlen(arr1));//4
printf("%d\n", strlen(arr2));
printf("%d\n", strlen(arr3));//4
return 0;
}
只有'\0'的字符串称为空字符串。
#include <stdio.h>
int main()
{
//空字符串
char str1[] = "";
char str2[] = { '\0' };
char str3[] = { 0 };
printf("%d\n", sizeof(str1));//1
printf("%d\n", sizeof(str2));//1
printf("%d\n", sizeof(str3));//1
return 0;
}
1.3 字符指针
可以使用字符指针存储字符串。
#include <stdio.h>
int main()
{
char* p = "abcd";//把字符串常量首字符a的地址放到指针变量p中
printf("%s\n", p);//abcd
return 0;
}
2. 转义字符
\? | 在书写连续多个问号时使用,防止他们被解析成三字母词 |
\' | 用于表示字符常量' |
\“ | 用于表示一个字符串内部的双引号 |
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符。 |
\a | 警告字符,蜂鸣 |
\b | 退格符 |
\f | 进纸符 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | 表示1~3位八进制数字在ASCII码表中对应的字符 如: \130→X |
\xdd | 表示1~2位十六进制数字在ASCII码表中对应的字符 如: \x30→0 |
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));//6
printf("%d\n", strlen("c:\test\628\test.c"));//14,\t和\62被解析成一个转义字符
//为什么转义字符是\62,不是\628?
//因为转义字符\ddd的ddd表示八进制数,八进制数都是0-7的数字,8不是八进制数字。
return 0;
}
3. 求字符串长度
3.1 strlen()函数
头文件 | string.h |
原型 | size_t strlen(const char* str); |
说明 | 获取一个字符串的长度【从字符串的开始(字符指针变量str指向的位置)到第一个\0(ASCII码值为0)的字符数(不包括\0)】 |
返回值 | 返回字符串str的长度 |
- 字符串以'\0'作为结束标志,strlen返回在字符串中'\0' 前面出现的字符个数(不包含'\0')。
- 参数指向的字符串必须要以'\0' 结束。
- strlen的返回类型为size_t,用来记录一个大小,是无符号的。
#include <stdio.h>
#include <string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2>str1\n");//str2>str1
}
else
{
printf("srt1>str2\n");
}
return 0;
}
strlen(str2) - strlen(str1)结果是size_t类型,是无符号整数,所以strlen(str2) - strlen(str1)>0。
3.1.1 模拟实现strlen()函数
3.1.1.1 计数器法
定义计数器count,循环中指针str每向后移动一次,count++,直到str指向'\0',结束循环。
#include <stdio.h>
#include <assert.h>
int my_strlen(const char* str)
{
assert(str);
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
3.1.1.2 递归
my_strlen("abc")
1+my_strlen("bc")
1+1+my_strlen("c")
1+1+1+my_strlen("")
1+1+1+0
#include <stdio.h>
#include <assert.h>
int my_strlen(const char* str)
{
assert(str);
if (*str != '\0')
return 1 + my_strlen(str+1);
else
return 0;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
3.1.1.3 指针-指针
最开始字符指针start和end指向字符串首元素,每次循环end++,直到end指向'\0',结束循环,end-start即为字符串长度。
#include <stdio.h>
#include <assert.h>
int my_strlen(const char* str)
{
assert(str);
const char* start = str;
const char* end = str;
while (*end != '\0')
{
end++;
}
return end - start;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
4. 长度不受限制的字符串函数
4.1 strcpy()函数
头文件 | string.h |
原型 | char* strcpy(char* destination, const char* source); |
说明 | 复制一个字符串【将source指向的字符串(包括'\0')复制到destination指向的字符串中】 |
返回值 | 返回目标字符串 |
- 源字符串必须以'\0'结束。
- 会将源字符串中的'\0'拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "xxxxxxxxx";
const char* str1 = "abcdef";
strcpy(arr1, str1);
printf("%s\n", arr1);//abcdef
char arr2[] = "xxxxxxxxx";
char str2[] = { 'a', 'b','c', '\0','d','e','f' };
strcpy(arr2, str2);
printf("%s\n", arr2);//abc
char arr3[] = "xx";
char str3[] = "abcdef";
strcpy(arr3, str3);//err 目标空间太小,不能存放源字符串
printf("%s\n", arr3);
char* p = "xxxxxxxxx";//字符串常量
char str4[] = "abcdef";
strcpy(p, str4);//err 字符串常量不能修改
printf("%s\n", arr3);
return 0;
}
4.1.1 模拟实现strcpy()函数
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest);
assert(src);
//assert(dest && src);
char* ret = dest;
//while (*src != '\0')
//{
// *dest = *src;
// dest++;
// src++;
//}
//*dest = *src;//复制'\0'
//代码优化如下
while (*dest++ = *src++)//复制'\0'后,表达式值为'\0'(ASCII码值为0),不进入循环
{
;//空语句
}
return ret;
}
int main()
{
char arr1[] = "xxxxxxxxx";
char arr2[] = "abcdef";
printf("%s\n", my_strcpy(arr1, arr2));//abcdef
return 0;
}
4.2 strcat()函数
头文件 | string.h |
原型 | char* strcat(char* destination, const char* source); |
说明 | 追加一个字符串【将source指向的字符串(包括'\0')追加到destination指向的字符串中,destination字符串的'\0'被source字符串的第一个字符覆盖】 |
返回值 | 返回目标字符串 |
- 源字符串必须以'\0'结束。
- 会将源字符串中的'\0'拷贝到目标空间。
- 目标空间必须足够大,以确保能追加源字符串。
- 目标空间必须可修改。
- 不能自己给自己追加。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "hello";
printf("%s\n", strcat(arr, arr));//err hello后面的'\0'被覆盖掉了,源字符串不能没有'\0'
return 0;
}
4.2.1 模拟实现strcat()函数
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char*src)
{
assert(dest);
assert(src);
//assert(dest && src);
char* ret = dest;
//找到目标字符串中的\0
while (*dest != '\0')
{
dest++;
}
//追加源字符串到目标字符串后面
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[15] = "hello ";
char arr2[] = "world";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}
4.3 strcmp()函数
头文件 | string.h |
原型 | int strcmp(const char* str1, const char* str2); |
说明 | 比较字符串【两个字符串自左向右逐个字符相比(按ASCII码值大小相比较),直到出现不同的字符或遇到'\0'为止】 |
返回值 | str1<str2,返回值<0 str1=str2,返回值=0 str1>str2,返回值>0 |
#include <stdio.h>
#include <string.h>
void my_print(int x)
{
if (x < 0)
printf("<\n");
else if (x > 0)
printf(">\n");
else
printf("=\n");
printf("%d\n", x);
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
my_print(strcmp(arr1, arr2));//< -1
char arr3[] = "abcd";
char arr4[] = "abc";
my_print(strcmp(arr3, arr4));//> 1
char arr5[] = "abc";
char arr6[] = "abc";
my_print(strcmp(arr5, arr6));//= 0
char arr7[] = { 'a', 'b', 'c' };
char arr8[] = { 'a', 'b', 'c' };
my_print(strcmp(arr7, arr8));//随机
return 0;
}
4.3.1 模拟实现strcmp()函数
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
//assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = my_strcmp(arr1, arr2);
if (ret < 0)
printf("<\n");
else if (ret > 0)
printf(">\n");
else
printf("=\n");
printf("%d\n", ret);
return 0;
}
//<
//-14 (c-q=99-113=-14)
5. 长度受限制的字符串函数
5.1 strncpy()函数
头文件 | string.h |
原型 | char* strncpy(char* destination, const char* source, size_t num); |
说明 | 复制一个字符串中的字符【将source指向的字符串中前num个字符复制到destination指向的字符串中】 |
返回值 | 返回目标字符串 |
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
- 如果num>strlen(source),那么复制完源字符串之后,在目标的后边追加'\0',直到num个。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[10] = "xxxxxxxxx";
const char* str1 = "abcdef";
strncpy(arr1, str1,6);
printf("%s\n", arr1);//abcdefxxx arr1[] = { 'a','b','c','d','e','f','x','x','x','\0' };
char arr2[10] = "xxxxxxxxx";
const char* str2 = "abcdef";
strncpy(arr2, str2, 8);
printf("%s\n", arr2);//abcdef arr2[] = { 'a','b','c','d','e','f','\0','\0','x','\0' };
return 0;
}
5.2 strncat()函数
头文件 | string.h |
原型 | char* strncat(char* destination, const char* source, size_t num); |
说明 | 追加一个字符串中的字符【将source指向的字符串中前num个字符追加到destination指向的字符串中,再加上'\0',destination字符串的'\0'被source字符串的第一个字符覆盖】 |
返回值 | 返回目标字符串 |
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
- 如果num>strlen(source),那么仅追加到'\0'为止。
- 可以自己给自己追加。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[10] = "abc\0xxxxx";
char str1[] = "def";
strncat(arr1, str1, 2);
printf("%s\n", arr1);//abcde arr1[10] = { 'a','b','c','d','e','\0','x','x','x','\0' };
char arr2[10] = "abc\0xxxxx";
char str2[] = "def";
strncat(arr2, str2, 5);
printf("%s\n", arr2);//abcdef arr2[10] = { 'a','b','c','d','e','f',\0','x','x','\0' };
char arr3[] = "abc\0xxxxx";
strncat(arr3, arr3, 3);
printf("%s\n", arr3);//abcabc arr2[10] = { 'a','b','c','a','b','c',\0','x','x','\0' };
return 0;
}
5.3 strncmp()函数
头文件 | string.h |
原型 | int strncmp(const char* str1, const char* str2, size_t num); |
说明 | 比较两个字符串中的字符【将str1中的最多num个字符与str2中的字符进行比较,自左向右逐个字符相比(按ASCII码值大小相比较),直到出现不同的字符或遇到num个字符全都比较完为止】 |
返回值 | str1子串<str2子串,返回值<0 str1子串=str2子串,返回值=0 str1子串>str2子串,返回值>0 |
#include <stdio.h>
#include <string.h>
int main()
{
int ret = strncmp("abcdef", "abc", 4);
printf("%d\n", ret);//1 "abcdef">"abc"
return 0;
}
6. 字符串查找
6.1 strstr()函数
头文件 | string.h |
原型 | char* strstr(const char* str1, const char* str2); |
说明 | 找到一个子串【判断str2指向的字符串是否是str1指向的字符串的子串】 |
返回值 | str2是str1的子串,返回在str1中str2第一次出现的位置。 str2不是str1的子串,返回NULL。 |
#include <stdio.h>
#include <string.h>
void my_printf(char* p)
{
if (p == NULL)
{
printf("不存在\n");
}
else
{
printf("%s\n", p);
}
}
int main()
{
char str[] = "abcdabcd";
char str1[] = "bcd";
my_printf(strstr(str, str1));//bcdabcd
char str2[] = "xyz";
my_printf(strstr(str, str2));//不存在
char str3[] = "";
my_printf(strstr(str, str3));//abcdabcd
return 0;
}
6.1.1 模拟实现strstr()函数
(1) 将str1的字符从左向右依次与str2的首字符进行比较,直到相等,且不等于'\0'。
(2) 当*s1==*s2,且!='\0'时,依次比较两个字符串的下一个字符,直到不相等;或直到比较到str2的'\0',此时找到子串,退出循环,返回ret。
(3) s2再次指向str2的首元素,重复(1) (2)。同时步骤(3)本身也是一个循环,每次找子串失败时, s2都会再次指向str2的首元素,再进行下一次比较。直到ret指向'\0'时,找不到子串。
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
//assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* ret = str1;
while (*ret != '\0')
{
s1 = ret;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return ret;//找到了
}
ret++;
}
return NULL;//找不到子串
}
int main()
{
char arr1[] = "abccdef";
char arr2[] = "cde";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("不存在\n");
}
else
{
printf("%s\n", ret);//cdef
}
return 0;
}
6.2 strtok()函数
头文件 | string.h |
原型 | char* strtok(char* str, const char* delim); |
说明 | 分解字符串为一组字符串 |
返回值 | 返回从str开头开始的一个个被分割的串。当str中的字符查找到末尾时,返回NULL。 |
- delim字符串包含用作分隔符的字符集合。
- str字符串包含0个或者多个由delim字符串中一个或者多个分隔符分割的标记。
- strtok找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(strtok会改变被操作的字符串,所以在使用strtok切分的字符串一般都是临时拷贝的内容并且可修改)
- 参数str不为NULL ,函数将找到str中第一个标记,strtok将保存它在字符串中的位置。
- 参数str为NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回NULL 指针。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abc@def.ghi";
char buf[200] = "";
strcpy(buf, arr);//将arr字符串拷贝一份
const char* p = "@.";
//char* str = strtok(buf, p);
//printf("%s\n", str);//abc
//
//str = strtok(NULL, p);
//printf("%s\n", str);//def
//
//str = strtok(NULL, p);
//printf("%s\n", str);//ghi
//代码优化如下
char* str = NULL;
for (str=strtok(buf, p); str!=NULL; str=strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}
7. 错误信息报告
7.1 strerror()函数
头文件 | string.h |
原型 | char* strerror(int errnum); |
说明 | 通过错误标号获取一个系统错误信息 |
返回值 | 返回一个指向错误信息字符串的指针 |
#include <stdio.h>
#include <string.h>
int main()
{
printf("%s\n", strerror(0));//No error
printf("%s\n", strerror(1));//Operation not permitted
printf("%s\n", strerror(2));//No such file or directory
printf("%s\n", strerror(3));//No such process
printf("%s\n", strerror(4));//Interrupted function call
return 0;
}
8. 字符操作
8.1 字符分类函数
头文件 | 函数 | 原型 | 如果他的参数符合下列条件就返回真 |
---|---|---|---|
ctype.h | iscntrl | int iscntrl(int c); | 任何控制字符 |
isspace | int isspace(int c); | 空白字符:空格‘’、换页‘\f’、换行'\n'、回车‘\r’、制表符'\t'、垂直制表符'\v' | |
isdigit | int isdigit(int c); | 十进制数字0~9 | |
isxdigit | int isxdigit(int c); | 十六进制数字(所有十进制数字、小写字母a~f、大写字母A~F) | |
islower | int islower(int c); | 小写字母a~z | |
isupper | int isupper(int c); | 大写字母A~Z | |
isalpha | int isalpha(int c); | 字母a~z、A~Z | |
isalnum | int isalnum(int c); | 字母或者数字,a~z、A~Z、0~9 | |
ispunct | int ispunct(int c); | 标点符号,任何不属于数字或者字母的图形字符(可打印) | |
isgraph | int isgraph(int c); | 任何图形字符 | |
isprint | int isprint(int c); | 任何可打印字符,包括图形字符和空白字符 |
8.2 字符转换函数
头文件 | 函数 | 原型 | 说明 |
---|---|---|---|
ctype.h | tolower | int tolower(int c); | 把字母字符转换成小写 非字母字符不做处理 |
toupper | int toupper(int c); | 把字母字符转换成大写 非字母字符不做处理 |
9. 内存操作函数
9.1 memcpy()函数
头文件 | string.h |
原型 | void* memcpy(void* destination, const void* source, size_t num); |
说明 | 复制内存块【从源source所指的内存地址的起始位置开始复制num个字节到目标destination所指的内存地址的起始位置中】 |
返回值 | 返回一个指向destination的指针 |
- memcpy遇到'\0'不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr1, arr2, 20);//20个字节是5个int的大小
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);//0 0 0 0 0 6 7 8 9 10
}
return 0;
}
9.1.1 模拟实现memcpy()函数
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, void* src, size_t num)
{
assert(dest);
assert(src);
//assert(dest && src);
void* ret = dest;
while(num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4 };
int arr2[3] = { 0 };
my_memcpy(arr1, arr2, 8);//8个字节是2个int的大小
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ", arr1[i]);//0 0 3 4
}
return 0;
}
9.2 memmove()函数
头文件 | string.h |
原型 | void* memmove(void* destination, const void* source, size_t num); |
说明 | 复制内存块【从源source所指的内存地址的起始位置开始复制num个字节到目标destination所指的内存地址的起始位置中】 |
返回值 | 返回一个指向destination的指针 |
与memcpy的区别是:memmove处理的源内存块和目标内存块是可以重叠的。
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr+2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);//1 2 1 2 3 4 5 8 9 10
}
return 0;
}
9.2.1 模拟实现memmove()函数
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memmove(void* dest, void* src, size_t num)
{
assert(dest);
assert(src);
//assert(dest && src);
void* ret = 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 ret;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);//20个字节是5个int的大小
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);//1 2 1 2 3 4 5 8 9 10
}
return 0;
}
9.3 memcmp()函数
头文件 | string.h |
原型 | int memcmp(const void* ptr1, const void* ptr2, size_t num); |
说明 | 比较两个内存块【从ptr1和ptr2开始的num个字节】 |
返回值 | ptr1<ptr2,返回值<0 ptr1=ptr2,返回值=0 ptr1>ptr2,返回值>0 |
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,0,5 };//01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00 ..
int arr2[] = { 1,2,3,4,0 };//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ..
int ret = memcmp(arr1, arr2, 13);
printf("%d\n", ret);//-1 arr1<arr2
return 0;
}
9.4 memset()函数
头文件 | string.h |
原型 | void* memset(void* ptr, int value, size_t num); |
说明 | 填充内存块【将ptr所指向的内存块的前num个字节设置为指定值】 |
返回值 | 返回ptr指针 |
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = { 2,3,4,5 };
memset(arr, 1, 9);
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ", arr[i]);//16843009 16843009 1 5
}
return 0;
}
观察arr的内存,前9个字节全部设置成01。