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. strlensize_t Strlen(const char *s) { int cnt 0; while (*s ! \0) { s; cnt; } return cnt; }功能计算\0前有效字符的个数。2. strcpychar *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. strncpychar *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拷完后剩余位置补04. strcatchar *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. strncatchar *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拼完即结束加\06. strcmpint Strcmp(const char *s1, const char *s2) { while (*s1 ! \0 *s2 ! \0 *s1 *s2) { s1; s2; } return *s1 - *s2; }功能从左到右逐个字符比较遇到不相同的字符或\0结束返回差值。返回0两字符串相等返回正数s1 s2返回负数s1 s27. strncmpint 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)); // *(pi) 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接收指针数组的首地址*(pi)s[i]