C++入门基础(下)
1、总述
今天给大家分享C++后面常用的一些知识:引用,inline,nullptr。尤其是引用后续用的非常多。
2、引用
2.1、引用的概念和定义
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间, 它和它引⽤的变量共⽤同⼀块内存空间。就比如水浒传中很多人物都有别名,但他们的别名和真名都指的是一个人。(所以简单来说引用就是取别名)。
语法格式:
类型& 引⽤别名=引⽤对象;
这里说一下这里引用也用到了&操作符,这里我们给他区分一下:
这里&放在数据类型后面就是引用,放在变量名前面就是取地址。然后我说一下引用是怎么来的?就是C++祖师爷因为某些地方使用指针太麻烦了,就创建了引用。a,b,c,d指向的是同一块地址,并且我们对其中一个别名++,它们所指向的值都会改变。
结果如下图:
2.2、引用的特性
1、引⽤在定义时必须初始化
2、⼀个变量可以有多个引⽤
3、引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
我们来一一解释:
先来看1、引⽤在定义时必须初始化
如果我们不初始化就会报错所以必须初始化。像图中初始化就不会报错了。
接下来看2、⼀个变量可以有多个引⽤
引用可以给引用起别名,一个变量可以有多个引用,且地址相同。
最后来看3、引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
从图中很明显的看到b是a的别名,后面把c的值赋给b时,b指向的还是a的地址,而不是c的地址。最终可以说明: 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体。
2.3、引用的使用
1、引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被引⽤对象。下面还是用代码给大家演示:
void STModityTop(ST& rs, int x)
{
rs.a[rs.top-1] = x;
}
STModityTop(sls,1);
就是大家在实现栈是会写一个改变栈顶的数据的函数,我们形参通常用的是指针我们学了引用后就可以用引用替换了,因为rs是sls的别名rs改变sls也会跟着改变。这里与传指针的效果是一样的。
2、引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些。
这句话同上,就不说明了。
3、引⽤返回值的场景相对⽐较复杂,我们在这⾥简单讲了⼀下场景,还有⼀些内容后续类和对象章节中会继续深⼊讲解。
如上所示,上面是一个取栈顶元素的函数,如果用常量返回时,返回的结果会存放到一个临时变量中再有一个变量从临时变量中拿到返回结果。但我们用引用值返回时 ,返回了函数返回值的引用,拿到了栈顶元素并进行了修改,且没产生临时变量。这样更方便快捷。达到减少拷贝,提高效率的目的。这里大家简单了解一下即可。
4、引⽤和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引⽤跟其他 语⾔的引⽤(如Java)是有很⼤的区别的,除了⽤法,最⼤的点,C++引⽤定义后不能改变指向, Java的引⽤可以改变指向。
这里简单说下,在链表中头结点必须得传指针,如果传引用在增删时由于引用的特性改变不了指向,所以只可以传指针,这也侧面说明了指针和引用相辅相成,不可替代,各有优却。
2.4、const引用
1、可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。
这时候我们该怎么做?
这里用const修饰就可以了,让我们想想如果权限不可以被放大,那么可以被缩小吗?
我们验证一下:
运行后没有报错,那么我们得出结论:权限可以被缩小,但不可以被放大。
2、表达式的结果和类型转换会产生临时变量存放中间值,⽽C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以。
看上图直接用int& 直接就报错了这是因为a+c这个表达式产生的结果会存放在临时变量中,而临时变量是常量,不能被修改。因此要用const修饰。
让我们再看一种情况:
刚看到这图时,我估计很多人会以为是单纯的类型不同问题,那么再接着看下图,又该如何理解:
加上const修饰后,既然编译通过了。我来解释一下: double转换为int会进行类型转换,会产生临时变量,加上const后是修饰临时变量的。综述,表达式的结果和类型转换会产生临时变量存放中间值,⽽C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以。
这里稍微解释一下临时变量:所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。
2.5、指针和引用的关系
C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。
1、语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。(但底层就是用指针实现的)。
2、引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
3、引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
4、引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
5、sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)。
6、指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
这就是一个引用为野引用的例子,a是局部变量,函数调用返回时,栈帧销毁,但引用返回的是a的地址但a被销毁了,这时会报错。
3、inline
1、⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联 函数就需要建⽴栈帧了,就可以提⾼效率。
2、inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
3、C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调试,C++设计了inline⽬的就是替代C的宏函数。
这里简单给大家回忆宏函数的实现:
4、vs编译器debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下以下两个地⽅。
5、inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
图中是代码的汇编(vs优化后的)我们明显看到Add函数被展开。
4、nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void)*0)
#endif
#endif
C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种 定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的 f(int*)函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。f((void*)NULL); 调⽤会报错。
因此C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。 所以以后在使用C++时用nullptr少用NULL。
今天的分享到此结束,谢谢大家!