1.c++类主要知识点二
8.父类和子类之间的转换
在 C++ 中,父类(基类)和子类(派生类)之间的转换是一个常见的操作。这种转换可以分为两种主要类型:向上转换(从派生类到基类)和向下转换(从基类到派生类)。
每种转换都有其特定的用途和规则。
1. 向上转换(Upcasting)
// 向上转换是指将派生类对象转换为基类对象。这种转换是隐式的,不需要显式转换操作符。
class Base {
public:
    void display() {
        std::cout << "Base display" << std::endl;
    }
};
class Derived : public Base {
public:
    void display() {
        std::cout << "Derived display" << std::endl;
    }
};
int main() {
    Derived obj;
    Base* basePtr = &obj; // 向上转换
    basePtr->display(); // 调用 Base 的 display 方法
    return 0;
}
// 输出结果: Base display
2. 向下转换(Downcasting)
向下转换是指将基类对象转换为派生类对象。这种转换是显式的,需要使用强制类型转换操作符,如 static_cast、dynamic_cast 或 reinterpret_cast。
class Base {
public:
    virtual void display() {
        std::cout << "Base display" << std::endl;
    }
};
class Derived : public Base {
public:
    void display() override {
        std::cout << "Derived display" << std::endl;
    }
};
int main() {
    Derived obj;
    Base* basePtr = &obj; // 向上转换
    // 向下转换
    Derived* derivedPtr = static_cast<Derived*>(basePtr);
    derivedPtr->display(); // 调用 Derived 的 display 方法
    return 0;
}
// 输出结果: Derived display
3. 使用 static_cast 和 dynamic_cast
// static_cast
static_cast 是一种静态类型转换操作符,它在编译时进行类型检查。使用 static_cast 进行向下转换时,编译器不会检查对象的实际类型,因此可能会导致未定义行为。
Derived* derivedPtr = static_cast<Derived*>(basePtr);
// dynamic_cast
dynamic_cast 是一种动态类型转换操作符,它在运行时进行类型检查。使用 dynamic_cast 进行向下转换时,如果转换失败(即对象的实际类型不是目标类型),结果将为 nullptr(对于指针)或抛出异常(对于引用)。
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
    derivedPtr->display(); // 调用 Derived 的 display 方法
} else {
    std::cout << "Conversion failed" << std::endl;
}
4.转换的注意事项
4.1 向上转换的安全性
    向上转换是安全的,因为派生类对象包含基类对象的所有成员和方法。因此,将派生类对象转换为基类对象时,不会丢失任何信息。
4.2 向下转换的风险
    向下转换存在风险,因为基类对象可能不包含派生类的所有成员和方法。如果向下转换失败,可能会导致未定义行为。因此,建议使用 dynamic_cast 进行向下转换,并在转换后检查结果是否为 nullptr。
4.3 虚函数的重要性
    在使用向下转换时,基类的析构函数应该是虚函数。这样可以确保在删除派生类对象时,调用正确的析构函数,从而避免资源泄漏。
9.类大小: 普通类大小,抽象类大小
在 C++ 中,类的大小取决于其成员变量、继承关系以及对齐要求。普通类和抽象类的大小计算方式有所不同,下面将详细解释它们的大小计算方法。
1. 普通类的大小
普通类的大小主要由其成员变量和对齐要求决定。
以下是一个简单的普通类示例:
class MyClass {
public:
    int a;
    double b;
    char c;
};
std::cout << "MyClass size: " << sizeof(MyClass) << std::endl;
1)成员变量的大小
    int a:通常占用 4 字节。
    double b:通常占用 8 字节。
    char c:占用 1 字节。
2) 对齐要求
C++ 编译器通常会对成员变量进行对齐,以提高访问效率。对齐要求通常取决于成员变量中最大对齐要求的类型。在这个例子中,double 的对齐要求是 8 字节,因此整个类的大小将是 8 字节的倍数。
计算类的大小
    * 计算成员变量的总大小:
        int a:4 字节
        double b:8 字节
        char c:1 字节
        总大小:4 + 8 + 1 = 13 字节
    * 考虑对齐要求:
        int占用4个字节,double占用8个字节,int后面不够double存放,因此为了对齐,int后补齐了4个字节,char占用了1个字节,后补齐了7个字节
        因此,类的总大小为 24 字节。
-----------------------------------如果改成如下1:-----------------------------------
class MyClass {
public:
    int a;
    char c;
    double b;
};
类的总大小为 16 字节。
-----------------------------------如果改成如下2:-----------------------------------
class MyClass {
public:
    int a;
    char b;
    int c;
};
类的总大小为 12 字节。
-----------------------------------如果改成如下3:-----------------------------------
class MyClass {
public:
    int a;
    char b;
    int c;
    virtual void test() {}
};
类的总大小为 24 字节。
如上得知: 不是按照8位对齐,而是按照成员变量中最大对齐要求的类型
2. 抽象类的大小
抽象类是包含纯虚函数(Pure Virtual Function)的类,不能直接实例化。抽象类的大小主要由其虚函数表(Virtual Table, V-Table)决定。
虚函数表(V-Table)
每个包含虚函数的类都有一个虚函数表(V-Table),它是一个指针数组,存储了类的虚函数的地址。每个对象都有一个指向其类的 V-Table 的指针。
计算抽象类的大小
    1.虚函数表指针:
        * 每个对象包含一个指向其类的 V-Table 的指针,通常占用 4 字节或 8 字节(取决于平台)。
    2.成员变量:
        * 抽象类可以有成员变量,这些变量的大小也会计入类的总大小。
class Base {
public:
    virtual void display() = 0; // 纯虚函数
    int a; // 成员变量
};
class Derived : public Base {
public:
    void display() override {
        std::cout << "Derived display" << std::endl;
    }
    int m;
};
int main() {
    std::cout << "Size of Base: " << sizeof(Base) << " bytes" << std::endl;
    std::cout << "Size of Derived: " << sizeof(Derived) << " bytes" << std::endl;
    return 0;
}
// Size of Base: 16 bytes
// Size of Derived: 16 bytes
解释
    Base 类:
        包含一个虚函数表指针(通常 8 字节)和一个 int 成员变量(4 字节)。
        由于对齐要求,总大小为 16 字节。
    Derived 类:
        继承自 Base,因此包含 Base 的虚函数表指针和成员变量。
        没有额外的成员变量,因此大小与 Base 相同。
3. 总结
普通类的大小:
    由成员变量的大小和对齐要求决定。
    编译器会根据最大对齐要求对成员变量进行对齐。
抽象类的大小:
    主要由虚函数表指针决定。
    包含成员变量时,成员变量的大小也会计入类的总大小。
每个包含虚函数的类都有一个虚函数表(V-Table),它是一个指针数组,存储了类的虚函数的地址。每个对象都有一个指向其类的 V-Table 的指针。
10.类的友元函数、友元类
在 C++ 中,友元函数和友元类是用于访问类的私有(private)和受保护(protected)成员的机制。它们提供了一种方式,允许特定的函数或类访问类的内部实现细节,
而不需要将这些成员公开给所有用户。
10.1 类的友元函数
友元函数是一个非成员函数,它可以访问类的私有和受保护成员。友元函数不是类的成员,但它需要在类的定义中显式声明。
class MyClass {
private:
    int privateVar;
public:
    MyClass(int value) : privateVar(value) {}
    // 声明友元函数
    friend void displayPrivate(MyClass& obj);
};
void displayPrivate(MyClass& obj) {
    std::cout << "Private variable: " << obj.privateVar << std::endl;
}
int main() {
    MyClass obj(10);
    displayPrivate(obj); // 调用友元函数
    return 0;
}
// 输出结果
// Private variable: 10
10.2 友元类
友元类是一个类,它的所有成员函数都可以访问另一个类的私有和受保护成员。友元类需要在类的定义中显式声明。
class MyClass {
private:
    int privateVar;
public:
    MyClass(int value) : privateVar(value) {}
    // 声明友元类
    friend class MyFriendClass;
};
class MyFriendClass {
public:
    void displayPrivate(MyClass& obj) {
        std::cout << "Private variable: " << obj.privateVar << std::endl;
    }
};
int main() {
    MyClass obj(10);
    MyFriendClass friendObj;
    friendObj.displayPrivate(obj); // 调用友元类的成员函数
    return 0;
}
// 输出结果
// Private variable: 10
10.3 友元函数和友元类的用途
- 访问私有成员:友元函数和友元类可以访问类的私有成员,这在某些情况下非常有用,例如实现类的序列化、调试工具或特定的算法。
 - 封装和模块化:通过使用友元,可以将某些功能与类的实现细节解耦,同时保持类的封装性。
 - 性能优化:在某些情况下,友元函数可以提供更高效的访问方式,因为它们可以直接访问类的内部数据,而不需要通过公共接口。
 
10.4 友元的限制
- 访问限制:友元函数和友元类只能访问类的私有和受保护成员,不能访问其他类的私有成员。
 - 显式声明:友元必须在类的定义中显式声明,否则编译器不会允许访问私有成员。
 - 滥用风险:滥用友元可能会破坏类的封装性,导致代码难以维护和理解。因此,应该谨慎使用友元。
 
11.类中运算符重载
在 C++ 中,运算符重载是一种允许用户定义类的运算符行为的特性。通过运算符重载,可以为类的对象提供类似于内置类型的运算符操作,使得代码更加直观和易于理解。
- 
- 运算符重载的基本概念
运算符重载允许你定义类的运算符行为。例如,你可以重载加法运算符 +,使得两个类的对象可以使用 + 进行加法操作。 
 - 运算符重载的基本概念
 - 
- 运算符重载的类型
运算符重载可以分为以下几种类型:
成员函数重载:在类内部定义的运算符重载函数。
非成员函数重载:在类外部定义的运算符重载函数。 
 - 运算符重载的类型
 
#include <iostream>
class Vector2D {
public:
    float x, y;
    Vector2D(float x, float y) : x(x), y(y) {}
    // 成员函数重载:在类内部定义的运算符重载函数,可以访问类的私有成员。
    // 示例 1: 重载加法运算符
    Vector2D operator+(const Vector2D& other) const {
        return Vector2D(x + other.x, y + other.y);
    }
    // 示例 2: 减法运算符 -
    Vector2D operator-(const Vector2D& other) const {
        return Vector2D(x - other.x, y - other.y);
    }
    // 示例 3: 乘法运算符 *
    Vector2D operator*(float scalar) const {
        return Vector2D(x * scalar, y * scalar);
    }
    // 示例 4: 除法运算符 /
    Vector2D operator/(float scalar) const {
        return Vector2D(x / scalar, y / scalar);
    }
    // 非成员函数重载:在类外部定义的运算符重载函数,需要显式声明所有参数。
    // 示例 5: 重载输入>>和输出运算符<<
    friend std::ostream& operator<<(std::ostream& os, const Vector2D& v);
    friend std::istream& operator>>(std::istream& is, Vector2D& v);
};
std::ostream& operator<<(std::ostream& os, const Vector2D& v) {
    os << "(" << v.x << ", " << v.y << ")";
    return os;
}
std::istream& operator>>(std::istream& is, Vector2D& v) {
    is >> v.x >> v.y;
    return is;
}
int main() {
    Vector2D v1(1.0f, 2.0f);
    Vector2D v2(3.0f, 4.0f);
    Vector2D v3 = v1 + v2; // 使用重载的加法运算符
    std::cout << "add: " << v3 << std::endl; // 使用重载的输出运算符<<
    Vector2D v4 = v1 - v2; // 使用重载的减法运算符
    std::cout << "sub: " << v4 << std::endl; // 使用重载的输出运算符<<
    Vector2D v5 = v1 * 2.0; // 使用重载的乘法运算符
    std::cout << "mul: " << v5 << std::endl; // 使用重载的输出运算符<<
    Vector2D v6 = v1 / 2.0; // 使用重载的除法运算符
    std::cout << "div: " << v6 << std::endl; // 使用重载的输出运算符<<
    return 0;
}
// 输出结果
// add: (4, 6)
// sub: (-2, -2)
// mul: (2, 4)
// div: (0.5, 1)
12.模板类
模板类(Template Class)是 C++ 中的一种强大的特性,它允许你定义通用的类,这些类可以使用不同的数据类型。
模板类在编译时生成具体的类,从而实现类型安全和代码复用。以下是对模板类的详细解释,包括其定义、使用和一些高级特性。
12.1 模板类的基本定义
模板类的定义使用 template 关键字,后跟模板参数列表。模板参数可以是类型参数或非类型参数。
template <typename T>
class MyTemplateClass {
private:
    T value;
public:
    MyTemplateClass(T val) : value(val) {}
    T getValue() const {
        return value;
    }
    void setValue(T val) {
        value = val;
    }
};
在这个例子中,MyTemplateClass 是一个模板类,T 是一个类型参数,表示类中使用的数据类型。
12.2 使用模板类
使用模板类时,需要指定具体的类型参数,编译器会根据这些参数生成具体的类。
int main() {
    MyTemplateClass<int> intObj(10);
    std::cout << "intObj value: " << intObj.getValue() << std::endl;
    MyTemplateClass<double> doubleObj(3.14);
    std::cout << "doubleObj value: " << doubleObj.getValue() << std::endl;
    return 0;
}
输出结果
intObj value: 10
doubleObj value: 3.14
12.3 模板类的特性
3.1 类型参数: 模板类可以有多个类型参数,这些参数可以是不同的类型。
template <typename T1, typename T2>
class MyTemplateClass {
private:
    T1 value1;
    T2 value2;
public:
    MyTemplateClass(T1 val1, T2 val2) : value1(val1), value2(val2) {}
    T1 getValue1() const {
        return value1;
    }
    T2 getValue2() const {
        return value2;
    }
};
3.2 非类型参数: 模板类也可以有非类型参数,这些参数可以是整数、指针等。
template <typename T, int size>
class MyTemplateClass {
private:
    T values[size];
public:
    void setValue(int index, T val) {
        if (index >= 0 && index < size) {
            values[index] = val;
        }
    }
    T getValue(int index) const {
        if (index >= 0 && index < size) {
            return values[index];
        }
        return T();
    }
};
3.3 默认模板参数: 模板类可以为模板参数提供默认值。
template <typename T = int>
class MyTemplateClass {
private:
    T value;
public:
    MyTemplateClass(T val) : value(val) {}
    T getValue() const {
        return value;
    }
};
12.4 模板类的特化
模板类的特化允许你为特定的类型参数提供特殊的实现。
4.1 显式特化: 显式特化是指为特定的类型参数提供完全不同的实现。
template <typename T>
class MyTemplateClass {
public:
    void display() {
        std::cout << "Generic display" << std::endl;
    }
};
// 为模板参数int提供新的实现
template <>
class MyTemplateClass<int> {
public:
    void display() {
        std::cout << "Specialized display for int" << std::endl;
    }
};
4.2 部分特化: 部分特化是指为特定的类型参数提供部分不同的实现。
template <typename T, typename U>
class MyTemplateClass {
public:
    void display() {
        std::cout << "Generic display" << std::endl;
    }
};
template <typename T>
class MyTemplateClass<T, int> {
public:
    void display() {
        std::cout << "Specialized display for int" << std::endl;
    }
};
12.5 模板类的继承
模板类可以继承自其他模板类或普通类。
template <typename T>
class BaseTemplateClass {
public:
    void baseDisplay() {
        std::cout << "Base display" << std::endl;
    }
};
template <typename T>
class DerivedTemplateClass : public BaseTemplateClass<T> {
public:
    void derivedDisplay() {
        std::cout << "Derived display" << std::endl;
    }
};
12.6 模板类的成员函数模板
模板类的成员函数也可以是模板函数。
template <typename T>
class MyTemplateClass {
public:
    template <typename U>
    void display(U value) {
        std::cout << "Display: " << value << std::endl;
    }
};
12.7 模板类的静态成员
模板类可以有静态成员,这些静态成员在所有实例之间共享。
template <typename T>
class MyTemplateClass {
private:
    static T staticValue;
public:
    MyTemplateClass(T val) {
        staticValue = val;
    }
    static T getStaticValue() {
        return staticValue;
    }
};
template <typename T>
T MyTemplateClass<T>::staticValue;
13.类的内存管理
在 C++ 中,类的内存管理是一个非常重要的主题,涉及到对象的创建、使用和销毁。合理的内存管理可以避免内存泄漏、悬挂指针等问题,确保程序的稳定性和性能。
以下是对类的内存管理的详细解释,包括自动存储期、动态存储期、智能指针等。
1. 自动存储期
自动存储期的对象在它们的作用域结束时自动销毁。这些对象通常在栈上分配。
class MyClass {
public:
    MyClass() { std::cout << "Constructor called" << std::endl; }
    ~MyClass() { std::cout << "Destructor called" << std::endl; }
};
void myFunction() {
    MyClass obj; // 自动存储期对象
}
int main() {
    myFunction();
    return 0;
}
// 输出结果
// Constructor called
// Destructor called
2. 动态存储期
动态存储期的对象在堆上分配,使用 new 和 delete 进行管理。这些对象的生命周期由程序员控制。
class MyClass {
public:
    MyClass() { std::cout << "Constructor called" << std::endl; }
    ~MyClass() { std::cout << "Destructor called" << std::endl; }
};
int main() {
    MyClass* obj = new MyClass(); // 动态存储期对象
    delete obj; // 手动销毁对象
    return 0;
}
// 输出结果
// Constructor called
// Destructor called
3. 智能指针
智能指针是现代 C++ 中推荐的内存管理方式,可以自动管理动态分配的内存,避免内存泄漏。
class MyClass {
public:
    MyClass() { std::cout << "Constructor called" << std::endl; }
    ~MyClass() { std::cout << "Destructor called" << std::endl; }
};
int main() {
    std::unique_ptr<MyClass> obj = std::make_unique<MyClass>(); // 智能指针管理对象
    return 0;
}
// 输出结果
// Constructor called
// Destructor called
4. 内存泄漏
内存泄漏是指动态分配的内存没有被正确释放,导致内存占用不断增加。为了避免内存泄漏,应该确保每个 new 都有对应的 delete。
class MyClass {
public:
    MyClass() { std::cout << "Constructor called" << std::endl; }
    ~MyClass() { std::cout << "Destructor called" << std::endl; }
};
int main() {
    MyClass* obj = new MyClass(); // 动态存储期对象
    // 忘记调用 delete,导致内存泄漏
    return 0;
}
// 输出结果
// Constructor called
5. 悬挂指针
悬挂指针是指指向已经释放的内存的指针。使用悬挂指针会导致未定义行为。
class MyClass {
public:
    MyClass() { std::cout << "Constructor called" << std::endl; }
    ~MyClass() { std::cout << "Destructor called" << std::endl; }
};
int main() {
    MyClass* obj = new MyClass(); // 动态存储期对象
    delete obj; // 销毁对象
    obj->someMethod(); // 悬挂指针,未定义行为
    return 0;
}
// 输出结果
// Constructor called
// Destructor called
6. 深拷贝和浅拷贝
深拷贝和浅拷贝是类的拷贝构造函数和赋值运算符的两种实现方式。它们的区别在于如何处理动态分配的内存或其他资源。
深拷贝和浅拷贝是类的拷贝构造函数和赋值运算符的两种实现方式。
1) 浅拷贝
浅拷贝是指在拷贝对象时,只复制对象的引用(指针),而不复制实际的数据。这意味着两个对象共享相同的底层数据。
class MyClass {
public:
    int* data;
    MyClass(int value) : data(new int(value)) {}
    // 浅拷贝的拷贝构造函数
    MyClass(const MyClass& other) : data(other.data) {}
    // 浅拷贝的赋值运算符
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            data = other.data;
        }
        return *this;
    }
    ~MyClass() {
        delete data;
    }
};
int main() {
    MyClass obj1(10);
    MyClass obj2 = obj1; // 调用拷贝构造函数
    *obj2.data = 20; // 修改 obj2 的数据
    std::cout << "obj1: " << *obj1.data << std::endl; // 输出 obj1 的数据
    std::cout << "obj2: " << *obj2.data << std::endl; // 输出 obj2 的数据
    return 0;
}
// 输出结果
// obj1: 20
// obj2: 20
2) 深拷贝
深拷贝是指在拷贝对象时,不仅复制对象的引用(指针),还复制实际的数据。这意味着两个对象拥有独立的底层数据。
class MyClass {
public:
    int* data;
    MyClass(int value) : data(new int(value)) {}
    // 深拷贝的拷贝构造函数
    MyClass(const MyClass& other) : data(new int(*other.data)) {}
    // 深拷贝的赋值运算符
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            *data = *other.data;
        }
        return *this;
    }
    ~MyClass() {
        delete data;
    }
};
int main() {
    MyClass obj1(10);
    MyClass obj2 = obj1; // 调用拷贝构造函数
    *obj2.data = 20; // 修改 obj2 的数据
    std::cout << "obj1: " << *obj1.data << std::endl; // 输出 obj1 的数据
    std::cout << "obj2: " << *obj2.data << std::endl; // 输出 obj2 的数据
    return 0;
}
// 输出结果
// obj1: 10
// obj2: 20
3) 深拷贝和浅拷贝的区别
- 浅拷贝:
只复制对象的引用(指针),不复制实际的数据。
两个对象共享相同的底层数据。
修改一个对象的数据会影响另一个对象。 - 深拷贝:
复制对象的引用(指针)和实际的数据。
两个对象拥有独立的底层数据。
修改一个对象的数据不会影响另一个对象。 
4)选择深拷贝还是浅拷贝
- 深拷贝:
适用于需要独立数据副本的场景。
避免数据共享导致的副作用。
通常需要更多的内存和计算资源。 - 浅拷贝:
适用于不需要独立数据副本的场景。
节省内存和计算资源。
需要注意数据共享可能导致的副作用。 
7. 总结
- 自动存储期:对象在作用域结束时自动销毁。
 - 动态存储期:对象在堆上分配,需要手动管理。
 - 智能指针:推荐使用智能指针管理动态分配的内存。
 - 内存泄漏:确保每个 new 都有对应的 delete。
 - 悬挂指针:避免使用指向已经释放的内存的指针。
 - 深拷贝和浅拷贝:深拷贝复制对象的所有成员,浅拷贝只复制对象的引用。
 - 规则三/五:如果类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符,应该同时定义所有这些函数。
 
14.类中使用const、static、inline、override、noexcept
14.1 const
常量成员变量:在构造函数中初始化后不能被修改。常量成员函数:保证不会修改类的任何成员变量。常量对象:所有成员函数必须是常量成员函数。常量引用:用于传递对象,确保对象不会被修改。常量指针:用于指向常量对象,确保对象不会被修改。常量表达式:使用 constexpr 定义编译时常量值。
1. 常量成员变量
常量成员变量在类的构造函数中初始化后,不能被修改。这确保了这些变量的值在对象的生命周期内保持不变。
class MyClass {
public:
    const int value;
    MyClass(int val) : value(val) {} // 在构造函数中初始化常量成员变量
    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};
2. 常量成员函数
常量成员函数保证不会修改类的任何成员变量。这使得这些函数可以在常量对象上调用。
class MyClass {
public:
    int value;
    MyClass(int val) : value(val) {}
    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
    void show()  {
        std::cout << "show: " << value << std::endl;
    }
};
int main() {
    const MyClass obj(10); // 常量对象
    obj.display(); // 可以调用常量成员函数
    // obj.show(); // 不可以调用非常量成员函数
    MyClass obj2(20); // 非常量对象
    obj2.display(); // 可以调用常量成员函数
    obj2.show(); // 可以调用非常量成员函数
    return 0;
}
3. 常量对象
常量对象的所有成员函数必须是常量成员函数,因为常量对象的成员变量不能被修改。
class MyClass {
public:
    int value;
    MyClass(int val) : value(val) {}
    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
    void modifyValue() {
        value = 20; // 修改成员变量
    }
};
int main() {
    const MyClass obj(10); // 常量对象
    obj.display(); // 可以调用常量成员函数
    // obj.modifyValue(); // 错误:不能调用非常量成员函数
    return 0;
}
4. 常量引用
常量引用可以用于传递对象,确保对象不会被修改。这在函数参数传递中非常有用。
class MyClass {
public:
    int value;
    MyClass(int val) : value(val) {}
    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};
void display(const MyClass& obj) {
    obj.display(); // 调用常量成员函数
}
int main() {
    MyClass obj(10);
    display(obj); // 传递常量引用
    return 0;
}
5. 常量指针
常量指针可以用于指向常量对象,确保对象不会被修改。这在函数参数传递中也非常有用。
class MyClass {
public:
    int value;
    MyClass(int val) : value(val) {}
    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};
void display(const MyClass* obj) {
    obj->display(); // 调用常量成员函数
}
int main() {
    MyClass obj(10);
    display(&obj); // 传递常量指针
    return 0;
}
6. 常量表达式
C++11 引入了 constexpr 关键字,用于声明编译时常量表达式。这可以用于定义常量值,确保这些值在编译时被计算。
class MyClass {
public:
    constexpr static int value = 10;
    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};
int main() {
    MyClass obj;
    obj.display();
    return 0;
}
14.2 static
在 C++ 中,static 关键字用于定义静态成员变量和静态成员函数。静态成员属于类本身,而不是类的某个特定对象。
这意味着静态成员在所有对象之间共享,并且可以通过类名直接访问,而不需要创建对象。
1. 静态成员变量
静态成员变量属于类本身,而不是某个特定对象。因此,它在所有对象之间共享。
访问方式:可以通过类名直接访问,也可以通过类的实例访问,但推荐使用类名访问。
应用场景:常用于统计类的实例数量等。
class MyClass {
public:
    static int count; // 静态成员变量
    MyClass() {
        count++; // 每次创建对象时,count 增加
    }
    static void displayCount() {
        std::cout << "Number of objects: " << count << std::endl;
    }
};
int MyClass::count = 0; // 静态成员变量的初始化
int main() {
    MyClass obj1;
    MyClass obj2;
    MyClass::displayCount(); // 输出对象数量
    return 0;
}
// 输出结果
// Number of objects: 2
2. 静态成员函数
定义:静态成员函数属于类本身,而不是类的某个特定对象。
特点: 不依赖于类的任何对象,因此不能访问非静态成员变量和非静态成员函数。
可以通过类名直接调用,也可以通过类的实例调用,但推荐使用类名调用。
应用场景:可以用来实现工具方法,这些方法不需要访问类的实例状态。
#include <iostream>
using namespace std;
class Student {
public:
    static int total; // 静态成员变量
    Student() { total += 1; } // 构造函数
    static void printTotal() { // 静态成员函数
        cout << "Number of students: " << total << endl;
    }
};
int Student::total = 0; // 静态成员变量的初始化
int main() {
    Student s1;
    Student::printTotal(); // 输出:Number of students: 1
    Student s2;
    Student::printTotal(); // 输出:Number of students: 2
    return 0;
}
注意事项
- 静态成员变量和静态成员函数在类加载时就初始化,与实例无关。
 - 静态成员函数不能直接访问非静态成员,因为非静态成员需要实例才能存在。
 
14.3 inline
inline关键字
- 定义:inline是一个编译器提示,建议编译器将函数的调用直接替换为函数体,从而减少函数调用的开销。
 - 使用场景:通常用于小的、频繁调用的函数,以提高性能。
 
限制:
- 编译器可以忽略inline提示,根据自己的优化策略决定是否内联函数。
 - 内联函数的定义必须在每个使用它的翻译单元中可见。
 
虚函数和静态函数中的inline
- 虚函数:虚函数不能被声明为inline,因为虚函数的目的是通过虚表实现多态,而内联会消除函数调用的开销,这与虚函数的动态绑定机制相冲突。
 - 静态函数:静态成员函数可以被声明为inline。静态成员函数不属于任何对象,因此它们可以被内联,以减少调用开销。
 
14.4 override
在C++中,override关键字是C++11引入的一个特性,用于显式地表明派生类中的成员函数是重写基类中的虚函数。以下是关于override的详细使用方法和注意事项:
作用
- 明确意图:override关键字明确地表示一个函数是对基类中一个虚函数的重写。
 - 编译时检查:使用override可以确保派生类中的函数正确地重写了基类中的虚函数。如果函数签名不匹配,编译器会报错。
 
语法
override关键字出现在成员函数声明或定义中,位于参数列表之后。例如:
class Base {
public:
    virtual void func() {}
};
class Derived : public Base {
public:
    void func() override {} // 使用override关键字
};
使用场景
- 避免意外隐藏:在没有使用override的情况下,如果派生类中的函数签名与基类中的虚函数不匹配,该函数将隐藏而不是覆盖基类中的虚函数。使用override可以避免这种情况。
 - 提高代码可读性:override关键字使代码更清晰,让其他开发者更容易理解某个函数是重写自基类的。
 
注意事项
- override不是保留关键字,只有在成员函数声明符之后使用时才具有特殊含义。
 - 如果函数没有重写基类中的虚函数,使用override会导致编译错误。
 
14.5 noexcept
noexcept 是一个关键字,用于指定一个函数是否可能抛出异常。noexcept 可以用于函数声明,表示该函数不会抛出异常。如果一个函数被声明为 noexcept,
那么它在运行时抛出异常时,程序将调用 std::terminate 来终止程序。
1. noexcept 的作用
用于移动构造函数和移动赋值运算符
在 C++11 中,移动构造函数和移动赋值运算符通常被声明为 noexcept,因为它们通常不会抛出异常。这有助于优化性能,因为编译器可以利用这一信息进行更高效的优化。
可以用于函数声明,表示该函数不会抛出异常。
void myFunction() noexcept;
也可以用于函数定义,表示该函数不会抛出异常。
void myFunction() noexcept {
// 函数体
}
也可以用于类的成员函数。
2. noexcept 的好处
- 
优化性能: noexcept 有助于编译器进行优化。如果编译器知道一个函数不会抛出异常,它可以避免生成异常处理代码,从而提高性能。
 - 
提高代码安全性: noexcept 有助于提高代码的安全性。如果一个 noexcept 函数在运行时抛出异常,程序将调用 std::terminate 来终止程序,避免未处理的异常导致程序行为不可预测。
 - 
与标准库的兼容性: 许多标准库函数和容器要求移动构造函数和移动赋值运算符是 noexcept 的。使用 noexcept 可以确保你的类与标准库的兼容性。
 
3. noexcept 的限制
- 
异常安全: noexcept 并不意味着函数是异常安全的。即使一个函数被声明为 noexcept,它仍然可以调用可能抛出异常的函数。因此,使用 noexcept 时需要确保函数内部的逻辑不会抛出异常。
 - 
与 std::terminate 的关系: 如果一个 noexcept 函数在运行时抛出异常,程序将调用 std::terminate 来终止程序。这可能导致程序意外终止,因此需要谨慎使用 noexcept
 
                    
                
                
            
        
浙公网安备 33010602011771号