当前位置: 首页 > news >正文

C 语言指针深度解析:从数组指针到指针函数的实战指南

        在 C 语言中,指针是提升代码效率与灵活性的核心工具,但也因其抽象性成为学习难点。本文将围绕数组指针、指针数组、字符指针、指针函数等关键概念,结合实例代码详细解析,帮助大家理清指针的核心用法与区别。

指针

数组指针与指针数组

数组指针

        指针和数组中符号优先级

() > [] > *

        通过指针引用二维数组

表示形式含义
arr二维数组名,指向一维数组arr[0],0行首地址
arr[0],*(arr+0), *arr0行0列元素地址,数组降级为指针
arr + 1,&arr[1]1行首地址
arr[1],*(arr+1)1行0列元素arr[1] [0]的地址
arr[1]+2,*(arr+1)+2,&arr[1] [2]1行2列元素arr[1] [2]的地址
*(arr[1]+2), *( *(arr+1)+2),arr[1] [2]1行2列元素 arr[1] [2]的值

        注意:二维数组中,数组整体的地址值 数组中0行元素的地址值 数组中0行0列元素的地址值

案例1

        需求:用指向元素的指针变量输出二维数组元素的值

        代码:

/*****************************************************************
********
> File Name: demo03.c
> Author:千夕
> Description:
> Created Time: 2025年05月21日 星期三 10时57分26秒
******************************************************************
******/
#include <stdio.h>
int main(int argc,char *argv[])
{// 定义一个二维数组int arr[3][4] = {1,3,5,7,11,33,55,77,111,333,555,777};// 创建一个数组指针指向二维数组int (*p)[4] = arr; // 二维数组中的第一个元素就是首行 int (*p)[3][4]// 创建两个变量,接收控制台输入的行和列int row, col;printf("请输入行号和列号:\n");scanf("%d,%d",&row,&col);printf("arr[%d][%d]=%d,%d,%d,%d\n",row,col,*(*(p+row)+col),(*(p+row))[col],*(p[row]+col),p[row][col]);return 0;
}
指针数组

定义:指针数组是一个数组,数组中每一个元素都是一个指针。

特点:

        先有指针,后有数组

        指针数组本质是一个数组,只是数组中的每一个元素是指针

语法:

数据类型 *数组名[容量];

案例:

/*************************************************************************> File Name:    demo03.c> Author:       千夕> Description:  > Created Time: 2025年07月28日 星期一 11时32分11秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{int a =10,b = 20,c =30;int *arr[3] = {&a,&b,&c};int len = sizeof(arr) / sizeof(arr[0]);for(int i = 0;i < len;i++){printf("%-4d",*arr[i]);}return 0;
}

        建议:我们一般使用指针数组处理字符串。

数组指针与指针数组的区别

对比项指针数组数组指针
定义数组的元素均为指针的数组指向一个完整数组的指针
存储内容存储多个指针,每个元素指向不同内存地址存储单个指针,指向一个完整的数组(首地址)
内存分配每个指针元素独立分配内存,可能分散指向的数组内存连续,指针本身存储数组首地址
语法示例int *arr[5] (元素为5个int *指针)int (*arr)[5] (指向5个int的数组的指针)
访问方式通过下标访问指针元素,再解引用:*arr[i]先解引用指针得到数组,再访问元素:(*arr)[i]
使用场景管理多个独立指针(如字符串数组、动态结构体数组)操作多维数组,(如传递二维数组的行指针))
内存布局[ptr1] → 数据1 [ptr2] → 数据2…ptr → s[数据1] [数据2]
示例代码int a = 1,b = 2,int *arr[] = {&a,&b}int arr[2] [3] = {1,2,3,4,5,6};int(*ptr)[3] = arr;
字符数组与字符指针
字符串实现

在C语言中,表示一个字符串有以下两种方式:

        ① 数组形式:用字符数组存放一个字符串

        ② 指针形式:用字符指针指向一个字符串

案例2
/*********************************************************************
****
> File Name: demo04.c
> Author: qianxi
> Description:
> Created Time: 2025年07月28日 星期一 14时15分31秒
**********************************************************************
**/
#include <stdio.h>
/**
* 方式1:使用字符数组实现字符串
*/
void str_test1()
{// 定义一个伪字符串char str[] = "I LOVE YOU";// 数组名是一个常量,也就是不支持赋值// str = "YUE QIAN"; // 编译报错,常量不支持修改,替代方案:strcpy(str,"YUE QIAN");printf("%s\n", str); // 数组在传参时,会被降级为指针
}
/**
* 方式2:使用字符指针指向字符串
*/
void str_test2()
{// 定义一个伪字符串char *str = "I LOVE YOU"; // 指针str指向一个字符串常量// 改变str的指向// str = "YUE QIAN";printf("%s\n", str);
}
int main(int argc,char *argv[])
{str_test1();str_test2();char arr[200];char *str = arr; // 局部变量,如果未初始化,默认是随机值,如果是指针变量,地址值就是随机printf("请输入一个字符串:\n");scanf("%s", str);printf("%s\n", str);return 0;
}

        注意:字符数组和字符指针变量都能实现字符串的存储与运算。(字符指针—> 字符类型的指针变量)

字符数组和字符指针的联系
概念

        字符数组由元素组成,每个元素中存放一个字符;而字符指针(指向char类型的指针变量)中存放的是地址;

        只能对字符数组中的各个元素赋值,而不能用赋值语句对整个字符数组赋值。        

char arr[3] = {}; // 等价于 {'\0'}; 等价于 {0};
arr[2] = 'A'; // 正确,对字符数组的元素赋值
arr = {'E','D','F'};// 错误,数组名是常量,不能对其进行整体赋值

        字符数组名虽然代表地址,但是数组名的值不能改变,因为数组名是常量。

char a = 'A';
char arr[50] = {};
char *p = arr; // 指针p指向数组第一个元素,p存储的是arr数组中第一个元素的地址
p = &a; // 改变指针p的指向,使其指向变量a,p存储的是a的地址值
p++; // p指向arr的第2个元素
arr+5;
*p = 'W';
printf("%c %c\n", *p, *arr);
return 0;

        对于字符串中字符的存取,可以用下标法,也可以用指针法。

/*****************************************************************
********
> File Name: demo05.c
> Author:千夕
> Description:
> Created Time: 2025年05月21日 星期三 14时58分00秒
******************************************************************
******/
#include <stdio.h>
int main(int argc,char *argv[])
{// 使用两种方式创建字符串char str1[] = "你好,双哥哥!";char *str2 = "你好,豪哥哥!";// 0x11// 赋值测试// str1 = "你好,强哥哥!"; // 错误,数组一旦创建,就无法改变其值。str2 = "你好,帅哥哥!";// 0x12// 打印测试printf("%s,%s\n", str1, str2);// 测试从控制台获取一个字符串// char *str;// 此时默认值是NULL,NULL对应的空间地址0x00000000,这块空间拒绝访问// printf("请输入一个字符串:\n");// scanf("%s",str);// printf("输出-%s\n",str);// 输出NULL// 注意:从控制台接收一个字符串只能用字符数组char a[] = "I LOVE YOU!";char *b = "I LOVE YOU!";printf("%c,%c\n%c,%c\n%s,%s\n",a[2],*(a+2),b[2],*(b+2),a+2,b+2);// L,L L,L1 2return 0;
}  
字符串作为形参
定义

        实参与形参都可以是字符数组

void fun(char str[], int len) {..}
void main()
{char str[] = "hello";int len = sizeof(str) / sizeof(str[0]);fun(str, len)
}

        实参用字符数组,形参用字符指针

void fun(char *str, int len) {..}
void main()
{char str[] = "hello";int len = sizeof(str) / sizeof(str[0]);fun(str, len)
}

        形参和实参都是字符指针。(在函数内部不能对字符串常量中的字符做修改)

/*****************************************************************
********
> File Name: demo06.c
> Author: qianxi
> Description:
> Created Time: 2025年07月28日 星期一 15时26分14秒
******************************************************************
******/
#include <stdio.h>
void fun(char *str, int len) {printf("%s\n", str);// 0x1000 str依然指向“hello"这个常量空间// *(str+1) = 'E';// 编译错误 不能修改"hello"这个常量空间的数据// str[2] = 'L'; // 编译错误str = "zhangsanfeng"; // 0x2000 此时并没有改变常量空间的数据,只是改变了指针的指向printf("%s\n", str);
}
void main()
{char *str = "hello"; // 0x1000 str指向的"hello"是一个常量空间,常量空间不支持修改int len = sizeof(str) / sizeof(str[0]);fun(str, len); // 0x1000
}

        实参是字符指针,形参是字符数组。(在函数内部不能对字符串常量中的字符做修改)

/*****************************************************************
********
> File Name: demo06.c
> Author: 千夕
> Description:
> Created Time: 2025年07月28日 星期一 15时26分14秒
******************************************************************
******/
#include <stdio.h>
void fun(char str[], int len) {printf("%s\n", str);// 0x1000 str依然指向“hello"这个常量空间// *(str+1) = 'E';// 编译错误 不能修改"hello"这个常量空间的数据// str[2] = 'L'; // 编译错误str = "zhangsanfeng"; // 此时并没有改变常量空间的数据,只是改变了指针的指向printf("%s\n", str);
}
void main()
{// char str[] = {'h','e','l','l','o','\0'}; // 这个可以看做是字符串变量,这个是支持修改元素char *str = "hello"; // 0x1000 str指向的"hello"是一个常量空间,常量空间不支持修改int len = sizeof(str) / sizeof(str[0]);fun(str, len);// 0x1000
}

注意

  1. 字符数组在创建的时候,会在内存中开辟内存空间,内存空间可以存放字符数据;字符指针在创建的时候,需要依赖于字符数组,字符指针在内存开辟的内存空间中,存放的是数组元素的地址。字符指针的创建依赖于字符数组,字符数组可以独立存在,而字符指针不能独立存在。

  2. 字符数组可以初始化,但是不能赋值;字符指针可以初始化,也可以值。

char str1[] = "hello"; // 对数组初始化
str1 = "hi"; // 对数组赋值,此时错误
str1[0] = 'H'; // 对数组中的元素赋值,此时正确
案例3

        字符指针作为函数参数:用函数调用实现字符串的复制以及长度计算

        代码:

/*****************************************************************
********
> File Name: demo07.c
> Author: 千夕
> Description:
> Created Time: 2025年07月28日 星期一 15时50分22秒
******************************************************************
******/
#include <stdio.h>
/**
* 定义一个函数,实现字符串拷贝
* @param source 拷贝的源字符串,该字符串不能被修改
* @param dest 需要拷贝的目标数组
* @return 字符串的长度
*/
int _str_cpy(const char *source, char *dest)
{// 定义一个循环变量register int i = 0;// 遍历循环while (source[i] != '\0'){// 实现拷贝*(dest + i) = *(source + i); // 等价于 dest[i] = source[i];i++;}// 拷贝结束,一定要给dest中插入\0*(dest + i) = '\0';return i;
}
int main(int argc,char *argv[])
{char source[20],dest[20];printf("请输入一个字符串:\n");scanf("%s", source);int size = _str_cpy(source, dest);printf("字符串:%s的长度是%d\n", dest, size);return 0;
}
案例4

        需求:字符指针作为函数的参数-给定一个字符串,截取start到end之间的字符串,含头不含尾

        代码:

/*****************************************************************
********
> File Name: demo08.c
> Author: 千夕
> Description:
> Created Time: 2025年07月28日 星期一 16时06分51秒
******************************************************************
******/
#include <stdio.h>
/**
* 定义一个函数,实现字符串的截取
* @param source 源字符串(字符数组、字符串常量、字符指针)
* @param start 开始位置
* @param end 结束位置
* @param dest 目标数组(字符数组)
* @return 目标字符串长度
*/
int str_substr(const char *source, int start, int end, char *dest)
{register int i = 0, k = 0;// 遍历字符串while (source[i] != '\0'){// 根据start和end截取if (i >= start && i < end) // 含头不含尾{*(dest + k) = *(source + i); // hellok++;}i++;}*(dest + k) = '\0';return k;
}
int main(int argc,char *argv[])
{char *str = "abcdefg";// cdechar dest[26];int len = str_substr(str,2,5,dest);printf("%s,%s,%d\n", str, dest, len);// abcdefg,cde,3return 0;
}  

函数指针与指针函数

指针函数
定义:

        本质上是上函数,这个函数的返回值类型是指针,这个函数称之为指针函数。(返回值是

指针的函数叫做指针函数)

语法:
// 写法1
返回类型* 函数名(形参列表)
{
函数体;
return 指针;
}
// 写法2
返回类型 *函数名(形参列表)
{
函数体;
return 指针;
}

举例:

int *get(int a)
{
int *p = &a;
return p;
}
int main()
{
int *a = get(5);
printf("%d\n", *a);
}

注意:

        在函数中不要直接返回一个局部变量的地址。因为函数调用完毕后,随着栈帧的回收,变量空间会销毁,使得返回的地址就不明确,此时返回的指针叫做野指针。

解决方案:

        如果非要访问,可以给这个局部变量添加(定义的时候添加) static ,可以延长它的生命周期,从而避免野指针(尽量少用,因为存在内存泄漏)

演示案例

/*********************************************************************
****
> File Name: demo05.c
> Author: qianxi
> Description:
> Created Time: 2025年05月22日 星期四 14时22分07秒
*********************************************************************
***/
#include <stdio.h>
int *add(int a, int b)
{
static int sum;
sum = a + b;
return &sum;// 执行完return 作为函数作用域的布局变量sum的空间被释放
}
int main(int argc,char *argv[])
{
int *res = add(5,3); // 接收到了地址,但是地址对应的空间已经释放
printf("%d\n", *res);
return 0;
}
案例5

        需求:有若干个学生,每个学生有4门成绩,要求在用户输入学号(int id)后,能输出该学生的全部成绩(float scores[4]),用指针函数实现。

代码:

/*****************************************************************
********
> File Name: demo09.c
> Author: qianxi
> Description:
> Created Time: 2025年07月28日 星期一 17时11分38秒
******************************************************************
******/
#include <stdio.h>
/**
* 定义一个函数,要求输入学号,返回该学号对应学生的4门成绩
* @param all:所有人的成绩传进来 查找源头
* @param id:要检索学生的学号
* @return id对应学生的4门成绩
*/
float* search(float (*all)[4], int id)
{// 定义一个指针变量,用来接收查询到的学生的所有成绩float *pt;pt = *(all + id);// 行偏移 {10,20,30,40}return pt;
}
int main(int argc,char *argv[])
{// 准备一个二维数组,存储3个学生的成绩float scores[3][4] = {{60,70,80,90}, // 0x2000 0x2004 0x2008 0x200C{66,77,88,99}, // 0x2010 0x2014 0x2018 0x201C{61,71,81,91}};// 定义一个变量,用来接收学生学号int id;printf("请输入学生学号(0~2):\n");scanf("%d", &id);printf("第%d个学生的成绩:\n", id);// 创建一个指针,用来接收成绩float *p;// 0x11 --> 0x2000p = search(scores, id);// 0x21 --> 0x2000// 遍历for (; p < scores[id] + 4; p++){printf("%5.2f\t", *p);}// *(p + i)printf("\n");return 0;
}   

 

总结

指针是 C 语言的灵魂,本文梳理了四大核心概念:

  • 数组指针:指向数组的指针,适合操作二维数组;
  • 指针数组:元素为指针的数组,适合管理多个独立地址(如字符串);
  • 字符指针:与字符数组相比更灵活,但需注意字符串常量的只读特性;
  • 指针函数:返回指针的函数,需避免返回局部变量地址。
http://www.dtcms.com/a/303838.html

相关文章:

  • 【21】C# 窗体应用WinForm ——图片框PictureBox属性、方法、实例应用
  • 重生之我在暑假学习微服务第四天《Docker-下篇》
  • Intellij Idea--解决Cannot download “https://start.spring.io‘: Connect timedout
  • React面试题目和答案大全
  • 队列算法之【用队列实现栈】
  • 系统重启过程和启动目标
  • Note3: CNN(卷积神经网络)
  • java每日精进 7.29【框架数据权限详解】
  • 远程Qt Creator中文输入解决方案
  • day064-kodbox接入对象存储与配置负载均衡
  • linux命令tail的实际应用
  • 网络数据传输与NAT技术的工作原理
  • 社区老人健康信息管理系统|基于springboot社区老人健康信息管理系统设计与实现(源码+数据库+文档)
  • SSO CAS+Shiro+springmvc单点登录解决方案
  • 符号计算与算法实践|使用Maple教授​​群论​​和​​图论​​课程
  • 【 MySQL集群架构与实践1】使用Docker实现简单主从复制
  • uni-app x开发避坑指南:拯救被卡顿的UI线程!
  • 【CF】Day114——杂题 (贪心 + 图论 | LCM + 贪心 | 最大最小子序列 + 图论)
  • 图论:Bellman_ford算法
  • docker设置iptables=false后容器内部无法互相访问
  • vue3组件通信的几种方法,详解
  • 工业补贴携手华为云,重塑汽车零部件供应链管理新趋势
  • B 站搜一搜关键词优化:精准触达用户的流量密码
  • 51c大模型~合集161
  • SQL注入SQLi-LABS 靶场less26-30详细通关攻略
  • Elasticsearch 深度分页问题与 `search_after` 解决方案
  • 从centos更换至ubuntu的安装、配置、操作记录
  • cpolar 内网穿透 ubuntu 使用石
  • 知识点梳理
  • 基于三台主机搭建 Web 服务环境:Nginx、NFS 与 DNS 配置全流程