QT学习记录(六)——C++学习基础(六)
一、继承基本概念继承是面向对象编程OOP中的一个核心概念特别是在C中。它允许一个类称为派生类或子类继承另一个类称为基类或父类的属性和方法。继承的主要目的是实现代码重用以及建立一种类型之间的层次关系。代码重用子类继承了父类的属性和方法减少了代码的重复编写。扩展性子类可以扩展父类的功能添加新的属性和方法或者重写覆盖现有的方法。多态性通过继承和虚函数C支持多态允许在运行时决定调用哪个函数。在C中继承可以是公有public、保护protected或私有private的这决定了基类成员在派生类中的访问权限。在这个例子中 Vehicle 类公有地继承自 Vehicle 类这意味着所有 Vehicle 类的公有成员在Vehicle 类中也是公有的。#include iostream using namespace std; //基类父类 class Vehicle{ //交通工具车,抽象的概念 public: string type; string contry; string color; double price; int numOfWheel; void run(){ cout 车跑起来了 endl; } void stop(); }; //派生类子类 class Bickle : public Vehicle{ }; //派生类子类 class Roadster : public Vehicle{ //跑车也是抽象比父类感觉上范围缩小了点 public: int stateOfTop; void openTopped(); void pdrifting(); }; int main() { Roadster ftype; ftype.type 捷豹Ftype; ftype.run(); Bickle bike; bike.type 死飞; bike.run(); return 0; }有一个基类 Animal 它定义了所有动物共有的特性和行为。然后我们可以创建几个派生类如 Lion 、Elephant 和 Bird 这些类继承自 Animal 类并添加或修改特定于它们自己的特性和行为。下面定义一个基类Animal#include iostream #include string class Animal { protected: std::string name; int age; public: Animal(std::string n, int a) : name(n), age(a) {} virtual void makeSound() { std::cout name makes a sound. std::endl; } virtual void display() { std::cout Animal: name , Age: age std::endl; } };下面再定义一个派生类Lionclass Lion : public Animal { public: Lion(std::string n, int a) : Animal(n, a) {} void makeSound() override { std::cout name roars. std::endl; } void display() override { std::cout Lion: name , Age: age std::endl; } };下面再定义派生类Elephantclass Elephant : public Animal { public: Elephant(std::string n, int a) : Animal(n, a) {} void makeSound() override { std::cout name trumpets. std::endl; } void display() override { std::cout Elephant: name , Age: age std::endl; } };下面再定义派生类Birdclass Bird : public Animal { public: Bird(std::string n, int a) : Animal(n, a) {} void makeSound() override { std::cout name sings. std::endl; } void display() override { std::cout Bird: name , Age: age std::endl; } };在main函数中使用这些类int main() { Lion lion(Leo, 5); Elephant elephant(Ella, 10); Bird bird(Bella, 2); lion.display(); lion.makeSound(); elephant.display(); elephant.makeSound(); bird.display(); bird.makeSound(); return 0; }Animal 是基类定义了所有动物共有的属性如 name 和 age 和方法如 makeSound 和display 。Lion 、Elephant 和 Bird 是派生类它们继承了 Animal 的特性并根据自身的特性重写了makeSound 和 display 方法。在 main 函数中创建了各种动物的实例并展示了它们的行为。这个例子展示了继承如何使代码更有组织、更易于管理并且如何通过重写基类方法来实现多态性。二、权限对继承的影响在C中访问控制符对继承的影响可以通过下表来清晰地展示。这个表格展示了不同类型的继承 public 、protected 、private 如何影响基类的不同类型成员 public 、protected 、private 在派生类中的访问级别。基类成员类型public继承protected继承private继承publicpublicprotectedprivateprotectedprotectedprotectedprivateprivate不可访问不可访问不可访问public 继承基类的 public 成员在派生类中仍然是 public 的 protected 成员仍然是protected 的。基类的 private 成员在派生类中不可访问。protected 继承基类的 public 和 protected 成员在派生类中都变成 protected 的。基类的 private 成员在派生类中不可访问。private 继承基类的 public 和 protected 成员在派生类中都变成 private 的。基类的private 成员在派生类中不可访问。这个表格提供了一个快速参考帮助理解在不同类型的继承中基类成员的访问级别是如何变化的。记住无论继承类型如何基类的 private 成员始终不可直接在派生类中访问。#include iostream using namespace std; //基类父类 class Vehicle{ //交通工具车,抽象的概念 public: string type; string contry; string color; double price; int numOfWheel; protected: int protectedData; private: int privateData; public: void run(){ cout 车跑起来了 endl; } void stop(); }; //私有继承测试 class TestClass : private Vehicle{ public: void tsetFunc(){ price 10; //基类的公有数据被私有继承后在派生类中权限编程私有只限在类内部使用 } }; //公有继承测试 class Truck : protected Vehicle{ public: void testFunc(){ type 数据测试; //编程了公有权限 protectedData 10; //保持公有权限 privateData 10; //报错了基类的私有成员不管哪种方式的继承都是不可访问的。 } }; //公有继承基类的公有权限和保护权限不变私有成员不能访问 class Bickle : public Vehicle{ public: void testFunc(){ protectedData 10; } }; //派生类子类 class Roadster : public Vehicle{ //跑车也是抽象比父类感觉上范围缩小了点 public: int stateOfTop; void openTopped(); void pdrifting(); }; int main() { TestClass test; test.price 3.3; //报错了基类的公有成员被私有继承后降为私有权限 Truck t; t.type 测试; //报错了基类的公有成员被保护继承后降为保护权限 t.protectedData 10; //从报错信息看出保护继承造成基类的保护成员还是保持保护权限 Roadster ftype; ftype.type 捷豹Ftype; ftype.run(); Bickle bike; bike.type 死飞; bike.run(); return 0; }三、基类构造函数在C中派生类可以通过其构造函数的初始化列表来调用基类的构造函数。这是在构造派生类对象时初始化基类部分的标准做法。当创建派生类的对象时基类的构造函数总是在派生类的构造函数之前被调用。如果没有明确指定将调用基类的默认构造函数。如果基类没有默认构造函数或者你需要调用一个特定的基类构造函数就需要在派生类构造函数的初始化列表中明确指定。假设我们有一个基类 Base 和一个派生自 Base 的类 Derived class Base { public: int data; Base(int x) { std::cout Base constructor with x x std::endl; } }; class Derived : public Base { public: double ydata; Derived(int x, double y) : Base(x) { // 调用 Base 类的构造函数 std::cout Derived constructor with y y std::endl; } }; int main() { Derived obj(10, 3.14); // 首先调用 Base(10)然后调用 Derived 的构造函数 return 0; }Base 类有一个接受一个整数参数的构造函数。Derived 类继承自 Base 它的构造函数接受一个整数和一个双精度浮点数。在其初始化列表中它调用 Base 类的构造函数并传递整数参数。当 Derived 类的对象被创建时首先调用 Base 类的构造函数然后调用 Derived 类的构造函数。通过这种方式派生类能够确保其基类部分被正确初始化。在继承层次结构中这是非常重要的特别是当基类需要一些特定的初始化操作时。#include iostream using namespace std; //基类父类 class Vehicle{ //交通工具车,抽象的概念 public: string contry; double price; Vehicle(string contry, double price){ cout 基类的构造函数被调用 endl; this-contry contry; this-price price; }; void run(){ cout 车跑起来了 endl; } void stop(); }; //派生类子类 class Roadster : public Vehicle{ //跑车也是抽象比父类感觉上范围缩小了点 public: int stateOfTop; Roadster(string contry, double price, int state) : Vehicle(contry, price){ cout 派生类的构造函数被调用 endl; stateOfTop state; } void openTopped(); void pdrifting(); }; int main() { Roadster FTYPE(法国,70,0); return 0; }四、虚函数在C中 virtual 和 override 关键字用于支持多态尤其是在涉及类继承和方法重写的情况下。正确地理解和使用这两个关键字对于编写可维护和易于理解的面向对象代码至关重要。virtual 关键字使用场景在基类中声明虚函数。目的允许派生类重写该函数实现多态。行为当通过基类的指针或引用调用一个虚函数时调用的是对象实际类型的函数版本。class Base { public: virtual void func() { std::cout Function in Base std::endl; } };override 关键字使用场景在派生类中重写虚函数。目的明确指示函数意图重写基类的虚函数。行为确保派生类的函数确实重写了基类中的一个虚函数。如果没有匹配的虚函数编译器会报错。class Derived : public Base { public: void func() override { std::cout Function in Derived std::endl; } };只在派生类中使用 override override 应仅用于派生类中重写基类的虚函数。虚析构函数如果类中有虚函数通常应该将析构函数也声明为虚的。默认情况下成员函数不是虚的在C中成员函数默认不是虚函数。只有显式地使用 virtual关键字才会成为虚函数。继承中的虚函数一旦在基类中声明为虚函数该函数在所有派生类中自动成为虚函数无论是否使用 virtual 关键字。正确使用 virtual 和 override 关键字有助于清晰地表达程序员的意图并利用编译器检查来避免常见的错误如签名不匹配导致的非预期的函数重写。五、多重继承在C中多重继承是一种允许一个类同时继承多个基类的特性。这意味着派生类可以继承多个基类的属性和方法。多重继承增加了语言的灵活性但同时也引入了额外的复杂性特别是当多个基类具有相同的成员时。在多重继承中派生类继承了所有基类的特性。这包括成员变量和成员函数。如果不同的基类有相同名称的成员则必须明确指出所引用的是哪个基类的成员。假设有两个基类 ClassA 和 ClassB 以及一个同时从这两个类继承的派生类 Derived 在这个示例中 Derived 类同时继承了 ClassA 和 ClassB 。因此它可以使用这两个类中定义的方法。class ClassA { public: void displayA() { std::cout Displaying ClassA std::endl; } }; class ClassB { public: void displayB() { std::cout Displaying ClassB std::endl; } }; class Derived : public ClassA, public ClassB { public: void display() { displayA(); // 调用 ClassA 的 displayA displayB(); // 调用 ClassB 的 displayB } }; int main() { Derived obj; obj.displayA(); // 调用 ClassA 的 displayA obj.displayB(); // 调用 ClassB 的 displayB obj.display(); // 调用 Derived 的 display return 0; }菱形继承问题如果两个基类继承自同一个更高层的基类这可能导致派生类中存在两份基类的副本称为菱形继承或钻石继承问题。这可以通过虚继承来解决。复杂性多重继承可能会使类的结构变得复杂尤其是当继承层次较深或类中有多个基类时。设计考虑虽然多重继承提供了很大的灵活性但过度使用可能导致代码难以理解和维护。在一些情况下使用组合或接口纯虚类可能是更好的设计选择。多重继承是C的一个强大特性但应谨慎使用。合理地应用多重继承可以使代码更加灵活和强大但不当的使用可能导致设计上的问题和维护困难。六、虚继承虚继承是C中一种特殊的继承方式主要用来解决多重继承中的菱形继承问题。在菱形继承结构中一个类继承自两个具有共同基类的类时会导致共同基类的成员在派生类中存在两份拷贝这不仅会导致资源浪费还可能引起数据不一致的问题。虚继承通过确保共同基类的单一实例存在于继承层次中来解决这一问题。菱形继承问题示例考虑以下的类结构class Base { public: int data; }; class Derived1 : public Base { // 继承自 Base }; class Derived2 : public Base { // 继承自 Base }; class FinalDerived : public Derived1, public Derived2 { // 继承自 Derived1 和 Derived2 };在这个例子中 FinalDerived 类通过 Derived1 和 Derived2 间接地继承自 Base 类两次。因此它包含了两份 Base 的成员拷贝。使用虚继承解决菱形继承问题要解决这个问题应使用虚继承class Base { public: int data; }; class Derived1 : virtual public Base { // 虚继承 Base }; class Derived2 : virtual public Base { // 虚继承 Base }; class FinalDerived : public Derived1, public Derived2 { // 继承自 Derived1 和 Derived2 };通过将 Derived1 和 Derived2 对 Base 的继承声明为虚继承 virtual public Base FinalDerived 类中只会有一份 Base 类的成员。无论通过 Derived1 还是 Derived2 的路径访问的都是同一个 Base 类的成员。特点和注意事项初始化虚基类在使用虚继承时虚基类如上例中的 Base 类只能由最派生的类如FinalDerived 初始化。内存布局虚继承可能会改变类的内存布局通常会增加额外的开销比如虚基类指针。设计考虑虚继承应谨慎使用因为它增加了复杂性。在实际应用中如果可以通过其他设计如组合或接口避免菱形继承那通常是更好的选择。虚继承是C语言中处理复杂继承关系的一种重要机制但它也带来了一定的复杂性和性能考虑。正确地使用虚继承可以帮助你建立清晰、有效的类层次结构。学习内容描述继承的基础理解基类和派生类的概念以及如何通过继承扩展类功能。了解不同继承类型公有、私有、保护及其影响。构造函数和析构函数在继承中的行为学习派生类如何调用基类的构造函数和析构函数以及它们的调用顺序。访问控制和继承理解公有、私有和保护继承对成员访问权限的影响。掌握继承中的访问修饰符public, protected, private。函数重写和多态学习多态和如何通过虚函数实现它了解如何重写基类方法以及纯虚函数和抽象类的概念。虚继承和解决菱形问题理解菱形继承问题及其解决方式学习如何使用虚继承。C11 新特性中的继承相关内容理解和应用 override 和 final 关键字了解移动语义在继承中的应用。设计原则与最佳实践学习正确使用继承的方法区分何时使用继承何时使用组合以及面向对象设计原则的应用。实际案例分析通过分析和编写实际代码示例加深理解研究设计模式中继承的应用。