指针是啥?
众所周知,你在程序中向计算机申请一个变量(如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];
1
声明一个有十个指针(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)
1
其中吧*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。
本文介绍c++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被c++11弃用。
为什么要使用智能指针:我们知道c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写了,但是我们不能避免程序还未执行到delete时就跳转了或者在函数中没有执行到最后的delete语句就返回了,如果我们不在每一个可能跳转或者返回的语句前释放资源,就会造成内存泄露。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。
-
区别以下指针类型?
int *p[10] int (*p)[10] int *p(int) int (*p)(int)
1、 int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
2、nt (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
3、int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
4、int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。
常量指针和指针常量区别?
常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *p或const int *p。
指针常量是一个不能给改变指向的指针。如int *const p。
数组名和指针(这里为指向数组首元素的指针)区别?
二者均可通过增减偏移量来访问数组中的元素。
数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。
当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。
野指针是什么?
也叫空悬指针,不是指向null的指针,是指向垃圾内存的指
产生原因及解决办法:
指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
指针free或delete之后没有及时置空 => 释放操作后立即置空。