C++进阶

news2024/11/23 8:30:42

C++进阶

  • 一、细节
    • 1.cout与输出缓冲区
    • 2.constexpr
    • 3.NULL和nullptr是不同的类型
    • 4.关于inline
    • 5.函数杂合用法
    • 6.const char*、char const*、char * const
    • 7.进程地址空间,所谓静态区常量区不准
    • 8.位运算
    • 9.多态
      • 9.1 内存切片
      • 9.2 转型
      • 9.3 构造函数和析构函数里是静态绑定
      • 9.4 dynamic_cast的跨类转换与向下转换,以及利用typeid调试多态
      • 9.5 类的成员函数的函数指针语法
      • 9.6 类中的虚函数表以及内存破坏

一、细节

1.cout与输出缓冲区

输出缓冲区是内存中的一块区域,用于临时存储将要输出的数据。这种缓冲机制有助于提高性能,因为直接进行I/O操作(如写入控制台)通常比操作内存慢得多。

缓冲区的刷新(flush)是将缓冲区中的内容实际写到输出设备(如控制台)上的过程。在以下情况下,缓冲区会被刷新:

  1. 缓冲区满了。
  2. 程序正常结束时。
  3. 调用特定的刷新函数(如 flush)。
  4. 输出流遇到某些特殊字符(如 \nendl)。

\nendl

  • \n 是换行符,用于在输出中插入一个换行。它不会立即刷新缓冲区,只是将换行符添加到缓冲区中。
  • endl 是一个操控符,它不仅会在输出中插入一个换行,还会强制刷新缓冲区。

2.constexpr

constexpr 是C++11引入的关键字,用于在编译时计算常量表达式(constant expressions)(避免运行时进行计算,提高程序性能)。它可以用于变量、函数和构造函数,帮助提高程序的性能和安全性。

image-20240626140224668

image-20240626140403419

constexpr 函数是可以在编译时求值的函数,这要求函数的所有操作都必须是常量表达式,并且函数体内不能包含任何可能导致运行时执行的操作。constexpr 函数可以用于编译时和运行时计算。

image-20240626141045512

image-20240626141117732

constexpr 构造函数允许类对象在编译时创建。这要求类的所有成员变量都必须是常量表达式,且构造函数本身不能进行任何运行时操作。

image-20240626141339602

image-20240626141408372

3.NULL和nullptr是不同的类型

虽然用NULL和nullptr赋值的指针可以相互比较,但是他们是不同的类型,在C++中用nullptr来给指针赋初值,因为NULL实际是整型数0。

image-20240626150614086

image-20240626150939935

4.关于inline

首先看一个例子,假设有一个这样的头文件,有防卫式声明,被不同的源文件包含。

image-20240626163355155

image-20240626163404578

image-20240626163412875

编译显示func重定义。
image-20240626163437558

project1.cppproject2.cpp 分别包含 func.h 时,每个编译单元(translation unit)都会包含 func 函数的定义。因此,在链接阶段(linking phase),链接器会发现有多个相同的 func 定义,从而导致重定义错误。因此,我们都是将函数的声明放在头文件中,定义放在源文件中。

如果在函数前加inline呢?
image-20240626163556661

  • inline 关键字提示编译器将函数的定义插入到调用点,而不是生成一个单独的函数调用。尽管如此,编译器并不总是会内联展开函数,它只是将其作为一个建议。
  • 更重要的是,inline 允许在多个编译单元中定义相同的函数,而不会引起重定义错误。这是因为 C++ 允许内联函数在多个编译单元中有相同的定义,并会在链接时进行特殊处理以避免重定义错误。
  • **内联函数的定义必须放在头文件中。**这是因为内联函数需要在每个使用它的编译单元(translation unit)中可见,以便编译器能够在调用点进行展开或者至少知道该函数的定义。

5.函数杂合用法

  • 函数返回类型是void,表示函数不返回任何类型。但是我们可以让一个返回类型是void的函数作为另一个返回类型是void的函数的返回值。

image-20240626164500325

  • 函数返回引用的情况。

image-20240626174626286

返回局部变量的引用,往一个不属于你的地址写了数据,虽然编译成功并且运行了,但是如果程序有地方用到这块内存空间,就会造成程序崩溃。

但是编译器对引用做了特殊处理,如果返回类型是引用,但是我们用普通类型去接受函数返回值,那么函数返回的就是值而不是引用。此时就相当于用10这个值赋给了变量a,然后a又将自身的值修改为20。
image-20240626174921518

  • 如果一个函数我们不调用的话,可以只有声明,不需要实现。

6.const char*、char const*、char * const

image-20240626180943953

这也就引申出我们为什么会在某些函数形参中加入const修饰。

image-20240626181615525

顶层 const 和底层 const

  • 顶层 const(Top-level const):
    • 修饰指针本身,使指针本身为常量,即指针的地址不能改变。
    • 示例:int* const p 中的 const 是顶层 const,表示 p 是一个常量指针。
  • 底层 const(Low-level const):
    • 修饰指针指向的对象,使指针指向的对象为常量,即不能通过指针修改指向的数据。
    • 示例:const int* p 中的 const 是底层 const,表示 *p 是常量。

7.进程地址空间,所谓静态区常量区不准

OIP-C

  1. Text Segment(文本段或代码段):这部分内存存放的是程序的机器代码,即编译后的程序。这部分是只读的,主要是为了防止程序代码被意外修改。
  2. Data Segment(数据段):存放初始化的全局变量和静态变量。这部分内存在程序开始执行时由编译器初始化,并在程序运行期间持续存在。
  3. BSS Segment(未初始化数据段):用于存放程序中未初始化的全局变量和静态变量。在程序启动时,操作系统会将此段内存初始化为零。
  4. Memory Mapping Segment(内存映射段):这部分内存通常用于映射外设的内存空间或实现文件映射。共享库。
  5. Stack(栈):用于存放函数的局部变量、函数参数和返回地址等。每当调用一个函数时,其相关信息就会被推送到栈上,函数返回时信息又会被弹出。栈具有后进先出的特性。
  6. Kernel Space(内核空间):这部分内存是操作系统保留的,用于运行内核代码和处理器内核模式下的操作。用户程序通常不能直接访问内核空间。

image-20240629084130454

8.位运算

#include<bitset>

输出二进制内容:

image-20240629091423980

image-20240629111430166

image-20240629111452600

异或运算的特点:

异或运算(XOR),表示为 ^,有几个关键特性:

  1. 一个数与自身异或的结果是0,即 x ^ x = 0
  2. 一个数与0异或的结果是数本身,即 x ^ 0 = x
  3. 异或运算满足交换律和结合律,即 a ^ b = b ^ aa ^ (b ^ c) = (a ^ b) ^ c

image-20240629094132536

image-20240629094554171

第一步: a = a ^ b; 这一步中,我们将 ab 进行异或运算,并将结果存储在 a 中。现在 a 存储的是原始 ab 异或的结果,我们可以称之为 a'。此时,aab 的信息混合体。

第二步: b = a ^ b; 在这一步中,新的 a (即 a') 与原始的 b 进行异或运算。根据异或的性质,由于 a' 包含了原始 ab 的信息,再与 b 异或相当于 b 与自己的信息抵消,留下了原始的 a。因此,这一步的结果是将原始的 a 值赋给了 b

第三步: a = a ^ b; 最后一步,现在的 a (即 a') 与现在的 b(即原始的 a)进行异或运算。由于 a' 是原始的 ab 的异或结果,与原始的 a 异或会抵消 a 的部分,留下原始的 b。因此,这一步的结果是将原始的 b 值赋给了 a

9.多态

9.1 内存切片

在面向对象编程中,多态允许我们通过基类指针或引用来调用派生类的方法。内存切片(Object Slicing)是多态中的一个问题,它发生在一个派生类对象被赋值给一个基类对象时。

内存切片现象解释:

当派生类对象通过值(而非指针或引用)赋值给基类对象时,派生类对象的额外属性(即派生类特有的成员)会被“切掉”,只剩下基类部分的成员数据被复制到基类对象中。这种现象就称为“内存切片”。

为什么会发生内存切片:

这是因为当通过值赋值时,赋值操作符只能处理目标对象(基类对象)类型的数据大小由于派生类可能包含额外的成员变量或方法,这些无法通过基类对象来存储,因此在赋值过程中这部分额外的信息就会丢失。

内存切片可能导致以下问题:

  1. 数据丢失:派生类特有的数据成员在赋值过程中丢失,这可能导致程序逻辑错误。
  2. 多态失效:由于派生类的特定行为(通过重写的方法实现)被切掉,使用基类对象调用方法时,不能表现出预期的多态行为。

假设有一个基类 Base 和一个从 Base 派生的类 DerivedDerived 类添加了一些新的成员变量和方法。

class Base {
public:
    int base_var;
    virtual void print() { cout << "Base class" << endl; }
};

class Derived : public Base {
public:
    int derived_var;
    void print() override { cout << "Derived class" << endl; }
};

如果我们创建一个 Derived 类的对象,并试图将其赋值给一个 Base 类的对象,就会发生内存切片:

Derived d;
d.derived_var = 10;
Base b = d;  // 这里发生内存切片,derived_var 信息丢失
b.print();  // 输出 "Base class"

在这个例子中,derived_var 的值不会存在于 b 中,因为 b 只是一个 Base 类型的对象,不包含 Derived 类的任何额外信息。

如何避免内存切片?

  • 使用指针或引用来维护多态行为,这样可以保证对象的完整性不被破坏。
  • 避免将派生类对象赋值给基类对象。

9.2 转型

向上转型 (Upcasting):

向上转型是将派生类的指针或引用转换为基类的指针或引用。这种转换是安全的,因为每个派生类对象都是一个基类对象,所以基类的部分是派生类对象的一部分。这种转换通常在多态中自动进行。

向下转型 (Downcasting):

向下转型是将基类的指针或引用转换为派生类的指针或引用。这种转换是不安全的,因为不是所有基类对象都是派生类对象。向下转型常常需要程序员确保这种转换是合理的,并且通常需要使用强制类型转换,如 dynamic_cast

9.3 构造函数和析构函数里是静态绑定

image-20240629150524290

构造函数总是静态绑定的。这意味着构造函数的调用不是基于对象的运行时类型,而是基于对象声明时的类型。构造函数的静态绑定确保了在创建派生类对象时,可以从基类到派生类正确地、按顺序初始化对象的各部分。

析构函数默认是静态绑定的,但如果将析构函数声明为虚函数,则会变成动态绑定。声明析构函数为虚函数是管理继承关系中的对象时的一个重要实践,因为它确保了当通过基类指针删除派生类对象时,对象的资源可以被适当地释放。

image-20240629151227909

autofunMainObject 类中的一个非虚成员函数,它内部调用了 func() 方法。

尽管 autofun() 定义在基类 MainObject 中,但是在它内部调用的 func() 方法是虚拟的(virtual)。这意味着调用的 func() 方法将会是对象 ob 运行时类型的版本,即 OtherObjectfunc()

由于 func() 方法在 MainObject 类中被声明为虚函数,因此 C++ 的动态绑定机制确保了在通过 ob.autofun() 调用时,实际上运行的是 OtherObjectfunc() 方法。即使调用发生在基类 MainObject 的上下文中,多态性质保证了派生类方法的调用。

9.4 dynamic_cast的跨类转换与向下转换,以及利用typeid调试多态

假设继承关系如下,我们想要从类Main转换到类Y

image-20240629161037713

image-20240629161404261

此时,跨类转换是成功的,因为y不是空指针。

image-20240629161525469

如果项目有很多的继承关系嵌套,那么我们跟踪调试的时候很难确定此时的指针究竟是哪一个类,因此可以用typeid打印类的信息,注意这时候指针需要解引用,否则等号右边需要比较Y*。

9.5 类的成员函数的函数指针语法

成员函数不同于普通函数,它需要一个对象上下文来访问成员变量和其他成员函数,这就是为什么需要使用特殊的语法来处理类成员函数指针(this指针)。

使用对象指针和成员函数指针调用。

image-20240629193125816

使用对象实例和成员函数指针调用。

image-20240629193147559

9.6 类中的虚函数表以及内存破坏

在C++中,虚函数是通过虚函数表(vtable)实现的,这是一个存储指向虚函数地址的指针数组。每个含有虚函数的类都有一个虚函数表。当类的对象被创建时,对象内部会包含一个指向其虚函数表的指针(通常称为vptr)。通过这种机制,当调用对象的虚函数时,实际上是通过虚函数表来动态确定要调用的函数。

我们可以看到,对于有虚函数的类,类的对象的大小多出了4个字节。

image-20240629200756174

这里我们用多态的方式调用一下虚函数,查看一下反汇编。

image-20240629201208486

image-20240629201358479

可以看见,对于第一个函数,这里用b指向的地址来进行函数调用。

image-20240629201625737

对于第二个函数,在第一个函数的地址上偏移了四位。

我们打印一下虚函数表的地址,以及表中前两个函数的地址。

image-20240629203023080

通过汇编查看一下地址是否正确。

image-20240629202547246

image-20240629202625511

image-20240629195221198

虚函数表的性质:

同一个类的多个实例都指向同一个虚函数表。

通过修改虚函数表的数据可以实现劫持。

只有通过指针访问函数才会调用虚函数表(多态的动态绑定)。

image-20240629203701709

image-20240629203744705

定义类的非指针对象,不涉及多态,没有虚函数表,因此调用是正常的。

…(img-3KUljj2O-1719664931428)]

[外链图片转存中…(img-XKAKTYTQ-1719664931428)]

虚函数表的性质:

同一个类的多个实例都指向同一个虚函数表。

通过修改虚函数表的数据可以实现劫持。

只有通过指针访问函数才会调用虚函数表(多态的动态绑定)。

[外链图片转存中…(img-q3WjviHA-1719664931428)]

[外链图片转存中…(img-sL7z2gm5-1719664931428)]

定义类的非指针对象,不涉及多态,没有虚函数表,因此调用是正常的。

image-20240629203920898

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

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

相关文章

石家庄高校大学智能制造实验室数字孪生可视化系统平台项目验收

智能制造作为未来制造业的发展方向&#xff0c;已成为各国竞相发展的重点领域。石家庄高校大学智能制造实验室积极响应国家发展战略&#xff0c;结合自身优势&#xff0c;决定引进数字孪生技术&#xff0c;构建一个集教学、科研、生产于一体的可视化系统平台。 数字孪生可视化…

隐藏Python运行产生的缓存文件(__pycache__)

不少同学使用VScode 提交或运行python代码的时候&#xff0c;出现一些缓存文件 类似于(__pycache__) 这种&#xff0c;对于我这种有一丢丢强迫症的人来说&#xff0c;运行一次就得删除一次&#xff0c;那有没有什么办法将其隐藏的&#xff1f; 在vscode编辑器中打开设置&#…

用易查分制作《假期安全承诺书》支持在线手写签名,一键导出打印

暑假将至&#xff0c;学校通常会下发假期安全承诺书让家长签署。 易查分可以实现网上下发安全承诺书通知&#xff0c;让学生家长进行签名确认&#xff0c;还可以导出PDF文件&#xff0c;方便打印一人一张的纸质版承诺书&#xff0c;下面就来教给大家如何使用吧&#xff01; 暑假…

搭建大型分布式服务(四十一)SpringBoot 整合多个kafka数据源-支持亿级消息生产者

系列文章目录 文章目录 系列文章目录前言一、本文要点二、开发环境三、原项目四、修改项目五、测试一下五、小结 前言 本插件稳定运行上百个kafka项目&#xff0c;每天处理上亿级的数据的精简小插件&#xff0c;快速上手。 <dependency><groupId>io.github.vipjo…

electron线上更新

一、安装electron-updater npm install --save electron-updater二、在main.js中引入使用 import { autoUpdater } from electron; if (!isDev) {const serverUrl https://your-update-server.com; // 自定义更新服务器地址或GitHub Releases地址autoUpdater.setFeedURL(${…

新奥集团校招面试经验分享、测评笔试题型分析

一、走进新奥集团 新奥集团成立于1989年&#xff0c;总部位于河北廊坊&#xff0c;是中国领先的清洁能源企业集团。业务涵盖城市燃气、能源化工、环保科技等多个领域&#xff0c;致力于构建现代能源体系&#xff0c;提升生活品质。 二、新奥集团校招面试经验分享 新奥集团的…

力扣第一道困难题《3. 无重复字符的最长子串》,c++

目录 方法一&#xff1a; 方法二&#xff1a; 方法三&#xff1a; 方法四&#xff1a; 没有讲解&#xff0c;但给出了优秀题解 本题链接&#xff1a;4. 寻找两个正序数组的中位数 - 力扣&#xff08;LeetCode&#xff09; 话不多说&#xff0c;我们直接开始进行本题的思路解…

Facebook的投流技巧有哪些?

相信大家都知道Facebook拥有着巨大的用户群体和高转化率&#xff0c;在国外社交推广中的影响不言而喻。但随着Facebook广告的竞争越来越激烈&#xff0c;在Facebook广告上获得高投资回报率也变得越来越困难。IPIDEA代理IP今天就教大家如何在Facebook上投放广告的技巧&#xff0…

使用stat()函数的例子

代码&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h>int main(void) {struct stat st;if(-1stat("test.txt",&st)){printf("获得文件状态失败\n");return -1;}printf(&q…

14 卡尔曼滤波及代码实现

文章目录 14 卡尔曼滤波及代码实现14.0 基本概念14.1 公式推导14.2 代码实现 14 卡尔曼滤波及代码实现 14.0 基本概念 卡尔曼滤波是一种利用线性系统状态方程&#xff0c;通过系统输入输出观测数据&#xff0c;对系统状态进行最优估计的算法。由于观测数据包括系统中的噪声和…

系统总结:AI产品经理知识体系

算法demo更偏向于命题作文&#xff0c;而在产品商业化的时候&#xff0c;关键的第一步就在于基于场景&#xff0c;去重新定义问题&#xff01; 近两年人工智能行业在国内外得到了爆发试的增长&#xff0c;各大巨头纷纷布局成立了自己的人工智能实验室和研究院&#xff0c;但是我…

已解决问题 | 该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的

在Chrome浏览器中&#xff0c;如果你看到“该扩展程序未列在 Chrome 网上应用店中&#xff0c;并可能是在您不知情的情况下添加的”这样的提示&#xff0c;通常是因为该扩展程序没有通过Chrome网上应用店进行安装。以下是解决这个问题的步骤&#xff1a; 解决办法&#xff1a;…

携程二面测开—中核

4.12 35min面试经验 自我介绍 在面试的开始&#xff0c;我简洁明了地进行了自我介绍&#xff0c;突出了我的教育背景、技能特长以及实习经历&#xff0c;为后续的面试内容打下了良好的基础。 实习的具体工作内容 在谈及实习经历时&#xff0c;我详细阐述了在实习期间所承担…

Sparse4D v1

Sparse4D: Multi-view 3D Object Detection with Sparse Spatial-Temporal Fusion 单位&#xff1a;地平线 GitHub&#xff1a;https://github.com/HorizonRobotics/Sparse4D 论文&#xff1a;https://arxiv.org/abs/2211.10581 时间&#xff1a;2022-11 找博主项目讨论方…

在Qt中,直接include <moc_xxxxx.cpp> 为什么不会出现符号冲突的错误?

在逛Qt官方社区的时候看到这样一个帖子&#xff1a; https://forum.qt.io/topic/117973/how-does-include-moc_-cpp-work 大概的意思是moc_xxx.cpp如果已经被编译器编译&#xff0c;那么在另一个cpp文件中include同一个moc_xxx.cpp应该出现符号冲突才对&#xff0c;但是Qt却能正…

音频Balance源码总结

音频Balance源码总结 何为音频Balance&#xff1f; 顾名思义&#xff0c;Balance及平衡&#xff0c;平衡也就是涉及多方&#xff0c;音频左右甚至四通道&#xff0c;调节所有通道的音量比&#xff0c;使用户在空间内听到各个通道的音频大小不一&#xff0c;好似置身于真实环境…

高考落幕,暑期西北行,甘肃美食等你来尝

高考结束&#xff0c;暑期来临&#xff0c;西北之旅成为许多人的热门选择。而来到甘肃&#xff0c;除了领略壮丽的自然风光和深厚的历史文化&#xff0c;甘肃特产和传统面点以其独特的风味和传统的制作工艺也为游客们带来了一场地道的甘肃美食体验。 平凉的美食&#x…

成立近30年,它如何找到政企采购突破点?

回看中国采购行业的发展&#xff0c;大致可以被分为四个阶段&#xff1a;上世纪90年代的传统采购时代、本世纪初的ERP采购时代、近10年的SRM采购时代以及2018年以来开启的数字化采购时代。近年来&#xff0c;大数据、人工智能和物联网的高速发展&#xff0c;为采购信息化提供底…

读书笔记-Java并发编程的艺术-第3章(Java内存模型)-第6节(final域的内存语义)

文章目录 3.6 final域的内存语义3.6.1 final 域的重排序规则3.6.2 写final 域的重排序规则3.6.3 读final 域的重排序规则3.6.4 final 域为引用类型3.6.5 为什么 final 引用不能从构造函数内“逸出”3.6.6 final 语义在处理器中的实现3.6.7 JSR-133 为什么要增强final 的语义 3.…

[知识点篇]《计算机组成原理》之计算机系统概述

1.1 计算机发展历程 世界上第一台电子数字计算机 1946年&#xff0c;ENIAC(Electronic Numerical Integrator And Computer)在美国宾夕法尼亚大学研制成功。性能低&#xff0c;耗费巨大&#xff0c;但却是科学史上的一次划时代的创新&#xff0c;奠定了电子计算机的基础&#x…