我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

C语言指针超级进阶:字符与字符串数组、string 库函数原型、指针与二维数组

C语言指针超级进阶:字符与字符串数组、string 库函数原型、指针与二维数组 1. 字符指针与字符串在 C 语言中字符串本质上是以\0结尾的字符数组。理解指针与字符串的关系是掌握指针进阶的第一步。1.1 用字符指针指向字符串#includestdio.hintmain(void){char*strhello csdn;printf(%s\n,str);return0;}这里str是一个字符指针指向字符串常量hello csdn的首字符。printf从该地址开始逐字节输出直到遇到\0为止。1.2 字符串常量不可被修改下面的写法会引发未定义行为char*strhello;str[0]H;/* 错误字符串常量通常存储在只读区 */正确做法是使用字符数组来存放可修改的字符串charbuf[]hello;buf[0]H;/* 正确buf 是栈上的可写内存 */1.3 字符数组与字符指针的区别对比项字符数组char s[]字符指针char *p内存来源栈或全局可写区指向字符串常量只读区内容可修改可以不可以指向常量时指针本身可变数组名是常量不可重新赋值可以改变指向sizeof含义整个数组字节数指针本身大小通常 8 或 4 字节#includestdio.hintmain(void){chars[]csdn;char*pcsdn;printf(sizeof(s) %zu\n,sizeof(s));/* 5含 \0 */printf(sizeof(p) %zu\n,sizeof(p));/* 8 或 4指针大小 *//* s p; 非法数组名不能作为左值 */ps;/* 合法指针可以重新指向 */return0;}2. 字符串数组字符串数组用于存放多个字符串常见有两种组织方式二维字符数组和指针数组。2.1 二维字符数组每个字符串占据固定长度的行适合长度相近且需要修改的场景#includestdio.hintmain(void){charnames[][16]{zhangsan,lisi,wangwu};for(inti0;i3;i){printf(%s\n,names[i]);}return0;}names[i]表示第i行的首地址类型为char *可以直接传给字符串处理函数。2.2 指针数组每个元素是指针指向不同长度的字符串内存更紧凑但指向常量时不可修改#includestdio.hintmain(void){constchar*colors[]{red,green,blue,yellow};for(inti0;i4;i){printf(%s\n,colors[i]);}return0;}2.3 两种写法对比二维字符数组内存连续、每行定长、内容可改适合固定长度的字符串集合。指针数组每行长度灵活、仅存地址更省内存但若指向字符串常量则内容不可改。3. 常用 string 库函数原型C 标准库string.h提供了一系列字符串操作函数。理解它们的函数原型与实现原理能更好地掌握指针运算。3.1 求长度strlensize_tstrlen(constchar*s);返回字符串长度不含结尾的\0。手写实现#includestddef.hsize_tmy_strlen(constchar*s){constchar*ps;while(*p!\0){p;}return(size_t)(p-s);}3.2 复制strcpy / strncpychar*strcpy(char*dest,constchar*src);char*strncpy(char*dest,constchar*src,size_tn);strcpy将src连同\0复制到dest不检查目标缓冲区大小易溢出。strncpy最多复制n个字符若src短于n则用\0补齐若src达到n仍未遇\0目标可能不以\0结尾。手写strcpy实现char*my_strcpy(char*dest,constchar*src){char*retdest;while((*dest*src)!\0){;}returnret;}推荐在日常开发中使用更安全的strncpy注意手动补\0或snprintf。3.3 拼接strcat / strncatchar*strcat(char*dest,constchar*src);char*strncat(char*dest,constchar*src,size_tn);strcat把src追加到dest末尾同样不检查缓冲区大小。strncat最多追加n个字符并始终补上\0相对更安全。手写strcat实现char*my_strcat(char*dest,constchar*src){char*retdest;while(*dest!\0){dest;}while((*dest*src)!\0){;}returnret;}3.4 比较strcmp / strncmpintstrcmp(constchar*s1,constchar*s2);intstrncmp(constchar*s1,constchar*s2,size_tn);按字典序逐字符比较返回负数表示s1 s2返回 0 表示相等返回正数表示s1 s2。手写strcmp实现intmy_strcmp(constchar*s1,constchar*s2){while(*s1!\0*s1*s2){s1;s2;}return(unsignedchar)*s1-(unsignedchar)*s2;}3.5 查找字符strchr / strrchrchar*strchr(constchar*s,intc);char*strrchr(constchar*s,intc);strchr返回字符c在字符串中首次出现的位置指针找不到返回NULL。strrchr返回最后一次出现的位置。手写strchr实现char*my_strchr(constchar*s,intc){while(*s!\0){if(*s(char)c){return(char*)s;}s;}if((char)c\0){return(char*)s;/* 匹配结尾 \0 */}returnNULL;}3.6 查找子串strstrchar*strstr(constchar*haystack,constchar*needle);在haystack中查找子串needle返回首次出现位置找不到返回NULL。手写实现#includestddef.hchar*my_strstr(constchar*haystack,constchar*needle){if(*needle\0){return(char*)haystack;}while(*haystack!\0){constchar*hhaystack;constchar*nneedle;while(*n!\0*h*n){h;n;}if(*n\0){return(char*)haystack;}haystack;}returnNULL;}3.7 使用示例#includestdio.h#includestring.hintmain(void){charbuf[32]hello;printf(len%zu\n,strlen(buf));/* 5 */strcpy(buf,world);strcat(buf,!);printf(%s\n,buf);/* world! */printf(cmp%d\n,strcmp(abc,abd));/* -1 或负数 */printf(chr%s\n,strchr(hello,l));/* llo */printf(str%s\n,strstr(hello,ll));/* llo */return0;}4. 指针与二维数组二维数组在内存中依然是按行连续存储的掌握「数组名 → 数组指针」的转换是指针进阶的关键。4.1 二维数组的内存布局#includestdio.hintmain(void){inta[2][3]{{1,2,3},{4,5,6}};printf(a[0][0] %d\n,a[0][0]);/* 1 */printf(%d\n,*(*a2));/* 3 */printf(%d\n,*(*(a1)0));/* 4 */return0;}内存布局如下a[0][0] 1a[0][1] 2a[0][2] 3a[1][0] 4a[1][1] 5a[1][2] 6a的类型是「指向int[3]的指针」即数组指针int (*)[3]。a i跳过一整行3 个int。*(a i)得到第i行的首地址再 j定位到该行第j个元素。因此a[i][j]等价于*(*(a i) j)。4.2 数组指针数组指针专门指向一个固定长度的数组#includestdio.hintmain(void){inta[2][3]{{1,2,3},{4,5,6}};int(*p)[3]a;/* p 指向 int[3] */for(inti0;i2;i){for(intj0;j3;j){printf(%d ,p[i][j]);}printf(\n);}return0;}其中int (*p)[3]表示「p 是一个指针指向包含 3 个int的数组」。4.3 数组指针与指针数组的区别这是最易混淆的知识点int(*p)[3];/* 数组指针指向 int[3]通常用于二维数组 */int*q[3];/* 指针数组q 有 3 个元素每个元素是 int* */优先级说明[]高于*所以int *q[3]先与[3]结合成数组元素是指针 → 指针数组。int (*p)[3]括号让p先与*结合成指针再指向数组 → 数组指针。指针数组示例#includestdio.hintmain(void){intx1,y2,z3;int*arr[3]{x,y,z};for(inti0;i3;i){printf(%d ,*arr[i]);}printf(\n);return0;}4.4 二维数组作为函数参数二维数组传参时第一维可以省略第二维必须指定本质上传入的是数组指针#includestdio.hvoidprint_matrix(int(*matrix)[3],introws){for(inti0;irows;i){for(intj0;j3;j){printf(%d ,matrix[i][j]);}printf(\n);}}/* 等价写法void print_matrix(int matrix[][3], int rows) */intmain(void){inta[2][3]{{1,2,3},{4,5,6}};print_matrix(a,2);return0;}matrix[i][j]同样等价于*(*(matrix i) j)理解这一点后即可灵活使用指针遍历二维数组。5. 总结字符指针char *指向字符串首地址指向的字符串常量不可修改。字符串数组二维字符数组内容可改、定长指针数组灵活省内存。string 库函数牢记strlen、strcpy、strcat、strcmp、strchr、strstr等原型体会指针移动的共性并注意缓冲区安全。指针与二维数组a[i][j]等价于*(*(a i) j)分清数组指针int (*p)[n]与指针数组int *p[n]。抓住「指针即地址、指针加减按其所指类型为单位移动」这一本质再结合上述原型与数组结构反复练习就能稳步跨过指针进阶这道坎。
返回列表