进阶C语言:指针的进阶(1)
迪丽瓦拉
2025-05-31 03:56:13
0
关于指针这个概念从初识C语言就开始进行了解,一直到了进阶C语言,通过前面初识C语言和初阶C语言对指针的学习还是不够深入,所以在进阶中再来对指针进行深入的学习
指针的主题:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*

使用方法:

#include 
int main()
{char ch = 'w';char* pc = &ch;*pc = 'w';return 0;
}

另外一种使用方法:

#include 
int main()
{const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?printf("%s\n", pstr);return 0;
}
const char* pstr = “hello bit.”;其实本质上是将hello bit.这个字符串的首字符的地址存放在了pstr里面,也就是把h的地址存进去了

看下面一道例题:

//代码输出的结果是什么?
#include int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

在这里我们就有必要来仔细分析一下了:

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2.指针数组

在前面的章节中我们也了解过了指针数组,指针数组是一个数组,数组的每一元素都是指针
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3.数组指针

数组指针和指针数组无非就是名字颠倒一下嘛,但是数组指针和指针数组的内容完全不相同,接下来就来深入了解一下数组指针

3.1数组指针的定义

数组指针是指针还是数组呢?可以结合前面学过的来对比一下:
整形指针:是一个指针、指着指向的类型是整形
字符指针:是一个指针、指针指向的类型是字符类型
因此:数组指针也是指针,只不过它的指向的类型是一个数组
//下面哪一个是数组指针?
int *p1[10];
int (*p2)[10];
解释:
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2数组名和&数组名

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥?

我们看一段代码:

#include 
int main()
{int arr[10] = { 0 };printf("%p\n", arr);printf("%p\n", &arr);return 0;
}

可以看到数组名和&数组名的结果都是一样的,但是在前面了解过:数组名是首元素的地址,而&数组名是整个数组的地址。

再来看一段代码:

#include 
int main()
{int arr[10] = { 0 };printf("arr   = %p\n", arr);printf("&arr  = %p\n", &arr);printf("arr+1 = %p\n", arr + 1);printf("&arr+1= %p\n", &arr + 1);return 0;
}

3.3数组指针的使用

那数组指针该怎么使用呢?
#include 
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p//但是一般很少这样写代码return 0;
}
可以使用数组指针来访问二维数组
#include 
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//arr[i] == *(arr+i)//arr是一个存放整型指针的数组int* arr[] = { arr1, arr2, arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", *(arr[i]+j));}printf("\n");}return 0;
}
#include 
void print_arr(int(*arr)[5], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){printf("%-2d ", *(arr[i] + j));}printf("\n");}
}
int main()
{int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };print_arr(arr, 3, 5);return 0;
}
下列代码分表示什么意思?
int arr[5];
//整形数组
int *parr1[10];
//指针数组
int (*parr2)[10];
//数组指针
int (*parr3[10])[5];
//parr3是一个数组,数组中存放的是指针,而这个存放的指针又指向的是一个数组

4.数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1一维数组传参

下面哪段代码设计合理
#include 
//代码1
void test(int arr[])
{}
//代码2
void test(int arr[10])
{}
//代码3
void test(int* arr)
{}
int main()
{//整形数组传参int arr[10] = { 0 };test(arr);return 0;
}
代码1:传的是数组名,用数组来接收,OK
代码2:传的是数组名,用数组来接受,OK
代码3:传的是数组名,用int类型的指针来接收,OK
#include 
//代码1
void test(int *arr[10])
{}//代码
void test(int** arr)
{}int main()
{//指针数组传参int* arr2[10] = { 0 };test(arr);return 0;
}
代码1:传的是数组名,用一个指针数组来接收,OK
代码2:传的是数组名(一级指针的地址),用二级指针来接受,OK
总结:一维数组传参,形参的部分可以是数组,也可以是指针,当形参是指针的时候,要注意指针的类型

4.2二维数组传参

#include void test(int arr[3][5])//ok?
{}
//实参用二维数组,形参也使用二维数组,合理void test(int arr[][])//ok?
{}
//形参使用二维数组时省略了行和列,这样子是不合理的
//就算不知道有多少行,但得知道有一行有多少个元素,这样才可以确定下一行从哪里结束void test(int arr[][5])//ok?
{}
//形参使用二维数组,省略了行,但没有省略列,合理void test(int* arr)//ok?
{}
//形参使用整形指针,不合理,二维数组的数组名是第一行的地址,因此需要使用数组指针来接受void test(int* arr[5])//ok?
{}
//形参使用指针数组来接收,不合理void test(int(*arr)[5])//ok?
{}
//形参使用数组指针来接受,合理void test(int** arr)//ok?
{}
//二级指针接受的是一级指针的地址,但是实参是数组名,所以不合理int main()
{int arr[3][5] = { 0 };test(arr);
}
总结:二维数组传参,可以是数组,也可以是指针,如果是数组,行可以省略,但是列不能省略,如果是指针,实参是一行的地址,因此要使用数组指针

4.3一级指针传参

#include 
void print(int* p, int sz)
{         //实参传递指针,形参用指针接收int i = 0;for (i = 0; i < sz; i++){printf("%d\n", *(p + i));}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
1.可以传一级指针
2.可以传数组名
3.可以传地址

4.4二级指针传参

#include 
void test(int** ptr)
{printf("num = %d\n", **ptr);
}
int main()
{int n = 10;int* p = &n;int** pp = &p;test(pp);test(&p);return 0;
}
当函数的参数为二级指针的时候,可以接收什么参数?
1.可以传二级指针
2.可以传指针数组
3.可以传一级指针变量的地址

5.函数指针

函数指针是指针,但是它指向的对象是一个函数

看下面的一段代码:

#include 
void test()
{printf("hehe\n");
}
int main()
{printf("%p\n", test);printf("%p\n", &test);return 0;
}

输出的是两个地址,这两个地址是 test 函数的地址。

那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

#include 
void test()
{printf("hehe\n");
}
int main()
{//下面哪个变量可以存放test函数的地址void (*ptr1)();void* ptr2();return 0;
}
函数指针,首先得是一个指针,因此*要先和变量名结合,ptr1先和*结合,因此ptr1就是一个函数指针,可以用来存放test函数。ptr1是一个函数指针,指向的是一个test函数,没有函数参数,返回类型是void。

6.函数指针数组

数组是一个存放相同类型数据的存储空间

比如

整形数组:数组中每个元素都是整形

字符数组:数组中每个元素都是字符类型

指针数组:数组中每个元素都是指针

那么,要把函数的地址存放到一个数组中去,就需要函数指针的数组,那函数指针的数组该怎么定义呢?

函数指针数组是一个数组,而数组的内容是一个函数指针,因此函数指针数组就可以写成下面的格式:
void (*ptr[10]) ()

那函数指针数组有什么用途呢?

转移表
可以将许多的函数调用放在一个数组里面,然后访问的时候以下标的形式来访问数组的内容
例如:写一个计算器

1.普通方式:

//普通方式
#include 
void menu()
{printf("************************************\n");printf("****** 1.Add   ***** 2. Less  ******\n");printf("****** 3.Times ***** 4.Except ******\n");printf("************ 0. Exit ***************\n");printf("************************************\n");}
int Add(int x, int y)
{return x + y;
}
int Less(int x, int y)
{return x - y;
}int Times(int x, int y)
{return x * y;
}int Expect(int x, int y)
{return x / y;
}
int main()
{int input = 0;int x = 0;int y = 0;do{menu();printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("加法运算->");printf("请输入两个操作数:");scanf("%d %d", &x, &y);int add = Add(x, y);printf("%d + %d = %d\n", x, y, add);break;case 2:printf("减法运算->");printf("请输入两个操作数:");scanf("%d %d", &x, &y);int less = Less(x, y);printf("%d - %d = %d\n", x, y, less);break;case 3:printf("乘法运算->");printf("请输入两个操作数:");scanf("%d %d", &x, &y);int times = Times(x, y);printf("%d * %d = %d\n", x, y, times);break;case 4:printf("除法运算->");printf("请输入两个操作数:");scanf("%d %d", &x, &y);int expect = Expect(x, y);printf("%d / %d = %d\n", x, y, expect);break;case 0:printf("退出程序!\n");break;default:printf("选择错误,请重新选择:");break;}} while (input);return 0;
}
可以看到,如果这样子写代码太多了,而且如果需要再加入一种运算,就会变得很麻烦,所以需要使用转移表

2.转移表:

#include 
void menu()
{printf("************************************\n");printf("****** 1.Add   ***** 2. Less  ******\n");printf("****** 3.Times ***** 4.Except ******\n");printf("************ 0. Exit ***************\n");printf("************************************\n");}
int Add(int x, int y)
{return x + y;
}
int Less(int x, int y)
{return x - y;
}int Times(int x, int y)
{return x * y;
}int Expect(int x, int y)
{return x / y;
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:");scanf("%d", &input);int (*p[5])(int x, int y) = { NULL, Add, Less, Times, Expect };//NULL起到占位的作用,这样子才能对应input和函数的选择if (input >= 1 && input <= 4){printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);  //通过下标进行访问printf("ret = %d\n", ret);}else if(input == 0){printf("退出程序!\n");}elseprintf("选择错误,请重新选择:\n");} while (input);return 0;
}

如果使用转移表完成计算器这样的代码就容易很多了

7.指向函数指针数组的指针

基本概念:
指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针 ;

如何定义呢?

#include 
void test(const char* str)
{printf("%s\n", str);
}
int main()
{char str = "hello world!";//函数指针void (*pfun)(const char*) = test;//函数指针数组void (*pfun_ptr[5])(const char* str) = { test };//指向函数指针数组的指针void (*(*ppfun_ptr)[5])(const char*) = &pfun_ptr;return 0;
}

8.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
可以使用上面的计算器实现的代码来使用回调函数

代码演示:

#include 
void menu()
{printf("************************************\n");printf("****** 1.Add   ***** 2. Less  ******\n");printf("****** 3.Times ***** 4.Except ******\n");printf("************ 0. Exit ***************\n");printf("************************************\n");}
int Add(int x, int y)
{return x + y;
}
int Less(int x, int y)
{return x - y;
}int Times(int x, int y)
{return x * y;
}int Expect(int x, int y)
{return x / y;
}void Calc(int(*pc)(int x, int y))
{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pc(x, y); //pc存放的是函数的地址,通过pc去调用函数,被调用的函数就被称为回调函数printf("ret = %d\n", ret);
}int main()
{int input = 0;    do{menu();printf("请选择:");scanf("%d", &input);switch (input){case 1:Calc(Add);//将函数的地址传给Calc函数break;case 2:Calc(Less);break;case 3:Calc(Times);break;case 4:Calc(Expect);break;case 0:printf("退出程序!\n");break;default:printf("选择错误,请重新选择:");break;}} while (input);return 0;
}

另外,关于回调函数还有一个例子:在另外一篇文章中,是关于qsort函数的使用以及使用冒泡排序的思想模拟实现qsort函数 -->C语言:冒泡排序

本篇的分享到此结束,感谢大家的支持!

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...