前言
本系列文章承接C语言的学习,需要有C语言的基础才能学会哦~
第3篇主要讲的是有关于C++的引用类型、内联inline和nullptr。
C++才起步,都很简单呢!
目录
引用类型
即对同一块连续存储空间,多取一个标识符(别名),语法上不开辟空间(但是汇编底层也是用指针实现的)
基本语法
int a = 0;
int& b = a;
如上,引用类型的定义方式为:类型& 引用别名 = 引用对象(“类型”,要与引用对象相同)。
这里表示引用b为a的别名。
引用类型变量同原变量指向同一空间,即a,b都指向同一块空间。
int a = 0;
int& b = a;
int& c = a;
//也可以给别名取别名
int& d = c;
//运行代码,四次输出的地址都相同,可知引用类型与原变量a指向同一个地址
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
而且,可以给别名起别名(如上代码)。
一个变量可以有多个别名。
特性
①引用在定义时必须初始化。
②一个变量可以有多个别名。
③引用不可以再改变指向。
应用
引用传参和引用做返回值的时候减少拷贝,从而提高效率。而且改变引用类型对象同时会改变被引用对象。
struct A
{
int arr[1000];//占用空间大
};
//不使用引用传参和指针传参
int Func(A aa)
{
//此处需要拷贝一个占用较大的A类型,效率低
return aa.arr[0];
}
int& Func2(A& aa)
{
//此处返回值和传参都无需拷贝
return aa.arr[0];//数组首元素
}
//并且,若修改返回的值,数组的首元素同时也会被修改。
int main()
{
struct A aa;
for (int i = 0; i < 1000; i++)
{
aa.arr[i] = i;
}
//首元素为0,输出为1
cout << ++Func2(aa) << endl;
return 0;
}
首先,将返回值传回时,还会开辟一个临时对象存储返回值。这里使用了引用作返回值。
然后,将引用类型作为临时对象返回,无需再进行拷贝,无需开辟空间,因为引用是被引用对象的别名,可以认为是同一个变量。
而且可以通过修改返回值,达到修改被引用对象的作用
调用Func2,可以Func2(aa)++,改变返回值,即可修改原数组的首元素
调用Func,Func(aa)++会报错,因为返回的是个int类型数据,他不是一个左操作数。
注意,不可以出现野引用现象(类似野指针)!!
const引用
const引用,用于引用const对象,也可以引用普通对象。(const对象,不可被赋值,必须初始化)
基本语法
const int a = 0;
const int& b = a;
引用const对象,要使用const引用。否则·······
这是一个关于访问权限的问题(指针和引用才有)。
访问权限演示
//①
const int a = 0;
//!!错误演示!!
int &b = a;//int&引用指向的对象是可以修改的,这里放大了访问权限
//!!正确演示!!
const int& ra = a;
//②
int c = 10;
const int&rc = c;//该引用为访问权限为const,缩小了权限,是允许的
//因此↓
c++;//c可以被修改
rc++;//不可以通过rc修改c
//③
double d = 12.34;
const int& rd = d;//d先要进行类型转换转为int,中间产生临时const对象,传给rd时要求rd也是const引用
void func(const int& rx)
{
int ret = rx;
return ret;
}
int e = 10;
const int& re = e * 10;//e * 10的中间结果为临时const对象,同上处理
func(e);//e的权限缩小为const
func(e * 3);//e * 3为临时const对象,函数传参要为const引用
//综上,类型转换、中间值、传参等情况可能会出现访问权限的问题
C++在类型转换或者多次运算时,中间结果也为const对象。
引用与指针的关系
①指针和引用相辅相成,各有特点,不可替代。
②在语法上,引用不开辟空间存储,指针变量要开辟空间存储。
③引用必须初始化,指针可以不初始化(只是建议要初始化)。
④引用可以直接访问对象,指针还需要解引用。
⑤在sizeof中,表达含义不同,引用,则为所引用的类型的大小;指针,则是指向地址空间的字节个数。
⑥指针容易出现空指针和野指针,引用很少会出现野引用,引用使用起来更安全。
⑦因为引用在初始化之后就不可以再赋值,因此不可以用在链表等数据结构中。
内联inline
inline修饰的函数,叫作内联函数。C++编译器会在调用的地方直接展开内敛函数。它设计的目的就是要平替C语言的宏,避免宏的坑。
注:define宏函数的使用要点
①宏函数最后,不可加分号#define ADD(x, y) ((x)+(y)); cout << ADD(1,2) << endl;//会报错,展开后多了个分号
②宏函数需要加内部的括号
③宏函数需要加外部的括号
都是为了避免宏函数展开时,因运算符优先级的问题,导致运算顺序没有满足实际需要,从而发生错误。
宏函数的好处:函数展开,不需要开辟栈帧,但是实现复杂,容易出错,展开后代码量大,不可以调试。
内联既保留了不用开辟栈帧的优点,而且没有宏的坑。
在vs编译器的debug版本下,内联默认不展开,inline修饰会忽略,这是为了能够展开调试(展开了就和宏一样无法调试了)。可以设置修改为不分内联函数展开,即只展开短小函数,递归函数等复杂函数不展开。
如图,可设置为简单inline函数展开。
究竟要多复杂的函数才会不展开呢?这取决于编译器自身,不同的编译器在这一点上就不同。在汇编层,不展开的内联函数会有call指令出现。
假设Add函数在汇编层内有100条指令,在工程中调用了10000次
若不展开,汇编层一共只有10000条call指令;
若展开,汇编层一共有100 * 10000条指令。
因此,若展开复杂函数,会让代码量剧增,会导致指令占用内存变多。
且内联函数不建议声明和定义分离在两个不同的文件上,可能会出现链接错误。因为内联函数没有地址。
nullptr
NULL在C++中为int类型的0,在C语言中为空指针,即void*类型。但在C++中void*不可以再转类型,这就导致无法实现泛式函数。
nullptr为特殊关键字,可以转换为任意类型的指针,在C++中要用nullptr来定义空指针。
❤~~本文完结!!感谢观看!!欢迎来我博客做客~~❤