c++ 接口/多态
目录
接口的通用定义
特点:
C++ 中的接口
接口的作用
接口与抽象类的区别
什么是多态?
多态的类型
1. 编译时多态
2. 运行时多态
多态的实现原理
注意事项
在编程中,接口(Interface) 是一个抽象概念,用于定义一组行为规范或功能约定,而不提供具体实现。它是一种契约,规定了实现它的类必须提供哪些方法或功能。接口广泛用于面向对象编程(OOP)中,以实现模块化、解耦和多态。
下面我会从通用概念和 C++ 的角度详细解释“接口”。
接口的通用定义
接口可以看作是类与类之间通信的桥梁。它只声明方法(或函数)的签名(如名称、参数、返回值类型),而不关心这些方法如何实现。实现接口的类必须按照接口的定义提供具体实现。
特点:
-
抽象性:接口本身不包含实现细节。
-
强制性:实现接口的类必须实现所有接口中定义的方法。
-
多态性:通过接口类型引用具体实现,可以在运行时动态调用不同类的行为。
比喻:接口就像一份合同,规定了“做什么”(what),但不关心“怎么做”(how)。比如,一个“电源插座”接口定义了“提供220V交流电”的规范,具体是水力发电还是太阳能发电由实现者决定。
C++ 中的接口
C++ 没有像 Java 或 C# 那样的显式 interface 关键字,但通过纯虚函数和抽象类,可以实现接口的功能。具体来说:
-
一个类中所有函数都是纯虚函数(virtual ... = 0)的抽象类,通常被用作接口。
-
派生类继承这个抽象类并实现所有纯虚函数。
示例:C++ 中的接口
#include <iostream>
using namespace std;
// 接口(抽象基类)
class Printable {
public:
virtual void print() const = 0; // 纯虚函数
virtual ~Printable() {} // 虚析构函数,确保正确清理
};
// 实现接口的类
class Book : public Printable {
private:
string title;
public:
Book(const string& t) : title(t) {}
void print() const override {
cout << "Book: " << title << endl;
}
};
class Article : public Printable {
private:
string author;
public:
Article(const string& a) : author(a) {}
void print() const override {
cout << "Article by: " << author << endl;
}
};
int main() {
Printable* items[2];
items[0] = new Book("C++ Primer");
items[1] = new Article("John Doe");
for (int i = 0; i < 2; i++) {
items[i]->print(); // 多态调用
delete items[i];
}
return 0;
}
输出:
Book: C++ Primer
Article by: John Doe
在这个例子中:
-
Printable 是一个接口,定义了 print 方法。
-
Book 和 Article 是具体类,实现了 Printable 接口。
-
通过 Printable* 指针,可以统一调用不同类的 print 方法,实现多态。
接口的作用
-
解耦合:
调用方只依赖接口,不依赖具体实现。例如,main 函数不需要知道 Book 或 Article 的细节,只需知道它们实现了 Printable。
-
可扩展性:
新增一个实现类(如 Magazine)时,无需修改现有代码,只要它实现 Printable 接口即可。
-
多态性:
通过基类指针或引用调用派生类的实现,动态绑定到正确的函数。
-
规范化:
强制所有实现类提供一致的行为(如 print 方法)。
接口与抽象类的区别
在 C++ 中,接口和抽象类都依赖纯虚函数,但它们有细微区别:
-
接口:通常只包含纯虚函数,纯粹定义行为规范,没有数据成员或实现。
-
抽象类:可以包含纯虚函数、普通虚函数甚至具体实现,可能还有数据成员,用于提供部分默认行为。
示例(抽象类 vs 接口):
// 接口
class ILog {
public:
virtual void log(const string& message) = 0;
virtual ~ILog() {}
};
// 抽象类
class Logger {
protected:
string prefix;
public:
Logger(const string& p) : prefix(p) {}
virtual void log(const string& message) = 0; // 纯虚函数
void info(const string& msg) { // 提供默认实现
log(prefix + " INFO: " + msg);
}
};
什么是多态?
多态,字面意思是“多种形态”,在编程中指的是同一个接口或方法可以在不同的对象上表现出不同的行为。换句话说,多态允许用统一的接口调用不同类的实现,而具体执行哪个实现取决于对象的实际类型。
通俗解释:多态就像一个遥控器,按下“播放”按钮,不同设备(电视、音响、DVD播放器)会有不同的反应,但你只需要知道“按播放”就行,不用管背后是哪种设备。
多态的类型
在 C++ 中,多态主要分为两种:
-
编译时多态(Compile-time Polymorphism)
-
通过**函数重载(Function Overloading)和运算符重载(Operator Overloading)**实现。
-
在编译时就确定调用哪个函数。
-
-
运行时多态(Run-time Polymorphism)
-
通过**虚函数(Virtual Function)**和继承实现。
-
在运行时根据对象的实际类型动态决定调用哪个函数。
-
1. 编译时多态
编译时多态是静态的,依赖函数名相同但参数不同的重载机制。
示例:函数重载
#include <iostream>
using namespace std;
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
};
int main() {
Calculator calc;
cout << calc.add(2, 3) << endl; // 调用 int 版本,输出 5
cout << calc.add(2.5, 3.7) << endl; // 调用 double 版本,输出 6.2
return 0;
}
这里 add 函数根据参数类型在编译时选择合适的版本。
示例:运算符重载
class Complex {
private:
double real, imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
Complex operator+(const Complex& other) {
return Complex(real + other.real, imag + other.imag);
}
void print() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex a(1.0, 2.0), b(3.0, 4.0);
Complex c = a + b; // 重载了 + 运算符
c.print(); // 输出 4 + 6i
return 0;
}
+ 运算符被重载,编译器根据上下文选择实现。
2. 运行时多态
运行时多态是动态的,依赖继承和虚函数机制。通过基类指针或引用调用派生类的函数,具体调用哪个版本在运行时决定。
示例:虚函数实现多态
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() { // 虚函数
cout << "Some generic animal sound" << endl;
}
virtual ~Animal() {} // 虚析构函数
};
class Dog : public Animal {
public:
void sound() override {
cout << "Woof" << endl;
}
};
class Cat : public Animal {
public:
void sound() override {
cout << "Meow" << endl;
}
};
int main() {
Animal* animals[2];
animals[0] = new Dog();
animals[1] = new Cat();
for (int i = 0; i < 2; i++) {
animals[i]->sound(); // 运行时根据对象类型调用
delete animals[i];
}
return 0;
}
输出:
Woof
Meow
-
Animal* 指针指向 Dog 或 Cat 对象,调用 sound() 时根据实际对象类型执行对应版本。
-
这是运行时多态的核心,依赖虚函数表(vtable)实现。
没有虚函数的对比
如果去掉 virtual:
class Animal {
public:
void sound() {
cout << "Some generic animal sound" << endl;
}
};
class Dog : public Animal {
public:
void sound() {
cout << "Woof" << endl;
}
};
int main() {
Animal* ptr = new Dog();
ptr->sound(); // 输出 "Some generic animal sound"
delete ptr;
return 0;
}
没有 virtual,调用的是 Animal 的版本,因为指针类型是 Animal*,这就是静态绑定。
多态的实现原理
运行时多态依赖虚函数表(vtable):
-
每个包含虚函数的类有一个虚函数表,存储虚函数的地址。
-
每个对象包含一个指向其类虚函数表的指针(vptr)。
-
调用虚函数时,通过 vptr 查找表中的函数地址,动态调用。
过程:
-
编译器在类中插入 vptr。
-
对象创建时,vptr 被初始化为指向对应类的 vtable。
-
调用虚函数时,程序通过 vptr 找到并执行正确的函数。
注意事项
-
性能开销:运行时多态通过 vtable 实现,有少量内存和间接调用的开销。
-
虚函数要求:必须通过指针或引用调用,对象直接调用仍是静态绑定。
-
虚析构函数:基类应有虚析构函数,避免删除派生类对象时资源泄漏。