C++ Lesson 2.引用
C++ Lesson 2.引用
1. 引用
a. 引用概念
引用不是新定义一个变量,而是给已存在的变量取一个别名,当然可以取多个别名,编译器也不会为引用变量开辟内存空间,引用实体和它引用的变量共用同一块内存空间
int main()
{
//注:引用类型必须与引用实体为同种类型
int a = 123;
int& ra = a;//类型 &引用变量名(对象名)=引用实体
cout << a << " " << ra << endl;
cout << &ra << " " << &a << endl;
return 0;
}
123 123
000000E06196FC44 000000E06196FC44
b. 引用特性
i. 引用在定义是必须初始化
ii. 一个变量可以有多个引用
iii. 引用一旦引用一个实体,再也不能引用其他实体
c. 常引用
//常引用
int main()
{
double d = 12.34;
//int& rdd = d;引用类型必须与引用实体为同种类型
const int& rd = d;//why?
cout << d <<endl<< rd << endl;
}
12.34
12
const int& rd=d;拿到整形部分,发生隐式转换
在编辑器中会创建一个临时变量(不知道变量名,不知道地址,具有常性),将隐式转换后的值放入rd中。临时变量具有常性,所以 const int& rd = d;
d. 使用场景
i.概念:给引用实体取别名——简化代码
struct A
{
int a = 10;
struct B
{
int b = 20;
};
struct B bb;
};
int main()
{
struct A aa;
int& ra = aa.a;
int& rb = aa.bb.b;
cout << ra << " " << rb << endl;
}
10 20
ii.做参数
如果引用类型作为函数形参,尽量传递引用
如果不想通过形参修改外部实参,将引用给为const int&
void swap( int& left, int& right)//会修改外部实参
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int a = 123;
int b = 456;
int& ra = a;
int& rb = b;
cout << a << " " << b << endl;
swap(ra,rb);
cout << a << " " << b << endl;
return 0;
}
123 456
456 123
iii.做返回值
//3.作为函数的返回值类型
using namespace std;
int& Add(int left, int right)
{
int temp = left + right;
cout << &temp << endl;
return temp;
}
//当Add函数运行完毕后,函数的栈帧已经被系统收回,
//及Add对应的栈帧空间不能使用了,但是空间还在,
//而且Add运行完成之后在空间中留下的垃圾数据并没有被消除
//注:如果引用方式作为函数返回值类型,不能返回函数栈上的空间
//如果返回,则返回的实体必须比函数的生命周期要长
int main()
{
int& ret = Add(10, 20);
cout << ret << endl;
Add(20, 30);
cout << ret << endl;
Add(40,50);
cout << ret << endl;
return 0;
}
000000E77F7BFA64
30
000000E77F7BFA64
50
000000E77F7BFA64
90
注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则使用传值返回或使用const 类型& 引用变量名(对象名)
e. 传值传址传引用效率比较
传址,引用效率几乎相同并且更好,传值效率非常低下
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A* a) {}
void TestFunc3(A& a) {}
void TestRefAndValue(int num)
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < num; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以指针作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < num; ++i)
TestFunc2(&a);
size_t end2 = clock();
// 以引用作为函数参数
size_t begin3 = clock();
for (size_t i = 0; i < num; ++i)
TestFunc3(a);
size_t end3 = clock();
// 分别计算三个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A*)-time:" << end2 - begin2 << endl;
cout << "TestFunc3(A&)-time:" << end3 - begin3 << endl;
}
int main()
{
int num=10000000;
TestRefAndValue(num);
return 0;
}
TestFunc1(A)-time:8998
TestFunc2(A*)-time:32
TestFunc3(A&)-time:40
f. 值,指针,引用作为返回值类型的性能比较
值作为返回值类型的性能很差,指针和引用作为返回值类型的性能更好
#include <time.h>
using namespace std;
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
// 指针返回
A* TestFunc3() { return &a; }
void TestReturnByRefOrValue(int num )
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < num; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
size_t begin2 = clock();
for (size_t i = 0; i < num; ++i)
TestFunc2();
size_t end2 = clock();
// 以指针作为函数的返回值类型
size_t begin3 = clock();
for (size_t i = 0; i < num; ++i)
TestFunc3();
size_t end3 = clock();
// 计算三个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
cout << "TestFunc3 time:" << end3 - begin3 << endl;
}
int main()
{
int num = 10000000;
TestReturnByRefOrValue(num);
return 0;
}
TestFunc1 time:17662
TestFunc2 time:34
TestFunc3 time:40
g. 引用与指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上引用实际是有空间的,因为引用就是按照指针实现的。在底层引用就是指针。
引用和指针不同点
1.引用在定义时必须初始化,指针没有要求(指向一个合法的内存空间或指向NULL;更合理)
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
int a=10;
int& ra=a;
//int* const ra=a;(指向不可被修改)
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
4个字节) - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
const int&& rra = 10;
//注:T&&是C++11中的新特性,称为右值引用
6.有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全