C++学习:六个月从基础到就业——面向对象编程:类与对象

C++学习:六个月从基础到就业——面向对象编程:类与对象

本文是我C++学习之旅系列的第八篇技术文章,主要介绍面向对象编程中的类与对象基础概念,包括类的定义、对象的创建、成员函数与成员变量、访问控制等核心知识。查看完整系列目录了解更多内容。

引言

面向对象编程(Object-Oriented Programming,OOP)是一种程序设计范式,它使用"对象"来组织代码和数据,而不是简单地使用函数和数据结构。在C++中,类和对象是实现面向对象编程的基础。类是定义对象特征的模板,而对象是类的具体实例。本文将深入探讨C++中类与对象的概念、语法和使用方法,帮助读者构建坚实的面向对象编程基础。

从结构体到类

我们在前一篇文章中介绍了结构体,在C++中,类可以看作是对结构体的扩展。实际上,在C++中,结构体和类的主要区别只是默认的访问权限不同:结构体的成员默认是公有的(public),而类的成员默认是私有的(private)。

结构体复习

struct Student {
    std::string name;  // 公有成员
    int age;           // 公有成员
    float gpa;         // 公有成员
};

Student s;
s.name = "Alice";  // 直接访问是允许的

转换为类定义

class Student {
    std::string name;  // 私有成员
    int age;           // 私有成员
    float gpa;         // 私有成员
};

Student s;
// s.name = "Alice";  // 编译错误:name是私有的

这种默认访问权限的差别反映了两种不同的设计思想:结构体主要用于组织数据,而类则更强调数据的封装和保护。

类的基本概念

类的定义

在C++中,类的定义使用关键字class开始,后跟类名和一对花括号,花括号内包含类的成员变量和成员函数(也称为方法):

class ClassName {
    // 成员变量和成员函数
};

一个更完整的例子:

class Rectangle {
private:  // 私有成员,外部不能直接访问
    double width;
    double height;

public:  // 公有成员,外部可以直接访问
    // 构造函数
    Rectangle(double w, double h) {
        width = w;
        height = h;
    }
    
    // 计算面积
    double area() {
        return width * height;
    }
    
    // 计算周长
    double perimeter() {
        return 2 * (width + height);
    }
    
    // 获取宽度
    double getWidth() {
        return width;
    }
    
    // 设置宽度
    void setWidth(double w) {
        if (w > 0) {  // 添加验证逻辑
            width = w;
        }
    }
    
    // 获取高度
    double getHeight() {
        return height;
    }
    
    // 设置高度
    void setHeight(double h) {
        if (h > 0) {  // 添加验证逻辑
            height = h;
        }
    }
};

访问修饰符

C++提供了三种访问修饰符来控制类成员的可见性:

  1. private(私有):只能被类内部的成员函数访问
  2. protected(保护):能被类内部和派生类访问
  3. public(公有):可以被任何代码访问

一个包含所有三种访问修饰符的例子:

class Account {
private:  // 私有成员
    std::string accountNumber;
    double balance;
    
protected:  // 保护成员
    std::string ownerName;
    bool isActive;
    
public:  // 公有成员
    Account(const std::string& owner, const std::string& accNum) {
        ownerName = owner;
        accountNumber = accNum;
        balance = 0.0;
        isActive = true;
    }
    
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
    
    bool withdraw(double amount) {
        if (isActive && amount > 0 && balance >= amount) {
            balance -= amount;
            return true;
        }
        return false;
    }
    
    double getBalance() const {
        return balance;
    }
};

在这个例子中:

  • accountNumberbalance是私有的,只能通过类的成员函数访问和修改
  • ownerNameisActive是保护的,在当前类和派生类中可访问
  • 构造函数、depositwithdrawgetBalance是公有的,任何代码都可以调用这些函数

对象的创建与使用

类定义了对象的模板,要创建对象,我们可以像定义变量一样声明类的实例:

// 创建Rectangle对象
Rectangle rect(5.0, 3.0);

// 调用成员函数
double area = rect.area();  // 返回15.0
double perimeter = rect.perimeter();  // 返回16.0

// 获取与设置属性
double currentWidth = rect.getWidth();  // 返回5.0
rect.setWidth(10.0);  // 修改宽度为10.0

还可以使用动态分配创建对象:

// 使用new动态创建对象
Rectangle* pRect = new Rectangle(5.0, 3.0);

// 使用指针调用成员函数
double area = pRect->area();
pRect->setWidth(10.0);

// 使用完对象后释放内存
delete pRect;

类成员的详解

成员变量

类的成员变量(也称为数据成员或属性)是与类的每个实例关联的变量。它们定义了对象的状态。

实例变量与静态变量

类中有两种主要类型的成员变量:

  1. 实例变量:与每个对象实例关联,每个对象都有自己的副本
  2. 静态变量:与类关联而非特定实例,所有对象共享同一副本
class Counter {
private:
    int instanceCount;      // 实例变量,每个对象有独立的副本
    static int totalCount;  // 静态变量,所有对象共享一个副本

public:
    Counter() {
        instanceCount = 0;
        totalCount++;       // 增加静态计数器
    }
    
    void increment() {
        instanceCount++;
        totalCount++;
    }
    
    int getInstanceCount() const {
        return instanceCount;
    }
    
    static int getTotalCount() {
        return totalCount;
    }
};

// 静态成员变量需要在类外初始化
int Counter::totalCount = 0;

使用示例:

Counter c1, c2;
c1.increment();  // c1.instanceCount = 1, totalCount = 3
c2.increment();  // c2.instanceCount = 1, totalCount = 4

// 通过类名访问静态成员
int total = Counter::getTotalCount();  // total = 4
const成员变量

当一个成员变量被声明为const时,它必须在创建对象时初始化,且之后不能更改:

class Circle {
private:
    const double PI;  // const成员变量
    double radius;

public:
    // const成员必须在初始化列表中初始化
    Circle(double r) : PI(3.14159), radius(r) {
        // PI = 3.14159;  // 错误:不能在构造函数体内为const成员赋值
    }

    double area() const {
        return PI * radius * radius;
    }
};

成员函数

成员函数是属于类的函数,它们定义了对象的行为。

定义和声明

成员函数可以在类定义内部声明,并在类内部或外部定义:

// 在类内部定义
class Circle {
private:
    double radius;

public:
    // 构造函数在类内部定义
    Circle(double r) : radius(r) {}
    
    // 成员函数在类内部定义
    double area() {
        return 3.14159 * radius * radius;
    }
};

或者:

// 在类外部定义
class Circle {
private:
    double radius;

public:
    Circle(double r);  // 构造函数声明
    double area();     // 成员函数声明
};

// 类外部定义构造函数
Circle::Circle(double r) : radius(r) {
}

// 类外部定义成员函数
double Circle::area() {
    return 3.14159 * radius * radius;
}

在大型项目中,通常在头文件(.h)中声明类,并在源文件(.cpp)中定义成员函数,这种分离有助于提高编译效率和代码组织。

const成员函数

const成员函数是承诺不会修改对象状态的函数,它们可以被const对象调用:

class Date {
private:
    int day, month, year;

public:
    Date(int d, int m, int y) : day(d), month(m), year(y) {}
    
    // const成员函数不能修改对象的状态
    int getDay() const {
        return day;
    }
    
    // 非const成员函数可以修改对象的状态
    void setDay(int d) {
        day = d;
    }
};

const Date birthday(1, 1, 2000);  // const对象
int day = birthday.getDay();      // 可以调用const成员函数
// birthday.setDay(2);            // 错误:不能在const对象上调用非const成员函数

const成员函数的重要性:

  • 表明函数的意图(不修改对象状态)
  • 允许const对象调用该函数
  • 提高代码安全性
静态成员函数

静态成员函数属于类而非对象实例,它们可以在不创建对象的情况下被调用,但只能访问静态成员变量:

class MathUtils {
public:
    // 静态成员函数
    static double square(double num) {
        return num * num;
    }
    
    static double cube(double num) {
        return num * num * num;
    }
};

// 通过类名调用静态函数,无需创建对象
double squared = MathUtils::square(4.0);  // 返回16.0
double cubed = MathUtils::cube(4.0);      // 返回64.0

静态成员函数的特点:

  • 使用static关键字声明
  • 通过类名直接调用,不需要对象
  • 不能访问非静态成员
  • 没有this指针

特殊成员函数

C++类有一些特殊的成员函数,如果不显式定义,编译器会自动生成它们。

构造函数

构造函数是创建对象时调用的特殊成员函数,用于初始化对象的状态:

class Person {
private:
    std::string name;
    int age;

public:
    // 默认构造函数(无参数)
    Person() : name("Unknown"), age(0) {
        std::cout << "Default constructor called" << std::endl;
    }
    
    // 带参数的构造函数
    Person(const std::string& n, int a) : name(n), age(a) {
        std::cout << "Parameterized constructor called" << std::endl;
    }
    
    // 拷贝构造函数
    Person(const Person& other) : name(other.name), age(other.age) {
        std::cout << "Copy constructor called" << std::endl;
    }
};

构造函数可以通过不同方式创建对象:

Person p1;                   // 调用默认构造函数
Person p2("Alice", 30);      // 调用带参数的构造函数
Person p3 = p2;              // 调用拷贝构造函数
Person p4 = Person("Bob", 25); // 临时对象初始化
析构函数

析构函数是对象被销毁时自动调用的特殊函数,用于释放对象占用的资源:

class ResourceManager {
private:
    int* data;

public:
    // 构造函数分配资源
    ResourceManager(int size) {
        data = new int[size];
        std::cout << "Resource allocated" << std::endl;
    }
    
    // 析构函数释放资源
    ~ResourceManager() {
        delete[] data;
        std::cout << "Resource freed" << std::endl;
    }
};

void useResource() {
    ResourceManager rm(100);  // 构造函数被调用
    // 使用资源...
}  // 函数结束时,rm超出作用域,析构函数被调用

析构函数的特点:

  • 名称是类名前加波浪号(~)
  • 没有返回值,也没有参数
  • 每个类只能有一个析构函数
  • 当对象超出作用域或被显式删除时,析构函数被调用

this指针

在C++中,每个成员函数都有一个隐含的参数this,它是一个指向当前对象的指针。this指针使成员函数能够访问调用它的对象:

class Counter {
private:
    int count;

public:
    Counter(int c = 0) : count(c) {}
    
    // 使用this指针访问成员变量
    void increment() {
        this->count++;  // 等同于 count++;
    }
    
    // 返回对象自身的引用,用于链式调用
    Counter& add(int value) {
        count += value;
        return *this;  // 返回当前对象的引用
    }
    
    int getCount() const {
        return count;
    }
};

// 链式调用示例
Counter c;
c.add(5).add(10).increment();
std::cout << c.getCount() << std::endl;  // 输出16

this指针的重要用途:

  • 区分同名的成员变量和参数
  • 实现链式调用
  • 在模板中用于确定类型
class Person {
private:
    std::string name;

public:
    // 使用this区分成员变量和参数
    Person(const std::string& name) {
        this->name = name;  // 没有this会导致歧义
    }
};

类的组合与关联

组合关系

组合是一种"has-a"关系,表示一个类包含另一个类的实例作为其成员:

class Engine {
public:
    void start() {
        std::cout << "Engine started" << std::endl;
    }
    
    void stop() {
        std::cout << "Engine stopped" << std::endl;
    }
};

class Car {
private:
    Engine engine;  // Car包含一个Engine
    std::string brand;

public:
    Car(const std::string& b) : brand(b) {}
    
    void turnOn() {
        std::cout << brand << " car turning on..." << std::endl;
        engine.start();
    }
    
    void turnOff() {
        std::cout << brand << " car turning off..." << std::endl;
        engine.stop();
    }
};

组合的优点:

  • 代码重用
  • 隐藏实现细节
  • 提高模块性

关联关系

关联表示一个类使用另一个类,但不一定拥有它:

class Student;  // 前向声明

class Course {
private:
    std::string name;
    std::vector<Student*> students;  // 关联多个Student

public:
    Course(const std::string& n) : name(n) {}
    
    void addStudent(Student* s) {
        students.push_back(s);
    }
    
    std::string getName() const {
        return name;
    }
};

class Student {
private:
    std::string name;
    std::vector<Course*> courses;  // 关联多个Course

public:
    Student(const std::string& n) : name(n) {}
    
    void enrollCourse(Course* c) {
        courses.push_back(c);
        c->addStudent(this);
    }
    
    void listCourses() const {
        std::cout << name << " is enrolled in:" << std::endl;
        for (const auto& course : courses) {
            std::cout << "- " << course->getName() << std::endl;
        }
    }
};

这种关系通常通过指针或引用实现,能表达多种关联类型:

  • 一对一关系
  • 一对多关系
  • 多对多关系

实例:设计一个银行账户系统

下面是一个简单的银行账户系统设计,展示了类、对象和组合的实际应用:

#include <iostream>
#include <string>
#include <vector>
#include <iomanip>

// 交易记录类
class Transaction {
private:
    std::string date;
    std::string description;
    double amount;
    bool isDeposit;

public:
    Transaction(const std::string& d, const std::string& desc, double amt, bool deposit)
        : date(d), description(desc), amount(amt), isDeposit(deposit) {}
    
    void display() const {
        std::cout << std::left << std::setw(12) << date
                  << std::setw(30) << description
                  << std::setw(15) << (isDeposit ? "+" : "-")
                  << std::right << std::fixed << std::setprecision(2)
                  << amount << std::endl;
    }
    
    double getAmount() const {
        return isDeposit ? amount : -amount;
    }
};

// 客户信息类
class Customer {
private:
    std::string name;
    std::string address;
    std::string phoneNumber;

public:
    Customer(const std::string& n, const std::string& addr, const std::string& phone)
        : name(n), address(addr), phoneNumber(phone) {}
    
    std::string getName() const {
        return name;
    }
    
    void updateAddress(const std::string& addr) {
        address = addr;
    }
    
    void updatePhone(const std::string& phone) {
        phoneNumber = phone;
    }
    
    void displayInfo() const {
        std::cout << "Customer: " << name << std::endl;
        std::cout << "Address: " << address << std::endl;
        std::cout << "Phone: " << phoneNumber << std::endl;
    }
};

// 银行账户类
class BankAccount {
private:
    std::string accountNumber;
    Customer* owner;  // 关联关系
    double balance;
    std::vector<Transaction> transactions;  // 组合关系
    
    // 生成交易记录并添加到历史
    void recordTransaction(const std::string& desc, double amount, bool isDeposit) {
        // 获取当前日期(简化版)
        std::string today = "2023-06-15";  // 实际应用应获取系统日期
        
        Transaction trans(today, desc, amount, isDeposit);
        transactions.push_back(trans);
    }

public:
    BankAccount(const std::string& accNum, Customer* cust, double initialDeposit = 0.0)
        : accountNumber(accNum), owner(cust), balance(initialDeposit) {
        
        if (initialDeposit > 0) {
            recordTransaction("Initial deposit", initialDeposit, true);
        }
    }
    
    void deposit(double amount, const std::string& desc = "Deposit") {
        if (amount <= 0) {
            std::cout << "Error: Deposit amount must be positive." << std::endl;
            return;
        }
        
        balance += amount;
        recordTransaction(desc, amount, true);
        std::cout << "Deposit successful. New balance: " << balance << std::endl;
    }
    
    bool withdraw(double amount, const std::string& desc = "Withdrawal") {
        if (amount <= 0) {
            std::cout << "Error: Withdrawal amount must be positive." << std::endl;
            return false;
        }
        
        if (amount > balance) {
            std::cout << "Error: Insufficient funds." << std::endl;
            return false;
        }
        
        balance -= amount;
        recordTransaction(desc, amount, false);
        std::cout << "Withdrawal successful. New balance: " << balance << std::endl;
        return true;
    }
    
    double getBalance() const {
        return balance;
    }
    
    void printStatement() const {
        std::cout << "\n====== Account Statement ======\n";
        owner->displayInfo();
        std::cout << "Account Number: " << accountNumber << std::endl;
        std::cout << "Current Balance: $" << std::fixed << std::setprecision(2) << balance << std::endl;
        std::cout << "\nTransaction History:\n";
        std::cout << std::left << std::setw(12) << "Date"
                  << std::setw(30) << "Description"
                  << std::setw(15) << "Type"
                  << "Amount" << std::endl;
        std::cout << std::string(70, '-') << std::endl;
        
        for (const auto& trans : transactions) {
            trans.display();
        }
        
        std::cout << std::string(70, '-') << std::endl;
    }
};

// 储蓄账户(继承示例,将在下一篇文章详细介绍继承)
class SavingsAccount : public BankAccount {
private:
    double interestRate;
    
public:
    SavingsAccount(const std::string& accNum, Customer* cust, double initialDeposit = 0.0, double rate = 0.01)
        : BankAccount(accNum, cust, initialDeposit), interestRate(rate) {}
    
    // 计算并添加利息
    void addInterest(const std::string& desc = "Interest payment") {
        double interest = getBalance() * interestRate;
        deposit(interest, desc);
    }
};

// 使用示例
int main() {
    // 创建客户
    Customer alice("Alice Smith", "123 Main St", "555-1234");
    
    // 创建银行账户
    BankAccount account("AC001", &alice, 1000.0);
    
    // 执行交易
    account.deposit(500.0, "Salary");
    account.withdraw(200.0, "Grocery shopping");
    account.deposit(100.0, "Gift");
    account.withdraw(50.0, "Dinner");
    
    // 打印账户报表
    account.printStatement();
    
    // 创建和使用储蓄账户
    SavingsAccount savings("SA001", &alice, 2000.0, 0.02);
    savings.deposit(1000.0, "Bonus");
    savings.addInterest();  // 添加利息
    savings.printStatement();
    
    return 0;
}

此示例展示了:

  • 如何定义和使用类
  • 如何使用成员函数和成员变量
  • 组合关系(BankAccount包含Transaction对象)
  • 关联关系(BankAccount关联一个Customer对象)
  • 继承的简单应用(将在下一篇文章中详细介绍)

类与对象的最佳实践

封装原则

  1. 隐藏实现细节:将成员变量声明为private,并提供public访问方法
  2. 提供清晰的接口:设计直观、一致的公共接口
  3. 最小权限原则:仅暴露必要的功能
// 不好的实践
class BadAccount {
public:
    double balance;  // 直接暴露数据
    
    void updateBalance(double newBalance) {
        balance = newBalance;  // 无验证
    }
};

// 好的实践
class GoodAccount {
private:
    double balance;
    
public:
    double getBalance() const {
        return balance;
    }
    
    bool deposit(double amount) {
        if (amount <= 0) return false;
        balance += amount;
        return true;
    }
    
    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
};

合理使用构造函数和初始化

  1. 始终初始化所有成员变量
  2. 使用初始化列表(更高效,对const成员和引用成员必需)
  3. 提供合理的默认构造函数
  4. 考虑使用默认参数代替多个构造函数
class Rectangle {
private:
    double width;
    double height;
    const std::string unit;

public:
    // 坏习惯:在构造函数体内赋值
    Rectangle(double w, double h) {
        width = w;
        height = h;
        // unit = "cm";  // 错误:const成员不能在这里赋值
    }
    
    // 好习惯:使用初始化列表
    Rectangle(double w, double h, const std::string& u = "cm")
        : width(w), height(h), unit(u) {
        // 构造函数体可以为空
    }
};

使用const修饰符

  1. 将不修改对象状态的成员函数声明为const
  2. 将函数参数尽可能声明为const引用
  3. 成员变量适当使用const修饰
class Vector {
private:
    double x, y, z;
    
public:
    Vector(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {}
    
    // 不修改对象状态的函数声明为const
    double magnitude() const {
        return std::sqrt(x*x + y*y + z*z);
    }
    
    // 接受const引用参数
    Vector add(const Vector& other) const {
        return Vector(x + other.x, y + other.y, z + other.z);
    }
    
    // 不能声明为const,因为它修改了对象状态
    void scale(double factor) {
        x *= factor;
        y *= factor;
        z *= factor;
    }
};

明智地使用友元

友元(friend)提供了一种允许外部函数或类访问私有成员的机制,但应谨慎使用:

class Complex {
private:
    double real;
    double imag;
    
public:
    Complex(double r, double i) : real(r), imag(i) {}
    
    // 声明友元函数
    friend Complex operator+(const Complex& a, const Complex& b);
    
    // 声明友元类
    friend class ComplexCalculator;
};

// 友元函数定义
Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real + b.real, a.imag + b.imag);
}

// 友元类
class ComplexCalculator {
public:
    static void printDetails(const Complex& c) {
        std::cout << "Real: " << c.real << ", Imaginary: " << c.imag << std::endl;
    }
    
    static Complex conjugate(const Complex& c) {
        return Complex(c.real, -c.imag);
    }
};

友元的使用准则:

  • 仅在确实需要时使用友元
  • 通常用于运算符重载和辅助类/函数
  • 避免破坏封装原则

避免构造过大的类

大型类难以理解和维护。将大型类分解为小型、专注于单一职责的类:

// 不好的设计:一个巨大的类
class Monolith {
private:
    // 客户数据
    std::string customerName;
    std::string address;
    
    // 产品数据
    std::vector<std::string> products;
    
    // 订单数据
    double totalAmount;
    bool isPaid;
    
    // 支付处理
    void processPayment() { /* ... */ }
    
    // 库存管理
    void updateInventory() { /* ... */ }
    
    // 发票生成
    void generateInvoice() { /* ... */ }
    
public:
    // 大量接口方法...
};

// 更好的设计:分离关注点
class Customer {
private:
    std::string name;
    std::string address;
    
public:
    // 客户相关方法...
};

class Product {
private:
    std::string name;
    double price;
    int stock;
    
public:
    // 产品相关方法...
};

class Order {
private:
    Customer customer;
    std::vector<Product> items;
    double totalAmount;
    bool isPaid;
    
public:
    // 订单相关方法...
};

class PaymentProcessor {
public:
    bool process(Order& order, const std::string& paymentMethod) {
        // 处理支付...
    }
};

class InvoiceGenerator {
public:
    void generate(const Order& order) {
        // 生成发票...
    }
};

总结

本文介绍了C++中类与对象的基础概念和使用方法,包括类的定义、访问控制、成员变量和成员函数、构造函数和析构函数、this指针以及类之间的关系。类和对象是C++面向对象编程的基础,掌握它们对于编写可维护、可重用和可扩展的代码至关重要。

面向对象编程(OOP)的核心优势包括:

  • 封装:隐藏实现细节,只暴露必要的接口
  • 代码重用:通过组合和继承实现
  • 模块化:将系统分解为相互协作的对象
  • 易维护性:对象封装变化,减少修改影响范围

在下一篇文章中,我们将深入探讨类的继承与多态性,这是面向对象编程的另外两个重要支柱。我们还将介绍虚函数、抽象类和接口的概念,进一步展示C++面向对象编程的强大能力。

练习题

  1. 设计一个Time类,表示时、分、秒,提供设置和获取时间、增加秒数以及格式化显示时间的功能。

  2. 创建一个Stack类实现基本的栈操作(push、pop、top、isEmpty、size),使用私有数组存储元素。

  3. 设计一个简单的String类,实现字符串的基本操作,包括构造函数、析构函数、复制、连接和比较等功能。

  4. 设计一个ShapeManager类,能够存储和管理不同类型的形状(如圆形、矩形),并计算它们的总面积。

参考资料

  1. Bjarne Stroustrup. The C++ Programming Language (4th Edition)
  2. Scott Meyers. Effective C++
  3. cppreference.com - Classes
  4. C++ Core Guidelines - Classes and Class Hierarchies
  5. Stanley B. Lippman. Inside the C++ Object Model

这是我C++学习之旅系列的第八篇技术文章。查看完整系列目录了解更多内容。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

superior tigre

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值