【软件开发】C++使用笔记

数据类型

值类型

存放在栈空间中的一段内存。

  • T:左值,最普通的变量,是具有变量名且可取地址的值。
  • \(~\) :右值,常量或不具备名称的值,无变量名不可取地址。通常都是一些生命周期极短的临时值,例如未接收的函数返回值。

备注:

右值及其相关的一系列操作都是 C++11 才新增的功能。

指针类型

一种用于保存内存地址的值类型,可以利用特定的访问运算符访问其指向的内存。

  • T*
  • T[]

引用类型

一种封装的指针类型,像指针一样存储着内存地址信息,但像值类型一样直接访问该内存。

  • T&:引用左值的引用,针对普通变量的引用。
  • T&&:引用右值的引用,使右值类型得以被标识和传递,可借此区分左值来实现一些高性能代码。
  • const T&:可用右值赋值的,引用常量左值的引用
  • T&&(当 T 是一个由编译器推导的类型):万能引用,既可能作为左值引用也可能作为右值引用。

关于引用值类型的重点说明

要注意的是引用类型也是值类型,若有变量名就是左值类型,无关其引用对象的类型。所以无论是右值引用还是万能引用,只要它有变量名,那它就是一个左值。

由于引用类型的使用就相当于引用对象的值类型,因此引用的左值特性会影响到引用对象的值类型,只要其是左值类型的引用,那该引用的对象也就是左值。

  • 右值的本质:

    所以应将右值引用看成一种特殊的左值引用,该引用的目的仅仅是为了声明引用的对象是一个尚无人认领的临时值,以鼓励使用者按需处理。

  • 右值的传递:

    因此传递右值引用的本质就是如何将一个左值引用转为右值引用传递出去。非常简单,强制转换就行。若想泛型化,则可借助模板自动推导返回值万能引用的特性,自动强制转换为所需的引用类型。

不同引用值类型间的转换

  • 左值转右值:直接强制转换即可,其标准函数为std::move(很多函数对右值有特殊处理,因此转换后原本的左值引用不该再继续使用)。
  • 右值转左值:使用变量承接即可(因此当一个右值传入函数时,由于函数参数有名称,该右值将被转换为左值)。
  • 完美转发:当不希望传递的引用因规则导致类型转换时,可以利用万能引用加传递时强制类型转换实现,其标准函数为std::forward

基本类型转换

  • 整数之间相互转换:直接按内存复制,受小端模式的内存布局影响,高精度直接掐断多余内存。
  • 任何数与小数转换:按数学逻辑转换,精度不同时,整数受限于其表示范围,小数则可以表示无穷非数值等数。

函数匹配顺序

  1. 参数完全匹配的普通函数
  2. 参数满足要求的模板函数
  3. 类型转换后可以匹配的普通函数

需要注意的是,自动推导的模板函数不支持自动类型转换,必须要输入值与函数参数类型完全匹配才行。

模板

使用场景

  • 函数模板
  • 变量模板
  • 类模板

需求与概念

需求(requires)和概念(concept)是从 C++20 开始新增的两个关键字,可借此轻松实现对模板参数的约束。

两者相关功能简述如下:

  • 需求

    1. 一种语法符号,表示接下来要声明模板约束。
    2. 一种返回布尔值的运算,支持多种运算方法,包括表达式有效性。
  • 概念
    1. 一种基于模板的编译时推导的常量布尔值。
    2. 一种具有限制要求的模板参数类型。
    3. 一种模板限制要求。

创建概念和需求

template <class T> //概念必须使用模板
concept userConcept = requires(T t) //requires表达式中要用的变量
{
t + t; //有效性验证:表达式是合法可编译的
{ t + t } noexcept; //异常验证:表达式是不会抛出异常的
{ t + t } -> std::same_as<int>; //返回类型验证:表达式计算结果满足特定要求
typename T::x; //类型要求:目标类型结构满足的指定的要求
requires std::is_same_v<T, int>; //条件验证:基于布尔值的验证要求
};

使用概念和需求

template <userConcept T> //将概念作为模板参数类型
requires //通过requires语法引入约束
userConcept //将概念作为布尔值条件
&& requires(T t) {} //通过requires计算的布尔值
&& std::is_same_v<T, int> //其他返回布尔值的手段
void Func()
{
}

其他说明

  • 这些条件和运算本质都是布尔值,因此也支持布尔值的&&||运算。
  • 建议将计算布尔值的表达式用()括起来,不然有时会因计算顺序问题导致编译失败。

可变参数模板

展开方式

  • 递归函数展开:通过传参的方式逐渐取出可变参数依次处理。
  • 逗号表达式展开:部分场景下省略号会将可变参数展开并用逗号分隔,可借此配合逗号表达式对每个参数执行自定义代码。
  • 折叠表达式展开:可以自定义分隔符号和执行顺序的展开方式,相比默认省略号展开更加强大。

默认成员函数

创建一个空的类/结构时 C++ 编译器会默认生成如下 9 个成员函数:

  • 构造函数

    • 默认构造函数
    • 默认拷贝构造函数
    • 默认移动构造函数(C++ 11)
    • 默认初始化列表构造函数(C++ 11)
  • 析构函数
    • 默认析构函数
  • 赋值运算符
    • 默认拷贝赋值运算符
    • 默认移动赋值运算符(C++ 11)
  • 取址运算符
    • 默认取址运算符
    • 默认常量取址运算符

失效条件

以下 5 个默认成员函数由于功能特殊,允许在特定环境下取消生成。

  • 默认构造函数:

    • 定义了任何构造函数。(可控)
    • 成员无法被默认构造。
  • 默认拷贝/移动构造函数:
    • 定义了拷贝或移动构造函数中的任何一个。(可控)
    • 成员或父类无法被拷贝 / 移动构造。
  • 默认拷贝/移动赋值运算符:
    • 定义了拷贝或移动赋值运算符中的任何一个。(可控)
    • 成员是引用类型。(可控)
    • 成员或父类无法被拷贝 / 移动赋值。

生成逻辑

  • 默认构造函数

    一个无参,对成员使用默认值或其默认构造函数进行初始化的构造函数。

  • 默认列表初始化构造函数

    实现基于大括号初始化功能,能更方便的构造对象。

    • 没有定义显式的构造函数时

      • 生成一个与成员对应并自带默认参数的初始化列表构造函数。
      • 有父类时,首个参数用于初始化父物体。
    • 有定义显式的构造函数时
      • 始终生成与对应构造函数参数一致的初始化列表构造函数。
  • 默认拷贝/移动构造函数

    自动对成员执行拷贝或移动操作,并调用父类的拷贝/移动构造函数来实现初始化。

  • 默认拷贝/移动赋值运算符

    自动对成员执行拷贝或移动操作,并调用父类的拷贝/移动赋值运算符来实现赋值。

  • 默认析构函数

    会自动对成员进行析构并调用父类析构函数。默认析构函数是强制性的,即使自定义了析构函数,默认析构函数依然会被随后调用。

预定义宏

  • __FILE__:当前代码所在文件的名称
  • __LINE__:当前代码所在位置的行数
  • __func__:当前代码所在函数的名称
  • __COUNTER__:一个计数器,每次调用返回数字加一

部分预定义宏可以用 C++20 的std::source_location代替。

内联

内联可以实现宏的效果,通过函数体替代函数调用来提高程序效率。内联对应的关键字是inline,但其真实作用并非是声明函数需要内联。实际上无论是否声明inline,函数都有可能内联。inline的真实用途是为了解决实现内联过程中导致的重定义问题。

  1. 要实现内联函数,其声明和定义必须放在同一个文件,否则肯定不会内联。
  2. 但定义放在头文件会导致引用该头文件的翻译单元重复包含该定义而导致重定义错误。
  3. 不过只要通过定义inline,便可让编译器特殊处理,将这些定义视作同一个而避免该错误。
  4. 重定义问题同样会出现在变量上,所以inline也可以对头文件定义的变量使用。
  5. 成员函数或变量隐式自带inline关键字,所以无需声明即可在头文件中直接定义。

二进制库

C++支持将部分代码单独编译成库文件,从而实现编译结果的复用。

库类型

静态库

静态库的本质就像是提前编译好的中间文件(.obj),而且确实有将中间文件当静态库来链接的办法,所以说静态库最终还是要合并到可执行文件的代码中的。在合并过程中编译器还会进行优化,自动剥离静态库中那些没有使用的代码部分。

动态库

动态库是一个可以独立使用的文件,编译过程中与可执行文件完全无关。所以为了实现动态链接,需要用户手动选择需要导出的符号,否则很多编辑器会默认全部导出,因此不会出现静态库那种部分代码被剥离的情况。

但要注意的是,在 MSVC 中可执行文件是通过一个静态库做中间人来连接动态库的,由于静态库会受剥离影响,因此相关的动态库可能会被整个无视而不被加载。

编译方法

MSVC 编译动态库的方法

https://stackoverflow.com/questions/225432/export-all-symbols-when-creating-a-dll/54067711

简单总结为三种方法:

  1. 手动给每个符号定义__declspec(dllexport)(太麻烦,而且代码不跨平台)
  2. 创建一个模块定义文件(.def)提供给 VS(麻烦,一样要自己整理导出函数名)
  3. 利用 CMake 的set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)功能,自动生成模块定义文件(无需额外操作,跨平台,呱呱好用)

处理库依赖

所有的库都只包含自身项目的源文件的编译结果,所以单独一个库是无法处理链式依赖问题的。若使用到的代码不直接涉及其他库的源文件,那不会有事,否则必须要手动指定被链式依赖的库文件。

全局变量初始化

当全局变量和可执行文件在同一个项目中构建时可以正常使用,但放在其他需要链接的项目中时就会出现问题。

  • 放在静态库

    静态库中的全局变量合并到可执行文件后,是和项目中的全局变量完全一样的,但问题是静态库合并过程中会进行优化,如果编译器检测认为这个静态库中的全局变量不会使用,那将被剥离,最终就无法触发该全局变量的初始化函数。

  • 放在动态库

    动态库本身不会剥离任何代码,所以当动态库加载时,其中的全局变量也是可以正常初始化的。但在 MSVC 中因为是通过静态库连接的动态库,所以当该静态库被完全剥离时,相关动态库也会被整个无视。另外若全局变量的初始化放在了头文件,则这两个库都将生成该全局变量,但地址不同。

参考资料

【软件开发】C++使用笔记的更多相关文章

  1. 学习笔记之三十年软件开发之路 - Things I Learnt The Hard Way (in 30 Years of Software Development)

    三十年软件开发之路 https://mp.weixin.qq.com/s/EgN-9bIHonRid1DM0csQDw https://blog.juliobiason.net/thoughts/th ...

  2. Code Complete 笔记—— 第二章 用隐喻来更充分理解软件开发

    在这章里面,提到的隐喻,类同于比喻(建模)的方法的去理解软件开发. 隐喻的优点在于其可预期的效果能被所有人所理解.不必要的沟通和误解也因此大为减低,学习与教授更为快速,实际上,隐喻是对概念进行内在化和 ...

  3. 软件开发学习笔记 <二>软件开发模型、Up、Rup、敏捷Up

    软件开发过程(process) 是一个将用户需求转化为软件系统所需要的活动的集合. 软件生命周期(SDLC,Software Devlopment Life Cycle) 软件从孕育.诞生.成长.成熟 ...

  4. 软件开发学习笔记 <一> UML

    UML http://www.uml-diagrams.org http://www.umlchina.com/index.htm 统一建模语言(UML)始于1997年的一个OMG(对象管理组织)标准 ...

  5. 《移山之道:VSTS软件开发指南》读书笔记

    这两天看了<移山之道:VSTS软件开发指南>,对团队软件开发又有了新的认识.也许对于我们这些软件开发的新手来说,最重要的是具体技术与应用框架,但读了这本书后我感觉到,实际团队项目中工具的使 ...

  6. 编程学习笔记(第三篇)面向对象技术高级课程:绪论-软件开发方法的演化与最新趋势(3)软件开发的现状、UML扩展

    一.软件开发的现状 软件领域正在发生一个巨变,特别是近几年来,软件领域正在发生翻天覆地的变化. 这一变化主要以这个云 + 端大数据, 这些是随着目前最先进的一些技术的产生而产生的. 随着这些新的技术以 ...

  7. 《敏捷软件开发-原则、方法与实践》-Robert C. Martin读书笔记(转)

    Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...

  8. INSPIRED启示录 读书笔记 - 第5章 产品管理与软件开发

    保持融洽的合作关系 形成合作关系的关键是双方承认彼此平等——任何一方不从属于另一方,产品经理负责定义正确的产品,开发团队负责正确地开发产品,双方相互依赖 产品经理要求开发团队完成任务,必须先取得他们的 ...

  9. 软件开发-MSF方法(《构建之法》读书笔记2)

    MSF-微软解决方案框架,是一套大型系统开发指南,它描述了如何用组队模型.过程模型和应用模型来开发Client/Server结构的应用程序,是在微软的工具和技术的基础上建立并开发分布式企业系统应用的参 ...

  10. c语言程序设计案例教程(第2版)笔记(五)-软件开发基础知识

    零散知识点: 软件的主要特征 软件是一种逻辑产品,而不是有型的物质: 软件需要设计.开发,但不是传统意义上的产品制造: 软件不会磨损,但软件需要维护,即:修改代码或增加模块: 虽然软件行业正在向基于组 ...

随机推荐

  1. openEuler创建和root一样的账号

    1. 使用以下命令在 openEuler 操作系统的 root 用户下创建管理员用户: useradd -m -G root admin -m 表示创建用户的同时创建用户的主目录, -G 表示将用户添 ...

  2. dockerfile实现tomcat以及java的war包自动部署

    1. 下载jdk和tomcat wget https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.93/bin/apache-tomcat-8.5.93.tar.g ...

  3. 在 K8S 中创建 Pod 是如何使用到 GPU 的: nvidia device plugin 源码分析

    本文主要分析了在 K8s 中创建一个 Pod 并申请 GPU 资源,最终该 Pod 时怎么能够使用 GPU 的,具体的实现原理,以及 device plugin.nvidia-container-to ...

  4. 【报错解决】【Vue】与后端交互时,http与https跨域问题

    问题 xhr.js:220 Mixed Content: The page at 'https://xxx' was loaded over HTTPS, but requested an insec ...

  5. Spring注解之-@ConditionalOnExpression表达式

    @ConditionalOnExpression("'true") 当括号中的内容为true时,使用该注解的类被实例化,支持语法如下: @ConditionalOnExpressi ...

  6. Qt编写的项目作品37-安卓综合应用示例

    一.功能特点 封装了通用的Qt安卓组件,打通了常规与java交互机制. 动态切换横屏竖屏及获取当前横屏竖屏状态. 支持手机震动.拨打电话.发送短信. 支持moketoast临时消息.notify顶部任 ...

  7. Qt编写地图综合应用52-加载离线地图

    一.前言 离线地图的加载其实和在线地图的加载方法几乎一样,唯一的最大区别就是,之前可能一个js文件引入即可,现在需要多个本地的js文件引入,而且网上流传的js文件的版本比较旧,意味着现在新版的支持op ...

  8. Qt音视频开发35-Onvif图片参数

    一.前言 视频中的图片的配置参数一般有亮度.饱和度.对比度.锐度等,以前一直以为这些需要通过厂家的私有协议SDK来设置才行,后面通过研究Onvif Device Manager 和 Onvif Dev ...

  9. elementPlus 问题总结

    第一次搞,遇上很多弱智问题,记录一下 安装elementPlus $ npm install element-plus --save 全局引入 import ElementPlus from 'ele ...

  10. error C2589: “(”:“::”右边的非法标记错误的处理

    问题:error C2589: "(":"::"右边的非法标记错误的处理 标准库在<algorithm>头中定义了两个模板函数std::min() ...