1. 引用
1.1 引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
语法
类型& 引用变量名(对象名) = 引用实体;
示例
很显然,在下面这个例子中,a与b共用同一地址。
这里需要注意:引用类型必须和引用实体是同种类型的。
1.2 引用特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体
示例
引用定义时必须初始化。
一个变量可以多次引用
引用一旦引用一个实体,再不能引用其他实体
1.3 常引用
1.权限只能缩小,不能放大。
2.类型转换时会产生临时变量,临时变量具有常性
关于这两个概念,我们结合代码来看,详见代码注释
所谓权限,无非就是对对象的操作权限,对常量我们的操作权限为只读,对变量则可读可写。
那么类型转换时会产生临时变量是什么意思呢?
我们来看这一段代码:
int main()
{double a = 13.14;int b = a;//double转换为intint i = 97;char ch = 'a';if (i == ch) //char转换为int{cout << "相等" << endl;}cout << b << endl;return 0;
}
这一段代码有两个类型转换的例子,两者相类似,都是在类型转换时产生一个临时变量。
double a = 13.14; int b = a;中,产生一个值为13的临时变量赋值给b。
i与ch的比较中,产生一个值为该字符ascll对应值的整型临时变量。
这些临时变量有一个共性,那就是具有常性。
1.4 关于引用的使用场景
1.做参数
2.做返回值
1.做参数
我们以下述代码为例,曾经的交换数据,我们需要借助指针来实现,而现在引用就可以,而且还比指针简单很多。
引用版:
void Swap(int& x, int& y)
{int temp = x;x = y;y = temp;
}int main()
{int a = 10;int b = 20;cout << "a:" << a << "\t" << "b:" << b << endl;Swap(a, b);cout << "a:" << a << "\t" << "b:" << b << endl;return 0;
}
指针版:
明显引用更为方便简洁。
2.做返回值
下面这段代码,大家觉得有没有问题呢?
int& add(int x, int y)
{int c = x + y;return c;
}
int main()
{int a = 10;int b = 5;int c = 9;int d = 10;int ret = add(a, b);cout << "ret:\t" << ret << endl;cout << "ret:\t" << ret << endl;return 0;
}
我们看看运行结果:
很好,貌似没有问题,但我们发现,编译器给我们发出了一个小小的警告,如果是以前我们可能会嗤之以鼻不管不顾,但现在的我们不一样了,一个小小的警告也要抹杀在牢笼里。
完蛋,当我们准备解决警告时,却发现他是一个大问题。
这个问题我们之前遇到过,费了很大的力气才解决掉,现在他又出来了。
这是为什么呢?
很简单,一个函数的生命周期很短,函数调用,产生函数栈帧;函数调用结束,函数栈帧销毁。 那函数内产生局部变量或临时变量的生命周期自然随之结束,这一段空间就还给了操作系统,而我们直接对局部变量起别名并返回,然后在其他地方一直使用,不就相当于一直在退了房的宾馆里转悠嘛,违法行为不可取。
大家不爱看字,那就画个图给大家看。
如果你的东西还放在里面,宾馆换了个把锁,你进不去了,东西就丢了。
所以如果是全局变量,staic修饰过的变量,或者动态开辟的内存就不受限制。
1.5 传值,传引用效率比较
我们使用如下代码进行比较两种效率。 ( 关于clock () 的用法大家可以在网上查阅 )
struct A
{int arr[10000];
};void TestTranVal(A a)//传值
{
}void TestTranQuote(A& a)//传引用
{
}
int main()
{A a;size_t begin1 = clock();for(int i=0;i<10000;i++)TestTranVal(a);size_t end1 = clock();size_t begin2 = clock();for (int i = 0; i < 10000; i++)TestTranQuote(a);size_t end2 = clock();cout << "TestTranVal--time:\t" << end1 - begin1 << endl;cout << "TestTranQuote--time:\t" << end2 - begin2 << endl;return 0;
}
至于为什么会这样?且看下面这段文字!
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
1.6值和引用的作为返回值类型的性能比较
这里只需要将上面的代码略微改变。
struct A
{int arr[10000];
};A a;//全局变量A TestTranVal()
{return a;
}A& TestTranQuote()
{return a;
}
int main()
{size_t begin1 = clock();for (int i = 0; i < 10000; i++)TestTranVal();size_t end1 = clock();size_t begin2 = clock();for (int i = 0; i < 10000; i++)TestTranQuote();size_t end2 = clock();cout << "TestTranVal--time:\t" << end1 - begin1 << endl;cout << "TestTranQuote--time:\t" << end2 - begin2 << endl;return 0;
}
造成这一现象的原因在1.5后面哦!
1.7引用和指针的区别
相信大家在学习引用的时候,总是有一种似曾相识的感觉,这是因为他的功能和指针有很大部分的重叠。
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
但在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main()
{int a = 10;int c = 9;int& b = a;b = 20;cout << " a " << a << endl;int* p = &a;*p = 30;cout << " a " << a << endl;p = &c;cout << " *p " << *p << endl;return 0;
}
我们来看看汇编视角的引用与指针:
很显然,在汇编看来,指针与引用并没有区别。
我们试着来总结一下指针与引用的不同点:
1.引用是给一个变量起别名,而指针存储变量的数据地址。
2.引用在定义时必须初始化,指针则没有这个要求。
3.引用定义后就不能再更改指向,指针可以,这是他们两个最本质的区别,也是引用无法完全替代指针的根本原因。
4.在sizeof中,引用的结果就是引用类型的大小,而指针恒定为4或者8(取决于编译器)。
5.引用相对于指针更加安全,没有空引用,但有空指针。
6.譬如自增自减,引用操作的是对象的实体内容,而指针操作的是地址块。
7.在底层汇编视角,引用与指针并没有什么区别。
8.对于对象的访问方式不同,指针需要解引用,而引用由编译器处理。
2. 内联函数
2.1 概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的
调用。
2.2 内联函数的特性
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到
2.3 内联函数与宏
宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?
1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数
结语
指舞键盘上,悠然博弈回。如果您感兴趣,不妨看看我其他的文章,也许会有更多的收获。希望我们能在未来的日子里一起成长,共同进步。