首页 > 编程语言 >【C/C++】--- 指针详解 2.0

【C/C++】--- 指针详解 2.0

时间:2024-06-01 16:58:54浏览次数:12  
标签:arr int 元素 C++ --- 地址 数组 2.0 指针

接下来进入指针的进阶部分,准备好大脑

补充:(重点)

数组名是数组首元素地址
数组首元素地址和数组地址,值相同,但本质不同,
区别在于二者的类型不相同
比如数组int arr[10];

  • 数组首元素地址的类型:首先这是一个地址所以要用指针接收,(),然后是地址指向元素的类型为int,所以这个指针的类型是int
  • 数组地址的类型:首先这是一个地址所以用指针接收,(),这个地址表示的是整个数组,这个数组的类型为int [10],所以这个指针的类型是int()[10]
  • 额外:数组的类型怎么判断,先判断数组的大小,[10],再判断数组中存储数据的类型,int,所以数组的类型就是int [10]
  • arr是数组首元素地址,&arr是数组地址,那么*(&arr) == arr,给数组地址解引用可以得到数组首元素地址
  • arr[i]的意思是访问数组第i个元素的位置,也可以表示为*(arr+i),数组元素地址+i后解引用,而系统在实现arr[i]这个功能本质就是用*(arr+i),理解到这里,有没有感受到为什么说C语言研究的是底层呢?

一、字符指针

char arr[] = "abcd";
const char* p = "abcd";
因为"abcd"是字符串常量,且*p不能修改这个常量
所以,用const修饰char* p保证*p不能修改指针变量p指向的值

特别地

  • 这里的指针变量p接收的是字符数组的首元素地址,也就是字符’a’的地址
  • 常量储存在代码区,是不能被修改的

二、.指针数组

字符数组 — 存放字符的数组
整形数组 — 存放整型的数组
指针数组 — 存放指针(地址)的数组

int a = 1;    int b = 2;    int c = 3;
int pa = &a;  int pb = &b;  int pc = &c;
int* arr[3] = {pa, pb, pc};
这里的数组arr存放的是指针变量

int* arr[3]可以这么理解:

  • arr[ 3 ] 代表这是一个大小为3的数组,int* 代表这个数组里存的数据类型为int*
  • 那么int* arr[ 3 ]的意思就是 叫做arr的数组里存放了3个类型为int*的指针变量

特别地:这个数组类型为:int* [ 3 ]

三、数组指针

1.指向一维数组的数组指针

  1. int * p[10]; 指针数组,数组里存放的是指针
    p与[ ]优先级更高,所以p[10]先是个数组,存放的是int* 类型的数据
  2. int (*p)[10]; 数组指针,指向数组的指针
    *p表示p是个指针变量,int [10]是数组类型,p是指向数组类型为int [10]的指针,而指针变量p的类型为int (*) [10]
    • 这里举个例子方便你理解:
    • int * p; 有*这个符号时,表示这个p是个指针变量,这个指针变量存放数据的类型为int, 而这个指针变量p的类型是int*

int arr[10];

类型
arr首元素地址int*
&arr[0]首元素地址int*
&arr整个数组的地址int (*) [10]

数组指针:指针变量指向的是整个数组的地址,而不是首元素地址
即使这两个地址在数值上是一样的,但指针对这两个地址进行+/-的操作时,结果是不同的,因为本质上指向数组地址和指向首元素地址的指针类型是有区别的,

  • 指向数组地址的指针类型为int (*) [10]
  • 指向首元素地址的指针类型为int*
  • 当对指向数组地址的指针进行+1,
    • 这时指针指向数组末尾的位置,指针移动的步长是整个数组的大小
  • 当对指向首元素地址的指针进行+1,这时指针指向的是数组中第二个元素的位置,是按照数组中元素所占大小决定步长,
    • 若数组元素为char型,指针+1的意思就是指针移动1个字节
    • 若为int型,指针移动4个字节

综上,这个表达式才是正确的 *int (p) [10] = &arr;
而不是 int (*p) [10] = arr;
p是个变量
—> *p表示p是个指针变量
—> (*p)[10]表示p指向的是个数组
—> int (*p)[10]表示的是指针变量p指向的数组,这个数组存放的是int型的数据
—> p的类型为int(*)[10]

2.指向二维数组的数组指针

首先我们要清楚
int arr[10];

  • arr是数组名,数组名是首元素地址
  • 二维数组的每一行可以理解为二维数组的一个元素
  • 每一行又是一个一维数组
  • 所以,二维数组其实就是存放一维数组的数组
  • 二维数组的数组名,也是数组名,数组名就是首元素的地址

将二维数组中的每一行看成一个元素,等同于,一行就是一个一维数组,二维数组的数组名表示的是首元素的地址,那就是第一个元素的地址,那第一个元素不就是第一行吗,也就是第一个一维数组,二维数组的首元素地址,就是第一个一维数组的地址(这个一维数组的地址代表的是整个一维数组的地址,不是一位数组的首元素地址,因此后序如果指针+1时,改变的步长是整个一维数组的大小)
在这里插入图片描述
arr
-------- 首元素地址
-------- 第一行地址
-------- 一维数组的地址(整个一维数组的地址,不是一维数组首元素的地址)

四、数组指针传参

实际应用,这里用二维数组传参举例子

常规的二维数组传参,用二维数组接收
void func1(int arr[3][5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

//利用数组指针接收地址,这里接收的实际上是二维数组arr的首元素地址,也就是第一个一维数组的数组地址
//所以需要用一维数组类型的指针接收就行
//*p表示p是个指针,指针指向的是个一维数组数据,这个一维数组的类型为int [5]
void func2(int(*p)[5], int row, int col)
{
	//这里指针指向的是第一个元素的地址,这个元素实际是个一维数组,所以指针指向的是第一个一维数组的数组地址
	for (int i = 0; i < row; i++)
	{
		//要访问一个数组的元素,就要遍历每个位置
		for (int j = 0; j < col; j++)
		{
			//这里的指针p是一维数组的数组地址,
			//p+i是决定p指向第几个一个数组,
			//*(p+i)表示将一维数组地址转化为一维数组的首元素地址,有了首元素的值,才可以用指针访问一维数组中的元素
			//(*(p+i)+j)表示指针指向该一维数组中下标为j的元素 
			//*((*(p + i)) + j)表示访问该元素的内容
			printf("%d ", *((*(p + i)) + j));
			//(*(p + i))相当于一维数组名,那么就可以这样访问数组中的元素(*(p + i))[j]
			//arr是数组名,arr[j],用来访问数组中的元素,arr[j]的底层实现是:*(arr+j)
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,
					  2,3,4,5,6,
				      3,4,5,6,7 };
	//func1(arr, 3, 5);
	func2(arr, 3, 5);

	return 0;
}

五、函数指针

函数指针:指向函数地址的指针

  • 函数名是函数的地址
  • &函数名也是函数的地址

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

int(p)(int,int) = &Add;
int ---------------> 函数返回类型
(int,int) ---------> 函数参数
&Add --------------> 函数地址
指针变量p的类型 ----> int(
)(int,int)

调用函数:
int ret = Add(3,4);
int ret1 = p(4,5);
int ret2 = (*p)(5,6);
用指针p调用函数时,*可写可不写

六、函数指针数组

int Add(int x, int y)  { return x + y;}
int Sub(int x, int y)  { return x - y;}
int Mul(int x, int y)  { return x * y;}
int Div(int x, int y)  { return x / y;}

int(* pArr[])(int,int) = {Add,Sub,Mul,Div};
首先pArr是个数组,数组里存放的数据类型为int(*)(int,int)

下面这张图根据我可以尝试去运行,然后去解释每个地址
在这里插入图片描述
这是完整代码

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

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

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

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

int main()
{
	int(* parr[])(int,int) = {add,sub,mul,div};
	//首先parr是个数组,数组里存放的数据类型为int(*)(int,int)
	printf("%x\n", parr[0]);
	printf("%x\n", add);
	
	printf("%d\n", sizeof(int(*)(int, int)));
	printf("%x\n", &parr[0]);
	printf("%x\n", (&parr)[0]);
	
	printf("%x\n", (&parr)[1]);
	printf("%x\n", (&parr) + 1);
	printf("%x\n", &(parr[3]) + 1);
	return 0;
}

七、指向函数指针数组的指针

int(*p)(int,int) ------------------ 函数指针
int(*pArr[ ])(int,int) ------------ 函数指针数组
int(*(*p)[ ])(int,int) = &pArr[ ] ------------- 函数指针数组的地址
p就是指向函数指针数组的指针
首先要有个指针变量区接收这个函数指针数组,*p,然后再看这个指针指向数据的类型,函数指针数组的类型是,int(*[])(int,int)

八、回调函数

通过函数指针调用的函数就是回调函数
如果你把一个函数的地址作为参数传递给给另一个函数,当这个地址被用来调用其所指向的函数时,我们就说这是回调函数
这里我们引入两个知识:

  1. void
  • ①void* 的指针,无具体类型的指针
  • ②void* 类型的指针可以接收任何类型的地址
  • ③这种类型的指针是不能直接用来解引用操作的
  • ④也不能直接进行指针运算的
int a = 10;
float f = 3.14f;
int* pa = &a; ------- 可以
char* pc = &a; ------ 不行 &a的类型是int*
void* pr = &a; ------ 可以②
pr = &f; ------------ 可以②
*pr; ---------------- 不行③
pr++; --------------- 不行④
  1. qosort库函数
void qsort( void* base,   //指向了需要排序的数组的第一个元素
				  size_t num,  //排序的元素个数
				  size_t size,  //一个元素的大小,单位是字节
				  int (*cmp)(const void*, const void*)  //函数指针类型,这个指针指向的函数,能比较base指向数组中的元素
				)
struct stu
{
	char name[20];
	int age;
};

int cmp(const void* p1, const void* p2)
{
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
qsort库函数默认为排序结果为升序

int main()
{
	struct stu arr[] = {{"猪八戒",30},{"孙悟空",20},{"沙僧",50}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]),cmp);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	return 0;
}
//利用冒泡排序实现qsort函数的功能
struct stu
{
	char name[20];
	int age;
};

int cmp(const void* p1, const void* p2)
{
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}

void Swap(char* p1, char* p2, int size)
{
	for (int i = 0; i < size; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}

void bubble_qsort(void* base, int num, int size, int(*cmp)(const void* p1, const void* p2))
{
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 1)
			{
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int main()
{
	struct stu arr[] = { {"猪八戒",30},{"孙悟空",20},{"沙僧",50} };
	int num = sizeof(arr) / sizeof(arr[0]);
	int size = sizeof(struct stu);
	bubble_qsort(arr, num, size, cmp);
	for (int i = 0; i < 3; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}

	return 0;
}

标签:arr,int,元素,C++,---,地址,数组,2.0,指针
From: https://blog.csdn.net/weixin_59005084/article/details/139335143

相关文章

  • macOS下使用bits/stdc++.h万能头文件
     macOS下使用bits/stdc++.h万能头文件1.终端中输入echo|g++-v-xc++-E-#include<...>searchstartshere:/usr/local/include/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/Library/Developer/CommandLineTools/usr/lib/clang/12.......
  • 基于Open3D的点云处理20- 基于Visualizer类自定义可视化
    1.自定义可视化官网测试用例Open3D/examples/python/visualization/customized_visualization.py自定义可视化工具窗口-Visualizer类Visualizer可视化基础操作defcustom_draw_geometry(pcd):#Thefollowingcodeachievesthesameeffectas:#o3d.v......
  • C++——类
    目录C++类访问权限虚函数1.定义底层实现2.构造函数/析构函数3.抽象类/纯虚函数常见问题1.虚函数不可以声明为inline吗2.构造函数为什么不能为虚函数?3.析构函数为什么可以为虚函数?4.构造函数和析构函数可以调用虚函数吗?5.虚析构函数的作用,父类的析构函数是否要设置为虚......
  • 力扣刷題---回文數 擊敗100%用戶的解法
    題目:给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。示例1:输入:x=121输出:true示例 2:输入:x=-121输出:false解释:从左向右读,为-121。从右......
  • 《童年》-- 罗大佑
    池塘边的榕树上,知了在声声地叫着夏天,操场边的秋千上,只有蝴蝶儿停在上面。黑板上老师的粉笔,还在拼命唧唧喳喳写个不停,等待着下课,等待着放学,等待游戏的童年。 福利社里面什么都有,就是口袋里没有半毛钱,诸葛四郎和魔鬼党,到底谁抢到那支宝剑。隔壁班的那个女孩,怎么还没经过我的......
  • 高精度-高精度(信息学奥赛1169)
    #include<iostream>#include<cmath>#include<vector>usingnamespacestd;intmain(){stringa,b;cin>>a>>b;vector<int>x,y,sum;for(inti=a.size()-1;i>=0;i--){x.push_back(a[i]-'0&#......
  • 全开源源码---小红书卡片-跳转微信-自动回复跳转卡片-商品卡片-发私信-发群聊-安全导
    做小红书的人都知道小红书的用户商业价值非常高,消费能力很强,很多做高客单产品的都想从小红书平台上引流到私域成交,但是都会遇到账号违规、被封的问题,因为小红书的平台是所有平台里对引流导流最严的。不允许留公众号、手机号、微信号等联系方式,一旦被发现就会面临封禁等处罚。......
  • 开源源码---小红书卡片-跳转微信-自动回复跳转卡片-商品卡片-发私信-发群聊-安全导流
     做小红书的人都知道小红书的用户商业价值非常高,消费能力很强,很多做高客单产品的都想从小红书平台上引流到私域成交,但是都会遇到账号违规、被封的问题,因为小红书的平台是所有平台里对引流导流最严的。不允许留公众号、手机号、微信号等联系方式,一旦被发现就会面临封禁等处罚。......
  • 宝塔Linux面板-Docker管理(2024详解)
    上一篇文章《宝塔Linux可视化运维面板-详细教程2024》,详细介绍了宝塔Linux面板的详细安装和配置方法。本文详细介绍使用Linux面板管理服务器Docker环境。目录1、安装Docker1.1在线安装​编辑 1.2手动安装1.3运行状态1.4镜像加速2应用商店 3总览 4容器4.1......
  • 宝塔Linux可视化运维面板-详细教程2024
    生产环境的Web服务器多数都是Linux操作系统。包括云服务器在内,都是通过命令行进行服务器的运维和管理。本文详细介绍如何通过宝塔面板实现可视化服务器管理。目录1什么是宝塔面板2Linux面板安装3面板使用3.1登录3.2绑定账号 3.3安装服务器环境套件(可选)3.4软件......