今天是学习《重学C++ 重构你的C++知识体系》 的第三天,重点在 C++高级语法。

四、C++高级语法

(一)常见运算符

class Complex {
    public:
        Complex(double r,double i);
        Complex operator+(const Complex &c);
        Complex operator+=(const Complex &c);
        Complex operator-(const Complex &c);
        Complex operator-=(const Complex &c);
        Complex operator*(const Complex &c);
        Complex operator*=(const Complex &c);
        Complex operator/(const Complex &c);
        Complex operator/=(const Complex &c);
        bool operator==(const Complex &c);
        bool operator!=(const Complex &c);
        bool operator>(const Complex &c);
        bool operator<(const Complex &c);
}

可以对运算符operator进行重载。

(二)前置++和后置++

++i表示先增加,后取值

Complex& operator++(); // 前置++,也就是 ++i

// Tips:C++中传递也基本用引用
// Tips:下面的 Complex& 指的不是地址,而是引用
Complex& Complex::operator++ () {
    _real++;
    _image++;
    return *this;
}

Complex operator++(int) ; // 后置++,也就是i++
Complex Complex::operator++(int) {
    Complex tmp(*this); // 这样会产生临时副本,效率不够高
    _real++;
    _image++;
    return tmp;
}

(三)关键词 protected

只暴露给子类的接口可以放到 protected 里。

(四)浅拷贝和深拷贝

浅拷贝:只拷贝指针地址。

  • 优点:节省空间
  • 缺点:容易被delete引发多次释放问题(使用std::shared_ptr能避免这个问题)

深拷贝:重新分配堆内存,拷贝指向内容。

  • 优点:不会导致多次释放
  • 缺点:浪费空间

五、C++编程思想

(一)单例模式 Singleton

实现思路:

  1. singleton 有用一个私有构造函数,确保用户无法通过new直接实例它

2 包含一个静态私有成员变量instance与静态公有方法Instance()

.cpp
class MySingleton {
public:
    static MySingleton& getInsntace() {
        static MySingleton instance;
        return instance;
    }

    void doSomething() {
        // 在这里实现单例的具体功能
    }

    // 禁止拷贝函数和赋值运算符
    MySingleton(const MySingleton&) = delete;
    MySingleton& operator=(const MySingleton&) = delete;

private:
    MySingleton() {
        // xxx
    }

    ~MySingleton() {
        // yyy
    }

}

.main
int main() {
    MySingleton& singleton = MySingleton::getInstance();
    singleton.doSomething();

    return 0;
}

iOS 实现单例也是通过使用了 static

@interface MySingleton : NSObject

+ (instancetype)sharedInstance;

@end

@implementation MySingleton

+ (instancetype)sharedInstance {
    static MySingleton *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

@end

(二)观察者模式

.h
class Observer {
public:
    Observer(){};
    virtual ~Observer(){};

    // 当被观察对象发生变化时,通知被观察者调用这个方法
    virtual void Update(void* pArg) = 0; // 表示没有实际的实现

}


.User1 // 观察者1
class User1:public Observer // 使用public继承
{
    void Update(void* pArg){
        cout << "User1 God News" <<endl;
    }
}


.User2 // 观察者2
class User1:public Observer // 使用public继承
{
    void Update(void* pArg){
        cout << "User2 God News" <<endl;
    }
}

.Observerable
.h
class Observerable {
public:
    Observerable();
    virtual ~Observerable();

    // 注册观察者
    void Attach(Observer *pOb);
    // 反注册观察者
    void Detach(Observer *pOb);

protected:
    // _bChange 状态变化,需要通知
    void SetChange();

private:
    // 通知
    void Notify(void *pArg);

private:
    bool _bChange = false;
    list<Observer *> _Obs;
}

.cpp
// 注册观察者
void Obnserverable::Attach(OBserver* pOb) {
    if(pOb == NULL) {
        return;
    }

    // 观察当前列表中是否有这个观察者
    auto it = _Obs.begin();
    for(; it != _Obs.end(); it++) {
        if(*it == pOb) {
            return;
        }
    }

    _Obs.push_back(pOb);
}

void Observerable::Detach(Observer* pOb) {
    if (pOb == NULL || (_Obs.empty() == true)) {
        return;
    }

    _Obs.remove(pOb);
}

void Observerable::SetChange() {
    _bChange = true;

    Notify((void *)string.c_str()); // 将string转成char*
}

void Observerable::Nofity(void* pArg) {
    if(_bChange == false) {
        return;
    }

    auto it = _Obs.begin();
    for(; it != _Obs.end(); it++) {
        (*it)->Update();
    } 
    _bChange = false;
}


.main 
int main() {
    User u1;
    User u2;

    return 0;
}

(三)void* 、NULL 和 nullptr 关系

在C语言中:
#define NULL ((void *)0)

在C++语言中:
#ifndef NULL
#ifdef _cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

在C++中,nullptr用来替代((void *)0) 空指针,NULL则只表示0。

(四)类型转换 static_cast 的使用

C 类型转换

隐式类型转换:
比如:double f = 1.0 / 2; ,这里会得到: 0.5,没问题的,因为除法会把2转成2.0

显示类型转换:
比如:double f = double(1) / double(2)

C类型转换的问题:
- 1. 任意类型之间都可以转换,编译器无法判断其正确性
- 2. 难于定位:在源码中无法快速定位。

C++ 类型转换

const_cast

用于转换指针或引用,去除类型的const属性:

const int a = 10;
int* pA = &a; // ❌会报错:const int* 类型的值不能用于初始化 int * 类型的实体
int* pA = const_cast<int *>(&a); // ✅
*pA = 100;

reinterpret_cast:很危险!最好不要用!

重新解释类型,既不检查指向的内容,也不检查指针类型本身;
但要求转换前后的类型所占用内存大小一致,否则将引发编译时错误。

int Test() { // Tips: Test()等价于Test(void *)
return 0;
}

int main() {
typedef void(*FuncPtr)(); // 函数指针
FuncPtr funcPtr;
funcPtr = reinterpret_cast(&Test);

return0;

}

(五)适配器(Adapter)模式

适配器将类接口转换为客户端期望的另一个接口。

使用适配器可防止类由于接口不兼容而一起工作。

适配器模式的动机是,如果可以更改接口,则可以重用现有软件。

// 旧的接口
class LegacyRectangle {
public:
    int calculateArea() {
        return width_ * height_;
    }

    void setWidth(int width) {
        width_ = width;
    }

    void setHeight(int height) {
        height_ = height;
    }

private:
    int with_;
    int height_;
}

// 新的接口
class Rectangle {
public:
    virtual int calculateArea() = 0;
    virtual void setWidth(int width) = 0;
    virtual void setHeight(int height) = 0;
}

// 适配器类:将LegacyRectangle适配到Rectangle接口
class RectangleAdapater:public Rectangle {
public:
    RectanglAdapater(LegacyRectangle* legacyRectangle) :     legacyRectangle_(legacyRectangle){}

    int calculateArea() override {
        return lagacyRectangle_ -> calculateArea();
    }

    void setWidth(int width) override {
        legacyRectangle_->setWidth(width);
    }

    void setHeight(int height) override {
        legacyRectangle_ -> setHeight(height);
    }

private:
    LegacyRectangle* legacyRectangle_;
}

int main() {
    LegacyRectangle legacyRectangle;
    legacyRectangle.setWidth(5);
    legacyRectangle.setHeight(10);

    RectangleAdapater adapter(&legacyRectangle);
    int area = adapter.calculateArea();

    std::cout << "Area:" << area < std::endl;

    return 0;
}

上个同事定义了一个旧的接口 LegacyRectangle,它提供了计算面积和设置宽高的方法。

然后我们定义了一个新的接口Rectangle,它也提供了计算面积和设置宽高的方法,但方法的实现是通过适配器类RectangleAdapter来实现的,该适配器类将LegacyRectangle适配到Rectangle接口。

main函数中,我们创建了一个LegacyRectangle对象并设置宽度和高度,然后使用适配器RectangleAdapter来计算面积。通过适配器模式,我们成功地将旧的接口转换成了新的接口,并实现了面积的计算。

适配器模式在许多场景中都有应用,例如在使用第三方库、兼容不同版本的接口、重用旧代码等情况下都可以使用适配器模式来统一接口。

(六)泛型编程的思想

C++语言的真正精髓,在于泛型编程。

不同于面向对象的动态器多态,泛型编程是一种静态期多态,通过编译器生成直接的代码。

C++中,泛型编程主要通过模板(templates)来实现,模板允许您编写一段通用的代码,该艾迪玛可以用于不同的数据类型和类,而无需为每个类型单独编写特定的实现。

C++模板有两种主要形式:函数模板和类模板。

泛型编程是更高程度的抽象,它是通过编译器生成大量代码。

1. 函数模板

函数模板是一种编写通用函数的方法,这些函数可以用于不同的数据类型。函数模板在编译时根据实际参数类型生成特定的函数实现。

// 函数模板示例
template <typename T>
T max(T a,T b) {
    return (a > b) ? a : b;
}

int main() {
    int a = 3, b = 4;
    double x = 1.2, y = 0.5;

    std::cout << max(a,b) << std::endl; // 输出:4
    std::cout << max(x,y) << std::endl; // 输出:1.2

    return 0;
}

(1)C++中typename的关键词是什么意思

在C++中,typename关键字用于表示类型。

template <typename T>

typename关键字在这里起到了与class关键字相同的作用,但typename更明确地表示类型参数。

(2)如果传入的a和b类型不一样,怎么办?

(3)模板特化处理

// 函数模板示例
template <typename T>
T max(T a,T b) {
    return (a > b) ? a : b;
}

// 特化处理
template<>
char* max(char* a,char* b) {
    return (strcmp(a,b)) > 0 ? a : b;
}

int main() {
    int a = 3, b = 4;
    double x = 1.2, y = 0.5;

    std::cout << max(a,b) << std::endl; // 输出:4
    std::cout << max(x,y) << std::endl; // 输出:1.2

    return 0;
}

(4)使用C++muban解决实际问题

计算: 1 + 2 + 3 + … + 100

template

2. 类模板

类模板是一种编写通用类的方法,这些类可以用于不同的数据类型。类模板在编译时根据实际模板参数类型生成特定的类实现。

// 类模板示例
template <typename T>
class MyClass {
public:
    MyClass(const T& value) : value_(value){}

    T get_value() const {
        return value_;
    }

private:
    T value_;
}

int main() {
    MyClass<int> int_obj(42);
    MyClass<double> double_obj(3.14);

    std::cout << int_obj.get_value() << std::endl; // 输出:42
    std::cout << double_obj.get_value() << std::endl; // 输出:3.14

    return 0;
}

在这个示例中,我们定义了一个名为MyClass的类模板,它可以存储不同类型的值。

章节延伸题思考

(一)观察者模式如何避免多线程问题?

下面是一个示例代码,展示了如何在C++中使用互斥锁来避免多线程问题,实现观察者模式:

#include <iostream>
#include <vector>
#include <mutex>

class Observer {
public:
    virtual void update(int data) = 0;
};

class Subject {
public:
    void addObserver(Observer* observer) {
        std::lock_guard<std::mutex> lock(mutex_);
        observers_.push_back(observer);
    }

    void removeObserver(Observer* observer) {
        std::lock_guard<std::mutex> lock(mutex_);
        observers_.erase(std::remove(observers_.begin(), observers_.end(),     observer), observers_.end());
    }

    void notifyObservers(int data) {
        std::lock_guard<std::mutex> lock(mutex_);
        for (auto observer : observers_) {
            observer->update(data);
        }
    }

private:
    std::vector<Observer*> observers_;
    std::mutex mutex_;
};

class ConcreteObserver : public Observer {
public:
    void update(int data) override {
        std::cout << "Received data: " << data << std::endl;
    }
};

int main() {
    Subject subject;
    ConcreteObserver observer1, observer2;

    subject.addObserver(&observer1);
    subject.addObserver(&observer2);

    subject.notifyObservers(42);

    subject.removeObserver(&observer2);

    subject.notifyObservers(99);

    return 0;
}

在上述代码中,Subject类是被观察者,Observer类是观察者。Subject类维护了一个观察者列表observers_,并提供了添加、移除和通知观察者的方法。

为了避免多线程问题,我们使用了std::mutex互斥锁来保护对观察者列表的访问。在添加、移除和通知观察者时,都先获取互斥锁,然后进行操作,最后释放互斥锁。

ConcreteObserverObserver类的具体实现,它实现了update方法来处理被观察者状态的更新。

main函数中,我们创建了一个Subject对象和两个ConcreteObserver对象。然后,我们添加观察者并通知它们。最后,我们移除一个观察者并再次通知。

请注意,这只是一个简单的示例,用于演示如何使用互斥锁来避免多线程问题。实际应用中,需要根据具体情况进行更复杂的同步和并发控制。

(二)给我介绍一下C++中的void* 、NULL 和 nullptr 关系

在C++中,void*NULLnullptr都与指针相关。

  1. void*是一种通用指针类型,可以指向任何类型的数据。它是一种无类型指针,不能直接解引用,需要进行类型转换后才能使用。通常用于需要处理不同类型指针的场景,如在函数参数中接收任意类型指针。

  2. NULL是C++中用于表示空指针的宏定义,其值通常为0。在早期的C++标准中,NULL被定义为整数0,可以用于表示空指针。然而,在现代的C++标准中,推荐使用nullptr来表示空指针。

  3. nullptr是C++11引入的新关键字,用于表示空指针。它是一种特殊的字面量,可以直接赋值给指针类型的变量。与NULL相比,nullptr具有类型安全性,可以避免一些潜在的问题。使用nullptr可以更清晰地表达代码意图,并在编译时进行类型检查。

下面是一些示例代码,展示了void*NULLnullptr的使用:

#include <iostream>

void printInt(void* ptr) {
    int* p = static_cast<int*>(ptr);
    std::cout << *p << std::endl;
}

int main() {
    int* p1 = NULL;  // 使用NULL表示空指针(旧的写法)
    int* p2 = nullptr;  // 使用nullptr表示空指针(推荐的写法)

    if (p1 == nullptr) {
        std::cout << "p1 is a null pointer" << std::endl;
    }

    if (p2 == nullptr) {
        std::cout << "p2 is a null pointer" << std::endl;
    }

    int num = 42;
    void* ptr = &num;
    printInt(ptr);  // 使用void*接收不同类型的指针

    return 0;
}

在上述代码中,我们定义了两个指针变量p1p2,分别使用NULLnullptr初始化为空指针。然后,我们使用条件语句检查它们是否为空指针。

printInt函数中,我们使用void*类型的指针参数接收任意类型的指针,并在函数内部将其转换为int*类型,然后输出指针所指向的整数值。

请注意,尽管void*NULLnullptr都与指针相关,但它们的使用方式和含义是不同的。void*是一种通用指针类型,NULL是旧的表示空指针的宏定义,而nullptr是C++11中引入的新关键字,用于表示空指针。在现代的C++编程中,推荐使用nullptr来表示空指针。

(三)dynamic_cast、static_cast、const_cast、reinterpret_cast、类型转换函数、模板编程 的应用场景是怎么样的? 什么时候应该用什么样的类型转换方法? 推荐用什么? 代码示例说明

c++中如果在强制类型转换,转换的不合理的话,会crash吗? 如何安全的进行类型转换?

在C++中,如果进行不合理的强制类型转换,可能会导致程序崩溃或产生未定义的行为。这是因为强制类型转换可以绕过编译器的类型检查,如果转换操作不符合语义规则,可能会破坏数据的完整性或访问无效的内存地址。

为了安全地进行类型转换,可以考虑以下几种方法:

  1. 使用dynamic_castdynamic_cast是C++中的一种安全类型转换操作符,用于在运行时进行动态类型检查。它可以用于将指针或引用转换为派生类类型,或者将基类指针或引用转换为派生类指针或引用。如果转换不合理,dynamic_cast将返回nullptr(对于指针)或抛出std::bad_cast异常(对于引用)。

  2. 使用static_caststatic_cast是C++中常见的类型转换操作符,用于进行静态类型转换。它可以用于[基本类型]之间的转换,以及具有继承关系的指针或引用之间的转换。但是,static_cast无法进行动态类型检查,因此在使用时需要确保转换是安全的。

  3. 使用类型转换函数:对于自定义类型,可以在类中定义类型转换函数来进行安全的类型转换。类型转换函数可以根据需要进行必要的检查和验证,以确保转换是合理的。

  4. 使用模板编程:C++中的模板编程可以提供类型安全的泛型转换。通过使用模板参数推断和编译时类型检查,可以在编译时捕获类型转换错误,以提高类型安全性。

需要注意的是,尽管上述方法可以提高类型转换的安全性,但在进行类型转换时仍然需要谨慎。尽量避免进行不必要或不安全的类型转换,遵循良好的设计原则和最佳实践,以确保代码的正确性和可维护性。

以下是使用dynamic_caststatic_cast、类型转换函数和模板编程的示例代码,展示了如何安全地进行类型转换:

  1. 使用dynamic_cast进行动态类型转换:

#include

class Base {
public:
    virtual ~Base() {}
};

class Derived : public Base {
public:
    void derivedMethod() {
        std::cout << "Derived method" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived();

    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        derivedPtr->derivedMethod();
    }

    delete basePtr;

    return 0;
}

在上述代码中,我们定义了一个基类Base和一个派生类Derived。在main函数中,我们创建了一个Derived对象,并将其指针赋给一个Base类型的指针。然后,我们使用dynamic_castBase指针转换为Derived指针,并在转换成功后调用派生类的方法。如果转换不合理,dynamic_cast将返回nullptr

  1. 使用static_cast进行静态类型转换:

    #include

    int main() {

     int num = 10;
     double result = static_cast<double>(num);
    
     std::cout << "Result: " << result << std::endl;
    
     return 0;
    

    }

在上述代码中,我们使用static_cast将一个整数类型转换为双精度浮点数类型。由于这是一种常见的类型转换,且在此情况下是安全的,因此可以使用static_cast进行转换。

  1. const_cast:const_cast用于添加或删除const限定符。您应该在需要修改const对象或调用非const成员函数时使用const_cast。请注意,对原本是const的对象执行修改可能导致未定义行为。

    const int const_value = 42;
    int* non_const_ptr = const_cast<int*>(&const_value); // 移除const限定符

  1. reinterpret_cast:reinterpret_cast用于在不相关类型之间进行低级别的类型转换。它可以用于指针类型之间的转换、整数和指针之间的转换或其他不安全的类型转换。应该在需要执行低级别的类型转换时使用reinterpret_cast,但要非常小心,因为它可能导致未定义行为。

    int int_value = 42;
    int* int_ptr = &int_value;
    void* void_ptr = reinterpret_cast<void*>(int_ptr); // 将int指针转换为void指针

  2. 使用类型转换函数进行自定义类型转换:

#include <iostream>

class Distance {
public:
    explicit Distance(int meters) : meters_(meters) {}

    operator int() const {
        return meters_;
    }

private:
    int meters_;
};

int main() {
    Distance distance(1000);
    int meters = static_cast<int>(distance);

    std::cout << "Meters: " << meters << std::endl;

    return 0;
}

在上述代码中,我们定义了一个Distance类,它表示距离(以米为单位)。我们在类中定义了一个类型转换函数,将Distance对象转换为整数类型。通过使用static_cast,我们可以调用该类型转换函数,将Distance对象转换为整数。

  1. 使用模板编程进行类型安全的泛型转换:
#include <iostream>

template<typename T, typename U>
T convert(U value) {
    return static_cast<T>(value);
}

int main() {
    double result = convert<double>(10);

    std::cout << "Result: " << result << std::endl;

    return 0;
}

在上述代码中,我们定义了一个模板函数convert,它接受两个模板参数TU,并将U类型的值转换为T类型。通过使用static_cast,我们可以在编译时进行类型检查,以确保类型转换的安全性。

请注意,这些示例代码只是展示了如何使用不同的方法进行类型转换,并不是所有情况下都适用。在实际应用中,需要根据具体情况选择合适的方法,并进行必要的检查和验证,以确保类型转换的安全性。

(四) dynamic_cast 和 static_cast 有什么区别吗? 什么时候用前者,什么时候用后者?

示例:

Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);
Derived* derived_ptr = static_cast<Derived*>(base_ptr);

dynamic_cast:dynamic_cast在运行时执行类型检查。它用于在类层次结构中进行安全的向下转换。如果转换失败(例如,指向基类的指针实际上没有指向派生类的对象),dynamic_cast将返回空指针。您应该在需要在派生类和基类之间进行安全向下转换时使用dynamic_cast。请注意,为了使dynamic_cast正常工作,基类中必须至少有一个虚函数。

static_cast:static_cast在编译时执行类型转换,不进行运行时类型检查。它可以用于在相关类型之间进行类型转换,包括类层次结构中的指针。然而,由于它不执行类型检查,因此在进行向下转换时可能不安全。您应该在类型之间的转换是可预期且安全的情况下使用static_cast。

以下是dynamic_cast和static_cast之间区别的示例:

class Base {
public:
    virtual ~Base() {} // 必须至少有一个虚函数以使dynamic_cast正常工作
};

class Derived : public Base {
};

int main() {
    Base* base_ptr = new Derived;

    // 使用dynamic_cast进行安全的向下转换(运行时类型检查)
    Derived* derived_ptr1 = dynamic_cast<Derived*>(base_ptr);
    if (derived_ptr1) { // 检查转换是否成功
        std::cout << "dynamic_cast: conversion succeeded" << std::endl;
    } else {
        std::cout << "dynamic_cast: conversion failed" << std::endl;
    }

    // 使用static_cast进行向下转换(编译时类型转换,无运行时类型检查)
    Derived* derived_ptr2 = static_cast<Derived*>(base_ptr);
    std::cout << "static_cast: conversion performed" << std::endl;

    delete base_ptr;
    return 0;
}

1. 为什么 dynamic_cast 转换时,必须要实现一个虚函数? 这个虚函数指的是在转换失败的时候触发析构函数吗?

在C++中,dynamic_cast要求基类具有虚函数,因为C++运行时需要使用虚函数表来执行运行时类型检查。虚函数表使得C++运行时可以在运行时确定对象的实际类型,并执行安全的向下转换。在实际开发过程中,您应该为基类提供至少一个虚函数,以确保dynamic_cast的正确性。

dynamic_cast 的原理是读函数的虚函数表,来确定类型的。相当于先有虚函数表,dynamic_cast 才能使用。

2. 如果上面给的例子中Base没有实现虚函数,我又调用了 dynamic_cast 进行转换,会发生什么?

如果在Base类中没有虚函数,那么dynamic_cast将无法执行运行时类型检查,因为C++运行时无法访问虚函数表来确定对象的实际类型。在这种情况下,尝试使用dynamic_cast进行类型转换将导致编译错误。

3. dynamic_cast 不是运行时检测吗? 为什么Base没有实现虚函数会导致编译错误?

抱歉刚刚的回答可能存在误导。您是对的,dynamic_cast是在运行时执行类型检查。但是,编译器会在编译时检查基类是否具有虚函数,以确保dynamic_cast可以在运行时正确地执行类型检查。如果基类没有虚函数,那么在编译时,编译器将无法生成虚函数表,从而导致编译错误。

当编译器遇到dynamic_cast时,它会检查基类是否具有虚函数,以确保可以在运行时执行类型检查。如果基类没有虚函数,编译器将报告一个错误,提示Base不是多态的(’Base’ is not polymorphic)。

因此,在实际开发过程中,为了确保dynamic_cast的正确性和运行时多态性,您应该为基类提供至少一个虚函数。这将允许编译器生成虚函数表,并在运行时执行类型检查。

编译器遇到 dynamic_cast 不会去动态检测,但会帮忙检测一下 dynamic_cast 运行的条件,也就是Base是否有虚函数。

4. 虚函数表是什么时候生成的? runtime还是编译期?

在C++中,虚函数表(vtable)是在编译期生成的。当编译器遇到具有虚函数的类时,它会为该类生成一个虚函数表。虚函数表是一个存储类虚函数地址的表。编译器还会为每个类对象生成一个指向虚函数表的指针(通常称为虚指针,vptr)。

在运行时,当程序需要调用虚函数时,它会使用对象的虚指针查找虚函数表,并根据虚函数表中的函数地址调用相应的函数实现。这允许在运行时执行多态行为,因为虚函数表可以根据对象的实际类型来调用适当的函数实现。

总之,在C++中,虚函数表是在编译期生成的。编译器在遇到具有虚函数的类时生成虚函数表,并在运行时使用虚函数表来执行多态行为。这使得程序可以在运行时根据对象的实际类型调用适当的函数实现,从而实现运行时多态性。

5. 虚函数是什么?虚函数表的本质是什么? 它的数据结构是怎么实现的?

6. 如果我使用 static_cast 进行类型转换时,实际转成了一个错误地类型,会出现什么情况?比如:B继承A,D继承C,但我用D转成了B。

在C++中,如果您使用static_cast将一个对象指针错误地转换为不相关类型的指针(例如,将D转换为B,其中D继承自C,B继承自A),编译器不会报告错误,因为static_cast不会在运行时执行类型检查。然而,在运行时,这可能导致未定义行为,因为您可能会访问错误的内存地址或调用错误的成员函数。

以下是一个使用static_cast进行错误类型转换的示例:

class A {
public:
    virtual void print() const {
        std::cout << "A" << std::endl;
    }
};

class B : public A {
public:
    void print() const override {
        std::cout << "B" << std::endl;
    }
};

class C {
public:
    virtual void print() const {
        std::cout << "C" << std::endl;
    }
};

class D : public C {
public:
    void print() const override {
        std::cout << "D" << std::endl;
    }
};

int main() {
    D* d_ptr = new D;

    // 使用static_cast进行错误的类型转换(未定义行为)
    B* b_ptr = static_cast<B*>(d_ptr);

    // 调用错误的成员函数(未定义行为)
    b_ptr->print();

    delete d_ptr;
    return 0;
}

在这个示例中,我们使用static_cast将D错误地转换为B。这可能导致未定义行为,因为我们在b_ptr->print()调用时可能访问错误的内存地址或调用错误的成员函数。

为了避免这种问题,您应该使用dynamic_cast进行安全的向下转换。dynamic_cast在运行时执行类型检查,确保转换是有效的。如果转换失败,dynamic_cast将返回空指针(对于指针类型)或抛出std::bad_cast异常(对于引用类型)。

总之,在C++中,如果您使用static_cast进行错误的类型转换,可能会导致未定义行为。为了确保类型转换的安全性,您应该使用dynamic_cast进行运行时类型检查。在实际开发过程中,您应该根据需要选择合适的类型转换方法,以确保代码的安全性和正确性。

7. 为什么C++高手都建议我使用 dynamic_cast 代替 static_cast ? 我应该这样做吗?

C++高手建议使用dynamic_cast代替static_cast的原因是,当涉及到类层次结构中的向下转换时,dynamic_cast提供了更安全的类型检查。然而,这并不意味着您应该在所有情况下都使用dynamic_cast。在选择使用dynamic_cast还是static_cast时,您需要根据具体需求和场景来权衡安全性和性能。