类型系统
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语言风格的类型转换。