• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2024-06-21 18:11 Aet |  隐藏边栏 |   抢沙发 |  9 
文章评分 1 次,平均分 5.0 :

友元类

适用情况

  1. 紧密协作:
    1. 当两个类需要紧密协作,且需要共享内部实现细节时
    2. 例如,操作类需要访问数据类的内部数据来实现复杂的功能
  2. 封装复杂操作:
    1. 当某些复杂操作不能或不应成为数据类的成员函数时
    2. 通过友元类可以将这些操作封装在独立的类中,从而简化数据类的设计
  3. 提高性能:
    1. 在某些情况下,通过友元类直接访问内部数据可以提高操作的性能,而不是通过公共接口进行间接访问
  4. 实现特定的设计模式:
    1. 在某些设计模式中,例如访问者模式,友元类可以用来访问对象的私有数据,以实现特定的行为

示例

友元函数

适用情况

  1. 非成员函数需要访问类的内部数据:
    1. 当一个非成员函数需要访问类的私有或保护成员时,可以将其声明为友元函数
    2. 例如,独立的计算函数需要访问对象的内部数据
  2. 辅助功能:
    1. 当一个辅助函数需要对类进行特定操作,但不应该成为类的成员函数时
    2. 例如,用于调试或日志记录的函数
  3. 提高性能:
    1. 当需要直接访问内部数据以提高操作性能,而不是通过公共接口进行间接访问时
  4. 特定算法实现:
    1. 在某些算法实现中,友元函数可以用来访问和修改对象的内部状态,以实现高效的操作

示例

联合体

概述

  1. 联合体(union)是C++中的一种数据结构
    1. 它允许你在同一存储空间内存储不同类型的数据,但只能同时存储其中的一种类型
    2. 联合体的成员共享同一个内存空间,这意味着在任何时间点,联合体只能存储一个成员的值
  2. 联合体在某些特定场景下非常有用,特别是当你希望节省内存或需要处理多种类型的数据时

作用

  1. 节省内存:
    1. 由于联合体的所有成员共享同一块内存,它在需要存储不同类型的数据但同时只需要一个的情况下,可以节省内存空间
    2. 例如,处理多种数据格式的网络数据包或协议消息时,可以使用联合体来节省内存
  2. 多用途数据结构:
    1. 联合体可以用来创建多用途的数据结构,这些结构能够根据具体情况存储不同类型的数据
    2. 例如,一个联合体可以存储一个整数或一个浮点数,具体取决于上下文需求
  3. 简化数据管理:
    1. 当你处理的变量可能是几种不同类型之一时,联合体可以简化数据管理
    2. 通过使用联合体,你可以避免使用多种变量来处理相同的数据逻辑

示例

注意事项

  1. 只能存储一个值:
    1. 联合体在任意时间点只能存储一个值,因此你需要小心处理不同类型的数据
    2. 存储新值会覆盖之前存储的值
  2. 数据有效性:
    1. 在使用联合体时,必须清楚当前存储的数据类型,以避免读取错误的值
    2. 这通常通过额外的标志变量或类型标签来管理
  3. 构造函数和析构函数:
    1. 联合体的成员不能有构造函数或析构函数,因为联合体的所有成员共享同一块内存
    2. 如果需要在联合体中使用复杂类型,可以考虑使用带有联合体的结构体,并在结构体中管理这些类型的构造和析构

带有匿名联合体的结构体

内联函数

概述

  1. 内联函数(inline function)是C++中的一种函数
  2. 它允许编译器在调用函数时将函数的代码直接插入到调用点,从而避免函数调用的开销
    1. 使用内联函数可以提高程序的执行效率,特别是在频繁调用的小函数中

作用

  1. 消除函数调用开销:
    1. 在传统的函数调用中,程序需要执行一些额外的操作,如压栈、跳转和返回等
    2. 对于频繁调用的小函数,这些操作的开销可能会显著影响性能
    3. 内联函数通过将函数体直接插入调用点,消除了这些额外的操作
  2. 提高性能:
    1. 通过内联函数,程序可以在不牺牲可读性和模块化的情况下提高性能
    2. 特别是在时间敏感的应用中,如游戏编程、实时系统等,内联函数可以显著提高性能
  3. 编译器优化:
    1. 内联函数为编译器提供了更多的优化机会
    2. 编译器可以在插入内联函数代码后进行进一步的优化,如消除冗余代码、常量折叠等

内联与宏

  1. 内联与宏都可以实现代码替换,比较如下:
  2. 类型安全:
    1. 内联函数是类型安全的,而宏只是简单的文本替换,没有类型检查
  3. 调试信息:
    1. 内联函数可以生成调试信息,有助于调试,而宏展开后很难调试
  4. 作用域控制:
    1. 内联函数受作用域控制,而宏在整个翻译单元中有效,容易引起命名冲突

注意

  1. 适用于小函数:
    1. 内联函数适用于小且频繁调用的函数
    2. 对于大函数,内联会导致代码膨胀,从而可能降低程序的性能
  2. 编译器的决定权:
    1. inline 关键字只是对编译器的建议,编译器可以选择忽略内联请求
    2. 如果编译器认为内联不合适(例如函数体太大),它可以选择不内联该函数
  3. 递归函数:
    1. 递归函数不适合内联,因为每次调用都需要新的函数调用环境
    2. 尽管可以将递归函数声明为内联,但编译器通常不会内联递归函数
  4. 代码膨胀:
    1. 过度使用内联函数会导致代码膨胀,增加可执行文件的大小,并可能导致指令缓存未命中,从而降低程序性能

内联本质

  1. 内联函数的本质是在编译阶段,编译器将函数调用处用函数体的代码直接替换(展开),从而避免了函数调用的开销
  2. 这个过程发生在编译期间,具体步骤如下:
    1. 源代码解析:编译器首先解析源代码,识别出内联函数
    2. 内联展开:在生成汇编指令之前,编译器会将内联函数的代码插入到每一个调用点
      这意味着,函数体的代码会直接嵌入到调用函数的代码中,而不是进行常规的函数调用(即跳转和返回)

示例

  1. 全局内联

  1. 类内内联

static

概述

  1. 可以用来修饰变量和函数,具有不同的含义和作用

静态局部变量

  1. 用法:
    1. static 可以用来修饰局部变量,使其在函数调用之间保持其值不变
  2. 作用:
    1. 静态局部变量只在第一次调用函数时初始化,以后每次调用函数时都会保留上一次的值
  3. 生命周期:
    1. 静态局部变量的生命周期是整个程序运行期间,但它的作用域仅限于定义它的函数

静态成员变量

  1. 用法:
    1. static 可以用来修饰类的成员变量,使其成为静态成员变量
  2. 作用:
    1. 静态成员变量在所有对象之间共享,一个类的所有对象对该静态成员变量只有一个实例
  3. 生命周期:
    1. 静态成员变量在程序启动时初始化,并在程序结束时销毁
  4. 初始化:
    1. 静态成员变量需要在类外进行初始化

静态成员函数

  1. 用法:
    1. static 可以用来修饰类的成员函数,使其成为静态成员函数
  2. 作用:
    1. 静态成员函数可以在不创建对象的情况下调用,它们不能访问类的非静态成员变量和非静态成员函数,但可以访问静态成员变量和静态成员函数
  3. 调用:
    1. 静态成员函数可以通过类名直接调用

静态全局变量

  1. 用法:
    1. static 可以用来修饰全局变量,使其成为静态全局变量
  2. 作用:
    1. 静态全局变量的作用域仅限于定义它的文件,而不是整个程序。这有助于防止命名冲突
  3. 生命周期:
    1. 静态全局变量在程序启动时初始化,并在程序结束时销毁

静态全局函数(文件作用域)

  1. 用法:
    1. static 可以用来修饰全局函数,使其成为静态函数
  2. 作用:
    1. 静态函数的作用域仅限于定义它的文件,这有助于防止命名冲突
  3. 生命周期:
    1. 静态函数在程序启动时加载,并在程序结束时卸载

问题

  1. 线程入口函数里面的静态局部变量,这个静态局部变量会不会在多个线程中保存副本?
    1. 在C++中,静态局部变量在函数中只会有一个实例,并且在所有调用中共享这个实例
    2. 即使这个函数被多个线程调用,所有线程也会共享同一个静态局部变量
    3. 静态局部变量的生命周期在程序运行期间内是唯一的,不会为每个线程创建单独的副本

  1. 怎么让每个线程都保存副本?
    1. 如果需要在每个线程中有自己独立的一份数据,可以使用线程本地存储(Thread Local Storage, TLS)
    2. 在C++11及之后的标准中,你可以使用 thread_local 关键字来实现每个线程都有自己的独立变量实例

  1. thread_local和static可以同时使用吗
    1. 在C++中,你不能同时使用 thread_local 和 static 关键字来修饰同一个变量
    2. 这是因为 thread_local 已经隐含了静态存储期,即变量在程序启动时分配,并在程序结束时销毁,而每个线程有其独立的实例
    3. 因此,thread_local 本身已经包含了 static 的语义,不需要再加 static 关键字

指针

概述

  1. 指针的定义:
    1. 指针是一个变量,它的值是另一个变量的地址
  2. 指针的声明:
    1. 使用 * 号来声明指针类型。例如,int *p 表示一个指向整数类型的指针
  3. 取地址操作符 &:
    1. 用于获取变量的地址。例如,int a = 5; int *p = &a;
  4. 解引用操作符 *:
    1. 用于访问指针指向的地址处的值。例如,*p 可以访问 p 所指向的 a 的值

指针类型

  1. 指针类型与它所指向的数据类型相关,指针类型决定了解引用时的行为
    1. 即指针类型决定了解引用时读取的内存字节数以及如何解释这些字节
    2. 例如,int* 指针解引用时会读取4个字节(在大多数平台上),并将这些字节解释为一个整数
    3. 而 double* 指针解引用时会读取8个字节,并将这些字节解释为一个双精度浮点数

解引用时的行为

  1. 不同类型的指针解引用时的表现不同,本质上是由于编译器根据指针的类型,从目标地址读取和解释内存的方式不同
  2. 编译器根据指针的类型确定需要读取多少内存字节,并且如何解释这些字节的内容

示例

  1. charPtr 作为 char* 类型指针,解引用时读取1个字节并解释为一个字符
  2. intPtr 作为 int* 类型指针,解引用时读取4个字节并解释为一个整数
  3. doublePtr 作为 double* 类型指针,解引用时读取8个字节并解释为一个双精度浮点数
  4. 内存布局示意图:
    1. charPtr 解引用时读取 0x01
    2. intPtr 解引用时读取 0x01 0x02 0x03 0x04 并将其解释为一个整数(例如,小端序表示下的值为 0x04030201)
    3. doublePtr 解引用时读取所有8个字节,并将其解释为一个双精度浮点数

  1. 汇编级表现:
    1. 从汇编级别看,不同类型指针解引用时,编译器生成的指令会不同,以读取正确数量的字节并正确解释这些字节
    2. charPtr 解引用(读取1字节):mov al, [charPtr]
    3. intPtr 解引用(读取4字节):mov eax, [intPtr]
    4. doublePtr 解引用(读取8字节):movq xmm0, [doublePtr]
    5. 在x86架构的汇编语言中,al寄存器是ax寄存器的低8位部分,而ax寄存器又是eax寄存器的低16位部分
      在64位架构中,eax寄存器是rax寄存器的低32位部分

备注

  1. movq
    1. 这是一条汇编指令,用于将数据从内存移动到寄存器,或从寄存器移动到内存
    2. movq 中的 q 表示操作的是四字(quadword),即 64 位(8 字节)
  2. xmm0
    1. 这是一个 128 位的寄存器,用于存储浮点数或 SIMD(单指令多数据)操作中的数据
    2. 在这个上下文中,我们只使用了 xmm0 的低 64 位
  3. [doublePtr]
    1. 这是一个内存地址操作数,表示从 doublePtr 指针所指向的内存地址读取数据

浅拷贝和深拷贝

概述

  1. 在默认情况下,C++ 会提供一个浅拷贝的拷贝构造函数,但在需要深拷贝的情况下,需要用户自定义拷贝构造函数

对比

  1. 浅拷贝(Shallow Copy):
    1. 复制对象的所有成员变量,但不复制成员指针所指向的资源
    2. 结果是多个对象共享相同的资源
  2. 深拷贝(Deep Copy):
    1. 不仅复制对象的所有成员变量,还复制成员指针所指向的资源
    2. 每个对象都有自己的独立资源副本

默认拷贝构造函数

自定义拷贝构造函数(深拷贝)

深拷贝的其他细节

  1. 拷贝赋值运算符:
    1. 当需要对现有对象赋值时,应该同时实现拷贝赋值运算符,以确保深拷贝行为

  1. 移动语义:
    1. 在C++11及以后,可以实现移动构造函数和移动赋值运算符,以优化资源管理,避免不必要的拷贝操作

编译器怎么选择调用哪种构造函数

  1. 编译器会根据具体的上下文和传递的对象来决定使用哪种构造函数或赋值运算符
  2. 具体来说,当你用一个对象去创建一个新的对象时,调用的是拷贝构造函数还是移动构造函数,取决于传递的对象的类型(是左值还是右值)

构造函数选择规则

  1. 拷贝构造函数:
    1. 当传递的是一个左值对象(即具名对象或通过引用传递的对象)时,会调用拷贝构造函数
  2. 移动构造函数:
    1. 当传递的是一个右值对象(即临时对象或通过 std::move 显式转换为右值引用的对象)时,会调用移动构造函数

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享
繁