C++枚举类详解

1. 枚举类的定义

在C++11新标准中引入了枚举类(Enum Class),也称为强类型枚举。枚举类的定义格式如下:

1
enum class 枚举类型名: 底层类型 {枚举值列表};
  • 默认底层类型:如果未指定底层类型,默认为int类型。
  • 自定义底层类型:可以指定其他整数类型,如char,以节省空间。

示例:

1
2
3
enum class Type { General, Light, Medium, Heavy}; // 所有枚举常量都是int类型
enum class Type: char { General, Light, Medium, Heavy}; // 所有枚举常量都是字符类型
enum class Category { General=1, Pistol, MachineGun, Cannon}; // 后面的枚举常量值依次增加

2. 枚举类的优势

  • 强作用域:枚举类的使用范围仅限于定义它的代码块,避免了传统枚举可能引起的作用域污染。
  • 转换限制:枚举类对象不能与整型隐式转换,这有助于避免许多由于类型混淆而导致的错误。
  • 可自定义底层类型:可以指定枚举类的底层类型为char等其他整数类型,以节省内存空间。

示例:

1
2
3
enum class Color {
RED, GREEN, BLUE
};

3. 枚举类的使用

  • 声明和定义:枚举类的声明需要使用enum class关键字,后面跟随枚举类的名字和一对花括号,花括号内定义枚举成员。
  • 访问枚举成员:枚举类作用域内定义的变量,其值只能通过枚举类进行类型限定访问。

示例:

1
2
3
4
5
6
7
enum class Side { Left, Right };
enum class Thing { Wrong, Right };

Side s = Side::Right;
Thing t = Thing::Right;

// s 和 t 不会冲突,因为它们属于不同的枚举类

4. 枚举类的位运算

当枚举值需要通过位运算进行组合时,可以为枚举类定义位运算符(如|&)。由于枚举类不支持隐式转换,因此需要显式转换。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
enum class Printer_flags {
acknowledge = 1,
paper_empty = 2,
busy = 4
};

// 定义位运算符
Printer_flags operator|(Printer_flags a, Printer_flags b) {
return static_cast<Printer_flags>(static_cast<int>(a) | static_cast<int>(b));
}

Printer_flags status = Printer_flags::acknowledge | Printer_flags::paper_empty;

5. 枚举类的应用场景

  • 函数参数或返回值:用于指示函数的状态或执行的结果。
  • 类的成员变量:用于表示对象的状态或属性。
  • 条件语句:在ifswitch等条件语句中作为条件表达式,以根据枚举值执行不同的代码路径。
  • 循环:在循环中作为迭代器的值,尽管这种情况较少见,但在某些特定场景下可能会用到。

示例:

1
2
3
4
5
void f(Traffic_light x) {
if (x == Traffic_light::red) {
// 执行某些操作
}
}

6. 枚举类与整数的转换

  • 显式转换:可以将整型值显式转换为枚举类型,但转换结果是未定义的,除非整型值在枚举类型的底层类型范围内。
  • 隐式转换:枚举类对象不能隐式转换为整型。

示例:

1
2
3
enum class Warning { green, yellow, orange, red };
Warning a4 = Warning::green; // 正确
int a2 = static_cast<int>(Warning::green); // 正确

总结

C++枚举类(Enum Class)是C++11引入的一种强类型枚举,提供了更强的类型安全性和作用域限制。通过使用枚举类,可以避免传统枚举的一些问题,如作用域污染和隐式类型转换。枚举类不仅限于与枚举类内部定义的数据进行对比,还可以用于各种需要这些枚举值的场景,如函数参数、类成员变量、条件语句和循环等。此外,通过定义位运算符,可以方便地对枚举值进行组合操作。

静态转换与动态转换

静态转换(Static Cast)

静态转换是将一种数据类型的值强制转换为另一种数据类型的值。

静态转换通常用于比较类型相似的对象之间的转换,例如将 int 类型转换为 float 类型。

静态转换不进行任何运行时类型检查,因此可能会导致运行时错误。

1
2
int i = 10;
float f = static_cast<float>(i); // 静态将int类型转换为float类型

动态转换(Dynamic Cast)

动态转换通常用于将一个基类指针或引用转换为派生类指针或引用。动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常。

1
2
3
4
class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针

C++ 中的类型限定符

类型限定符提供了变量的额外信息,用于在定义变量或函数时改变它们的默认行为的关键字。

限定符 含义
const const 定义常量,表示该变量的值不能被修改。
volatile 修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。
restrict 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符
mutable mutable 用于修饰类的成员变量。被 mutable 修饰的成员变量可以被修改,即使它们所在的对象是 const 的。
static 用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。
register 用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。

C++ 引用 vs 指针

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。

  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。

  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。

虚函数

C++虚函数是如何调用子类重写函数的

在C++中,虚函数是实现运行时多态的关键机制。通过使用virtual关键字声明虚函数,子类可以重写这些方法,而无需在父类中实现它们。当创建指向子类的父类指针并调用其虚函数时,实际上是在调用子类中的重写函数,而非父类中的函数。以下是详细的调用过程和相关机制:

  1. 虚函数声明
    • 在父类中使用virtual关键字声明函数,使其成为虚函数。例如:
1
2
3
4
5
6
class Human {
public:
virtual void eat() {
std::cout << "Human eating" << std::endl;
}
};
  1. 子类重写
    • 子类可以重写父类的虚函数。为了确保正确重写,可以使用override关键字。例如:
1
2
3
4
5
6
class Man : public Human {
public:
void eat() override {
std::cout << "Man eating" << std::endl;
}
};
  1. 动态绑定
    • 当通过父类指针调用虚函数时,实际调用的是子类中重写的函数。这称为动态绑定(Dynamic Dispatch)。例如:
1
2
3
4
5
6
int main() {
Human* ptr = new Man();
ptr->eat(); // 输出 "Man eating"
delete ptr;
return 0;
}
  1. 虚函数表(vTable)

    • 每个包含虚函数的类都有一个对应的虚函数表(vTable),其中存储了该类所有虚函数的地址。
    • 每个类对象都有一个指向其对应vTable的指针(vptr)。
    • 当对象被创建时,vptr被初始化为指向该类的vTable。
  2. 虚函数调用过程

    • 当通过父类指针调用虚函数时,编译器会生成代码来访问对象的vptr。
    • 通过vptr找到对应的vTable,然后根据vTable中的地址调用实际的函数。
    • 如果子类重写了虚函数,vTable中的地址会被更新为子类函数的地址。
  3. 析构函数

    • 为了确保正确清理资源,建议将析构函数也声明为虚函数。例如:
1
2
3
4
5
6
class Human {
public:
virtual ~Human() {
std::cout << "Human destructor" << std::endl;
}
};
  1. 纯虚函数
    • 纯虚函数是在基类中声明但未实现的虚函数,其声明以= 0结尾。例如:
1
2
3
4
class Animal {
public:
virtual void speak() = 0;
};
  • 包含纯虚函数的类称为抽象类,不能被实例化。
  1. override关键字
    • override关键字用于标记派生类的成员函数,表明其覆盖了基类的虚函数。这有助于提高代码的可读性和编译器检查。例如:
1
2
3
4
5
6
class Man : public Human {
public:
void eat() override {
std::cout << "Man eating" << std::endl;
}
};

总结

虚函数通过动态绑定机制实现了运行时多态性。通过在父类中声明虚函数并在子类中重写,可以灵活地控制类的行为。虚函数表(vTable)和虚函数指针(vptr)是实现动态绑定的关键结构。正确使用虚函数可以提高代码的可读性和可扩展性。

关键点 描述
虚函数声明 使用virtual关键字声明
子类重写 子类使用相同的函数签名重写虚函数
动态绑定 通过父类指针调用虚函数时,实际调用子类重写的函数
虚函数表(vTable) 存储虚函数地址的表
虚函数指针(vptr) 指向vTable的指针
析构函数 建议声明为虚函数以确保正确清理资源
纯虚函数 声明为= 0的虚函数,使类成为抽象类
override关键字 标记覆盖基类虚函数,提高代码可读性和编译检查