C++入门:内联函数、auto关键字、基于范围for循环及指针空值nullptr

news2024/11/24 9:38:48

目录

一. 内联函数

1.1 内联函数的概念

1.2 内联函数的特性

1.3 内联函数和宏的优缺点对比

二. auto关键字(C++11)

2.1 auto的功能

2.2 auto在使用时的注意事项

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

四. 指针空值nullptr(C++11)


一. 内联函数

1.1 内联函数的概念

内联函数,就是使用inline关键字,让C++编译器在调用函数的位置处将函数在开展被调用的位置,从而减少函数栈帧创建和销毁的时间。

内联函数的声明方法:inline 返回值类型 函数名(参数列表)。下面的代码以add函数为例,演示了内联函数的定义和声明的方法。

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

int main()
{
	int x = 10, y = 10;
	int ret1 = add(x, y);
    int ret2 = add(x, y);
    return 0;
}
  • 在Debug模式下,内联函数默认不展开,但可以通过更改编译器设置,来让内联函数在Debug模式下也展开。
  • 在Release版本下,内联函数展开。

知识拓展:Debug被称为调试版本,编译器不会对程序进行优化,但可以调试找bug。Release版本被称为发布版本,编译器会对程序进行优化,但是程序员不可以在Release版本下调试。

设置在Debug版本下内联函数展开的方法:

  1. 打开属性设置,选择C/C++ -> 常规,将调试信息格式改为程序数据库。
  2. 选择C/C++ -> 优化,将内联函数扩展改为:只适用于_inline (Ob1)。
图1.1  更改设置使编译器在Debug版本下也展开内联函数

图1.2和1.3展示了使用内联函数的不使用内联函数时,调用add函数的汇编代码的区别。不使用内联函数时,调用add函数要先为通过call指令来跳转,建立函数栈帧后才会执行函数中的指令。使用inline时,汇编语言中不再有call指令,函数的指令直接展开在主函数中。

图1.2  不使用内联函数时的汇编代码
图1.3 使用内联函数时的汇编代码

1.2 内联函数的特性

内联函数是一种以空间换时间的方法

C++内联函数类似于宏,都是在使用的位置展开,从而减少函数栈帧创建和销毁的开销。假设,一个函数(func)编译完成后有10条汇编指令,调用这个函数1000次,使用内联和不使用内联的情况下,汇编指令的条数为:

  • 不使用内联函数:1000 + 10次,call func() 1000次 + 10条函数指令。
  • 使用内联函数:1000*10次,每次调用展开函数,每次调用都需要独立的10条指令。

inline对于编译器来说只是建议,展不展开最终由编译器决定

这一点和register寄存器关键字类似,register关键字的功能是建议将变量存储在寄存器,仅仅是建议,到底要不要将变量放在寄存器由编译器决定而不是register。

  • 对于比较长(指令较多)的函数,即使不进行展开,创建函数栈帧的开销相对于执行函数指令很小,编译器很可能就不展开。
  • 递归函数不适用于inline,因此,对于存在递归调用的函数,即使使用inline进行声明,编译器也不会展开函数。

inline声明和定义不能分离

由于inline会直接在调用函数的位置处展开,在编译阶段生成的符号表中不会存储函数的地址,因此,如果定义和声明分离,则会存在找不到函数的问题,这样会发生链接错误。下面的代码在头文件中使用inline声明sub函数,在func.c文件中定义函数,报错。

//head.h
#include<iostream>
using namespace std;
inline int sub(int x, int y);

//func.cpp
#include "head.h"
inline int sub(int x, int y)
{
	return x - y;
}

//test.cpp
#include "head.h"
int main()
{
	int x = 10, y = 10;
	int ret1 = sub(x, y);
	int ret2 = sub(x, y);
	return 0;
}
图1.4 代码报错信息

总结:内联函数与宏类似,适用于函数代码量少且频繁被调用的场景。

1.3 内联函数和宏的优缺点对比

内联函数和宏共有的优点:

  • 省去了函数栈帧的创建消耗,提高了代码的效率。

内联函数和宏共有的缺点:

  • 代码量变大

内联函数相对于宏的优点:

  • 可读性好,读内联函数与读普通函数无明显区别。
  • 宏本质上是替换,不可以调试,而内联函数在Debug模式下默认不展开,可以进行调试。
  • 宏没有类型检查,而内联函数有类型检查。

下面的代码就会报警告:从“double”转换到“int”,可能丢失数据

int add(int x, int y)
{
	return x + y;
}

int main()
{
	double x = 10, y = 10;
	int ret1 = add(x, y);
	int ret2 = add(x, y);
	return 0;
}

二. auto关键字(C++11)

2.1 auto的功能

在C++98和C++03的标准中,auto关键字的作用是使变量出了定义变量的作用域就自动销毁,但是,在默认情况下,变量都是具有auto属性的且出了定义变量的作用域就会自动销毁。因此,在早期的C++标准下,auto关键字没有任何实质性意义。

C++11标准中,auto被赋予了全新的功能,摒弃了C++98和C++11原来的作用。auto的新功能为:自动推断类型。

下面的代码中通过auto来声明变量类型,再通过typied().name来打印类型。typeid().name()获取类型时,经常会省去const。

int main()
{
	int a = 10;
	char c = 'a';
	
	auto a1 = a;
	auto a2 = c;
	auto a3 = 10;
	auto a4 = 'a';
	auto a5 = 12.345;

	//typeid().name() 能够自动识别变量(常量)类型并实现对变量类型的打印
	//但是,使用typeid获取类型很多时候会舍去const
	cout << typeid(a1).name() << endl;
	cout << typeid(a2).name() << endl;
	cout << typeid(a3).name() << endl;
	cout << typeid(a4).name() << endl;
	cout << typeid(a5).name() << endl;

	//auto类型数据在定义是就必须初始化,因为编译器要通过判断其被初始化的数据的类型来判断auto的类型
	//auto a6;  //编译不通过

	return 0;
}

如果定义const int a = 10,在使用auto b = a将a的值赋给b,这时b是可以被修改的,auto不会将a的const属性带给b。如果希望b不能被修改,则应当使用const auto b = a。

int main()
{
	const int a = 10;
	auto b = a;
	cout << b << endl;
	b = 30;
	cout << b << endl;

	//const auto b = a;  //这时b具有只读const属性
	//b = 40;   //报错

	return 0;
}

2.2 auto在使用时的注意事项

1、在使用auto声明变量是必须初始化

auto是根据变量被初始化的数据类型来推断变量类型的,如果不初始化,那么就无法确定auto是什么类型的数据,编译会报错。

int main()
{
	int a = 10;
	auto b;
	return 0;
}
图2.1  程序报错信息

2、使用auto在同一行声明多个变量时,类型必须相同

编译器只会对第一个变量的类型进行推导,用推导出来的类型定义后面的变量。

int main()
{
	auto a = 10, b = 20;  //编译通过
	auto c = 20, d = 12.23;   //编译报错
}

3、auto不能做为函数的类型

auto需要通过被初始化的数据来推断,而函数形参类型没有初始化,无法推断函数具体的参数类型。

void func(auto x)
{
	cout << "void func(auto x)" << endl;
}

int main()
{
	func(10);
	return 0;
}
图2.2  程序报错信息

4、auto不能直接用来声明数组

这里不需要纠结原因,明确不用auto声明数组就好。

int main()
{
	int a[] = { 1,2,3 };
	//auto a1[] = { 1,2,3 };  //编译报错
	return 0;
}

5、使用auto声明指针类型和引用类型 

在声明指针类型时,auto*和auto没有任何区别,但使用auto声明引用类型时,就必须写为auto&,&不能丢。

int main()
{
	int a = 10;
	auto pa1 = &a;   //auto获取指针类型
	auto* pa2 = &a;  //auto*获取指针类型

	auto& ra = a;   //使用auto定义a的引用

	cout << typeid(a).name() << endl;   //int
	cout << typeid(pa1).name() << endl;  //int *
	cout << typeid(pa2).name() << endl;  //int *
	cout << typeid(ra).name() << endl;   //int 

	*pa1 = 20;   //a = 20;
	*pa2 = 30;   //a = 30;
	ra = 40;

	cout << &a << endl;
	cout << &ra << endl; //&a和&ra相同

	return 0;
}
图2.3 程序运行结果

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

在之前使用C语言的时候,要打印数组中的每个元素,我们需要获取数组元素的个数,通过for循环来实现,就有了下面的代码。但是,普通for循环sizeof(arr)/sizeof(arr[0])使用起来相对复杂,有更简单的方法吗?当然有。

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

在C++11标准中,给出了基于范围的for循环语法,语法格式为:for(auto x : arr)其实现的功能为:将arr数组中的数据依次赋给x,直到数组的最后一个元素,每一个数据赋给x表示一层循环。下面这段代码使用基于范围的for循环,打印数组中的每个数据。

int main()
{
	int arr[] = { 1,2,3,4,5 };
	for (auto x : arr)
	{
		cout << x << " ";
	}
	cout << endl;
	return 0;
}

不需要程序员去计算数组中元素个数,这里编译器会自动处理。同时,有两点注意事项:

  1. auto x : arr中的x可以被替换为i、j等任意名称。
  2. auto可以被替换为int,但是,如果数组元素的类型发生变化,就需要更改int,因此最好直接声明为auto。

那么,如何通过基于范围的for循环修改数组中元素的值呢?这里就需要引用。

int main()
{
	int arr[] = { 1,2,3,4,5 };

	for (auto& a : arr)
	{
		a += 1;   //数组每个元素+1
	}

	for (auto x : arr)
	{
		cout << x << " ";
	}
	cout << endl;
	return 0;
}
图3.1 程序运行结果

看到这里,可能会有疑惑:引用在有了引用实体之后,就不能再引用其他实体,那么为什么for (auto& a : arr)不存在改变引用实体的问题呢?答:每一层for循环结束后,变量a都会被销毁,进入下一层循环时,a是再次创建,而不是更改以前的引用实体,所有不存在问题。

四. 指针空值nullptr(C++11)

在C++03、C++98和C语言中,使用NULL来表示指针空值。我们可以看到,C++头文件中对NULL的定义如下:

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

也就是说,在C++中,NULL只是将NULL定义为值为0的宏,并没有将其转化为指针类型,所以,NULL存在类型不明确问题。为了解决C++98和C++03中NULL类型冲突的问题,C++11引入了新的指针空值nullptr,其定义为:#define nullptr ((void *)0) -- 将0强转类型转化为void*类型。

下面代码定义了一组函数重载,两个func函数的参数分别为int类型和int*类型,如果传入NULL调用func,我们希望调用的函数为func(int*),但实际上是func(int)被调用了,这是因为NULL被替换为了0,从而误调用了func(int),而传入nullptr就不存在问题。

void fun(int x)
{
	cout << "void fun(int x)" << endl;
}

void fun(int* x)
{
	cout << "void fun(int* x)" << endl;
}

int main()
{
	//C++98、C++03
	int* p1 = NULL;
	int* p2 = 0;  //会与数字0发生冲突

	//C++11
	int* p3 = nullptr;

	fun(NULL);     //void fun(int x)
	fun(nullptr);  //void fun(int* x)

	return 0;
}
图4.1 程序运行结果

 

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

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

相关文章

开发日记-lombok

开发日记-lombok环境问题解决方案&#xff1a;1 Data注解失效 无法正常生成 get和set方法2 RequiredArgsConstructor(onConstructor _(Lazy)) 符号_无法识别环境 idea2020.1lombok1.18.24jdk1.8 问题 Data注解失效 无法正常生成 get和set方法RequiredArgsConstructor(onCons…

机器学习:学习k-近邻(KNN)模型建立、使用和评价

机器学习&#xff1a;学习k-近邻&#xff08;KNN&#xff09;模型建立、使用和评价 文章目录机器学习&#xff1a;学习k-近邻&#xff08;KNN&#xff09;模型建立、使用和评价一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤1.数据读取2.数据理解3.数据准备4.算…

Talk预告 | 悉尼科技大学澳大利亚人工智能研究所讲师方震:广义分布外检测的学习理论

本期为TechBeat人工智能社区第476期线上Talk&#xff01; 北京时间2月22日(周三)20:00&#xff0c;悉尼科技大学澳大利亚人工智能研究所讲师——方震的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “广义分布外检测的学习理论”&#xff0c;届时将…

信息加密技术

介绍信息加密 信息加密是实现数据保密性的手段。 信息加密&#xff08;Encryption&#xff09;是将明文信息转换为密文信息&#xff0c;使之在缺少特殊信息时不可读的过程。只有拥有解密方法的对象&#xff0c;经由解密过程&#xff0c;才能将密文还原为正常可读的内容。 现…

Vue — 详解mixins混入使用

前言 当我们的项目越来越大&#xff0c;我们会发现组件之间可能存在很多相似的功能&#xff0c;你在一遍又一遍的复制粘贴相同的代码段&#xff08;data&#xff0c;method&#xff0c;watch、mounted等&#xff09;&#xff0c;如果我们在每个组件中去重复定义这些属性和方法会…

看了这份Java高级笔试宝典覆盖近3年Java笔试中98%高频知识点,反打面试官

首先声明&#xff1a; 本书覆盖了近3年程序员面试笔试中超过98%Java高频知识点&#xff0c;当你细细品读完本书后&#xff0c;面试都是小问题。 一书在手/工作不愁 记住重点&#xff0c;考试要考 前言 程序员求职始终是当前社会的一个热点&#xff0c;而市面上有很多关于程…

ROS | 键盘控制

文章目录 概述一、定义介绍二、功能作用三、使用方法四、实例演示概述 在研发机器人/自动驾驶时,可能没有遥控器,又或者是仿真环境等情况,常常需要通过键盘控制小车。 一、定义介绍 本节详细讲述了如何通过键盘来控制ROS小车,附有全套源码。 二、功能作用 本程序通过向RO…

Netty (三):进阶

文章目录1. 粘包与半包1.1 粘包现象1.2 半包现象1.3 现象分析1.4 解决方案方法1&#xff0c;短链接方法2&#xff0c;固定长度方法3&#xff0c;固定分隔符方法4&#xff0c;预设长度2. 协议设计与解析2.1 为什么需要协议&#xff1f;2.2 redis 协议举例2.3 http 协议举例2.4 自…

超级完整 的 Maven 讲解 以及私服搭建

第一章 Maven 简介 1.1、Maven 概述 Maven 是一款基于 Java 平台的项目管理和整合工具&#xff0c;它将项目的开发和管理过程抽象成一个项目对象模型&#xff08;POM&#xff09;。开发人员只需要做一些简单的配置&#xff0c;Maven 就可以自动完成项目的编译、测试、打包、发…

Linux系统点亮LED

目录应用层操控硬件的两种方式sysfs 文件系统sysfs 与/sys总结标准接口与非标准接口LED 硬件控制方式编写LED 应用程序在开发板上测试对于一款学习型开发板来说&#xff0c;永远都绕不开LED 这个小小的设备&#xff0c;基本上每块板子都至少会有一颗 LED 小灯&#xff0c;对于我…

Allegro无法打开10度走线命令的原因和解决办法

Allegro无法打开10度走线命令的原因和解决办法 做PCB设计的时候,10度走线也是较为常见的设计方式,Allegro支持10度走线,如下图 需要10度走线的时候,Options只需要勾选Route offset命令即可 但有时options处会看不到10度走线的命令,如下图

从0到1一步一步玩转openEuler--20 openEuler 管理服务-服务管理

文章目录20 管理系统服务20.1 sysvinit命令和systemd命令20.2 显示所有当前服务20.3 显示服务状态20.4 运行服务20.5 关闭服务20.6 重启服务20.7 启用服务20.8 禁用服务20 管理系统服务 systemd提供systemctl命令来运行、关闭、重启、显示、启用/禁用系统服务。 20.1 sysvini…

开源社首届正式成员大会暨2022年度总结会圆满召开

开源社KAIYUANSHE近期微信公众号订阅功能做调整啦&#xff01;没有被星标的账号在信息流里可能不显示大图了&#xff01;快星标⭐我们&#xff0c;就可以及时看到发布的文章啦&#xff01;STEP01 点击右上角标志STEP02 点击【设为星标】开源社 2023 年度首届全体正式成员大会于…

【2】linux命令每日分享——ls列出目录和文件

大家好&#xff0c;这里是sdust-vrlab&#xff0c;Linux是一种免费使用和自由传播的类UNIX操作系统&#xff0c;Linux的基本思想有两点&#xff1a;一切都是文件&#xff1b;每个文件都有确定的用途&#xff1b;linux涉及到IT行业的方方面面&#xff0c;在我们日常的学习中&…

快速制作一个chrome插件

说在前面 在我日常开发以及娱乐生活中&#xff0c;浏览器是我使用频率较高的一个应用&#xff0c;当我大学拥有第一部电脑开始&#xff0c;之后不论电脑换成什么&#xff0c;以及使用的是什么系统&#xff0c;我的首选浏览器都是Chrome&#xff0c;不仅仅是因为其速度快&#x…

Jetpack之ViewModel

The ViewModel class is a business logic or screen level state holder. 上面是官方给的定义&#xff0c;ViewModel 类是业务逻辑或屏幕级状态持有者。 一、业务逻辑持有者 在此之前&#xff0c;无论是MVC模式&#xff0c;还是MVP模式&#xff0c;在视图层&#xff0c;都会…

简化客户服务操作的最佳方式:客户服务响应模板

关键词&#xff1a;客户服务响应模板&#xff1b;SaleSmartly&#xff08;ss客服&#xff09; 客户服务响应模板可以通过提供标准响应来帮助简化客户服务操作。这些客户服务模板可用于各种目的和方案&#xff0c;包括欢迎客户、回复查询、结束对话、请求评论等。请继续阅读&am…

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题A(5)

目录 模块A 基础设施设置与安全加固 一、项目和任务描述&#xff1a; 二、服务器环境说明 三、具体任务&#xff08;每个任务得分以电子答题卡为准&#xff09; A-1任务一 登录安全加固&#xff08;Windows&#xff09; 1.密码策略 a.密码策略必须同时满足大小写字母、数…

【ICLR 2022】重新思考点云中的网络设计和局部几何:一个简单的残差MLP框架

文章目录RETHINKING NETWORK DESIGN AND LOCAL GEOMETRY IN POINT CLOUD: A SIMPLE RESIDUAL MLP FRAMEWORKPointMLP残差点模块几何仿射模块精简版模型&#xff1a;PointMLP-elite实验结果消融实验RETHINKING NETWORK DESIGN AND LOCAL GEOMETRY IN POINT CLOUD: A SIMPLE RESI…

电容的参数-详细描述

贴片电容 如同如所示&#xff0c;MLCC&#xff08;Multi-layer Ceramic Capacitors&#xff09;&#xff0c;外形很好区分。 实际内部结构 使用的还是平行板电容器原理&#xff0c;只是这个是叠层结构&#xff1b;电解电容是卷起来的圆柱状&#xff1b; 容值&#xff1a; …