news 2026/10/8 18:54:10

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言指针超级进阶:字符与字符串数组、string 库函数原型、指针与二维数组

1. 字符指针与字符串

在 C 语言中,字符串本质上是以\0结尾的字符数组。理解指针与字符串的关系,是掌握指针进阶的第一步。

1.1 用字符指针指向字符串

#include<stdio.h>intmain(void){char*str="hello csdn";printf("%s\n",str);return0;}

这里str是一个字符指针,指向字符串常量"hello csdn"的首字符。printf从该地址开始逐字节输出,直到遇到\0为止。

1.2 字符串常量不可被修改

下面的写法会引发未定义行为:

char*str="hello";str[0]='H';/* 错误!字符串常量通常存储在只读区 */

正确做法是使用字符数组来存放可修改的字符串:

charbuf[]="hello";buf[0]='H';/* 正确,buf 是栈上的可写内存 */

1.3 字符数组与字符指针的区别

对比项字符数组char s[]字符指针char *p
内存来源栈或全局可写区指向字符串常量(只读区)
内容可修改可以不可以(指向常量时)
指针本身可变数组名是常量,不可重新赋值可以改变指向
sizeof含义整个数组字节数指针本身大小(通常 8 或 4 字节)
#include<stdio.h>intmain(void){chars[]="csdn";char*p="csdn";printf("sizeof(s) = %zu\n",sizeof(s));/* 5,含 '\0' */printf("sizeof(p) = %zu\n",sizeof(p));/* 8 或 4,指针大小 *//* s = p; 非法,数组名不能作为左值 */p=s;/* 合法,指针可以重新指向 */return0;}

2. 字符串数组

字符串数组用于存放多个字符串,常见有两种组织方式:二维字符数组和指针数组。

2.1 二维字符数组

每个字符串占据固定长度的行,适合长度相近且需要修改的场景:

#include<stdio.h>intmain(void){charnames[][16]={"zhangsan","lisi","wangwu"};for(inti=0;i<3;i++){printf("%s\n",names[i]);}return0;}

names[i]表示第i行的首地址,类型为char *,可以直接传给字符串处理函数。

2.2 指针数组

每个元素是指针,指向不同长度的字符串,内存更紧凑,但指向常量时不可修改:

#include<stdio.h>intmain(void){constchar*colors[]={"red","green","blue","yellow"};for(inti=0;i<4;i++){printf("%s\n",colors[i]);}return0;}

2.3 两种写法对比

  • 二维字符数组:内存连续、每行定长、内容可改,适合固定长度的字符串集合。
  • 指针数组:每行长度灵活、仅存地址更省内存,但若指向字符串常量则内容不可改。

3. 常用 string 库函数原型

C 标准库<string.h>提供了一系列字符串操作函数。理解它们的函数原型与实现原理,能更好地掌握指针运算。

3.1 求长度:strlen

size_tstrlen(constchar*s);

返回字符串长度,不含结尾的\0。

手写实现:

#include<stddef.h>size_tmy_strlen(constchar*s){constchar*p=s;while(*p!='\0'){p++;}return(size_t)(p-s);}

3.2 复制:strcpy / strncpy

char*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*ret=dest;while((*dest++=*src++)!='\0'){;}returnret;}

推荐在日常开发中使用更安全的strncpy(注意手动补\0)或snprintf。

3.3 拼接:strcat / strncat

char*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*ret=dest;while(*dest!='\0'){dest++;}while((*dest++=*src++)!='\0'){;}returnret;}

3.4 比较:strcmp / strncmp

intstrcmp(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 / strrchr

char*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 查找子串:strstr

char*strstr(constchar*haystack,constchar*needle);

在haystack中查找子串needle,返回首次出现位置,找不到返回NULL。

手写实现:

#include<stddef.h>char*my_strstr(constchar*haystack,constchar*needle){if(*needle=='\0'){return(char*)haystack;}while(*haystack!='\0'){constchar*h=haystack;constchar*n=needle;while(*n!='\0'&&*h==*n){h++;n++;}if(*n=='\0'){return(char*)haystack;}haystack++;}returnNULL;}

3.7 使用示例

#include<stdio.h>#include<string.h>intmain(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 二维数组的内存布局

#include<stdio.h>intmain(void){inta[2][3]={{1,2,3},{4,5,6}};printf("a[0][0] = %d\n",a[0][0]);/* 1 */printf("%d\n",*(*a+2));/* 3 */printf("%d\n",*(*(a+1)+0));/* 4 */return0;}

内存布局如下:

a[0][0] = 1

a[0][1] = 2

a[0][2] = 3

a[1][0] = 4

a[1][1] = 5

a[1][2] = 6

  • a的类型是「指向int[3]的指针」,即数组指针int (*)[3]。
  • a + i跳过一整行(3 个int)。
  • *(a + i)得到第i行的首地址,再+ j定位到该行第j个元素。
  • 因此a[i][j]等价于*(*(a + i) + j)。

4.2 数组指针

数组指针专门指向一个固定长度的数组:

#include<stdio.h>intmain(void){inta[2][3]={{1,2,3},{4,5,6}};int(*p)[3]=a;/* p 指向 int[3] */for(inti=0;i<2;i++){for(intj=0;j<3;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先与*结合成指针,再指向数组 → 数组指针。

指针数组示例:

#include<stdio.h>intmain(void){intx=1,y=2,z=3;int*arr[3]={&x,&y,&z};for(inti=0;i<3;i++){printf("%d ",*arr[i]);}printf("\n");return0;}

4.4 二维数组作为函数参数

二维数组传参时,第一维可以省略,第二维必须指定,本质上传入的是数组指针:

#include<stdio.h>voidprint_matrix(int(*matrix)[3],introws){for(inti=0;i<rows;i++){for(intj=0;j<3;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]。

抓住「指针即地址、指针加减按其所指类型为单位移动」这一本质,再结合上述原型与数组结构反复练习,就能稳步跨过指针进阶这道坎。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 18:49:44

冬天掉的电会还给你,夏天掉的不会:笔记本电池的两条账

冬天掉的电会还给你&#xff0c;夏天掉的不会&#xff1a;笔记本电池的两条账 先分清两件事&#xff1a;一种损失会还给你&#xff0c;一种不会 现象你每年都遇到一次&#xff1a;冬天笔记本的续航从六小时变成三小时&#xff1b;手机在室外掉到 8% 直接黑屏&#xff0c;进屋放…

作者头像 李华
网站建设 2026/10/8 18:48:20

AI 论文降重改写,几款常用降AI率软件选哪个才不踩坑

摘要&#xff1a;本文围绕论文写作中的改写与降重需求&#xff0c;比较了几款常见的AI辅助工具&#xff0c;从改写能力、语言润色、引用规范等维度做横向梳理&#xff0c;并给出按写作阶段和语种匹配的选型思路。结论是先看清自己卡在改写还是润色&#xff0c;再决定用哪一类工…

作者头像 李华
网站建设 2026/10/8 18:47:58

pstack+Claude Code:AI辅助定位C++服务线程卡死与死锁

做后端服务维护的朋友&#xff0c;大概率都碰过这种场景&#xff1a;线上进程还挂着&#xff0c;端口能通&#xff0c;业务却像被按了暂停键&#xff0c;请求全部卡住。我第一次被拉去处理这类问题时&#xff0c;第一反应就是掏出 pstack 打一份线程栈&#xff0c;结果面对 80 …

作者头像 李华
网站建设 2026/10/8 18:46:59

DeepSeek开源昇腾基础组件:国产算力的软件栈补得怎么样了

9月30日&#xff0c;DeepSeek 宣布开源一整套面向华为昇腾算力平台的基础设施组件&#xff0c;包括 TileLang 高级语言编译工具、高性能计算库和分布式通信库。据 DeepSeek 官方消息&#xff0c;这些组件与其此前在英伟达平台上开源的组件一一对应。换句话说&#xff0c;DeepSe…

作者头像 李华