C++手记

总是还没得到就在想着失去

文章目录


前言

手记


一、register

据说现在已经不推荐且不用了。

(register 是 C++ 中的一个存储类说明符,但其使用在现代 C++ 中已经不推荐,并且在 C++17 及之后的标准中被废弃。以下是关于 register 的详细说明:)

但是由于遇到了,还是记一下

register 是 C 和 C++ 中的一个存储类说明符,用于提示编译器将变量存储在寄存器中,而不是在内存中,以提高访问速度。

这种提示通常用于对性能有较高要求的场合,例如循环中的计数变量。

  • 注意事项:
    提示性质: register 只是一个建议,编译器可以忽略这个建议,特别是寄存器资源有限时。
    不允许获取地址: 对 register 变量不能使用取地址运算符 &,因为寄存器没有内存地址。
    现代 C++ 的情况:

在 C++17 中,register 被正式移除,因为现代编译器已经非常优化,能够自动选择将哪些变量放入寄存器中,而不需要程序员的建议。

register int counter;

这段代码建议编译器将 counter 变量存储在寄存器中。

  • 现在,直接使用 register 会导致编译警告或错误,取决于编译器和编译标准的设置。但是我用vscode还是能跑应该与gcc的版本有关

二、constexpr

constexpr 是 C++ 中的一个关键字,用于定义常量表达式,主要目的是在编译时执行计算,提高性能和代码安全性。

constexpr 是 C++11 引入的关键字,用于指示编译器在编译时对表达式求值,而不是在运行时。

通过 constexpr,可以定义编译时常量、编译时函数,从而减少运行时的计算开销,提高程序的效率。

常量变量: 定义在编译时即可确定的常量。

常量函数: 定义的函数可以在编译时执行,返回一个常量值。

编译时计算: 使用 constexpr 可以使得一些复杂的计算在编译阶段完成,减少运行时的计算负担。

编译时执行: 使用 constexpr 声明的函数和变量,其值在编译时就必须是确定的。

必须是常量: constexpr 函数的参数和返回值都必须是常量表达式。

内联执行: constexpr 函数默认是 inline 的。

返回值要求: constexpr 函数的返回值必须是可以在编译时计算的表达式。

逐渐增强: 在 C++14 及之后,constexpr 函数的功能得到了增强,允许更复杂的逻辑,如条件语句和循环。

三、内联函数(inline)

内联函数的概念:

内联函数是指在编译过程中,编译器将函数的代码直接插入到每个调用点,而不是通过函数调用的方式(即压栈、跳转等)来执行。这种方式可以减少函数调用的开销。

inline 关键字:

使用 inline 关键字可以建议编译器将函数定义为内联函数。如下:

c++ 复制代码
inline int add(int a, int b) {
    return a + b;
}

减少了函数调用的开销,如函数调用栈的建立和销毁。

提高了代码执行效率,因为将代码直接嵌入可以避免多次的跳转和返回。

内联执行有助于 constexpr 函数在编译时被完全展开,确保编译时计算的执行。

内联行为并不会改变 constexpr 函数的主要特性,即在编译时求值的能力,但它也有助于优化运行时性能。

四、size_t

size_t 是 C 和 C++ 中一种用于表示对象大小或计数的无符号整数类型。

size_t 的全称是 size type,意为"尺寸类型"。

它是标准库中定义的一种无符号整数类型,用于表示对象的大小(如数组、字符串、内存块等)或数组的索引。

size_t 被用于表示对象的大小或内存块的尺寸。例如,malloc() 函数返回的指针需要传入一个 size_t 类型的参数,表示要分配的内存字节数。

它是无符号的,因此避免了负数索引的错误,更适合用于数组、字符串等的下标索引。

sizeof 运算符返回值的类型就是 size_t,用于表示内存大小。

size_t 的大小根据编译器和系统架构自动调整,确保可以在不同平台上正确表示对象的大小(例如 32 位系统上为 4 字节,64 位系统上为 8 字节)。

由于 size_t 是无符号类型,它能够避免与负值相关的错误,从而更安全地用于描述大小和计数。

size_t 的大小适配当前系统架构,确保能表示系统允许的最大内存大小,而不像固定宽度的整数类型那样受限。

size_t 是 ANSI C 和 C++ 标准定义的类型,使代码在不同平台上的表现一致且可移植。

五、std::

std:: 是 C++ 标准库的命名空间 std 的标识符,代表 standard(标准)。在 C++ 中,std 命名空间包含了所有 C++ 标准库的类、函数、变量和对象等。使用 std:: 前缀是为了访问这些标准库的功能:类、函数、变量等,确保代码使用的是标准库的实现,而不是用户自定义的同名实现,并且避免与用户自定义的代码发生命名冲突。

六、命名空间

命名空间 (namespace)

命名空间是 C++ 提供的用于组织代码的机制,主要目的是避免命名冲突。例如,不同的库中可能有同名的函数、类或变量,使用命名空间可以将这些名称隔离开来,防止冲突。

七、std::aligned_alloc

这是一个 C++ 标准库函数,用于分配一块指定对齐方式的内存。

void* std::aligned_alloc(std::size_t alignment, std::size_t size);

这段代码的主要目的是分配一段特定对齐方式的内存,而不仅仅是一般的动态内存分配。这种方式常用于需要高效访问内存的应用,如科学计算、图形处理、音视频处理等。

对齐内存能让 CPU 更高效地访问数据,因为对齐的内存块往往符合处理器的缓存线和内存访问优化策略。

所谓缓存线:

  • 处理器缓存 (Cache)
    缓存的作用:缓存是处理器内部的一种高速存储器,用于临时存储经常使用的数据和指令。其目的是减少处理器访问主内存的次数,从而提高程序执行速度。
    层级结构:缓存一般分为多级,如 L1(Level1)、L2、L3 缓存,L1 最靠近处理器核心,速度最快,但容量最小。L2 和 L3 离核心稍远,速度较慢但容量更大。
  • 缓存线 (Cache Line)
    定义:缓存是以"缓存线"为最小存储单位的。缓存线是一块连续的内存区域,大小通常是 32、64、128 字节(不同处理器可能不同)。
    加载机制:当处理器需要读取某个内存地址的数据时,它不会仅加载该单个字节,而是会把整个包含该地址的缓存线加载到缓存中。
    缓存行命中和未命中:
    命中 (Hit):数据已经在缓存中,可以快速访问。
    未命中 (Miss):数据不在缓存中,需要从主内存加载,导致更高的延迟。

回到正题 void* std::aligned_alloc(std::size_t alignment, std::size_t size);

alignment:指定对齐要求(以字节为单位),必须是 2 的幂。对齐方式决定了分配的内存地址是对齐值的整数倍,通常用于提升性能,尤其是在 SIMD 运算或需要严格内存对齐的硬件上。

alignment:指定所需的内存对齐方式,以字节为单位。对齐方式必须是 2 的幂,如 16、32、64 等。

size:要分配的内存大小(以字节为单位),必须是 alignment 的倍数。

八、void*

void* 是 C 和 C++ 中的一种指针类型,称为 通用指针 或 无类型指针。它可以指向任何类型的数据,但本身没有类型信息,因此不能直接用于解引用或指针运算。void* 的主要用途是实现通用性和灵活性,尤其在需要处理不同数据类型的场合,例如内存分配、通用数据结构等。

void* 是一种指针类型,表示指向未知类型的指针。

它不能直接解引用(即不能直接访问指向的数据),因为没有数据类型信息。

  • 通用性:void* 可用于实现与数据类型无关的操作。例如,标准库中的 malloc 函数返回 void*,允许它分配内存而不指定内存块的具体类型。
  • 数据转换:void* 可以通过类型转换(类型强制转换)转换为任何其他类型的指针。这使得它在需要与多种数据类型交互时非常有用。
  • 函数参数:用于函数参数时,void* 可以接受任意类型的指针,常用于通用接口设计,如回调函数的上下文指针。
  • 数据结构:例如,通用链表、哈希表等需要存储不同类型的数据时,常使用 void* 指针来实现多态。

使用 void* 的注意事项:

  • 安全性:由于 void* 没有类型信息,误用可能导致类型不匹配或指针错误,因此在使用时必须确保类型转换正确。
  • 类型转换:在使用 void* 之前,必须将它转换为正确的类型指针(如 int*、float*),否则无法正确操作数据。
  • 性能:void* 的使用不会自动进行类型检查,因此对类型安全性有更高的要求。在进行转换和操作时可能会引入运行时错误。

九、static_cast

是 C++ 提供的四种类型转换操作符之一(其他包括 dynamic_cast、const_cast 和 reinterpret_cast),主要用于在编译时执行类型转换。它提供了一种类型安全的转换方式,用于在相关类型之间进行显式转换。static_cast 可以进行多种类型转换操作,但不包括动态类型检查。

cpp 复制代码
static_cast<new_type>(expression)

new_type:要转换成的目标类型。

expression:要转换的表达式。

十、基类与派生类

  • 基类(Base Class)
    定义: 基类是一个被其他类继承的类。它通常包含一些共同的属性和方法,这些属性和方法可以被派生类共享。
    功能: 基类提供基本的功能和接口,供派生类扩展或重写。
c++ 复制代码
class BaseClassName {
    // 访问修饰符
public:
    // 公有成员,也可以只是变量
    void somePublicFunction();

protected:
    // 保护成员,也可以只是变量
    int someProtectedVariable();

private:
    // 私有成员,也可以只是变量
    void somePrivateFunction();
};

public: 公有成员可以被任何类访问。

protected: 保护成员只能被基类及其派生类访问。

private: 私有成员只能在基类内部访问。

  • 派生类(Derived Class)
    定义: 派生类是从基类继承的类。它可以继承基类的属性和方法,同时也可以添加新的属性和方法或重写基类的方法。
    功能: 派生类可以实现更具体的功能,扩展基类的能力。
c++ 复制代码
class DerivedClass : access_specifier BaseClass {
    // 派生类成员(属性和方法)
};

十一、函数类别

11.1 成员函数:

属于类的一部分,用于操作类的成员变量,是面向对象编程的基础

成员函数是定义在类中的函数,专门用于操作类的对象。它们可以访问和修改类的成员变量,提供对象的行为实现。

成员函数分为普通成员函数、构造函数、析构函数、虚函数、常成员函数和静态成员函数等。

11.2 全局函数:

在类外定义的函数,无法直接访问类的私有成员。

定义在类外的普通函数,不属于任何类。

全局函数可以在整个程序中访问,并且不能直接访问类的私有成员,但是可以间接通过调用存在的公有成员函数来访问

例子:

c++ 复制代码
#include <iostream>

class MyClass {
private:
    int privateValue;  // 私有成员变量,不能直接被全局函数访问

public:
    MyClass(int value) : privateValue(value) {}  // 构造函数,用于初始化私有成员

    // 公有成员函数,用于访问私有成员
    int getValue() const {
        return privateValue;
    }

    // 公有成员函数,用于设置私有成员
    void setValue(int value) {
        privateValue = value;
    }
};

// 全局函数
void displayValue(MyClass obj) {
    // 直接访问私有成员会导致编译错误
    // std::cout << obj.privateValue << std::endl;  // 错误:无法访问类的私有成员

    // 通过公有成员函数间接访问私有成员
    std::cout << "Value: " << obj.getValue() << std::endl;
}

int main() {
    MyClass myObj(10);  // 创建对象,并初始化私有成员
    displayValue(myObj);  // 调用全局函数,输出对象的私有成员值

    myObj.setValue(20);  // 修改私有成员值
    displayValue(myObj);  // 再次调用全局函数,输出修改后的值

    return 0;
}

11.3 构造函数和析构函数:

用于对象的创建和销毁,管理对象的生命周期。

构造函数是类的特殊成员函数,与类名相同,无返回类型 。用于在对象创建时初始化对象。

构造对象、分配资源、初始化成员变量。

析构函数是类的特殊成员函数,名称与类名相同但带有波浪号 ~,无参数且无返回值

在对象销毁时自动调用,用于清理资源,释放内存。

11.4 虚函数:

支持多态,通过基类指针或引用调用派生类的重写版本。

虚函数是基类中使用 virtual 关键字声明的成员函数,允许派生类重写,支持多态性

通过基类指针或引用调用派生类的重写版本。

11.5 内联函数:

用于减少小型函数的调用开销。

内联函数(Inline Function)

内联函数使用 inline 关键字定义,提示编译器将函数代码直接插入调用点,以减少函数调用开销。

提高小型、频繁调用函数的执行效率

11.6 常成员函数:

保证函数不会修改类的状态。

常成员函数在函数签名后加 const,表示该函数不会修改对象的成员变量。

保证函数不修改类的状态,确保安全性

11.7 静态成员函数:

不依赖于对象的成员函数,直接通过类名调用。

静态成员函数使用 static 关键字声明,不依赖于对象,可以直接通过类名调用

不访问对象的成员,只能访问静态成员变量。

11.8 友元函数:

可以访问类的私有成员,但不是类的一部分。

友元函数使用 friend 关键字声明,不属于类,但可以访问类的私有成员。

在类外实现功能,同时访问类的私有成员。

11.9 友元函数与虚函数

c++ 复制代码
#include <iostream>

class Base {
private:
    int value;

public:
    Base(int v) : value(v) {}

    // 声明虚函数,允许派生类重写
    virtual void display() {
        std::cout << "Base class display: " << value << std::endl;
    }

    // 声明友元函数
    friend void showFriend(Base& obj);
};

// 实现友元函数,不能参与重写
void showFriend(Base& obj) {
    std::cout << "Friend function accessing Base class: " << obj.value << std::endl;
}

class Derived : public Base {
public:
    Derived(int v) : Base(v) {}

    // 重写基类的虚函数
    void display() override {
        std::cout << "Derived class display" << std::endl;
    }
    
    // 注意:友元函数不能在派生类中重写
    // void showFriend(Derived& obj) { ... } // 这是一个独立的函数,无法作为重写
};

int main() {
    Base* b = new Derived(10);

    // 调用派生类重写的虚函数,表现多态性
    b->display();

    // 调用友元函数,无法表现多态性,也不能重写
    showFriend(*b);

    delete b;
    return 0;
}

虚函数的重写:

display() 是一个虚函数,在基类中声明为虚函数,在派生类 Derived 中被重写。当通过基类指针 b->display() 调用时,程序会根据对象的实际类型调用 Derived 类的版本,实现了多态。

友元函数的调用:

showFriend(Base& obj) 是一个友元函数,定义在类的外部,具有访问 Base 私有成员的权限。

showFriend 不能被派生类重写,因为它不属于 Base 类,也不属于 Derived 类。即使在派生类中定义一个同名函数,它们也是完全独立的函数,不涉及重写关系。

友元函数不能被派生类重写,因为它们不属于类成员,而是类外部的普通函数。

虚函数是类的成员函数,允许通过继承机制被派生类重写,实现多态性。

友元函数的主要作用是访问类的私有和保护成员,而虚函数则是用于支持多态和动态绑定的机制,两者在概念和用途上有本质区别。

十二、成员初始化列表

: privateData(p1), protectedData(p2) 是成员初始化列表。它在构造函数的函数体 {} 之前,用来初始化类的成员变量。

这种初始化方式直接在成员初始化列表中进行赋值,而不是在构造函数的函数体内赋值。

成员初始化列表的语法是 : member1(value1), member2(value2), ...,用于直接初始化类成员。

privateData(p1) 表示用参数 p1 初始化成员变量 privateData。

protectedData(p2) 表示用参数 p2 初始化成员变量 protectedData。

例子:

c++ 复制代码
#include <iostream>

class Base {
private:
    int privateData;       // 私有成员变量
protected:
    int protectedData;     // 保护成员变量

public:
    // 构造函数,使用成员初始化列表初始化成员变量
    Base(int p1, int p2) : privateData(p1), protectedData(p2) {
        std::cout << "Base constructor called." << std::endl;
    }

    // 显示成员变量的值
    void show() {
        std::cout << "Private data: " << privateData << std::endl;
        std::cout << "Protected data: " << protectedData << std::endl;
    }
};

int main() {
    // 创建 Base 类的对象,初始化成员变量
    Base obj(10, 20);
    obj.show();  // 输出成员变量的值
    return 0;
}

总结

友元和虚函数还要多看一下,有点混淆了多态等概念

相关推荐
可均可可5 分钟前
C++之OpenCV入门到提高004:Mat 对象的使用
c++·opencv·mat·imread·imwrite
今天我又学废了16 分钟前
Scala学习记录,List
学习
杨荧21 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
白子寰27 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
小芒果_0132 分钟前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
gkdpjj37 分钟前
C++优选算法十 哈希表
c++·算法·散列表
王俊山IT39 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。41 分钟前
c++多线程
java·开发语言
-Even-42 分钟前
【第六章】分支语句和逻辑运算符
c++·c++ primer plus
小政爱学习!43 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript