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

【类型黑市】指针

大家好我是#Y清墨,今天我要介绍的是指针。

意义

指针就是存放内存地址的变量。

分类

因为变量本身是分类型的,我们学过的变量类型有 int, long long, char, double, string, 甚至还有结构体变量。

同样,指针也分类型,如果指针指向一个存放 int 数据的地址,那么这个指针就是 int 类型的指针;如果指针指向一个存放 long long 数据的地址,那么这个指针就是 long long 类型指针,.....,如果一个指针是指向 stu 结构体数据的地址,那么这个指针就是 stu 结构体类型的指针。

运算

每一种数据类型,都有一些与之匹配的运算。例如,对于 int 类型,有 +, -, *, /, % 运算。如果对应 string 类型,+ 运算虽然也有,但是那就不是数学上的加法,而是对两个字符串进行连接操作,而且,string 类型也没有 -, *, / 运算。

同理,指针运算也有一些与之对应的运算。这是我们下一步要学习的重点。

定义指针

定义指针的时候,需要用到 星号(*),我们直接通过一些例子来演示:

int *p1; // 定义一个 int 指针,名字叫 p1,它将来可以用来存放一个 int 变量的内存地址
double *p2; // 定义一个 double 指针,名字叫 p2,它将来可以用来存放一个 double 变量的内存地址
long long *p3; // 定义一个 long long 指针,名字叫 p3,它将来可以用来存放一个 long long 变量的内存地址
struct stu
{
    string name;
    int yu,shu,ying;
};
stu *p4; // 定义一个 stu 结构体 指针,名字叫 p4,它将来可以用来存放一个 stu 结构体变量的内存地址

指针的赋值

指针变量的赋值和别的变量的赋值是一样的,只不过赋的值是地址信息。地址的分配是由操作系统来做的,我们的程序是和操作系统配合,我们的程序让操作系统给我们分配地址,操作系统把分配到的内存的地址告诉我们的程序,我们再来用这片内存。(所以,这里有很多很多啰嗦事,我们只能挑一些很简单的来讲)

我们要学的其实是如何获取一个变量的地址。获取变量地址需要用 & 运算符。

int a=5;
int *p1;
p1 = &a; // 获取变量 a 的地址,把地址赋给 p1

int b;
int *p2 = &a; // 定义指针变量 p2 的时候,顺带赋值

上面对 p1 和 p2 的赋值,一个地方是有 星号 的,一个地方是没有星号的,要特别注意。

对 p1 赋值,前面不能有星号;但是,如果 p1 是在声明的时候顺带赋值,声明的时候需要有星号。

指针的间接引用

我们已经知道,指针是指向一个变量的内存地址。既然如此,那我们能否通过指针去修改这个变量的值呢?

答案是可以的。这就是指针的间接引用。

举个例子,int 指针 p1 指向 123450 这个地址的内存,123450 就是“门牌号”(这个地址是我随便说的),我现在要对 123450 这个地址的 int 变量赋值,赋值成 99,这个过程就是对指针 p1 的间接引用。因为,我引用的不是指针的值(指针的值是地址),我引用的是指针的值对应的地址的值。

太绕了,直接用例子说明:

#include<bits/stdc++.h>
using namespace std;
int main()
{

	int a[1000]; //定义了数组 a
	
	*(a+5)=99; // a+5 相当于一个指针,指向 a[5] ,所以这一句相当于 a[5] = 99
	
	printf("a[5]=%d\n",a[5]);
	int b=0;
	
	int *p = &b;
	
	*p = 71; // 因为 p 指向 b 对应的内存,这一句相当于 b=71

	printf("b=%d\n",b);

	return 0;
}

但是,实际上没有人这样写程序的。如果我要对 a[5] 赋值,为什么不直接写 a[5]=99呢?

所以,上面的例子,只是为了讲述语法,讲述原理,帮助大家理解

市选拔赛是要考指针的,我敢肯定,不会考太复杂的。我们要学到这个程度:

  1. 会定义指针变量
  2. 会给指针变量赋值
  3. 会取一个变量的内存地址(&运算)
  4. 会间接引用(*运算)

区分 * 和 & 运算符

我们过去一直都认为 * 就是乘法运算符,怎么今天就说他是地址的间接引用了呢?我们怎么区分,什么情况把它理解成乘法运算,什么情况把它理解成指针运算?

乘法运算

乘法运算的 * 是双目运算符。意思是,* 的前面和后面都有一个参数(数字),例如,5*2 ,或者 12*13,可以看到 * 是加在两个数字中间的。

 地址访问运算符

地址访问运算符是单目运算符,* 前面是没有参数的,后面有参数,而且后面的参数是指针类型。

#include<bits/stdc++.h>
using namespace std;
int main()
{

	int a=100,b=500;
	int *p1,*p2; //这里是定义语句,这里的 * 并不是"地址访问运算符"
	p1 = &a;
	p2 = &b;
	
	if(*p1 > * p2) cout<<"p1 指向的数字比 p2 指向的数字大"<<endl;
	else cout<<"p1 指向的数字小于等于 p2 指向的数字"<<endl;

	int c = *p1 * *p2;
	printf("c=%d\n",c);

	return 0;
}

乘法运算的优先级低于地址访问,所以,是访问地址返回数值,然后再来乘。

类似,& 既可以是按位与运算符,也可以是取地址运算符,字符的样子是一样的,区分方法也是通过单目运算符和双目运算符进行区分。 按位与是双目运算符,前后应该有两个整数;取地址运算符是单目运算符,前面没有参数的。

二维数组和指针

先复习一下一维数组和指针的关系

int x[10];
int * p;
p = x; // 直接引用 x 的时候,相当于获取了 x[0] 的地址,本行等同于 p = &x[0]
cout<<*(p+2) * *(p+3); // 本行其实就是输出 x[2]*x[3]

那二维数组的情况又是怎么样呢?

以前我们学习二维数组的时候就说过,二维数组相当于是一维数组的数组,例如,我们定义 int x[4][3],相当于说有一个数组,有 4 个元素,而每个元素又是一个一维数组,里面的这个一维数组是包含了 3 个 int 变量。这句话很绕,但是很关键,如果这句话理解不了,下面的内容也理解不了的。

所以,假设我们定义了 int x[4][3] 之后,如果引用 x[0] ,相当于引用的是第一个一维数组,这个一维数组包含了 { x[0][0], x[0][1], x[0][2] },x[1] 相当于引用第二个一维数组 { x[1][0], x[1][1], x[1][2] },x[2] 是第三个一维数组 { x[2][0], x[2][1], x[2][2] },x[3] 是第四个一维数组 { x[3][0], x[3][1], x[3][2] }。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int x[4][3];

	for(int i=0;i<4;i++)
	printf("x[%d]=%d\n",i,x[i]);
	
	return 0;
}

程序的运行结果是:

一个 int 是 4 个 byte,3 个 int 就是 12 个 Byte,所以看到 x[0] 和 x[1] 之间差了 12 ,单位就是 Byte。

所以,x[i] 其实是一个指针,它存放的是内容是内存地址。x[0] 是地址,x[1] 是地址,x[2], x[3] 都是地址。

#include<bits/stdc++.h>
using namespace std;
int *p[4];
int main()
{
	int x[4][3]={1,2,3,4,5,6,7,8,9,10,11,12};
	
	for(int i=0;i<4;i++)
		p[i] = x[i];

	for(int i=0;i<4;i++)
		printf("p[%d]的地址是=%d p[%d]=%d\n",i,&p[i],i,p[i]);

	
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<3;j++)
			printf("x[%d][%d] 的内存地址是 %d\n",i,j,&x[i][j]);
		
		printf("\n");
	}

	return 0;
}

结合输出信息,p 数组有 4 个元素,p[0] 这个数据是存放在地址为 4878400 的内存那里,值是 228720080。而这个值本身也是一个内存地址,p[1], p[2], p[3] 的情况都类似,它们的内容(值)都是地址信息,。

说得更具体一点,p[0] 的值就是 x[0][0] 的地址,p[1] 的值就是 x[1][0] 的地址,p[2] 就是 x[2][0] 的地址.......

之前我们已经学习过,基于地址偏移的方法去间接引用数据,那么对于二维数组,如何进行间接引用呢?

既然 p[0] 存放的是 x[0][0] 的地址,x 数组的元素又是连续排列的,所以,p 就是 x[0][0] 的地址,因此 *(p[0]+1) 就是对 x[0][0] 的间接引用。而 p[1] 存放的是 x[1][0] 的地址,因此 p[1]+2 就是 x[1][2] 的地址,*(p[1]+2) 就是 x[1][2] 的间接引用.....,这里,用 p 来做间接引用也行,用 x 做间接引用也行,p[1] 的值就是 x[1],所以,*(p[1]+2) 相当于 *(x[1]+2),也相当于 *(*(x+1)+2)

#include<bits/stdc++.h>
using namespace std;
int *p[4];
int main()
{
	int x[4][3]={1,2,3,4,5,6,7,8,9,10,11,12};
	
	for(int i=0;i<4;i++)
		p[i] = x[i];

	// 通过 p 指针间接引用	
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<3;j++)
			printf("x[%d][%d]=%d ",i,j,*(p[i]+j));
		
		printf("\n");
	}

	printf("\n");
	// 通过 x 指针间接引用	
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<3;j++)
			printf("x[%d][%d]=%d ",i,j,*(*(x+i)+j));
		
		printf("\n");
	}

	return 0;
}

上面的代码是比较绕的,死机硬背的话过上一阵就忘了,需要理解再来记忆。

首先说,x 是二维数组,也就是说它是一维数组的数组。x+i 相当于 x[i] , x+1 和 x+2 之间差了 12 的(因为内层的一维数组有 3 个 int,每个 int 是 4 个 byte,3*4=12)。x[i] 就是记录着第 i 个一维数组的起始地址,x[i] (等同于 x+i ) 相当于就是取到 x[i][0] 的地址 ,然后基于 这个地址再偏移 j 个 int,就是 x[i][j] 的地址了。

*(*(x+i)+j)) 这个表达式里有两个 * 运算符,右边的运算符,就是取到 x[i][0] 的地址,或者说是第 i 个一维数组的开始地址(i 从 0 开始算),所以,左边的 * 运算之后之后,得到的还是地址。然后左边的 * 就是基于地址去间接引用 x[i][j] 了。


http://www.kler.cn/a/312698.html

相关文章:

  • 运行WHTools批量启动游戏房间工具提示要安装.Net Framework3.5解决
  • Java复习42(PTA)
  • Prompt 工程
  • ORA-01092 ORA-14695 ORA-38301
  • 从0开始学PHP面向对象内容之(常用魔术方法续一)
  • linux,1.NFS和autofs,2.podman容器,3.http服务和虚拟web主机,4.内网DNS服务搭建
  • Angular面试题四
  • 如何使用ssm实现企业人事管理系统+vue
  • monorepo基础搭建教程(从0到1 pnpm+monorepo+vue)
  • 最新版本TensorFlow训练模型TinyML部署到ESP32入门实操
  • matlab模拟时间有负数的信号频谱
  • 前端在网络安全攻击问题上能做什么?
  • jacoco-maven-plugin使用
  • Qt中文乱码解决
  • 动手学深度学习(pytorch土堆)-06损失函数与反向传播、模型训练、GPU训练
  • 零基础玩转实在Agent -- 基础篇|实在Agent研究
  • 算法leecode笔记
  • 基于PHP的电脑线上销售系统
  • 小米电视,无需U盘,直接通过ADB远程安装APK,很方便!
  • 【Verilog学习日常】—牛客网刷题—Verilog快速入门—VL16
  • 修改状态的标准模版
  • 【移动端】Flutter与uni-app:全方位对比分析
  • MATLAB绘图:2.plot函数
  • vulnhub靶场 DC-3
  • axios二次封装
  • web基础—dvwa靶场(十二)JavaScript Attacks