IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言学习笔记----4(指针) -> 正文阅读

[C++知识库]C语言学习笔记----4(指针)

指针深入介绍

众所周知,亚里士多德1撑起了古希腊科学、哲学的半壁江山。

而指针就像是C语言中的亚里士多德一样,撑起了C语言的半壁江山。
ALT
不,与其说,指针撑起了C语言的半壁江山,不如说是指针创造了C语言的一片天地。请添加图片描述

小明:指针不就是存放了一个地址的变量而已嘛?就这?

小明,数学题你都会做了?还是你终于考上大学了?来这凑啥热闹?

请添加图片描述

指针类型

首先,要登场的是我们的二级指针
请添加图片描述

二级指针

小明同学在不经意间,已经说出了一个惊天大秘密!!!

指针就是一个变量
只不过是说 指针 这个变量和其他的变量不同,它存放的是某个数据的位置

变量,就意味着指针也是存放在内存中的某个位置,它也有自己的“门牌号”
也就是只要我有钥匙,我就可以光明正大进入指针的小家

请添加图片描述
em…当然最关键的还是需要 “&” 老大哥来帮忙配个钥匙
ALT

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	//此时ppa变量中存放的内容就是pa指针的地址
	return 0;
}

此时,我们就通过ppa可以进入pa的小家,“光明正大”地实施改造计划。

请添加图片描述

首先,趁着 pa 出门打酱油,将 a 进行改变

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 100;
	return 0;
}

在这里插入图片描述

小明:就这???

那来个狠的

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 100;
	int b = 20;
	*ppa = &b;
	return 0;
}

在这里插入图片描述

新创建一个 b 变量,进行操作 **ppa = &b;
此时再使用 pa 得到的就不是 a 变量,而是 b 变量

指针pa:我媳妇呢???

通过观察,可以知道 二级指针的结构int* *p
其中 int* 表示 p 的数据类型是 int* 类型的
剩下的 **“ * ”**则表示 p 是一个指针
合起来就是一个 指向一个int*类型的变量的指针

可以通过观察二级指针的结构,推导出三级指针,四级指针…

总结

  1. 二级指针存放的是一级指针的地址,而一级指针存放的是数据的地址
  2. 二级指针可以通过两次解引用操作来访问数据
  3. 二级指针进行一次解引用操作就可以改变一级指针存放的内容

小明:感觉也就一般般吧

请添加图片描述

字符指针

小明(不屑):字符指针?

eg

char ch = 'w';
char* pc = &ch;
printf("%c\n", ch);
printf("%c\n", *pc);

在这里插入图片描述

小明(不屑地准备滑走)

eg

char* str1 = "Hello World";
char* str2 = "Hello World";
char strArr1[] = "Hello World";
char strArr2[] = "Hello World";

问:
1、此时 str1str2 相等吗?

答:相等在这里插入图片描述

2、此时 strArr1strArr2 相等吗?

答:不相等
在这里插入图片描述

小明:为什么 str1str2 相等,而 strArr1strArr2 不相等呢?

"Hello World"是一个字符串常量2,将字符串常量赋值给字符指针的时候是将字符串常量的首元素的地址传给了字符指针,因此 str1str2 指向的都是 字符串常量的首元素的地址

strArr1strArr2 是数组,将字符串赋值给字符数组,相当于将字符一个一个复制到了数组中。

小明:那为啥他俩不还是一模一样?为啥电脑告诉我他们不相等?电脑骗人?

注意,计算机是不会骗人的,他们只是严格的执行一条接一条的指令而已。
之所以 strArr1strArr2 不相等是因为他们确实不相等。

请添加图片描述

咳咳~strArr1strArr2 是两个数组
只要不是同一个数组,那么他们的首元素地址不可能相等

小明(点头):em。。。我明白了

说到数组,有两个孪生兄弟忍不住想来了请添加图片描述

指针数组

顾名思义,指针数组就是一个存放指针的数组
网恋需谨慎,能见才靠谱

int main()
{
	int* arr[10];
	char** arr2[10];
	return 0;
}

在这里插入图片描述
而 char** arr2[10]; 表示一个有10个数据类型是 char** 的元素的数组

数组指针

顾名思义,数组指针就是一个指向数组指针
同样,先来看看数组指针的结构

int main()
{
	int* p1[10];
	int* (p2[10]);
	int (*p3)[10];
	return 0;
}

小明:这都是数组指针???

我:你猜

小明:请添加图片描述

在这里插入图片描述

所以 p1 不是数组指针

在这里插入图片描述

所以 p2 也不是数组指针

在这里插入图片描述
所以这里只有 p3数组指针
因此,不难发现,数组指针和指针数组的区别

名字本质所占空间
数组指针指针4byte / 8byte
指针数组数组4 * n byte / 8 * n byte(n为数组元素个数)
名字数据类型
数组指针int (*) [](指向的数组中的元素是int类型)
指针数组int*[](数组中的元素都是int*类型)
名字作用
数组指针存放一个数组的首元素的地址,表示整个数组的地址
指针数组存放n个指针,每个指针指向的元素类型相同

数组指针更多的是用来当二维数组3作为函数实参的形参

#include<stdio.h>
void print(int (*pa)[3], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for(j = 0; j < col; j++)
		{
			printf("%d ", pa[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][3] = {{1, 2}, {3, 4}, {5, 6}};
	print(arr, 2, 3);
	return 0;
}

此时 arr 表示的是首元素的地址,同时也表示 第一行的地址 ,也是一个一维数组的地址
类比可得:
arr[1] 表示第二行的地址;
arr[2] 表示第三行的地址;

因此,pa相当于接收了第一行的地址(arr的每行都是 两个int类型 的数据)
而 pa[ i ] [ j ] 和 *( *( pa + i ) + j)是等价的
所以 pa[ i ] [ j ] 可以读取到第 i 行,第 j 列的元素

介绍到这里,是时候来看看指针Plus版本了
请添加图片描述
问:解释下列代码的含义

int arr1[5];         //1
int *arr2[5];        //2
int (*arr3)[5];      //3
int (*arr4[10])[5];  //4

小明:
1 是数组
2 是数组指针
3 是指针数组
4 …

你这不行啊,小明

在这里插入图片描述

当括号将 * 和 字符单独括起来,后面加一个[ n ],前面加一个数据类型
那这就是数组指针

小明:所以第四个是数组?

对,但也不全对

在这里插入图片描述
那我们不妨将arr[10]拿掉,看看它到底是何方神圣

在这里插入图片描述

小明:天哪?!这不是个数组指针吗?

是的,就是数组指针

由此,4到底是什么已经真相大白了。
它就是
一个有 10 个元素的指针数组
每个指针都是指向的一个有 10 个整形元素的数组

答:
1 是一个有 5 个 int 类型的元素的数组
2 是一个有 5 个 int* 类型的元素的数组
3 是一个指向一个有 5 个 int 类型的元素的数组的指针
4 是一个有 10个 int*[ 5 ] 类型的元素的数组

还有两个小伙伴也是迫不及待了

小明:还有???

请添加图片描述

函数指针

介绍函数指针之前,先看一段代码

#include<stdio.h>
void test()
{
	;
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
}

在这里插入图片描述

由此可见, 函数也没有那么神秘
也是在栈4中开辟的一块地址
而且函数和数组很相似,名字都是它们的地址

要能够存储地址就必须是一个指针
判断一下,pfun1和pfun2哪个是指针

void (*pfun1)();
void *pfun2();

小明:pfun1
因为有括号,所以 pfun1 先和 * 结合
后面那个括号应该就是表示这是一个函数指针

有点东西啊,小明
不过,还是没完全对

pfun1 和 * 先结合表示这个是一个指针
后面的括号是指向函数的 参数(此时什么都没写,表示传递的参数为空)
void 表示 函数的返回类型

pfun2就是一个函数,它的返回值的类型是void*,同时传递的参数为空

又到了提问的环节
解释下列代码的含义:

(*(void (*)())0)();                      //1
void (* signal(int, void(*)(int)))(int); //2

小明:
请添加图片描述

不慌,且听我慢慢道来

首先,先一层一层推进
在这里插入图片描述

小明:还是不明白

再推进一层

在这里插入图片描述
显然,这是一个函数指针类型

在这里插入图片描述
而这,就是将 0 进行强制类型转换为void(*)()类型
在这里插入图片描述
再对 0 进行解引用
在这里插入图片描述
末尾的 ( ) 表示函数的参数(此时也是什么都没有传递)

综上,代码1的含义是:
0 强制类型转换为void(*)(),再进行解引用,再传递参数
从而实现了调用在 0 地址处的函数( 0 地址一般无法这样操作)

小明:代码2呢?

同样,括号这么多,先一层一层向内部推进
在这里插入图片描述

小明:有点眼熟

再推进一层
在这里插入图片描述

小明:这表示的是两个类型,一个int, 一个void(*)(int)

没错
在这里插入图片描述
这就是函数signal的两个参数类型
字符串已经和括号结合,表示这是一个函数的声明
那么剩下的就是这个函数的返回类型了
在这里插入图片描述
所以,这个函数的返回值类型就是 void(*)(int)

综上所述,
代码2表示的含义是 函数signal 的声明
其中,这个 signal 的参数一个是 int, 另一个是 void(*)(int)
signal 的返回值的类型也是 void(*)(int)

这两个代码均在《C陷阱和缺陷》中提及

代码2的结构太复杂,可以利用 typedef 来简化

typedef void (*pfun_t)(int);
pfun_t signal (int, pfun_t);

小明:如果,我需要使用多个函数,而他们的参数,返回值都是一样的,我可以用什么来进行简化呢?

接下来就是函数指针数组来大放光彩了

函数指针数组

函数指针数组和一般的指针数组一样
只不过
函数指针数组中的指针指向的都是函数

在见到函数指针数组之前,先看看正常情况下计算器的实现代码

void menu()
{
	printf("******************************\n");
	printf("0.exit\n");
	printf("1.Add\n");
	printf("2.Subtract\n");
	printf("3.Multiply\n");
	printf("4.Divide\n");
	printf("******************************\n");
}

int Add(int x, int y)
{
	return x + y;
}

int Subtract(int x, int y)
{
	return x - y;
}

int Multiply(int x, int y)
{
	return x * y;
}

int Divide(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	menu();
	do
	{
		scanf("%d", &input);
		switch (input)
		{
		case 0: 
			break;
		case 1: 
			scanf("%d %d", &x, &y);
			printf("%d\n", Add(x, y));
			break;
		case 2:
			scanf("%d %d", &x, &y);
			printf("%d\n", Subtract(x ,y));
			break;
		case 3:
			scanf("%d %d", &x, &y);
			printf("%d\n", Multiply(x, y));
			break;
		case 4:
			scanf("%d %d", &x, &y);
			printf("%d\n", Divide(x, y));
			break;
		default:
			break;
		}

	} while(input);
	return 0;
}

而现在对其进行改造

int main()
{
	int input = 0;
	int(*arr[5])(int, int) = { 0 , Add, Subtract, Multiply, Divide };
	int x = 0;
	int y = 0;
	do
	{
		menu();
		scanf("%d", &input);
		if (input > 0 && input < 5)
		{
			scanf("%d %d", &x, &y);
			printf("%d\n", (*arr[input])(x, y));
		}

	} while (input);
	return 0;
}

其中 int(*arr[5])(int, int) = { 0 , Add, Subtract, Multiply, Divide }; 就是一个函数指针数组

既然有函数指针数组,那么函数指针数组指针自然也是少不了的
请添加图片描述

函数指针数组指针

将函数指针去掉,就是之前介绍过的数组指针
所以,通过观察数组指针
可以得到
函数指针数组指针
是一个指向一个函数指针数组的指针,里面存放的是数组的地址

函数指针数组指针虽然名字看起来很复杂,但是他的结构也一点不简单

小明:
请添加图片描述

首先,写出数组中的元素类型

int(*)(int, int)

其次,将指针和数组添加上去

int(*(*parr)[5])(int, int) = arr;

回调函数

简介:回调函数就是一个通过函数指针调用的函数。
也就是说,将A函数的指针,作为参数传递给B函数
当A函数的指针被用来调用它所指向的A函数时,称此为回调函数

qsort就有回调函数的使用
在使用qsort的时候就需要传递一个函数指针,用来自定义如何排序
下面来简单介绍一下 qsort

在这里插入图片描述

qsort 的头文件是<stdlib.h>
qsort 的参数含义是数组名,元素个数,元素所占字节大小,比较方法的函数

#include<stdlib.h>
int com(void* q1, void* q2)
{
	return *(void*)q1 - *(void*)q2;
}
int main()
{
	int arr[10] = { 1,3,2,4,6,8,7,10,9,5 };
	qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr), com);
	return 0;
}

我们自己自定义的函数返回值的类型一定要是int,同时要有两个void*类型的指针作为参数
com 的返回值是小于等于 0 的时候,qosrt不会对数组做出改变
而当 com 的返回值是大于 0 的时候,qsort对数组进行排序
在这里插入图片描述
比较方法的函数可以根据需要排序的数组中的元素类型来灵活调整

总结

以上,便是C中部分的指针的介绍。
C的指针的奥妙绝不是我这三言两语能够说的清楚的,它在我看来,指针就是C中最牛逼的部分,不接受反驳请添加图片描述

注释:


  1. 亚里士多德(Aristotle公元前384~前322),古代先哲,古希腊人,世界古代史上伟大的哲学家、科学家和教育家之一,堪称希腊哲学的集大成者。他是柏拉图的学生,亚历山大的老师。 ??

  2. 字符串常量是一对双引号括起来的字符序列。 字符常量可以赋值给字符变量,如"char b=‘a’;",但不能把一个字符串常量赋给一个字符变量,同时也不能对字符串常量赋值! ??

  3. 因为二维数组在内存中的使用是连续的,因此,二维数组的本质就是一维数组。只不过,二维数组相当于是多个一维数组叠加在一起 ??

  4. 关于栈的内容,可以移步到这篇文章??

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 01:23:14  更:2021-08-17 01:23:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/26 16:49:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计