【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;
}

 

先介绍到这里啦~

有不对的地方请指出💞

 

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

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

相关文章

nginx的安装教程

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

简单说清楚什么是SQL Injection?

最近看完了《The Pragmatic Programmer: 20th Anniversary Edition, 2nd Edition: Your Journey to Mastery》&#xff0c;在第7章&#xff1a;While You Are Coding的footnotes中&#xff0c;提到了一幅漫画&#xff1a; 这不仅用简单的方式说清楚了什么是SQL Injection&#…

C语言数据结构易错知识点(6)(快速排序、归并排序、计数排序)

快速排序属于交换排序&#xff0c;交换排序还有冒泡排序&#xff0c;这个太简单了&#xff0c;这里就不再讲解。 归并排序和快速排序都是采用分治法实现的排序&#xff0c;理解它们对分支思想的感悟会更深。 计数排序属于非比较排序&#xff0c;在数据集中的情况下可以考虑使…

百度贝塞尔曲线证码识别代码

一、前言 百度出了如图所示的验证码&#xff0c;需要拖动滑块&#xff0c;与如图所示的曲线轨迹进行重合。经过不断研究&#xff0c;终于解决了这个问题。我把识别代码分享给大家。 下面是使用selenium进行验证的&#xff0c;这样可以看到轨迹滑动的过程&#xff0c;如果需要…

Windows11系统缺少相关DLL解决办法

一.缺少msvcp120.dll 下载Mircrosoft Visual C 2015等系统关键组件 Microsoft Visual C 2015-2022 Redistributable (x86) - 14.34.31931 Installation Error etc.. - Microsoft Q&A 二.缺少python27.dll 重新下载python2.7进行安装(选择Windows x86-64 MSI installer)…

DS2438Z+TR智能电池监测器多场景行业应用解决方案

DS2438ZT&R智能电池监视器为电池组提供了若干很有价值的功能&#xff1a;可用于标识电池组的唯一序列号&#xff1b;直接数字化的温度传感器省掉了电池组内的热敏电阻&#xff1b;可测量电池电压和电流的A/D转换器&#xff1b;集成电流累积器用于记录进入和流出电池的电流总…

前端学习<二>CSS基础——14-CSS3属性详解:Web字体

前言 开发人员可以为自已的网页指定特殊的字体&#xff08;将指定字体提前下载到站点中&#xff09;&#xff0c;无需考虑用户电脑上是否安装了此特殊字体。从此&#xff0c;把特殊字体处理成图片的方式便成为了过去。 支持程度比较好&#xff0c;甚至 IE 低版本的浏览器也能…

格雷希尔G10系列L150A和L200A气动快速连接器,在新能源汽车线束线缆剥线后的气密性测试密封方案

线束线缆在很多用电环境都有使用&#xff0c;比如说新能源汽车&#xff0c;从电池包放电开始&#xff0c;高低压、通讯都开始进行工作&#xff0c;线束在连接的地方需要具有较高的气密性和稳定性&#xff0c;才能保证车辆在不同环境下能够正常的运行。 线束在组装铜鼻子前需要剥…

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

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

C++第十四弹---模板初阶

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、泛型编程 2、函数模板 2.1、函数模板的概念 2.2、函数模板的格式 2.3、函数模板的原理 2.4、函数模板的实例化 2.5、模板参数的匹配原则 …

抽象类和接口(2)(接口部分)

❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&a…

增加网站搜索引擎排名的6个准则

怎样提高网站排名首页 在竞争激烈的网络世界中&#xff0c;网站的排名对于吸引流量和提升曝光至关重要。登上搜索引擎结果页面的首页&#xff0c;意味着更多的曝光和点击率。以下是一些方法&#xff0c;可以帮助您提高网站在搜索引擎中的排名&#xff0c;让其跻身首页&#xf…

2014年认证杯SPSSPRO杯数学建模A题(第二阶段)轮胎的花纹全过程文档及程序

2014年认证杯SPSSPRO杯数学建模 A题 轮胎的花纹 原题再现&#xff1a; 轮胎被广泛使用在多种陆地交通工具上。根据性能的需要&#xff0c;轮胎表面常会加工出不同形状的花纹。在设计轮胎时&#xff0c;往往要针对其使用环境&#xff0c;设计出相应的花纹形状。   第二阶段问…

SAP 销售分销中的免费货物

销售业务中&#xff0c;免费货物在您与客户协商价格时起着重要作用。在零售、化工或消费品这样的行业部门中&#xff0c;通常以免费货物的形式向客户提供折扣。 作为用户&#xff0c;业务用户希望能自动确定免费货物并将它们归入销售凭证中。同时需要向成本控制部门提供免费货物…

SOC内部集成网络MAC外设+ PHY网络芯片方案:PHY芯片基础知识

一. 简介 本文简单了解一下 "SOC内部集成网络MAC外设 PHY网络芯片方案" 这个网络硬件方案中涉及的 PHY网络芯片的基础知识。 二. PHY芯片基础知识 PHY 是 IEEE 802.3 规定的一个标准模块。 1. IEEE规定了PHY芯片的前 16个寄存器功能是一样的 前面说了&#xf…

[优选算法专栏]专题十五:FloodFill算法(二)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

基于8086多路模拟采集LED报警系统设计

**单片机设计介绍&#xff0c;基于8086多路模拟采集LED报警系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于8086多路模拟采集LED报警系统设计概要 一、引言 在工业控制、环境监测以及智能家居等领域&#xff0c;多…

探索http-vue-loader的奥秘:原理、使用方法、在Vue开发中的应用

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

软考历史题目

2023.3 1. 磁盘索引块1KB,每个地址项4字节&#xff0c;每个磁盘索引块可以存放256个物理块地址 2.5个地址项为直接索引地址&#xff0c;所以0-5逻辑块是直接索引 3.一级间接地址索引&#xff0c;每个指向的物理块存255个地址 4.二级间接地址&#xff1a;256个间接索引表地址…

碧桂园服务:以进促稳,年收入增长至人民币约426.1亿元

碧桂园服务控股有限公司今日发布截至2023年12月31日十二个月报告期内之经审核综合业绩。2023年&#xff0c;碧桂园服务收入持续增长至人民币约426.1亿元。同时&#xff0c;业务发展保持稳健且市场化程度高&#xff0c;来自于第三方的收入占比进一步提升达到新高至约96.9%。 业绩…