第12章 面向对象编程简介 面向对象编程的基本概念 面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将数据和操作数据的方法封装在一起,组成对象,通过对象之间的交互来实现程序功能。
核心概念 对象(Object) :现实世界中的实体在程序中的表示,具有状态(属性)和行为(方法)类(Class) :对象的蓝图或模板,定义了对象的属性和方法封装(Encapsulation) :将数据和操作数据的方法封装在一起,隐藏内部实现细节继承(Inheritance) :从已有类派生出新类,继承父类的属性和方法多态(Polymorphism) :不同对象对同一消息做出不同的响应类和对象 类的定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ClassName {private : 数据类型 成员变量; public : 数据类型 成员变量; 返回类型 成员方法(参数列表); protected : 数据类型 成员变量; 返回类型 成员方法(参数列表); };
对象的创建和使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Person {private : std::string name; int age; public : Person (const std::string& n, int a) : name (n), age (a) {} void setName (const std::string& n) { name = n; } std::string getName () const { return name; } void setAge (int a) { age = a; } int getAge () const { return age; } void introduce () const { std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl; } }; int main () { Person person1 ("Alice" , 25 ) ; person1. introduce (); person1. setName ("Bob" ); person1. setAge (30 ); person1. introduce (); return 0 ; }
封装 封装的概念 封装是将数据和操作数据的方法封装在一起,隐藏内部实现细节,只暴露必要的接口给外部使用。
访问控制 C++提供了三种访问控制修饰符:
private :私有成员,只能在类内部访问public :公共成员,可以在任何地方访问protected :保护成员,可以在类内部和子类中访问封装的优点 数据隐藏 :隐藏内部实现细节,提高安全性代码重用 :封装的代码可以被多次使用可维护性 :修改内部实现不影响外部代码接口清晰 :只暴露必要的接口,使代码更易理解示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class BankAccount {private : std::string accountNumber; double balance; public : BankAccount (const std::string& accNum, double initialBalance) : accountNumber (accNum), balance (initialBalance) {} void deposit (double amount) { if (amount > 0 ) { balance += amount; std::cout << "Deposited: $" << amount << std::endl; std::cout << "New balance: $" << balance << std::endl; } else { std::cout << "Invalid deposit amount" << std::endl; } } void withdraw (double amount) { if (amount > 0 && amount <= balance) { balance -= amount; std::cout << "Withdrew: $" << amount << std::endl; std::cout << "New balance: $" << balance << std::endl; } else { std::cout << "Invalid withdrawal amount" << std::endl; } } double getBalance () const { return balance; } std::string getAccountNumber () const { return accountNumber; } }; int main () { BankAccount account ("123456789" , 1000.0 ) ; account.deposit (500.0 ); account.withdraw (200.0 ); return 0 ; }
继承 继承的概念 继承是从已有类派生出新类,新类继承父类的属性和方法,同时可以添加自己的属性和方法,或者重写父类的方法。
基类和派生类 基类(Base Class) :被继承的类,也称为父类派生类(Derived Class) :从基类派生的类,也称为子类继承的语法 1 2 3 class DerivedClass : accessSpecifier BaseClass { };
其中,accessSpecifier 可以是:
public :公有继承,基类的public成员在派生类中仍然是public,protected成员仍然是protectedprotected :保护继承,基类的public和protected成员在派生类中都是protectedprivate :私有继承,基类的public和protected成员在派生类中都是private示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Animal {protected : std::string name; public : Animal (const std::string& n) : name (n) {} void eat () { std::cout << name << " is eating." << std::endl; } void sleep () { std::cout << name << " is sleeping." << std::endl; } }; class Dog : public Animal {public : Dog (const std::string& n) : Animal (n) {} void bark () { std::cout << name << " is barking." << std::endl; } void eat () { std::cout << name << " is eating bones." << std::endl; } }; class Cat : public Animal {public : Cat (const std::string& n) : Animal (n) {} void meow () { std::cout << name << " is meowing." << std::endl; } void eat () { std::cout << name << " is eating fish." << std::endl; } }; int main () { Dog dog ("Rex" ) ; dog.eat (); dog.sleep (); dog.bark (); Cat cat ("Whiskers" ) ; cat.eat (); cat.sleep (); cat.meow (); return 0 ; }
继承的优点 代码重用 :继承父类的代码,减少重复代码层次结构 :建立类的层次结构,更符合现实世界的模型多态支持 :为多态提供基础扩展性 :易于扩展现有代码多态 多态的概念 多态是指不同对象对同一消息做出不同的响应,即同一操作作用于不同的对象会产生不同的结果。
编译时多态和运行时多态 编译时多态 :通过函数重载和运算符重载实现运行时多态 :通过虚函数和继承实现虚函数 虚函数是在基类中声明的,允许派生类重写的函数。使用 virtual 关键字声明。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Shape {public : virtual void draw () { std::cout << "Drawing a shape." << std::endl; } virtual ~Shape () { std::cout << "Destructing a shape." << std::endl; } }; class Circle : public Shape {public : void draw () override { std::cout << "Drawing a circle." << std::endl; } ~Circle () { std::cout << "Destructing a circle." << std::endl; } }; class Rectangle : public Shape {public : void draw () override { std::cout << "Drawing a rectangle." << std::endl; } ~Rectangle () { std::cout << "Destructing a rectangle." << std::endl; } }; int main () { Shape* shape1 = new Circle (); Shape* shape2 = new Rectangle (); shape1->draw (); shape2->draw (); delete shape1; delete shape2; return 0 ; }
多态的优点 灵活性 :代码可以处理不同类型的对象可扩展性 :添加新的派生类不需要修改现有代码可维护性 :代码更清晰,更易于维护接口一致性 :不同类型的对象通过相同的接口进行操作抽象类和接口 抽象类 抽象类是包含纯虚函数的类,不能直接实例化,只能作为基类被继承。
纯虚函数 纯虚函数是在基类中声明的,没有实现的虚函数,派生类必须实现它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class AbstractClass {public : virtual void pureVirtualFunction () = 0 ; virtual void virtualFunction () { } void memberFunction () { } };
接口 在C++中,接口是一种特殊的抽象类,只包含纯虚函数,没有数据成员。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Drawable {public : virtual void draw () = 0 ; virtual ~Drawable () = default ; }; class Circle : public Drawable {public : void draw () override { std::cout << "Drawing a circle." << std::endl; } }; class Rectangle : public Drawable {public : void draw () override { std::cout << "Drawing a rectangle." << std::endl; } }; void drawShape (Drawable& shape) { shape.draw (); } int main () { Circle circle; Rectangle rectangle; drawShape (circle); drawShape (rectangle); return 0 ; }
面向对象编程的设计原则 1. 单一职责原则(Single Responsibility Principle) 一个类应该只有一个引起它变化的原因,即一个类只负责一项功能。
2. 开放-封闭原则(Open-Closed Principle) 软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
3. 里氏替换原则(Liskov Substitution Principle) 子类应该能够替换其父类,并且程序的行为不会改变。
4. 接口隔离原则(Interface Segregation Principle) 客户端不应该依赖它不使用的接口,应该将庞大的接口拆分为更小的、更具体的接口。
5. 依赖倒置原则(Dependency Inversion Principle) 高层模块不应该依赖低层模块,两者都应该依赖于抽象;抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
6. 组合优于继承原则(Composition Over Inheritance) 优先使用对象组合而不是类继承来实现代码重用,这样可以降低类之间的耦合度,提高代码的灵活性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Engine {public : virtual void start () = 0 ; }; class GasolineEngine : public Engine {public : void start () override { std::cout << "Gasoline engine starting..." << std::endl; } }; class Car {private : GasolineEngine engine; public : void start () { engine.start (); } }; class Car {private : std::unique_ptr<Engine> engine; public : Car (std::unique_ptr<Engine> e) : engine (std::move (e)) {} void start () { engine->start (); } void setEngine (std::unique_ptr<Engine> e) { engine = std::move (e); } };
面向对象编程的优点 代码重用 :通过继承和组合实现代码重用模块化 :将代码组织为独立的对象,便于管理和维护可维护性 :封装使得修改内部实现不影响外部代码可扩展性 :通过继承和多态易于扩展现有代码可理解性 :代码结构更符合现实世界的模型,易于理解安全性 :封装保护数据,减少错误面向对象编程的缺点 复杂性 :相比过程式编程,OOP的概念和实现更复杂性能开销 :继承和多态可能带来一定的性能开销过度设计 :可能导致过度设计,增加不必要的复杂性学习曲线 :学习OOP的概念和设计模式需要一定的时间C++20新特性:概念(Concepts) C++20引入了概念(Concepts),用于约束模板参数,提高模板代码的可读性和错误信息的清晰度:
概念的基本概念 概念 :对类型的约束,指定类型必须满足的条件约束 :使用概念来限制模板参数的类型requires表达式 :定义概念的具体约束条件概念的使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <concepts> #include <iostream> template <typename T> concept Arithmetic = std::is_arithmetic_v<T>; template <Arithmetic T> T add (T a, T b) { return a + b; } template <typename T> concept Comparable = requires (T a, T b) { { a < b } -> std::convertible_to<bool >; { a > b } -> std::convertible_to<bool >; { a == b } -> std::convertible_to<bool >; }; template <Comparable T> T max (T a, T b) { return a > b ? a : b; } template <std::integral T> T square (T x) { return x * x; } template <std::floating_point T>T square (T x) { return x * x; } int main () { std::cout << "Add integers: " << add (1 , 2 ) << std::endl; std::cout << "Add doubles: " << add (1.5 , 2.5 ) << std::endl; std::cout << "Max integer: " << max (10 , 20 ) << std::endl; std::cout << "Max string: " << max (std::string ("apple" ), std::string ("banana" )) << std::endl; std::cout << "Square integer: " << square (5 ) << std::endl; std::cout << "Square double: " << square (2.5 ) << std::endl; return 0 ; }
概念的优点 类型安全 :在编译时检查类型约束,避免运行时错误错误信息清晰 :当类型不满足约束时,提供更清晰的错误信息代码可读性 :通过概念名称明确表达模板参数的要求代码重用 :概念可以被多个模板使用,提高代码重用性灵活性 :概念可以组合使用,创建更复杂的类型约束面向对象编程与其他编程范式的比较 与过程式编程的比较 过程式编程 :关注过程和函数,将程序分解为一系列函数调用面向对象编程 :关注对象和交互,将程序分解为一系列对象的交互与函数式编程的比较 函数式编程 :关注纯函数,避免状态和副作用面向对象编程 :关注对象的状态和行为,允许状态变化与泛型编程的比较 泛型编程 :关注类型参数化,实现代码重用面向对象编程 :关注对象和继承,实现代码重用小结 本章介绍了面向对象编程的基本概念,包括:
面向对象编程的核心概念 :对象、类、封装、继承、多态类和对象 :类的定义、对象的创建和使用封装 :数据隐藏、访问控制继承 :基类和派生类、继承的语法和优点多态 :编译时多态和运行时多态、虚函数抽象类和接口 :纯虚函数、抽象类的使用面向对象编程的设计原则 :单一职责、开放-封闭、里氏替换、接口隔离、依赖倒置面向对象编程的优缺点 :代码重用、模块化、可维护性等优点,以及复杂性、性能开销等缺点与其他编程范式的比较 :与过程式编程、函数式编程、泛型编程的比较面向对象编程是现代编程语言的重要特性,它提供了一种更符合人类思维方式的编程方法,使得代码更易于理解、维护和扩展。掌握面向对象编程的概念和技巧,对于编写高质量的C++程序至关重要。
在后续章节中,我们将更深入地学习C++的面向对象特性,包括类的设计、构造函数和析构函数、运算符重载、模板等内容。