C++初识(三)--- 引用详解

C++初识(三)--- 引用详解,第1张

目录
  • 引用😇
  • 1.1、引用的概念😇
  • 1.2、引用的特性😈
  • 1.3、常量引用😇
  • 1.4、引用的使用场景😇
  • 1.5、传值和传引用的效率😇
  • 1.6、值和引用的作为返回值类型的性能😈
  • 1.7、引用和指针的区别😇

引用😇 1.1、引用的概念😇
  • 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
    比如:<<西游记>>中孙悟空有着“孙行者、心猿、金公、斗战胜佛”等别名…
  • 引用是一个复合类型,复合类型是基于其他类型的基础上定义的类型
  • 类型& 引用变量名(对象名) = 引用实体

注意:引用类型必须和引用实体是同种类型的


1.2、引用的特性😈
  • 引用必须再定义时初始化
  • 一个变量可以有多个引用(别名)
  • 引用一旦绑定一个实体,再不能绑定其他实体
void Test()
{
	int a = 10;
	// int& ra; // 该条语句编译时会出错
	int& ra = a; //<=== 引用必须初始化
	int& rra = a; //<=== 一个变量可以有多个引用

	int b = 20;
	ra = b; //<=== 这里是把b的值赋值给ra
}

通过内存地址来理解😇


1.3、常量引用😇
  • 引用可以绑定到被const修饰的对象(变量)上,这样的对象称之为“常量的引用"
  • 常量引用在初始化时允许任何表达式作为初始化的值
  • 引用的原则:对原引用的对象(变量),权限只能缩小或不变,但不能放大
    Ps:权限是指”对原引用对象(变量)的读写 *** 作“
void Test()
{
   const int a = 10;
   //<===权限被放大,原引用对象为"只读"
   //int& ra = a;

   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“修饰的对象一样,不可修改

  • 为什么引用要加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 
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 

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中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
  • 有多级指针,但是没有多级引用
  • 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  • 引用比指针使用起来相对更安全

感谢大家支持,有错请指出!!!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/914561.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-16
下一篇2022-05-16

发表评论

登录后才能评论

评论列表(0条)

    保存