一.指针
** 1.指针概念**:指针就是用来保存内存地址的变量。
2.声明指针的方式:int *p
;与运算符*结合,p就表示一个指针
为什么使用指针(指针的三大作用):由于指针可以通过内存地址直接访问数据,可避免在程序中复制大量的代码,因此指针的效率最高,其三大作用如下
2.1 处理堆中存放的大型数据
2.2 快速访问类的成员数据和函数
2.3 以别名的方式向函数传递参数
3.运算符和&,以及运算符->
*
&
是取地址运算符(后面再讲引用的时候这个就变成引用运算符了)。 如
int a=5;
cout<<&a<<endl;//使用&获取变量a的内存地址
*
是指针运算符或间接引用运算符(注意:如果*用于声明指针,那么它就是指针说明符,如下)
int a=1;
int *p=&a;//这里的*表明是指针说明符
cout<<*p<<endl;//这里的*表示指针运算符,使用*获取指针变量p中保存的地址处的值,也就是1
->
是成员指针运算符或指向成员运算符
4.复杂变量的解释(要判断是一个变量的类型就看与他最先结合的运算符是什么),举例如下:
int p
变量p是一个普通的整形变量
int *p
由于变量p先与运算符*结合,所以p本质是一个指针,再与int结合,所以p就是一个指向整形数据的指针
int p[3]
由于变量p只与运算符[]结合,所以p本质是一个数组,再与int结合,所以p就是一个由整形数据组成的数组
int *p[3]
变量p先与[]结合,因为[]的优先级高于,所以p本质上是一个数组,剩下的就是要知道p是一个什么数组,然后p再与结合,说明是p是一个指针数组,然后再与int结合,说明p是一个指向整形数据的指针所组成的数组
int (*p)[3]
由于()改变了优先级,所以变量p先与*结合,所以p本质上是一个指针,那它是什么类型的指针呢,然后p再与[]结合,说明p是一个指向数组的指针,然后再与int结合,说明p是一个指向整形数据组成的数组的指针
int **p
变量p先与结合,说明p本质是一个指针,那它是什么指针呢,然后再与结合,说明p是一个指向指针的指针,然后再与int结合,说明p是一个指向整形数据的指针的指针
int p(int a)
p首先与()结合,说明p是一个函数,然后进入()分析,发现函数有一个int型的参数a,然后再与int结合,说明p是具有整型参数且返回类型为整型的函数。
int (*p)(int a)
由于(p)改变了优先级,所以p先与结合,说明p本质是一个指针,然后再与(int a)结合,说明p是一个函数指针,然后再看(int a)发现函数具有一个int型的参数a,然后再与int结合,说明p是一个指向具有整型参数且返回类型为整型的函数的指针
5.指针的四方面重要内容
指针的类型,指针所指向的类型,指针的值(指针所指向的内存区),指针本身所占据的内存区
5.1 判断这四个方面的规则
指针的类型
:把指针声明语句的指针名字去掉,剩下的部分就是指针的类型
指针所指向的类型
:把指针声明语句里的指针名字和名字左边的指针声明符*去掉,就是指针所指向的类型
指针的值
:在32位程序里,所有类型的指针的值都是一个32位整数,指针的值是指向的内存区域的首地址
指针本身所占据的内存区
:指针本身占据的内存长度可以使用sizeof(指针的类型)测一下就知道,在32位平台里,指针本身占据了4个字节的长度
举例说明指针的类型和指针所指向的类型:
举例 | 指针(ptr)(本身)的类型 | 指针所指向的类型 |
---|---|---|
int *ptr | int * | int |
char *ptr | char * | char |
int **ptr | int ** | int * |
int (*ptr)[3] | int (*)[3] | int ()[3] |
int (ptr)[4] | int ()[4] | int *()[4] |
6.指针与常量
声明定义式 | 注释 | |
---|---|---|
常量指针 | int *const p; | 指针本身不可改变,指向的变量可变 |
指向常量的指针 | const int *p; | 指针本身可变,其指向的变量不可变 |
指向常量的常指针 | const int *const p; | 指针本身不可变,其指向的变量也不可变 |
** 7.指针的注意事项:迷途指针**
定义一个指针之后,如果没有给他赋初值,那么该指针就是一个迷途指针,它可以指向任何地址,并且如果对该指针进行操作就会对位置区域的数据进行修改或删除,照成意想不到的后果,所以解决办法是将定义的指针进行初始化,如下
[cpp]
int *p=0;
这样,这个指针就称为空指针
。
不仅在初始化的时候,还有一种情况迷途指针也会造成危害,就是删除指针delete p;之后,虽然指向的内存空间释放了,但是指针本身还存在,如果再次使用该指针也会造成很严重的后果,所以再删除一个指针之后,将该指针赋值为空。虽然空指针是非法的,容易是程序奔溃,但是我们宁愿程序崩溃,也不愿意调试起来很困难。如下:
int *p=new int;
delete p;
p=0;
[cpp] view plain copy print?
*p=23;
删除指针p后,赋值为0,然后在使用该空指针,程序运行的时候,运行到*p=23就会报错,这样我们就知道我们使用了一个迷途指针,从而及时修改程序。要是我们将p=0这句话去掉,那么程序就不会报错,但是何时崩溃就不知道了,这样加重了我们查问题的难度。
二.引用
1.引用的概念:引用就是别名,如
[cpp]
int &rnum=num;//这里的&是引用运算符
rnum
是整形变量num
的别名,这样,对rnum的操作实际就是对num的操作
。
这里要注意,别名rnum前面的符号&不是取地址运算符,而是引用运算符。
2.引用的作用(为什么要用“引用”)
其实引用只是为变量另外起了一个名字,就像#define num rnum==(int &runm=num),将num定义成rnum,两者在内存中是同一个空间。引用它不像int rnum=num;rnum其实是在内存中新分配了一个空间,所以rnum和num占据的是两个不同的内存空间。那么引用在程序中到底有什么用呢?
我们知道我们在传函数的参数的时候,分两种:按值传递
和按地址传递
。
按值传递:
void swap(int a,int b)
按值传递,编译器会自动在栈中创建a和b的副本,如果形参不是int类型,而是类类型,那么副本就会很大,效率很低,这时候就要考虑按地址传递
按地址传递:
[cpp]
void swap(int *a,int *b)
{
int c;
c=*a;
*a=*b;
*b=c;
}
上面的功能是达到了,把指针作为函数的接受参数虽然能正常使用,但是它却不易阅读,而且很难使用。这时候引用作为形参就派上用场了:
如果引用作为参数,在函数内部可以修改a的值和b的值,这样破坏了按值传递的保护机制,不过我们可以使用const来声明一个不可修改值的引用,假设我们不想在函数内修改a的值,那么上面的代码修改如下:
[cpp]
<span style="font-size:14px;">void swap(const int &a,int &b)
{
int c;
c=a;
a=b;//编译报错:不能给常量a赋值
b=c;
}
通过上面的代码,a就不能被赋值了,这样a就被称为常引用
总结:引用在使用中单纯的给某个变量取个别名是没有意义的,引用的主要目的是可以作为按地址的参数传递还可以作为函数的返回值(注意:局部变量是不能返回引用的,因为局部变量在函数返回后会被销毁)
3.引用的两个特点:
第一:定义引用的同时要对该引用进行初始化,否则编译不能通过。如下
[cpp]
//正确的定义引用
int a=0;
int &ra=a;
//错误的定义引用ra,必须进行初始化
int a=0;
int &ra;
ra=a;
第二:引用可以改变其指向地址的数据,但是不能改变其自身的地址(也就是说别名的地址是不会被改变的,但是别名的值会变),如
[cpp]
int a;
int &ra=a;
a=999;
cout<<"a="<<a<<" "<<"&a="<<&a<<endl;//a=999 &a=0012ff60
cout<<"ra="<<ra<<" " <<"&ra="<<&ra<<endl;//ra=999 &ra=0012ff60
int b=1000;
ra=b;
cout<<"a="<<a <<" "<<"&a="<<&a<<endl;//a=1000 &a=0012ff60
cout<<"ra="<<ra<<" " <<"&ra="<<&ra<<endl;//ra=1000 &ra=0012ff60
cout<<"b="<<b <<" "<<"&b="<<&b<<endl;//b=1000 &b=0012ff48
ra=1;
cout<<"a="<<a <<" "<<"&a="<<&a<<endl;//a=1 &a=0012ff60
cout<<"ra="<<ra<<" " <<"&ra="<<&ra<<endl;//ra=1 &ra=0012ff60
cout<<"b="<<b<<" " <<"&b="<<&b<<endl;//b=1000 &b=0012ff48
上面的例子中ra=b之后,查看ra的地址可以看出来,ra的地址并没有变化,也就是说ra是a的别名,那么就不可能变成其他变量(b)的别名,对ra的操作还是在操作a,而不是b,所以最后在ra=1之后改变的还是a的值。
4.引用的注意事项
4.1 引用声明的时候必须进行初始化 int num=5;int &rnum=num;
4.2 不能建立数组的引用(int &a[5]),不能建立引用的引用(int &&a),不能建立引用的指针(int &*a),
4.3 可以建立指针的引用:
[cpp]
int *p;
int *&q=p;
上面的q与它最先结合的运算符是&,所以他的本质是一个引用,然后再与*结合,所以q是一个指针的引用,也就是指针p的别名。
4.4 引用在作为函数的返回值的时候,千万注意,局部变量是不能作为返回值的,因为局部变量在函数返回的时候已经被释放了,如下:
[cpp]
class A
{
}
A &func()
{
A a;
return a;
}
上面的func函数返回的是局部变量类A的对象a,如果外部使用了它会报错,因为局部变量a在函数返回的时候已经被释放了。
三.指针和引用
1.首先对指针和引用的运算符&和*进行说明:
运算符&
和*
在声明定义的时候(包括形参的声明)称为引用运算符(声明变量是引用)和指针说明符(声明变量是指针);它们在使用的时候称为取地址运算符(获取变量的内存地址)和指针运算符(获取指针指向的地址里的内存数据),例如:
运算符&
:
[cpp]
int a=5;
int &a1=a;//引用运算符
cout<<&a1<<endl;//取地址运算符
int func(int &a,int &b);//引用运算符
运算符*
[cpp]
int b=6;
int *b1=&b;//指针说明符;取地址运算符
cout<<*b1<<endl;//指针运算符
int func(int *a,int *b);//指针说明符
2.常指针和常引用
它们的声明方式相同,都是使用const来定义,但是由于引用本身是不可更改的,所以不用这样声明:int const &a;
3.指针和引用的区别
指针可以为空,引用不可以。
指针可以被赋值,引用只能被初始化,不能被赋值。
在堆中创建一块内存区域,必须使用指针来指向它,不能使用引用来指向它,如int &r=new int;这句话是错误的。这时候你可以这样int *&r=new int;r表示一个指针的引用,也就是指向new int所在的堆区的指针的别名。
网友评论