C语言指针进阶——字符串函数手写实现、指针与二维数组

📅 2026/7/30 5:35:12 👁️ 阅读次数 📝 编程学习
C语言指针进阶——字符串函数手写实现、指针与二维数组

一、字符型一维数组回顾

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; }

功能:比较s1s2前 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; }

理解:

  • schar *[]类型,数组名代表首元素地址,首元素是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]