C语言:指针的进阶讲解

目录

[1. 二级指针](#1. 二级指针)

[1.1 二级指针是什么?](#1.1 二级指针是什么?)

[1.2 二级指针的作用](#1.2 二级指针的作用)

[2. 一维数组和二维数组的本质](#2. 一维数组和二维数组的本质)

[3. 指针数组](#3. 指针数组)

[4. 数组指针](#4. 数组指针)

[5. 函数指针](#5. 函数指针)

[6. typedef的使用](#6. typedef的使用)

[7. 函数指针数组](#7. 函数指针数组)

[7.1 转移表](#7.1 转移表)


1. 二级指针

如果了解了一级指针,那二级指针也是可以很好的理解的

1.1 二级指针是什么?

二级指针跟一级指针一样,也是接收地址,但是它存的地址是一级指针的地址

cpp 复制代码
int a = 10;
int* p = &a; //存放a的地址
int** pp = &p;//存放p的地址

它们的关系就类似这样:

一级指针能解引用获取到a的值,二级指针也能通过解引用获取a的值,区别就是次数不同而已

cpp 复制代码
int main()
{
	int a = 10;
	int* p = &a; 
	int** pp = &p;
	printf("*p = %d, **pp = %d", *p, **pp);
	return 0;
}

我们可以理解二级指针用一次 * 就降一级

所以需要两个 * 才能获取到a,第一次的*是得到p

1.2 二级指针的作用

一级指针的作用是可以在函数内部实现两个数的交换

如果只是简单的传参是无法实现两个变量的交换的

cpp 复制代码
#include <stdio.h>

void Swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 10;
	int b = 8;
	Swap(a, b);
	printf("a = %d, b = %d\n", a, b);
	return 0;
}
输出:a = 10, b = 8

指针就可以实现

cpp 复制代码
#include <stdio.h>

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int main()
{
	int a = 10;
	int b = 8;
	Swap(&a, &b);
	printf("a = %d, b = %d\n", a, b);
	return 0;
}
输出:a = 8, b = 10

一级指针可以实现的东西二级指针当然也能实现了

cpp 复制代码
#include <stdio.h>

void Swap(int** a, int** b)
{
	int tmp = **a;
	**a = **b;
	**b = tmp;
}

int main()
{
	int a = 10;
	int b = 8;
	int* pa = &a;
	int* pb = &b;
	Swap(&pa, &pb);
	printf("a = %d, b = %d\n", a, b);
	return 0;
}

但这样做明显有点小题大做了

前面说了它们是一个分级的关系,那么一级指针能对初始变量做的,二级指针也能对一级指针做

我们要传参给数组改变变量需要传它的地址(指针),那么我们需要改变一级指针的时候就要传一级指针的地址(二级指针),作用也就体现再了这里

cpp 复制代码
#include <stdio.h>

void Swap(int** a, int** b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int main()
{
	int* a = 10;
	int* b = 8;
	Swap(&a, &b);
	printf("a = %d, b = %d\n", a, b);
	return 0;
}

2. 一维数组和二维数组的本质

一维数组其实就是指针的另一种形式,二维数组也就是二级指针的另一种形式

例如:

cpp 复制代码
int* a 和 int a[]
int** a 和 int a[][5]
//二维数组第二个[]必须有值

怎么证明呢?

cpp 复制代码
int main()
{
	int a[] = { 1,2,3,4,5 };
	printf("a[1] = %d, *(a + 1) = %d\n", a[1], *(a + 1));
	return 0;
}

这里的a[1] 和 *(a+1) 最终打印出来的结果是一致的

所以为什么数组的第一个数组要从0开始而不是从1开始呢?

大概是为了契合指针的引用而做了从0开始的决定,这样a的下标是几指针加几都是一样的结果

二级指针也是这样

cpp 复制代码
int main()
{
	int a[][5] = { 1,2,3,4,5, 1,2,3,4,5, 1,2,3,4,5 };
	printf("a[1][1] = %d, *(*(a + 1) + 1) = %d\n", a[1][1], *(*(a + 1) + 1));
	return 0;
}

甚至指针和数组结合起来一起使用也是可以的,两者并不冲突

cpp 复制代码
int main()
{
	int a[][5] = { 1,2,3,4,5, 1,2,3,4,5, 1,2,3,4,5 };
	printf("*(a[1] + 1) = %d\n", *(a[1] + 1));
	return 0;
}

所以我们使用[]也是解引用,*也是解引用

3. 指针数组

指针数组是指针还是数组?

答案是数组,它是存放指针的数组

我们可以这么记:什么的什么,前面是形容词后面是名词,那么答案当然就是那个名词了

形似这样

这个数组的每一个元素都是存放着一个指针的,上图存放的是一个整型指针

指针数组的每个元素又是一个个地址,又可以指向另一块区域

cpp 复制代码
int main()
{
	int* p1 = 1;
	int* p2 = 2;
	int* p3 = 3;
	int* p4 = 4;
	int* p5 = 5;
	int* arr[5] = { p1,p2,p3,p4,p5 };
	printf("arr[2] = %d", arr[2]);
	return 0;
}
输出:3

4. 数组指针

前面讲了指针数组是数组,那么数组指针当然就是指针了

让我们来睁大眼睛好好的区分一下

cpp 复制代码
int *p1[10]; //指针数组
int (*p2)[10]; //数组指针

上面的指针数组里的指针没有加上小括号,所以 * 会优先和 int 结合,p1自然就和[10]结合,所以这是个有10个元素的整型指针数组

下面的数组指针里的指针加上了小括号,所以*先和p2形成一个指针,那么这个指针会指向后面的数组,所以这是个整型的数组指针

如果我们需要存放一个数组的地址,那么当然就是存放在数组指针里了

cpp 复制代码
int arr[5];
int (*p2)[10] = &arr;

5. 函数指针

函数也是有它自己的地址的

cpp 复制代码
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int main()
{
	printf("Swap: %p\n", Swap);
	printf("&Swap: %p\n", &Swap);
	return 0;
}

既然函数是有地址的,那么我们未来也有可能会需要将函数的地址存储起来,所以就有了函数指针

cpp 复制代码
void (*pf1)(int, int) = &Swap;
void (*pf2)(int, int)= Swap;

上面的两种方法都是一样的,可以获取Swap的地址存储到pf1或者pf2中

前面的返回值要和函数相同,后面的参数也要和函数相同,即使没有参数也要加个 ()

6. typedef的使用

typedef是用来对类型进行重命名的,可以将复杂的类型简单化

如果你觉得unsigned int 写起来不方便,那么我们可以用typedef对它进行重命名,那么以后就可以用uint代替unsigned int 了

cpp 复制代码
typedef unsigned int uint;

自定义类型也是可以使用的,以后自己定义的结构体、枚举等都可以用这个方法重命名,让我们的代码写起来更方便,看起来更简洁

cpp 复制代码
typedef int* ptr_t;
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
typedef void(*pfun_t)(int); //新的类型名必须在*的右边

上面还有一些特殊的写法,新的名字并不是一定都是写在后面的,要注意看是什么类型才能决定怎么使用

7. 函数指针数组

跟前面的理解方法一样,函数指针数组是数组,是用一个数组存放多个函数的地址,这个数组就是函数指针数组

下面的转移表可以很好的帮助我们理解它

7.1 转移表

cpp 复制代码
#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}


int sub(int a, int b)
{
	return a - b;
}


int mul(int a, int b)
{
	return a * b;
}


int div(int a, int b)
{
	return a / b;
}


void menu()
{
	printf("***********************\n");
	printf("***** 1.add 2.sub *****\n");
	printf("***** 3.mul 4.div *****\n");
	printf("***** 0.exit      *****\n");
	printf("***********************\n");
}


int main()
{
	int x, y;
	int input;
	int (*p[5])(int, int) = { 0,add,sub,mul,div };
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			int ret = p[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}


	} while (input);
	return 0;
}

上面我们定义了一个p[5]数组来存放0和4个函数的地址,我们知道了它的地址就可以直接使用它

使用方法:

这里的ret是用来存放函数返回之后的结果,这里先用p[input]解引用得到函数的地址,再加上参数就可以使用那个函数了

比如 input = 1 ,那么这个p[1]存放的是add的地址,那么就相当于add(x, y),跟平常调用函数没有区别,使用函数指针数组可以让我们的代码更加简洁,如果一个一个写调用的话就比较麻烦,看起来的效果自然没有这个好

感谢观看


++完++

相关推荐
ZSYP-S7 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos10 分钟前
c++------------------函数
开发语言·c++
yuanbenshidiaos14 分钟前
C++----------函数的调用机制
java·c++·算法
唐叔在学习18 分钟前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
程序员_三木22 分钟前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
是小崔啊32 分钟前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
ALISHENGYA38 分钟前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
tianmu_sama38 分钟前
[Effective C++]条款38-39 复合和private继承
开发语言·c++
chengooooooo39 分钟前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
黄公子学安全41 分钟前
Java的基础概念(一)
java·开发语言·python