工具为了解决问题而创造出来,不要为了制造工具而制造工具。
目录
概念
引用的特性
引用的常见用法
引用作为函数返回值
引用作为参数
常引用
引用的效率
引用与指针
指针与引用的区别
概念
引用(reference)就是C++对C语言的重要扩充,C++在C++1.0版本中添加了引用等一系列概念。
引用并不是重新定义一个变量,引用是目标变量的一个别名,对引用的 *** 作与对变量的直接 *** 作完全一样。
类型标识符 & 引用名 = 目标变量名
引用的定义方法如上。
引用类型 必须和引用 实体 是 同种类型引用的特性
引用的常见用法 引用作为函数返回值1.引用必须在定义时初始化。
2.一个变量可以有多个引用。
3.引用一旦引用一个实体,不能再引用其它实体。
4.引用本身不占内存空间,与被引用对象共用内存。
5.不能建立引用的数组。因为数组是一个由若干个元素所组成的集合,所以无法建立一个由引用组成的集合。但是可以建立数组的引用。
引用类型是可以作为函数返回值的,但是它其中涉及到的部分要素容易被忽略,导致程序出错。
int& add(int a, int b) {
int ret = a + b;
return ret;
}
int main() {
int& r = add(2, 3);
return 0;
}
如以上代码示例中, 如果运行,在编译器中可能不会报错,但是会有一个警告:
这意味着我们函数返回的空间可能是非法的。
当我们将 ret 的引用作为返回值返回给主函数时,ret 所占用的空间在编译器中已经成为了无主的地盘了。如果我们在 add 函数执行之后再加上其它的内容,就可能将 r 引用空间再次占用,导致报错。
所以,引用作为函数返回值时,一定要注意,不能返回函数栈上的空间。
由此我们也可以得到引用类型作为函数返回值的要求:
引用作为参数返回实体的生命周期一定要比函数长,即不能随着函数的结束而销毁。
在C++中,参数传递的方式有两种:传值与传引用。
传值是我们都熟悉的,函数的形参是实参的一个拷贝,函数在执行过程中,形参的改变不会影响到实参。
传引用的传参方式下,形参是对应实参的引用,而引用的一大特性在于:对引用的 *** 作与对实体的 *** 作完全一致。即,通过传引用的方式传入参数,我们对形参的修改就等同于对实参的修改。
#include
using std::cout;
using std::endl;
void swap1(int a1, int b1) {
int tmp = a1;
a1 = b1;
b1 = tmp;
}
void swap2(int& a2, int& b2) {
int tmp = a2;
a2 = b2;
b2 = tmp;
}
int main() {
int a = 1;
int b = 230;
cout << "初始:" << "a = " << a << " b = " << b << endl;
swap1(a, b);
cout << "swap1:" << "a = " << a << " b = " << b << endl;
swap2(a, b);
cout << "swap2:" << "a = " << a << " b = " << b << endl;
return 0;
}
结果是显而易见的,单纯的传值是无法完成我们预期的交换功能的,而传引用却可以。
这是因为传值传入的形参本质是实参的拷贝,两者的地址并不相同,对形参的 *** 作自然无法影响到实参。
而传引用传入的引用是实参的别名,形参实参二者使用的是同一块地址空间,对形参的 *** 作自然也就可以对实参造成影响。
我们之前在C语言中绝大多数使用一级指针的地方搜可以使用引用代替,同理,大多数使用二级指针的地方可以使用一级指针的引用代替。
常引用常引用,顾名思义,即 const 类型的引用。定义引用时,在前面加 const 关键字,则该引用为常引用。
常引用与普通引用的差别在于——不能通过常引用来修改实体的内容。
注意,只是不能通过常引用来修改实体内容,如果实体本身不是 const 类型,我们可以通过修改其它引用或实体本身来达到修改的目的。
#include
using std::cout;
using std::endl;
int main() {
int a = 10;
const int& ca = a;
cout << ca << endl;
a = 20;
cout << ca << endl;
int& nca = a;
nca = 30;
cout << ca << endl;
return 0;
}
对于 const 常量的引用只能是常引用,同理,对于常引用的引用只能是常引用。
常引用在特殊情况下可以产生临时变量。 (该情况并不具备太高的实用性)
常引用引用不同类型的变量。
#include
using std::cout;
using std::endl;
int main() {
double d = 12.3;
const int& cd = d;
cout << cd << endl;
d = 32.1;
cout << cd << endl;
return 0;
}
可见,以上代码并没有发生报错,并且还成功运行了起来,但是这次对于实体的修改并没有影响到实体的常引用。
通过对代码运行时的进一步分析可以得到:
变量实体与其常引用的地址并不相同。所以,对变量实体的修改与常引用并没有关系。
在对 double 类型变量 d 进行常引用时,因为变量类型并不相同,编译器便会创建一个 int 类型的临时变量用来存储 double 类型变量的整型部分的数据,然后将临时变量给常引用作为实体。
同时,由于临时空间是编译器创建的,用户无法知道空间的名字以及地址,所以用户无法修改临时空间中的内容,所以该空间具有常性,所以可以使用 const 类型的变量来引用。
引用的效率我们可以通过令程序多次重复调用满足某些条件的函数,计算第一次开始调用到最后一次调用完毕之间的时间间隔,通过比较得出传值,传引用;普通返回值,引用作为返回值的效率关系。
#include
#include
using namespace std;
struct A { int a[10000]; };
A TestFunc1(A a) { return a; }
A TestFunc2(A& a) { return a; }
A& TestFunc3(A& a) { return a; }
int main()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i) {
TestFunc1(a);
}
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i) {
TestFunc2(a);
}
size_t end2 = clock();
//以引用作为返回值
size_t begin3 = clock();
for (size_t i = 0; i < 10000; ++i) {
TestFunc3(a);
}
size_t end3 = clock();
// 分别计算三个函数运行结束后的时间
cout << "A_TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "A_TestFunc2(A&)-time:" << end2 - begin2 << endl;
cout << "A&_TestFunc3(A&)-time:" << end3 - begin3 << endl;
return 0;
}
运行结果:
可见传引用的效率明显高于传值,引用作为返回值的效率明显高于普通返回值
引用与指针int main() {
int a = 3;
int& ra = a;
int* pa = &a;
a = a + 1;
ra = ra + 1;
*pa = (*pa) + 1;
return 0;
}
由以上代码部分反汇编可以看出,从汇编指令的角度来看,对指针与引用在底层的实现与相关 *** 作是完全一致的。即,从底层实现方式上:指针与引用的实现方法是一样的,即引用在底层是按照指针的方式来实现的。
而在概念层面,为了方便使用者的理解,C++将引用解释为:引用是一个别名,与其被引用的实体公用一分内存空间,编译器不会给引用变量分配新的内存空间。
所以,引用实际上是有空间的,空间中的内容是引用实体的地址。
T& 可以看作是 T * const 类型的指针。
指针与引用的区别1.概念上,引用是定义一个变量的别名,指针是存储一个地址。
2.引用在定义时必须初始化,指针没有要求。
3.引用在初始化时引用一个实体后,就不能再引用其它实体,指针可以再任何时候指向任何一个同类型实体。
4.没有空引用,但有NULL指针
5.再 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节数。
6.引用自加为实体加一,指针自加为向后偏移一个类型大小。
7.有多级指针,没有多级引用。
8.访问实体方式不同,指针需要显式解引用,引用由编译器自己处理。
9.引用比指针更安全。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)