7C++ Primer(第5版)

类

面向对象的基础

类定义成员函数构造函数访问控制

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第7章:类(Classes)

一、导读

1.1 本章学习目标

第7章介绍C++面向对象编程的基础:类的定义和使用。本章核心目标:

  • 掌握类的定义:数据成员和成员函数
  • 理解访问控制:public、private、protected
  • 掌握构造函数和析构函数:对象的初始化和清理
  • 理解this指针:指向当前对象的指针
  • 学会使用友元:友元函数和友元类
  • 掌握类的声明和定义分离:头文件和源文件

1.2 为什么本章如此重要

类是C++面向对象编程的基石。理解类的设计原则,才能编写出结构清晰、可维护的C++代码。本章介绍的构造函数、析构函数、访问控制等概念,贯穿整个C++面向对象编程。

1.3 核心问题引导

构造函数和析构函数的调用顺序是什么?

为什么需要初始化列表?直接在构造函数体内赋值有什么问题?

const成员函数有什么作用?为什么getter通常应该是const的?

友元破坏了封装性,为什么C++还要提供这个特性?


二、核心概念详解

7.1 类的定义

cpp
#include <iostream>
#include <string>

class Student {
private:
    std::string name;
    int age;
    float score;
    
public:
    // 构造函数
    Student(std::string n, int a, float s) 
        : name(n), age(a), score(s) {}
    
    // 默认构造函数
    Student() : name("Unknown"), age(0), score(0.0f) {}
    
    // 成员函数
    void display() const {
        std::cout << "Name: " << name 
                  << ", Age: " << age 
                  << ", Score: " << score << std::endl;
    }
    
    // getter
    std::string getName() const { return name; }
    int getAge() const { return age; }
    float getScore() const { return score; }
    
    // setter
    void setName(const std::string &n) { name = n; }
    void setAge(int a) { 
        if (a > 0 && a < 150) age = a; 
    }
    void setScore(float s) { 
        if (s >= 0 && s <= 100) score = s;
    }
};

int main() {
    Student s1("Alice", 20, 95.5f);
    Student s2;
    s2.setName("Bob");
    s2.setAge(21);
    s2.setScore(88.0f);
    
    s1.display();
    s2.display();
    
    return 0;
}

7.2 构造函数和析构函数

cpp
#include <iostream>
#include <string>

class Resource {
private:
    std::string name;
    
public:
    // 构造函数
    Resource(std::string n) : name(n) {
        std::cout << "Constructor: " << name << std::endl;
    }
    
    // 拷贝构造函数
    Resource(const Resource &other) : name(other.name + "_copy") {
        std::cout << "Copy constructor: " << name << std::endl;
    }
    
    // 移动构造函数(C++11)
    Resource(Resource &&other) noexcept : name(std::move(other.name)) {
        std::cout << "Move constructor: " << name << std::endl;
    }
    
    // 析构函数
    ~Resource() {
        std::cout << "Destructor: " << name << std::endl;
    }
    
    void display() const {
        std::cout << "Resource: " << name << std::endl;
    }
};

int main() {
    Resource r1("File1");
    Resource r2 = r1;        // 拷贝构造
    Resource r3 = std::move(r1);  // 移动构造
    
    r1.display();
    r2.display();
    r3.display();
    
    return 0;
}

7.3 const成员函数和this指针

cpp
#include <iostream>

class Counter {
private:
    int count;
    
public:
    Counter() : count(0) {}
    
    // const成员函数:不能修改成员变量
    int getCount() const { 
        return count; 
    }
    
    void display() const {
        std::cout << "Count: " << count << std::endl;
    }
    
    // 非const成员函数:可以修改成员变量
    void increment() { 
        count++; 
    }
    
    // 使用this指针
    Counter& setCount(int c) {
        this->count = c;
        return *this;  // 支持链式调用
    }
    
    void reset() {
        this->count = 0;
    }
};

int main() {
    Counter c;
    c.increment();
    c.increment();
    c.display();  // Count: 2
    
    // 链式调用
    c.setCount(10).setCount(20);
    c.display();  // Count: 20
    
    // const对象只能调用const成员函数
    const Counter cc;
    std::cout << cc.getCount() << std::endl;
    // cc.increment();  // 错误:const对象不能调用非const函数
    
    return 0;
}

7.4 友元

cpp
#include <iostream>

class Counter;  // 前向声明

// 友元函数:可以访问类的私有成员
void reset(Counter &c);

class Counter {
private:
    int count;
    
public:
    Counter() : count(0) {}
    void increment() { count++; }
    int getCount() const { return count; }
    
    // 友元函数声明
    friend void reset(Counter &c);
    
    // 友元类
    friend class CounterDisplay;
    
    // 友元运算符重载
    friend std::ostream& operator<<(std::ostream &os, const Counter &c);
};

void reset(Counter &c) {
    c.count = 0;  // 友元函数可以访问私有成员
}

class CounterDisplay {
public:
    void show(const Counter &c) {
        std::cout << "Counter value: " << c.count << std::endl;
    }
};

std::ostream& operator<<(std::ostream &os, const Counter &c) {
    os << "Counter(" << c.count << ")";
    return os;
}

int main() {
    Counter c;
    c.increment();
    c.increment();
    
    std::cout << c << std::endl;  // Counter(2)
    
    reset(c);
    std::cout << c << std::endl;  // Counter(0)
    
    CounterDisplay display;
    c.increment();
    display.show(c);  // Counter value: 1
    
    return 0;
}

7.5 类的声明和定义分离

cpp
// student.h - 头文件
#ifndef STUDENT_H
#define STUDENT_H

#include <string>

class Student {
private:
    std::string name;
    int age;
    float score;

public:
    Student();
    Student(std::string n, int a, float s);
    
    void display() const;
    
    std::string getName() const;
    void setName(const std::string &n);
    
    float getScore() const;
    void setScore(float s);
};

#endif

// student.cpp - 实现文件
#include "student.h"
#include <iostream>

Student::Student() : name("Unknown"), age(0), score(0.0f) {}

Student::Student(std::string n, int a, float s) 
    : name(n), age(a), score(s) {}

void Student::display() const {
    std::cout << "Name: " << name 
              << ", Age: " << age 
              << ", Score: " << score << std::endl;
}

std::string Student::getName() const { return name; }
void Student::setName(const std::string &n) { name = n; }
float Student::getScore() const { return score; }
void Student::setScore(float s) { 
    if (s >= 0 && s <= 100) score = s;
}

// main.cpp - 主程序
#include "student.h"

int main() {
    Student s("Alice", 20, 95.5f);
    s.display();
    return 0;
}

三、实战案例

3.1 银行账户类

cpp
#include <iostream>
#include <string>
#include <stdexcept>

class BankAccount {
private:
    std::string owner;
    double balance;
    
public:
    BankAccount(std::string o, double initial = 0.0) 
        : owner(o), balance(initial) {
        if (initial < 0) {
            throw std::invalid_argument("Initial balance cannot be negative");
        }
    }
    
    void deposit(double amount) {
        if (amount <= 0) {
            throw std::invalid_argument("Deposit amount must be positive");
        }
        balance += amount;
    }
    
    void withdraw(double amount) {
        if (amount <= 0) {
            throw std::invalid_argument("Withdraw amount must be positive");
        }
        if (amount > balance) {
            throw std::runtime_error("Insufficient funds");
        }
        balance -= amount;
    }
    
    double getBalance() const { return balance; }
    std::string getOwner() const { return owner; }
    
    void display() const {
        std::cout << "Account: " << owner 
                  << ", Balance: $" << balance << std::endl;
    }
    
    friend std::ostream& operator<<(std::ostream &os, const BankAccount &acc) {
        os << acc.owner << ": $" << acc.balance;
        return os;
    }
};

int main() {
    BankAccount acc("Alice", 1000.0);
    acc.display();
    
    acc.deposit(500);
    acc.withdraw(200);
    acc.display();
    
    std::cout << acc << std::endl;
    
    try {
        acc.withdraw(2000);
    } catch (const std::runtime_error &e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    
    return 0;
}

四、本章小结

第7章介绍了C++类的基础:类的定义、访问控制、构造函数/析构函数、const成员函数、this指针、友元、声明与定义分离。类是面向对象编程的基石。


五、思考与练习

思考题

构造函数初始化列表和构造函数体内赋值有什么性能差异?

为什么拷贝构造函数的参数必须是const引用?

移动语义解决了什么问题?

友元破坏了封装性,什么场景下必须使用友元?

编程练习

实现一个复数类(Complex),支持加减乘除和输出。

实现一个日期类(Date),支持日期运算和比较。

实现一个简单的字符串类(MyString),管理动态内存。


7/12类
0%