【类型黑市】指针
大家好我是#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呢?
所以,上面的例子,只是为了讲述语法,讲述原理,帮助大家理解
市选拔赛是要考指针的,我敢肯定,不会考太复杂的。我们要学到这个程度:
- 会定义指针变量
- 会给指针变量赋值
- 会取一个变量的内存地址(&运算)
- 会间接引用(*运算)
区分 * 和 & 运算符
我们过去一直都认为 * 就是乘法运算符,怎么今天就说他是地址的间接引用了呢?我们怎么区分,什么情况把它理解成乘法运算,什么情况把它理解成指针运算?
乘法运算
乘法运算的 * 是双目运算符。意思是,* 的前面和后面都有一个参数(数字),例如,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] 了。