【C++详解】C++入门(二)引用、内联函数、nullptr宏

文章目录

  • 一、引用
    • 引用的概念和定义
    • 引用的功能
    • 引用的特性
    • const引用
      • const用法回顾
      • 权限的放大缩小
      • const引用的功能
    • 指针和引用的关系
  • 二、内联函数
  • 三、nullptr
  • 补充
    • 结构体指针变量类型重定义

一、引用

引用的概念和定义

C++祖师爷为了优化在部分场景中使用指针会出现的效率较低和比较复杂的情况,引入了一个新概念——引用。
引用不是新定义一个变量,而是为已经存在的变量取别名,编译器不会为引用变量开辟空间,它和它引用的变量共用一块空间,通俗来说就是为一个变量取别名,虽然叫法不同,变量的不同的别名还是指代这个变量本身,也就是你的正式名字和你的外号都是你本身,引用用法如下:
类型& 引用别名 = 引用的对象;

#include <iostream>
using namespace std;
int main()
{int a = 0;//引用:b和c是a的别名int& b = a;int& c = a;//也可以为别名b取别名,d相当于还是a的别名int& d = b;//这里我们可以看到abcd的地址都是一样的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}

在这里插入图片描述

下面代码不是让d变成x的别名,只是赋值修改d指向的这块空间的值。

在这里插入图片描述

引用的功能

一、

其实引用在功能上和指针是有部分重叠的,至于怎么个重叠法呢,跟随小编的脚步一起来看看吧。
在C语言阶段我们想要实现一个交换变量值的函数时是需要传变量的地址的,这样形参的改变才会影响实参,现在我们了解了引用后其实就可以把它用上了,如果我们传引用调用函数,那么函数的形参名就是实参的别名,形参发生了什么变化实参也会跟着变。
补充:变量引用的生命周期一定和变量本身一样或者比变量本身小,比如下面x是a的引用,x出了swap这个函数后就销毁了,但是a还存在。

void swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}void swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}int main()
{int a = 10;int b = 20;swap(&a, &b);cout << a << " " << b << endl;swap(a, b);cout << a << " " << b << endl;return 0;
}

在这里插入图片描述

这里我们可以总结出引用的第一个功能:
引用做函数形参,修改形参影响实参

二、

在调用函数传参的时候当参数很大时,比如下面这个结构体变量A,如果传参数本身会把这个参数拷贝过去(因为形参是实参的临时拷贝),这样空间开销就太大了,所以我们通常会传它的指针,指针最大也就8个字节。我们想想,引用在这里依旧可以实现同样的功能,前面介绍到引用是不会为变量开辟空间的,所以这里也可以采用传引用的方式。

struct A
{int a[100];int b;
};void Func(struct A* a)
{ }void Func(struct A& aa)
{ }int main()
{struct A a;Func(&a);Func(a);
}

引用做函数形参,减少拷贝,提高效率

三、

引用不仅可以作为函数形参,还可作为函数返回值返回,在介绍引用做函数返回值有哪些功能之前,还请允许小编科普一些有关函数返回值的知识点。

在这里插入图片描述

上面小编实现了一个简单的传值返回,其中变量ret在出了Func这个函数作用域后就销毁了,所以我们可以确定x接受的返回值一定不会是ret本身。这里返回的其实是ret的一份临时拷贝变量,并且语法规定了这个临时变量是具有常性的,这里可以简单理解成它被const修饰过,并且语法规定就算返回值出了作用域还存在它返回的也是临时变量,因为编译器不会去识别返回值出作用域是否销毁。

有了上面的铺垫过后接下来小编就要介绍今天的主角了。

typedef struct SeqList
{int* arr;int size;int capacity;
}SL;void SLInit(SL& sl, int n = 4)
{sl.arr = (int*)malloc(n * sizeof(SL));sl.size = 0;sl.capacity = n;
}void SLPushBack(SL& sl, int x)
{sl.arr[sl.size] = x;sl.size++;
}//该函数表示返回顺序表第i个位置的数据
int& SLAt(SL& sl, int i)
{//断言检查i是否越界assert(i < sl.size);return sl.arr[i];
}int main()
{SL s;SLInit(s);SLPushBack(s, 1);SLPushBack(s, 2);SLPushBack(s, 3);SLPushBack(s, 4);for (int i = 0; i < s.size; i++){SLAt(s, i) += 1;}for (int i = 0; i < s.size; i++){cout << SLAt(s, i) << endl;}return 0;
}

上面是小编实现的一个简单顺序表,我们可以看到顺序表尾插了四个数字1234,SLAt函数实现的是返回顺序表第i个位置的数据,如果我们想要在主函数中修改该函数的返回值该怎么做呢?读者朋友应该可以发现SLAt的返回类型是int&,这不就是才介绍的引用吗?没错,这就是传引用返回。

在这里插入图片描述

上图我们可以直观看到传值返回和传引用返回的区别,传值返回产生了临时变量,所以修改临时变量不会改变返回值,直观来讲就是顺序表的值不会发生改变(并且这种操作会编译报错,因为临时变量具有常性,具有常性的变量无法被修改),但是传引用返回就不一样了,它返回的是返回值的别名,所以不会产生临时变量,编译就不会报错,我们可以简单理解别名就是它本身,所以修改返回值的别名就是修改它自己,所以我们可以看到顺序表的值确实被修改了。

注意:传引用返回只适用于返回对象出了函数作用域还存在的情况,例如上面这个例子返回值对象是在堆上申请的。

在这里插入图片描述

这里我们可以类比引用的前两个功能,总结出另外两个功能:
引用做函数返回类型,修改返回对象
引用做函数返回类型,减少拷贝,提高效率

引用的特性

  • 引用在定义时必须初始化,变量和指针都可以先定义后赋值。
	//变量可以先初始化再赋值int x;x = 10;//引用不能先初始化再赋值int& r;r = x;//错误用法
  • 一个变量可以有多个引用
	//变量x可以有多个引用int x = 10;int& a = x;int& b = x;int& c = x;
  • 引用一但引用一个实体,就不能引用其他实体。
    在这里插入图片描述

就是平时所说的引用不能改变指向。(可以形象理解引用是非常忠贞的,一个别名只能对应一个对象)
所以引用是不能完全替代指针的,有些情况比如数据结构中的链式结构或者树形结构我们有修改指向的需求,所以这类情况只能使用指针,指针可以修改指向。

const引用

const用法回顾

在介绍const引用之前,我们先回顾一下C语言阶段我们掌握的有关const的用法:

  • const修饰变量
  • const修饰的变量无法直接修改,但是可以通过它的指针绕过他,使用它的地址修改,虽然这样做打破了语法的规则。
	//const修饰变量const int n = 10;n = 20;     //无法实现int* pn = &n;*pn = 20;   //可以实现,但是破坏了语法规则
  • const修饰指针变量

1、const在*左边,修饰指针指向的对象,保证指针指向的对象本身无法通过指针修改,但是指针变量本身可变。

	//const在*左边,修饰指针指向的内容,const int* p = &n;*p = m;  //无法实现p = &m;  //可以实现

2、const在*右边,修饰指针变量本身,保证指针变量本身无法被修改,但是指针指向的对象可以通过指针改变。

	//const在*左边,修饰指针变量本身int* const p = &n;*p = m;  //可以实现p = &m;  //无法实现

权限的放大缩小

	const int a = 10;//权限不能放大int& r1 = a;//权限可以平移const int& r2 = a;int b = 20;//权限可以缩小const int& r3 = b;

上面我们定义了一个const修饰的int类型变量a,它具有常性无法被修改。下面我们用没被const修饰的r1做a的别名,编译器会报错,因为a本身不能被修改,r1为a的别名却没有任何限制,可以随意修改,这里就会涉及到权限的放大。但是权限是可以平移和缩小的,下面两种引用编译器都不会报错。
注意:指针和引用才会涉及权限的放大和缩小,因为指针和引用对象的改变会影响原对象。

const引用的功能

	int a = 10;double d = 1.1;//以下两种情况都是引用的临时对象const int& r1 = d;       //1const int& r2 = a * 10;  //2

我们先看第一个引用,double类型的d被const修饰的int类型的r1引用,这里会发生隐式类型转换(C语言规定相关类型可以进行隐式转换),这里C++语法还规定类型转换会产生临时对象,这里r1引用的其实是那个临时对象,临时对象是是编译器需要一个空间来暂存表达式的求值结果或者隐式转化时产生的中间对象时临时创建的一个未命名对象,它的特点是具有常性,不能修改。
所以这里r1需要加const修饰,防止权限的放大。

在这里插入图片描述

有了上面临时对象的概念后第二个引用为什么要加const就很好理解了,直接上图。

在这里插入图片描述

总结:C++引入const引用的目的就是方便传参,后续函数形参部分若用const引用来接受,即可适配大部分传参的情况。

指针和引用的关系

在C++中,引用和指针功能有重叠性,它们各自有各自的特点,在实践中相辅相成,互相不可替代,所以C++从某种程度上会比C语言更便捷,能实现更多功能。
1、指针存储一个变量地址需要开空间,引用是为一个变量取别名不用开空间。
2、引用在定义时必须初始化,指针可以先定义再初始化。
3、指针可以更改指向的对象,引用一旦引用一个实体后就不能再引用其他对象。
4、引用可以直接访问指向对象,而指针必须解引用后访问指向对象。
5、sizeof中含义不同,引用结果为引用类型大小,而指针始终是地址空间所占字节数(32位为4字节,64位为8字节)。
6、指针容易出现空指针和野指针的问题,引用很少出现,所以引用更安全一些。
7、在指令汇编角度,引用也是用指针实现的,但是一般我们都以语法层面为准。

二、内联函数

1、我们在C语言阶段学习的宏函数会在预处理时替换展开,但是宏函数是很容易出错的,比如要注意运算符优先级,并且不能调试,但是函数会自动处理运算符优先级所以C++设计内联函数就是为了替代C语言的宏函数。
2、内联函数的关键字是inline,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不会创建栈帧了,提高了时间效率(创建栈帧消耗时间),这里我给大家演示一下普通函数和内联函数在汇编指令层面的区别:

inline int Add(int x, int y)
{int ret = x + y;return ret;
}
int main()
{int ret = Add(1, 2);cout << ret << endl;return 0;
}

不是内联:
在这里插入图片描述
是内联:
在这里插入图片描述

3、既然内联函数有这么多优点,那么我们就可以无脑用它了吗?肯定不行,因为内联函数调用过多会造成程序变大,会占用很多空间,比如内联函数转化为指令有50行,调用10000次就是500000行,我们不内联的话函数就一直待在一块公共空间,每一次调用的时候会call指令跳转到函数所在位置,那么就是程序指令一共就是10000+50行。世界上没有什么是完美的,内联函数可以简单理解成是用空间换时间
4、inline对于编译器而言只是一个建议,也就是说就算你加了inline编译器也可以在调用的地方不展开,比如说递归函数或者代码相对多一点的函数加了inline也会被编译器忽略,所以短小并且需要频繁调用的函数适合用内联

5、内联函数不能声明和定义分离,因为内联函数会直接展开,所以在编译阶段不会进符号表,那么在另一个文件调用内联函数时,因为文件里只有内联函数的声明,(声明只有无效地址,只有在函数定义才会分配确切的地址)所以在编译阶段找不到函数的地址,那么编译器只有寄希望于链接的时候合并符号表时得到函数的地址,但是内联函数不会进符号表,所以编译器就会报链接错误。
所以内联函数的定义和声明只能在一起,简单理解就是内联函数只能在有它的定义的文件里展开,若内联函数定义在头文件里,其他文件包了这个头文件,调用这个内联函数就可以展开。

6、普通函数必须声明和定义分离,如果普通函数定义在头文件里,那么如果有两个文件都包含了这个头文件,最后两个文件编译后生成的目标文件都有这个函数的确切地址,后面链接这两个目标文件时两个文件的符号表都有这个函数的确切地址,就会链接报错。

与前面知识的联系:前面我们知道有static关键字,它修饰全局变量和函数后使得它们之只能在当前源文件里使用,之前我们的解释是static让它的外部链接属性变成了内部链接属性,其实本质就是让他不进符号表,和inline类似。

三、nullptr

NULL实际上是一个宏,在传统C头文件中,可以看到如下代码:

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

C++中NULL被定义为字面常量0,C中被定义为无类型指针(void*)的常量,不论采用何种定义,在使用空值的指针时,都不可避免会遇到一些麻烦,比如下面:

在这里插入图片描述

本来想通过f(NULL)调用指针版本的Func(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序初衷相悖。
所以C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。

补充

结构体指针变量类型重定义

下面代码上面的写法等价于下面

typedef struct ListNode
{int val;struct ListNode* next;
}ListNode, *pnode;typedef struct ListNode ListNode;
typedef struct ListNode* *pnode;

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的赞和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/77525.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

毕业设计-基于深度学习的实时网络入侵检测系统

项目技术说明 深度学习实时网络入侵检测系统是一种利用深度学习技术对网络流量进行实时分析&#xff0c;以识别和阻止潜在网络攻击的安全解决方案。相比传统基于规则的入侵检测系统(IDS)&#xff0c;这种系统能够通过学习网络流量的正常模式和异常模式&#xff0c;更有效地检测…

中药企业数字化转型:从传统制造到智能制药的跨越

在当今数字化浪潮下&#xff0c;中药企业正积极拥抱变革&#xff0c;努力实现从传统制造向智能制药的跨越&#xff0c;以适应市场竞争和满足人们对中药质量与效率的更高要求。 在原料管理环节&#xff0c;企业通过采用物联网技术&#xff0c;对中药材种植、采集过程进行全程监…

Vue 2 的响应式 API 和 Vue 3 的组合式 API 的详细对比,从核心机制、使用方式、代码示例及优缺点展开

以下是 Vue 2 的响应式 API 和 Vue 3 的组合式 API 的详细对比&#xff0c;从核心机制、使用方式、代码示例及优缺点展开&#xff1a; 1. Vue 2 的响应式 API 核心机制 基于 Object.defineProperty&#xff1a; 通过劫持对象的 getter 和 setter 实现数据变化追踪。限制&…

“八股训练营”学习总结

在参加为期 40 天的八股训练营的这段时间里&#xff0c;我收获满满&#xff0c;不仅在知识技能上得到了提升&#xff0c;更在学习习惯和自我认知方面有了很大的进步。 在知识层面&#xff0c;训练营涵盖了网络、数据库、缓存以及python测试开发等多方面的知识点。 网络方面&a…

Python对比两张CAD图并标记差异的解决方案

以下是使用Python对比两张CAD图并标记差异的解决方案&#xff0c;结合图像处理和CAD结构分析&#xff1a; 一、环境准备与库选择 图像处理库&#xff1a;使用OpenCV进行图像差异检测、颜色空间转换和轮廓分析。CAD解析库&#xff1a;若为DXF格式&#xff0c;使用ezdxf解析实体…

记录学习记录学习《手动学习深度学习》这本书的笔记(九)

马不停蹄地来到了第十二章&#xff1a;计算性能…… 感觉应该是讲并行计算方面的&#xff0c;比如GPU、CPU、CUDA那些。 第十二章&#xff1a;计算性能 12.1 编译器和解释器 这里先提出了命令式编程和符号式编程的概念。 命令式编程VS符号式编程 目前为止&#xff0c;本书…

模板引擎语法-过滤器

模板引擎语法-过滤器 文章目录 模板引擎语法-过滤器[toc]1.default过滤器2.default_if_none过滤器3.length过滤器4.addslashes过滤器5.capfirst过滤器6.cut过滤器7.date过滤器8.dictsort过滤器 1.default过滤器 default过滤器用于设置默认值。default过滤器对于变量的作用&…

make学习三:书写规则

系列文章目录 Make学习一&#xff1a;make初探 Make学习二&#xff1a;makefile组成要素 文章目录 系列文章目录前言默认目标规则语法order-only prerequisites文件名中的通配符伪目标 Phony Targets没有 Prerequisites 和 recipe内建特殊目标名一个目标多条规则或多个目标共…

网络安全技能大赛B模块赛题解析Server12环境

已知靶机存在⽹站系统&#xff0c;使⽤Nmap⼯具扫描靶机端⼝&#xff0c;并将⽹站服务的端⼝号作为Flag &#xff08;形式&#xff1a;Flag字符串&#xff09;值提交 使用nmap扫描目标靶机网站服务的端口号为8089 Falg&#xff1a;8089 访问⽹站/admin/pinglun.asp⻚⾯&#…

1、Linux操作系统下,ubuntu22.04版本切换中英文界面

切换中英文界面的方法很多&#xff0c;我也是按照一个能用的方法弄过来并且记录&#xff0c; 1.如果刚开始使用Ubuntu环境&#xff0c;桌面的语言环境为英文&#xff0c;需要安装中文简体的字体包 打开桌面终端&#xff0c;输入 sudo apt install language-pack-zh-hans lan…

SmolVLM2: The Smollest Video Model Ever(六)

继续微调 微调视频的代码如下&#xff1a; # 此Python文件用于对SmolVLM2进行视频字幕任务的微调 # 导入所需的库 import os os.environ["CUDA_VISIBLE_DEVICES"] "1" import torch from peft import LoraConfig, prepare_model_for_kbit_training, get…

Spring Boot安装指南

&#x1f516; Spring Boot安装指南 &#x1f331; Spring Boot支持两种使用方式&#xff1a; 1️⃣ 可作为常规Java开发工具使用 2️⃣ 可作为命令行工具安装 ⚠️ 安装前提&#xff1a; &#x1f4cc; 系统需安装 Java SDK 17 或更高版本 &#x1f50d; 建议先运行检查命令…

数据结构(七)---链式栈

#### 链式栈实现 ##### linkstack.h #ifndef _LINKSTACK_H #define _LINKSTACK_H // 引入相关的库文件 #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义元素类型的别名 typedef int DATA; //定义链式栈节点 typedef struct node { …

【Spring Boot】Maven中引入 springboot 相关依赖的方式

文章目录 Maven中引入 springboot 相关依赖的方式1. 不使用版本管理&#xff08;不推荐&#xff09;2、使用版本管理&#xff08;推荐&#xff09;2.1 继承 spring-boot-starter-parent2.2 使用 spring-boot-dependencies 自定义父工程2.3引入 spring-framework-bom Maven中引…

DataStreamAPI实践原理——快速上手

引入 通过编程模型&#xff0c;我们知道Flink的编程模型提供了多层级的抽象&#xff0c;越上层的API&#xff0c;其描述性和可阅读性越强&#xff0c;越下层API&#xff0c;其灵活度高、表达力越强&#xff0c;多数时候上层API能做到的事情&#xff0c;下层API也能做到&#x…

WPF 图片文本按钮 自定义按钮

效果 上面图片,下面文本 样式 <!-- 图片文本按钮样式 --> <Style x:Key="ImageTextButtonStyle" TargetType="Button"><Setter Property="Background" Value="Transparent"/><Setter Property="BorderTh…

驱动开发硬核特训 · Day 22(上篇): 电源管理体系完整梳理:I2C、Regulator、PMIC与Power-Domain框架

&#x1f4d8; 一、电源子系统总览 在现代Linux内核中&#xff0c;电源管理不仅是系统稳定性的保障&#xff0c;也是实现高效能与低功耗运行的核心机制。 系统中涉及电源管理的关键子系统包括&#xff1a; I2C子系统&#xff1a;硬件通信基础Regulator子系统&#xff1a;电源…

设计模式全解析:23种经典设计模式及其应用

创建型模式 1. 单例模式&#xff08;Singleton Pattern&#xff09; 核心思想&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点。适用场景&#xff1a;需要共享资源的场景&#xff0c;如配置管理、日志记录等。 public class Singleton {// 静态变量保存…

力扣热题100题解(c++)—矩阵

73.矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 int m matrix.size(); // 行数int n matrix[0].size(); // 列数bool firstRowZero false; // 标记第一行是否包含 0bool f…

本地部署DeepSeek-R1(Dify升级最新版本、新增插件功能、过滤推理思考过程)

下载最新版本Dify Dify1.0版本之前不支持插件功能&#xff0c;先升级DIfy 下载最新版本&#xff0c;目前1.0.1 Git地址&#xff1a;https://github.com/langgenius/dify/releases/tag/1.0.1 我这里下载到老版本同一个目录并解压 拷贝老数据 需先停用老版本Dify PS D:\D…