C++ 全栈知识体系C++ 全栈知识体系
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
  • 基础

    • C++ 基础 - 学习路线
    • C++ 基础 - 知识点
    • C++ 基础 - 面向对象
    • C++ 基础 - 语法糖
    • C++ 基础 - 关键字
    • C++ 基础 - 常用宏定义
    • C++ 基础 - 宏函数、内联函数、普通函数
  • 函数

    • C++ 函数 - 闭包
    • C++ 函数 - Linux系统调用Hook
    • C++ 函数 - getopt函数分析以及命令行解析
    • C++ 函数 - 函数指针及其应用
    • C++ 函数 - 作用域运算符::的使用
    • C++ 函数 - 智能指针shared_ptr的使用
    • C++ 函数 - struct结构体学习
    • C++ 函数 - typedef 语句的使用
    • C++ 函数 - va_list函数学习
  • 知识点

    • C++ 知识点 - 写时拷贝技术(copy-on-write)
    • C++ 知识点 - 前向声明(forward declaration)
    • C++ 知识点 - 头文件.h 和 源文件.cpp 以及多重定义(multiple definition)
    • C++ 知识点 - 为什么C语言不支持函数重载,C++又是如何支持的
    • C++ 知识点 - return 局部变量
    • C++ 知识点 - linux下c/cplusplus头文件和动态库的搜索
    • C++ 知识点 - 模板特化与偏特化
  • IO框架

    • C++ IO框架 - I/O 复用
    • C++ IO框架 - select 函数
    • C++ IO框架 - poll 函数
    • C++ IO框架 - epoll 函数
    • C++ IO框架 - Reactor 和 Proactor
  • 新版本特性

    • C++ 新版本特性 - C++11
    • C++ 新版本特性 - C++14
    • C++ 新版本特性 - C++17
    • C++ 新版本特性 - C++20

C++ 基础 - 关键字

    const

    作用

    1. 修饰变量,说明该变量不可以被改变;
    2. 修饰指针,分为指向常量的指针和指针常量;
    3. 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;
    4. 修饰成员函数,说明该成员函数内不能修改成员变量,除了mutable修饰的变量。

    使用

    详情
    // 类
    class A
    {
    public:
        A() : a(0) { };             // 构造函数
        A(int x) : a(x) { };        // 初始化列表
    
        // const可用于对重载函数的区分
        int getValue();             // 普通成员函数
        int getValue() const;       // 常成员函数,不得修改类中的任何数据成员的值
        const int& fun() const;     // 第一个const代表该函数的返回值无法被改变。第二个const代表该函数不会对调用者内部成员进行更改。
    private:
        const int a;                // 常对象成员,只能在初始化列表赋值
    };
    
    void fun()
    {
        // 对象
        A b;                        // 普通对象,可以调用全部成员函数、更新常成员变量
        const A a;                  // 常对象,只能调用常成员函数
        const A *p = &a;            // 常指针
        const A &q = a;             // 常引用
    
        // 指针
        char greeting[] = "Hello";
        char* p1 = greeting;                // 指针变量,指向字符数组变量
        const char* p2 = greeting;          // 指针变量,指向字符数组常量
        char* const p3 = greeting;          // 常指针,指向字符数组变量
        const char* const p4 = greeting;    // 常指针,指向字符数组常量
    }
    
    // 函数
    void fun1(const int Var);           // 传递过来的参数在函数内不可变
    void fun2(const char* Var);         // 参数指针所指内容为常量
    void fun3(char* const Var);         // 参数指针为常指针
    void fun4(const int& Var);          // 引用参数在函数内为常量
    
    // 函数返回值
    const int fun5();      // 返回一个常数
    const int* fun6();     // 返回一个指向常量的指针变量,使用:const int *p = fun6();
    int* const fun7();     // 返回一个指向变量的常指针,使用:int* const p = fun7();
    
    //const修饰函数时的重载
    
    //实际上没有区别,因为函数调用的时候,存在形实结合的过程,所以不管有没有const都不会改变实参的值。所以不能通过编译,提示重定义。
    void fun(const int i);
    void fun(int i);
    
    //char *a 中a指向的是一个字符串变量,而const char *a指向的是一个字符串常量,所以当参数为字符串常量时,调用第二个函数,而当函数是字符串变量时,调用第一个函数。
    void fun(char *a);    
    void fun(const char *a);
    
    //这两个都是指向字符串变量,不同的是char *a是指针变量 而char *const a是指针常量。所以不能通过编译,提示重定义。
    void fun(char *a);  
    void fun(char * const a);  
    
    //原因是第一个i引用的是一个变量,而第二个i引用的是一个常量,两者是不一样的,类似于上面的指向变量的指针的指向常量的指针。
    void fun(int &i);  
    void fun(const int &i);   
    
    
    

    mutable

    用于在类的成员函数中修饰非静态成员变量,允许在常量成员函数中修改这些成员变量的值(不违反const限定符)。

    static

    1. 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
    2. 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定义为static。
    3. 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
    4. 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。

    volatile

    volatile 关键字可以用来提醒编译器使用 volatile 声明的变量随时有可能改变,因此编译器在代码编译时就不会对该变量进行某些激进的优化,故而编译生成的程序在每次存储或读取该变量时,都会直接从内存地址中读取数据。

    使用 volatile 关键字声明的变量具有三种特性:

    • 易变的: 假设有读、写两条语句,依次对同一个 volatile 变量进行操作,那么后一条的读操作不会直接使用前一条的写操作对应的 volatile 变量的寄存器内容,而是重新从内存中读取该 volatile 变量的值。
    volatile int nNum = 0;  // 将nNum声明为volatile
    int nSum = 0;
    nNum = FunA();      // nNum被写入的新内容,其值会缓存在寄存器中
    nSum = nNum + 1;    // 此处会从内存(而非寄存器)中读取nNum的值
    
    • 不可优化的: 不要对 volatile 声明的变量进行各种激进的优化,从而保证程序员写在代码中的指令一定会被执行。
    volatile int nNum;  // 将nNum声明为volatile
    nNum = 1;
    printf("nNum is: %d", nNum);
    

    在上述代码中,如果变量 nNum 没有声明为 volatile 类型,则编译器在编译过程中就会对其进行优化,直接使用常量“1”进行替换(这样优化之后,生成的汇编代码很简介,执行时效率很高)。而当使用 volatile 进行声明后,编译器则不会对其进行优化,nNum 变量仍旧存在,编译器会将该变量从内存中取出,放入寄存器之中,然后再调用 printf() 函数进行打印。

    • 顺序执行的: 能够保证 volatile 变量间的顺序性不会被编译器进行乱序优化。由于 volatile 关键字的“顺序执行特性”并非会完全保证语句的顺序执行(如 volatile 变量与非 volatile 变量之间的操作;又如一些 CPU 也会对语句的执行顺序进行优化)。

    其他:

    • const 可以是 volatile (如只读的状态寄存器)
    • 指针可以是 volatile

    struct 和 class

    总的来说,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现体。

    区别

    • 默认的访问控制(最本质)

      • 默认的继承访问权限。struct 是 public 的,class 是 private 的。
      • struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。
    • 成员函数

      • 类可以包含成员函数,这些函数可以操作类的私有成员,并且可以实现类的行为和功能。
      • 结构体也可以有成员函数,但是它们的主要目的是为了实现一些操作,而不是定义类似于类的行为。
    • 继承

      • 类可以通过继承实现子类与父类之间的关系,可以使用公共、保护或私有继承来控制成员的访问权限。
      • 结构体也可以继承,但由于其成员默认是公共的,继承可能导致访问权限问题。
    • 构造函数和析构函数

      • 类可以拥有构造函数和析构函数,用于对象的初始化和清理。
      • 结构体也可以有构造函数和析构函数,但是它们的使用场景通常是比较简单的数据封装。

    注意:C++ class和 C struct 结合时,最好将C struct作为C++ class的成员,而不是通过继承。

    explicit(显式)关键字

    • explicit 修饰构造函数时,可以防止隐式转换和复制初始化
    • explicit 修饰转换函数时,可以防止隐式转换
    详情
    struct A
    {
        A(int) {}
        operator bool() const { return true; }
    };
    
    struct B
    {
        explicit B(int) {}
        explicit operator bool() const { return true; }
    };
    
    void doA(A a) {}
    
    void doB(B b) {}
    
    int main()
    {
        A a1(1);     // OK:直接初始化
        A a2 = 1;    // OK:复制初始化
        A a3{1};     // OK:直接列表初始化
        A a4 = {1};  // OK:复制列表初始化
        A a5 = (A)1; // OK:允许 static_cast 的显式转换
        doA(1);      // OK:允许从 int 到 A 的隐式转换
        if (a1);                            // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
        bool a6(a1);                   // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
        bool a7 = a1;                    // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
        bool a8 = static_cast<bool>(a1); // OK :static_cast 进行直接初始化
    
        B b1(1);     // OK:直接初始化
        B b2 = 1;    // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
        B b3{1};     // OK:直接列表初始化
        B b4 = {1};  // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
        B b5 = (B)1; // OK:允许 static_cast 的显式转换
        doB(1);      // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
        if (b1);                            // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
        bool b6(b1);                     // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
        bool b7 = b1;                    // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
        bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化
    
        return 0;
    }
    
    

    friend 友元类和友元函数

    • 能访问私有成员
    • 破坏封装性
    • 友元关系不可传递
    • 友元关系的单向性
    • 友元声明的形式及数量不受限制

    override、final

    • override 用于表示当前函数重写了基类的虚函数。
    • final 用于禁止类继承、禁止重载虚函数。
    class Base {
    public:
        virtual void g(); // 虚函数
        virtual void h() = 0; // 纯虚函数
    };
    
    class Derived : public Base {
        void g() override; // 表示派生类重写基类虚函数
        void h() final; // 表示不可再被派生类进一步重载
    };
    
    class Student final {// 表示类不能被继承
    };
    

    virtual

    用于声明虚基类、虚函数。虚函数=0时,则为纯虚函数,纯虚函数所在的类称为抽象类。

    const、constexpr

    • const 表示所修饰的对象或变量不能被改变。
    • constexpr 用于生成常量表达式,常量表达式主要是允许一些计算发生在编译时,而不是运行的时候。

    noexcept

    C++11中,用于声明一个函数不可以抛出任何异常。

    using

    • using声明一次之引入命名空间中的一个成员。
    • using指示使得某个特定的命名空间中所有的名字都可见。
    using std::cout;//using声明
    using std;//using指示
    

    头文件与using声明或指示

    头文件如果在其顶层作用域中含有using指示或using声明,则会将名字注入到所有包含了该头文件的文件中。通常情况下,头文件应该只负责定义接口部分的名字,而不定义实现部分的名字。因此,头文件最多只能在它的函数或命名空间内使用using指示或using声明。

    对命名空间内部名字的查找遵循常规的查找规则:由内向外依次查找每个外层作用域。外层作用域也可能是一个或多个嵌套的命名空间,直到最外层的全局命名空间查找过程终止。

    重载与using声明

    一个using声明包括了重载函数的所有版本以确保不违反命名空间的接口。

    using NS::print

    尽量少使用 using 指示 污染命名空间

    一般说来,使用 using 声明比使用 using 指示更安全,这是由于它只导入了指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。using指示导入所有的名称,包括可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。

    enum

    C++ 包含两种枚举:限定作用域和不限定作用域

    enum class open_modes {input,output,append};//限定作用域
    int i = open_modes::input;//限定作用域的枚举类型不会进行隐式转换
    
    enum color {red,yellow,green};//不限定作用域
    int j = color::red;//不限定作用域的枚举类型的枚举成员隐式转换成int
    

    typeid

    typeid 运算符用来获取一个表达式的类型信息。

    应用: 运行时类型识别(run-time type identification, RTTI)

    • typeid 运算符,用于返回表达式的类型
    • dynamic_cast 运算符,用基类指针或引用安全地转换成派生类的指针或引用

    类型含有虚函数时,运算符将使用指针或引用所绑定对象的动态类型。

    详情
    if(Derived *dp = dynamic_cast<Derived*>(bp)){
        // 使用dp指向的 Derived 对象
    }
    else{
        // 使用bp指向的 Base 对象
    }
    
    if(typeid(*bp) == typeid(*dp)){
        //bp 和 dp指向同一类型对象
    }
    
    if(typeid(*bp) == typeid(Derived)){
        //bp 实际指向Derived对象
    }
    
    if(typeid(bp) == typeid(Derived)){
        //永远不被指向,bp是指向Base的指针
    }
    
    

    应用: type_info类

    详情
    #include <iostream>
    #include <typeinfo>
    
    class Base {
    public:
        virtual void print() {}
    };
    
    class Derived : public Base {
    public:
        virtual void print() {}
    };
    
    int main() {
        Base* basePtr = new Derived();
        
        // 使用typeid运算符获取类型信息
        const std::type_info& type = typeid(*basePtr);
        
        // 输出类型的名称
        std::cout << "Type name: " << type.name() << std::endl;
        
        // 比较类型
        if (type == typeid(Base)) {
            std::cout << "Type is Base" << std::endl;
        } else if (type == typeid(Derived)) {
            std::cout << "Type is Derived" << std::endl;
        } else {
            std::cout << "Type is unknown" << std::endl;
        }
        
        delete basePtr;
        
        return 0;
    }
    
    

    结果

    Type name: class Derived
    Type is Derived
    

    extern "C"

    • 被 extern 限定的函数或变量是 extern 类型的
    • 被 extern "C" 修饰的变量和函数是按照 C 语言方式编译和链接的

    extern "C" 的作用是让 C++ 编译器将 extern "C" 声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和C语言库中的符号进行链接的问题。

    extern "C" 使用

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    void *memset(void *, int, size_t);
    
    #ifdef __cplusplus
    }
    #endif
    
    Last Updated:
    Contributors: klc407073648
    Prev
    C++ 基础 - 语法糖
    Next
    C++ 基础 - 常用宏定义