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.mzph.cn/news/863164.shtml

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

相关文章

DP:解决路径问题

文章目录 二维DP模型如何解决路径问题有关路径问题的几个问题1.不同路径2.不同路径Ⅱ3.下降路径最小和4.珠宝的最高价值5.地下城游戏 总结 二维DP模型 二维动态规划&#xff08;DP&#xff09;模型是一种通过引入两个维度的状态和转移方程来解决复杂问题的技术。它在许多优化和…

docker容器内为什么能解析宿主机的hosts文件

Docker容器可以通过特定的网络设置来解析宿主机的hosts文件&#xff0c;这是因为Docker容器在创建网络时&#xff0c;会自动将宿主机的DNS配置信息传递给容器。 当你启动一个Docker容器时&#xff0c;如果没有指定任何DNS相关的选项&#xff0c;Docker默认会使用宿主机的DNS配…

Hi3861 OpenHarmony嵌入式应用入门--LiteOS MessageQueue

CMSIS 2.0接口中的消息&#xff08;Message&#xff09;功能主要涉及到实时操作系统&#xff08;RTOS&#xff09;中的线程间通信。在CMSIS 2.0标准中&#xff0c;消息通常是通过消息队列&#xff08;MessageQueue&#xff09;来进行处理的&#xff0c;以实现不同线程之间的信息…

【机器学习300问】135、决策树算法ID3的局限性在哪儿?C4.5算法做出了怎样的改进?

ID3算法是一种用于创建决策树的机器学习算法&#xff0c;该算法基于信息论中的信息增益概念来选择最优属性进行划分。信息增益是原始数据集熵与划分后数据集熵的差值&#xff0c;熵越小表示数据集的纯度越高。有关ID3算法的详细步骤和算法公式在我之前的文章中谈到&#xff0c;…

探索 Electron:将 Web 技术带入桌面应用

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

VMware Workstation 安装 Centos 虚拟机

1. 下载 VMware Workstation 直接上网找官网下载即可 2. 下载 Centos 镜像 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 3.打开 VMware 创建虚拟机 3.1点击创建虚拟机 3.2 选择自定义安装 3.3 选择使用 Workstation 的版本 版本越高兼容性越低但性能越好&#xff0c;一…

智慧校园-实训管理系统总体概述

智慧校园实训管理系统&#xff0c;专为满足高等教育与职业教育的特定需求而设计&#xff0c;它代表了实训课程管理领域的一次数字化飞跃。此系统旨在通过革新实训的组织结构、执行流程及评估标准&#xff0c;来增强学生的实践操作技能和教师的授课效率&#xff0c;为社会输送具…

数据结构-分析期末选择题考点(图)

我是梦中传彩笔 欲书花叶寄朝云 目录 图的常见考点&#xff08;一&#xff09;图的概念题 图的常见考点&#xff08;二&#xff09;图的邻接矩阵、邻接表 图的常见考点&#xff08;三&#xff09;拓扑排序 图的常见考点&#xff08;四&#xff09;关键路径 图的常见考点&#x…

c语言实现贪吃蛇小游戏

源码 /** * FileName: snakec* Author:PowerKing * Version&#xff1a;V1.0* Date:2024.6.28* Description: 贪吃蛇小游戏*/#include <curses.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h>/*贪吃蛇游戏 */#define UP 1…

S32K3 工具篇2:如何在S32DS中使用Segger JLINK下载

S32K3 工具篇2&#xff1a;如何在S32DS中使用Segger JLINK下载 一&#xff0c; S32DS中JLINK下载1.1 Segger JLINK 驱动1.2 S32DS JLINK驱动路径配置1.3 S32DS JLINK debug configuration1.4 S32DS JLINK debug S32K3板子结果 二&#xff0c; JLINK驱动实现S32K344代码下载2.1 …

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

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

005-GeoGebra基础篇-GeoGebra的点

新手刚开始操作GeoGebra的时候一般都会恨之入骨&#xff0c;因为有些操作不进行学习确实有些难以凭自己发现。 目录 一、点的基本操作1. 通过工具界面添加点2. 关于点的选择&#xff08;对象选择通用方法&#xff09;&#xff08;1&#xff09;选择工具法&#xff08;2&#xf…

Vue3使用jsbarcode生成条形码,以及循环生成条形码

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;我是前端菜鸟的自我修养&#xff01;今天给大家分享Vue3使用jsbarcode生成条形码&#xff0c;以及循环生成条形码&#xff0c;介绍了JsBarcode插件的详细使用方法&#xff0c;并提供具体代码帮助大家深入理解&#xff0c;彻…

【Docker】集群容器监控和统计 CAdvisor+lnfluxDB+Granfana的基本用法

集群容器监控和统计组合&#xff1a;CAdvisorlnfluxDBGranfana介绍 CAdvisor&#xff1a;数据收集lnfluxDB&#xff1a;数据存储Granfana&#xff1a;数据展示 ‘三剑客’ 安装 通过使用compose容器编排&#xff0c;进行安装。特定目录下新建文件docker-compose.yml文件&am…

日志分析-windows系统日志分析

日志分析-windows系统日志分析 使用事件查看器分析Windows系统日志 cmd命令 eventvwr 筛选 清除日志、注销并重新登陆&#xff0c;查看日志情况 Windows7和Windowserver2008R2的主机日志保存在C:\Windows\System32\winevt\Logs文件夹下&#xff0c;Security.evtx即为W…

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

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

[小试牛刀-习题练]《计算机组成原理》之计算机系统概述【详解过程】

【计算机系统概述】 1、【冯诺伊曼结构】计算机中数据采用二进制编码表示&#xff0c;其主要原因是&#xff08;D&#xff09; I、二进制运算规则简单II、制造两个稳态的物理器件较为容易III、便于逻辑门电路实现算术运算 A.仅I、Ⅱ B.仅I、Ⅲ C.仅Ⅱ、Ⅲ D. I、Ⅱ、Ⅲ I…

基于 Spring Boot 的健康咨询系统

1 项目介绍 1.1 摘要 本项目旨在通过构建一个对用户更加友好的健康咨询平台&#xff0c;帮助用户方便、快捷地获取专业并且准确的健康咨询服务&#xff0c;同时为医疗机构提供一个高效易用的可以提供信息管理的服务平台。 项目采用了Spring Boot框架作为主要的开发平台。本系…

论文阅读_基于嵌入的Facebook搜索

英文名称&#xff1a;Embedding-based Retrieval in Facebook Search 中文名称&#xff1a;基于嵌入式检索的Facebook搜索 时间&#xff1a;Wed, 29 Jul 2020 (v2) 地址&#xff1a;https://arxiv.org/abs/2006.11632 作者&#xff1a;Jui-Ting Huang, Ashish Sharma, Shuying …

Postman设置请求间自动保存返回参数,方便后续请求调用,减少复制粘贴

postman中常常出现&#xff1a;有两个请求&#xff0c;一个请求首先获取验证码或者token&#xff0c;再由得到的验证码或token编写body发送另一个请求。如何设置两个请求间自动关联相关数据呢&#xff1f; 通过环境存储全局变量 现在有两个请求如下图&#xff0c;生成验证码是…