在C语言前面的内容中,我们学习到的库函数主要还是针对输入输出以及数字相关的内容,今天我们来学习一下有关于字符相关的函数,来对字符和字符串进行操作。
目录
字符分类函数
C语言中有一系列自带的函数专门处理字符分类,即处理一个字符属于什么类型的。使用这些函数需要包含头文件<ctype.h>
以下就是<ctype.h>头文件中所有函数的函数名及其返回为真的条件
函数名 | 返回真值的条件(参数满足) |
---|---|
isalnum(int c) | c 是字母(a-z /A-Z )或数字(0-9 ) |
isalpha(int c) | c 是字母(a-z /A-Z ) |
iscntrl(int c) | c 是控制字符(ASCII 0-31 或 127) |
isdigit(int c) | c 是十进制数字(0-9 ) |
isgraph(int c) | c 是除空格外的可打印字符(ASCII 33-126) |
islower(int c) | c 是小写字母(a-z ) |
isprint(int c) | c 是可打印字符(包括空格,ASCII 32-126) |
ispunct(int c) | c 是标点符号(即非空格、非字母数字的可打印字符,如 !@#$%^&*() 等) |
isspace(int c) | c 是空白字符(空格 ' ' 、换行符 '\n' 、制表符 '\t' 、回车 '\r' 、垂直制表符 '\v' 、换页符 '\f' ) |
isupper(int c) | c 是大写字母(A-Z ) |
isxdigit(int c) | c 是十六进制数字(0-9 /a-f /A-F ) |
注意:
所有函数的参数 c
应为 unsigned char
类型或 EOF
(通常定义为 -1
)。
直接传递 char
类型可能导致符号扩展问题(如 char
为负值时行为未定义)。
使用的方法十分简单,如下:
int islower(int c);
练习:挑出一段字符中所有的数字:
#include<stdio.h>
#include<ctype.h>
int main()
{
char ch[] = "ABCcdfe123###26 qwe21w";
int i = 0;
while (ch [i] != '\0')
{
if (isdigit(ch[i]) !=0)
{
printf("%c ",ch[i]);
}
i++;
}
return 0;
}
练习:将一段字符中所有的大写字母转化为小写字母:
#include<stdio.h>
#include<ctype.h>
int main()
{
char ch[] = "ABCcdfe123###26 qwe21w";
int i = 0;
while (ch[i] != '\0')
{
if (islower(ch[i]) != 0)
{
ch[i] = ch[i] - 32;
}
printf("%c", ch[i]);
i++;
}
return 0;
}
字符转换函数
C语言有两个字符转换函数
int tolower(int c)//将参数传入进去的大写字母转化为小写
int toupper(int c)//将参数传入进去的小写字母转化为大写
以下的函数到strtok函数使用均需要包含头文件<string.h>
strlen函数的使用和模拟实现
使用的基本语法:
size_t strlen(const char* str);
字符串以’\0‘为结束标志,strlen函数返回的是在字符串中'\0'前面出现的字符个数(不包括’\0‘)
但是参数str指向的字符串必须以'\0'为结束标识
注意函数的返回值为size_t,为无符号类型(注意!!!)
模拟实现
方案1(计数器):
int my_strlen(const char* str)
{
int count = 0;
assert(str!=NULL);
while (*str)
{
count++:
str++;
}
return count;
}
方案2(递归):
int my_strlen(const char* str)
{
assert(str);
if (*str=='\0')
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
方案3(指针—指针):
int my_strlen(const char* str)
{
assert(str);
char* p = str;
while (*p != '\0')
{
p++;
}
return p - str;
}
strcpy函数的使用和模拟实现
strcpy函数是字符串拷贝函数。
在实际应用中如果我们要进行字符的赋值是不能这么做的
char st1[]="hello world";
char st2[] = "abcdfe";
st1[] = st2[];//这么写是绝对错误的
因为数组本质上是指针,是常量值,指针是不能改变的。
因此我们用strcpy函数来实现字符串的复制拷贝
基本语法如下:
char* strcpy(char *destination, const char *source);
其中destination为目标字符串,source为源头字符串
源字符串必须以’\0‘结束,并且会将源字符串的'\0'拷贝至目标空间
目标空间必须足够大,并且必须可以修改
模拟实现
主函数:
int main()
{
char ch1 [] = "ABCDEFGHIKJHG";
char ch2 [] = "hello world";
my_strcpy(ch1, ch2);
printf("%s\n", ch1);
return 0;
}
方案1:
void my_strcpy(char *dest ,char *soc)
{
char* ret = *soc;
assert(dest!=NULL && soc!=NULL);
while (*soc!='\0')
{
*dest = *soc;
soc++;
dest++;
//可以简写为*dest++ = *soc++;
}
*dest = *soc;//拷贝'\0'
}
方案2:
char* my_strcpy(char *dest ,char *soc)
{
char* ret = *soc;
assert(dest!=NULL && soc!=NULL);
while (*soc!='\0')
{
*dest = *soc;
soc++;
dest++;
//可以简写为*dest++ = *soc++;
}
*dest = *soc;//拷贝'\0'
return ret;//返回原字符串的地址
}
strcat函数的使用和模拟实现
strcat函数的作用就是将两个字符串函数拼接(追加)起来。
语法上与strcpy很类似:
char* strcat(char *destination, const char *source);
其中destination为目标字符串,source为源头字符串
源字符串和目标字符串都必须有’\0‘结束,如果目标字符串没有'\0'不知道从哪里开始
目标空间必须足够大,并且必须可以修改
应用举例:
int main()
{
char ch1 [200] = "hello";
char ch2 [] = " world";
strcat(ch1, ch2);
printf("%s\n", ch1);
return 0;
}
模拟实现:
char* my_strcat(char* dest, const char* soc)
{
assert(dest!=NULL && soc!=NULL);
char* ret = dest;
//找到*dest指向的'\0'
while (*dest!='\0')
{
dest++;
}
//然后数据拷贝
while (*dest++ = *soc++)
{
;//空语句
}
return ret;//返回原字符串的地址
}
但是请注意,这么写运行不通过:
char* my_strcat(char* dest, const char* soc)
{
assert(dest!=NULL && soc!=NULL);
char* ret = dest;
//找到*dest指向的'\0'
while (*dest!='\0')
{
dest++;
}
//然后数据拷贝
while (*dest++ = *soc++)
{
;//空语句
}
return ret;//返回原字符串的地址
}
int main()
{
char ch1 [200] = "hello";
char ch2 [] = " world";
my_strcat(ch1, ch1);
printf("%s\n", ch1);
return 0;
}
因为自己给自己追加会消除掉'\0',就会造成死循环,因此程序就会崩溃。
strcmp函数的使用和模拟实现
strcmp函数是用来比较字符串大小的函数。
比较标准:比较两个字符串中对应位置上字符ASCII码值的大小(一旦有一个字符比较出来了大小就停止不再进行下一个字符的比较大小,如果二者相等就继续比较下一个位置的字符)
如果第一个字符串大于第二个字符串,则返回一个大于0的数字
如果第一个字符串等于第二个字符串,则返回0
如果第一个字符串小于第二个字符串,则返回一个小于0的数字
对比辨析:
模拟实现:
int my_strcmp(const char* str1, const char* str2)
{
int ret = 0;
assert(str1!=NULL && str2!=NULL);
while (*str1==*str2 )
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
以上的三个字符函数是长度受限制的字符串函数,接下来我们要学习长度不受限制的字符串函数
strncpy函数的使用
strncpy函数语法如下:
char* strncpy(char *destination, const char *source,size_t num);
int main()
{
char arr1[200] = "abcdfghijk";
char arr2[20] = " ";
strncpy(arr2, arr1,5);
printf("%s\n",arr2);
return 0;
}
当不够的时候,自动补齐“\0”。
strncat函数的使用
strncat函数的语法结构如下:
char* strncat(char *destination, const char *source,size_t num);
int main()
{
char arr1[200] = "abcdfghijkxxxxxxxxxxxxxx";
char arr2[20] = "xxx";
strncat(arr2, arr1,5);
printf("%s\n",arr2);
return 0;
}
strncmp函数的使用
strcnmp函数的语法如下:
char* strncmp(char *destination, const char *source,size_t num);
strstr函数的使用和模拟实现
strstr函数主要是用于在一个字符串中找另外一个子字符串是否存在的函数。
strstr函数的语法类型:
char*strstr(const char*str1,char*str2)
如果找到了,返回起始地址;如果没找到返回NULL。
应用:str1中找str2第一次出现的位置,如果找到了返回起始位置的地址。
int main()
{
char arr1[100] = "sgudusbvdugdabngfduedudhuidguadbefgbeudhidugauidufuegeuiw";
char arr2[100] = "ab";
const char *ret=strstr(arr1, arr2);
if (ret == NULL)
{
printf("找不到");
}
else
{
printf("%s\n",ret);
}
return 0;
}
模拟实现
方案1:
const char* my__strstr(const char* str1, const char* str2)
{
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = str1;
while (*cur)
{
s1=cur;
s2=str2;
while (*s1 && *s2 && !(*s1 - *s2))
{
s1++;
s2++;
}
if (!*s2)
{
return cur;
}
cur++;
}
}
方案2:
char* my_strstr(const char* str1, const char* str2)
{
char* cp = (char*)str1;
char* s1, * s2;
if (!*str2)
{
return (char*)str1;
}
while (*cp)
{
s1 = cp;
s2 = (char*)str2;
while (*s1 && *s2 && !(*s1 - *s2))
{
s1++;
s2++;
}
if (!*s2)
{
return cp;
}
cp++;
}
return NULL;
}
strtok函数的使用
strtok函数是用来提取被分隔符分隔的字符串的函数。
如邮箱“loutianlizi@qq.com”
经过strtok函数后产生三段字符串“loutianlizi”,“qq”,“com”
strtok函数的语法形式为:
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 arr[] = "hello world";
char* sep = " . ";
char* str = NULL;
for (str==strtok(arr,sep);str!=NULL;str=strtok(NULL,sep))
{
printf("%s\n",str);
}
return 0;
}
strerror函数
strerror函数的语法如下:
char*strerror(int errnum);
strerror可以将参数部分错误码对应的错误信息的字符串地址返回回来。
在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在 errno.h 这个头⽂件中说明的,C语⾔程序启动的时候就会使⽤⼀个全局的变量 errno 来记录程序的当前错误码,只不过程序启动的时候errno是 0,表⽰没有错误,当我们在使⽤标准库中的函数的时候发⽣了某种错误,就会将对应的错误码,存放在 errno 中,⽽⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。
perrior函数
perroir函数功能同上,直接打印错误信息。打印完参数部分后,再打印“:”和空格,接着打印错误信息。
今天的内容就到这里了,谢谢各位读者朋友,求一个赞谢谢。
以下的内容仅供查询参考。
strdup
函数
1. 函数定义与功能
定义
strdup函数是
通过封装内存分配和字符串复制操作,简化了字符串的动态复制流程的函数。但其使用时需注意内存管理和平台兼容性。strdup
是C标准库(非C89/C99标准,属于POSIX标准,C23引入)中的一个字符串处理函数,用于复制字符串并动态分配内存。
在需要动态创建字符串副本的场景中,合理使用strdup
可提升代码的简洁性和可维护性。
其原型为:
#include <string.h> // 需要包含的头文件
char* strdup(const char* s);
功能
-
输入:接收一个以
\0
结尾的源字符串指针s
。 -
输出:返回一个指向新分配内存的指针,该内存包含源字符串的副本。
-
内存管理:内部调用
malloc
分配内存,调用者需负责后续的free
释放。
2. 工作流程
-
验证输入:若
s
为NULL
,行为未定义(可能导致崩溃)。 -
计算长度:通过
strlen(s)
获取源字符串长度len
。 -
分配内存:调用
malloc(len + 1)
分配足够容纳字符串及其终止符\0
的内存。 -
复制内容:使用
strcpy
或memcpy
将源字符串复制到新内存。 -
返回指针:返回新内存的指针,若分配失败则返回
NULL
。
3. 手动实现(兼容性方案)
对于不支持 strdup
的环境(如旧版C标准),可自定义实现:
#include <stdlib.h>
#include <string.h>
char* my_strdup(const char* s) {
if (s == NULL) return NULL;
size_t len = strlen(s) + 1;
char* copy = (char*)malloc(len);
if (copy != NULL) {
memcpy(copy, s, len); // 比strcpy更高效
}
return copy;
}
4. 使用示例
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
const char* original = "Hello, World!";
// 使用strdup复制字符串
char* copy = strdup(original);
if (copy == NULL) {
perror("strdup失败");
return 1;
}
printf("原字符串:%s\n", original);
printf("副本字符串:%s\n", copy);
free(copy); // 必须释放内存
return 0;
}
输出:
原字符串:Hello, World!
副本字符串:Hello, World!
5. 与手动malloc+strcpy
的对比
使用strdup
char* copy = strdup(original);
if (copy) { /* ... */ }
free(copy);
手动实现
size_t len = strlen(original) + 1;
char* copy = (char*)malloc(len);
if (copy) {
strcpy(copy, original);
/* ... */
}
free(copy);
优势:
-
简化代码,减少错误(如忘记
+1
或strcpy
)。 -
提高可读性。
6. 注意事项
注意事项 | 说明 |
---|---|
内存泄漏 | 必须手动free 返回的指针,否则内存泄漏。 |
输入验证 | 若传入NULL ,可能导致崩溃(未定义行为)。 |
跨平台兼容性 | Windows下需使用_strdup (需定义_CRT_NONSTDC_NO_DEPRECATE )。 |
错误处理 | 必须检查返回值是否为NULL (内存分配失败时)。 |
性能 | 相比直接操作内存,多一次strlen 调用(但通常可忽略)。 |
7. 相关函数对比
函数 | 功能 | 内存管理 | 标准化 |
---|---|---|---|
strdup | 复制字符串并分配内存 | 需手动free | POSIX/C23 |
strndup | 复制前n 个字符并分配内存 | 需手动free | POSIX |
strcpy | 复制字符串到已有内存 | 目标内存需预先分配 | C89 |
snprintf | 格式化复制到缓冲区 | 目标内存需预先分配 | C99 |
8. 常见问题
Q1: 为什么strdup
不属于C89/C99标准?
-
历史原因:
strdup
最初是POSIX标准的扩展函数,C23才将其纳入标准库。 -
替代方案:使用
malloc
+strcpy
组合。
Q2: 如何检测strdup
是否可用?
-
编译器检查:
#ifdef _POSIX_C_SOURCE // 使用strdup #else // 使用自定义实现 #endif
Q3:
strdup
与strcpy
的性能差异? -
strdup
多一次strlen
调用和内存分配,适用于动态需求;strcpy
更高效,适用于静态内存操作。
ASCII值字符表
一、标准ASCII字符表(0-127)
十进制 | 十六进制 | 字符/符号 | 描述 | |
---|---|---|---|---|
0-31 | 0x00-0x1F | 控制字符 | 不可打印,具体功能见下方表格 | |
32 | 0x20 | 空格(Space) | ||
33 | 0x21 | ! | 感叹号 | |
34 | 0x22 | " | 双引号 | |
35 | 0x23 | # | 井号 | |
36 | 0x24 | $ | 美元符号 | |
37 | 0x25 | % | 百分号 | |
38 | 0x26 | & | 与符号 | |
39 | 0x27 | ' | 单引号 | |
40 | 0x28 | ( | 左圆括号 | |
41 | 0x29 | ) | 右圆括号 | |
42 | 0x2A | * | 星号 | |
43 | 0x2B | + | 加号 | |
44 | 0x2C | , | 逗号 | |
45 | 0x2D | - | 减号/连字符 | |
46 | 0x2E | . | 句号 | |
47 | 0x2F | / | 斜杠 | |
48-57 | 0x30-0x39 | 0 -9 | 数字字符 | |
58 | 0x3A | : | 冒号 | |
59 | 0x3B | ; | 分号 | |
60 | 0x3C | < | 小于号 | |
61 | 0x3D | = | 等号 | |
62 | 0x3E | > | 大于号 | |
63 | 0x3F | ? | 问号 | |
64 | 0x40 | @ | at符号 | |
65-90 | 0x41-0x5A | A -Z | 大写字母 | |
91 | 0x5B | [ | 左方括号 | |
92 | 0x5C | \ | 反斜杠 | |
93 | 0x5D | ] | 右方括号 | |
94 | 0x5E | ^ | 脱字符 | |
95 | 0x5F | _ | 下划线 | |
96 | 0x60 | ``` | 反引号 | |
97-122 | 0x61-0x7A | a -z | 小写字母 | |
123 | 0x7B | { | 左花括号 | |
124 | 0x7C | ` | ` | 竖线 |
125 | 0x7D | } | 右花括号 | |
126 | 0x7E | ~ | 波浪号 | |
127 | 0x7F | DEL | 删除字符(不可打印) |
二、控制字符和转义字符表(0-31 和 127)
十进制 | 十六进制 | 转义序列 | 符号 | 功能 |
---|---|---|---|---|
0 | 0x00 | \0 | NUL | 空字符(字符串结束符) |
7 | 0x07 | \a | BEL | 响铃(终端提示音) |
8 | 0x08 | \b | BS | 退格(Backspace) |
9 | 0x09 | \t | HT | 水平制表符(Tab) |
10 | 0x0A | \n | LF | 换行(Line Feed) |
11 | 0x0B | \v | VT | 垂直制表符 |
12 | 0x0C | \f | FF | 换页符(Form Feed) |
13 | 0x0D | \r | CR | 回车(Carriage Return) |
27 | 0x1B | \e | ESC | 退出键(终端控制序列起始) |
127 | 0x7F | - | DEL | 删除字符(历史用途) |
C语言官方文档ASCII 图表
下表包含所有 128 个 ASCII 十进制 (dec)、八进制 (oct)、十六进制 (hex) 和字符 (ch) 代码。
dec | oct | hex | ch | dec | oct | hex | ch | dec | oct | hex | ch | dec | oct | hex | ch | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 00 | NUL (空) | 32 | 40 | 20 | (空格) | 64 | 100 | 40 | @ | 96 | 140 | 60 | ` | |||
1 | 1 | 01 | SOH (标头的开头) | 33 | 41 | 21 | ! | 65 | 101 | 41 | A | 97 | 141 | 61 | a | |||
2 | 2 | 02 | STX (文本开头) | 34 | 42 | 22 | " | 66 | 102 | 42 | B | 98 | 142 | 62 | b | |||
3 | 3 | 03 | ETX (文末) | 35 | 43 | 23 | # | 67 | 103 | 43 | C | 99 | 143 | 63 | c | |||
4 | 4 | 04 | EOT (传输结束) | 36 | 44 | 24 | $ | 68 | 104 | 44 | D | 100 | 144 | 64 | d | |||
5 | 5 | 05 | ENQ (查询) | 37 | 45 | 25 | % | 69 | 105 | 45 | E | 101 | 145 | 65 | e | |||
6 | 6 | 06 | ACK (确认) | 38 | 46 | 26 | & | 70 | 106 | 46 | F | 102 | 146 | 66 | f | |||
7 | 7 | 07 | BEL (铃铛) | 39 | 47 | 27 | ' | 71 | 107 | 47 | G | 103 | 147 | 67 | g | |||
8 | 10 | 08 | BS (退格键) | 40 | 50 | 28 | ( | 72 | 110 | 48 | H | 104 | 150 | 68 | h | |||
9 | 11 | 09 | HT (水平选项卡) | 41 | 51 | 29 | ) | 73 | 111 | 49 | I | 105 | 151 | 69 | i | |||
10 | 12 | 0a | LF (换行 - 新行) | 42 | 52 | 2a | * | 74 | 112 | 4a | J | 106 | 152 | 6a | j | |||
11 | 13 | 0b | VT (垂直选项卡) | 43 | 53 | 2b | + | 75 | 113 | 4b | K | 107 | 153 | 6b | k | |||
12 | 14 | 0c | FF (表单馈送 - 新页面) | 44 | 54 | 2c | , | 76 | 114 | 4c | L | 108 | 154 | 6c | l | |||
13 | 15 | 0d | CR (回车) | 45 | 55 | 2d | - | 77 | 115 | 4d | M | 109 | 155 | 6d | m | |||
14 | 16 | 0e | SO (移出) | 46 | 56 | 2e | . | 78 | 116 | 4e | N | 110 | 156 | 6e | n | |||
15 | 17 | 0f | SI (Shift 键) | 47 | 57 | 2f | / | 79 | 117 | 4f | O | 111 | 157 | 6f | o | |||
16 | 20 | 10 | DLE (数据链路转义) | 48 | 60 | 30 | 0 | 80 | 120 | 50 | P | 112 | 160 | 70 | p | |||
17 | 21 | 11 | DC1 (设备控制 1) | 49 | 61 | 31 | 1 | 81 | 121 | 51 | Q | 113 | 161 | 71 | q | |||
18 | 22 | 12 | DC2 (设备控制 2) | 50 | 62 | 32 | 2 | 82 | 122 | 52 | R | 114 | 162 | 72 | r | |||
19 | 23 | 13 | DC3 (设备控制 3) | 51 | 63 | 33 | 3 | 83 | 123 | 53 | S | 115 | 163 | 73 | s | |||
20 | 24 | 14 | DC4 (设备控制 4) | 52 | 64 | 34 | 4 | 84 | 124 | 54 | T | 116 | 164 | 74 | t | |||
21 | 25 | 15 | NAK (否定承认) | 53 | 65 | 35 | 5 | 85 | 125 | 55 | U | 117 | 165 | 75 | u | |||
22 | 26 | 16 | SYN (同步空闲) | 54 | 66 | 36 | 6 | 86 | 126 | 56 | V | 118 | 166 | 76 | v | |||
23 | 27 | 17 | ETB (传输块末端) | 55 | 67 | 37 | 7 | 87 | 127 | 57 | W | 119 | 167 | 77 | w | |||
24 | 30 | 18 | CAN (取消) | 56 | 70 | 38 | 8 | 88 | 130 | 58 | X | 120 | 170 | 78 | x | |||
25 | 31 | 19 | EM (媒介结束) | 57 | 71 | 39 | 9 | 89 | 131 | 59 | Y | 121 | 171 | 79 | y | |||
26 | 32 | 1a | SUB (替补) | 58 | 72 | 3a | : | 90 | 132 | 5a | Z | 122 | 172 | 7a | z | |||
27 | 33 | 1b | ESC (逃脱) | 59 | 73 | 3b | ; | 91 | 133 | 5b | [ | 123 | 173 | 7b | { | |||
28 | 34 | 1c | FS (文件分隔符) | 60 | 74 | 3c | < | 92 | 134 | 5c | \ | 124 | 174 | 7c | | | |||
29 | 35 | 1d | GS (组分隔符) | 61 | 75 | 3d | = | 93 | 135 | 5d | ] | 125 | 175 | 7d | } | |||
30 | 36 | 1e | RS (记录分隔符) | 62 | 76 | 3e | > | 94 | 136 | 5e | ^ | 126 | 176 | 7e | ~ | |||
31 | 37 | 1f | US (单位分隔符) | 63 | 77 | 3f | ? | 95 | 137 | 5f | _ | 127 | 177 | 7f | DEL (删除 |