一、封装
1.1 定义
封装是将数据(属性)和对数据的操作(方法)捆绑在一起,并隐藏内部实现细节,只暴露必要的接口给外部使用。
1.2 主要特点
-
数据隐藏:将数据设为私有,只能通过公共方法访问
-
接口暴露:提供公共方法来操作数据
-
实现隔离:内部实现变化不影响外部使用
1.3 优点
-
提高安全性:防止数据被意外修改
-
易于维护:内部实现变化不影响外部代码
-
模块化:代码组织更清晰
class BankAccount {
private: // 数据隐藏
double balance;
string accountNumber;
public: // 接口暴露
BankAccount(string accNum, double initialBalance) {
accountNumber = accNum;
balance = initialBalance;
}
// 公共接口方法
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
double getBalance() const {
return balance; // 只读访问
}
};
// 使用
BankAccount account("12345", 1000);
account.deposit(500); // 通过接口操作
// account.balance = 10000; // 错误:不能直接访问私有数据
1.4 访问权限概述
| 访问修饰符 | 类内部 | 派生类 | 类外部 |
|---|---|---|---|
private | ✅ 可访问 | ❌ 不可访问 | ❌ 不可访问 |
protected | ✅ 可访问 | ✅ 可访问 | ❌ 不可访问 |
public | ✅ 可访问 | ✅ 可访问 | ✅ 可访问 |
1.4.1 private(私有)
-
最严格的访问控制
-
只能在本类内部访问
-
派生类和外部代码都不能直接访问
class Base {
private:
int privateVar; // 私有成员变量
void privateFunc() { // 私有成员函数
cout << "Private function" << endl;
}
public:
Base() : privateVar(10) {}
void accessPrivate() {
// 类内部可以访问私有成员
privateVar = 20; // ✅ 可访问
privateFunc(); // ✅ 可访问
}
};
class Derived : public Base {
public:
void tryAccess() {
// privateVar = 30; // ❌ 错误:派生类不能访问基类私有成员
// privateFunc(); // ❌ 错误:派生类不能访问基类私有成员
}
};
int main() {
Base obj;
// obj.privateVar = 40; // ❌ 错误:外部不能访问私有成员
// obj.privateFunc(); // ❌ 错误:外部不能访问私有成员
obj.accessPrivate(); // ✅ 可访问:通过公有方法间接访问
return 0;
}
1.4.2 protected(保护)
-
中等访问控制
-
本类和派生类可以访问
-
外部代码不能直接访问
class Base {
protected:
int protectedVar; // 保护成员变量
void protectedFunc() { // 保护成员函数
cout << "Protected function" << endl;
}
private:
int privateVar;
public:
Base() : protectedVar(10), privateVar(20) {}
void show() {
cout << "Protected: " << protectedVar << endl; // ✅ 可访问
protectedFunc(); // ✅ 可访问
}
};
class Derived : public Base {
public:
void accessProtected() {
protectedVar = 30; // ✅ 可访问:派生类可以访问基类保护成员
protectedFunc(); // ✅ 可访问:派生类可以访问基类保护成员
// privateVar = 40; // ❌ 错误:不能访问基类私有成员
}
};
int main() {
Base obj;
// obj.protectedVar = 50; // ❌ 错误:外部不能访问保护成员
// obj.protectedFunc(); // ❌ 错误:外部不能访问保护成员
obj.show(); // ✅ 可访问:通过公有方法间接访问
return 0;
}
1.4.3 public(公有)
-
最宽松的访问控制
-
本类、派生类、外部代码都可以访问
class Base {
public:
int publicVar; // 公有成员变量
void publicFunc() { // 公有成员函数
cout << "Public function" << endl;
}
protected:
int protectedVar;
private:
int privateVar;
};
class Derived : public Base {
public:
void accessAll() {
publicVar = 10; // ✅ 可访问:派生类可以访问基类公有成员
publicFunc(); // ✅ 可访问:派生类可以访问基类公有成员
protectedVar = 20; // ✅ 可访问:派生类可以访问基类保护成员
// privateVar = 30; // ❌ 错误:不能访问基类私有成员
}
};
int main() {
Base obj;
obj.publicVar = 40; // ✅ 可访问:外部可以访问公有成员
obj.publicFunc(); // ✅ 可访问:外部可以访问公有成员
return 0;
}
二、 继承 (Inheritance)
2.1 定义
继承允许一个新类(派生类)基于现有类(基类)来创建,继承基类的特性和行为,并可以添加新的特性或重写现有行为。
2.2 主要特点
-
代码复用:重用基类的代码
-
层次结构:建立类之间的"is-a"关系
-
扩展性:在基类基础上添加新功能
2.3 继承类型:
决定父类属性在子类中的访问权限,默认为private继承(与struct的唯一区别,struct默认为public继承)
-
公有继承:
class Derived : public Base;父类属性在子类中保持原有访问权限 -
保护继承:
class Derived : protected Base;父类public权限的属性在子类中升级为protected -
私有继承:
class Derived : private Base;父类public、protected权限的属性在子类中升级为private
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
// 公有继承
class PublicDerived : public Base {
// publicVar → public
// protectedVar → protected
// privateVar → 不可访问
};
// 保护继承
class ProtectedDerived : protected Base {
// publicVar → protected
// protectedVar → protected
// privateVar → 不可访问
};
// 私有继承
class PrivateDerived : private Base {
// publicVar → private
// protectedVar → private
// privateVar → 不可访问
};
三、多态 (Polymorphism)
3.1 定义
多态意为"多种形态",指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
3.2 主要特点
-
接口统一:通过基类接口操作不同派生类对象
-
动态绑定:运行时确定调用哪个方法
-
灵活性:易于扩展新类型
3.3 多态类型
3.3.1 编译时多态(静态多态)
-
函数重载:同一函数名可以根据输入参数类型、输入参数数量、输入参数顺序定义不同的实现,编译器会根据输入自行选择哪种实现方法。
#include <iostream>
using namespace std;
class Calculator {
public:
// 重载的add函数 - 根据参数类型和数量不同
int add(int a, int b) {
cout << "整数相加: ";
return a + b;
}
double add(double a, double b) {
cout << "浮点数相加: ";
return a + b;
}
int add(int a, int b, int c) {
cout << "三个整数相加: ";
return a + b + c;
}
string add(string a, string b) {
cout << "字符串连接: ";
return a + b;
}
};
int main() {
Calculator calc;
cout << calc.add(5, 3) << endl; // 调用 int add(int, int)
cout << calc.add(2.5, 3.7) << endl; // 调用 double add(double, double)
cout << calc.add(1, 2, 3) << endl; // 调用 int add(int, int, int)
cout << calc.add("Hello", " World") << endl; // 调用 string add(string, string)
return 0;
}
-
运算符重载:主要运用于需要对自定义数据类型进行运算符操作时。
#include <iostream>
using namespace std;
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 重载 + 运算符
Complex operator + (const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// 重载 - 运算符
Complex operator - (const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
// 重载 << 运算符(友元函数)
friend ostream& operator << (ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
// 重载 == 运算符
bool operator == (const Complex& other) const {
return (real == other.real) && (imag == other.imag);
}
};
int main() {
Complex c1(3.0, 4.0);
Complex c2(1.0, 2.0);
Complex sum = c1 + c2; // 调用重载的 + 运算符
Complex diff = c1 - c2; // 调用重载的 - 运算符
cout << "c1 = " << c1 << endl;
cout << "c2 = " << c2 << endl;
cout << "c1 + c2 = " << sum << endl;
cout << "c1 - c2 = " << diff << endl;
cout << "c1 == c2? " << (c1 == c2 ? "Yes" : "No") << endl;
return 0;
}
-
模板:用于适用于不同的输入类型
#include <iostream>
using namespace std;
// 函数模板 - 泛型编程
template <typename T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}
// 类模板
template <class T>
class Container {
private:
T value;
public:
Container(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
void display() const {
cout << "Value: " << value << endl;
}
};
// 特化模板
template <>
class Container<char> {
private:
char value;
public:
Container(char v) : value(v) {}
void display() const {
cout << "Character: '" << value << "' (ASCII: " << (int)value << ")" << endl;
}
};
int main() {
// 函数模板使用
cout << "Max of 5 and 3: " << getMax(5, 3) << endl;
cout << "Max of 2.7 and 3.1: " << getMax(2.7, 3.1) << endl;
cout << "Max of 'a' and 'z': " << getMax('a', 'z') << endl;
// 类模板使用
Container<int> intContainer(42);
Container<double> doubleContainer(3.14);
Container<string> stringContainer("Hello Template");
Container<char> charContainer('A');
intContainer.display();
doubleContainer.display();
stringContainer.display();
charContainer.display();
return 0;
}
3.3.2 运行时多态(动态多态)
-
虚函数
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
// 基类
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {}
// 虚函数 - 允许派生类重写
virtual void speak() const {
cout << name << " makes a sound." << endl;
}
// 纯虚函数 - 抽象类,必须被派生类实现
virtual void move() const = 0;
virtual ~Animal() {
cout << "Animal destructor: " << name << endl;
}
};
// 派生类
class Dog : public Animal {
private:
string breed;
public:
Dog(string n, string b) : Animal(n), breed(b) {}
void speak() const override {
cout << name << " the " << breed << " says: Woof! Woof!" << endl;
}
void move() const override {
cout << name << " is running on four legs." << endl;
}
~Dog() override {
cout << "Dog destructor: " << name << endl;
}
};
class Bird : public Animal {
private:
double wingspan;
public:
Bird(string n, double w) : Animal(n), wingspan(w) {}
void speak() const override {
cout << name << " says: Tweet! Tweet!" << endl;
}
void move() const override {
cout << name << " is flying with " << wingspan << "m wingspan." << endl;
}
~Bird() override {
cout << "Bird destructor: " << name << endl;
}
};
class Fish : public Animal {
public:
Fish(string n) : Animal(n) {}
void speak() const override {
cout << name << " says: Blub! Blub!" << endl;
}
void move() const override {
cout << name << " is swimming." << endl;
}
~Fish() override {
cout << "Fish destructor: " << name << endl;
}
};
// 使用多态的函数
void animalConcert(const vector<Animal*>& animals) {
cout << "\n=== Animal Concert ===" << endl;
for (const auto& animal : animals) {
animal->speak(); // 动态绑定 - 运行时确定调用哪个speak()
}
}
void animalMovement(const vector<Animal*>& animals) {
cout << "\n=== Animal Movement ===" << endl;
for (const auto& animal : animals) {
animal->move(); // 动态绑定 - 运行时确定调用哪个move()
}
}
int main() {
// 创建不同类型的动物对象
Dog dog("Buddy", "Golden Retriever");
Bird bird("Tweety", 0.5);
Fish fish("Nemo");
// 通过基类指针调用 - 多态行为
Animal* animals[] = {&dog, &bird, &fish};
cout << "=== Direct Calls ===" << endl;
for (int i = 0; i < 3; i++) {
animals[i]->speak(); // 多态调用
animals[i]->move(); // 多态调用
cout << endl;
}
// 使用vector和智能指针
vector<unique_ptr<Animal>> animalList;
animalList.push_back(make_unique<Dog>("Max", "Bulldog"));
animalList.push_back(make_unique<Bird>("Robin", 0.3));
animalList.push_back(make_unique<Fish>("Dory"));
cout << "=== Smart Pointer Calls ===" << endl;
for (const auto& animal : animalList) {
animal->speak();
animal->move();
cout << endl;
}
return 0;
}
3.3.3 常见问题
构造函数和析构函数可以是虚函数吗?为什么?
回答:
构造函数不能是虚函数,原因:
-
对象创建时机问题:虚函数表(vtable)是在构造函数执行期间建立的。在构造函数被调用时,对象还没有完全构造完成,虚函数机制尚未就绪。这里顺便解释一下什么是虚函数表:每个包含虚函数的类都有一个虚函数表,它是一个静态数组,存储了该类及其基类中所有虚函数的地址。当创建该类的对象时,对象中会包含一个指向该类虚函数表的指针(vptr)。当通过指针或引用调用虚函数时,程序会通过vptr找到对应的虚函数表,再通过虚函数表找到实际的函数地址。虚函数机制依赖于已构造的对象,而构造函数的任务就是创建这个对象,这是个"先有鸡还是先有蛋"的问题。
-
语义矛盾:虚函数用于实现运行时多态,但构造函数的目的是创建特定类型的对象。调用构造函数时,对象的类型已经明确,不需要动态绑定。
-
语法限制:C++标准明确规定构造函数不能是虚函数。
析构函数可以是虚函数
析构函数应该是虚函数的情况:
当类可能被继承,且可能通过基类指针删除派生类对象时,基类的析构函数必须声明为虚函数。
class Base {
public:
virtual ~Base() { // 正确:析构函数可以是虚函数
cout << "Base destructor" << endl;
}
};
class Derived : public Base {
public:
~Derived() override {
cout << "Derived destructor" << endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 正确调用 Derived 和 Base 的析构函数
return 0;
}
为什么析构函数需要是虚函数?
如果基类析构函数不是虚函数,那么当用基类指针指向派生类对象时,该对象在析构时只会调用基类的虚函数:
class Base {
public:
~Base() { // 非虚析构函数
cout << "Base destructor" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor" << endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 只调用 Base 的析构函数!
return 0;
}
派生类的构造函数和析构函数的执行过程?
构造过程:基类->成员类->派生类
析构过程:派生类 → 成员 → 基类
#include <iostream>
#include <string>
using namespace std;
// 基类
class Base {
private:
string name;
public:
Base() : name("Base") {
cout << "Base默认构造函数: " << name << endl;
}
Base(const string& n) : name(n) {
cout << "Base带参构造函数: " << name << endl;
}
virtual void show() {
cout << "Base::show(): " << name << endl;
}
virtual ~Base() {
cout << "Base析构函数: " << name << endl;
}
};
// 成员类
class Member {
private:
string name;
public:
Member() : name("Member") {
cout << "Member默认构造函数: " << name << endl;
}
Member(const string& n) : name(n) {
cout << "Member带参构造函数: " << name << endl;
}
~Member() {
cout << "Member析构函数: " << name << endl;
}
};
// 派生类
class Derived : public Base {
private:
Member m1;
Member m2;
string name;
public:
// 默认构造函数
Derived() : Base("BaseFromDerived"), m1("FirstMember"), name("Derived") {
cout << "Derived默认构造函数: " << name << endl;
cout << "--- 构造完成 ---" << endl;
}
// 带参构造函数
Derived(const string& baseName, const string& member1, const string& member2)
: Base(baseName), m1(member1), m2(member2), name("Derived") {
cout << "Derived带参构造函数: " << name << endl;
cout << "--- 构造完成 ---" << endl;
}
void show() override {
cout << "Derived::show(): " << name << endl;
}
~Derived() {
cout << "--- 开始析构 ---" << endl;
cout << "Derived析构函数: " << name << endl;
}
};
// 测试函数
void testConstruction() {
cout << "=== 创建默认Derived对象 ===" << endl;
Derived d1;
cout << endl;
cout << "=== 创建带参Derived对象 ===" << endl;
Derived d2("CustomBase", "CustomMember1", "CustomMember2");
cout << endl;
cout << "=== 多态测试 ===" << endl;
Base* ptr = new Derived("PolyBase", "PolyMember1", "PolyMember2");
ptr->show();
delete ptr; // 重要:由于Base有虚析构函数,会正确调用Derived的析构函数
cout << endl;
cout << "=== 局部对象即将离开作用域 ===" << endl;
}
int main() {
testConstruction();
cout << "=== testConstruction函数结束 ===" << endl;
return 0;
}
3.3.4 总结对比
| 特性 | 编译时多态 | 运行时多态 |
|---|---|---|
| 确定时机 | 编译时 | 运行时 |
| 实现机制 | 函数重载、运算符重载、模板 | 虚函数、继承 |
| 性能 | 高效,无额外开销 | 有轻微性能开销(虚表查找) |
| 灵活性 | 相对较低 | 高,支持动态绑定 |
| 典型应用 | 数学运算、通用算法 | 图形系统、游戏开发、框架设计 |
关键区别:
-
编译时多态:编译器在编译时就知道调用哪个具体函数
-
运行时多态:程序运行时根据对象的实际类型决定调用哪个函数
968

被折叠的 条评论
为什么被折叠?



