【C++笔记】引用和const引用以及inline和nullptr

embedded/2024/10/15 19:12:24/

【C++笔记】引用和const引用以及inline和nullptr

在这里插入图片描述

🔥个人主页大白的编程日记

🔥专栏C++笔记


文章目录

  • 【C++笔记】引用和const引用以及inline和nullptr
    • 前言
    • 一.引用
      • 1.1引用的概念和定义
      • 1.2引用的特性
      • 1.3引用的使用
    • 二. const引用
    • 三.指针和引用的关系
    • 四.inline
    • 五.nullptr
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了命名空间。这期我们来讲引用。话不多说,咱们进入正题!向大厂冲锋!

一.引用

1.1引用的概念和定义

引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间, 它和它引用的变量共用同⼀块内存空间。比如:水壶传中李逵,宋江叫"铁牛",江湖上⼈称"黑旋风";林冲,外号豹子头;

C++中为了避免引⼊太多的运算符,会复⽤C语言的⼀些符号,比如前⾯的<<和>>,这⾥引用也和取地址使用了同⼀个符号&,大家注意使用方法⻆度区分就可以。

在这里插入图片描述

  • 引用的使用
    类型& 引用别名=引用对象;
int main()
{int a = 0;// 引⽤:b和c是a的别名int& b = a;int& c = a;// 也可以给别名b取别名,d相当于还是a的别名int& d = b;++d;// 这⾥取地址我们看到是⼀样的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}




注意引用并没有开空间。引用只是给对象取别名。所以d++,a,b,c也都++。他们的地址也是一样的。

1.2引用的特性

  • 初始化
    引用在定义时必须初始化。
  • 引用个数
    ⼀个变量可以有多个引用。这里我们用b给a取别名。再给用c给b取别名。其实也是给a取别名。
int main()
{int a = 0;// 引⽤:b和c是a的别名int& b=a;int& c = a;// 也可以给别名b取别名,d相当于还是a的别名int& d = b;
}
  • 唯一对象
    引用⼀旦引用⼀个实体,再不能引用其他实体。
int main()
{int a = 10;// 编译报错:“ra” :必须初始化引⽤//int& ra;int& b = a;int c = 20;b = c;// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,// 这⾥是⼀个赋值cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}

1.3引用的使用

引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象

  • 改变对象
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
int main()
{int a = 10;int b = 20;Swap(&a, &b);return 0;
}

以前我们交换变量只能传地址。因为形参是拷贝,形参的改变不影响实参。

void Swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}

现在我们就可以这样写。因为引用就是引用对象本身。只是取别名而已。
引⽤传参跟指针传参功能是类似的,也不用&和*,引用传参相对更方便⼀些。

  • 引用传参
int Add(int& x, int& y)
{int ret=x + y;return ret;//拷贝生成临时对象,再返回临时对象
}

这是传值返回。他拷贝生成一份临时对象,再将临时对象返回。

int& Add(int& x, int& y)
{int ret=x + y;return ret;//传引用返回,直接返回对象的别名
}

这是传引用返回,直接返回ret的别名。这样就能减少传值返回的拷贝。提高效率。
但是这样写会产生野引用的问题(类似野指针)。因为ret是局部变量。函数结束后就会销毁。这时我们再通过别名去访问ret就会产生野引用。

  • 引用和指针
    引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很⼤的区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。

  • 教材使用
    ⼀些主要用C代码实现版本数据结构教材中,使⽤C++引用替代指针传参,目的是简化程序,避开复杂的指针,但是很多同学没学过引用,导致⼀头雾水。

typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, * PNode;
// 指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
// 这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序
void ListPushBack(LTNode** phead, int x);
void ListPushBack(LTNode*& phead, int x);
void ListPushBack(PNode& phead, int x)
{PNode newnode = (PNode)malloc(sizeof(LTNode));newnode->val = x;newnode->next = NULL;if (phead == NULL){phead = newnode;}else{}
}
int main()
{PNode plist = NULL;ListPushBack(plist, 1);return 0;
}

二. const引用

  • 权限放大
    可以引用⼀个const对象,但是必须用const引用。。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。

权限变化有三种情况:权限放大 权限平移 权限缩小。
在这里插入图片描述

int main()
{const int a = 0;const int& b = a;//权限平移
}


a只读不写,b也只读不写。

int main()
{const int a = 0;int& b = a;//权限放大
}


a只读只写,b取别名后却能修改a,这是不合理的。

int main()
{int a = 0;const int& b = a;//权限缩小
}

a可读可写,b取别名后只读取a不修改,这是合理的。

int main()
{const int a = 10;int& b = a;//b取别名可读可写 权限放大int c = b;//b拷贝给c c可读可写并不是a可读可写。
}

但是大家注意这段代码c是否涉及权限放大呢?

没有因为这只是将a拷贝给c。c可读可写并没有影响a。a依然还是可读不可写。

  • 临时对象
    所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
    一般临时对象出现在返回值,表达式的结果,类型转化等场景。
    需要注意的是临时对象具有常性。不能修改。
int main()
{int a = 10;const int& ra = 30;// 编译报错: “初始化” :⽆法从“int”转换为“int& ”int& rb = a * 3;double d = 12.34;// 编译报错:“初始化” :⽆法从“double”转换为“int& ”int& rd = d;return 0;
}


这段代码为什么报错呢?

因为他们都生成了临时对象。临时对象具有常性。可读不可写。
所以要使用const引用。

int main()
{int a = 10;const int& ra = 30;const int& rb = a * 3;double d = 12.34;const int& rd = d;return 0;
}


那临时对象销毁后。这时的引用不就是野引用了吗。

不会,引用临时对象后。临时对象的生命周期和引用一样。

  • 使用场景
    那const引用有什么用呢?
void f(int x)
{}

例如我们写一个函数f。这样写就会增加拷贝。所以我们会用引用做参数。

void f(int& x)
{}
int main()
{ int a = 2;int b = 1;double d = 1.5;f(3);//常量不可改变f(a + b);//临时对象 常性f(1.5); // 临时对象 常性return 0;
}


但是如果我们不修改x的话。这样写常量,表达式来传参都传不过去。因为他们具有常性。

但是如果我们把const加上就可以传参。这样我们的传参范围更宽泛。这就是const引用的价值。

三.指针和引用的关系

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。

  • 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
  • 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
  • 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
  • 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
  • sizeof中含义不同,引用结果为引⽤类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
  • 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。

但是底层上指针和引用都没有区别。转到汇编指令都是一样的。没有引用的概念。

四.inline

  • 内联的概念
    用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,因为建立栈帧还是有消耗的。这样就可以提高效率。
    所以inline其实是用来提效的。

其实就相当于在调用的地方替换在函数内部展开。相当于宏的作用。

inline int Add(int x, int y)//内联函数
{int ret = x + y;ret += 1;ret += 1;ret += 1;return ret;
}
int main()
{//可以通过汇编观察程序是否展开// 有call Add语句就是没有展开,没有就是展开了int ret = Add(1, 2);cout << Add(1, 2) * 5 << endl;return 0;
}

可是我们Add不是内联函数了吗。怎么还有call。还是展开了?

  • Debug展开
    vs编译器debug版本下⾯默认是不展开inline的,这样方便调试。
    debug版本想展开需要设置⼀下以下两个地方。 在这里插入图片描述

  • 编译器决定
    inline对于编译器而言只是⼀个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。

也就是内联对编译器只是建议。如果建议合理他就听,不合理就不听。因为无条件展开也是会付出代价的。

不合理的展开,如大段代码的函数展开就会导致可执行程序变大。

  • 宏和内联
    C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。

    所以为了解决宏的问题。同时方便调试。设计出了内联。
  • 声明和定义
    inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。

    所以内联函数声明和定义都放在.h文件即可。

五.nullptr

NULL实际是⼀个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif

C++中NULL被定义为字面量0,0编译器默认时int类型。
C语言中NULL被定义为void*指针类型。
所以在某些场景下会出现歧义。

void f(int x)
{cout << "f(int x)" << endl;
}
void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}
int main()
{f(0);f(NULL);// 本想通过f(NULL)调⽤指针版本的f(int*)函数,// 但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。return 0;
}


这里f(NULL)本想调用第二个函数。却调用到第一个函数。因为C++NULL定义为字面两0,默认属于int整型。
那是不是把NULL改为void就可以了呢?

也不行因为C++中void
不能转为int*。这样两个类型都不匹配。
但是C语言检查不严格。void*可以转为任意类型的指针。
在这里插入图片描述
在这里插入图片描述

为了解决这个问题C++涉及了nullptr.

  • nullptr
    C++11中引入nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。

后言

这就是C++的引用和一些语法。大家多加学习掌握。今天就分享到这,感谢大家的耐心垂阅!咱们下期见!拜拜~

在这里插入图片描述


http://www.ppmy.cn/embedded/98695.html

相关文章

vue项目配置基础路由vue-router

1、运行以下命令安装vue-router yarn add vue-router 2、在src目录下的components中新建两个vue页面 3、在src目录下新建router文件夹&#xff0c;在router文件夹下面新建index.js文件 4、配置main.js文件 //引入Vue import Vue from "vue"; //引入App import App…

EmguCV学习笔记 VB.Net 4.4 图像形态学

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 教程VB.net版本请访问&#xff1a;EmguCV学习笔记 VB.Net 目录-CSDN博客 教程C#版本请访问&#xff1a;EmguCV学习笔记 C# 目录-CSD…

数学建模算法总结

数学建模常见算法总结 评价决策类模型 层次分析法 层次分析法根据问题的性质和要达到的总目的&#xff0c;将问题分解为不同的组成因素&#xff0c;并按照因素间的相互关联影响以及隶属关系将因素按不同层次聚集组合&#xff0c;形成一个多层次的分析结构模型&#xff0c;从…

SpringBoot整合MongoDB

目录 什么是MongoDB&#xff1f;Spring Boot整合MongoDB使用MongoDB1.新增文档2.修改文档3.删除文档4.查询文档5.创建索引 什么是MongoDB&#xff1f; MongoDB是一种开源的 分布式文档型数据库管理系统 &#xff0c;它使用类似于JSON的BSON格式&#xff08;Binary JSON&#x…

C++ 设计模式——策略模式

策略模式 策略模式主要组成部分例一&#xff1a;逐步重构并引入策略模式第一步&#xff1a;初始实现第二步&#xff1a;提取共性并实现策略接口第三步&#xff1a;实现具体策略类第四步&#xff1a;实现上下文类策略模式 UML 图策略模式的 UML 图解析 例二&#xff1a;逐步重构…

【区块链+金融服务】“吉惠通”一站式金融综合服务平台 | FISCO BCOS应用案例

释放数据要素价值&#xff0c;FISCO BCOS 2024 应用案例征集 核心企业拥有良好的企业信誉&#xff0c;但有时会由于无可传递的信任背书&#xff0c;而无法为其他环节供应商提供融资支持&#xff0c;无 法促进产业链条良性发展&#xff1b;金融机构难以得到有议价空间的、弹性的…

Apache Flink细粒度资源管理原理

粗粒度资源管理 Apache Flink 1.1.4版本之前使用的是粗粒度的资源管理&#xff0c;即每个算子Slot Request所需要的资源都是未知的&#xff0c;Flink内部用UNKNOWN的特殊值来表示&#xff0c;这个值可以和任意资源规则的物理Slot匹配&#xff0c;站在Taskmanager的角度&#x…

MySQL键分区分区表

什么是键分区分区表&#xff1f; 键分区是一种MySQL数据库中的分区策略&#xff0c;它基于某个列的键值将数据分割成不同的分区。每个键值都会被映射到一个唯一的分区&#xff0c;这样可以确保数据在不同分区中均匀分布。键分区广泛应用于MySQL Cluster环境中&#xff0c;它可…