跳转至

类型系统

C++的类型可以分成两种:

  • 标量类型:包含定义范围的单个值的类型。 标量包括算术类型(整型或浮点值)、枚举类型成员、指针类型、指针到成员类型以及 std::nullptr_t。 基本类型通常是标量类型。
  • 复合类型:不是标量类型的类型。 复合类型包括数组类型、函数类型、类(或结构)类型、联合类型、枚举、引用指向非静态类成员的指针

特殊类型

这里主要介绍一下C语言中没有的类型。

指针到成员类型

指针到成员类型(Pointer to member type)是一种特殊的指针,它允许指向类的成员(无论是数据成员还是成员函数)。这种指针类型与普通指针(例如指向整数或对象的指针)不同,因为它不直接存储对象的内存地址,而是存储访问类内特定成员的偏移量。在 C++ 中,指针到成员常被用于实现状态机,这是因为它们允许动态地改变当前激活的状态处理函数。

#include <iostream>

class TrafficLight {
public:
    enum State {
        RED,
        GREEN,
        YELLOW
    };

    TrafficLight() : currentState(&TrafficLight::red) {}

    void changeState(State newState) {
        switch (newState) {
            case RED:
                currentState = &TrafficLight::red;
                break;
            case GREEN:
                currentState = &TrafficLight::green;
                break;
            case YELLOW:
                currentState = &TrafficLight::yellow;
                break;
        }
    }

    void runCurrentState() {
        (this->*currentState)();  // 调用当前状态对应的成员函数
    }

private:
    void (TrafficLight::*currentState)();  // 指向成员函数的指针

    void red() {
        std::cout << "Red light - stop" << std::endl;
    }

    void green() {
        std::cout << "Green light - go" << std::endl;
    }

    void yellow() {
        std::cout << "Yellow light - caution" << std::endl;
    }
};

int main() {
    TrafficLight light;
    light.runCurrentState();  // 初始状态:红灯

    light.changeState(TrafficLight::GREEN);
    light.runCurrentState();  // 绿灯

    light.changeState(TrafficLight::YELLOW);
    light.runCurrentState();  // 黄灯

    return 0;
}

nullptr_t

nullptr_t顾名思义是空指针字面量nullptr的类型。但是他既不是指针类型,也不是指针到成员类型。但是他可以隐式转换为任何 指针 和 指针到成员。

事实上,C23也引入了nullptr_t。至于指针到成员的功能,可以用宏来模拟,标准库提供了offsetof宏,Linux内核中著名的例子是基于offsetof实现的container_of宏。

引用类型和指针类型类似,都指向内存中的一块内存。与指针不同的是,引用必须在定义时初始化,初始化之后的引用无法引用不同的对象或设置为 null,换句话说,引用永远指向定义时的对象。引用多用于函数参数的传递,以及函数的返回值(请不要返回局部变量的引用,一般认为引用比指针更加安全。

class Box {  
private:  
    int width;  
public:  
    Box(int w) : width(w) {}  
    int& getWidthRef() {  
        return width;  
    }  
    int getWidth() const {  
        return width;  
    }  
};  

int main() {  
    Box box(10);  
    box.getWidthRef() = 20; // 修改私有成员  
    std::cout << "Box width: " << box.getWidth() << std::endl; // 输出:Box width: 20  
    return 0;  
}

指向非静态类成员的指针类型是一种你特殊的指针类型,指向类的成员而非对象本身。他常用于回调函数、事件处理器等复杂的场景。

#include <iostream>  

class MyClass {  
public:  
    void display() const { std::cout << "Hello from MyClass!\n"; }  
};  

int main() {  
    void (MyClass::* ptrToMemberFunc)() const = &MyClass::display;  

    MyClass obj;  
    (obj.*ptrToMemberFunc)(); // 调用成员函数  

    return 0;  
}

枚举类

C语言也有枚举,C++也支持C语言式的枚举。但是C++有枚举类,供了比C中更多的特性和更强的类型安全。C的枚举更像是整数的别名。

enum class Color: unsigned int { Red, Green, Blue };

Color c = Color::Red;
unsigned int num = c; // 错误: 无法直接将Color直接转换,需要用static_cast

POD 类型

POD 类型(Plain Old Data,纯旧数据):一个POD类型是一个简单的数据结构,其中包含标量或其他POD类型的数据成员,没有没有用户定义的构造函数、析构函数或赋值运算符,没有虚函数和基类(不涉及继承和多态性),且其数据成员对外部代码完全开放。

POD 类型通常用于外部数据交换,例如与用 C 语言编写的模块(仅具有 POD 类型)进行的数据交换。通过std::is_trivial以及std::is_standard_layout可以判断一个类型是否为 POD 类型。

在你首次声明变量后,稍后无法更改其类型。 但是,你可以将变量的值或函数的返回值复制到其他类型的另一个变量中。这个过程隐式的引入了类型转换,这些操作有时很必要,但也是造成数据丢失或不正确的潜在原因。

类型转换

除了C语言风格的类型转换,C++还有3种特殊的类型转换方式:

  • 在C++中,static_cast 是一种编译时检查类型转换运算符,用于进行显式类型转换。它比旧式的C风格类型转换(例如 (int)x)提供更为安全和有表达力的方式。
  • 与之相对应的有dynamic_cast,它用于多态的类型转换。需要虚函数支持,转换失败返回nullptr(指针)或抛出异常(引用)。
  • reinterpret_cast 顾名思义重新解释二进制进行表示。谨慎使用。

这3种类型转换方式可以完全替代C语言风格的类型转换,现代C++不推荐使用C语言风格的类型转换。

参考链接

C++ 类型系统 | Microsoft Learn