【C++】引用、内联函数、auto关键字、基于范围的for循环、指针空值nullptr

文章目录

  • 前言
  • 引用
    • 引用概念
    • 引用特性
    • 常引用
    • 使用场景
    • 传值、传引用效率对比
    • 引用和指针的区别
  • 内联函数
    • 概念
    • 特性
  • auto关键字
    • auto概念
    • auto的使用细则
    • auto不能推导类型的场景
  • 基于范围的for循环(C++11)
    • 范围for的语法形式
    • 范围for的使用条件
  • 指针空值nullptr的出现
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

C++是一门强大而灵活的编程语言,拥有许多特性和语法糖,让程序员能够更高效地编写代码。在本博客中,我们将探讨一些C++中常用的特性,包括引用、内联函数、auto关键字、基于范围的for循环以及指针空值nullptr。通过深入了解这些特性,我们可以写出更简洁、高效且易于维护的C++代码。


提示:以下是本篇文章正文内容,下面案例可供参考

引用

引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

这就好比一个人的名字,身份证上是你的名字,你可能也有乳名或者外号,但是这都代表的是你本人!!!

使用:类型& 引用变量名(对象名)= 引用实体;

看一段程序
在这里插入图片描述

你会发现,pa和a的地址是同一个地址,这也证明了变量和引用变量共享同一块内存空间

注意:引用类型必须和引用实体是同种类型的。扩展点:引用的本质是指针,但是语法上引用变量不会开辟空间

引用特性

  • 引用在定义的时候必须初始化
int& b;//这种写法编译器会报错
  • 一个变量可以有多个引用
int a=0;
int& ra=a;
int& rb=a;
int& rra=ra;
  • 引用一旦引用一个实体,再也不能引用其他实体
int a=0;
int& ra=a;
int x=0;
ra=x;

这里最后是赋值操作,并不是ra重新引用x变量

常引用

先看一段代码,思考一下为什么???

#include <iostream>
using namespace std;
int main()
{int a = 0;int& b = a;const int c = 0;int& d = c;return 0;
}

在这里插入图片描述
这里很明显上面代码没错,下面代码报错了。结论:这是因为在指针和引用中,权限只能缩小,不能放大!!!
在上面代码中,a变量是可读可写的,b也是可读可写的,这里没问题,但是下面c是只读的,d是可读可写的,这里就会出大问题,问题就在于,他们两个共用一块内存,变量c自身都是只读的,被你引用后你怎么能修改我呢??这就是权限只能缩小,不能放大的由来

还有一种情况就是类型不一致的情况

在这里插入图片描述
这里编译器也报错了,报错原因是无法用 “double” 类型的值初始化 “int &” 类型的引用(非常量限定) ,这里又是为什么呢?接下来我讲用画图板来给大家解释。
在这里插入图片描述
类型不同时,隐式类型转换和引用都会中间产生一个临时变量,而临时变量具有常性(相当于被const修饰),因此int&b的引用相当于权限的放大,这里加上const之后就没问题了

修改后,程序编译通过

在这里插入图片描述

注意:变量之间的赋值没有权限的的缩小和放大问题,只有引用和指针有,因为变量赋值不是共用一块内存空间

在这里插入图片描述
在这里插入图片描述

使用场景

  1. 作参数
#include <iostream>
using namespace std;
void swap(int& num1, int& num2)//引用做参数
{int tmp = num1;num1 = num2;num2 = tmp;
}
int main()
{int num1 = 10;int num2 = 20;swap(num1, num2);cout << num1 << " " << num2 << endl;return 0;
}

图解
在这里插入图片描述

  1. 作返回值
int& test()//引用做返回值
{static int n = 0;n++;return n;
}
int main()
{/*int num1 = 10;int num2 = 20;swap(num1, num2);cout << num1 << " " << num2 << endl;*/cout << test() << endl;cout << test() << endl;cout << test() << endl;return 0;
}

注意:如果函数返回时,出了函数作用域,如果返回对象还未还给操作系统,则可以使用引用返回,如果已经还给系统,则必须使用传值返回

总结:通常情况下,如果引用返回的是局部变量,那么很可能会出现潜在的问题,返回的变量中的值可能是随机值,所以一般情况下,引用返回的是全局变量或者被static修饰等,那么原因是什么??

图解

在这里插入图片描述
总的来说,传值返回发生了一次拷贝,多开辟了一次空间,而传引用返回仅仅是返回变量的别名,没有开辟新的空间!!

传值、传引用效率对比

传值与传引用效率对比示例:

在C++中,函数参数的传递方式对性能有一定影响。传值方式会将实参的值复制到形参,而传引用方式直接传递实参的地址。以下是一个简单的例子,展示了传值与传引用的效率对比:

#include <iostream>
#include <chrono>// 传值方式
void passByValue(int a, int b) {// 一些操作int result = a + b;
}// 传引用方式
void passByReference(const int& a, const int& b) {// 一些操作int result = a + b;
}int main() {// 测试传值方式auto startValue = std::chrono::high_resolution_clock::now();for (int i = 0; i < 1000000; ++i) {passByValue(42, 23);}auto endValue = std::chrono::high_resolution_clock::now();auto durationValue = std::chrono::duration_cast<std::chrono::microseconds>(endValue - startValue);std::cout << "传值方式耗时: " << durationValue.count() << " 微秒" << std::endl;// 测试传引用方式auto startReference = std::chrono::high_resolution_clock::now();for (int i = 0; i < 1000000; ++i) {passByReference(42, 23);}auto endReference = std::chrono::high_resolution_clock::now();auto durationReference = std::chrono::duration_cast<std::chrono::microseconds>(endReference - startReference);std::cout << "传引用方式耗时: " << durationReference.count() << " 微秒" << std::endl;return 0;
}

示例说明:

在上述例子中,通过std::chrono库计算了通过传值方式和传引用方式调用函数的耗时。通常情况下,传引用方式相对于传值方式可能具有更好的性能,因为它避免了值的复制操作。

注意事项:

  • 对于小型数据或内置类型,传值可能更为合适。在大型数据结构或自定义类型时,传引用可以减少复制开销。
  • 优化建议:在实际应用中,应该根据具体情况选择合适的传递方式,并根据实际性能需求进行优化。

引用和指针的区别

在语法概念上,引用就是一个别名,没有自己独立的空间,和其引用实体共用同一块空间。

int main()
{int a = 10;int& ra = a;cout<<"&a = "<<&a<<endl;cout<<"&ra = "<<&ra<<endl;return 0;
}

但是在底层实现上,引用实际是有空间的,因为引用是按照指针的方式来实现的。

我们来看下引用和指针的汇编代码对比

在这里插入图片描述
在这里插入图片描述

引用和指针的不同点:

1. 定义和声明:

  • 引用: 引用是变量的别名,必须在定义时初始化,并且初始化后不能再引用其他变量。
    int x = 10;
    int& ref = x;  // 引用的初始化
    
  • 指针: 指针是一个变量,存储另一个变量的地址,可以在任何时候指向其他变量。
    int x = 10;
    int* ptr = &x;  // 指针的初始化
    

2. 内存操作:

  • 引用: 引用在内部被视为被引用变量的别名,没有自己的内存地址。
  • 指针: 指针有自己的内存地址,存储其他变量的地址。

3. 空值(Null):

  • 引用: 不存在空引用的概念,必须在初始化时指向有效的对象。
  • 指针: 可以具有空指针值,即指向空地址(nullptr)。

4. 重指向:

  • 引用: 一旦引用被初始化,就无法更改其引用对象。
  • 指针: 可以通过重新分配来更改指针所指向的对象。
    int x = 10;
    int* ptr = &x;  // 指向 x
    int y = 20;
    ptr = &y;       // 指向 y
    

5. 访问方式:

  • 引用: 使用引用时,不需要使用解引用运算符*,直接使用引用变量即可。
  • 指针: 使用指针时,需要通过解引用运算符*来访问指针所指向的值。
    int x = 10;
    int& ref = x;    // 使用引用
    int* ptr = &x;   // 使用指针
    std::cout << ref << std::endl;  // 不需要解引用
    std::cout << *ptr << std::endl; // 需要解引用
    

6. 大小:

  • 引用: 引用在内存中通常不占用额外空间。
  • 指针: 指针在内存中占用一定的空间,通常是机器的字长。

示例说明:

#include <iostream>int main() {int x = 10;// 引用的使用int& ref = x;std::cout << "引用值:" << ref << std::endl;// 指针的使用int* ptr = &x;std::cout << "指针值:" << *ptr << std::endl;// 重指向int y = 20;ptr = &y;std::cout << "重指向后的指针值:" << *ptr << std::endl;return 0;
}

上述示例展示了引用和指针的基本用法以及它们之间的不同点。根据具体的场景和需求,选择引用或指针将有助于更清晰和有效地编写代码。

内联函数

概念

inline修饰的函数叫内联函数,编译时,C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升了程序运行的效率。
在这里插入图片描述
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化.

特性

  1. inline是一种以空间换时间的做法,省去调用函数的额外开销,所以代码很长或者有递归的函数不适宜使用作为内联函数。
  2. inline对于编译器来说只是一个建议,编译器会自动优化,如果定义为inline的函数体内有递归等等,编译器优化时会忽略内联。
  3. inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开了,就没有函数地址了,链接就会找不到。

宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?

  1. 常量定义 换用const
  2. 函数定义 换用内联函数

auto关键字

auto概念

早期C++语法中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但是可惜的是一直没有人去使用,大家可以去网上搜寻一下是为什么?
C++11中,赋予了auto全新的含义:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto申明的变量必须由编译器在编译时期推导而得

int TestAuto()
{return 10;
}
int main()
{int a = 10;auto b = a;auto c = 'a';auto d = TestAuto();cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化return 0;
}

上述代码中使用的auto关键字来推导接收到的值是什么类型,并使用typeid().name()来判断对象或者变量所属的类型是什么

注意:使用auto定义的变量必须对其初始化(这里和引用一样),在编译阶段编译器需要根据初始化表达式来推导auto的实际类型,因此auto并非是一种“类型的”声明,而是一个类型声明时的“占位符”,编译器在编译期间会将auto替换成变量实际的类型

auto的使用细则

  • auto把指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用时则必须加&

int a = 10;
auto b=&a;
auto* c=&a;
auto& d=a;
  • 在同一行,使用auto定义多个变

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际上只对第一个类型进行推导,然后用推导出来的类型定义其他变量

auto i=10,j=20;
auto x=10,y='1';// 该行代码会编译失败,因为x和y的初始化表达式类型不同

auto不能推导类型的场景

  • 在函数形参中不能使用
void test(auto a)
{}
  • auto不能直接用来声明数组
void test()
{auto arr[]={1,2,3,4,5,6,7,8,9,10}
}
  • auto在实际中最常见的用法就是跟以后C++11提供的新式for循环,还有lambda表达式等进行配合使用。

基于范围的for循环(C++11)

范围for的语法形式

对于一个有范围的集合来说,由程序员自己来说明循环的范围是非常多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号:分成两个部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

#include <iostream>
using namespace std;
void test()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };for (auto& e : arr){e*=2;}for (auto e : arr){cout << e << " " ;}
}
int main()
{test();return 0;
}

注意:和普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

范围for的使用条件

  • for循环迭代的范围必须是确定的

对于数组而已,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

以下代码就存在问题,因为for的范围不确定

void test(int arr[])
{for(auto&e : array){cout<<e<<" ";}
}

因为这里形参的数组,已经退化成指针,所有形参中的arr并不能获得数组的范围

  • 迭代的对象要实现++和==的操作。(这里牵涉到后期的知识,留给大家思考一下吧哈哈)

指针空值nullptr的出现

相信一个拥有良好编程习惯的同学,一定会在声明一个变量的时候给他初始化,否则可能就会出现使用未初始化的变量的错误,比如未初始化指针等等。在C语言中,如果指针没有一个合法的指向,我们基本都是按照下面的方式对其进行初始化的:
在这里插入图片描述
NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到以下代码:
在这里插入图片描述
这段代码是对NULL宏的定义,用于在C和C++中表示空指针的常量。

  1. 条件编译:

    • #ifndef NULL:如果NULL未定义(即未被之前的代码或头文件定义过)。
    • #ifdef __cplusplus:如果是C++环境。
  2. 宏定义:

    • #define NULL 0:在C++环境下,将NULL宏定义为整数0,表示空指针。
  3. 备选定义:

    • #else#define NULL ((void *)0):如果不在C++环境下,则将NULL宏定义为一个空指针,即(void *)0

这段代码的目的是确保在C和C++中都有一个合适的空指针表示。在C++中,空指针通常表示为整数0,而在C中,可以用(void *)0表示空指针。这种做法是为了保持对旧代码的兼容性,以及确保在不同编译环境下都能正确使用NULL

在这个宏定义中,可以看到,NULL可能被定义成字面常量0,或者在cpp中被定义为(void*)0。但是不管采取何种定义,在使用空值指针的时候,都不可避免的会遇到一些麻烦

void f(int)
{cout<<"f(int)"<<endl;
}
void f(int*)
{cout<<"f(int*)"<<endl;
}
int main()
{f(0);f(NULL);f((int*)NULL);return 0;
}

程序结果
在这里插入图片描述
程序本意是想把f(NULL)调用指针版本的 f(int)。但是由于NULL被定义为字面常量0,所以程序输出恰好相反。
在C++98中,字面常量0既可以是一个整型数字,也可以是无类型的指针 (void
)0 ,但是编译器默认情况下会将其看成是一个整型常量,如果要将其按照指针方式来使用,必须要对其进行强转(void*)0**

注意:建议在中统一使用nullptr代替NULL++

  • 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  • 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同
  • 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

总结

C++作为一门现代编程语言,提供了许多方便的语法糖和特性,使得程序员能够更好地应对各种编程场景。引用为我们提供了更直观的数据操作方式,内联函数优化了程序的执行效率,auto关键字简化了变量声明,基于范围的for循环使代码更具可读性,而指针空值nullptr则更安全地表示空指针。通过充分利用这些特性,我们能够在C++中编写出更加优雅和高效的代码,提升编程体验和代码质量。

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

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

相关文章

计算机网络—网络搭建NAT内外网映射

使用Windows Server 2003 网络拓扑 Router 外网&#xff1a;NAT IP 网段 192.168.17.0/24内网&#xff1a;仅主机模式 IP 172.16.29.4 Client1&#xff1a;仅主机模式 IP 172.16.29.2 网关 172.16.29.1 Client2&#xff1a;仅主机模式 IP 172.16.29.3 网关 172.16.29.1…

Vue3+Typescript+setup / Vue2使用scrollIntoView()实现锚点跳转指定列表

在标签上添加ref属性来引用DOM元素&#xff0c; Vue2中使用$refs来获取该DOM元素并调用scrollIntoView()方法。 使用ref"yearDiv"在每个年份的div元素上添加了一个引用。然后&#xff0c;在yearClick方法中&#xff0c;我们通过this.$refs.yearDiv[year]来获取对应…

使用Go语言采集1688网站数据对比商品价格

目录 引言 一、数据采集原理 二、数据采集流程 三、数据采集代码实现 四、数据分析与比较 五、注意事项 六、结论 引言 随着电子商务的快速发展&#xff0c;越来越多的消费者开始通过在线平台购买商品。在众多电商平台中&#xff0c;1688作为中国最大的批发交易平台&am…

CMake入门教程【核心篇】查找包(find_package)

&#x1f608;「CSDN主页」&#xff1a;传送门 &#x1f608;「Bilibil首页」&#xff1a;传送门 &#x1f608;「本文的内容」&#xff1a;CMake入门教程 &#x1f608;「动动你的小手」&#xff1a;点赞&#x1f44d;收藏⭐️评论&#x1f4dd; 文章目录 1.使用方法1.1基本用…

【python_将列表整合成文本】

python_将列表整合成文本 # -*- coding: utf-8 -*-data [[指令卡主, 2023-12-25, 经贸有限公司, 孙悟空], [使用了屏幕保护之后&#xff0c;元素找不到了, 2023-12-25, 科技有限公司, 许三多], [操作用友的时候&#xff0c;找不到元素, 2024-01-02, 食品科技有限公司, 小张],…

BUG汇总

20240103 通用&#xff0c;驼峰命名法&#xff0c;mybatis。 mybatis入门程序中&#xff0c; // 获取对象的顺序为&#xff1a;SqlSessionFactoryBuild-》SqlSessionFactory-》SqlSessionSqlSessionFactoryBuilder sqlSessionFactoryBuilder new SqlSessionFactoryBuilder();I…

js中的事件传递

事件传递分为两个阶段&#xff0c;一是 事件捕获&#xff0c;二是 事件冒泡。分别对应下图1~5和6-10&#xff0c;每次触发的事件从window开始向下传播&#xff0c;一直到叶子节点&#xff0c;再往回传播。每个节点都允许添加监听器&#xff0c;浏览器在事件传播过程中一旦遇到监…

【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 概述

1、 Hadoop 是什么 &#xff08;1&#xff09;Hadoop是一个由Apache基金会所开发的分布式系统基础架构 &#xff08;2&#xff09;主要解决海量数据的存储和海量数据的分析计算问题 &#xff08;3&#xff09;广义上来说&#xff0c;Hadoop通常是指一个更广泛的概念——Hadoop生…

keras人工智能框架 MNIST 数据集 随机展示

阅读本文之前&#xff0c;请先参考--------win10搭建keras深度学习框架 安装运行环境 使用Python绘图库Matplotlib随机输出mnist数据集的几个图片&#xff1a;代码见下图&#xff1a; 在sublimeText中 使用ctrlB运行代码&#xff0c;结果如下图&#xff1a;

c++ / day06

1. 利用模板类完成顺序表(两天时间&#xff0c;今天至少写出大致框架) 代码 //implement template in sqlist #include <iostream> #include <cstring>#define MAXSIZE 100using namespace std;template <typename T> class Sqlist {unsigned int len 0;T…

GaussDB数据库使用COPY命令导数

目录 一、前言 二、GaussDB数据库使用COPY命令导数语法 1、语法COPY FROM 2、语法COPY TO 3、特别说明及参数示意 三、GaussDB数据库使用COPY命令导数示例 1、操作步骤 2、准备工作&#xff08;示例&#xff09; 3、把一个表的数据拷贝到一个文件&#xff08;示例&…

kbdnecnt.DLL文件缺失,软件或游戏无法启动运行,怎样快速修复?

不少人都在问“kbdnecnt.DLL文件”是什么&#xff1f;为什么电脑总是报错提示说“kbdnecnt.DLL文件缺失&#xff0c;软件无法启动”&#xff1f; 首先&#xff0c;先来了解“kbdnecnt.DLL文件”是什么&#xff1f; kbdnecnt.DLL是Windows操作系统中的一个动态链接库文件&#…

Spark二、Spark技术栈之Spark Core

Spark Core spark核心&#xff1a;包括RDD、RDD算子、RDD的持久化/缓存、累加器和广播变量 学习链接&#xff1a;https://mp.weixin.qq.com/s/caCk3mM5iXy0FaXCLkDwYQ 一、 RDD 1.1 为什么要有RDD 在许多迭代式算法(比如机器学习、图算法等)和交互式数据挖掘中&#xff0c;…

STL——string详解

目录 &#x1f4a1;介绍 &#x1f4a1;string的基本操作 &#x1f4a1;string的构造函数 &#x1f4a1;string赋值操作 &#x1f4a1;string字符串拼接 &#x1f4a1;string的查找和替换 &#x1f4a1;string字符串比较 &#x1f4a1;string字符存取 &#x1f4a1;str…

alibabaCloud学习笔记01(小滴课堂)

微服务架构常见的核心组件 讲解业务微服务架构常见解决方案 讲解AlibabaCloud核心组件介绍 创建数据库。 建表&#xff1a; 添加数据&#xff1a; 再建个用户库&#xff1a; 建表&#xff1a; 插入数据&#xff1a; 创建订单库&#xff1a; 建表&#xff1a; 创建项目&#x…

大数据时代的WEB运维高级架构师,Web系统运维工程师的实战成长之路

一、教程描述 本套WEB架构师教程&#xff0c;大小30.61G&#xff0c;共有183个文件。 二、教程目录 01-Web架构之单机时代&#xff08;共7课时&#xff09; 02-Web架构之集群时代&#xff08;共9课时&#xff09; 03-Web架构之DNS&#xff08;共6课时&#xff09; 04-Web…

常见的共轭先验分布

经常会遇到后验分布不能求解的问题&#xff0c;对于这个问题可以应用共轭先验分布解决&#xff0c;这些先验分布具有比较好的特征&#xff0c;能够使得出的后验分布和先验分布具有相同的分布族。如果一个具有参数属于分布的先验分布&#xff0c;则生成的后验分布也属于相同的分…

【InnoDB数据存储结构】第2章节:InnoDB行格式

目录结构 之前整篇文章太长&#xff0c;阅读体验不好&#xff0c;将其拆分为几个子篇章。 本篇章讲解 InnoDB 行格式。 InnoDB 行格式 InnoDB 一行记录是如何存储的&#xff1f; 这个问题是本文的重点&#xff0c;也是面试中经常问到的问题&#xff0c;所以就引出了下文的 …

【flink番外篇】9、Flink Table API 支持的操作示例(14)- 时态表的join(java版本)

Flink 系列文章 一、Flink 专栏 Flink 专栏系统介绍某一知识点&#xff0c;并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分&#xff0c;比如术语、架构、编程模型、编程指南、基本的…

Unity 基于UDP实现本地时间与网络时间校验 防客户端修改日期作弊

新建一个Unity GameObject 挂上NTPComponent脚本 时间校验 源码 using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UnityEngine.Networking; using System.Text; using System.Net.Sockets; using System.Net; using Sys…