IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++初识(三)--- 引用详解 -> 正文阅读

[C++知识库]C++初识(三)--- 引用详解

引用😇

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

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-13 11:37:20  更:2022-05-13 11:38:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:41:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码