【C++程序员的自我修炼】基础语法篇(二)

风力掀天浪打头

只须一笑不须愁


目录

内联函数 

概念💞

 性质 ⭐

不建议变量分离

inline的优劣势

 inline的局限性

auto关键字

auto的概念💞

auto的使用细则💞

auto不能推导的场景 💞

auto基于范围的for循环💞

指针空值nullptr

 

内联函数 

契子

在大型项目中我们会对某一种函数进行反复调用,比如排序中的 Swap ,调用函数所带来的后果是建立栈帧,多次调用就要建立多重栈帧严重影响运行效率。

在 C 语言阶段我们曾采用宏定义的方式来解决这个问题,比如说两数相加函数

#define Add(a,b) ((a)+(b))

优势在调用的地方直接展开(替换),增强代码的复用性,没有函数调用建立栈帧的开销

劣势无法对宏定义中的变量进行类型检查,嵌套定义过多影响程序的可读性,容易出错

为了解决这个问题,我们的 C++ 推出了内联函数:

概念💞

inline 修饰的函数叫做内联函数,编译时 C++ 编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率

以下将会用汇编代码解释这个问题:

如果在上述函数前增加 inline 关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用

查看方式🌤️

在release模式下,查看编译器生成的汇编代码中是否存在call Add
在debug模式下,需要对编译器进行设置,否则不会展开 

 在debug模式下对编译器的设置:

这个时候我们发现,Add函数展开了 

 性质 ⭐

不建议变量分离

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

例如,我们先来看以下代码:

无法解析的外部符号 "int __cdecl Add(int,int)" (?Add@@YAHHH@Z)函数main中引用了该符号   所以 inline 不建议声明和定义分离为好

inline的优劣势

inline 是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用

🌤️缺陷:可能会使目标文件变大
🌤️优势:少了调用开销,提高程序运行效率

 inline的局限性

inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同,一般建议:将函数规模较小、不是递归、且频繁调用的函数采用 inline 修饰,否则编译器会忽略 inline 特性(函数调用不展开

auto关键字

auto的概念💞

随着程序越来越复杂,程序中用到的类型也越来越复杂

经常体现在:

类型难于拼写

含义不明确导致容易出错

例如~

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange","橙子" },{"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

std::map<std::string, std::string>::iterator 是一个类型,但是该类型太长了,特别容易写错

在 C语言阶段 我们可以通过 typedef 给类型取别名,比如:

typedef std::map<std::string, std::string> Map
typedef int SListDataType;

 为我们 C++ 中提供了一个很方便的关键字 auto自动类型推导

std::map<std::string, std::string> m;
<1>std::map<std::string, std::string>::iterator it = m.begin();
<2>auto it = m.begin();

其中 <1>、<2>的写法是等价的,auto的作用就是从右边推导类型、简化代码

例如

#include<iostream>
int main()
{
	auto a = 0;
	return 0;
}

因为 表达式a 的右边为整型,所以 auto 推导的类型就为 int 

我们可以用 typeid 进行验证:

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int TestAuto(int n = 0)
{
	return n;
}
int main()
{
	auto a = 0;
	auto b = 0.0;
	auto c = '0';
	auto d = TestAuto();
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

<1>使用 auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto 的实际类型

<2> auto 并非是一种 (类型) 的声明,而是一个类型声明时的 (占位符) ,编译器在编译期会将 auto 替换为变量实际的类型

auto的使用细则💞

注意💞

auto与指针和引用结合起来使用

auto 与指针和引用结合起来使用 auto 声明指针类型时,用 autoauto* 没有任何区别,但用auto* 声明引用类型时则必须加 &

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int main()
{
	int x = 10;
	auto a = &x;    
	auto* b = &x;  
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}


在同一行定义多个变量

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

auto不能推导的场景 💞

auto不能作为函数的参数

错误示范

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;

void Fun(auto a)
{
	cout << a << endl;
}

int main()
{
	Fun(5);
	return 0;
}

 

 auto不能直接用来声明数组

错误示范 

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;

int main()
{
	int a[] = { 1,2,3 };
	auto b[] = { 4,5,6 };
	return 0;
}

那硬是要用 auto 声明数组该怎么办呢? 

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int main()
{
	int a[] = { 1,2,3 };
	auto b = a;
	cout << typeid(b).name() << endl;
    return 0;
}

我们可以看到 b 是一个 int* 的类型 

如果表达式为数组且 auto 带上&,则推导类型为数组类型

auto基于范围的for循环💞

我们平时写 C语言 的循环是不是特别麻烦,例如以下的代码

	int a[] = { 1,3,5,7,9,2,4,6,8,0 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误

因此 C++ 中引入了基于范围的for循环

for 循环后的括号由冒号“ ”分为两部分:

第一部分是范围内用于迭代的变量(自己喜欢用什么就写什么
第二部分则表示被迭代的范围(数组名
	int a[] = { 1,3,5,7,9,2,4,6,8,0 };
	for (int i : a)
		cout << i << " ";

这个方法的爽点就在于:自动判断循环条件,执行完语句变量自动 ++ 或 -- 

到这里就有人问这个循环只能遍历吗?能不能修改数组的值呢?

答案是可以的,以下我们来看:

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int main()
{
	int a[] = { 1,2,3,4,5 };
	for (int& i : a)
	{
		i *= 2;
		cout << i << " ";
	}
    return 0;
}

这里传引用的目的就是为了改变该地址的内容 

注意

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

优点:简化代码、减少出错率

局限性:范围的 for 循环的底层是 begin、end 两个迭代器相当于从数组的首元素开始到尾元素,一遍历就是整个数组

auto 的出现解决了范围 for 循环变量类型有时不好写的问题以及可以无脑循环整个数组🌤️

	int a[] = { 1,2,3,4,5 };
	for (auto i : a)
		cout << i << " ";

 for (auto i : a) 就成了循环遍历整个数组的模板,因为自动类型推导嘛,不用考虑类型

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

错误示范

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;

void Fun(int* a)
{
	for(auto i: a)
		cout << i << " ";
}

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

因为数组传参传的是数组首元素地址从而导致 begin 和 end  不完整,所以报错

指针空值nullptr

  • 是否可以混着用 NULL和 nullptr呢?
  • 这两者到底有什么区别呢?
  • 滥用NULL到底会带来什么不可思议的错误?

别急接下来我们谈谈他们两者中的那些破事

NULL的介绍

🔥在良好的 C/C++ 编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

 int* p1 = NULL;
 int* p2 = 0;

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

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

NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦

#include<iostream>
using namespace std;
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;
}

可以发现 NULL 在传参的时候竟然走了 int 类型!!!

我们想 f(NULL) 调用指针版本的 f(int*) 函数,但是由于 NULL 被定义成 0,走了 f(int) 函数

NULL 是具有一定风险的:

NULL终究只是一个宏。它是一个整型,它不是指针。因而随之带来的问题是,我们没有办法在不显示声明指针类型的情况下定义一个空指针。

nullptr的介绍

🔥而在 C++ 中有一个关键字完美的解决了这个问题——nullptr。作为一个字面常量和一个零指针常数,它可以被隐式转换为任何指针类型

拿上面那个栗子看

    f(0);
	f(nullptr);
	f((int*)NULL);

 nullptr 只能隐式转换为 int 类型吗?我们来看

#include <iostream>
using namespace std;

void Fun(void* c) {
    cout << "Fun(void* c)" << endl;
}
void Fun(int n) {
    cout << "Fun(int n)" << endl;
}
int main() {
    Fun(NULL);
    Fun(nullptr);
    return 0;
}

由于 nullptr 无法隐式转换为整形,而可以隐式匹配指针类型

特性:

nullptr 是一个有用的小特性,它能让你的代码更安全

sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同

#include <iostream>
using namespace std;

int main() 
{
    cout << sizeof(nullptr) << endl;
    cout << sizeof((void*)0) << endl;
    return 0;
}

 

先介绍到这里啦~

有不对的地方请指出💞

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

谈谈对CPU IOwait的理解

谈谈对CPU IOwait的理解 %iowait表示在一个采样周期内有百分之几的时间属于以下情况&#xff1a;CPU空闲并且有仍未完成的I/O请求&#xff08;如果单纯是CPU空闲&#xff0c;但是并没有IO请求&#xff0c;那么这个时间就是CPU的idle时间&#xff09;&#xff0c;两个条件必须同…

JAVA学习笔记21

1.IDEA的使用 1.ctrl B 快速定位到方法 2.ctrl Y 快速删除行 3.ctrl D 快速复制行 4.ctrl H 查看继承的层级关系 5.快速格式化代码 ctrl shift L 6.alt R 快速允许程序 7.ctrl / 快速添加注释 1.包(软件包) 1.1包的三大作用 1.区分相同名字的类 2.当类很多的…

宝宝洗衣机买几公斤?四款顶尖婴儿洗衣机合集分享

由于婴儿类衣服的数目以及体积&#xff0c;一般婴儿洗衣机的体积比普通的家用洗衣机要小&#xff0c;而且在功能上比传统的大型洗衣机多了一个高温蒸煮除菌的功能。婴儿洗衣机和传统的大型洗衣机一样&#xff0c;都是具有着波轮式清洗方式和滚筒式清洗方式两种不同的选择&#…

【C++】Google Gtest测试框架的使用

本文首发于 ❄️慕雪的寒舍 gtest模块的安装参考站内教程 ubuntu安装google gtest 本文使用的gtest版本为1.14.0&#xff1b; 1.gtest是用来干嘛的&#xff1f; google gtest是一个c的单元测试模块&#xff0c;它提供了一系列规范化的宏&#xff0c;来帮助我们进行函数的单元…

Linux之 线程池 | 单例模式的线程安全问题 | 其他锁

目录 一、线程池 1、线程池 2、线程池代码 3、线程池的应用场景 二、单例模式的线程安全问题 1、线程池的单例模式 2、线程安全问题 三、其他锁 一、线程池 1、线程池 线程池是一种线程使用模式。线程池里面可以维护一些线程。 为什么要有线程池&#xff1f; 因为在…

一文教会女朋友学会日常Git使用!Git知识总结

文章目录 一文教会女朋友学会日常Git使用&#xff01;Git知识总结一、git基本知识了解1.git简介2.git区域了解3.git常用命令 二、常用工作场景1.克隆远程仓库&#xff0c;把仓库代码拉到本地2.推送代码到远程仓库&#xff08;1&#xff09;本地代码和远程仓库版本相同&#xff…

细谈SolidWorks教育版的一些基础知识

SolidWorks教育版是一款广泛应用于工程设计和教育领域的三维建模软件。它具备直观易用的操作界面和强大的设计功能&#xff0c;为学生提供了一个学习和实践的平台。在本文中&#xff0c;我们将详细探讨SolidWorks教育版的一些基础知识&#xff0c;帮助初学者更好地了解和掌握这…

鸿蒙实战开发-如何使用三方库

使用三方库 在使用三方库之前&#xff0c;需要安装 ohpm&#xff0c;并在环境变量中配置。 在项目目录的Terminal窗口执行ohpm命令下载依赖 ohpm install yunkss/eftool 命令运行成功后&#xff0c;在项目的oh-package.json5文件中会自动添加上依赖&#xff0c;如下所示&am…

【氮化镓】GaN器件中关态应力诱导的损伤定位

概括总结&#xff1a; 这项研究通过低频1/f噪声测量方法&#xff0c;探究了在关态&#xff08;OFF-state&#xff09;应力作用下&#xff0c;AlGaN/GaN高电子迁移率晶体管&#xff08;HEMTs&#xff09;中由应力引起的损伤的定位。研究中结合了电致发光&#xff08;EL&#xf…

【Java面试题系列】基础篇

目录 基本常识标识符的命名规则八种基本数据类型的大小&#xff0c;以及他们的封装类3*0.10.3返回值是什么short s1 1; s1 s1 1;有什么错? short s1 1; s1 1;有什么错?简述&&与&的区别&#xff1f;简述break与continue、return的区别&#xff1f;Arrays类的…

(负载点电源)PCD3203高转换率同步降压40V/3A内置高低侧MOSFET只需极少外围元件

1. 产品特性 ➢ 输入电压范围&#xff1a; 4.5V~40V ➢ 最大负载&#xff1a; 3A ➢ 上下管导通电阻&#xff1a; 110mΩ/70mΩ ➢ 软启保护时间 tss&#xff1a; 1ms ➢ 工作频率范围&#xff1a; 500kHz~2.5MHz ➢ 逐周期峰值电流限制 ➢ 内部补偿 ➢ 可调的输入欠压…

这个AI 应用万人使用:真人视频转动漫、手绘风,丝滑感前所未有

视频的次元壁就这么被打破了。 在 AI 的加持下&#xff0c;真人视频变身二次元就这么简单 只需要导入原始视频&#xff0c;它就可以帮你把视频改成你想要的风格&#xff0c;比如动漫风、手绘风或者 3D 卡通风格。 这一应用一经推出立刻引起了很多人的关注 因其操作简单&#x…

蓝桥杯-穿越雷区

题目要求 需求&#xff1a;从一个方格中A点&#xff0c;按要求移动到B点。 要求&#xff1a;每次只能走上下左右&#xff0c;每次只能走一次&#xff0c;每次是轮换穿越’‘,’-两个&#xff0c;否则就会能量失衡&#xff0c;发生爆炸。 使用的算法&#xff1a;这题典型的就是使…

nginx的安装教程

文章目录 简介nginx安装windows下安装linux下安装 简介 nginx是一个开源的web服务器和反向代理服务器&#xff0c;可以用作负载均衡和HTTP缓存。它处理并发能力是十分强大的&#xff0c;能够经受高负载的考验。 正向代理 Nginx不仅可以做反向代理&#xff0c;实现负载均衡&am…

AI工作站设计方案:903-多路PCIe3.0的单CPU 学习型AI工作站

多路PCIe3.0的单CPU 学习型AI工作站 一、机箱功能和技术指标&#xff1a; 系统 系统型号 ORI-SR500 主板支持 EEB(12*13)/CEB(12*10.5)/ATX(12*9.6)/Mi cro ATX 前置硬盘 最大支持2个3.5寸1个2.5寸SATA 硬盘2个2.5寸SATA 硬盘 &#xff08;背部&#xff09; 电源类型 C…

【Django开发】前后端分离美多商城项目第4篇:用户部分,1. 判断用户名是否存在【附代码文档】

美多商城项目4.0文档完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;美多商城&#xff0c;项目准备1.B2B--企业对企业,2.C2C--个人对个人,3.B2C--企业对个人,4.C2B--个人对企业,5.O2O--线上到线下,6.F2C--工厂到个人。项目准备&#xff0c;配置1. 修改set…

阿里云服务器ECS经济型e实例怎么样?

阿里云服务器ECS经济型e系列是阿里云面向个人开发者、学生、小微企业&#xff0c;在中小型网站建设、开发测试、轻量级应用等场景推出的全新入门级云服务器&#xff0c;CPU处理器采用Intel Xeon Platinum架构处理器&#xff0c;支持1:1、1:2、1:4多种处理器内存配比&#xff0c…

vue3组合式函数

vue3的组合式函数的作用是封装和复用响应式状态的函数。只能在setup 标签的script标签汇总或者setup函数中使用。 普通的函数只能调用一次&#xff0c;但是组合式函数接受到响应式参数&#xff0c;当该值发生变化时&#xff0c;也会触发相关函数的重新加载。 如下 定义了一个…

算法(滑动窗口四)

1.串联所有单词的子串 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff…

《QDebug 2024年3月》

一、Qt Widgets 问题交流 1. 二、Qt Quick 问题交流 1.Qt5 ApplicationWindow 不能使用父组件 Window 的 transientParent 属性 ApplicationWindow 使用 transientParent 报错&#xff1a; "ApplicationWindow.transientParent" is not available due to compone…