C++进阶

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.mfbz.cn/a/756832.html

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

相关文章

如何将Hive表的分区字段插入PG表对应的时间戳字段?

文章目录 1、背景描述2、场景分析 1、背景描述 数据仓库的建设通常是为业务和决策服务的。在数仓开发的应用层阶段&#xff0c;BI可以直接从主题层/业务层取数&#xff0c;而前端需要根据具体的作图需求通过后端查询数据库 作图的指标需要根据主题层/业务层做查询计算&#xf…

C#——SortedList 排序列表详情

SortedList 排序列表 SortedList 类用来表示键/值对的集合&#xff0c;这些键/值对按照键值进行排序&#xff0c;并且可以通过键或索引访问集合中的各个项。 我们可以将排序列表看作是数组和哈希表的组合&#xff0c;其中包含了可以使用键或索引访问各项的列表。如果您使用索…

文章分享 | seq-DAP-seq和ChIP-seq联合检测揭示花发育器官转录因子调控机制

技术简介 MADS转录因子的同源蛋白SEPALLATA3 (SEP3)和AGAMOUS (AG)是调控拟南芥花发育分化的重要DNA结合蛋白。在雌蕊发育阶段&#xff0c;SEP3和AG形成异源四聚体&#xff0c;通过识别CArG-box序列来调控基因的表达。Seq-DAP-seq技术与ChIP-seq联合使用&#xff0c;可用于分析…

无需向量量化的自回归图像生成

摘要 https://arxiv.org/pdf/2406.11838 传统观点认为&#xff0c;用于图像生成的自回归模型通常伴随着向量量化的标记。我们观察到&#xff0c;尽管离散值空间可以方便地表示分类分布&#xff0c;但它对于自回归建模来说并不是必需的。在这项工作中&#xff0c;我们提出使用扩…

Kafka入门-分区及压缩

一、生产者消息分区 Kafka的消息组织方式实际上是三级结构&#xff1a;主题-分区-消息。主题下的每条消息只会保存在某一个分区中&#xff0c;而不会在多个分区中被保存多份。 分区的作用就是提供负载均衡的能力&#xff0c;或者说对数据进行分区的主要原因&#xff0c;就是为…

spring和springboot的关系是什么?

大家好&#xff0c;我是网创有方的站长&#xff0c;今天给大家分享下spring和springboot的关系是什么&#xff1f; Spring和Spring Boot之间的关系可以归纳为以下几个方面&#xff1a; 技术基础和核心特性&#xff1a; Spring&#xff1a;是一个广泛应用的开源Java框架&#…

我们后端程序员不是操作MyBatis的CRUD Boy

大家好&#xff0c;我是南哥。 一个对Java程序员进阶成长颇有研究的人&#xff0c;今天我们接着新的一篇Java进阶指南。 为啥都戏称后端是CRUD Boy&#xff1f;难道就因为天天怼着数据库CRUD吗&#xff1f;要我说&#xff0c;是这个岗位的位置要的就是你CRUD&#xff0c;你不…

JeecgBoot中如何对敏感信息进行脱敏处理?

数据脱敏即将一些敏感信息通过加密、格式化等方式处理&#xff0c;展示给用户一个新的或是格式化后的信息&#xff0c;避免了敏感信息的暴露。 一、接口脱敏注解 针对接口数据实现脱敏加密&#xff0c;只加密&#xff0c;一般此方案用于数据加密展示。 1.1 注解介绍 注解作用域…

Qt信号槽的坑

1、重载的信号&#xff08;以QSpinBox为例&#xff09; 像是点击按钮之类的信号槽很好连接&#xff0c;这是因为它的信号没有重载&#xff0c;如果像SpinBox那样有重载信号的话&#xff08;Qt5.12的见下图&#xff0c;不过Qt5.15LTS开始就不再重载而是换信号名了&#xff09;&…

Linux常用命令大全(超详细!!!)

文章目录 1.Linux是什么1.1 关于Linux我们主要学习什么1.1 学习Linux常见命令的前置知识 2. Linux常见命令2.1 ls命令2.2 cd命令2.3 pwd命令2.4 touch命令2.5 cat命令2.6 echo命令2.7 vim命令2.8 mkdir 命令2.9 rm命令2.10 cp命令2.11 mv命令2.12 grep命令2.13 ps命令2.14 nets…

<Python><ffmpeg>基于python使用PyQt5构建GUI实例:音频格式转换程序(MP3/aac/wma/flac)(优化版2)

前言 本文是基于python语言使用pyqt5来构建的GUI,功能是使用ffmpeg来对音频文件进行格式转换,如mp3、aac、wma、flac等音乐格式。 UI示例: 环境配置 系统:windows 平台:visual studio code 语言:python 库:pyqt5、ffmpeg 概述 本文是建立在之前的博文的基础上的优化版…

(笔记)Error: qemu-virgl: Failed to download resource “qemu-virgl--test-image“解决方法

错误&#xff1a; > Downloading https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.2/FD12FLOPPY.zip curl: (22) The requested URL returned error: 404Error: qemu-virgl: Failed to download resource "qemu-virgl--test-image" D…

Grafana-11.0.0 在线部署教程

Grafana-11.0.0 在线部署教程 环境&#xff1a; 操作系统&#xff1a; ubuntugrafana版本&#xff1a; 11.0.0 &#xff08;建议不要按照最新版&#xff09;grafana要求的系统配置不高&#xff0c;建议直接部署在监控服务器上&#xff0c;比如zabbix服务器、prometheus服务器…

文华财经通达信同花顺期货通盘立方博易大师主图指标公式源码

买线:EMA(C,2); 卖线:EMA(SLOPE(C,21)*20C,42); BU:CROSS(买线,卖线); SEL:CROSS(卖线,买线); STICKLINE1(买线>卖线,LOW,MIN(O,C),0.1,1),COLORRED; STICKLINE1(买线>卖线,MAX(O,C),HIGH,0.1,1),COLORRED; STICKLINE(买线>卖线,CLOSE,OPEN,8,1),COLORRED; STI…

页面开发感想

页面开发 1、 前端预览 2、一些思路 2.1、首页自定义element-plus的走马灯 :deep(.el-carousel__arrow){border-radius: 0%;height: 10vh; }需要使用:deep(标签)才能修改样式 或者 ::v-deep 标签 2.2、整体设计思路 <template><div class"card" style&…

跟《经济学人》学英文:2024年6月22日这期 India’s electronics industry is surging

India’s electronics industry is surging Foreign and domestic firms are investing in local manufacturing surge:激增&#xff1b;急剧上升&#xff1b; 原文&#xff1a; To witness India’s growing role as a manufacturing hub, dodge Bangalore’s notorious t…

maven安装jar和pom到本地仓库

举例子我们要将 elastic-job-spring-boot-starter安装到本地的maven仓库&#xff0c;如下&#xff1a; <dependency><groupId>com.github.yinjihuan</groupId><artifactId>elastic-job-spring-boot-starter</artifactId><version>1.0.5&l…

使用腾讯云服务器从0搭建个人网站,超简单图文教程

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com整理使用腾讯云服务器建站教程&#xff0c;…

Java的IO体系

目录 1、Java的IO体系2、IO的常用方法3、Java中为什么要分为字节流和字符流4、File和RandomAccessFile5、Java对象的序列化和反序列化6、缓冲流7、Java 的IO流中涉及哪些设计模式 1、Java的IO体系 IO 即为 input 输入 和 output输出 Java的IO体系主要分为字节流和字符流两大类…

【51单片机】串口通信(发送与接收)

文章目录 前言串口通信简介串口通信的原理串口通信的作用串口编程的一些概念仿真图如何使用串口初始化串口串口模式波特率配置 发送与接收发送接收 示例代码 总结 前言 在嵌入式系统的开发中&#xff0c;串口通信是一种常见且重要的通信方式。它以其简单、稳定的特性在各种应用…