第14章 类和封装 封装的概念 封装是面向对象编程的三大核心特性之一,它将数据和操作数据的方法捆绑在一起,形成一个独立的单元(类),并控制对数据的访问权限。
封装的目的 数据隐藏 :将内部实现细节隐藏起来,只暴露必要的接口安全性 :防止外部代码意外修改内部数据模块化 :将相关的代码组织在一起,提高代码的可维护性可扩展性 :可以在不影响外部代码的情况下修改内部实现访问控制 C++通过访问修饰符实现封装,主要有三种访问修饰符:
public(公有) public成员可以被任何代码访问,通常用于定义类的接口。
1 2 3 4 5 6 7 8 9 10 11 12 class Person {public : void setName (const std::string& name) { this ->name = name; } std::string getName () const { return name; } private : std::string name; };
private(私有) private成员只能被类的成员函数和友元访问,通常用于定义类的内部数据和实现细节。
protected(保护) protected成员可以被类的成员函数、友元和派生类访问,通常用于定义需要被派生类访问的内部数据。
类的设计原则 接口与实现分离 将类的接口(public成员)与实现(private成员)分离,使外部代码只依赖于接口,而不依赖于实现细节。
最小化接口 只暴露必要的接口,避免暴露内部实现细节,减少外部代码对内部实现的依赖。
数据抽象 通过抽象数据类型(ADT)将数据和操作数据的方法捆绑在一起,形成一个独立的单元。
封装的实现 构造函数 构造函数用于初始化对象的状态,是封装的重要组成部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Date {private : int year, month, day; public : Date (int y, int m, int d) { if (y < 1 || m < 1 || m > 12 || d < 1 || d > getDaysInMonth (y, m)) { throw std::invalid_argument ("Invalid date" ); } year = y; month = m; day = d; } private : int getDaysInMonth (int y, int m) { } };
析构函数 析构函数用于清理对象的资源,是封装的重要组成部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class FileHandler {private : FILE* file; public : FileHandler (const char * filename, const char * mode) { file = fopen (filename, mode); if (!file) { throw std::runtime_error ("Failed to open file" ); } } ~FileHandler () { if (file) { fclose (file); } } };
成员函数 成员函数是类的行为,用于操作类的内部数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Stack {private : std::vector<int > elements; public : void push (int value) { elements.push_back (value); } int pop () { if (elements.empty ()) { throw std::runtime_error ("Stack is empty" ); } int value = elements.back (); elements.pop_back (); return value; } bool isEmpty () const { return elements.empty (); } };
封装与信息隐藏 信息隐藏的原则 最小知识原则 :一个对象应该只了解与其直接相关的对象单一职责原则 :一个类应该只有一个引起它变化的原因开闭原则 :软件实体应该对扩展开放,对修改关闭信息隐藏的实现 通过将数据成员声明为private,并提供public的访问器和修改器方法来实现信息隐藏。
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 class BankAccount {private : std::string accountNumber; double balance; public : BankAccount (const std::string& number, double initialBalance) : accountNumber (number), balance (initialBalance) {} void deposit (double amount) { if (amount > 0 ) { balance += amount; } } void withdraw (double amount) { if (amount > 0 && amount <= balance) { balance -= amount; } } double getBalance () const { return balance; } std::string getAccountNumber () const { return accountNumber; } };
封装的最佳实践 1. 使用访问器和修改器方法 为private数据成员提供public的访问器(getter)和修改器(setter)方法,而不是直接暴露数据成员。
2. 验证输入数据 在修改器方法中验证输入数据的有效性,确保对象的状态始终保持一致。
3. 使用const成员函数 对于不修改对象状态的成员函数,声明为const成员函数。
4. 避免使用全局变量 尽量使用类的成员变量代替全局变量,提高代码的封装性。
5. 使用命名约定 为private成员变量使用特殊的命名约定,如在变量名前加下划线,以便于区分。
6. 现代C++类设计特性 移动语义(C++11+) 使用移动语义提高对象的性能,避免不必要的拷贝操作:
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 class String {private : char * data; size_t length; public : String (const char * str) { length = strlen (str); data = new char [length + 1 ]; strcpy (data, str); } String (String&& other) noexcept : data (other.data), length (other.length) { other.data = nullptr ; other.length = 0 ; } String& operator =(String&& other) noexcept { if (this != &other) { delete [] data; data = other.data; length = other.length; other.data = nullptr ; other.length = 0 ; } return *this ; } ~String () { delete [] data; } String (const String&) = delete ; String& operator =(const String&) = delete ; };
默认成员函数控制(C++11+) 显式控制默认成员函数的生成:
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 class MyClass {private : int value; public : MyClass () = default ; MyClass (int v) : value (v) {} MyClass (const MyClass&) = default ; MyClass (MyClass&&) = default ; MyClass& operator =(const MyClass&) = default ; MyClass& operator =(MyClass&&) = default ; ~MyClass () = default ; };
委托构造函数(C++11+) 使用委托构造函数减少代码重复:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Date {private : int year, month, day; public : Date (int y, int m, int d) : year (y), month (m), day (d) { } Date () : Date (2000 , 1 , 1 ) {} Date (int y) : Date (y, 1 , 1 ) {} Date (int y, int m) : Date (y, m, 1 ) {} };
继承构造函数(C++11+) 使用继承构造函数简化派生类的构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Base {private : int value; public : Base (int v) : value (v) {} }; class Derived : public Base {private : std::string name; public : using Base::Base; Derived (int v, const std::string& n) : Base (v), name (n) {} };
示例:封装的实现 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 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <iostream> #include <string> #include <stdexcept> class Rectangle {private : double width; double height; public : Rectangle (double w, double h) { setWidth (w); setHeight (h); } double getWidth () const { return width; } double getHeight () const { return height; } void setWidth (double w) { if (w <= 0 ) { throw std::invalid_argument ("Width must be positive" ); } width = w; } void setHeight (double h) { if (h <= 0 ) { throw std::invalid_argument ("Height must be positive" ); } height = h; } double area () const { return width * height; } double perimeter () const { return 2 * (width + height); } }; int main () { try { Rectangle rect (5.0 , 3.0 ) ; std::cout << "Width: " << rect.getWidth () << std::endl; std::cout << "Height: " << rect.getHeight () << std::endl; std::cout << "Area: " << rect.area () << std::endl; std::cout << "Perimeter: " << rect.perimeter () << std::endl; rect.setWidth (7.0 ); rect.setHeight (4.0 ); std::cout << "\nAfter modification:" << std::endl; std::cout << "Width: " << rect.getWidth () << std::endl; std::cout << "Height: " << rect.getHeight () << std::endl; std::cout << "Area: " << rect.area () << std::endl; std::cout << "Perimeter: " << rect.perimeter () << std::endl; } catch (const std::exception& e) { std::cerr << "Error: " << e.what () << std::endl; } return 0 ; }
封装与设计模式 单例模式 单例模式确保一个类只有一个实例,并提供一个全局访问点。
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 class Singleton {private : static Singleton* instance; Singleton () {} ~Singleton () {} Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; public : static Singleton* getInstance () { if (!instance) { instance = new Singleton (); } return instance; } }; Singleton* Singleton::instance = nullptr ;
工厂模式 工厂模式通过工厂类创建对象,隐藏对象的创建细节。
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 class Product {public : virtual void use () = 0 ; virtual ~Product () = default ; }; class ConcreteProductA : public Product {public : void use () override { std::cout << "Using Product A" << std::endl; } }; class ConcreteProductB : public Product {public : void use () override { std::cout << "Using Product B" << std::endl; } }; class Factory {public : static std::unique_ptr<Product> createProduct (const std::string& type) { if (type == "A" ) { return std::make_unique <ConcreteProductA>(); } else if (type == "B" ) { return std::make_unique <ConcreteProductB>(); } return nullptr ; } };
总结 封装是C++面向对象编程的重要特性,它通过访问修饰符控制对数据的访问权限,将数据和操作数据的方法捆绑在一起,形成一个独立的单元。合理使用封装可以提高代码的安全性、可维护性和可扩展性,是现代C++编程中的重要技术之一。