【c++之于c的优化 - 下】

news2024/11/28 15:55:05

前言


一、inline

概念

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

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

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

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

特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,
  2. 缺陷:可能会使目标文件变大,
  3. 优势:少了调用开销,提高程序运 行效率。
  4. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为《C++prime》第五版关于inline的建议:
    在这里插入图片描述
  5. 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(int) 被声明为 inline类型,在编译阶段会对它进行展开,而且不会被加入符号表,但是由于 文件main.cpp
包含的 F.h头文件 中只有声明没有定义,因此展开的只有声明,所以在链接时会出错。 解决方法:内联函数,声明和定义都写到 .h文件中。

使用inline的原由

使用inline是为了替代C语言中的宏函数。
宏的优点:

  1. 短小代码直接替换,不需要进行函数调用,速度更快;(提高性能)
  2. 没有类型限制;(提高代码复用性)

宏的缺点:

  1. 无法进行调试;
  2. 没有类型安全的检查;
  3. 会大大增加源代码长度。
  4. 导致代码可读性差,可维护性差,容易误用。

宏定义的内容会在预处理阶段进行替换,因此在调试时是找不到的。
因此我们在日常练习的时候可以减少对宏的使用
当然,宏也有其不可替代的功能:传递变量类型
eg:计算结构体成员偏移量的宏:offsetof
#define MY_offsetof(s, m) (size_t)&(((s*)0)->m)

#define MY_offsetof(s, m) (size_t)&(((s*)0)->m) 

class Person
{
public:
	char name[22];
	int age;
};

void Test03()
{
	cout << offsetof(Person, Person::age) << endl;
	cout <<MY_offsetof(Person, Person::age) << endl;
}

结果:
在这里插入图片描述


二、auto(c++11)

引入

随着程序越来越复杂,程序中用到的类型也越来越复杂,主要表现在两个方面:

  1. 类型难以拼写;
  2. 含义不明确导致出错。

示例(这里以实际开发情况为例):

#include<map>
#include<string>


void Test01()
{
	std::map<std::string, std::string> m = { {"宁姚", "剑气长城"} , {"裴钱","莲藕福地"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		std::cout << it->first << ' ' << it->second << std::endl;
		++it;
	}
}

std::map<std::string, std::string>::iterator这一长串字符就是 it 的类型名,可见是比较复杂的, 这里我们有一种比较简单的方法是使用 typedef

typedef std::map<std::string, std::string>::iterator MAP;
不过这样仍然需要再写一遍类型名,还是会有出错的情况; 而且,typedef在设计时是有些“小问题的”,使用起来会让人难以理解,比如:

void Test02()
{
	typedef int* T;
	const T val1;
}
// 在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的类型。
// 然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义

在这里插入图片描述

使用

在早期C/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类型的变量必须初始化,因为编译器在编译阶段需要通过初始化数据来推导变量的类型;
因此auto并不是类型,而是类型占位符,在编译阶段会被替换为变量的实际类型。

注意事项

  1. auto与指针和引用联用;
void TestAuto()
{
	auto val = 10;
	auto p1 = &val;  //  设置指针时加不加 *号都一样,加上后可以当做提醒
	auto* p2 = &val;
	auto& b = val;  // 设置引用时必须写明 &符号
	cout << typeid(val).name() << endl;
	cout << typeid(p1).name() << endl;
	cout << typeid(p2).name() << endl;
	cout << typeid(b).name() << endl;
}

在这里插入图片描述

  1. 使用auto在同一行声明多个变量时,每个变量必须是同一类型;
    因为编译器实际上只对第一个变量进行推导,然后用推导出来的类型初始化其余变量。
void TestAuto()
{
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
  1. auto不能作为函数的参数;
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
  1. auto不能声明数组;
void TestAuto()
{
    int a[] = {1,2,3};
    auto b[] = {456};
}
  1. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法;
  2. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

三、范围for循环(c++11)

语法

在C++98中如果要遍历一个数组,可以按照以下方式进行:

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
     array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
     cout << *p << endl;
}

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

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)  // 朋友们可以思考为何此处需要加 引用
     e *= 2;
for(auto e : array)
     cout << e << " ";
return 0;
}

注意:与不同for循环类似,可以使用continue结束本次循环,也可以使用break跳出整个循环。

使用条件

  1. for循环迭代的范围必须是确定的对于数组而言,就是数组中第一个元素和最后一个元素的范围;
    对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
    注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[])  // 数组名作为参数传递时只传首地址。
{
    for(auto& e : array) 
        cout<< e <<endl;
}
  1. 迭代的对象要实现++和==的操作。

四、指针空值nullptr(c++11)

引入

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

void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}

NULL实际上是一个宏,在头文件<stddef.h>中有如此定义:
在这里插入图片描述
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。
不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

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*)常量,但是编译器默认情况下将其看成是一个整形常量,
如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

注意事项

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

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

void Test04()
{
	f(NULL);
	f(nullptr);
	cout << "sizeof(int) = " << sizeof(int) << endl;
	cout << "sizeof(NULL) = " << sizeof(NULL) << endl;
	cout << "sizeof(void*) = " << sizeof(void*) << endl;
	cout << "sizeof(nullptr) = " << sizeof(nullptr) << endl;
}

在这里插入图片描述

补充:此处为何不就NULL的宏定义进行修改,而从新引入一个新的关键字:nullptr?
因为语言是不断发展的,在发展过程中会有数以万计的程序员写下以亿为单位的代码,说不得就有程序员就是想要NULL代表0,
因此语言在发展过程中即便之前的内容有问题,也不会修改原本的内容,而是再创造出一种新的语法。

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

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

相关文章

契约锁与200多家软件厂商实现集成应用,实现更多业务电子签需求

在中大型组织的数字化建设中&#xff0c;电子签章需求紧紧围绕业务展开&#xff0c;实现电子签章与管理软件集成应用已经成为普遍需求。 契约锁数字可信产品拥有200多家管理软件厂商集成对接经验&#xff0c;覆盖ERP、OA、专项业务系统、移动APP应用、低代码平台、BPM流程、小程…

Vue - 使用Lodash实现防抖和节流

GitHub Demo 地址 在线预览 Lodash 官网 参考&#xff1a; Vue 防抖节流 详细介绍 vue 优雅的实现防抖和节流 防抖、节流的介绍 js防抖和节流的实现原理及应用场景 js防抖节流应用场景及写法 JS中的防抖 什么的防抖和节流&#xff1f; 函数节流&#xff08;throttle&#x…

论文翻译:2022_Phase-Aware Deep Speech Enhancement: It’s All About The Frame Length

摘要 虽然相位感知语音处理近年来受到越来越多的关注&#xff0c;但大多数帧长约为32 ms的窄带STFT方法显示出相位对整体性能的影响相当有限。与此同时&#xff0c;现代基于深度神经网络(DNN)的方法&#xff0c;如Conv-TasNet&#xff0c;隐式修改幅度和相位&#xff0c;在非常…

CUDA虚拟内存管理

CUDA中的虚拟内存管理 文章目录CUDA中的虚拟内存管理1. Introduction2. Query for support3. Allocating Physical Memory3.1. Shareable Memory Allocations3.2. Memory Type3.2.1. Compressible Memory4. Reserving a Virtual Address Range5. Virtual Aliasing Support6. Ma…

自动化测试实战篇(6)jmeter实现脚本录制,抓取接口信息

Jmeter中脚本录制&#xff0c;是一个非常方便找到接口内容的一种工具&#xff0c;不用想fiddler抓包定位接口信息速度不够快 设置代理服务器 这里以谷歌浏览器为例子 打开您的计算机的代理设置 把代理服务器打开这里就以127.0.0.1和8080端口为例子&#xff0c;这个需要记住…

ChatGPT背后的经济账

ChatGPT能否取代Google、百度这样的传统搜索引擎&#xff1f;为什么中国不能很快做出ChatGPT&#xff1f;当前&#xff0c;对这些问题的探讨大多囿于大型语言模型&#xff08;LLM&#xff09;的技术可行性&#xff0c;忽略或者非常粗糙地估计了实现这些目标背后的经济成本&…

((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第4天(搜索与图论-下 专题)】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有63天

&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6; 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&a…

差分隐私学习笔记

2021网络空间安全西湖学术论坛线上报告中介绍了差分隐私过去发展&#xff0c;目前现状以及未来研究方向。博主对这个报告进行了介绍与总结。总结中提到学习差分隐私最重要的环节是&#xff1a; 了解差分隐私的基本机制&#xff1a;拉普拉斯机制、指数机制和高斯机制差分隐私的组…

【数据结构与算法】前缀树的实现

&#x1f320;作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《数据结构与算法要啸着学》 &#x1f387;座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;…

54 循环神经网络 RNN【动手学深度学习v2】

54 循环神经网络 RNN【动手学深度学习v2】 深度学习学习笔记 学习视频&#xff1a;https://www.bilibili.com/video/BV1D64y1z7CA/?spm_id_from333.880.my_history.page.click&vd_source75dce036dc8244310435eaf03de4e330 对序列化数据集的训练网络&#xff0c;通常称为RN…

第三章 Opencv图像像素操作

目录1.像素1-1.确定像素位置1-2.获取指定像素的像素值1-3.修改像素的BGR值2.用numpy模块操作像素2-1.创建图像1.创建黑白图像2.创建彩色图像3.创建随机图像2-2.拼接图像1.水平拼接hstack()方法2.垂直拼接vstack()方法1.像素 1.像素是构成数字图像的最小单位。每一幅图像都是由M…

【第29天】SQL进阶-查询优化- performance_schema系列实战四:查看最近的SQL执行信息(SQL 小虚竹)

回城传送–》《32天SQL筑基》 文章目录零、前言一、 查看最近的top sql1.1 数据准备&#xff08;如果已有数据可跳过此操作&#xff09;1.2 查询events_statements_summary_by_digest表二、查看最近执行失败的SQL2.1 开启第一个会话&#xff0c;执行错误sql2.2 开启第二个会话&…

pytest当中pytest.ini使用

目录 一、作用 二、存放位置 三、功能&#xff08;只列了简单的&#xff09; 1、 addopts 2、更改测试用例收集规则 四、运行就减少了命令了 前言&#xff1a;pytest配置文件可以改变pytest的运行方式&#xff0c;它是一个固定的文件pytest.ini文件。 一、作用 pytest.in…

Ceph分部署存储知识总结

Ceph 一.deploy-ceph部署 投入使用ceph前&#xff0c;要知道一个很现实得问题&#xff0c;ceph对低版本内核得客户端使用非常不友好&#xff0c;低内核是指小于等于3.10.0-862&#xff0c;默认的centos7.5及以下的系统都是小于此类内核&#xff0c;无法正常使用ceph的文件存储…

内网渗透(十一)之内网信息收集-内网IP扫描和发现

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

用YOLOv8推荐的Roboflow工具来训练自己的数据集

YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本&#xff0c;相较于之前的版本&#xff0c;YOLOv8可以更快速有效地识别和定位图像中的物体&#xff0c;以及更准确地分类它们。 作为一种深度学习技术&#xff0c;YOLOv8需要大量的训练数据来实现最佳性能。…

如何旋转YUV图片数据且使用Qt显示

前言 提一下这篇文章的需求&#xff1a;将USB相机获取到的YUV数据进行旋转&#xff0c;然后转为QImage进行显示。原本程序中是有旋转的代码&#xff0c;但不知道为什么&#xff0c;旋转出来的图片会花屏。关于花屏的问题&#xff0c;后面会稍微阐述一下。所以&#xff0c;经过…

[多线程进阶] 常见锁策略

专栏简介: JavaEE从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录: 1. 常见的锁策略 1.1 乐观锁 vs 悲观锁 1.2 读写…

bootstrap 框架

文章目录bootstrap必须使用 HTML5 文档类型排版和链接默认栅格系统带有基本栅格的 HTML 代码媒体类型媒体类型逻辑运算符 用来做条件判断页面布局&#xff1a; 引入 css&#xff08;bootstrap.min.css&#xff09; 类名03-面包屑导航警告框、徽章、面包屑导航、按钮、按钮组卡…

css行内块元素垂直居中

css行内块元素垂直居中 div里边有个img标签&#xff0c;要想让img垂直居中&#xff0c;需要 给父盒子设置line-heightheightimg设置vertical-align:middle <div style"background-color: red; height: 150px;line-height: 150px;"><img src"images/…