指针是啥
众所周知,你在程序中向计算机申请一个变量(如int a
),计算机会分配给你的变量一个空间。用int a
举例子,计算机就给你了一个名叫a的房子,只能在里面放int类型的值。当然,你可以直接写a=10
,这样计算机会帮你找到名叫a的房子,把10放进去。
那么这个房子在哪呢?就有了地址,每个变量都会有一个地址(即它在内存中的位置),指针,便是一根棍子,指着这个位置,指针里存的就是这个地址。
怎么用
声明
在类型后面加上*
即可,当然指针也是分类型的,如:
int* p;//一个指向(还没确定指向哪里)int类型房子的指针p
double* k;//指向double的k
如果你不确定这个类型,可以用void:
void* p;
但是,你如果这样写:
int* p,q;
事实上你是定义了一个指针和一个普通变量,所以我通常这样写:
int *p,*q;
注意定义指针后指针会指向一个随机的位置,如果你对这个位置进行操作,就有可能发生内存错误。
操作
存入地址
这里需要用到一个新运算符:&
,它称为取地址符,用于获取一个变量的地址。
例如:
int *p;
int a;
p=&a;
你应该发现,scanf后面如果输入一个变量要用&
,所以scanf后面的参数就是一个地址。
你甚至可以输出一个地址,如果用cout就直接输出p即可,用printf需要用到格式控制符%p
,如:
int a;
scanf("%d",&a);
printf("%p",&a);
每个电脑上的结果可能会不一样,例如我的:
输出该地址上的值
如果你想知道指针所指向位置的值,需要再次用到*
。
int *p;
int a;
p=&a;
a=10;
printf("%d\n",*p);
a=15;
printf("%d\n",*p);
运行结果是什么呢?
不难理解,因为一个变量的地址永远不会变。
加/减
你可能会想,指针是不是也有+,-操作呢?答案是肯定的,但不是把这个位置的值+,-,而是把地址往后或往前移动。
可以做一个实验:
#include<cstdio>
int main()
{
int a=1,*p,*q;
p=&a;
q=p+1;
printf("%p %p",p,q);
}
结果:
为什么多了4呢?因为int类型是4个字节,所以加int类型的1
会往后移4个字节。
到这里很想问了,那加char类型的'1'
会怎么样?
#include<cstdio>
int main()
{
int a=1,*p,*q;
p=&a;
q=p+'1';
printf("%p %p",p,q);
}
结果:
并不是只移了1位,因为这里的指针q是int类型的,你的'1'
被自动转为了ASCII码,所以后移了很多位。
应该这样做:
#include<cstdio>
int main()
{
char a='1',*p,*q;
p=&a;
q=p+1;
printf("%p %p",p,q);
}
这里的+1
就成了char的后移一位,也就是1个字节,结果:
指针数组
很好理解:
int *p[10];
声明一个有十个指针(p[0],p[1],p[2],…,p[9])的数组,每个元素都是一个指针。
其他
scanf
要输入n(n≤1000000)个数存入a数组再输出,一般我们会这样写:
#include<cstdio>
#define MAXN 1000000
int a[MAXN+5],n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
}
知道了指针,我们有了更装逼的方法:
#include<cstdio>
#define MAXN 1000000
int a[MAXN+5],n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",a+i);
for(int i=1;i<=n;i++)
printf("%d ",*(a+i));
}
结果:
指针与其他数据结构
指针与数组
我们知道,当int a[10]
时,系统会连续开10个空间,所以我们可以这样访问数组的元素:
#include<cstdio>
int main()
{
int a[10]={0,2,4,6,8,10,12,14,16,18};
int *p;
for(p=&a[0];p<&a[0]+10;p++)
printf("%d ",*p);
}
结果就不截图了。
其中有一个:p<&a[0]+10
是什么意思呢?
不难理解,10是数组的大小,&a[0]是数组的首地址,&a[0]+10就是数组末尾地址的下一位。
这里的p还有一个名称,叫做数组的迭代器,我们不说这些东西。
其实获取数组首地址还有一个办法,直接这样:
int a[10];
int *p;
p=a;
这样就可以了,事实上,数组你可以看成一个指针,可以这样写:
#include<cstdio>
int a[10]={0,2,4,6,8,10,12,14,16,18};
int main()
{
int *p;
p=a;
printf("%d\n",*a);
printf("%d\n",*(a+5));//注意加括号,否则就是输出“a指向的值加5”了
printf("%d\n",*p);
printf("%d\n",*(++p));
}
结果:
相反的,指针也可以看做一个数组,所以经常有这样写的:
int sum(int *a,int n)
{
int ans=0;
for(int i=1;i<=n;i++)
ans+=a[i];
return ans;
}
那么可以这样用:
#include<cstdio>
int sum(int *a,int n)
{
int ans=0;
for(int i=1;i<=n;i++)
ans+=a[i];
return ans;
}
int main()
{
int A[10],n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&A[i]);
printf("%d\n",sum(A,n));
}
结果:
函数指针
你需要知道,在一个程序中,不仅仅是变量需要分配内存,函数也一样,那么函数自然也可以有指针,是函数的入口地址。函数指针声明只比函数声明多一个*
和一对括号,例如:
int (*Psum)(int*,int)
其中吧*Psum
括起来的括号一定不能少,不然编译器会认为你声明了一个叫Psum的函数,返回类型是int*。
也就是说可以这样用:
#include<cstdio>
int (*Psum)(int*,int);
int sum(int *a,int n)
{
int ans=0;
for(int i=1;i<=n;i++)
ans+=a[i];
return ans;
}
int main()
{
int A[10],n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&A[i]);
Psum=sum;//不用'&',因为这里函数没有括号视为是函数的地址
printf("%d\n",(*Psum)(A,n));
}
到了这里,我们就知道sort是如何把函数作为函数的参数的了,可以拟出一个sort(只针对int数组):
void sort(int *begin,int *end,bool (*cmp)(int,int))
{
/*......*/
}
结构体指针
关于结构体,请看:C++重载运算符详解
和声明一般指针一样,名称前加*
即可。
要访问这个指针所指向结构体当中的元素有2种方法:
1.’*’法:在指针前加上*
,再把它们括起来,就可以当该指针指向的结构体用了。
2.’->’法,和一般结构体用法一样,只是把成员运算符.
变为->
即可。
详见示例:
#include<cstdio>
struct student
{
int snum;
int age,grade;
};
student T;
student *p;
int main()
{
p=&T;
T.snum=15;
T.age=12;
T.grade=90;
printf("%d\n",(*p).snum);
printf("%d\n",p->age);
printf("%d\n",T.grade);
}
结果:
多重指针
和数组一样,你可以在声明时连续打2个(或多个)*
,例如:
#include<cstdio>
int main()
{
int **p,*q;//p就是指向一个指针的指针,q是指向一个普通变量的指针
int a;
a=1;
q=&a;
p=&q;
printf("%p %p %p %p\n",p,*p,q,&a);
printf("%d %d %d",**p,*q,a);
}
猜猜结果:
前面的地址有可能会不一样,但输出的格式肯定是:
Q P P
A A A
为什么*p
要用%p
输出?因为*p
是q
,而q
是一个指针,所以用%p
。