C语言指针进阶——字符串函数手写实现、指针与二维数组
📅 2026/7/30 5:35:12
👁️ 阅读次数
📝 编程学习
一、字符型一维数组回顾
char s1[10] = "hello"; // 字符数组,数据存在栈上 s1[0] = 'B'; // ✓ 可以修改 char *s2 = "hello"; // s2 指向字符串常量区 s2[0] = 'B'; // ✗ 不行——字符串常量区的数据不可修改通过指针对字符进行访问时,函数形参推荐用const修饰:
void Puts(const char *s) // const 保护:函数内部不能通过 *s 修改内容 { }二、const 与指针(回顾)
const char *s; // *s 不能被修改(const 限定基类型) char const *s; // 同上 char * const s; // s 不能被修改(const 限定指针变量本身)原则:const离谁近,就限定谁。
三、常用字符串函数手写实现
1. strlen
size_t Strlen(const char *s) { int cnt = 0; while (*s != '\0') { ++s; cnt++; } return cnt; }功能:计算\0前有效字符的个数。
2. strcpy
char *Strcpy(char *dest, const char *src) { char *ret = dest; while (*src != '\0') { *dest = *src; dest++; src++; } *dest = '\0'; return ret; }功能:将src的字符逐个拷贝到dest中,最后以\0结尾。
返回dest的目的是支持链式操作,例如:
// 类似 a = b = c = 1 的链式赋值 printf("%s\n", Strcpy(buf, "hello"));3. strncpy
char *Strncpy(char *dest, const char *src, size_t n) { char *ret = dest; while (n > 0 && *src != '\0') { *dest = *src; dest++; src++; n--; } while (n > 0) // src 已经拷完,剩余用 0 填充 { *dest = 0; dest++; n--; } return ret; }功能:将src的前 n 个字符拷贝到dest中。
- 若
n < strlen(src):拷贝 n 次就结束 - 若
n > strlen(src):src拷完后,剩余位置补0
4. strcat
char *Strcat(char *dest, const char *src) { char *ret = dest; // 1. 定位到 dest 的 '\0' 位置 while (*dest != '\0') dest++; // 2. 从 '\0' 位置开始,将 src 中字符逐个给到 dest while (*src != '\0') { *dest = *src; dest++; src++; } // 3. 最后加上 '\0' *dest = '\0'; return ret; }功能:将src拼接到dest末尾。
5. strncat
char *Strncat(char *dest, const char *src, size_t n) { char *ret = dest; while (*dest != '\0') dest++; while (n > 0 && *src != '\0') { *dest = *src; dest++; src++; n--; } *dest = '\0'; // 无论 n 是否耗时,都以 '\0' 结尾 return ret; }功能:将src的前 n 个字符拼接到dest末尾。
- 若
n < strlen(src):拼接 n 个字符后结束,加\0 - 若
n > strlen(src):src拼完即结束,加\0
6. strcmp
int Strcmp(const char *s1, const char *s2) { while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) { s1++; s2++; } return *s1 - *s2; }功能:从左到右逐个字符比较,遇到不相同的字符或\0结束,返回差值。
- 返回
0:两字符串相等 - 返回正数:
s1 > s2 - 返回负数:
s1 < s2
7. strncmp
int Strncmp(const char *s1, const char *s2, size_t n) { while (n > 0 && *s1 != '\0' && *s2 != '\0' && *s1 == *s2) { s1++; s2++; n--; } if (n == 0) return 0; return *s1 - *s2; }功能:比较s1和s2的前 n 个字符,比够 n 个或遇到差异就结束。
四、补充:定义多个指针变量
int *p, a; // p 是 int* 指针变量,a 是 int 型变量 int *p, *q, *r; // p、q、r 都是 int* 指针变量
*是跟变量名结合的,不是跟类型结合的。定义多个指针时,每个变量前都要加*。
五、指针操作二维数组
1. 整型二维数组
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};二维数组的本质是一维数组的数组。a包含 3 个元素,每个元素的类型是int[4]。
如何定义指针指向二维数组
关键:看首元素的类型。
- 首元素是
a[0],数据类型为int[4] - 首元素地址
&a[0]的类型为int(*)[4](数组指针)
int (*p)[4] = a; // 等价于 int (*p)[4] = &a[0];
int(*p)[4]的含义:基类型是int[4](一个包含4个int的数组),p是指向这种数组的指针。
通过指针访问二维数组元
2. 字符型二维数组
char s[3][10] = {"hello1", "hello2", "hello3"};等价理解:char[10] s[3],即s是一个包含 3 个char[10]的数组。
char (*p)[10] = s; // p 指向首元素 &s[0],类型为 char(*)[10] p[i] // 第i行整个一行的地址,类型 char(*)[10] *(p[i]) // 第i行的首字符地址,类型 char* printf("%s\n", *(p + i)); // 输出第i行字符串3. 指针数组 vs 数组指针
// 字符数组方式——数据存在栈上的二维数组中 char s[3][10] = {"hello1", "hello2", "hello3"}; // 指针数组方式——数组中存放的是指向字符串常量的指针 char *s[3] = {"hello1", "hello2", "hello3"};区别:
char s[3][10] | char *s[3] | |
|---|---|---|
| 存储 | 二维字符数组,每个字符串占固定空间 | 指针数组,每个元素是char*指针 |
| 可修改 | s[i][0] = 'B'✓ | s[0][0] = 'B'✗(指向常量区) |
| 内存 | 每行都占满10字节 | 指针占8字节(64位),字符串存于常量区 |
char *s[3] = {"hello1", "hello2", "hello3"}; s[0] // 数据类型 char*(一个指针) &s[0] // 数据类型 char**(指向 char* 的指针)
&s[0]的类型是char**——因为s[0]本身是char*类型,取它的地址就是char**。
六、二维指针操作字符串数组
当需要将指针数组传入函数时,形参使用char **:
void outputStr(char **p, int len) { for (int i = 0; i < len; i++) { printf("%s\n", *(p + i)); // *(p+i) <=> s[i] } } int main() { char *s[10] = {"hello", "world", "china", "english", "america"}; outputStr(s, 5); return 0; }理解:
s是char *[]类型,数组名代表首元素地址,首元素是char*,所以s的类型是char***(p + i)取到的是第 i 个char*指针,直接传给%s即可输出字符串
八、小结
| 知识点 | 核心要点 |
|---|---|
char s[]vschar *s | 前者在栈上可修改,后者指向常量区不可修改 |
const char * | 函数形参推荐写法,适用性更强 |
| 字符串函数 | strcpy/strncpy/strcat/strncat/strcmp/strncmp,均可手写实现 |
数组指针int(*p)[4] | 操作二维数组,基类型为一维数组 |
指针数组char *s[3] | 存放多个char*指针,每个指向一个字符串 |
二级指针char **p | 接收指针数组的首地址,*(p+i)<=>s[i] |
编程学习
技术分享
实战经验