1. 初始化列表
初始化列表在三种情况下必须使用:
- 继承关系下,父类没有无参构造函数情况
#include <iostream>using namespace std;class Base{
public:string name;int num;Base(){cout<< "基类的无参构造..." <<endl;}Base(string name){cout<< "基类的有参构造1..." <<endl;}Base(string name,int num){cout<< "基类的有参构造2..." <<endl;}};class Derived : public Base {
public:Derived(){cout<< "派生类的无参构造..." <<endl;}Derived(string name):Base(name){cout<< "派生类的有参构造1..." <<endl;}Derived(string name,int num):Base(name,num){cout<< "派生类的有参构造2..." <<endl;}
};int main() {Derived d;cout << "------------------------" << endl;Derived d0("zhangfei");Derived d1("libai",24);return 0;
}
运行结果:
基类的无参构造...
派生类的无参构造...
------------------------
基类的有参构造1...
派生类的有参构造1...
基类的有参构造2...
派生类的有参构造2...
- 需要初始化const修饰的类成员或初始化引用成员数据
#include <iostream>using namespace std;class person{public:const int ID;int & age;person(int no , int age):ID(no),age(age){cout << "执行构造函数..." <<endl;}
};int main(){person p(78 , 28);cout << p.ID << " , " << p.age << endl;return 0 ;
}
- 需要初始化的数据成员是对象,并且对应的类没有无参构造函数
#include <iostream>using namespace std;class Base{
public:string name;int num;Base(){cout<< "基类的无参构造..." <<endl;}Base(string name){cout<< "基类的有参构造1..." <<endl;}Base(string name,int num){cout<< "基类的有参构造2..." <<endl;}};class Derived{
public:int no;Base b;
// Derived(){
// cout<< "派生类的无参构造..." <<endl;
// }Derived(string name):b(name){cout<< "派生类的有参构造1..." <<endl;}Derived(string name,int num):b(name,num){cout<< "派生类的有参构造2..." <<endl;}
};int main() {cout << "------------------------" << endl;Derived d0("zhangfei");cout << "------------------------" << endl;Derived d1("libai",24);return 0;
}
运行结果:
------------------------
基类的有参构造1...
派生类的有参构造1...
------------------------
基类的有参构造2...
派生类的有参构造2...
or
#include <iostream>using namespace std;class Base{
public:string name;int num;Base(){cout<< "基类的无参构造..." <<endl;}Base(string name){cout<< "基类的有参构造1..." <<endl;}Base(string name,int num){cout<< "基类的有参构造2..." <<endl;}};class Derived{
public:int no;Base b;Derived():b(){cout<< "派生类的无参构造..." <<endl;}Derived(string name):b(name){cout<< "派生类的有参构造1..." <<endl;}Derived(string name,int num):b(name,num){cout<< "派生类的有参构造2..." <<endl;}
};int main() {Derived d;cout << "------------------------" << endl;Derived d0("zhangfei");cout << "------------------------" << endl;Derived d1("libai",24);return 0;
}
运行结果:
基类的无参构造...
派生类的无参构造...
------------------------
基类的有参构造1...
派生类的有参构造1...
------------------------
基类的有参构造2...
派生类的有参构造2...
2. 重写父类同名函数
在C++中,子类可以重写(override)父类的同名函数。这被称为函数的覆盖(function overriding)。当子类重写父类的函数时,它必须具有相同的名称、参数列表和返回类型。
- 特点:
- 多态性:通过重写父类函数,子类对象可以根据实际类型来调用不同的函数实现,实现多态性。
- 继承性:子类继承了父类的函数,并且可以对其进行更改或增加新的功能。
- 使用场景:
- 扩展功能:子类可以通过重写父类函数来添加额外的行为或修改原有行为,从而实现功能扩展。
- 自定义实现:子类可以根据自己的需求提供不同于父类的实现逻辑。
- 适应特定情境:根据特定场景需要,在子类中针对某些特殊情况重新定义父类方法。
代码:
#include <iostream>// 父类
class Shape {
public:virtual void draw() {std::cout << "绘制形状" << std::endl;}
};// 子类 Circle
class Circle : public Shape {
public:void draw() override { // 使用 override 关键字表示重写std::cout << "绘制圆形" << std::endl;}
};// 子类 Rectangle
class Rectangle : public Shape {
public:void draw() override {std::cout << "绘制矩形" << std::endl;}
};int main() {Shape* shape1 = new Circle();shape1->draw(); // 输出:绘制圆形Shape* shape2 = new Rectangle();shape2->draw(); // 输出:绘制矩形delete shape1;delete shape2;return 0;
}
3. 多继承
在C++中,子类多继承是指一个派生类从多个基类继承特性和行为。这意味着一个子类可以同时拥有多个父类的成员和方法。
- 特点
- 子类可以获得多个基类的属性和方法,增强了代码复用性。
- 多继承可以构建更复杂的继承关系,允许在一个子类中结合不同的功能。
- 多继承提供了更大的灵活性,使得对象之间可以共享接口和实现。
- 使用场景
- 当存在一种逻辑上属于不同概念、但具有共同行为的情况时,可以使用多继承来实现。
- 当需要通过组合不同功能或角色来创建一个新的对象时,也可以考虑使用多继承。
- 代码实现
#include <iostream>// 基类A
class A {
public:void funcA() {std::cout << "This is function A." << std::endl;}
};// 基类B
class B {
public:void funcB() {std::cout << "This is function B." << std::endl;}
};// 子类C从基类A和基类B进行多继承
class C : public A, public B {
public:void funcC() {std::cout << "This is function C." << std::endl;}
};int main() {C c;c.funcA(); // 调用基类A的函数c.funcB(); // 调用基类B的函数c.funcC(); // 调用子类C自身的函数return 0;
}
3.1 多继承的构造函数
多继承形式下的构造函数和单继承形式基本相同,只是要在子类的构造函数中调用多个父类的构造函数 。 他们调用的顺序由定义子类时,继承顺序 决定。
#include <iostream>using namespace std;// 基类A
class A {
public:A(){cout<< "A类的无参构造..." <<endl;}~A(){cout<< "A类的析构构造..." <<endl;}
};// 基类B
class B {
public:B(){cout<< "B类的无参构造..." <<endl;}~B(){cout<< "B类的析构构造..." <<endl;}
};// 子类C从基类A和基类B进行多继承
class C : public A, public B {
public:C(){cout<< "C类的无参构造..." <<endl;}~C(){cout<< "C类的析构构造..." <<endl;}
};int main() {C c;return 0;
}
运行结果:
A类的无参构造...
B类的无参构造...
C类的无参构造...
C类的析构构造...
B类的析构构造...
A类的析构构造...
4. 类的前置声明
C++中的类前置声明是指在使用类之前提前声明类的存在,而不需要完整地定义类。它可以在某些场景下用于解决循环依赖或减少编译时间的问题。
- 特点:
- 允许在程序中引用尚未完整定义的类。
- 只需要提供类名和分号即可进行声明,无需包含类的详细实现。
- 前置声明通常用于解决循环依赖或减少编译时间。
- 使用场景
- 解决循环依赖
当两个或多个类相互引用时,可以使用前置声明来解决循环依赖的问题。通过提前声明类的存在,可以在类定义之前使用该类的指针或引用。
- 注意:
B b // 报错,是因为不知道前面的声明的B类有没有无参构造
B* b; // 这是指针,它只是一个地址而已,不会执行无参构造。
B& b; // 这是别名,不创建对象,所以不会执行无参构造。
#include <iostream>class B; // 前置声明class A {
private:B* b;
public:void setB(B* obj) {b = obj;}void doSomething();
};class B {
private:A* a;
public:void setA(A* obj) {a = obj;}void doSomething();
};void A::doSomething() {std::cout << "a->doSomething()" << std::endl;if (b) {b->doSomething();}
}void B::doSomething() {std::cout << "b->doSomething()" << std::endl;if (a) {a->doSomething();}
}int main() {A a;B b;a.setB(&b);b.setA(&a);std::cout << "-----------------------------" << std::endl;a.doSomething();b.doSomething();return 0;
}
类A和类B相互引用,并使用了前置声明。在类A中,成员变量B* b是一个指针类型,不会执行B类的无参构造函数,因此不需要知道B类是否有无参构造函数。而如果将成员变量声明为B& b,则是一个引用类型,也不会执行B类的无参构造函数。
在类定义之后,我们定义了类A和类B的成员函数doSomething()。在A::doSomething()中,调用了b->doSomething(),即调用了B类的成员函数。同样,在B::doSomething()中,调用了a->doSomething(),即调用了A类的成员函数。
在main()函数中,我们创建了类A和类B的对象,并通过setB()和setA()方法设置了它们之间的循环依赖关系。最后,调用了a.doSomething()和b.doSomething()来触发成员函数的调用。
- 提高编译速度
在一些情况下,完整的类定义可能不是必需的,例如在函数声明中只需要使用类的指针或引用,而不需要访问类的成员。这时,使用前置声明可以减少编译时间,因为编译器不需要包含和处理完整的类定义。
#include <iostream>// 前置声明
class SomeClass;void useSomeClass(SomeClass* obj);int main() {SomeClass* obj = new SomeClass();useSomeClass(obj);delete obj;return 0;
}// 完整类定义
class SomeClass {
public:void doSomething();
};void useSomeClass(SomeClass* obj) {obj->doSomething();
}void SomeClass::doSomething() {std::cout << "Doing something..." << std::endl;
}
5. 继承的使用场景
- 代码重用和封装:当多个类具有相似的属性和行为时,可以将这些共同特征提取到一个基类中,派生类继承基类以获得这些共同特征,并在派生类中添加额外的特定功能。
class Animal {
public:void eat() {std::cout << "Animal is eating." << std::endl;}
};class Dog : public Animal {
public:void bark() {std::cout << "Dog is barking." << std::endl;}
};int main() {Dog dog;dog.eat(); // 继承自Animal类dog.bark(); // Dog类自身的函数return 0;
}
- 实现多态性:通过基类的指针或引用,可以以统一的方式操作不同的派生类对象,实现多态性。这样可以在运行时动态地选择调用不同派生类的特定函数。
class Shape {
public:virtual void draw() {std::cout << "Drawing a shape." << std::endl;}
};class Circle : public Shape {
public:void draw() override {std::cout << "Drawing a circle." << std::endl;}
};class Rectangle : public Shape {
public:void draw() override {std::cout << "Drawing a rectangle." << std::endl;}
};int main() {Shape* shape1 = new Circle();Shape* shape2 = new Rectangle();shape1->draw(); // 动态调用Circle类的draw函数shape2->draw(); // 动态调用Rectangle类的draw函数delete shape1;delete shape2;return 0;
}
- 扩展和特化功能:通过继承,可以在派生类中添加额外的成员变量和成员函数,以实现对基类功能的扩展和特化。
class Vehicle {
protected:int wheels;public:Vehicle(int numWheels) : wheels(numWheels) {}void printWheels() {std::cout << "Number of wheels: " << wheels << std::endl;}
};class Car : public Vehicle {
public:Car() : Vehicle(4) {}void startEngine() {std::cout << "Engine started." << std::endl;}
};int main() {Car car;car.printWheels(); // 继承自Vehicle类car.startEngine(); // Car类自身的函数return 0;
}