引用😇
1.1、引用的概念😇
- 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
比如:<<西游记>>中孙悟空有着“孙行者、心猿、金公、斗战胜佛”等别名… - 引用是一个复合类型,复合类型是基于其他类型的基础上定义的类型
- 类型& 引用变量名(对象名) = 引用实体
注意:引用类型必须和引用实体是同种类型的
1.2、引用的特性😈
- 引用必须再定义时初始化
- 一个变量可以有多个引用(别名)
- 引用一旦绑定一个实体,再不能绑定其他实体
void Test()
{
int a = 10;
int& ra = a;
int& rra = a;
int b = 20;
ra = b;
}
通过内存地址来理解😇
1.3、常量引用😇
- 引用可以绑定到被const修饰的对象(变量)上,这样的对象称之为“常量的引用"
- 常量引用在初始化时允许任何表达式作为初始化的值
- 引用的原则:对原引用的对象(变量),权限只能缩小或不变,但不能放大
Ps:权限是指”对原引用对象(变量)的读写操作“
void Test()
{
const int a = 10;
int b = 20;
const int& rb = b;
const int c = 30;
const int& rc = c;
}
void Test()
{
double a = 1.0;
const int& ra = a;
cout << ra << endl;
cout << &a << endl;
cout << &ra << endl;
}
结论:
- 当引用被const修饰并且右值与左值类型不相同时,会进行隐式类型转换
- 隐式类型转换时,会生成一个新的临时变量,可能造成数据丢失的风险
- 最后引用将指向这个临时变量
- 如果引用不加const修饰,将不会进行隐式类型转换,因为“被引用的临时变量不能改变”
注意:临时变量具有"常性",跟被"const“修饰的对象一样,不可修改
void Test()
{
double a = 1.0;
int b = a;
const int& ra = a;
cout << ra << endl;
cout << &a << endl;
cout << &ra << endl;
}
结论:
- 普通类型进行隐式转换时,不存在权限的问题
- 左值的改变,并不会影响临时变量(将临时变量的值拷贝到左值)
- 引用如果不加const修饰,将不会进行隐式类型转换,因为“被引用的临时变量不能改变”
- 左值的改变,会影响临时变量,而临时变量具有常性…
注意:常引用生成临时变量并且指向它时,引用所指向的不是原对象,而是临时变量
1.4、引用的使用场景😇
引用初始化右值为字面值时
void Test()
{
const int& r = 10;
}
引用作为函数参数时
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
- 函数形参使用引用时,也会有权限的问题
- 不改变引用对象的值时,最好使用const修饰
- 被引用的变量作为形参一般为输出型参数,只进行输出,不改变
- 减少拷贝,提高效率(传值会拷贝一份临时变量,而引用不会)
引用作为函数返回类型时
- 引用作为函数返回类型时,返回的值不会生成临时变量(直接返回变量的别名)
- 传值返回一个变量时,会生成临时变量
int& Count1()
{
static int n = 0;
++n;
return n;
}
int Count2()
{
int a = 0;
return a;
}
int main()
{
int& n2 = Count1();
int n1 = Count2();
return 0;
}
下面代码输出什么结果?为什么?
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
- 在子函数中,不能返回一个局部变量的引用,会导致不确定性
- 会出现越界的问题(函数调用访问非法空间)
注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回
1.5、传值和传引用的效率😇
- 以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回
- 而是传递实参或者返回变量的一份临时的拷贝
- 因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
void TestRefAndValue()
{
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();
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
运行结果: 在语法的角度上,引用和指针的区别是什么?
- 引用在绑定一个左值时,不会开辟额外的空间
- 指针在指向一个变量时,会开辟空间存储它(4/8Byte)
void Test()
{
int a = 10;
int& ra = a;
int* pa = &a;
}
- 在底层的角度上,引用和指针的区别是差不多的
1.6、值和引用的作为返回值类型的性能😈
#include <time.h>
struct A{ int a[10000]; };
A a;
A TestFunc1() { return a;}
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
总结:传值和指针在作为传参以及返回值类型上效率相差很大
1.7、引用和指针的区别😇
- 在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
int main()
{
int a = 10;
int& ra = a;
cout<<"&a = "<<&a<<endl;
cout<<"&ra = "<<&ra<<endl;
return 0;
}
- 在底层实现上实际是有空间的,因为引用是按照指针方式来实现的
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
我们来看下引用和指针的汇编代码对比: 引用和指针的不同点::
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
感谢大家支持,有错请指出!!!
|