1.c++类主要知识点一
1.类的定义,类的种类
类的定义
在 C++ 中,类是一种封装数据和操作数据的函数的用户定义类型。类的定义和种类是面向对象编程的基础。
class 关键字:用于定义类。
成员函数:类中的函数,用于操作类的数据。
成员变量:类中的变量,用于存储数据。
类的种类
1) 普通类
普通类是最基本的类,用于封装数据和操作数据的函数。
2) 抽象类
抽象类是包含纯虚函数的类,不能实例化。它通常用于定义接口。
注意: 仅仅含有虚函数的不叫抽象类,必须是纯虚函数
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
virtual ~Shape() {} // 虚析构函数
};
3) 模板类
模板类使用模板参数定义类,可以处理不同类型的数据。
template <typename T>
class MyTemplateClass {
public:
MyTemplateClass(T value) : value_(value) {}
T getValue() const {
return value_;
}
private:
T value_;
};
4) 内嵌类
内嵌类是在另一个类中定义的类,通常用于封装内部逻辑。
class OuterClass {
public:
class InnerClass {
public:
void innerFunction() {
std::cout << "Inner function called" << std::endl;
}
};
};
5) 单例类
单例类确保一个类只有一个实例,并提供一个全局访问点。
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
void doSomething() const {
std::cout << "Doing something" << std::endl;
}
private:
Singleton() {}
// 为什么需要删除拷贝构造函数和赋值运算符?
// 防止拷贝构造: 如果允许拷贝构造,可以通过复制现有实例来创建新的实例,这会破坏单例的唯一性。
// 防止赋值操作: 如果允许赋值操作,可以通过赋值语句将一个实例的内容复制到另一个实例,这同样会破坏单例的唯一性。
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
6) 具体类
具体类是实现了所有纯虚函数的类,可以实例化。
// Shape是抽象类,Circle类中实现纯虚函数draw()
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle" << std::endl;
}
};
7) 混合类
混合类是继承了多个基类的类,通常用于组合多个接口或实现。
class Drawable {
public:
virtual void draw() const = 0;
};
class Movable {
public:
virtual void move(int dx, int dy) = 0;
};
class Ball : public Drawable, public Movable {
public:
void draw() const override {
std::cout << "Drawing a ball" << std::endl;
}
void move(int dx, int dy) override {
std::cout << "Moving a ball" << std::endl;
}
};
8) 特殊类
8.1 类模板特化
类模板特化为特定类型提供不同的实现。
template <typename T>
class MyTemplateClass {
public:
T value_;
};
template <>
class MyTemplateClass<int> {
public:
int value_;
void specialFunction() {
std::cout << "Special function for int" << std::endl;
}
};
8.2 友元类
友元类可以访问另一个类的私有成员。
class MyClass {
friend class MyFriendClass;
private:
int privateVar;
};
class MyFriendClass {
public:
void accessPrivate(MyClass& obj) {
std::cout << obj.privateVar << std::endl;
}
};
2.类的访问修饰符public、protected、private
在C++中,默认情况下,类的成员(包括属性和方法)的访问权限是私有(private)。如果你没有明确指定访问权限,那么类的成员将默认为私有。
1) 访问修饰符
public
定义:公开成员,可以在类外部访问。
访问权限:凡是在它下面声明的变量和函数,都可以在类的内部和外部访问。
protected
定义:受保护成员,只能在类内部和派生类中访问。
访问权限:凡是在它下面声明的变量和函数,只能在类的内部以及派生类(子类)中访问。
private
定义:私有成员,只能在类内部访问。
访问权限:凡是在它下面声明的变量和函数,只能在类的内部访问。可以使用公有成员函数,用于获取私有变量的值。
// 外部访问举例
class MyClass {
public:
void publicFunction() {} // 公开成员
protected:
void protectedFunction() {} // 受保护成员
private:
void privateFunction() {} // 私有成员
};
// 外部代码
int main() {
MyClass obj;
obj.publicFunction(); // 允许:外部代码可以访问公开成员
// obj.protectedFunction(); // 错误:外部代码不能访问受保护成员
// obj.privateFunction(); // 错误:外部代码不能访问私有成员
return 0;
}
2) 继承时的访问条件
访问规则
public 成员:
public 继承:在派生类中保持 public,外部代码可以访问。
protected 继承:在派生类中变为 protected,外部代码不能访问。
private 继承:在派生类中变为 private,外部代码不能访问。
protected 成员:
public 继承:在派生类中保持 protected,外部代码不能访问。
protected 继承:在派生类中保持 protected,外部代码不能访问。
private 继承:在派生类中变为 private,外部代码不能访问。
private 成员:
public 继承:在派生类中不可访问。
protected 继承:在派生类中不可访问。
private 继承:在派生类中不可访问。
// 1.public 继承举例
class BaseClass {
public:
void publicFunction() {}
protected:
void protectedFunction() {}
private:
void privateFunction() {}
};
class DerivedClass : public BaseClass {
public:
void test() {
publicFunction(); // 可以访问
protectedFunction(); // 可以访问
// privateFunction(); // 不能访问
}
};
3) 友元函数和友元类
友元函数可以访问类的私有和受保护成员。
class MyClass {
friend void myFriendFunction(MyClass& obj);
private:
int privateVar;
};
void myFriendFunction(MyClass& obj) {
std::cout << obj.privateVar << std::endl; // 可以访问私有成员
}
// 访问权限:友元函数可以访问类的私有和受保护成员,但外部代码不能。
友元类的所有成员函数都可以访问类的私有和受保护成员。
class MyClass {
friend class MyFriendClass;
private:
int privateVar;
};
class MyFriendClass {
public:
void accessPrivate(MyClass& obj) {
std::cout << obj.privateVar << std::endl; // 可以访问私有成员
}
};
// 访问权限:友元类的所有成员函数都可以访问类的私有和受保护成员,但外部代码不能。
3.类的默认成员函数
在 C++ 中,类可以有多个默认成员函数,这些函数在某些情况下会由编译器自动生成。
这些默认成员函数包括默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符。
3.1 默认构造函数
定义:无参数的构造函数,用于初始化对象。
生成条件:
如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数。
如果类中定义了其他构造函数,但没有定义无参数的构造函数,编译器不会自动生成默认构造函数。
示例:
class MyClass {
public:
MyClass() {} // 显式定义的默认构造函数
};
// 如果类中没有定义任何构造函数:
class MyClass {
// 没有定义任何构造函数
};
// 编译器会自动生成一个默认构造函数。
3.2 析构函数
定义:用于清理资源,当对象销毁时调用。
生成条件:
如果类中没有定义析构函数,编译器会自动生成一个默认析构函数。
默认析构函数会调用基类和成员变量的析构函数。
示例:
class MyClass {
public:
~MyClass() {} // 显式定义的析构函数
};
// 如果类中没有定义析构函数:
class MyClass {
// 没有定义析构函数
};
// 编译器会自动生成一个默认析构函数。
3.3 拷贝构造函数
定义:用于创建对象的副本。
生成条件:
如果类中没有定义拷贝构造函数,编译器会自动生成一个默认拷贝构造函数。
默认拷贝构造函数会逐成员进行浅拷贝。
示例:
class MyClass {
public:
MyClass(const MyClass& other) {} // 显式定义的拷贝构造函数
};
// 如果类中没有定义拷贝构造函数:
class MyClass {
// 没有定义拷贝构造函数
};
// 编译器会自动生成一个默认拷贝构造函数。
3.4 拷贝赋值运算符
定义:用于将一个对象的内容赋值给另一个对象。
生成条件:
如果类中没有定义拷贝赋值运算符,编译器会自动生成一个默认拷贝赋值运算符。
默认拷贝赋值运算符会逐成员进行浅拷贝。
示例:
class MyClass {
public:
MyClass& operator=(const MyClass& other) { return *this; } // 显式定义的拷贝赋值运算符
};
// 如果类中没有定义拷贝赋值运算符:
class MyClass {
// 没有定义拷贝赋值运算符
};
// 编译器会自动生成一个默认拷贝赋值运算符。
3.5 移动构造函数(C++11)
定义:用于将资源从一个对象移动到另一个对象,通常用于优化性能。
生成条件:
如果类中没有定义移动构造函数,且类中没有定义拷贝构造函数、拷贝赋值运算符或析构函数,编译器会自动生成一个默认移动构造函数。
默认移动构造函数会逐成员进行移动。
示例:
class MyClass {
public:
MyClass(MyClass&& other) noexcept {} // 显式定义的移动构造函数
};
// 如果类中没有定义移动构造函数:
class MyClass {
// 没有定义移动构造函数
};
// 编译器会自动生成一个默认移动构造函数(如果满足条件)。
3.6 移动赋值运算符(C++11)
定义:用于将资源从一个对象移动到另一个对象,通常用于优化性能。
生成条件:
如果类中没有定义移动赋值运算符,且类中没有定义拷贝构造函数、拷贝赋值运算符或析构函数,编译器会自动生成一个默认移动赋值运算符。
默认移动赋值运算符会逐成员进行移动。
示例:
class MyClass {
public:
MyClass& operator=(MyClass&& other) noexcept { return *this; } // 显式定义的移动赋值运算符
};
// 如果类中没有定义移动赋值运算符:
class MyClass {
// 没有定义移动赋值运算符
};
// 编译器会自动生成一个默认移动赋值运算符(如果满足条件)。
4.类的对象和类的指针,以及他们的生命周期
4.1 类的对象
// 1) 创建对象
class MyClass {
public:
MyClass(int value) : value_(value) {
std::cout << "Constructor called" << std::endl;
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
void display() const {
std::cout << "Value: " << value_ << std::endl;
}
private:
int value_;
};
MyClass obj(10); // 创建对象
// 自动存储期对象:在栈上创建的对象,其生命周期与作用域相同。当作用域结束时,对象自动销毁。
// 2) 对象的生命周期
void myFunction() {
MyClass obj(10); // 自动存储期对象
obj.display();
} // obj 在函数结束时自动销毁
// 自动存储期对象:在函数内部创建的对象,其生命周期与函数的作用域相同。当函数结束时,对象自动销毁。
4.2 类的指针
// 1) 创建指针
MyClass* ptr = new MyClass(10); // 创建指针
ptr->display();
// 指针:指针是一个变量,存储了对象的地址。通过指针可以间接访问对象。
// 2) 指针的生命周期
void myFunction() {
MyClass* ptr = new MyClass(10); // 动态存储期对象
ptr->display();
delete ptr; // 手动销毁对象
} // ptr 在函数结束时自动销毁,但对象需要手动销毁
// 指针的生命周期:指针本身在栈上,其生命周期与作用域相同。但指针指向的对象在堆上,需要手动销毁。
// 对象的生命周期:指针指向的对象在堆上,其生命周期由程序员控制。必须使用 delete 关键字手动销毁对象,以避免内存泄漏。
4.3 常量对象和常量指针
// 1) 常量对象
const MyClass obj(10); // 常量对象
obj.display(); // 只能调用常量成员函数
// 常量对象:常量对象的成员变量和成员函数不能被修改。只能调用常量成员函数。
// 2) 常量指针
const MyClass* ptr = new MyClass(10); // 常量指针
ptr->display(); // 只能调用常量成员函数
delete ptr; // 手动销毁对象
// 常量指针:常量指针指向的对象不能被修改。只能调用常量成员函数。
4.4 智能指针
#include <memory>
void myFunction() {
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>(10); // 智能指针
ptr->display();
} // ptr 在函数结束时自动销毁,对象也会自动销毁
// 智能指针:智能指针(如 std::unique_ptr 和 std::shared_ptr)自动管理对象的生命周期,避免手动管理内存。
// 生命周期:智能指针在栈上,其生命周期与作用域相同。智能指针指向的对象在堆上,但智能指针会自动销毁对象,避免内存泄漏。
总结
对象的生命周期:
自动存储期对象:在栈上,生命周期与作用域相同,自动销毁。
动态存储期对象:在堆上,生命周期由程序员控制,需要手动销毁。
指针的生命周期:
指针本身:在栈上,生命周期与作用域相同,自动销毁。
指针指向的对象:在堆上,生命周期由程序员控制,需要手动销毁。
智能指针:自动管理对象的生命周期,避免手动管理内存,推荐使用。
5.普通类继承以及构造和析构顺序
5.1 没有虚函数的普通类继承以及构造和析构顺序-单继承
// 1) 没有虚函数的普通类继承
class BaseClass {
public:
BaseClass() {
std::cout << "BaseClass constructor called" << std::endl;
}
~BaseClass() {
std::cout << "BaseClass destructor called" << std::endl;
}
void baseFunction() {
std::cout << "BaseClass function called" << std::endl;
}
};
class DerivedClass : public BaseClass {
public:
DerivedClass() {
std::cout << "DerivedClass constructor called" << std::endl;
}
~DerivedClass() {
std::cout << "DerivedClass destructor called" << std::endl;
}
void derivedFunction() {
std::cout << "DerivedClass function called" << std::endl;
}
};
// 2) 构造函数的调用顺序
// 基类的构造函数:首先调用基类的构造函数。
// 派生类的构造函数:然后调用派生类的构造函数。
// 3) 析构函数的调用顺序
// 派生类的析构函数:首先调用派生类的析构函数。
// 基类的析构函数:然后调用基类的析构函数。
// 这种顺序确保了对象的基类部分在派生类部分之前被初始化,并且在销毁对象时,派生类部分先被销毁,然后是基类部分。
int main() {
DerivedClass obj;
return 0;
}
// BaseClass constructor called
// DerivedClass constructor called
// DerivedClass destructor called
// BaseClass destructor called
5.2 没有虚函数的普通类继承以及构造和析构顺序-多重继承
一个类可以继承多个基类,这称为多重继承。多重继承允许一个派生类继承多个基类的成员函数和成员变量。然而,多重继承也带来了一些复杂性,例如菱形继承问题。
class BaseClass1 {
public:
BaseClass1() {
std::cout << "BaseClass1 constructor called" << std::endl;
}
~BaseClass1() {
std::cout << "BaseClass1 destructor called" << std::endl;
}
void baseFunction1() {
std::cout << "BaseClass1 function called" << std::endl;
}
};
class BaseClass2 {
public:
BaseClass2() {
std::cout << "BaseClass2 constructor called" << std::endl;
}
~BaseClass2() {
std::cout << "BaseClass2 destructor called" << std::endl;
}
void baseFunction2() {
std::cout << "BaseClass2 function called" << std::endl;
}
};
class DerivedClass : public BaseClass1, public BaseClass2 {
public:
DerivedClass() {
std::cout << "DerivedClass constructor called" << std::endl;
}
~DerivedClass() {
std::cout << "DerivedClass destructor called" << std::endl;
}
void derivedFunction() {
std::cout << "DerivedClass function called" << std::endl;
}
};
int main() {
DerivedClass obj;
return 0;
}
// 输出结果
// BaseClass1 constructor called
// BaseClass2 constructor called
// DerivedClass constructor called
// DerivedClass destructor called
// BaseClass2 destructor called
// BaseClass1 destructor called
// 构造顺序
// 基类的构造函数:按照继承列表中的顺序调用基类的构造函数。在上面的例子中,BaseClass1 的构造函数先被调用,然后是 BaseClass2 的构造函数。
// 派生类的构造函数:在所有基类的构造函数调用完成后,派生类的构造函数被调用。
// 析构顺序
// 派生类的析构函数:首先调用派生类的析构函数。
// 基类的析构函数:按照继承列表中的逆序调用基类的析构函数。在上面的例子中,BaseClass2 的析构函数先被调用,然后是 BaseClass1 的析构函数。
5.3 没有虚函数的普通类继承以及构造和析构顺序-菱形继承问题
多重继承的一个常见问题是菱形继承。当两个基类继承自同一个祖先类时,派生类会继承两个基类的祖先类部分,这可能导致祖先类的成员被继承两次。
class BaseClass {
public:
BaseClass() {
std::cout << "BaseClass constructor called" << std::endl;
}
~BaseClass() {
std::cout << "BaseClass destructor called" << std::endl;
}
void baseFunction() {
std::cout << "BaseClass function called" << std::endl;
}
};
class DerivedClass1 : public BaseClass {
public:
DerivedClass1() {
std::cout << "DerivedClass1 constructor called" << std::endl;
}
~DerivedClass1() {
std::cout << "DerivedClass1 destructor called" << std::endl;
}
};
class DerivedClass2 : public BaseClass {
public:
DerivedClass2() {
std::cout << "DerivedClass2 constructor called" << std::endl;
}
~DerivedClass2() {
std::cout << "DerivedClass2 destructor called" << std::endl;
}
};
class FinalClass : public DerivedClass1, public DerivedClass2 {
public:
FinalClass() {
std::cout << "FinalClass constructor called" << std::endl;
}
~FinalClass() {
std::cout << "FinalClass destructor called" << std::endl;
}
};
// 输出结果
// BaseClass constructor called
// DerivedClass1 constructor called
// BaseClass constructor called
// DerivedClass2 constructor called
// FinalClass constructor called
// FinalClass destructor called
// DerivedClass2 destructor called
// BaseClass destructor called
// DerivedClass1 destructor called
// BaseClass destructor called
5.4 没有虚函数的普通类继承以及构造和析构顺序-解决菱形继承问题: 虚继承
class BaseClass {
public:
BaseClass() {
std::cout << "BaseClass constructor called" << std::endl;
}
~BaseClass() {
std::cout << "BaseClass destructor called" << std::endl;
}
void baseFunction() {
std::cout << "BaseClass function called" << std::endl;
}
};
class DerivedClass1 : virtual public BaseClass {
public:
DerivedClass1() {
std::cout << "DerivedClass1 constructor called" << std::endl;
}
~DerivedClass1() {
std::cout << "DerivedClass1 destructor called" << std::endl;
}
};
class DerivedClass2 : virtual public BaseClass {
public:
DerivedClass2() {
std::cout << "DerivedClass2 constructor called" << std::endl;
}
~DerivedClass2() {
std::cout << "DerivedClass2 destructor called" << std::endl;
}
};
class FinalClass : public DerivedClass1, public DerivedClass2 {
public:
FinalClass() {
std::cout << "FinalClass constructor called" << std::endl;
}
~FinalClass() {
std::cout << "FinalClass destructor called" << std::endl;
}
};
// 输出结果
// BaseClass constructor called
// DerivedClass1 constructor called
// DerivedClass2 constructor called
// FinalClass constructor called
// FinalClass destructor called
// DerivedClass2 destructor called
// DerivedClass1 destructor called
// BaseClass destructor called
6.虚函数、静态绑定、动态绑定
6.1 虚函数、方法隐藏(Method Hiding)、方法覆盖
C++ 中,虚函数(Virtual Function)是实现多态(Polymorphism)的关键机制。虚函数允许派生类重写基类的函数,从而在运行时根据对象的实际类型调用相应的函数。
1. 虚函数的作用
// 1.1 实现多态: 虚函数的主要作用是实现多态。多态允许通过基类的指针或引用调用派生类的函数,从而实现动态绑定(Dynamic Binding)。
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;
}
};
void callDisplay(Base* obj) {
obj->display(); // 调用虚函数
}
int main() {
Derived obj;
callDisplay(&obj); // 输出 "Derived display"
return 0;
}
// 多态:通过基类的指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数。
// 动态绑定:在运行时根据对象的实际类型调用相应的函数。
// 思考: 如果直接使用派生类的指针或引用,代码会变得硬编码,难以扩展
void callDisplay(Derived* obj) {
obj->display(); // 调用派生类的函数
}
// 这种写法限制了函数只能处理 Derived 类型的对象,无法处理其他派生自 Base 的类的对象。这使得代码缺乏通用性和可扩展性。
// 1.2 提供默认实现: 虚函数可以提供默认实现,派生类可以选择性地重写这些函数。如果派生类没有重写虚函数,将使用基类的默认实现。
class Base {
public:
virtual void display() {
std::cout << "Base display" << std::endl;
}
};
class Derived : public Base {
// 没有重写 display 函数
};
int main() {
Derived obj;
obj.display(); // 输出 "Base display"
return 0;
}
// 默认实现:基类的虚函数提供了默认实现,派生类可以选择性地重写。
2.虚函数的特性
// 2.1 动态绑定: 虚函数的调用是在运行时根据对象的实际类型进行的,这称为动态绑定。
Base* obj = new Derived();
obj->display(); // 输出 "Derived display"
// 动态绑定:在运行时根据对象的实际类型调用相应的函数。
// 2.2 虚函数表(V-Table): 每个包含虚函数的类都有一个虚函数表(V-Table),用于存储虚函数的地址。对象在内存中包含一个指向其类的 V-Table 的指针,通过这个指针调用虚函数。
// V-Table:每个类的 V-Table 存储了虚函数的地址。
// 对象的 V-Table 指针:对象在内存中包含一个指向其类的 V-Table 的指针。
// 2.3 虚函数的覆盖和隐藏: 派生类可以覆盖基类的虚函数,也可以隐藏基类的非虚函数。
class Base {
public:
virtual void display() {
std::cout << "Base display" << std::endl;
}
void show() {
std::cout << "Base show" << std::endl;
}
};
class Derived : public Base {
public:
void display() override {
std::cout << "Derived display" << std::endl;
}
void show() {
std::cout << "Derived show" << std::endl;
}
};
int main() {
Derived obj;
obj.display(); // 输出 "Derived display"
obj.show(); // 输出 "Derived show"
return 0;
}
// 覆盖:派生类重写基类的虚函数。
// 隐藏:派生类隐藏基类的非虚函数。
3.虚析构函数: 如果基类有虚析构函数,派生类的析构函数会自动调用基类的析构函数。这确保了在销毁对象时,基类和派生类的资源都能被正确释放。
// 问题点: 如果基类没有虚析构函数
class Base {
public:
~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base* obj = new Derived();
delete obj; // 只调用 Base 的析构函数
return 0;
}
// 输出结果
// Base destructor
// 问题分析
// 基类没有虚析构函数:当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。
// 资源泄漏:如果派生类中分配了动态资源(如动态内存、文件句柄、网络连接等),这些资源不会被正确释放,导致资源泄漏。
// 未定义行为:在某些情况下,可能会导致程序崩溃或其他未定义行为。
// 解决方法
// 使用虚析构函数: 为基类提供一个虚析构函数,确保在删除派生类对象时,派生类的析构函数会被正确调用。
4.总结
多态:通过基类的指针或引用调用派生类的函数。
动态绑定:在运行时根据对象的实际类型调用相应的函数。
默认实现:基类的虚函数可以提供默认实现。
纯虚函数和抽象类:纯虚函数用于定义接口,包含纯虚函数的类称为抽象类。
V-Table:每个类的 V-Table 存储了虚函数的地址。
覆盖和隐藏:派生类可以覆盖基类的虚函数,也可以隐藏基类的非虚函数。
虚析构函数:确保在销毁对象时,基类和派生类的资源都能被正确释放。
5.思考: 使用了虚函数,派生类才能重写父类的方法,如果没有使用虚函数,那么不能重写吗 ----方法隐藏、方法覆盖
// 在 C++ 中,即使没有使用虚函数,派生类仍然可以“重写”基类的方法,但这并不是真正的多态意义上的重写。
// 这种行为称为方法隐藏(Method Hiding),而不是方法覆盖(Method Overriding)。
1) 方法覆盖(Method Overriding)
// 方法覆盖是指派生类中的方法覆盖了基类中的同名方法。这要求基类的方法是虚函数(virtual)。当通过基类的指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数,这称为动态绑定(Dynamic Binding)。
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;
}
};
void callDisplay(Base* obj) {
obj->display(); // 调用虚函数
}
int main() {
Derived obj;
callDisplay(&obj); // 输出 "Derived display"
return 0;
}
// 输出结果 "Derived display"
// 动态绑定:通过基类的指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数。
// override:显式表示派生类的方法覆盖了基类的虚函数。
2) 方法隐藏(Method Hiding)
// 方法隐藏是指派生类中的方法隐藏了基类中的同名方法。这不需要基类的方法是虚函数。当通过派生类的对象调用方法时,会调用派生类中的方法,而不是基类中的方法。这种行为称为静态绑定(Static Binding)。
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;
obj.display(); // 输出结果: Derived display 静态绑定: Derived对象,所以调用Derived方法
Base base;
base.display(); // 输出结果: Base display 静态绑定: Base对象,所以调用Base方法
Base* basePtr = new Derived(); // 指针类型:basePtr 是 Base 类型的指针。对象类型:basePtr 指向的是 Derived 类型的对象。
basePtr->display(); // 输出结果: Base display 静态绑定: 通过基类指针调用方法时,会根据指针的静态类型(而不是对象的实际类型)来决定调用哪个方法。
delete basePtr;
return 0;
}
// 静态绑定:通过派生类的对象调用方法时,会调用派生类中的方法。
// 方法隐藏:派生类中的方法隐藏了基类中的同名方法。
3) 区别
// 3.1 虚函数 vs 非虚函数
// 虚函数:基类的方法是虚函数,派生类可以覆盖基类的方法,实现多态。
// 非虚函数:基类的方法不是虚函数,派生类可以隐藏基类的方法,但不会实现多态。
// 3.1 总结
// 方法覆盖(Method Overriding):
// 虚函数:基类的方法是虚函数。
// 动态绑定:通过基类的指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数。
// 多态:实现多态,允许通过基类的指针或引用调用派生类的方法。
// 方法隐藏(Method Hiding):
// 非虚函数:基类的方法不是虚函数。
// 静态绑定:通过派生类的对象调用方法时,会调用派生类中的方法,而不是基类中的方法。
// 非多态:不会实现多态,通过基类的指针或引用调用方法时,只会调用基类中的方法。
6.2 静态绑定、动态绑定
静态绑定,是指成员函数的地址在编译期就可以确定,运行时则直接跳转到对应地址执行;
特点
编译时确定:函数调用在编译时就已经确定,不会在运行时改变。
类型信息:编译器根据变量的声明类型来决定调用哪个函数。
效率:静态绑定通常比动态绑定更高效,因为编译器可以直接生成调用特定函数的代码,而不需要在运行时进行额外的查找。
Base* obj = new Derived();
obj->display(); // 调用 Base 的 display 函数
动态绑定则是指编译期不确定,只有到运行时才能找到函数地址,这需要两次额外的寻址指令:第1次找到虚表,第2次从虚表中找到函数地址。
特点
运行时确定:函数调用在运行时根据对象的实际类型决定,而不是在编译时。
虚函数表(V-Table):每个包含虚函数的类都有一个虚函数表(V-Table),用于存储虚函数的地址。对象在内存中包含一个指向其类的 V-Table 的指针,通过这个指针调用虚函数。
灵活性:动态绑定允许通过基类的指针或引用调用派生类的函数,从而实现多态。
Base* obj = new Derived();
obj->display(); // 调用 Derived 的 display 函数
哪些情况会出现动态绑定?答案是只有使用指针或引用调用虚成员函数时才会出现。
动态绑定的实现
动态绑定的实现条件:
类的定义中成员函数声明为虚函数
通过引用或指针来访问对象的虚函数
virtual声明需要注意:
一旦在基类中指定某成员函数为虚函数,那么,不管在派生类中是否给出virtual声明,派生类(以及派生类的派生类,…)中对其重定义的成员函数均为虚函数
重定义: 对派生类中定义的成员函数, 其函数名, 参数个数和类型以及返回值类型与基类的某个虚成员函数相同(override)
使用场景
静态绑定:
非多态场景:当你不需要多态,且希望函数调用尽可能高效时。
简单类型:适用于没有继承层次结构的简单类型。
动态绑定:
多态场景:当你需要通过基类的指针或引用调用派生类的函数时。
继承层次结构:适用于有继承层次结构的类,特别是需要实现多态的场景。
7.含有虚函数的类/抽象类的构造和析构顺序
7.1 含有虚函数的类的构造和析构顺序
// 1. 构造函数的调用顺序
// 当创建一个派生类的对象时,构造函数的调用顺序如下:
// 基类的构造函数:首先调用基类的构造函数。
// 派生类的构造函数:然后调用派生类的构造函数。
// 2. 析构函数的调用顺序
// 当销毁一个派生类的对象时,析构函数的调用顺序如下:
// 派生类的析构函数:首先调用派生类的析构函数。
// 基类的析构函数:然后调用基类的析构函数。
class Base {
public:
Base() {
std::cout << "Base constructor called" << std::endl;
}
virtual ~Base() {
std::cout << "Base destructor called" << std::endl;
}
virtual void display() {
std::cout << "Base display" << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor called" << std::endl;
}
~Derived() {
std::cout << "Derived destructor called" << std::endl;
}
void display() override {
std::cout << "Derived display" << std::endl;
}
};
int main() {
Derived obj;
return 0;
}
// 输出结果
// Base constructor called
// Derived constructor called
// Derived destructor called
// Base destructor called
// 3. 虚函数的调用
// 在构造函数和析构函数中,虚函数的调用与普通函数的调用类似。然而,由于虚函数的多态性,通过基类的指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数。
void callDisplay(Base* obj) {
obj->display(); // 调用虚函数
}
int main() {
Derived obj;
callDisplay(&obj); // 输出 "Derived display"
return 0;
}
// 输出结果
// Derived display
7.2 抽象类的构造和析构顺序
抽象类是包含纯虚函数的类,不能直接实例化。然而,派生类可以继承抽象类,并实现纯虚函数。抽象类的构造和析构顺序与普通类类似。
1. 为什么抽象类不能实例化?
抽象类不能实例化的原因是它包含纯虚函数,而纯虚函数没有实现。纯虚函数的作用是定义接口,要求派生类必须实现这些接口。
如果允许直接实例化抽象类,那么在调用纯虚函数时,程序将不知道调用哪个实现,因为抽象类本身没有提供实现。
2. 总结
实例化:根据类的定义创建一个具体的对象。
抽象类:包含一个或多个纯虚函数的类,不能直接实例化。
派生类:继承自抽象类的类,必须实现抽象类中的纯虚函数,才能被实例化。
3.抽象类不能被直接实例化,但可以创建一个指向派生类对象的指针或引用。这是实现多态的关键机制。通过基类的指针或引用,可以调用派生类的实现,从而实现动态绑定和多态行为。
class AbstractBase {
public:
AbstractBase() {
std::cout << "AbstractBase constructor called" << std::endl;
}
virtual void display() = 0; // 纯虚函数
virtual ~AbstractBase() {
std::cout << "AbstractBase destructor called" << std::endl;
}
};
class Derived : public AbstractBase {
public:
Derived() {
std::cout << "Derived constructor called" << std::endl;
}
void display() override {
std::cout << "Derived display" << std::endl;
}
~Derived() {
std::cout << "Derived destructor called" << std::endl;
}
};
int main() {
Derived obj; // 实例化派生类
AbstractBase* basePtr = &obj; // 创建指向派生类对象的基类指针
basePtr->display(); // 调用派生类的实现
// AbstractBase base; // 编译错误: 不能实例化
// AbstractBase* base = new AbstractBase(); // 编译错误: 不能实例化
AbstractBase* base = new Derived();
return 0;
}
// 输出结果
// AbstractBase constructor called
// Derived constructor called
// Derived display
// Derived destructor called
// AbstractBase destructor called
// AbstractBase* base = new Derived();输出结果: 只有构造,因为指针没有删除,所有没有调用析构函数,需要手动删除
// AbstractBase constructor called
// Derived constructor called
浙公网安备 33010602011771号