C++ auto 内联函数 指针空值

news2024/12/24 3:37:53

本博客基于 上一篇博客的 序章,主要对 C++ 当中对C语言的缺陷 做的优化处理。

上一篇博客:C++ 命名空间 输入输出 缺省参数 引用 函数重载_chihiro1122的博客-CSDN博客

auto关键字

 auto作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

 也就是说,auto可以作为类型来使用,他的意思就是,他会根据右边的 表达式自动的推出我们定义的这个变量的类型,如这个例子:

int main()
{
	int a = 10;
	auto b = a;

	auto c = 1 + 11.11;

	cout << typeid(b).name() << endl; // 打印b的类型  输出:int 
	cout << typeid(c).name() << endl; // 打印c的类型  输出:double

	return 0;
}

我们发现,auto自动的推导出了 变量的类型。

需要注意的是:auto 是必须在编译时期就要 推导出来的,也就是说,auto 类型的变量必须要初始化:

 如果在定义的时候不初始化,就会向上述代码一样报错。

我们上述的几个例子还不能体现出 auto 的真正用途,auto 主要用在 有一些类型很长的 变量 在定义的时候,很难书写,这时候,我们就可以用 auto 来自动推导出 变量的类型,从而减少麻烦:

#include<map>
#include<string>
#include<vector>

int main()
{
	int a = 0;
	int b = a;
	auto c = a; // 根据右边的表达式自动推导c的类型
	auto d = 1 + 1.11; // 根据右边的表达式自动推导d的类型
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

	vector<int> v;

	// 类型很长
	//vector<int>::iterator it = v.begin();
	// 等价于
	auto it = v.begin();

	std::map<std::string, std::string> dict;
	//std::map<std::string, std::string>::iterator dit = dict.begin();
	// 等价于
	auto dit = dict.begin();

	return 0;
}

auto不能同时推导

 我们在定义的时候,可能会这样写:

int i = 0, b = 0, c = 0;

这样一次定义多个相同类型的变量,但是auto不能这样写,假设我两个的变量 推导出来的类型是不相同的,那么就会报错:

 auto不能用来作为函数的参数

 这个想想也知道,auto是需要在程序编译时期就要进行推导的,那么都作为函数的参数了,如何进行推导呢?

如这个例子:

 直接报错了。

auto不能用来作为数组的类型

 同样,不能根据右边的推导出类型。

 auto作为指针类型和引用类型

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

	int a = 10;
	int* pa = &a;
	auto prev1 = pa;
	auto* prev2 = pa;

	cout << typeid(prev1).name() << endl; //int * __ptr64
	cout << typeid(prev2).name() << endl; //int * __ptr64

推导出来的类型是一样的。

	auto& ppa = a;
	cout << typeid(ppa).name() << endl;//int

	ppa++;
	cout << "a = " << a << endl;//a = 11

 auto* 只能推导指针类型:

int a = 10;
auto* b = 10;  // 报错
auto* b = &a;

以前的auto

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的
是一直没有人去使用它,大家可思考下为什么?
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是向上述一样是一个类型了。

为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

 auto在实际中最常见的优势用法就是C++11提供的新式for循环,还有lambda表达式等进行配合使用。

 以前我们如果要用for循环访问一个数组那么我们会这样去使用:

	int arr[] = { 1,2,3,4,5 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		printf("%d", arr[i]);
	}
	printf("\n");

我们用了新式的for循环之后,可以这样遍历数组:

for (auto e : arr)
	{
		cout << e << endl;
	}
	//1
	//2
	//3
	//4
	//5

既然我们可以直接用e来访问数组,那么我们是否可以用e来进行修改呢?

我们来看这个例子:

	// 修改数组
	for (auto e : arr)
	{
		e = 1;
	}
	for (auto e : arr)
	{
		cout << e << endl;
	}
	//1
	//2
	//3
	//4
	//5

我们发现是不行的,这是因为 e 只是一个变量,这个for循环代表的意思是,每一次从arr数组中依次取出一个数据,拷贝到e当中,那么我们去修改e的值,是不会改变 arr数组当中的值的。

 这时候,如果我们想通过e 来修改arr数组当中的内容,我们可以用一个 引用去接收这个数组当中的元素:

		// 修改数组
	for (auto& e : arr)
	{
		e = 1;
	}
	for (auto e : arr)
	{
		cout << e << endl;
	}
	//1
	//1
	//1
	//1
	//1

这样我们就修改成功了。

内联函数

解决调用函数需要创建函数栈帧的问题

 假设我们需要大量的调用某一个函数,那么此时就会创建很多次函数栈帧,就会有消耗,如下面这个例子:

int Add(int x, int y)
{
    return (x + y) * 10;
}

int main()
{
    for(int i = 0; i < 10000;i++)
    {
        cout << Add(i, i+1 ) << endl;
    }

    return 0;
}

我们调用了10000次 Add函数,大量的调用就会多次生辰函数栈帧。

要解决这样的问题,我们可以使用宏函数,这个概念是在C当中就有定义的,也就是说,我们可以在C的语法当中去实现它。

宏只是一种替换,不需要传参,也不需要有返回值。

 错误的宏函数定义:

#define Add(int x, int y) x + y   // 宏函数不是传参,而是替换,而且后面的 x + y 需要打括号
#define Add(x , y) (x + y)  // 后面的 (x + y) 里面的 x 和 y 都需要打括号
//  原因是  假设我写的是这个 ,那么就会有运算符优先级的问题:
    Add(a | b , a & b);  // 这样在宏替换进去的时候,就会发生问题
#define Add(x , y) ((x) + (y))   //错误写法,宏的定义后面不需要分号

宏函数最容易误解的点是,宏是替换,不是传参,他是在预处理的时候,会把其中的 x 和 y 换成我们在外部写入的表达式,所以我们要注意上述的几种错误定义宏函数的方式。

我们在外部调用宏函数的时候,和调用其他函数是一样的。

宏函数对比原本的函数调用的优点和缺点

 优点是:宏函数的调用不需要建立栈帧,他只是一个在预处理阶段替换的过程,他提高了调用的效率。

缺点是定义的时候,相对有点复杂,容易出错,而且会让代码的可读性变差。我们上述定义的宏函数只是一个非常简单的函数,如果我们定义一个实现算法相对复杂的宏函数,那么这个宏函数看着会非常的复杂。

而且,因为宏是在预处理的阶段要进行替换的,那么也就意味着,宏函数是不支持调试的。

 内联函数

 那么在C++当中就有一个关键字inline,被这个关键字修饰的函数,就可以解决上述的问题。

内联函数会在函数被调用的地方,进行展开。它的方式就像 我们在引头文件的时候,在编译的预处理阶段会把 头文件当中的内容,在引头文件的位置进行展开一样。他只是在调用函数的时候进行展开。

这样做,就不需要创建函数栈帧,而且函数的定义没有宏函数那么复杂,可读性也比宏函数要高,内联函数提升程序运行的效率

 例子:

inline int Add(int x, int y)
{
	return x + y;
}

int main()
{
	Add(1, 2);

	return 0;
}

但是,就算内联函数综合了 宏函数和普通函数的优缺点,但是也不是说 什么函数都适用于做内联函数。

内联函数和宏函数都只适用于 简单的频繁调用的函数,如果我们所定义的内联函数当中实现得很复杂,会出现一个很大问题,就是 代码膨胀。

 假设我们没有使用 内联来定义函数,我们在主函数中调用了10000次这个函数:

如上图,每一次都是 call  去调用 找到函数定义的地址,然后去调用Func()函数的代码,那么此时我们 代码总长度 就是 10000 + 50。

如果我们是使用内联函数去定义这个Func()函数的,那么就会在主函数中的10000个位置都 进行展开,那么总代码行数就是 : 10000 * 50。

那么像上述的内联展开,我们发现,主函数中的代码就已经很多了,那么在最后会生成可执行程序,因为代码行数变得很多,那么生成的可执行程序就会很大。

 如果我们写的是 一个升级程序,或者是一个安装包。如果像上面这样写,这个安装包或者是升级程序就会变得很大,这是我们不希望的。

 其实编译器也会自己判断,也就是说此处的inline 修饰只是一个建议,是建议编译器把这个函数作为内联函数使用,最终是否是inline 修饰内联函数,由编译器自己决定。

 比如,较长的函数,递归的函数这些都是编译器考虑不做内联的函数。

 inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到。

/ F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl
f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用

 如上述代码,他只是在头部声明了 这个内联函数,但是声明是没有地址的,编译器就只能在链接去找,但是编译器找不到地址,他就不能在 调用位置展开。

 如果我们想在 其他文件中写内联函数,在其他文件中调用的话,就不要把这个内联函数的声明和定义分离,也就是让这个函数的声明和定义写在一个文件当中。

// F.h

#include <iostream>
using namespace std;

inline void f(int i)
{
cout << i << endl;
}

// F.cpp
#include <F.h>
void text()
{
    f(10);
}

void text();

// main.cpp

int main()
{
    f(10);

    return 0;
}

我们在VS当中可以通过一些设置来对 内联函数进行调试

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

指针空值nullptr(C++11)

 我们一般在定义指针的时候,一般都要给他初始化的值,如果不知道给谁,一般给一个 NULL,但是,有的时候我们在C++当中使用NULL的时候会出一些问题:
 

void f(int)       //func1
{
	cout << "f(int)" << endl;
}
void f(int*)       //func2
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);       //f(int)      1
	f(NULL);    //f(int)       2
	f((int*)NULL); //f(int*)    3
	return 0;
}

如上述例子中的2,我们本来想调用 的是 func2 这个函数,但是却调用了 func1 这个函数,这个不是我们的本意,当时我们把 NULL的类型强转为 int* 之后,发现才调用的是func2 这个函数,说明是NULL有问题,NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:


 把NULL替换成0 了才会去调用func1 这个函数。

 在C++11当中对着错误进行了修改,但是如果直接修改上述的 宏定义,那么会导致以前的用户写的代码可能会出现问题,所以在C++11作为新关键字引入nullptr,既然是关键字,那么就不需要引头文件。

此处的sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同,也就是说,我们就可以把 nullptr 理解为  (void)*0   。

 如上述例子,我们来使用这个 nullptr ,发现调用的就是 func2 这个函数了。

int main()
{
    func(nullptr);  // f(int*)

    return 0;
}

我们发现上述我们实现 f  函数的时候,只写了形参的类型,没有写形参,我们发现还是编译通过了,在C++中 形参是不一定一定要接收的:

 

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

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

相关文章

uni-app使用时遇到的坑

一.uni-app开发规范 1.微信小程序request请求需要https 小程序端&#xff1a; 在本地运行时&#xff0c;可以使用http 但是预览或者上传时&#xff0c;使用http无法请求 APP端&#xff1a; 一般APP可以使用http访问 高版本的APP可能需要用https访问 二. uni-app项目 配置App升…

Java语言请求示例,电商商品详情接口,接口封装

Java具有大部分编程语言所共有的一些特征&#xff0c;被特意设计用于互联网的分布式环境。Java具有类似于C语言的形式和感觉&#xff0c;但它要比C语言更易于使用&#xff0c;而且在编程时彻底采用了一种以对象为导向的方式。 使用Java编写的应用程序&#xff0c;既可以在一台…

如何更好的进行数据管理?10 条建议给到你

这个时代数据量的快速增长和数据复杂性的大幅度提高&#xff0c;让企业迫切的寻找更加智能的方式管理数据&#xff0c;从而有效提高 IT 效率。 管理数据库不是单一的目标&#xff0c;而是多个目标并行&#xff0c;如数据存储优化、效率、性能、安全。只有管理好数据从创建到删除…

newman结合jenkins实现自动化测试

一、背景 为了更好的保障产品质量和提升工作效率&#xff0c;使用自动化技术来执行测试用例。 二、技术实现 三、工具安装 3.1 安装newman npm install -g newman查看newman版本安装是否成功&#xff0c;打开命令行&#xff0c;输入newman -v&#xff0c;出现 版本信息即安…

浅述 国产仪器 6362D光谱分析仪

6362D光谱分析仪&#xff08;简称&#xff1a;光谱仪&#xff09;是一款高分辨、大动态高速高性能光谱分析仪&#xff0c;适用于600&#xff5e;1700nm光谱范围的DWDM、光放大器等光系统测试&#xff1b; LED、FP-LD、DFB-LD、光收发器等光有源器件测试&#xff1b;光纤、光纤光…

C语言基础应用(五)循环结构

引言 如果要求123…100&#xff0c;你会怎么求解呢&#xff1f; 如果按照常规代码 int main() {int sum 0;sum 1;sum 2;sum 3;...sum 100;printf("The value of sum is %d\n",sum);return 0; }就会特别麻烦&#xff0c;并且代码过于冗长。下面将引入循环的概念…

硬件知识的基础学习

GPIO、继电器、三极管、PWM、MOS管 的 输入与输出。 本人没有系统的学习过专业的硬件知识&#xff0c;只有在实践过程中向前辈简单的学习&#xff0c;若有问题&#xff0c;还请大佬指正。 目录 一、GPIO 1.1 输入与输出的区别 1.2 输入 1.2.1 电流流向和电阻区分上拉输入…

动力节点老杜Vue笔记——Vue程序初体验

目录 一、Vue程序初体验 1.1 下载并安装vue.js 1.2 第一个Vue程序 1.3 Vue的data配置项 1.4 Vue的template配置项 一、Vue程序初体验 可以先不去了解Vue框架的发展历史、Vue框架有什么特点、Vue是谁开发的&#xff0c;对我们编写Vue程序起不到太大的作用&#xff0c;…

计算机网络 实验六

⭐计网实验专栏&#xff0c;欢迎订阅与关注&#xff01; ★观前提示&#xff1a;本篇内容为计算机网络实验。内容可能会不符合每个人实验的要求&#xff0c;因此以下内容建议仅做思路参考。 一、实验目的 掌握以太网帧的格式及各字段的含义掌握IP包的组成格式及各字段的含义掌…

java中HashMap的使用

HashMap 键值对关系&#xff0c;值可以重复&#xff0c;可以实现多对一&#xff0c;可以查找重复元素 记录&#xff1a; 做算法遇到好多次了&#xff0c;就总结一下大概用法。 例如今天遇到的这个题&#xff1a; 寻找出现一次的数&#xff0c;那就使用哈希表来存储&#xf…

X射线吸收光谱知识点

1) 什么是XAS XAS是X-ray Absorbtion Spectra的缩写&#xff0c;全称为X射线吸收光谱。X射线透过样品后&#xff0c;其强度发生衰减且其衰减程度与材料结构、组成有关。这种研究透射强度I与入射X射线强度Io之间的关系&#xff0c;称为X射线吸收光谱;由于其透射光强与元素、原子…

express项目的创建

前言 前端开发者若要进行后端开发&#xff0c;大多都会选择node.js&#xff0c;在node生态下是有大量框架的&#xff0c;其中最受新手喜爱的便是老牌的express.js&#xff0c;接下来我们就从零创建一个express项目。 安装node 在这里&#xff1a;https://nodejs.org/dist/v16…

《Linux0.11源码解读》理解(一)

计算机启动时, 内存(RAM)没有任何东西, 自然也无法跑操作系统. 但是可以执行固化在ROM里面的BIOS程序. 在按下电源键的一刻. CPU的cs和ip寄存器硬件被设置为0xf000和0xfff0, 于是cs:ip也就指向0xffff0这个地址, 而这个地址正是指向了ROM的BIOS范围(这里是0xfe000~0xfffff, 20根…

2023 减少人工标注,获取大量数据的能力

关键词&#xff1a; 零样本泛化能力模型 半监督 减少人工标注成本&#xff1a; 1、CVPR 2023 | 单阶段半监督目标检测SOTA&#xff1a;ARSL https://zhuanlan.zhihu.com/p/620076458 2、CVPR 2023 | 标注500类&#xff0c;检测7000类&#xff01;清华大学等提出通用目标检测算…

Java版本工程行业管理系统源码-专业的工程管理软件-提供一站式服务

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示1…

java Excel清除表格条件格式规则

© Ptw-cwl 目录 文章目录 目录Excel清除表格条件格式规则1.开始 -> 条件格式2.条件格式 -> 清除规则3.管理规则也能删除 代码报java.lang.IllegalArgumentException: Specified CF index 43 is outside the allowable range (0..42)如何解决源码 Excel清除表格条件…

初识Spring框架

文章目录 IOC &#xff08;Inverse of Control 控制反转&#xff09;DI &#xff08;Dependency Injection 依赖注入&#xff09;入门案例 --- 组件、注册第一步、引入依赖第二步、编写配置文件 ---xml从官网查看 xml 格式 然后保存为模板 注册组件 ---在配置文件 xml 文件中写…

c/c++:visual studio的代码快捷键,VS设置自定义默认代码,使用快捷键

c&#xff1a; 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学会c的话&#xff0c; 我所知道的周边的会c的同学&#xff0c;可手握10多个offer&#xff0c;随心所欲&#xff0c;而找啥算法岗的&#xff0c;基本gg 提…

文心一言作画:有点东西但不多...

随着ChatGPT的持续火热 与AI领域有关的话题 是越来越热闹了 前几天百度发布 “文心一言” 自然也成了网友们 重点关注的对象 不过大家的目光主要还是集中在 文心一言的绘画功能上 在人工智能加成下出来的画面 一个比一个绝 成功颠覆 大家对绘画的认知 生意火爆的商铺…

ATTCK v12版本战术介绍——防御规避(三)

一、引言 在前几期文章中我们介绍了ATT&CK中侦察、资源开发、初始访问、执行、持久化、提权战术、防御规避部分理论知识及实战研究&#xff0c;本期我们为大家介绍ATT&CK 14项战术中防御规避战术技术第13-18种技术&#xff0c;后续会介绍防御规避其他子技术&#xff0…