继承
继承是面向对象编程的一个基本特性,它允许创建一个类(派生类)继承另一个类(基类)的属性和方法。继承支持代码重用,能够建立一个类层次结构。
- 基类(父类):被其他类继承的类。
- 派生类(子类):继承基类的属性和方法的类。
继承的类型
- 公有继承:基类的公有成员和保护成员在派生类中保持原有的访问级别。
- 保护继承:基类的公有成员和保护成员在派生类中成为保护成员。
- 私有继承:基类的公有成员和保护成员在派生类中成为私有成员。
示例
#include <iostream>
using namespace std;
// 基类
class Animal {
public:
void eat() {
cout << "I can eat!" << endl;
}
};
// 派生类
class Dog : public Animal {
public:
void bark() {
cout << "I can bark! Woof woof!" << endl;
}
};
int main() {
Dog myDog;
myDog.eat(); // 调用基类的方法
myDog.bark(); // 调用派生类的方法
return 0;
}
多态
多态是面向对象编程的另一个核心概念,允许使用相同的接口表示不同的基本形态(数据类型)。多态性意味着有多重形式。在C++中,多态通常通过虚函数实现。
- 静态多态:通过函数重载和运算符重载实现。
- 动态多态:通过虚函数和基类指针或引用实现。
动态多态的关键点
- 虚函数:在基类中使用
virtual
关键字声明的函数,允许在派生类中被重写。 - 纯虚函数:在基类中声明但没有定义的虚函数,用来创建抽象基类。
示例
#include <iostream>
using namespace std;
// 抽象基类
class Shape {
public:
// 纯虚函数
virtual void draw() = 0;
};
// 派生类
class Circle : public Shape {
public:
// 重写draw函数
void draw() override {
cout << "Drawing circle..." << endl;
}
};
class Square : public Shape {
public:
// 重写draw函数
void draw() override {
cout << "Drawing square..." << endl;
}
};
void renderShape(Shape& shape) {
shape.draw();
}
int main() {
Circle circle;
Square square;
renderShape(circle); // Drawing circle...
renderShape(square); // Drawing square...
return 0;
}
这个例子展示了如何通过虚函数实现多态,Shape
是一个抽象基类,具有一个纯虚函数draw
。Circle
和Square
类继承自Shape
并重写了draw
方法,展现了不同的行为。
继承和多态是面向对象编程的两个核心概念,它们使得代码更加模块化,增强了代码的复用性和扩展性。
C++访问说明符与继承
在C++中,访问说明符决定了类成员的访问权限。这些访问权限对于继承尤为重要,因为它们影响派生类可以访问的基类成员。C++提供了三种访问说明符:public
、protected
、和private
。
访问说明符
- public:成员可以被任何外部代码访问。
- protected:成员不能被外部代码直接访问,但可以被派生类访问。
- private:成员只能被其所在类的成员函数和友元函数访问,即使是派生类也不能访问。
继承与访问说明符
继承类型(公有、保护、私有)决定了基类成员在派生类中的访问权限:
- 公有继承(public):基类的公有成员在派生类中仍为公有;保护成员在派生类中仍为保护;私有成员在派生类中不可访问,但可以通过基类的公有或保护成员函数访问。
- 保护继承(protected):基类的公有和保护成员在派生类中变为保护成员;私有成员在派生类中不可访问。
- 私有继承(private):基类的公有和保护成员在派生类中变为私有成员;私有成员在派生类中不可访问。
示例
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class PublicDerived : public Base {
// publicVar 是公有的
// protectedVar 是保护的
// privateVar 在PublicDerived中不可访问
};
class ProtectedDerived : protected Base {
// publicVar 成为保护的
// protectedVar 仍然是保护的
// privateVar 在ProtectedDerived中不可访问
};
class PrivateDerived : private Base {
// publicVar 成为私有的
// protectedVar 成为私有的
// privateVar 在PrivateDerived中不可访问
};
重要点
- 访问说明符影响继承关系中成员变量和函数的可见性。
- 公有继承保留了基类的接口部分,是最常见的继承方式。
- 保护继承和私有继承更多地被用于实现细节的隐藏和封装,而非接口的继承。
- 选择适当的访问说明符和继承类型对于设计良好的类层次结构至关重要。
理解访问说明符和继承之间的关系对于在C++中设计和实现面向对象的程序非常重要,它们共同定义了类之间的关系和相互作用。
抽象类和接口在C++中的概念
在面向对象的C++编程中,抽象类和接口是定义类的层次结构、强制实现特定接口、并实现多态的重要工具。
抽象类
抽象类是至少包含一个纯虚函数的类,不能被实例化。它通常用于定义基类,其中一些功能在基类层次结构中更高层次地实现。
- 纯虚函数:使用
= 0
语法定义,表示没有实现体的函数。 - 目的:提供一个基础框架,让派生类实现具体功能。
示例
#include <iostream>
using namespace std;
class Shape {
public:
// 纯虚函数
virtual void draw() const = 0;
virtual ~Shape() {} // 虚析构函数,确保派生类的析构函数被调用
};
class Circle : public Shape {
public:
void draw() const override {
cout << "Drawing a circle." << endl;
}
};
class Square : public Shape {
public:
void draw() const override {
cout << "Drawing a square." << endl;
}
};
void renderShape(const Shape& shape) {
shape.draw();
}
int main() {
Circle circle;
Square square;
renderShape(circle);
renderShape(square);
return 0;
}
接口
在C++中,接口可以通过纯抽象类实现,即一个不包含成员变量且所有成员函数都是纯虚函数的类。
- 目的:定义一个完全抽象的接口,无任何实现细节。
- C++没有直接的“interface”关键字,但纯抽象类的概念与接口相同。
示例
#include <iostream>
using namespace std;
// 接口
class Printable {
public:
virtual void print() const = 0;
virtual ~Printable() {} // 虚析构函数
};
class Document : public Printable {
public:
void print() const override {
cout << "Printing document..." << endl;
}
};
void printItem(const Printable& item) {
item.print();
}
int main() {
Document doc;
printItem(doc);
return 0;
}
关键点
- 抽象类和接口是通过纯虚函数实现的,它们定义了一个类必须遵守的规则和结构。
- 抽象类可以包含非纯虚函数的实现,提供一些基本的功能或默认行为。
- 接口(纯抽象类)强制派生类实现特定的方法,但不提供任何实现。
- 使用抽象类和接口可以实现多态,允许以统一的方式处理不同类型的对象。
理解和使用抽象类与接口是创建清晰、可维护和可扩展的C++应用程序的关键。
虚析构函数在C++中的使用
在面向对象编程中,析构函数用于对象被销毁时执行清理工作。在C++中,当你在基类中使用多态时,使用虚析构函数变得尤为重要。
为什么需要虚析构函数
当通过基类指针删除派生类对象时,如果基类的析构函数不是虚的,则只会调用基类的析构函数。这意味着派生类的析构函数不会被执行,可能会导致资源泄露。
- 虚析构函数确保当删除指向派生类对象的基类指针时,派生类的析构函数会被正确调用。
- 即使基类析构函数没有任何实现,也应将其声明为虚函数,以保证资源的正确释放。
示例
不使用虚析构函数的情况:
#include <iostream>
using namespace std;
class Base {
public:
~Base() {
cout << "Base destructor called." << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor called." << endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 只会调用Base的析构函数
return 0;
}
使用虚析构函数:
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { // 虚析构函数
cout << "Base destructor called." << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor called." << endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 先调用Derived的析构函数,然后是Base的析构函数
return 0;
}
关键点
- 当类设计为基类时,如果类中有虚函数,则析构函数也应该是虚的。
- 虚析构函数保证了派生类对象通过基类指针被正确销毁,避免了内存泄露。
- 即使不打算直接创建基类的实例,将析构函数声明为虚函数也是一个好习惯。
理解并正确使用虚析构函数对于资源管理和防止内存泄漏至关重要。它是C++中多态性和资源管理的重要组成部分。