【C++20】关键字全解析(96)

文章目录

关键字

在这里插入图片描述

一、常用关键字

(一)数据类型相关关键字

  1. bool
    布尔类型,其取值仅有true(真)和false(假)两种情况,两者也皆是关键字。主要用于条件判断等逻辑场景中,例如:
    bool isTrue = true;
    if (isTrue) {
        std::cout << "条件为真" << std::endl;
    }
    
  2. char
    字符类型,用来表示单个字符,在内存中通常占用1个字节,可存储ASCII码表中的字符,像:char ch = 'A';,常用于文本处理相关操作。
  3. short
    短整型,一般情况下它所占用的字节数比int类型少,通常为2个字节,能表示的整数范围相对较小,适用于对内存占用要求较为严格且数值范围不大的场景,比如:short num = 10;
  4. int
    整数类型,是最常用的整型数据类型,在多数系统中占用4个字节,可表示一定范围内的整数,例如:int age = 25;,广泛应用于各种数值计算及变量定义场景。
  5. long
    长整型,可用于表示比int类型更大范围的整数,其具体的字节数在不同系统环境下可能有所差异,在32位系统中常为4个字节,在64位系统中可能是8个字节,例如:long bigNum = 1234567890L;(一般建议加上后缀L来明确表示长整型字面量)。
  6. float
    单精度浮点型,占用4个字节,能够表示带有小数部分的数值,但精度相对有限,常用于对精度要求不是特别高的浮点数场景,且在定义时需要添加后缀f以区分double类型,例如:float pi = 3.14f;
  7. double
    双精度浮点型,通常占用8个字节,相较于float能提供更高的精度,更适合用于科学计算、高精度数值处理等场景,例如:double result = 3.1415926;
  8. wchar_t
    宽字符类型,主要用于处理扩展的字符集,像存储中文字符等非ASCII字符,它的大小根据系统实现而定,在使用时需要添加L前缀来表示宽字符字面量,例如:wchar_t wideChar = L'好';
  9. char8_t
    用于表示8位字符类型,通常与UTF - 8编码配合使用,方便处理基于UTF - 8编码的字符数据,例如在处理网络传输中的文本信息(常采用UTF - 8编码)时可能会用到,像char8_t utf8Char = u8'中';(这里u8前缀表示UTF - 8编码的字符字面量)。
  10. char16_t
    用于表示16位字符类型,多与UTF - 16编码相关联,常用于存储和处理遵循UTF - 16编码规则的字符,例如:char16_t utf16Char = u'😀';u前缀表示UTF - 16编码的字符字面量)。
  11. char32_t
    用于表示32位字符类型,对应UTF - 32编码,适合在需要精确处理每个字符且按照UTF - 32编码方式的场景下使用,比如一些国际化文本处理库的内部实现中,例如:char32_t utf32Char = U'🌍';U前缀表示UTF - 32编码的字符字面量)。

(二)类型修饰相关关键字

  1. signed
    与整型类型结合使用时,表示该整型是有符号的,意味着它能够表示负数、零和正数,例如signed int num = -10;(实际上int类型默认就是有符号的,这里明确写上是为了强调语义)。在内存中,最高位用于表示符号位(0表示正数,1表示负数)。
  2. unsigned
    同样用于修饰整型类型,表明该类型是无符号的,此时它只能表示零和正数,所以能增大可表示的正数范围,常用于计数、表示内存地址等不可能出现负数的场景,例如:unsigned int count = 0;
  3. typedef
    用于给已有的数据类型定义一个新的别名,以此增强代码的可读性和可移植性。比如:typedef int MyInt;,定义之后就可以使用MyInt来替代int声明变量,像MyInt var = 5;,在复杂的代码结构或者跨平台开发中,合理使用typedef可以让代码更易于理解和维护。
  4. using
    在C++11之后引入,功能与typedef类似,也是用于类型别名的定义,但语法更加灵活。例如:using NewInt = int;,同样可以用NewInt来声明变量,而且在模板别名等复杂场景下使用起来更加方便,例如:using IntList = std::vector<int>;(定义了一个std::vector<int>的别名IntList)。
  5. const
    常量修饰符,用于声明常量,一旦被声明为const的变量,在其作用域内禁止修改其值,常用于定义那些在程序运行过程中不应该被改变的数据,例如:const int MAX_VALUE = 100;,这样MAX_VALUE的值就不能再被重新赋值了,编译器会对试图修改它的操作报错,有助于提高代码的安全性和可维护性。
  6. register
    表示请求编译器将变量存储在CPU寄存器中,目的是提高变量的访问速度,因为寄存器的读写速度比内存快很多。不过在现代编译器中,编译器通常会自动根据变量的使用频率等因素来决定是否将变量放入寄存器,所以这个关键字很多时候会被编译器忽略,例如:register int fastVar;,但在一些对性能极致优化且开发者对硬件架构比较了解的特定场景下,仍可能会使用它来给编译器提供优化提示。
  7. auto
    从C++11开始重新定义了其功能,用于让编译器自动推导变量的类型,能够极大地简化代码编写,尤其是在使用复杂的模板类型或者迭代器等场景下。例如:auto num = 10;,这里编译器会自动推导出num的类型为int;再比如对于std::vector<int>::iterator it = vec.begin();,可以写成auto it = vec.begin();,让代码更加简洁明了。

(三)程序控制相关关键字

  1. if
    最基本的条件判断语句,根据给定的条件表达式的真假来决定是否执行相应的代码块。条件表达式通常是一个能够返回布尔值(true或者false)的表达式,例如:
    int num = 5;
    if (num > 3) {
        std::cout << "num大于3" << std::endl;
    }
    
  2. else
    常与if语句配合使用,当if语句中的条件不满足(即条件表达式的值为false)时,就会执行else后面的代码块,用于提供另一种执行路径,例如:
    int num = 2;
    if (num > 3) {
        std::cout << "num大于3" << std::endl;
    } else {
        std::cout << "num小于等于3" << std::endl;
    }
    
  3. switch
    用于多分支的条件选择场景,它会根据一个表达式的值来跳转到相应的case分支去执行代码。表达式的值必须是整型、枚举类型或者是能隐式转换为整型的类型,例如:
    int day = 2;
    switch (day) {
        case 1:
            std::cout << "星期一" << std::endl;
            break;
        case 2:
            std::cout << "星期二" << std::endl;
            break;
        // 其他case分支
        default:
            std::cout << "无效的日期" << std::endl;
    }
    
  4. case
    switch语句中用于定义具体的分支情况,每个case后面跟着一个常量表达式,当switch语句中的表达式的值与之匹配时,就会执行该case下的代码,直到遇到break等跳转语句,例如上述switch语句中的各个case分支定义。
  5. default
    switch语句中用于定义默认分支,当switch表达式的值与所有的case常量表达式都不匹配时,就会执行default分支下的代码,用于处理一些意外或者未考虑到的情况,保证程序的逻辑完整性,如上述switch示例中的default部分。
  6. break
    用于跳出当前所在的循环(比如forwhiledo - while循环)或者switch语句,提前结束相应的执行流程,避免多余的循环迭代或者执行不必要的switch分支代码,例如在for循环中达到某个条件后跳出循环:
    for (int i = 0; i < 10; i++) {
        if (i == 5) {
            break;
        }
        std::cout << i << std::endl;
    }
    
  7. continue
    在循环语句(forwhiledo - while)中使用,它会跳过本次循环剩余的语句,直接进入下一次循环的条件判断环节,常用于在循环中排除某些不符合要求的值继续进行下一轮循环,例如在循环中跳过偶数:
    for (int i = 0; i < 10; i++) {
        if (i % 2 == 0) {
            continue;
        }
        std::cout << i << std::endl;
    }
    
  8. return
    用于从函数中返回值并结束函数的执行。如果函数的返回类型为void,则可以直接使用return;来结束函数;如果函数有具体的返回类型,那么就需要返回对应类型的值,例如:
    int add(int a, int b) {
        return a + b;
    }
    
  9. for
    一种常用的循环结构,通常适用于已知循环次数的情况,它的语法格式为for(初始化; 条件判断; 迭代更新),初始化部分用于定义循环变量并赋初值,条件判断部分决定是否继续执行循环体,迭代更新部分用于在每次循环结束后对循环变量进行更新操作,例如:
    for (int i = 0; i < 10; i++) {
        std::cout << i << std::endl;
    }
    
  10. while
    只要条件表达式为真,就会不断执行循环体中的代码,适用于事先不确定循环次数,而是根据某个条件来决定是否继续循环的场景,例如:
    int num = 0;
    while (num < 10) {
        std::cout << num << std::endl;
        num++;
    }
    
  11. do
    while类似,但它先执行一次循环体,再判断条件表达式,这样能保证循环体至少会被执行一次,格式为do { 循环体 } while(条件表达式);,例如:
    int num = 0;
    do {
        std::cout << num << std::endl;
        num++;
    } while (num < 10);
    
  12. goto
    可以跳转到程序中指定的标签位置,不过由于它会破坏程序的结构化流程,使得代码的可读性和可维护性变差,所以在实际编程中通常要尽量避免使用,例如:
    int num = 0;
    start:
    num++;
    if (num < 10) {
        goto start;
    }
    

(四)异常处理相关关键字

  1. try
    用于异常处理,标记可能抛出异常的代码块,在这个代码块内如果发生了异常,程序会立即停止当前代码的执行,转而去查找与之匹配的catch块来处理异常,例如:
    try {
        // 可能抛出异常的代码,比如动态内存分配失败、文件读取错误等情况
        int* ptr = new int[1000000000];
    } catch (...) {
        std::cerr << "发生异常" << std::endl;
    }
    
  2. catch
    try配套使用,用于捕获异常,并定义相应的处理逻辑。可以有多个catch块,每个catch块可以针对不同类型的异常进行处理,例如:
    try {
        throw std::runtime_error("运行时错误");
    } catch (const std::runtime_error& e) {
        std::cerr << "捕获到运行时错误: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "捕获到其他未知异常" << std::endl;
    }
    
  3. throw
    用于在程序中抛出异常,异常可以是基本数据类型、对象等各种类型,通过抛出异常来通知程序的其他部分出现了错误或者意外情况,需要进行相应处理,例如:throw 10;(抛出一个int类型的异常值)或者throw std::string("出现错误");(抛出一个std::string类型的异常对象)。

(五)函数相关关键字

  1. void
    常用于函数声明中,表示函数没有返回值,例如:void printHello();声明了一个名为printHello的函数,它执行完相应操作后不会返回任何数据给调用者;同时void还可以作为指针类型,表示通用的指针,能够指向任意类型的数据,例如:void* ptr;,不过在使用这种通用指针时,需要进行适当的类型转换才能正确操作所指向的数据。

  2. static
    当用于函数内部变量声明时,该变量在函数多次调用间会保留其值,具有静态生存期,它只会被初始化一次,例如:

    void func() {
        static int count = 0;
        count++;
        std::cout << count << std::endl;
    }
    

    每次调用func函数,count的值都会在上一次调用结束后的基础上继续变化。

    用于类的成员变量时,所有该类的对象共享这一变量,意味着无论创建了多少个类的对象,这个静态成员变量只有一份,在类的不同对象之间是共享的,例如:

    class MyClass {
    public:
        static int count;
    };
    int MyClass::count = 0;
    

    用于类的成员函数时,表示该函数不依赖于具体的对象实例,可以通过类名直接调用,常用于实现一些与类的整体状态相关而不依赖于具体对象个体的操作,例如:

    class MyClass {
    public:
        static void printCount() {
            std::cout << count << std::endl;
        }
    private:
        static int count;
    };
    int MyClass::count = 0;
    
  3. new
    用于在堆上动态分配内存,返回所分配内存的指针,可以用来创建对象或者基本数据类型的数组等。例如:

    • 分配单个int类型的内存空间:int* ptr = new int;,之后可以通过*ptr来操作这块内存空间。
    • 分配包含10个int元素的数组内存空间:int* arr = new int[10];,然后可以像操作普通数组一样使用arr[i]i取值范围为0到9)来访问和修改数组元素。
  4. delete
    new配套使用,用于释放由new分配的内存空间,要注意正确匹配使用,对于单个对象用delete ptr;,对于数组要用delete[] arr;,否则可能导致内存泄漏等问题,例如:

    int* ptr = new int;
    *ptr = 10;
    delete ptr;
    
    int* arr = new int[5];
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    delete[] arr;
    
  5. sizeof
    用于获取数据类型或者变量所占用的字节数,是一个编译时确定的值,可用于了解不同数据类型在当前系统下的内存占用情况,或者计算数组、结构体等复合类型的大小,例如:std::cout << sizeof(int) << std::endl;会输出int类型在当前系统下占用的字节数;对于自定义结构体也可以获取其大小,比如:

    struct Point {
        int x;
        int y;
    };
    std::cout << sizeof(Point) << std::endl;
    
  6. extern
    表示变量或函数是在其他文件中定义的,常用于多文件项目中跨文件的变量或函数引用,通过extern声明可以让当前文件知道某个变量或函数在其他地方已经定义了,从而可以使用它们。
    例如在一个文件(比如 main.cpp)中声明:

    extern int globalVar;
    

    然后在另一个文件(比如 other.cpp)中进行定义 int globalVar = 10;,这样在 main.cpp 中就能使用这个在 other.cpp 中定义的全局变量了。对于函数也是类似的用法,比如在头文件中声明 extern void someFunction();,然后在对应的源文件中去定义这个函数具体的实现内容。

(六)面向对象相关关键字

  1. class
    用于声明类,类是面向对象编程的基础构建块,它将数据(成员变量)和操作这些数据的函数(成员函数)封装在一起,形成一个独立的抽象数据类型。例如:
    class Person {
    public:
        int age;
        void sayHello() {
            std::cout << "Hello!" << std::endl;
        }
    };
    
    通过创建类的对象(如 Person p;),就可以访问类中的成员变量(p.age)和调用成员函数(p.sayHello())来实现相应的功能,体现了面向对象中数据和操作的封装性。
  2. public
    访问控制符,表示类的成员(成员变量和成员函数)对外公开,即可以在类的外部通过对象直接访问,是类对外提供接口的常见方式,方便其他代码与该类进行交互,例如:
    class Person {
    public:
        int age;
        void sayHello() {
            std::cout << "Hello!" << std::endl;
        }
    };
    Person p;
    p.age = 25;
    p.sayHello();
    
  3. protected
    访问控制符,表示类的成员在类内可以访问,同时在派生类(子类)中也能访问,主要用于实现类的继承体系中的访问控制,能够让子类继承并访问父类中一些不希望对外完全公开的成员,例如:
    class Base {
    protected:
        int protectedData;
    };
    class Derived : public Base {
    public:
        void accessProtected() {
            protectedData = 10;
        }
    };
    
  4. private
    访问控制符,表示类的成员只能在类内访问,外界无法直接访问,用于隐藏类的内部实现细节,保证类的封装性,使得类内部的数据和操作可以按照设计的逻辑进行管理,避免外部随意修改而导致错误,例如:
    class BankAccount {
    private:
        double balance;
    public:
        void deposit(double amount) {
            balance += amount;
        }
    };
    
    外部代码不能直接访问 balance,只能通过类提供的 deposit 等公有函数来间接操作账户余额。
  5. explicit
    用于防止编译器进行隐式类型转换。当构造函数使用了 explicit 关键字修饰时,在进行初始化或者赋值操作时,编译器不会自动将其他类型转换为该类类型,除非使用显式的构造函数调用形式,例如:
    class MyClass {
    public:
        explicit MyClass(int num) {}
    };
    // 以下代码会报错,因为不能隐式转换
    // MyClass obj = 10;
    // 正确的做法是显式调用构造函数
    MyClass obj(10);
    
  6. friend
    声明友元函数或类,允许外部的函数或类访问本类的私有成员,打破了类的访问限制,常用于需要在类外部方便地访问类内部数据的特殊情况,比如实现一些操作符重载函数或者与其他类有紧密关联的功能时,例如:
    class Point {
    private:
        int x, y;
        friend void printPoint(const Point& p);
    };
    void printPoint(const Point& p) {
        std::cout << "(" << p.x << ", " << p.y << ")" << std::endl;
    }
    
    这里 printPoint 函数作为 Point 类的友元函数,可以访问 Point 类的私有成员 xy 来实现打印坐标的功能。
  7. operator
    用于重载运算符,使得自定义类型可以像基本数据类型一样使用相应的运算符进行操作,增强了自定义类型在表达式中的使用灵活性和直观性。比如重载 + 运算符实现两个自定义类对象的相加逻辑,示例:
    class Complex {
    public:
        double real, imag;
        Complex operator+(const Complex& other) {
            Complex result;
            result.real = real + other.real;
            result.imag = imag + other.imag;
            return result;
        }
    };
    
    这样就可以像 Complex c1, c2; Complex c3 = c1 + c2; 这样使用 + 运算符来操作 Complex 类对象了。
  8. virtual
    用于声明虚函数,支持多态性。在基类中声明为虚函数后,当通过基类指针或引用调用该虚函数时,会根据对象的实际类型来决定调用哪个派生类中重写的虚函数,实现了运行时的动态绑定,例如:
    class Shape {
    public:
        virtual void draw() {
            std::cout << "Drawing a shape" << std::endl;
        }
    };
    class Circle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a circle" << std::endl;
        }
    };
    Shape* shape = new Circle();
    shape->draw();  // 会调用 Circle 类中重写的 draw 函数
    
    虚函数可以被派生类通过 override 标识符标记为重写,这样编译器可以帮忙检查是否正确重写了虚函数(函数签名等是否匹配),增强代码的健壮性。当一个类中包含纯虚函数(形式为 virtual void func() = 0;)时,类变为抽象类,无法直接实例化,抽象类通常作为基类,用于定义派生类共有的接口规范。
  9. this
    指向当前对象的指针,在类的成员函数内部,this 指针自动指向调用该函数的那个对象本身,常用于区分成员变量和局部变量同名的情况,或者在返回当前对象时使用,例如:
    class Person {
    public:
        int age;
        Person& setAge(int a) {
            this->age = a;
            return *this;
        }
    };
    
    这里 this->age 明确表示访问的是类的成员变量 age,而不是可能存在的同名局部变量,并且通过 return *this 可以实现方法链调用(如 p.setAge(20).setAge(25);)。
  10. mutable
    允许在常成员函数(被 const 修饰的成员函数)中修改成员变量的值,通常用于那些虽然成员函数被声明为 const,但仍有一些内部状态需要改变的情况,例如:
    class MyClass {
    public:
        mutable int count;
        void print() const {
            count++;
            std::cout << count << std::endl;
        }
    };
    
    即使 print 函数是 const 的,也能对 mutable 修饰的 count 变量进行修改。
  11. namespace
    命名空间,用于组织代码和避免命名冲突。在大型项目中,不同的模块或者库可能会定义相同名称的类、函数等,通过使用命名空间可以将它们区分开来,例如:
    namespace Math {
        int add(int a, int b) {
            return a + b;
        }
    }
    namespace Graphics {
        class Point {
            //...
        };
    }
    
    使用时可以通过 Math::add(3, 5) 或者 using namespace Math; add(3, 5);(后者将 Math 命名空间下的名称引入当前作用域)等方式来调用相应的函数或访问类等。

(七)结构和枚举相关关键字

  1. union
    定义联合体,它允许在同一个内存空间存储不同类型的数据,但在某一时刻只能存储其中一种类型的数据,能节省内存空间,常用于一些对内存布局有特殊要求或者需要根据不同情况复用同一块内存的场景,例如:
    union Data {
        int num;
        char ch;
    };
    Data data;
    data.num = 10;
    // 此时若再给 data.ch 赋值,那么 data.num 的值就会被覆盖,因为它们共用同一块内存
    data.ch = 'A';
    
  2. enum
    定义枚举类型,表示一组具名的常量,使代码更具可读性,通过使用枚举可以用有意义的名称来代替一些魔法数字或者固定的常量值,例如:
    enum Color { RED, GREEN, BLUE };
    Color myColor = GREEN;
    switch (myColor) {
        case RED:
            std::cout << "红色" << std::endl;
            break;
        case GREEN:
            std::cout << "绿色" << std::endl;
            break;
        case BLUE:
            std::cout << "蓝色" << std::endl;
            break;
    }
    
  3. struct
    定义结构体,成员默认是公共的,常用于简单的数据结构组合,将相关的数据成员放在一起方便管理和传递,和 class 类似但在访问控制方面有默认差异,例如:
    struct Point {
        int x;
        int y;
    };
    Point p = {1, 2};
    std::cout << "坐标为: (" << p.x << ", " << p.y << ")" << std::endl;
    

(八)模板相关关键字

  1. template
    声明模板,支持泛型编程,使得可以编写与类型无关的代码,能够提高代码的复用性和通用性。例如函数模板可以用来创建适用于多种数据类型的函数,像:
    template <typename T>
    T add(T a, T b) {
        return a + b;
    }
    // 可以使用不同类型调用 add 函数
    int result1 = add(3, 5);
    double result2 = add(3.14, 2.71);
    
    模板还可以用于类的定义等场景,比如定义一个通用的链表类模板,根据不同的类型参数实例化出不同类型元素的链表。
  2. typename
    用于声明模板类型参数,表示后面的标识符是一个类型名,在某些复杂的模板定义中,用于明确指出依赖名称是类型,避免编译器产生歧义,例如:
    template <typename T>
    void func() {
        typename T::iterator it;
    }
    
    这里通过 typename 明确告诉编译器 T::iterator 是一个类型,以便正确编译代码,尤其在模板参数 T 所代表的类型有内部嵌套类型的情况下很关键。
  3. requires
    用于约束模板参数,指定模板函数或类的特定要求,通常用于概念(concepts)中,从 C++20 开始引入,它可以让模板代码更加安全和可读,通过定义清晰的类型要求来限制模板实例化的条件,例如:
    template <typename T>
    concept Addable = requires(T a, T b) {
        a + b;
    };
    template <typename T>
        requires Addable<T>
    T add(T a, T b) {
        return a + b;
    }
    
    这里定义了 Addable 概念,要求类型 T 能够进行 + 运算,只有满足这个要求的类型才能作为 add 函数模板的参数类型。
  4. concept
    C++20 引入的关键字,用于定义类型的要求或条件,配合 requires 一起使用,能够让模板编程在编译阶段就进行更严格的类型检查,避免一些不符合预期的模板实例化情况,使得代码的逻辑更加严谨,比如可以定义多个不同的概念来描述不同的类型特征,然后应用到各种模板函数和类模板中,就像上面 Addable 概念的例子一样。

(九)常量和类型相关关键字

  1. inline
    声明内联函数,提示编译器将函数代码插入到调用处,这样可以减少函数调用的开销(如压栈、跳转等操作),一般用于短小的、频繁调用的函数,不过最终是否真正内联还是由编译器根据实际情况决定,例如:
    inline int add(int a, int b) {
        return a + b;
    }
    
    在编译优化较好的情况下,调用 add 函数的地方可能就直接替换成函数体的代码了,提高了程序的执行效率。
  2. volatile
    用于表示变量可能会被外部因素(如硬件设备、多线程环境中的其他线程等)修改,防止编译器对该变量进行优化,保证每次访问该变量时都从内存中读取最新的值,常用于嵌入式开发、多线程编程中与共享变量相关等场景,例如:
    volatile int sensorValue;
    // 可能在其他地方(比如中断处理函数中)会修改 sensorValue 的值,
    // 编译器不会对其进行常规的优化,每次使用都会从内存获取
    while (sensorValue < 100) {
        // 进行相关处理
    }
    

二、不常用关键字

(一)类型转换相关关键字

  1. reinterpret_cast
    用于进行底层类型转换,通常用于不同类型指针之间或者指针与整数之间的转换,这种转换很危险,容易导致未定义行为,因为它不进行任何类型安全检查,完全依赖于程序员对底层内存布局的了解,应谨慎使用,例如:int* ptr = reinterpret_cast<int*>(0x12345678);(将一个地址值强制转换为 int 指针),或者在不同类型的指针转换上,如将 char* 转换为 int*(但要确保内存布局是符合预期的,否则会出现错误),像:
    char buffer[4] = {1, 2, 3, 4};
    int* intPtr = reinterpret_cast<int*>(buffer);
    // 这里虽然进行了转换,但后续对 intPtr 的操作要非常小心,因为可能不符合实际内存语义
    
  2. dynamic_cast
    主要用于在类的继承体系中进行安全的向下转型(从基类指针或引用转换为派生类指针或引用),运行时会进行类型检查,如果转换失败会返回 nullptr(指针情况)或抛出异常(引用情况),常用于在多态场景下,根据对象的实际类型来准确获取派生类对象指针或引用,例如:
    class Shape {
    public:
        virtual void draw() {}
    };
    class Circle : public Shape {
    public:
        void draw() override {}
    };
    Shape* shape = new Circle();
    Circle* circle = dynamic_cast<Circle*>(shape);
    if (circle) {
        circle->draw();
    } else {
        std::cout << "转换失败" << std::endl;
    }
    
  3. static_cast
    用于常规的类型转换,是编译器能在编译时确定合法性的类型转换,比如基本数据类型之间的转换(在合理范围内)、派生类指针到基类指针的向上转型等,相对比较安全,但也要遵循类型转换的基本规则,例如:double num = static_cast<double>(10);(将 int 转换为 double),或者对于类的继承关系中向上转型:
    class Base {};
    class Derived : public Base {};
    Derived d;
    Base* basePtr = static_cast<Base*>(&d);
    
  4. const_cast
    用于去除变量的 const 属性,不过只能用于修改那些原本不是真正不可变的数据(比如通过指针或引用指向的对象其本身在内存中是可变的情况),使用时需要小心避免违反 const 的语义,因为它可能破坏程序原本期望的常量性,例如:
    const int num = 10;
    int* ptr = const_cast<int*>(&num);
    *ptr = 20;  // 这种修改可能不符合设计初衷,要谨慎使用,并且如果 num 所在内存确实是不可变的(如在只读内存区域),会导致运行时错误
    

(二)编译时控制关键字

  1. alignas
    用于指定类型或变量的对齐要求,通过它可以强制要求数据在内存中的对齐方式,有助于提高内存访问效率,特别是在与硬件交互或者处理一些对内存布局敏感的结构体等情况时很有用,例如:

    struct alignas(16) AlignedStruct {
        int data;
    };
    // 这里 AlignedStruct 类型的对象在内存中会按照 16 字节对齐,
    // 编译器会根据这个要求进行内存布局安排
    
  2. alignof
    获取类型的对齐要求,用于了解某个数据类型在当前系统和编译器下自然的内存对齐方式,可用于检查或者在一些动态内存分配等场景中参考对齐情况,例如:

    std::cout << alignof(int) << std::endl;  // 输出 int 类型在当前环境下的对齐字节数
    std::cout << alignof(AlignedStruct) << std::endl;  // 输出上述自定义对齐结构体的对齐字节数
    
  3. export
    在C++20中用于模板的分离编译,但在许多编译器中尚未完全支持,其设计初衷是希望能够将模板的声明和定义分开放在不同的文件中,方便大型项目中对模板代码的组织和管理,然而由于实现的复杂性以及兼容性等问题,实际应用场景比较有限。理论上的用法示例如下:

    // header.h
    export template <typename T>
    void func(T t);
    
    // source.cpp
    #include "header.h"
    template <typename T>
    void func(T t) {
        // 函数具体实现
    }
    

    但目前很多时候还是倾向于采用传统的将模板定义直接放在头文件中的方式来确保在不同编译单元中能正确实例化模板。

  4. static_assert
    用于在编译时进行条件断言检查,如果条件不成立,编译器会生成错误,常用于模板编程中检查类型约束等情况,能帮助在编译阶段就发现代码中不符合预期要求的地方,避免问题遗留到运行时。例如:

    template <typename T>
    void func() {
        static_assert(sizeof(T) > 0, "Type must have a size");
    }
    

    这里要求模板参数类型 T 必须是有大小的,如果传入一个不符合要求的类型(比如一个不完整类型),编译时就会报错提示相应的错误信息,保证模板使用的正确性。

(三)C++11及以后引入的关键字

  1. nullptr
    表示空指针,从C++11开始引入,用于替代传统的 NULL(在C++中 NULL 实际上是 (void*)0,可能会带来一些类型不匹配等问题),nullptr 类型更安全、语义更明确,在进行指针赋值、比较等操作时能避免一些潜在的错误。例如:int* ptr = nullptr;,在函数参数传递、指针初始化等涉及指针为空的场景中都可以清晰准确地表示空指针状态,像:
    void func(int* ptr) {
        if (ptr == nullptr) {
            std::cout << "指针为空" << std::endl;
        } else {
            // 正常处理指针指向的数据
        }
    }
    
  2. noexcept
    声明函数不会抛出异常,编译器可以基于此进行一些优化,比如可能省略一些异常处理相关的代码生成,同时也能让代码的异常处理逻辑更清晰,明确告知调用者该函数不会抛出异常,例如:void func() noexcept;,如果在这个函数内部违反了 noexcept 声明抛出了异常,会导致程序调用 std::terminate 函数来终止程序执行,所以使用时要确保函数内部确实不会抛出异常,常用于一些对性能要求较高且异常情况可提前预知的函数定义中。
  3. thread_local
    声明线程局部存储变量,每个线程有独立的副本,互不干扰,常用于多线程编程场景下每个线程需要独立维护的数据,比如每个线程的唯一标识、线程私有的计数器等。例如:thread_local int threadId;,不同的线程在访问 threadId 时,各自操作的是属于自己线程的那一份独立副本,像:
    #include <iostream>
    #include <thread>
    
    thread_local int num = 0;
    
    void threadFunc() {
        num++;
        std::cout << "线程 " << std::this_thread::get_id() << " 中 num 的值为: " << num << std::endl;
    }
    
    int main() {
        std::thread t1(threadFunc);
        std::thread t2(threadFunc);
        t1.join();
        t2.join();
        return 0;
    }
    
    在此示例中,t1t2 两个线程分别有自己的 num 副本,各自的修改不会影响到其他线程中的值。
  4. constexpr
    声明编译时常量,常用于模板和常量表达式,从C++11引入,它要求所修饰的函数或者变量在编译时就能计算出确定的值,这样的函数或变量可以用于需要常量表达式的地方,例如数组大小的定义等。例如:
    constexpr int square(int x) {
        return x * x;
    }
    int arr[square(5)];  // 因为 square 是 constexpr 函数,所以可以在这里用于定义数组大小
    
    对于变量来说,constexpr 修饰的变量必须有编译时可确定的初始值,像 constexpr int num = 10;,并且其后续不能被修改,和普通 const 变量相比,更强调编译时就能求值的特性。
  5. decltype
    用于推断表达式的类型,从C++11引入,它可以根据给定的表达式在编译时推导出其类型,方便在一些复杂的类型定义或者模板编程中使用,例如:
    int x = 10;
    decltype(x) y = 20;  // 这里 y 的类型会被推导为 int,和 x 的类型一致
    
    在模板中也很有用,比如:
    template <typename T>
    void func(T t) {
        decltype(t) anotherVar;  // 根据传入的参数 t 的类型来定义 anotherVar 的类型
    }
    
  6. atomic
    C++11引入,用于处理原子操作,通常与 std::atomic 一起使用,在多线程环境下保证操作的不可分割性,确保数据的一致性和正确性,避免出现数据竞争等问题。例如:std::atomic<int> atomicVar;,可以对 atomicVar 进行原子的读、写、自增、自减等操作,像:
    #include <iostream>
    #include <thread>
    #include <atomic>
    
    std::atomic<int> atomicCounter(0);
    
    void incrementCounter() {
        for (int i = 0; i < 1000; i++) {
            atomicCounter++;
        }
    }
    
    int main() {
        std::thread t1(incrementCounter);
        std::thread t2(incrementCounter);
        t1.join();
        t2.join();
        std::cout << "原子计数器的值为: " << atomicCounter << std::endl;
        return 0;
    }
    
    这里通过 std::atomic 保证了多个线程对 atomicCounter 进行自增操作时的原子性,不会出现因并发访问导致的数据错误。
  7. consteval
    声明一个常量表达式函数,该函数必须在编译时立即求值的函数,从C++20引入,其参数必须是常量表达式,函数体中也只能包含常量表达式操作,例如:
    consteval int square(int x) {
        return x * x;
    }
    const int result = square(5);  // 只能用常量表达式作为参数调用 consteval 函数,并且编译时就会计算出结果
    
    如果试图用非常量表达式作为参数调用 consteval 函数,编译时就会报错,它进一步严格限制了函数求值的时机和使用方式,用于那些必须在编译阶段就能确定结果的场景。

(四)协程相关关键字(C++20)

  1. co_await
    用于协程,表示暂停执行,等待某个操作完成,是实现协程异步操作的关键关键字之一。它使得协程在执行到 co_await 语句时可以让出执行权,暂停当前协程的执行,直到等待的操作完成后再继续执行后续代码,例如在进行异步网络请求或者异步文件读取等场景中,协程可以通过 co_await 等待这些操作结束,像:

    #include <iostream>
    #include <experimental/coroutine>
    
    struct Awaitable {
        bool await_ready() { return false; }
        void await_suspend(std::experimental::coroutine_handle<> h) {}
        void await_resume() {}
    };
    
    struct MyCoroutine {
        struct promise_type {
            MyCoroutine get_return_object() { return MyCoroutine(); }
            std::experimental::suspend_never initial_suspend() { return {}; }
            std::experimental::suspend_never final_suspend() { return {}; }
            void return_void() {}
            void unhandled_exception() {}
        };
    
        std::experimental::coroutine_handle<> coro;
    
        bool resume() {
            if (!coro.done()) {
                coro.resume();
                return true;
            }
            return false;
        }
    };
    
    MyCoroutine my_coro() {
        std::cout << "协程开始" << std::endl;
        co_await Awaitable();
        std::cout << "协程继续执行" << std::endl;
    }
    
    int main() {
        MyCoroutine coro = my_coro();
        coro.resume();
        coro.resume();
        return 0;
    }
    

    在这个简化示例中,协程执行到 co_await 处会暂停,通过手动调用 resume 函数来恢复执行,展示了 co_await 控制协程暂停和继续的基本用法。

  2. co_return
    用于协程,表示返回一个结果,并结束协程,它类似于普通函数中的 return 关键字,但专门用于协程中,用于向协程的调用者返回数据并终止协程的执行,例如:

    #include <iostream>
    #include <experimental/coroutine>
    
    struct ReturnValue {
        int value;
    };
    
    struct MyCoroutineWithReturn {
        struct promise_type {
            ReturnValue get_return_object() { return ReturnValue{0}; }
            std::experimental::suspend_never initial_suspend() { return {}; }
            std::experimental::suspend_never final_suspend() { return {}; }
            ReturnValue return_value(int v) { return ReturnValue{v}; }
            void return_void() {}
            void unhandled_exception() {}
        };
    
        std::experimental::coroutine_handle<> coro;
    
        ReturnValue resume() {
            if (!coro.done()) {
                coro.resume();
                return coro.promise().get_return_object();
            }
            return ReturnValue{0};
        }
    };
    
    MyCoroutineWithReturn my_coro_with_return() {
        std::cout << "协程开始" << std::endl;
        co_return ReturnValue{10};
    }
    
    int main() {
        MyCoroutineWithReturn coro = my_coro_with_return();
        ReturnValue result = coro.resume();
        std::cout << "协程返回值: " << result.value << std::endl;
        return 0;
    }
    

    此示例中协程通过 co_return 返回一个包含整数值的 ReturnValue 结构体,调用者可以获取到这个返回值。

  3. co_yield
    用于协程,表示暂时中断执行并返回某个值,稍后可以继续执行,它和 co_await 有些类似都能暂停协程,但 co_yield 更侧重于生成数据并暂停,常用于实现生成器类型的协程,例如可以用来按顺序生成一系列数值或者对象等,像:

    #include <iostream>
    #include <experimental/coroutine>
    
    struct Generator {
        struct promise_type {
            int current_value;
            Generator get_return_object() { return Generator(this); }
            std::experimental::suspend_always initial_suspend() { return {}; }
            std::experimental::suspend_always final_suspend() { return {}; }
            std::experimental::suspend_always yield_value(int value) {
                current_value = value;
                return {};
            }
            void return_void() {}
            void unhandled_exception() {}
        };
    
        std::experimental::coroutine_handle<> coro;
    
        bool move_next() {
            if (!coro.done()) {
                coro.resume();
                return true;
            }
            return false;
        }
    
        int current() { return coro.promise().current_value; }
    };
    
    Generator my_generator() {
        co_yield 1;
        co_yield 2;
        co_yield 3;
    }
    
    int main() {
        Generator gen = my_generator();
        while (gen.move_next()) {
            std::cout << gen.current() << std::endl;
        }
        return 0;
    }
    

    在这个示例中,通过 co_yield 协程依次生成了1、2、3这几个数值,外部循环通过不断调用 move_next 函数来推进协程执行并获取每次 co_yield 产生的值。

(五)其他关键字

  1. asm
    用于嵌入汇编代码,在一些对性能极致优化或者需要直接操作硬件底层的特殊场景下可能会用到,不过使用 asm 会降低代码的可移植性和可读性,因为汇编语言是与具体的硬件架构相关的。例如在某些嵌入式开发中,为了精准控制硬件寄存器等,可以嵌入汇编代码,像(以下是一个简单的示例,实际根据不同架构会有很大差异):

    asm("movl $1, %eax");  // 将立即数1移动到寄存器eax中,这只是一个示意,不同平台汇编语法不同
    
  2. bitandbitorcompland_eqor_eqxor_eq
    这些是按位操作符的关键字形式,分别对应于 &(按位与)、|(按位或)、~(按位取反)、&=(按位与赋值)、|=(按位或赋值)、^=(按位异或赋值)操作符,例如:

    int a = 5;  // 二进制表示为 0101
    int b = 3;  // 二进制表示为 0011
    int result = a bitand b;  // 等同于 int result = a & b; 结果为 0001,即十进制的1
    a and_eq b;  // 等同于 a &= b; 此时 a 的值变为 0001,即十进制的1
    

    使用这些关键字形式可以在某些特定的代码风格或者编程场景下,让代码看起来更具一致性或者符合特定的命名规范要求,但实际编程中按位操作符的符号形式使用更为普遍。

  3. andornot
    逻辑操作符的关键字形式,分别与 &&(逻辑与)、||(逻辑或)、!(逻辑非)等价,例如:

    bool a = true;
    bool b = false;
    if (a and b) {  // 等同于 if (a && b)
        // 这里不会执行,因为逻辑与结果为假
    }
    if (a or b) {  // 等同于 if (a || b)
        // 这里会执行,因为逻辑或结果为真
    }
    if (not b) {  // 等同于 if (!b)
        // 这里会执行,因为逻辑非后 b 的值变为真
    }
    

    这些关键字形式在某些特定的编程风格或者遵循特定代码规范要求时会被使用,不过在日常的C++编程中,符号形式(&&||!)的逻辑操作符应用更为广泛和常见,因为它们简洁且符合大多数开发者的习惯。

  4. xornot_eq
    分别表示按位异或和不等于操作符的关键字形式,xor对应于^(按位异或)操作符,not_eq对应于!=(不等于)操作符。例如:

    int num1 = 5;  // 二进制为 0101
    int num2 = 3;  // 二进制为 0011
    int result = num1 xor num2;  // 等同于 int result = num1 ^ num2,结果为 0110,即十进制的6
    
    int a = 5;
    int b = 3;
    if (a not_eq b) {  // 等同于 if (a!= b)
        std::cout << "a和b不相等" << std::endl;
    }
    

    同样,虽然有这些关键字形式存在,但在实际编写代码时,符号形式的操作符往往使用得更为普遍,它们更符合人们常规的编程认知和书写习惯。不过在一些代码风格统一要求或者特定的历史遗留代码等场景中,也可能会见到对这些关键字形式的运用。

  5. atomic_cancelatomic_commitatomic_noexcept
    原子操作相关关键字,它们与 atomic 关键字配合使用,用于更精细地控制原子操作的行为,不过使用场景相对较专业和特定,例如在一些复杂的并发控制逻辑中,当涉及到对原子操作的事务性处理(类似数据库中的事务概念,需要保证一组操作要么全部成功要么全部失败)时可能会用到这些关键字,具体的使用方式和语义依赖于具体的标准库实现以及应用场景,目前应用相对不是那么广泛。

  6. synchronized
    用于同步代码块,其作用类似于在多线程编程中使用互斥锁来保证在同一时刻只有一个线程能访问被 synchronized 修饰的代码块或方法,但在C++中,通常使用 std::mutex 等机制来实现同步,synchronized 在C++中的使用并不像在Java等语言中那样普遍,不过在一些特定的库或者框架中可能会提供类似的基于这个关键字的同步功能实现,用于确保多线程环境下数据的一致性和操作的原子性,例如(假设存在这样的实现):

    synchronized {
        // 这里是需要同步访问的代码块,同一时刻只有一个线程能进入执行
    }
    
  7. typeid
    用于获取对象的类型信息,通常与 std::type_info 类一起使用,可以用来判断对象的实际类型,在运行时进行类型识别等操作,不过要注意它是基于对象的动态类型(即实际的对象类型,在多态情况下可能和指针或引用的声明类型不同)来获取信息的。例如:

    #include <iostream>
    #include <typeinfo>
    class Base {};
    class Derived : public Base {};
    
    int main() {
        Derived d;
        const std::type_info& t = typeid(d);
        std::cout << t.name() << std::endl;
        Base* b = &d;
        const std::type_info& t2 = typeid(*b);
        std::cout << t2.name() << std::endl;
        return 0;
    }
    

    在这个示例中,通过 typeid 分别获取了 Derived 类对象本身以及通过基类指针指向 Derived 类对象时的类型信息,输出的结果通常是与编译器相关的类型名称表示形式(不同编译器可能有不同的具体输出内容)。

总之,C++的这96个关键字各有其独特的用途和语法规则,熟练掌握它们对于深入理解和高效运用C++语言进行程序开发起着至关重要的作用,无论是构建简单的控制台应用,还是复杂的大型系统、游戏开发、嵌入式项目等不同领域的编程工作,都离不开对这些关键字的准确运用以及它们之间相互配合所形成的语言逻辑和功能实现机制。 同时,随着C++标准的不断演进,新的关键字也在不断被引入,开发者需要持续学习来适应语言的发展,更好地发挥C++语言强大的编程能力。

课程目标本课程详细讲解了C++的所有关键字,包括C++11和C++20新增的关键字和语言特性适用人群本课程并非零基础,适合有C语言基础,尤其是学过嵌入式linux核心课程的《C语言高级专题》的同学。课程简介《跟朱老师从C高级到征服C++》属于系列课程《朱有鹏老师热门编程语言解》的第一部分,主要针对C++语言完学习。本课程可以看做是嵌入式linux核心课程中《C语言高级专题》课程的延续,在学好了C语言前提下继续深度学习C++语言。整个课程共分为5部分。涵盖了C++基础语法和使用,面向对象,STL与泛型,Boost库和设计模式,C++编程优化技巧等非常广泛的内容,是市面上非常缺少的深度完整学习C++,培养编程技能和修养的一套系列学习课程。整个课程预计2020年底前录制完成,总时长预计150-200小时。朱老师C++第1部分-从C到C++朱老师C++第2部分-C++面向对象朱老师C++第3部分-STL等高阶话题朱老师C++第4部分-Boost库和设计模式朱老师C++第5部分-C++编程优化课程特色*完零基础,降低学习门槛。*深入浅出,通俗易懂。不怕学不会,就怕你不学习。*思路清晰、语言风趣,对着视频看也不会想睡觉······*视频 + 文档 + 练习题 + 答疑,方位保证学习质量。*基础知识 + 思路引导的教学方式,授之以鱼更授之以渔。*系列课程。本教程只是入门篇,后续还有更多更精彩视频更新中。
### 安装 PowerShell 的方法 #### 在 Windows 上安装 PowerShell 对于希望更新到最新版本的用户,可以通过以下方式来安装或升级至 PowerShell Core 7.1.2: - **Windows 版本支持** 对于大多数现代 Windows 操作系统(包括但不限于 Windows 10 和 Server 2019),推荐直接从 GitHub 下载并执行 MSI 或者 ZIP 文件来进行安装[^3]。 ```powershell # 使用 winget (适用于已启用此功能的 Windows 10/11 系统) winget install Microsoft.Powershell ``` 另一种通用的方法是从微软官方提供的链接下载对应的安装包,并按照提示完成安装过程。 #### 在 Linux 发行版上安装 PowerShell Linux 用户同样能够享受到 PowerShell 带来的便利。具体步骤取决于所使用的发行版类型: ##### Ubuntu / Debian 类系统: ```bash wget https://packages.microsoft.com/config/debian/$(lsb_release -rs)/packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb sudo apt-get update sudo apt-get install -y powershell ``` ##### Red Hat Enterprise Linux / CentOS: ```bash curl https://packages.microsoft.com/config/rhel/8/prod.repo | sudo tee \ /etc/yum.repos.d/microsoft.repo sudo yum install -y powershell ``` 这些命令会自动配置软件源并将 PowerShell 添加进去以便后续管理。 #### macOS 平台上安装 PowerShell macOS 用户也可以轻松获得 PowerShell 支持,只需利用 Homebrew 工具即可快速部署: ```bash brew install --cask powershell ``` 这将会把最新的稳定版 PowerShell 部署到 Mac 设备之上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖喱年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值