c++基础学习之如何区分引用和指针
作者:努力学习的少年
前言
对于我刚学c++的时候,最令我头疼的是引用和指针,老是区分不了它们,那么今天笔者将我的学习到的笔记总结出来,让大家少走一些坑。
术语:什么是对象?(对象在下面将会一直提到)
c++程序员们在很多场合都会使用对象这个名词,通常情况下,对象是指一块能存储数据并具有某种类型的内存空间。有些人仅在与类有关的场景下才使用“对象”这个词。在这里,即认为对象是具有某种数据类型的内存的空间
1.引用
1.1引用的概念
引用为对象起另外一个名字,通过声明符写成&d的形式来定义引用类型,其中d是声明的变量名。
int i = 10; int& refi = i;//refi是i的另外一个名字 int& refi2;//报错:引用必须初始化
一个对象可以有多个引用,相当于给对象起多个名字。
//refi,refi1,refi2与i绑定在一起 int& refi1 = i; int& refi2 = refi1;
定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦引用无法重新绑定到另外一个对象,因此引用必须初始化。
定义引用后,对它进行所有的操作都是与之绑定的对象上进行的。
refi = 100;//将100的值赋值给refi,即把值给i int j=refi;//与int j=i;是一样的
1.2引用的定义
允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头。
int i = 10, int i1 = 10;//i和i1都是int型 int& r = i, int i1 = i;//r是引用,与i进行绑定,i1是int int i3 = i1, & r2 = i1;//i3是int,r2是引用,与i1绑定一起 int& r3 = i, & r4 = i;//r3和r4都是引用
除特殊情况下(下面会讲),引用需要与要绑定的对象严格匹配,而且引用只能绑定在对象上,不能与字面某个值或某个表达式的计算结果绑定在一起。
int& refi = 10;//错误:引用类型的初始值必须是一个对象 int i = 10; double& d = i;//错误:引用的类型必须是int型
1.3引用与const
把引用绑定到const对象上,我们称之为对常量的引用,与普通引用不同的是,对常量引用不能被做修改它所绑定的值。
const int i = 10; const int& r1 = i;//正确:引用及其对应的对象都是常量 const int& r2 = 200;//正确:引用及其对应的对象都是常量 r1 = 40;//错误:r1是常量的引用,不能修改 int& r2 = r1;//错误:r1是常量引用,只能读,r2是非常量引用,能读能修改
术语:c++程序员们经常把词组“对const的引用”简称“常量引用”。
在我们之前提到,引用的类型必须与其所引用的对象的类型一致,但是有两个例外,第一种是情况是初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换为引用类型即可,第二种是,常量引用可以绑定非const对象可以绑定。
int i = 42; const int& r1 = i;//正确:允许将const int&绑定到一个普通int对象上 const int& r2 = i * 2;//正确:r2是一个常量引用 int& r3 = i * 2;//错误:r3是一个普通的非常量引用
而且对于常量引用还有更神奇的地方是:
double d1 = 1.111; const int& r3 = d1;
上面的代码是正确的,r3是一个常量int类型的&引用,按理说应该只能绑定int类型的常量引用,单d1是一个double类型的对象 ,但编译器为了确保让r3绑定一个int类型对象,编译器自动把上述代码转换为:
const int tmp = d1;//由double生成一个int类型的临时量 const int& r3 = tmp;//让r3绑定这个临时量tmp
此处r3是绑定了临时量,而非d1对象,所谓的临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。
对const引用可以引用非const对象:
int i = 10; int& r1 = i; const int& r2 = i;//正确:const引用可以引用非const对象,但是不能通过r2修改i r1 = 20;//正确:r1为非const引用,可以修改 r2 = 30;//错误:r2为cosnt引用,不能对i进行修改
1.4引用的使用场景
1.做参数
void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } int main() { int a=0,b=10; swap(a,b); }
引用可以用来做参数,把对象传给函数时,则函数中的参数则绑定到对应的对象中,不会产生临时变量例如:left则则是a的引用,right则是b的引用。
2.做返回值
int& Count() { static int n = 0;//n存在静态区中 n++; return n; //返回对象是n的引用 } int& Add(int a, int b) { int c = a + b; return c; } int main() { //这是错误的:因为c出了函数作用域后则会被销毁,则c这块空间就不存在 //ret引用就无效。 int& ret=ADD(1,2); }
总结:
如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已 经还给系统了,则必须使用传值返回。
2.指针
2.1概念
指针是“指向”另外一种类型的复合类型。指针是用来存储变量的地址,本身就是一个对象,允许对指针进行赋值和拷贝,而且指针的生命周期内它可以先后指向不同的对象。而且指针无需在定义时就对它初始化,它跟其它内置类型一样,如果没有初始化,也将拥有一个不确定的值。
定义指针时类型的方法将声明符写成*d的形式,其中d是变量名,如果在一条语句中定义了几个指针变量,每个变量名之前必须有*。
int* i1, * i2;//i1和i2都是int类型的指针 int d, * d1;//d是int对象,d1是指向int类型的指针
2.2获取对象的地址
指针是存放对象的地址,要想获取某个对象的地址,需要使用 &(取地址操作符)
int i = 10; int* pi = &i;//pi存放变量的i的地址,或者说pi是指向变量i的指针
注意:指针也是有大小的,它的大小根据在不同的平台是不同的,与指针的类型无关。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
在32位机器上,不管是int,char,double等内置类型,或者自定义类型,指针的大小永远都是4个字节。
除了特殊情况(下面会讲),指针的类型都要和它所指向的对象严格匹配。
double d; double* pd = &d;//正确:pd是指向double对象的指针 int* pi1 = &d;//错误:试图将double对象的地址给int类型的指针 pi1=pd;//错误:指针pd和pi1的类型不匹配
2.3利用指针访问对象
如果指针指向了一个对象,那么想要通过指针访问对象,需要使用操作符 *(解引用操作符)
int i = 10; int* pi = &i;//pi存放变量的i的地址,或者说pi是指向变量i的指针 *pi = 20;//将i的值修改为20 cout << *pi << endl;//输出的是i对象输出20
如上述,为*pi赋值实际上是为p所指的对象赋值。
注意:解引用仅适合那些确实指向某个对象的有效指针。
2.3空指针
空指针不指向任何对象,在使用一个指针之前,需要检查该指针是否为空指针。生成空指针的方法:
int* p1 = nullptr;//等价于int *p1=0; int* p2 = 0;//直接将p2初始化为字面常量0 //需要先#inclde cstdlib int* p3 = NULL;
把int变量直接赋给指针是错误的操作,即使int变量恰好等于0也不可以。
int zero = 0; int* p4 = zero;//错误:不能将int变量赋值给指针
2.4野指针
2.4.1概念:
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
访问野指针,相当于去访问一个本不存在的位置上本不存在的对象。
2.4.2野指针的产生:
int* p1;//指针没有初始化 int arr[5] = { 0 }; arr[5] = 11;//指针的越界访问 int* ptr = (int*)malloc(sizeof(int)); free(ptr);//指针指向的空间被释放
1.指针未初始化:
上诉中的p1是没有初始化的指针,它没有指向的空间,但它所占的内容将被看作一个地址值,糟糕的是,如果指针所占的空间恰好有内容,而这些内容恰好被当作一个地址,那么我们很难分清它到底是合法还是非法的。所以 建议初始化所有指针,如果不知道不清楚指针指向何处,就把它初始化为空指针。
2.指针的越界访问:
当指针指向的范围超出数组 arr 的范围时, p 就是野指针 ,当我们定义只能存储5个int类型的空间的数组,假设我们要去访问数组的第6个位置。由于第6个位置的空间没有定义出来,是未知的。所以arr[5]就是野指针。
3. 指针指向的空间释放:
当我们去malloc一块空间时,就会返回这个空间的指针去管理这块空间,当这块空间释放(销毁)掉时,但指针依然还在,指针指向的空间就未知的,就为野指针,所以我们释放一块空间时,我们需要把指针置为空,如ptr=nullptr。
2.5各个指针类型的含义
我们知道指针也有不同的类型,对于指针来说,指针的大小只和平台有关,相同平台下指针的大小都是一样的,那么指针的含义是什么?
int n = 10; char* pc = (char*)&n;//pc为char类型的指针,但它指向n int* pi = &n;//pi为int类型的指针,指向n printf("%p\n", &n);//输出结果:012FF7E0 printf("%p\n", pc);//输出结果:012FF7E0 printf("%p\n", pc + 1);//输出结果:012FF7E1 printf("%p\n", pi);//输出结果:012FF7E0 printf("%p\n", pi + 1);//输出结果:012FF7E4
注意:%p是打印地址的符号,一个字节给一个对应的地址。
我们可以看出char类型的指针加1是走一个字节的距离,int类型的指针加1是走4个字节的距离。
同样的double类型的指针加1是走8个字节的距离。
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
//11223344是16进制数字,0x是16进制的标识符,每两个数字是一个字节 int n = 0x11223344; char *pc = (char *)&n;//pc为char类型的指针,指向n int *pi = &n;//pi为int类型指针,指向n *pc = 0; //i变为0x11223300 *pi = 0; //i变为0x00000000
pi和pc存放的都是n的地址,pc是char类型的指针,通过pc访问n的时候只能访问1个字节,所以*pc只改变一个字节的数值,pi是int类型的指针,通过pi访问n的时候只能访问4个字节,所以*pc改变4个字节的数值.
总结:指针的类型决定了指针能够访问对象有多少个字节的空间。
2.6 void* 指针
void*指针是一种特殊类型的指针,它可存放任意类型的指针.但我们无法确定对该地址中到底是个什么类型的对象。所以我们不能直接操作void*指针所指的对象。
double d; void* pv = &d;//正确,d可以是任意类型的对象
利用void*指针能做的事比较有限,拿它和别的指针比较、作为函数的输入或输出或者赋给另外一个void*指针。
2.7指向指针的指针
指针是内存中的对象,同样指针也有地址,因此,允许把指针的地址在存放到另一个指针中。
通过*的个数可以区别指针的级别,例如 **表示指向指针的指针,***表示指向指针的指针指针。
int i = 0; int* pi = &i; int** ppi = π//ppi指向pi的指针 *ppi;//*ppi等于pi **ppi = 10;//**pi等于i
2.8指针与const
指向常量的指针不能用于改变它所指向的值,要想存放常量对象的地址,只能使用指向常量的指针:
const int i = 10; int* pi = &i;//错误:pi是一个普通的指针 const int* pi1 = &i;//正确:pi1是一个指向常量的指针 *pi1 = 20;//错误:pi1指向的值不能修改
允许另一个指向常量的指针指向一个非常量对象:
int i1 = 10; pi1 = &i1;//正确:但不能通过pi1修改i1的值
指针是对象,所以允许指针本身定为常量,不能被修改,而且常量指针必须初始化,把const放在*之后,则说明指针是一个常量指针,常量指针不变的是指针本身,不是指针指向的值不能改变。
int i2 = 0; int i3-10; int* const pi2 = &i2;//pi2一直指向i2 pi2=&i3;//错误:pi2是一个常量指针 *pi2=12;//正确:pi2指向的值可以被修改 const double d = 1.111; const double* const pd = &d;//pd是一个指向常量的常量指针 *pd=2.22;//错误:pd指向的值不能被修改
const在*之前是修饰指针指向的值,即指针指向的值不能被修改,const在*之后是修饰指针本身,即指针不能被修改。
3.指针和引用的区别
1.指针就是一个对象,允许对指针赋值和拷贝,引用是给对象多起一个名字,不创建对象。
2.指针定义时可以不初始化,引用定义时必须初始化。
3.引用初始化完成后,引用将和它绑定的初始值一直绑定在一起。指针可以先后指向几个不同的对象。
4.有多级指针,但没有多级引用。
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
总结
到此这篇关于c++基础学习之如何区分引用和指针的文章就介绍到这了,更多相关c++区分引用和指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!