C语言基础之【指针】(下)
往期《C语言基础系列》回顾:
链接:
C语言基础之【C语言概述】
C语言基础之【数据类型】(上)
C语言基础之【数据类型】(下)
C语言基础之【运算符与表达式】
C语言基础之【程序流程结构】
C语言基础之【数组和字符串】(上)
C语言基础之【数组和字符串】(下)
C语言基础之【函数】
C语言基础之【指针】(上)
C语言基础之【指针】(中)
指针和字符串
字符指针
在C语言中,字符串是以字符数组的形式存储的,而字符指针(
char *
)可以用于操作字符串。
字符指针定义的语法 :
char *指针变量名;
字符指针初始化:
cchar *str1 = "Hello"; // 指向字符串字面量 char str2[] = "World"; // 字符数组 char *str3 = str2; // 指向字符数组
字符指针的使用:
访问字符串
cchar *str = "Hello"; //通过字符指针可以访问字符串中的字符。 printf("%c\n", *str); // 输出:H printf("%c\n", *(str + 1)); // 输出:e
遍历字符串
cchar* str = "Hello"; //通过字符指针可以遍历字符串中的所有字符。 for (int i = 0; str[i] != '\0'; i++) { printf("%c", str[i]); // 输出:Hello }
修改字符串
cchar str[] = "Hello"; char *p = str; p[0] = 'h'; // 修改字符串内容 printf("%s\n", str); // 输出:hello
如果字符指针指向的是
字符数组
,可以通过指针修改字符串的内容如果字符指针指向的是
字符串字面量
,则不能修改字符串的内容(行为未定义)
代码示例:字符数组和字符指针的区别
c#include <stdio.h> #include <stdlib.h> int main(void) { char str1[] = "hello"; // {'h', 'e', 'l', 'l', 'o', '\0'} char m[] = "hello"; char* str2 = "hello"; // "hello" 是一个字符串常量, 不能修改。 char* n = "hello"; //char* str3 = {'h','e','l','l','o','\0'} error:初始化语法错误 //str1[0] = 'R'; //str2[0] = 'R'; error printf("str1 = %p\n", str1); printf("m = %p\n", m); printf("str2 = %p\n", str2); printf("n = %p\n", n); system("pause"); return EXIT_SUCCESS; }
输出:
cstr1 = 000000B9A54FF534 m = 000000B9A54FF554 str2 = 00007FF788E7ACF0 n = 00007FF788E7ACF0
分析:
1.字符数组
str1
和m
cchar str1[] = "hello"; char m[] = "hello";
存储方式:
str1
和m
是字符数组,存储在栈上。每个数组都有独立的内存空间
- 数组的内容是
"hello"
,包括结尾的\0
字符。可修改性:
数组的内容可以修改。
cstr1[0] = 'R'; // 合法,str1 变为 "Rello" 因为str1是字符数组,内容可以修改。
地址:
str1
和m
的地址不同,因为它们是独立的数组。2.字符指针
str2
和n
cchar* str2 = "hello"; char* n = "hello";
存储方式:
str2
和n
是指向字符串常量的指针,字符串常量"hello"
存储在只读内存区域(通常是.rodata
段)编译器可能会将相同的字符串常量优化为同一内存地址
可修改性:
字符串常量的内容不能修改。
cstr2[0] = 'R'; // 非法,会导致未定义行为(通常是程序崩溃) //因为str2指向的是字符串常量,内容不能修改。
地址:
str2
和n
的地址可能相同,因为它们指向相同的字符串常量。
c// char* str3 = {'h','e','l','l','o','\0'} error:初始化语法错误
注意:
- 在 C 语言中,对于指针类型的变量,不能直接用花括号
{}
包含的字符列表来初始化。- 花括号这种初始化方式通常用于数组的初始化,而不是指针。
字符指针与字符数组的区别:
特性 | 字符指针 | 字符数组 |
---|---|---|
定义 | char *str |
char str[] |
内存分配 | 指向字符串字面量(只读)或 动态分配的内存 | 静态分配内存,大小固定 |
可修改性 | 如果指向字符数组,可以修改 如果指向字符串字面量,不能修改 | 可以修改 |
大小 | sizeof(str) 返回指针的大小(通常为4或8字节) |
sizeof(str) 返回整个数组的大小 |
灵活性 | 可以指向不同的字符串 | 不能指向其他字符串 |
字符指针的常见应用:
示例代码:字符指针的使用
c#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { // 示例1:字符指针指向字符串字面量 char* str1 = "Hello"; printf("str1: %s\n", str1); // 输出:Hello // 示例2:字符指针指向字符数组 char str2[] = "Hello"; char* str3 = str2; str3[0] = 'h'; // 修改字符串内容 printf("str2: %s\n", str2); // 输出:hello // 示例3:动态分配字符串 char* str4 = (char*)malloc(20 * sizeof(char)); strcpy(str4, "Hello, World!"); printf("str4: %s\n", str4); // 输出:Hello, World! free(str4); // 释放内存 // 示例4:字符串数组 char* strArr[] = { "Apple", "Banana", "Cherry" }; for (int i = 0; i < 3; i++) { printf("strArr[%d]: %s\n", i, strArr[i]); // 输出每个字符串 } return 0; }
输出:
cstr1: Hello str2: hello str4: Hello, World! strArr[0]: Apple strArr[1]: Banana strArr[2]: Cherry
字符指针做函数参数
在C语言中,字符指针(
char *
)作为函数参数时,通常用于传递字符串或字符数组的地址。
字符指针作为函数参数的注意事项:
1.
字符串的长度
字符指针作为函数参数时,字符串的长度信息会丢失
- 因此通常需要额外传递字符串的长度。
- 或者依赖
\0
来判断字符串的结束。
c#include <stdio.h> //需要额外传递字符串的长度 void printString1(char* str, int n) { for (int i = 0; i < n; i++) { printf("%c", str[i]); // 输出字符串的前n个字符 } printf("\n"); } //依赖 \0 来判断字符串的结束 void printString2(char* str) { for (int i = 0; str[i] != '\0'; i++) { printf("%c", str[i]); // 输出字符串 } printf("\n"); } int main() { char str[] = "Hello, World!"; int n = sizeof(str) / sizeof(str[0]); printString1(str, n); // 输出:Hello, World! printString2(str); // 输出:Hello, World! return 0; }
2.
字符串的可修改性
如果字符指针指向的是字符串字面量
- 如:
char *str = "Hello";
,则不能通过指针修改字符串的内容(行为未定义)如果字符指针指向的是字符数组
- 如:
char str[] = "Hello";
,则可以通过指针修改字符串的内容
c#include <stdio.h> void modifyString(char* str) { str[0] = 'h'; } int main() { char* str1 = "Hello"; modifyString(str1); // 未定义行为 char str2[] = "Hello"; modifyString(str2); // 修改字符串内容 printf("%s\n", str2); // 输出:hello return 0; }
示例代码:字符指针作为函数参数的使用
c#include <stdio.h> #include <stdlib.h> #include <string.h> // 打印字符串 void printString(char *str) { printf("%s\n", str); } // 将字符串转换为大写 void toUpperCase(char *str) { for (int i = 0; str[i] != '\0'; i++) { if (str[i] >= 'a' && str[i] <= 'z') { str[i] = str[i] - 32; } } } // 动态分配字符串 char *createString(const char *src) { char *str = (char *)malloc((strlen(src) + 1) * sizeof(char)); strcpy(str, src); return str; } // 打印字符串数组 void printStrings(char *strArr[], int n) { for (int i = 0; i < n; i++) { printf("%s\n", strArr[i]); } } int main() { // 示例1:打印字符串 char str1[] = "Hello, World!"; printString(str1); // 示例2:修改字符串 toUpperCase(str1); printString(str1); // 示例3:动态分配字符串 char *str2 = createString("动态分配字符串"); printString(str2); free(str2); // 示例4:字符串数组 char *strArr[] = {"Apple", "Banana", "Cherry"}; int n = sizeof(strArr) / sizeof(strArr[0]); printStrings(strArr, n); return 0; }
输出:
cHello, World! HELLO, WORLD! Dynamic String Apple Banana Cherry
注意:
当
字符串数组
做函数参数时,我们通常在函数定义中,封装2个参数。
一个表示字符串数组首地址,一个表示数组元素个数
const修饰的指针变量
const
修饰指针的常见用法:
- 保护函数参数
使用
const
修饰指针参数,防止函数内部修改数据。
cvoid printArray(const int *arr, int n) { for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); }
- 定义常量字符串
使用
const
修饰指针,定义常量字符串。
cconst char *str = "Hello, World!"; // str[0] = 'h'; // 错误:不能修改常量字符串
指针数组做为main函数的形参
在C语言中,main 函数可以接受两个参数:
argc
和argv
理解 main 函数的参数及其用法对于编写支持命令行输入的程序非常重要。
main
函数原型的语法:
cint main(int argc, char *argv[]);
argc
(参数计数):是一个整形,用于表示命令行参数的数量(包括程序名本身)
argc
的最小值为1
,因为argv[0]
总是程序名。
argv
(参数向量):是一个指针数组,用于存储命令行参数的字符串
指针数组argv的每个元素是一个
char *
类型的指针,指向一个命令行参数字符串。
argv[0]
:是程序名,argv[1]
到argv[argc-1]
是实际的命令行参数。argv[argc]
: 是一个空指针(NULL
),表示参数列表的结束。
main
函数参数的使用:1.打印所有命令行参数
通过遍历
argv
数组,可以打印所有命令行参数。
c#include <stdio.h> int main(int argc, char *argv[]) { printf("程序名: %s\n", argv[0]); // 打印程序名 for (int i = 1; i < argc; i++) { printf("参数 %d: %s\n", i, argv[i]); // 打印每个参数 } return 0; }
2.处理命令行参数
通过
argc
和argv
可以处理命令行参数。
- 例如:解析选项或传递配置信息。
c#include <stdio.h> int main(int argc, char *argv[]) { if (argc < 2) { printf("用法: %s <参数>\n", argv[0]); // 提示用法 return 1; } printf("第一个参数: %s\n", argv[1]); // 打印第一个参数 return 0; }
示例代码 :运行带参数的
main
函数
c#include <stdio.h> int main(int argc, char *argv[]) { printf("程序名: %s\n", argv[0]); // 打印程序名 printf("参数总数: %d\n", argc - 1); // 打印参数总数 // 打印所有命令行参数 for (int i = 1; i < argc; i++) { printf("参数 %d: %s\n", i, argv[i]); } return 0; }
借助VS编辑工具运行
- 编写好含有带参数的
main
函数C程序 --->右击该程序所在的项目名 ---> 属性--->调试 ---> 命令参数 ---> 添加命令参数 ---> 确定
2.借助记事本、gcc编译工具运行
切记 :粘贴完代码一定要记得按Ctrl + S
进行保存
项目开发常用字符串应用模型
while和do-while模型
在处理字符串时,经常需要统计某个特定子字符串在主字符串中出现的次数。
- 给定一个主字符串
p
,其内容为"11abcd111122abcd333abcd3322abcd3333322qqq"
- 要求编写一个 C 语言程序,统计子字符串
"abcd"
在主字符串中出现的次数
c
//while模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
const char* p = "11abcd111122abcd333abcd3322abcd3333322qqq";
int n = 0;
int len = strlen("abcd");
while (p != NULL)
{
n++;
p = p + len;
p = strstr(p, "abcd");
}
printf("n = %d\n", n);
return 0;
}
输出:
n = 4
c
//do-while模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
const char* p = "11abcd111122abcd333abcd3322abcd3333322qqq";
int n = 0;
int len = strlen("abcd");
do
{
p = strstr(p, "abcd");
if (p != NULL)
{
n++; //累计个数
p = p + len; //重新设置查找的起点
}
else //如果没有匹配的字符串,跳出循环
{
break;
}
} while (*p != 0); //如果没有到结尾
printf("n = %d\n", n);
return 0;
}
输出:
n = 4
两头堵模型
在字符串处理过程中,常常需要统计去除首尾空格后字符串中非空字符的数量。
- 编写一个 C 语言程序,实现一个名为
fun
的函数,该函数的功能是统计一个字符串去除首尾空格后非空字符的数量。
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int fun(char *p, int *n)
{
// 检查指针是否为空
if (p == NULL || n == NULL)
{
return -1;
}
int begin = 0;
int end = strlen(p) - 1;
// 从左边开始,跳过空格
while (p[begin] == ' ' && p[begin] != '\0')
{
begin++;
}
// 从右边开始,跳过空格
while (p[end] == ' ' && end >= begin)
{
end--;
}
// 如果字符串全是空格,返回错误
if (end < begin)
{
return -2;
}
// 计算非空字符的个数
*n = end - begin + 1;
return 0;
}
int main()
{
char *p = " abcddsgadsgefg ";
int ret = 0;
int n = 0;
// 调用函数处理字符串
ret = fun(p, &n);
if (ret != 0)
{
printf("函数执行失败,错误码:%d\n", ret);
return ret;
}
// 输出结果
printf("非空字符串元素个数:%d\n", n);
return 0;
}
输出:
非空字符串元素个数:14
编写一个C语言程序,判断一个字符串是否是回文字符串。
c
#include <stdio.h>
#include <stdlib.h>
// 判断回文字符串 abcdpba
int str_abcbb(char* str)
{
char* start = str; // 记录首元素地址
char* end = str + strlen(str) - 1; // 记录最后一个元素地址。
while (start < end)
{
if (*start != *end)
{
return 0; // 0 表示 非回文
}
start++;
end--;
}
return 1; // 1 表示 回文
}
int main(void)
{
char s2[] = "abccba";
int ret = str_abcbb(s2);
if (ret == 0)
{
printf("不是回文\n");
}
else if (ret == 1)
{
printf("是回文\n");
}
system("pause");
return EXIT_SUCCESS;
}
输出 :
是回文
字符串反转模型
在字符串处理的诸多场景中,字符串反转是一个常见的需求。
- 编写一个 C 语言程序,实现字符串反转的功能。
- 要求创建一个名为
inverse
的函数,该函数接收一个字符串作为参数,并将该字符串的字符顺序进行反转。
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int inverse(char* str)
{
// 检查指针是否为空
if (str == NULL)
{
return -1;
}
int begin = 0;
int end = strlen(str) - 1;
char tmp;
// 交换字符,直到begin和end相遇
while (begin < end)
{
// 交换元素
tmp = str[begin];
str[begin] = str[end];
str[end] = tmp;
begin++; // 往右移动位置
end--; // 往左移动位置
}
return 0;
}
int main()
{
//char *str = "abcdef";
// 使用字符数组,而不是字符指针,因为字符指针指向的是常量区,内容不允许修改
char str[] = "abcdef";
// 调用反转函数
int ret = inverse(str);
if (ret != 0)
{
printf("函数执行失败,错误码:%d\n", ret);
return ret;
}
// 输出结果
printf("str = %s\n", str);
return 0;
}
输出:
str = fedcba
字符串处理函数
strchr()
函数的介绍:
strchr()
:用于在一个字符串中查找指定字符首次出现的位置。
strchr()
函数会从字符串str
的开头开始,逐个字符进行比较,查找字符c
首次出现的位置。
一旦找到该字符,就返回指向该字符的指针
如果遍历完整个字符串都没有找到该字符,则返回
NULL
函数的原型:
cchar *strchr(const char *str, int c);
str
:是指向要进行查找的字符串的指针。
c
:要查找的字符。
- 虽然参数类型为
int
,但实际上是一个字符的 ASCII 值- 字符
c
会被转换为char
类型
函数的返回值:
如果在字符串
str
中找到字符c
:则返回指向该字符首次出现位置的指针如果在字符串
str
中未找到字符c
:则返回NULL
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char str[] = "Hello, World!"; char target = 'o'; char* result = strchr(str, target); if (result != NULL) { printf("字符 '%c' 首次出现在位置: %ld\n", target, result - str); printf("从该位置开始的子字符串: %s\n", result); } else { printf("未找到字符 '%c'\n", target); } return 0; }
输出:
字符 'o' 首次出现在位置: 4
从该位置开始的子字符串: o, World!
函数的注意事项:
字符查找的范围
:strchr 函数会查找字符首次出现的位置,并且会查找包括字符串结束符'\0'
之前的所有字符。
- 例如:如果要查找的字符是
'\0'
,函数会返回指向字符串结束符的指针。
c#include <stdio.h> #include <string.h> int main() { char str[] = "Hello"; char *result = strchr(str, '\0'); if (result != NULL) { printf("找到字符串结束符,位置: %ld\n", result - str); } return 0; }
输出:
找到字符串结束符,位置: 5
strrchr()
函数的介绍:
strrchr()
:用于查找字符串中某个字符最后一次出现的位置。(即:用于在字符串中从后往前查找指定字符的第一次出现位置)
函数的原型:
cchar *strrchr(const char *str, int c);
str
:是指向要被搜索的字符串的指针。
c
:是要查找的字符。
- 虽然参数类型为
int
,但实际上是一个字符的 ASCII 值- 在函数内部会将字符
c
会被转换为char
类型
函数的返回值:
如果在字符串
str
中找到了字符c
的最后一次出现位置,则返回一个指向该位置的指针如果在字符串
str
中未找到字符c
,则返回NULL
函数的使用:
c#include <stdio.h> #include <string.h> int main() { const char *str = "Hello, World!"; char target = 'o'; char *result = strrchr(str, target);//error:const char *类型的值不能用于初始化 char *类型的实体 if (result != NULL) { printf("字符 '%c' 最后一次出现的位置是: %ld\n", target, result - str); printf("从该位置开始的子字符串是: %s\n", result); } else { printf("未找到字符 '%c'\n", target); } return 0; }
分析错误原因:
strrchr
的参数str
:是const char*
类型,表示它指向的字符串是常量不能修改。strrchr
的返回值:是char*
类型,表示它返回的指针可以用于修改字符串。注意 :
将 char* 赋值给 const char* 是允许的,但反过来不行,因为这会破坏 const 的语义
问题解决方法:
将
str
的类型改为char*
:(如果str
指向的字符串不需要保护,即:可以修改)
cchar str[] = "Hello, World!";
- 这样,
str
就是一个可修改的字符数组,strrchr
的返回值可以直接赋值给result
。使用类型转换 :(如果你需要
str
是const char*
类型)
cconst char* str = "Hello, World!"; const char* result = strrchr(str, target);
- 这样,
result
也是const char*
类型,与str
的类型一致。
修改后的代码:
方法 1 :将
str
改为char*
c#include <stdio.h> #include <string.h> int main() { char str[] = "Hello, World!"; // 改为 char[] char target = 'o'; char* result = strrchr(str, target); if (result != NULL) { printf("字符 '%c' 最后一次出现的位置是: %ld\n", target, result - str); printf("从该位置开始的子字符串是: %s\n", result); } else { printf("未找到字符 '%c'\n", target); } return 0; }
方法 2:使用类型转换
c#include <stdio.h> #include <string.h> int main() { const char* str = "Hello, World!"; // 保持 const char* char target = 'o'; const char* result = strrchr(str, target); // 将返回值转换为 const char* if (result != NULL) { printf("字符 '%c' 最后一次出现的位置是: %ld\n", target, result - str); printf("从该位置开始的子字符串是: %s\n", result); } else { printf("未找到字符 '%c'\n", target); } return 0; }
strchr()
和 strrchr()
的区别:
函数 | 搜索方向 | 返回的字符位置 |
---|---|---|
strchr |
从字符串开头向后搜索 | 字符第一次出现的位置 |
strrchr |
从字符串末尾向前搜索 | 字符最后一次出现的位置 |
strstr()
函数的介绍:
strstr()
:用于在一个字符串中查找另一个子字符串首次出现的位置。
strstr()
函数会从主字符串haystack
的起始位置开始,逐个字符地进行比对,尝试找到与子字符串needle
完全匹配的部分。
一旦找到匹配的子字符串,就返回指向该匹配部分起始位置的指针
若遍历完整个
haystack
字符串都没有找到匹配的needle
子字符串,就返回NULL
函数的原型:
cchar *strstr(const char *haystack, const char *needle);
haystack
:是指向要进行搜索的主字符串的指针。
- 这个字符串相当于 "干草堆",是搜索操作的范围。
needle
:是指向要查找的子字符串的指针。
- 这个子字符串就是我们要在 "干草堆" 里找的 "针"。
函数的返回值:
若在
haystack
字符串中找到了needle
子字符串:函数返回指向该子字符串首次出现位置的指针。若在
haystack
字符串中未找到needle
子字符串:函数返回NULL
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char haystack[] = "Hello, World! This is a test."; char needle[] = "World"; char* result = strstr(haystack, needle); if (result != NULL) { printf("子字符串 '%s' 首次出现在位置: %ld\n", needle, result - haystack); printf("从该位置开始的子字符串: %s\n", result); } else { printf("未找到子字符串 '%s'\n", needle); } return 0; }
输出:
子字符串 'World' 首次出现在位置: 7
从该位置开始的子字符串: World! This is a test.
函数的注意事项:
空字符串的处理
:如果needle
是一个空字符串(即""
),strstr()
函数会直接返回haystack
本身。
- 因为:
空字符串可以被认为在任何字符串的开头都存在
c#include <stdio.h> #include <string.h> int main() { char haystack[] = "Hello"; char needle[] = ""; char* result = strstr(haystack, needle); if (result != NULL) { printf("找到空字符串,返回主字符串: %s\n", result); } return 0; }
输出:
找到空字符串,返回主字符串: Hello
strchr()
和 strstr()
的区别:
特性 | strchr() |
strstr() |
---|---|---|
搜索对象 | 单个字符 | 子字符串 |
返回值 | 指向字符的指针 | 指向子字符串的指针 |
区分大小写 | 是 | 是 |
适用场景 | 查找单个字符 | 查找子字符串 |
strtok()
函数的介绍:
strtok()
:用于将一个字符串按照指定的分隔符分割成多个子字符串。
strtok()
函数会在字符串str
中查找由分隔符delim
分隔的子字符串。它会将分隔符替换为字符串结束符
'\0'
,并返回指向分割出的子字符串的指针。函数会记录当前分割的位置,以便后续调用时继续从该位置进行分割。
第一次调用:
- 传入待分割的字符串
str
strtok()
会找到第一个标记,并在标记末尾添加\0
,返回指向该标记的指针。- 内部会保存剩余的字符串,供后续调用使用。
后续调用:
- 传入
NULL
作为str
参数。strtok()
会从上一次保存的位置继续查找下一个标记。结束条件:
- 当字符串被完全分割后,返回
NULL
函数的原型:
cchar *strtok(char *str, const char *delim);
str
:是指向要进行分割的字符串的指针。
- 在第一次调用
strtok()
时,需要传入待分割的字符串- 后续调用时,应传入
NULL
,以继续从上次的位置分割,因为函数会记录上一次分割的位置
delim
:是指向包含分隔符字符的字符串的指针。
- 分隔符可以是一个或多个字符,函数会根据这些字符来分割字符串。
函数的返回值:
若找到下一个子字符串:返回指向该子字符串的指针
若没有更多的子字符串可分割:返回
NULL
函数的使用:
1.处理连续分隔符
c#include <stdio.h> #include <string.h> int main() { char str[] = "Hello,World,How,Are,You"; const char delim[] = ","; // 第一次调用 strtok(),传入待分割的字符串 char* token = strtok(str, delim); while (token != NULL) { printf("分割出的子字符串: %s\n", token); // 后续调用,传入 NULL token = strtok(NULL, delim); } return 0; }
输出:
分割出的子字符串: Hello
分割出的子字符串: World
分割出的子字符串: How
分割出的子字符串: Are
分割出的子字符串: You
2.处理多分隔符
c#include <stdio.h> #include <string.h> int main() { char str[] = "Hello;World|This-is/C"; const char delim[] = ";|-/"; char* token = strtok(str, delim); while (token != NULL) { printf("分割出的子字符串: %s\n", token); token = strtok(NULL, delim); } return 0; }
输出:
分割出的子字符串: Hello
分割出的子字符串: World
分割出的子字符串: This
分割出的子字符串: is
分割出的子字符串: C
函数的注意事项:
原字符串会被修改
:strtok()
函数会将分隔符替换为字符串结束符'\0'
,因此原字符串会被修改。
- 如果需要保留原字符串,建议先复制一份再进行分割操作。
c#include <stdio.h> #include <string.h> int main() { char original[] = "Apple,Banana,Cherry"; char copy[50]; strcpy(copy, original); const char delim[] = ","; char *token = strtok(copy, delim); while (token != NULL) { printf("分割出的子字符串: %s\n", token); token = strtok(NULL, delim); } printf("原字符串: %s\n", original); return 0; }
输出:
分割出的子字符串: Apple
分割出的子字符串: Banana
分割出的子字符串: Cherry
原字符串: Apple,Banana,Cherry
传入的参数必须可读可写
:strtok函数拆分字符串是直接在原串上进行的操作,所以要求传入的参数必须,可读可写。
- 例如:
char *str = "www. baidu.com"
不行!!!
分隔符的处理
:连续的分隔符会被视为一个分隔符。
- 例如:字符串
"Hello,,World"
中连续的两个逗号会被当作一个分隔符处理,只替换第一个为\0
,分割结果为"Hello"
和"World"
空字符串的处理
:如果字符串以分隔符开头,第一个返回的子字符串将是空字符串。
- 例如:字符串
",Hello,World"
分割时,第一个返回的子字符串为空
strcpy()
函数的介绍:
strcpy()
:用于将一个字符串复制到另一个字符串中。
- 将源字符串(包括终止符
\0
)复制到目标缓冲区。- 也就是说,它会逐字符地将
src
中的内容复制到dest
中,直到遇到src
的结束符'\0'
,并将这个结束符也复制到dest
中。
函数的原型:
cchar *strcpy(char *dest, const char *src);
dest
:目标字符串的起始地址,即要将源字符串复制到的内存位置。
- 必须有足够空间容纳源字符串(包括
\0
)src
:源字符串的起始地址,即要复制的字符串。
- 必须以
\0
结尾
函数的返回值:
strcpy()
:返回目标字符串dest
的起始地址,即复制后的字符串的地址。
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char src[] = "Hello, World!"; char dest[20]; // 确保足够大 char* p = strcpy(dest, src); printf("p: %s\n", p); // 输出:Hello, World! printf("dest: %s\n", dest); // 输出:Hello, World! return 0; }
函数的注意事项:
目标字符串的空间足够
:目标字符串dest
必须有足够的空间来容纳源字符串及其终止符'\0'
- 由于strcpy() 函数本身不会检查目标字符串的空间是否足够,也不会返回错误信息。
- 所以如果目标字符串的空间不足,会导致缓冲区溢出,这是一个严重的安全隐患,可能会导致程序崩溃或产生不可预期的行为。
c#include <stdio.h> #include <string.h> int main() { char source[] = "Hello"; char destination[5]; // 目标字符串空间不足 char* p = strcpy(destination, source); // 溢出!destination 容量不足(需要6字节,包含\0) printf("%s\n", p); return 0; }
避免自我复制
:不要将源字符串和目标字符串设置为同一个字符串,否则可能会导致不可预期的结果。
cchar str[] = "Example"; strcpy(str, str); // 不建议这样做
strncpy()
函数的介绍:
strncpy()
:用于将一个字符串的部分或全部
内容复制到另一个字符串中。
- 它能更好地控制复制的字符数量,一定程度上避免了缓冲区溢出的问题。
strncpy()
函数会尝试将源字符串src
中的最多n
个字符复制到目标字符串dest
中,具体复制规则如下:
- 若
src
的长度小于n
:
strncpy()
会将src
中的所有字符(包括字符串结束符'\0'
)复制到dest
中,然后用'\0'
填充dest
中剩余的空间,直到复制满n
个字符- 若
src
的长度大于等于n
:
strncpy()
只会复制src
的前n
个字符到dest
中,并且不会自动在dest
的末尾添加字符串结束符'\0'
函数的原型:
cchar *strncpy(char *dest, const char *src, size_t n);
dest
:目标字符串的起始地址,即要将源字符串复制到的内存位置。src
:源字符串的起始地址,即要复制的字符串。n
:目标字符串的最大长度,即最多复制的字符数。
函数的返回值:
strncpy()
:返回目标字符串dest
的起始地址,即复制后的字符串的地址。
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char dest[20]; char src[] = "hello world"; char* p = strncpy(dest, src, 5); printf("%s\n", p); dest[5] = '\0'; printf("%s\n", p); return 0; }
输出:
hello烫烫烫烫烫烫烫烫烫?N┖濑?
hello
代码示例:更好的使用strncpy()函数的方法
c#include <stdio.h> #include <string.h> int main() { char src[] = "Hello, World!"; char dest[10]; char* p = strncpy(dest, src, sizeof(dest) - 1); // 留1字节给\0 dest[sizeof(dest) - 1] = '\0'; // 手动添加终止符 printf("p: %s\n", p); // 输出:Hello, Wo printf("dest: %s\n", dest); // 输出:Hello, Wo return 0; }
函数的注意事项:
手动添加字符串结束符
:当src
的长度大于等于n
时,strncpy()
不会在dest
的末尾添加'\0'
- 因此,在使用
strncpy()
后,需要根据实际情况手动添加字符串结束符,否则在将dest
作为字符串处理时可能会出现问题
cchar dest[5]; strncpy(dest, "Hello", 5); // 复制5个字符,但未添加\0 printf("%s\n", dest); // 可能输出乱码(dest未以\0结尾)
代码示例 :更好的理解
strcpy()
与strncpy()
函数的区别
c#include <stdio.h> #include <string.h> int main() { char src[] = "hello world"; char dest[10] = { 0 }; char* p = strncpy(dest, src, 10); // 字符串src 拷贝给dest for (size_t i = 0; i < 10; i++) { printf("%c", p[i]); } printf("\n"); printf("p= %s\n", p); printf("dest = %s\n", dest); system("pause"); return 0; }
输出:
chello worl p= hello worl烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫橔/戈 dest = hello worl烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫橔/戈
strcpy()
与 strncpy()
的对比:
特性 | strcpy() |
strncpy() |
---|---|---|
复制长度 | 复制整个字符串(包括 \0 ) |
最多复制 n 个字符 |
安全性 | 不安全(不检查缓冲区大小) | 相对安全(限制最大长度) |
自动添加 \0 |
是 | 仅在 src 长度 < n 时添加 |
性能 | 高(直接复制) | 低(可能需要填充 \0 ) |
适用场景 | 已知 src 长度且目标缓冲区足够大时 |
需要控制复制长度,避免溢出 |
总结:
strcpy()
容易导致缓冲区溢出,因此在处理不可信输入时应避免使用。strncpy()
更安全,但需要手动处理字符串的终止符\0
,否则可能导致未定义行为。
strcat()
函数的介绍:
strcat()
:用于将一个字符串追加到另一个字符串的末尾。
- 它会先找到目标字符串
dest
的结束符'\0'
,然后从这个位置开始将源字符串src
的内容复制过去,包括源字符串的结束符'\0'
,- 最终覆盖掉目标字符串原来结束符的位置,并在追加完成后在新字符串的末尾添加结束符
'\0'
函数的原型:
cchar *strcat(char *dest, const char *src);
dest
:目标字符串的起始地址,即要将源字符串追加到的字符串。
- 它需要有足够的空间来容纳源字符串追加后的内容。
- 必须是一个以
\0
结尾的有效 C 字符串。src
:源字符串的起始地址,即要追加的字符串。
函数的返回值:
strcat()
:返回目标字符串dest
的起始地址,即连接后的字符串的地址。
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char dest[20] = "Hello, "; char src[] = "World!"; // 使用 strcat() 函数将 src 字符串追加到 dest 字符串末尾 strcat(dest, src); printf("追加后的字符串: %s\n", dest); return 0; }
函数的注意事项:
目标字符串的空间足够
:目标字符串dest
必须有足够的空间来容纳源字符串追加后的内容,包括源字符串和结束符'\0'
- 如果目标字符串的空间不足,会导致缓冲区溢出,这是一个严重的安全隐患,可能会使程序崩溃或产生不可预期的行为。
c#include <stdio.h> #include <string.h> int main() { char dest[10] = "Hello"; char src[] = " World!"; // 目标字符串空间不足 strcat(dest, src); printf("%s\n", dest); return 0; }
目标字符串必须以 '\0'结尾
:
strcat()
函数通过查找目标字符串的结束符'\0'
来确定追加的起始位置。如果目标字符串不以
'\0'
结尾,strcat()
函数会继续在内存中查找,直到找到'\0'
为止,这可能会导致不可预期的结果。
自我追加问题
:不能将目标字符串和源字符串设置为同一个字符串。
- 因为这样会导致不可预测的结果。
cchar str[20] = "Example"; strcat(str, str); // 不建议
strncat()
函数的介绍:
strncat()
:用于将源字符串中的最多n个字符追加到目标字符串的末尾。
函数首先找到目标字符串
dest
的结束符'\0'
从该位置开始,将源字符串
src
中的字符逐个复制到目标字符串dest
中,最多复制n
个字符。
如果
src
的长度小于n
,则会将整个源字符串(包括结束符'\0'
)追加到目标字符串dest
中如果
src
的长度大于或等于n
,则只会追加n
个字符,并在目标字符串dest
的末尾添加一个字符串结束符'\0'
函数的原型:
cchar *strncat(char *dest, const char *src, size_t n);
dest
:目标字符串的起始地址,即要将源字符串追加到的字符串。
- 它需要有足够的空间来容纳追加的字符和最终的字符串结束符
'\0'
- 必须是一个以
\0
结尾的有效 C 字符串src
:源字符串的起始地址,即要追加的字符串。n
:最多从源字符串中追加到目标字符串的字符数量。
函数的返回值:
strcat()
:返回目标字符串dest
的起始地址,即连接后的字符串的地址。
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char dest[20] = "Hello, "; char src[] = "World!"; // 使用 strncat() 函数将 src 字符串的前 3 个字符追加到 dest 字符串末尾 strncat(dest, src, 3); printf("追加后的字符串: %s\n", dest); //输出:Hello, Wor return 0; }
函数的注意事项:
目标字符串的空间
:目标字符串dest
必须有足够的空间来容纳追加的字符和最终的字符串结束符'\0'
- 如果空间不足,会导致缓冲区溢出,从而引发程序崩溃或产生不可预期的结果。
c#include <stdio.h> #include <string.h> int main() { char dest[10] = "Hello"; char src[] = " World!"; // 目标字符串空间不足 strncat(dest, src, 5); //dest初始存储"Hello"后剩余空间不足以容纳追加的5个字符及结束符 printf("%s\n", dest); return 0; }
目标字符串需以'\0'结尾
:
strncat()
函数通过查找目标字符串的结束符'\0'
来确定追加的起始位置。- 如果目标字符串不以
'\0'
结尾,函数会继续在内存中查找,直到找到'\0'
为止,这可能会导致不可预期的结果。
strcat()
和 strncat()
的区别:
特性 | strcat() |
strncat() |
---|---|---|
追加长度 | 追加整个 src 字符串(包括 \0 ) |
最多追加 n 个字符 |
自动添加 \0 |
是 | 是 |
缓冲区溢出风险 | 高(如果 dest 不够大) |
较低(可以控制追加的长度) |
适用场景 | 已知 dest 足够大 |
需要控制追加长度,避免溢出 |
strcmp()
函数的介绍:
strcmp()
:用于完全比较两个字符串。
strcmp()
函数会逐字符比较两个字符串,比较是基于字符的 ASCII 值进行的- 比较结束的条件为直到
遇到不同的字符
或者到达字符串的结束符 '\0'
- 从两个字符串的第一个字符开始,依次比较对应位置的字符。
- 如果对应位置的字符相同,则继续比较下一个字符,直到
遇到不同的字符
或者到达字符串末尾
- 如果在比较过程中遇到不同的字符,函数会根据这两个不同字符的 ASCII 值大小关系返回相应结果。
- 如果到达字符串末尾都没有遇到不同的字符,说明两个字符串相等,函数返回 0
函数的原型:
cint strcmp(const char *str1, const char *str2);
str1
:指向第一个要比较的字符串的指针。
str2
:指向第二个要比较的字符串的指针。
函数的返回值:
如果 s1 和 s2
相等
,返回 0如果 s1 按字典序
小于
s2,返回一个负整数如果 s1 按字典序
大于
s2,返回一个正整数
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char str1[] = "apple"; char str2[] = "banana"; int result = strcmp(str1, str2); if (result < 0) { printf("%s按字典序小于%s\n", str1, str2); } else if (result > 0) { printf("%s按字典序大于%s\n", str1, str2); } else { printf("%s等于%s\n", str1, str2); } return 0; }
输出:
apple按字典序小于banana
strncmp()
函数的介绍:
strncmp()
:用于比较两个字符串str1
和str2
的前n
个字符。
strncmp()
函数会它会逐字符比较这两个字符串,基于字符的 ASCII 值进行判断,直到出现以下情况之一:
- 比较完前
n
个字符- 遇到其中一个字符串的结束符
'\0'
函数的原型:
cint strncmp(const char *str1, const char *str2, size_t n);
s1
:指向第一个要比较的字符串的指针。
s2
:指向第二个要比较的字符串的指针。
n
:表示最多比较的字符数量。
函数的返回值:
如果 s1 和 s2 的前 n 个字符
相等
,返回 0如果 s1 的前 n 个字符按字典序
小于
s2 的前 n 个字符,返回一个负整数如果 s1 的前 n 个字符按字典序
大于
s2 的前 n 个字符,返回一个正整数
函数的使用:
c#include <stdio.h> #include <string.h> int main() { char str1[] = "Hello, World!"; char str2[] = "Hello, C!"; char str3[] = "Goodbye"; int result1 = strncmp(str1, str2, 5); // 比较前 5 个字符 int result2 = strncmp(str1, str3, 5); // 比较前 5 个字符 printf("strncmp(str1, str2, 5): %d\n", result1); // 输出:0 printf("strncmp(str1, str3, 5): %d\n", result2); // 输出:正整数(具体值取决于实现) return 0; }
strcmp()
和 strncmp()
的区别:
特性 | strcmp() |
strncmp() |
---|---|---|
比较长度 | 比较整个字符串(直到 \0 ) |
只比较前 n 个字符 |
比较范围 | 无限制 | 受 n 限制 |
适用场景 | 需要完全匹配时使用 | 需要部分匹配或限制比较长度时使用 |
sprintf()
函数的介绍:
sprintf()
:用于将格式化后的内容存储到一个字符数组中。
sprintf 函数的定义位于
<stdio.h>
头文件中。sprintf 函数的用法与 printf 函数类似,但操作的对象是字符串而不是标准输出。
函数的原型:
cint sprintf(char *str, const char *format, ...);
str
:是指向一个字符数组的指针,用于存储格式化后的字符串。
- 该字符数组必须有足够的空间来容纳格式化后的内容以及字符串结束符
'\0'
format
:是格式化字符串,它指定了后续参数应该如何被格式化。
- 格式化字符串中可以包含
普通字符
和格式说明符
- 格式说明符以
%
开头,用于指定不同类型数据的输出格式。- 如:
%d
用于整数,%f
用于浮点数,%s
用于字符串等。...
:是可变参数列表,根据format
字符串中的格式说明符,提供相应数量和类型的参数。
函数的返回值:
成功时:返回值是一个整数,表示成功写入目标字符串的字符数(不包括末尾的
\0
)失败时:返回一个负数
函数的使用:
c#include <stdio.h> int main() { char buffer[50]; int num = 123; float f = 3.14; char str[] = "Hello"; // 使用 sprintf() 函数将不同类型的数据格式化到 buffer 数组中 int result = sprintf(buffer, "整数: %d, 浮点数: %.2f, 字符串: %s", num, f, str); printf("格式化后的字符串: %s\n", buffer); printf("存储的字符数量: %d\n", result); return 0; }
输出:
格式化后的字符串: 整数: 123, 浮点数: 3.14, 字符串: Hello
存储的字符数量: 38
sscanf()
函数的介绍:
sscanf()
:用于从一个字符串中按照指定的格式读取数据,并将读取的数据存储到相应的变量中。
- sscanf 函数的定义位于
<stdio.h>
头文件中。- sscanf 函数的用法与 scanf 函数类似,但输入数据来自字符串而不是标准输入。
sscanf()
函数会根据format
字符串的规则,从str
字符串中提取数据,并将这些数据存储到可变参数列表指定的变量中。它会按照格式说明符依次读取字符串中的数据,直到遇到不匹配的字符或者字符串结束。
函数的原型:
cint sscanf(const char *str, const char *format, ...);
str
:是指向要进行解析的字符串的指针,该字符串包含了需要提取的数据。format
:是格式化字符串,它规定了如何从str
中读取数据。...
:是可变参数列表,一系列用于存储读取数据的变量的地址,这些变量的类型要与format
中的格式说明符相匹配。
函数的返回值:
成功时:返回成功匹配并赋值的输入项的数量
失败时:返回
EOF
函数的使用:
c#include <stdio.h> int main() { char input[] = "123 3.14"; int num; float f; // 使用 sscanf() 从字符串中读取整数和浮点数 int result = sscanf(input, "%d %f", &num, &f); if (result == 2) { printf("成功读取整数: %d, 浮点数: %.2f\n", num, f); } else { printf("读取数据失败\n"); } return 0; }
输出:
成功读取整数: 123, 浮点数: 3.14
函数的注意事项:
变量地址传递
:在传递用于存储读取数据的变量时,必须传递它们的地址(使用&
运算符)否则函数无法将数据正确存储到变量中。
- 例如:对于整数变量
int num
,应该传递&num
缓冲区溢出风险
:当使用%s
格式说明符读取字符串时,如果没有限制读取的字符数量,可能会导致缓冲区溢出。
- 可以使用
%ns
(n
为正整数)的形式来限制读取的最大字符数
- 例如:
%19s
表示最多读取 19 个字符,会预留一个位置给字符串结束符'\0'
c#include <stdio.h> int main() { char input[] = "This is a very long string"; char str[10]; // 限制读取的字符数,避免缓冲区溢出 sscanf(input, "%9s", str); //最多读取9个字符,留一个位置给字符串结尾的空字符 \0 printf("读取的字符串: %s\n", str); return 0; }
输出:
读取的字符串: This
分析:
- 为什么输出是
"This"
?
sscanf
使用%9s
格式说明符,表示最多读取 9 个字符。- 但是,
%s
在读取时会遇到空白字符(如空格)停止。- 在
input
中,第一个单词是"This"
,后面有一个空格,因此sscanf
读取到"This"
后停止。
代码示例 :使用正则表达
[^\n]
式让 sscanf 函数可以读取包括空格的字符串
c#include <stdio.h> int main() { char input[] = "This is a very long string"; char str[20]; // 增大缓冲区大小 // 读取直到换行符(不包括换行符) sscanf(input, "%19[^\n]", str); printf("读取的字符串: %s\n", str); return 0; }
格式匹配
:format
字符串中的普通字符必须与str
中的对应字符完全匹配。
- 例如:
format
为"x = %d"
,那么str
必须以"x = "
开头,后面接着一个整数,否则读取会失败。
sprintf()
和 sscanf()
的区别:
特性 | sprintf() |
sscanf() |
---|---|---|
功能 | 将格式化数据写入字符串 | 从字符串中读取格式化数据 |
目标 | 字符串 str |
字符串 str |
返回值 | 写入的字符数 | 成功读取的输入项数量 |
适用场景 | 生成格式化字符串 | 解析格式化字符串 |
atoi()
函数的介绍:
atoi()
:用于将字符串转换为整数。
atoi()
是 C 语言标准库<stdlib.h>
中的一个函数。- 它的名字是 "ASCII to Integer" 的缩写。
atoi()
函数会扫描输入的字符串,跳过前面的空白字符(如:空格、制表符\t
、换行符\n
等),然后尝试将后续的字符序列转换为一个整数。转换过程会在遇到非数字字符(除了开头的正负号)时停止。
符号处理
:字符串开头可以有一个可选的正负号(+
或-
),用于表示整数的正负性。数字识别
:函数会识别字符串中的数字字符(0
-9
),并将其转换为对应的整数值。终止条件
:当遇到非数字字符(除开头的正负号)或字符串末尾时,转换停止。
函数的原型:
cint atoi(const char *nptr);
nptr
:是指向要转换为整数的字符串的指针。
函数的返回值:
若字符串能转换为有效的整数:函数返回转换后的整数值
若字符串不能转换为有效的整数:函数返回 0
函数的使用:
c#include <stdio.h> #include <stdlib.h> int main() { char str1[] = "1234"; char str2[] = " -567"; char str3[] = "12abc"; char str4[] = "abc123"; int num1 = atoi(str1); int num2 = atoi(str2); int num3 = atoi(str3); int num4 = atoi(str4); printf("str1 转换后的整数: %d\n", num1); printf("str2 转换后的整数: %d\n", num2); printf("str3 转换后的整数: %d\n", num3); printf("str4 转换后的整数: %d\n", num4); return 0; }
1.处理数字字符
c#include <stdio.h> #include <stdlib.h> int main() { const char* str1 = "12345"; const char* str2 = "-6789"; const char* str3 = " 42"; int num1 = atoi(str1); int num2 = atoi(str2); int num3 = atoi(str3); printf("str1 转换后的整数: %d\n", num1); printf("str2 转换后的整数: %d\n", num2); printf("str3 转换后的整数: %d\n", num3); return 0; }
输出:
str1 转换后的整数: 12345
str2 转换后的整数: -6789
str3 转换后的整数: 42
2.处理非数字字符
c#include <stdio.h> #include <stdlib.h> int main() { const char* str1 = "123abc"; const char* str2 = "abc123"; //开头就是非数字字符,无法转换为有效的整数 const char* str3 = "12.34"; int num1 = atoi(str1); int num2 = atoi(str2); int num3 = atoi(str3); printf("str1 转换后的整数: %d\n", num1); printf("str2 转换后的整数: %d\n", num2); printf("str3 转换后的整数: %d\n", num3); return 0; }
输出:
str1 转换后的整数: 123
str2 转换后的整数: 0
str3 转换后的整数: 12
3.处理溢出
c#include <stdio.h> #include <stdlib.h> #include <limits.h> int main() { const char* str1 = "2147483647"; // INT_MAX const char* str2 = "2147483648"; // 超出 INT_MAX int num1 = atoi(str1); int num2 = atoi(str2); printf("str1 转换后的整数: %d\n", num1); printf("str2 转换后的整数: %d\n", num2); return 0; }
输出:
str1 转换后的整数: 2147483647
str2 转换后的整数: 2147483647
函数的注意事项:
错误处理有限
:atoi 函数在遇到无法转换的情况时,只是简单地返回 0,没有提供更详细的错误信息。
- 因此:无法区分字符串本身表示的就是 0 还是转换失败导致返回 0
溢出问题
:atoi 函数不会检查转换结果是否会导致整数溢出。
- 如果字符串表示的数值超出了
int
类型的表示范围,可能会得到错误的结果。
- 例如:尝试将一个非常大的数值字符串转换为
int
时,可能会发生溢出。
空白字符
:atoi 函数会跳过开头的空白字符,但不会跳过字符串中间的空白字符。
👨💻 博主正在持续更新C语言基础系列中。
❤️ 如果你觉得内容还不错,请多多点赞。
⭐️ 如果你觉得对你有帮助,请多多收藏。(防止以后找不到了)
👨👩👧👦
C语言基础系列
持续更新中~,后续分享内容主要涉及C++全栈开发
的知识,如果你感兴趣请多多关注博主。