首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《C++ 程序设计》第 4 章 - 类与对象

《C++ 程序设计》第 4 章 - 类与对象

作者头像
啊阿狸不会拉杆
发布2026-01-21 12:33:23
发布2026-01-21 12:33:23
1710
举报
引言

        在 C++ 编程世界中,类与对象是面向对象程序设计(OOP)的核心基石。从简单的数据封装到复杂的继承多态,类与对象机制为我们提供了模块化、可复用、易维护的代码组织方式。本章将全面解析类与对象的核心概念、语法规则及实战应用,带你从面向过程思维平滑过渡到面向对象思维。

本章知识思维导图

4.1 面向对象程序设计的基本特点

        面向对象程序设计(OOP)是一种以 "对象" 为中心的编程思想,其核心特点可概括为抽象、封装、继承和多态四大支柱。

4.1.1 抽象

        抽象是从具体事物中提取核心特征而忽略次要细节的过程。在编程中,我们通过类来实现抽象,聚焦于 "是什么" 而非 "怎么做"。

例如:描述 "汽车" 时,我们关注颜色、品牌、行驶功能,而非发动机内部结构。

4.1.2 封装

        封装是将数据(属性)和操作数据的函数(方法)捆绑在一起,并隐藏内部实现细节,仅对外提供接口。这提高了安全性和模块化。

  • 数据隐藏:内部数据仅能通过类提供的方法访问
  • 接口公开:对外提供有限的交互方式
代码语言:javascript
复制
// 封装示例:时钟类
class Clock {
private:
    // 私有数据(隐藏)
    int hour, minute, second;
public:
    // 公开接口(访问控制)
    void setTime(int h, int m, int s) {
        hour = h;
        minute = m;
        second = s;
    }
    void showTime() {
        cout << hour << ":" << minute << ":" << second << endl;
    }
};
4.1.3 继承

        继承允许新类(子类)继承现有类(父类)的属性和方法,实现代码复用和扩展。

        例如:"学生" 类可以继承 "人" 类的姓名、年龄属性,再添加学号、专业等特有属性。

4.1.4 多态

        多态指同一接口(函数名)在不同对象上有不同实现,程序运行时根据对象类型动态选择执行的代码。

        例如:"动物" 类的 "发声" 方法,在 "猫" 类中实现为 "喵喵叫",在 "狗" 类中实现为 "汪汪叫"。

4.2 类和对象

        类是对象的模板,定义了对象的属性和行为;对象是类的实例,是具体存在的实体。

4.2.1 类的定义

        类通过class关键字定义,包含数据成员(属性)和成员函数(方法)。

代码语言:javascript
复制
// 类定义的基本格式
class 类名 {
    访问控制符:
        数据成员;  // 属性
        成员函数声明;  // 方法
};

// 成员函数实现(类外)
返回类型 类名::成员函数名(参数列表) {
    // 函数体
}
4.2.2 类成员的访问控制

        C++ 通过访问控制符限制成员的访问权限,实现封装:

访问控制符

访问范围

public

类内、类外、子类均可访问

private

仅类内可访问(默认)

protected

类内和子类可访问,类外不可访问

代码语言:javascript
复制
class Person {
public:  // 公开成员
    string name;  // 姓名
    void setAge(int a);  // 设置年龄的方法
    
private:  // 私有成员
    int age;  // 年龄(直接访问被限制)
    
protected:  // 保护成员
    string idCard;  // 身份证号(子类可访问)
};
4.2.3 对象

        对象是类的实例化,通过类名 对象名创建。可通过.运算符访问对象的成员。

代码语言:javascript
复制
// 创建对象并使用
int main() {
    Person p;  // 创建Person类对象p
    p.name = "张三";  // 访问public成员
    p.setAge(20);  // 调用public成员函数
    // p.age = 20;  // 错误:private成员不可直接访问
    return 0;
}
4.2.4 类的成员函数

成员函数分为内联函数非内联函数

  • 内联函数:函数体在类内定义,编译时直接嵌入调用处,提高执行效率(适合短函数)
  • 非内联函数:类内声明,类外实现
代码语言:javascript
复制
class Circle {
private:
    double radius;  // 半径
public:
    // 内联函数(类内实现)
    void setRadius(double r) {
        radius = r;
    }
    
    // 成员函数声明(类外实现)
    double getArea();  // 计算面积
    double getPerimeter();  // 计算周长
};

// 类外实现成员函数(需加类名限定)
double Circle::getArea() {
    return 3.14159 * radius * radius;
}

double Circle::getPerimeter() {
    return 2 * 3.14159 * radius;
}
4.2.5 程序实例:点类的定义与使用

下面通过完整实例展示类的定义、对象创建和成员访问:

代码语言:javascript
复制
#include <iostream>
using namespace std;

// 点类:描述平面上的点
class Point {
private:
    int x;  // x坐标
    int y;  // y坐标

public:
    // 设置坐标(内联函数)
    void setPoint(int x_, int y_) {
        x = x_;
        y = y_;
    }
    
    // 获取x坐标
    int getX() {
        return x;
    }
    
    // 获取y坐标
    int getY() {
        return y;
    }
    
    // 输出点的坐标(类外实现)
    void printPoint();
};

// 类外实现成员函数
void Point::printPoint() {
    cout << "(" << x << ", " << y << ")" << endl;
}

int main() {
    // 创建Point对象
    Point p1, p2;
    
    // 设置坐标
    p1.setPoint(3, 4);
    p2.setPoint(5, 12);
    
    // 输出坐标
    cout << "p1的坐标:";
    p1.printPoint();
    cout << "p2的坐标:";
    p2.printPoint();
    
    // 获取坐标并计算距离
    int dx = p1.getX() - p2.getX();
    int dy = p1.getY() - p2.getY();
    double distance = sqrt(dx*dx + dy*dy);  // 需要#include <cmath>
    cout << "p1与p2的距离:" << distance << endl;
    
    return 0;
}

运行结果

4.3 构造函数和析构函数

        构造函数和析构函数是特殊的成员函数,分别用于对象的初始化和资源清理。

4.3.1 构造函数

构造函数在对象创建时自动调用,用于初始化对象数据成员。

  • 函数名与类名相同
  • 无返回类型(包括 void)
  • 可重载(定义多个参数不同的构造函数)
代码语言:javascript
复制
#include <iostream>
#include <string>

class Student {
private:
    std::string name;  // 使用std::前缀
    int age;
    std::string id;

public:
    Student(std::string n, int a, std::string i) {
        name = n;
        age = a;
        id = i;
        std::cout << "构造函数被调用" << std::endl;  // 使用std::前缀
    }
    
    void showInfo() {
        std::cout << "姓名:" << name << ",年龄:" << age << ",学号:" << id << std::endl;
    }
};

int main() {
    Student s1("张三", 18, "2023001");
    s1.showInfo();
    
    Student s2 = Student("李四", 19, "2023002");
    s2.showInfo();
    
    return 0;
}
4.3.2 默认构造函数

        无参数的构造函数称为默认构造函数。如果未定义任何构造函数,编译器会自动生成默认构造函数(空实现)。

代码语言:javascript
复制
#include <iostream>   // 包含输入输出流头文件
#include <string>     // 包含字符串头文件
using namespace std;  // 使用标准命名空间

class Car {
private:
    string brand;
    string color;

public:
    // 默认构造函数(无参数)
    Car() {
        brand = "未知品牌";
        color = "未知颜色";
        cout << "默认构造函数被调用" << endl;
    }
    
    // 带参数的构造函数
    Car(string b, string c) {
        brand = b;
        color = c;
        cout << "带参数构造函数被调用" << endl;
    }
    
    void showInfo() {
        cout << "品牌:" << brand << ",颜色:" << color << endl;
    }
};

int main() {
    Car c1;  // 调用默认构造函数
    c1.showInfo();
    
    Car c2("特斯拉", "红色");  // 调用带参数构造函数
    c2.showInfo();
    
    return 0;
}
4.3.3 委托构造函数

        委托构造函数允许一个构造函数调用同一类中的另一个构造函数,减少代码重复。

代码语言:javascript
复制
#include<iostream>
#include<string>
using namespace std; 

class Rectangle {
private:
    int length;
    int width;

public:
    // 目标构造函数(被委托的构造函数)
    Rectangle(int l, int w) {
        length = l;
        width = w;
        cout << "目标构造函数被调用" << endl;
    }
    
    // 委托构造函数(调用目标构造函数)
    Rectangle() : Rectangle(1, 1) {  // 委托给带参数的构造函数
        cout << "委托构造函数(默认)被调用" << endl;
    }
    
    // 另一个委托构造函数
    Rectangle(int side) : Rectangle(side, side) {  // 委托创建正方形
        cout << "委托构造函数(正方形)被调用" << endl;
    }
    
    int getArea() {
        return length * width;
    }
};

int main() {
    Rectangle r1;  // 调用默认委托构造函数
    cout << "面积:" << r1.getArea() << endl;
    
    Rectangle r2(5);  // 调用正方形委托构造函数
    cout << "面积:" << r2.getArea() << endl;
    
    Rectangle r3(3, 4);  // 调用目标构造函数
    cout << "面积:" << r3.getArea() << endl;
    
    return 0;
}

运行结果

4.3.4 复制构造函数

        复制构造函数用于根据已有对象创建新对象(拷贝对象),参数为同类对象的引用。

  • 格式:类名(const 类名& 对象名)
  • 当未定义时,编译器生成默认复制构造函数(浅拷贝)
代码语言:javascript
复制
#include<iostream>
#include<string>
using namespace std;

class Array {
private:
    int* data;  // 动态数组
    int size;

public:
    // 构造函数
    Array(int s) {
        size = s;
        data = new int[size];  // 分配内存
        cout << "构造函数:分配了" << size << "个int的内存" << endl;
    }
    
    // 复制构造函数(深拷贝)
    Array(const Array& other) {
        size = other.size;
        data = new int[size];  // 重新分配内存
        // 拷贝数据
        for (int i = 0; i < size; i++) {
            data[i] = other.data[i];
        }
        cout << "复制构造函数:深拷贝完成" << endl;
    }
    
    // 设置数组元素
    void setData(int index, int value) {
        if (index >= 0 && index < size) {
            data[index] = value;
        }
    }
    
    // 打印数组
    void print() {
        for (int i = 0; i < size; i++) {
            cout << data[i] << " ";
        }
        cout << endl;
    }
};

int main() {
    Array arr1(3);  // 创建对象
    arr1.setData(0, 10);
    arr1.setData(1, 20);
    arr1.setData(2, 30);
    cout << "arr1: ";
    arr1.print();
    
    // 使用复制构造函数创建新对象
    Array arr2 = arr1;  // 等价于 Array arr2(arr1);
    cout << "arr2: ";
    arr2.print();
    
    return 0;
}
4.3.5 析构函数

        析构函数在对象销毁时自动调用,用于释放对象占用的资源(如动态内存、文件句柄等)。

  • 函数名:~类名
  • 无返回类型,无参数
  • 一个类只能有一个析构函数(不能重载)
代码语言:javascript
复制
class MyString {
private:
    char* str;  // 动态分配的字符串

public:
    // 构造函数(分配内存)
    MyString(const char* s) {
        int len = strlen(s);
        str = new char[len + 1];  // +1 用于存储结束符'\0'
        strcpy(str, s);
        cout << "构造函数:创建字符串 \"" << str << "\"" << endl;
    }
    
    // 析构函数(释放内存)
    ~MyString() {
        cout << "析构函数:释放字符串 \"" << str << "\"" << endl;
        delete[] str;  // 释放动态分配的内存
    }
    
    void show() {
        cout << str << endl;
    }
};

int main() {
    {  // 代码块,用于演示对象销毁时机
        MyString s1("Hello");
        s1.show();
    }  // s1超出作用域,析构函数被调用
    
    MyString s2("World");
    s2.show();
    
    return 0;
}  // s2超出作用域,析构函数被调用

运行结果

代码语言:javascript
复制
构造函数:创建字符串 "Hello"
Hello
析构函数:释放字符串 "Hello"
构造函数:创建字符串 "World"
World
析构函数:释放字符串 "World"
4.3.6 移动构造函数

        移动构造函数用于将临时对象(右值)的资源 "移动" 到新对象,避免不必要的拷贝,提高效率。

  • 参数为右值引用:类名(类名&& 对象名)
  • 使用std::move()将左值转换为右值
代码语言:javascript
复制
#include <utility>  // 包含std::move

class Buffer {
private:
    int* data;
    int size;

public:
    // 构造函数
    Buffer(int s) : size(s) {
        data = new int[size];
        cout << "构造函数:分配" << size << "个int的内存" << endl;
    }
    
    // 移动构造函数
    Buffer(Buffer&& other) noexcept {  // noexcept表示不会抛出异常
        // 移动资源
        data = other.data;
        size = other.size;
        
        // 置空原对象指针(避免原对象析构时释放资源)
        other.data = nullptr;
        other.size = 0;
        
        cout << "移动构造函数:资源移动完成" << endl;
    }
    
    // 析构函数
    ~Buffer() {
        if (data != nullptr) {
            cout << "析构函数:释放" << size << "个int的内存" << endl;
            delete[] data;
        } else {
            cout << "析构函数:无资源可释放" << endl;
        }
    }
};

int main() {
    // 创建临时对象(右值)
    Buffer buf1(10000);
    
    // 使用移动构造函数创建新对象(转移资源)
    Buffer buf2 = std::move(buf1);  // 将左值转换为右值
    
    return 0;
}

运行结果

代码语言:javascript
复制
构造函数:分配10000个int的内存
移动构造函数:资源移动完成
析构函数:无资源可释放
析构函数:释放10000个int的内存
4.3.7 default、delete 函数
  • default:显式要求编译器生成默认版本的函数
  • delete:显式禁用某个函数(如禁止拷贝)
代码语言:javascript
复制
class Example {
private:
    int value;

public:
    // 使用default生成默认构造函数
    Example() = default;
    
    // 带参数的构造函数
    Example(int v) : value(v) {}
    
    // 禁用复制构造函数
    Example(const Example&) = delete;
    
    // 禁用赋值运算符
    Example& operator=(const Example&) = delete;
    
    int getValue() const { return value; }
};

int main() {
    Example e1;  // 调用默认构造函数
    Example e2(10);  // 调用带参数构造函数
    
    // Example e3 = e2;  // 错误:复制构造函数已禁用
    // e1 = e2;  // 错误:赋值运算符已禁用
    
    return 0;
}
4.3.8 程序实例:构造函数与析构函数综合示例
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class Book {
private:
    string title;  // 书名
    string author;  // 作者
    int pages;  // 页数
    double* ratings;  // 评分数组(动态分配)
    int ratingCount;  // 评分数量

public:
    // 默认构造函数
    Book() : title("未知书名"), author("未知作者"), pages(0), 
             ratings(NULL), ratingCount(0) {  // 使用NULL替代nullptr
        cout << "默认构造函数被调用" << endl;
    }
    
    // 带参数的构造函数
    Book(string t, string a, int p) : title(t), author(a), pages(p),
                                      ratings(NULL), ratingCount(0) {  // 使用NULL替代nullptr
        cout << "带参数构造函数被调用" << endl;
    }
    
    // 不使用委托构造函数(改为普通构造函数)
    Book(string t, string a, int p, int rCount) : title(t), author(a), pages(p) {
        ratingCount = rCount;
        if (ratingCount > 0) {
            ratings = new double[ratingCount]();  // 初始化为0
        } else {
            ratings = NULL;  // 使用NULL替代nullptr
        }
        cout << "带评分构造函数被调用" << endl;
    }
    
    // 复制构造函数(深拷贝)
    Book(const Book& other) : title(other.title), author(other.author),
                             pages(other.pages), ratingCount(other.ratingCount) {
        if (ratingCount > 0) {
            ratings = new double[ratingCount];
            // 拷贝评分数据
            for (int i = 0; i < ratingCount; i++) {
                ratings[i] = other.ratings[i];
            }
        } else {
            ratings = NULL;  // 使用NULL替代nullptr
        }
        cout << "复制构造函数被调用" << endl;
    }
    
    // 移除移动构造函数(C++11特性)
    
    // 设置评分
    void setRating(int index, double score) {
        if (index >= 0 && index < ratingCount) {
            ratings[index] = score;
        }
    }
    
    // 显示书籍信息
    void showInfo() {
        cout << "书名:" << title << endl;
        cout << "作者:" << author << endl;
        cout << "页数:" << pages << endl;
        if (ratingCount > 0) {
            cout << "评分:";
            for (int i = 0; i < ratingCount; i++) {
                cout << ratings[i] << " ";
            }
            cout << endl;
        }
    }
    
    // 析构函数
    ~Book() {
        if (ratings != NULL) {  // 使用NULL替代nullptr
            delete[] ratings;
            cout << "析构函数:释放评分数组内存" << endl;
        }
        cout << "析构函数:销毁书籍对象 \"" << title << "\"" << endl;
    }
};

int main() {
    // 测试默认构造函数
    Book b1;
    b1.showInfo();
    cout << endl;
    
    // 测试带参数构造函数
    Book b2("C++ Primer", "Stanley Lippman", 1300);
    b2.showInfo();
    cout << endl;
    
    // 测试带评分构造函数
    Book b3("数据结构与算法", "严蔚敏", 400, 3);  // 3个评分
    b3.setRating(0, 4.5);
    b3.setRating(1, 4.8);
    b3.setRating(2, 4.7);
    b3.showInfo();
    cout << endl;
    
    // 测试复制构造函数
    Book b4 = b3;  // 调用复制构造函数
    cout << "b4是b3的复制:" << endl;
    b4.showInfo();
    cout << endl;
    
    // 移除移动构造函数测试代码
    
    return 0;
}

4.4 类的组合

        类的组合指一个类包含其他类的对象作为成员,体现 "has-a"(有一个)的关系。

4.4.1 组合

        当类 A 包含类 B 的对象作为成员时,称类 A 组合了类 B。创建 A 的对象时,会先调用 B 的构造函数,再调用 A 的构造函数;销毁时则相反。

代码语言:javascript
复制
// 日期类
class Date {
private:
    int year, month, day;

public:
    // 构造函数
    Date(int y, int m, int d) : year(y), month(m), day(d) {
        cout << "Date构造函数被调用:" << year << "-" << month << "-" << day << endl;
    }
    
    // 显示日期
    void showDate() {
        cout << year << "-" << month << "-" << day << endl;
    }
    
    // 析构函数
    ~Date() {
        cout << "Date析构函数被调用:" << year << "-" << month << "-" << day << endl;
    }
};

// 学生类(组合了Date类对象)
class Student {
private:
    string name;  // 姓名
    Date birthday;  // 生日(Date类对象,组合关系)
    int score;  // 成绩

public:
    // 构造函数:先初始化成员对象birthday
    Student(string n, int y, int m, int d, int s) 
        : name(n), birthday(y, m, d), score(s) {  // 成员初始化列表
        cout << "Student构造函数被调用:" << name << endl;
    }
    
    // 显示学生信息
    void showInfo() {
        cout << "姓名:" << name << endl;
        cout << "生日:";
        birthday.showDate();
        cout << "成绩:" << score << endl;
    }
    
    // 析构函数
    ~Student() {
        cout << "Student析构函数被调用:" << name << endl;
    }
};

// 组合类的构造/析构顺序演示
int main() {
    cout << "创建学生对象:" << endl;
    Student s("张三", 2005, 3, 15, 90);
    s.showInfo();
    
    return 0;
}

运行结果

代码语言:javascript
复制
创建学生对象:
Date构造函数被调用:2005-3-15
Student构造函数被调用:张三
姓名:张三
生日:2005-3-15
成绩:90
Student析构函数被调用:张三
Date析构函数被调用:2005-3-15
4.4.2 前向引用声明

        当两个类相互引用时,需使用前向引用声明(forward declaration)告知编译器类的存在。

代码语言:javascript
复制
// 前向引用声明:告知编译器Teacher类存在
class Teacher;

// 学生类:引用Teacher类
class Student {
private:
    string name;
    Teacher* advisor;  // 只能声明为指针或引用

public:
    Student(string n) : name(n) {}
    
    // 设置导师
    void setAdvisor(Teacher* t);
    
    void showAdvisor();
};

// 教师类:引用Student类
class Teacher {
private:
    string name;
    Student* student;  // 学生

public:
    Teacher(string n) : name(n) {}
    
    void setStudent(Student* s) {
        student = s;
    }
    
    string getName() const {
        return name;
    }
};

// 实现Student类的成员函数(此时Teacher类已完整定义)
void Student::setAdvisor(Teacher* t) {
    advisor = t;
    t->setStudent(this);  // 相互关联
}

void Student::showAdvisor() {
    cout << name << "的导师是:" << advisor->getName() << endl;
}

int main() {
    Student s("李四");
    Teacher t("王教授");
    
    s.setAdvisor(&t);  // 建立关联
    s.showAdvisor();
    
    return 0;
}

4.5 UML 图形标识

        UML(统一建模语言)是用于软件设计的标准化图形语言,类图是最常用的 UML 图之一。

4.5.1 UML 简介

        UML 提供了一套图形符号用于可视化软件系统的结构和行为,包括类图、用例图、时序图等。类图用于描述类的结构及类之间的关系。

4.5.2 UML 类图

UML 类图用矩形表示类,分为三部分:类名、属性、方法。

符号说明

  • +:public 成员
  • -:private 成员
  • #:protected 成员
  • 类之间的连线表示关系:--表示关联,<-表示继承

4.6 结构体和联合体

4.6.1 结构体

        结构体(struct)与类类似,但默认访问权限为public(类默认是private)。通常用于存储数据集合,类用于封装数据和行为。

代码语言:javascript
复制
// 结构体示例:点坐标
struct Point {
    int x;  // 默认public
    int y;  // 默认public
    
    // 结构体也可以有成员函数
    Point(int x_, int y_) : x(x_), y(y_) {}
    
    void print() {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

// 结构体与类的区别演示
class PointClass {
    int x;  // 默认private
    int y;  // 默认private
    
public:  // 需要显式声明public
    PointClass(int x_, int y_) : x(x_), y(y_) {}
    
    void print() {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

int main() {
    // 结构体使用
    Point p1(3, 4);
    p1.x = 5;  // 直接访问public成员
    p1.print();  // (5, 4)
    
    // 类使用
    PointClass p2(6, 7);
    // p2.x = 8;  // 错误:x是private成员
    p2.print();  // (6, 7)
    
    return 0;
}
4.6.2 联合体

        联合体(union)所有成员共享同一块内存空间,同一时间只能存储一个成员的值,节省内存。

代码语言:javascript
复制
#include <cstring>  // 用于strcpy

// 联合体示例:存储不同类型的数据,但只能同时使用一种
union Data {
    int i;  // 整数
    float f;  // 浮点数
    char str[20];  // 字符串
};

int main() {
    Data d;
    
    d.i = 100;  // 存储整数
    cout << "整数值:" << d.i << endl;
    
    d.f = 3.14f;  // 存储浮点数(覆盖之前的整数)
    cout << "浮点数值:" << d.f << endl;
    
    strcpy(d.str, "Hello Union");  // 存储字符串(覆盖之前的浮点数)
    cout << "字符串值:" << d.str << endl;
    
    // 注意:此时访问i或f会得到无意义的值
    cout << "此时访问整数i:" << d.i << "(无意义)" << endl;
    
    // 联合体大小为最大成员的大小
    cout << "Data联合体大小:" << sizeof(Data) << "字节" << endl;
    
    return 0;
}

4.7 枚举类型 ——enum

枚举类型用于定义命名的整数常量集合,提高代码可读性。

代码语言:javascript
复制
// 传统枚举
enum Weekday {
    Monday,    // 0
    Tuesday,   // 1
    Wednesday, // 2
    Thursday,  // 3
    Friday,    // 4
    Saturday,  // 5
    Sunday     // 6
};

// 带作用域的枚举(C++11):避免命名冲突
enum class Color {
    Red,    // 0
    Green,  // 1
    Blue,   // 2
    Yellow  // 3
};

// 指定枚举值
enum Month {
    January = 1,
    February,  // 2
    March,     // 3
    // ... 其他月份
    December = 12
};

int main() {
    // 使用传统枚举
    Weekday today = Wednesday;
    cout << "今天是星期" << today + 1 << endl;  // Wednesday是2,输出3
    
    // 使用带作用域的枚举(必须加作用域)
    Color favorite = Color::Blue;
    if (favorite == Color::Blue) {
        cout << "我最喜欢的颜色是蓝色" << endl;
    }
    
    // 使用指定值的枚举
    Month currentMonth = January;
    cout << "当前月份是:" << currentMonth << endl;  // 输出1
    
    return 0;
}

4.8 综合实例 —— 个人银行账户管理程序

4.8.1 类的设计

设计BankAccount类实现个人银行账户管理,包含以下功能:

  • 账户信息:账号、姓名、余额
  • 操作:存款、取款、查询余额、显示账户信息
  • 记录交易历史
4.8.2 源程序及说明
代码语言:javascript
复制
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>  // 用于格式化输出
#include <ctime>    // 用于记录交易时间
#include <sstream>  // 用于数值转字符串(C++98方式)
using namespace std;

// 辅助函数:将double转换为string(C++98兼容)
string doubleToString(double value) {
    stringstream ss;
    ss << value;
    return ss.str();
}

// 银行账户类
class BankAccount {
private:
    string accountId;  // 账号
    string name;       // 账户姓名
    double balance;    // 账户余额
    vector<string> transactionHistory;  // 交易历史记录

    // 获取当前时间字符串(辅助函数)
    string getCurrentTime() {
        time_t now = time(0);
        char* dt = ctime(&now);
        return string(dt);  // 转换为string并返回
    }

public:
    // 构造函数:初始化账户
    BankAccount(string id, string n, double initial = 0.0) 
        : accountId(id), name(n), balance(initial) {
        // 记录开户信息(使用自定义的doubleToString替代to_string)
        if (initial > 0) {
            transactionHistory.push_back(getCurrentTime() + "  开户存款:" + doubleToString(initial) + "元");
        } else {
            transactionHistory.push_back(getCurrentTime() + "  账户开户,初始余额:0元");
        }
    }

    // 存款
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            // 使用自定义的doubleToString替代to_string
            transactionHistory.push_back(getCurrentTime() + "  存款:" + doubleToString(amount) + "元,余额:" + doubleToString(balance) + "元");
            cout << "存款成功!当前余额:" << balance << "元" << endl;
        } else {
            cout << "存款金额必须大于0!" << endl;
        }
    }

    // 取款
    bool withdraw(double amount) {
        if (amount <= 0) {
            cout << "取款金额必须大于0!" << endl;
            return false;
        }
        if (amount > balance) {
            cout << "余额不足!当前余额:" << balance << "元" << endl;
            return false;
        }
        
        balance -= amount;
        // 使用自定义的doubleToString替代to_string
        transactionHistory.push_back(getCurrentTime() + "  取款:" + doubleToString(amount) + "元,余额:" + doubleToString(balance) + "元");
        cout << "取款成功!当前余额:" << balance << "元" << endl;
        return true;
    }

    // 查询余额
    double getBalance() const {
        return balance;
    }

    // 显示账户基本信息
    void showInfo() const {
        cout << "\n===== 账户信息 =====" << endl;
        cout << "账号:" << accountId << endl;
        cout << "姓名:" << name << endl;
        cout << "当前余额:" << fixed << setprecision(2) << balance << "元" << endl;
        cout << "====================" << endl << endl;
    }

    // 显示交易历史(使用C++98的迭代器循环替代范围for循环)
    void showHistory() const {
        cout << "\n===== 交易历史 =====" << endl;
        // 用迭代器循环替代范围for循环
        for (vector<string>::const_iterator it = transactionHistory.begin(); 
             it != transactionHistory.end(); ++it) {
            cout << *it;
        }
        cout << "====================" << endl << endl;
    }
};

// 主函数:实现用户交互
int main() {
    // 创建银行账户
    string id, name;
    double initial;
    
    cout << "===== 欢迎使用个人银行账户管理系统 =====" << endl;
    cout << "请输入账号:";
    cin >> id;
    cin.ignore();  // 忽略输入缓冲区的换行符
    
    cout << "请输入账户姓名:";
    getline(cin, name);
    
    cout << "请输入初始存款金额(0表示不存款):";
    cin >> initial;
    
    // 创建账户对象
    BankAccount account(id, name, initial);
    cout << "账户创建成功!" << endl;
    
    // 菜单循环
    int choice;
    double amount;
    do {
        cout << "\n===== 操作菜单 =====" << endl;
        cout << "1. 存款" << endl;
        cout << "2. 取款" << endl;
        cout << "3. 查询余额" << endl;
        cout << "4. 显示账户信息" << endl;
        cout << "5. 查看交易历史" << endl;
        cout << "0. 退出系统" << endl;
        cout << "请选择操作:";
        cin >> choice;
        
        switch (choice) {
            case 1:  // 存款
                cout << "请输入存款金额:";
                cin >> amount;
                account.deposit(amount);
                break;
                
            case 2:  // 取款
                cout << "请输入取款金额:";
                cin >> amount;
                account.withdraw(amount);
                break;
                
            case 3:  // 查询余额
                cout << "当前账户余额:" << fixed << setprecision(2) 
                     << account.getBalance() << "元" << endl;
                break;
                
            case 4:  // 显示账户信息
                account.showInfo();
                break;
                
            case 5:  // 查看交易历史
                account.showHistory();
                break;
                
            case 0:  // 退出
                cout << "感谢使用,再见!" << endl;
                break;
                
            default:
                cout << "无效的选择,请重新输入!" << endl;
        }
    } while (choice != 0);
    
    return 0;
}

程序说明

  1. BankAccount类封装了账户的核心功能,通过私有成员保护账户数据
  2. 使用vector存储交易历史,每次操作都记录时间和金额
  3. 主函数实现交互式菜单,用户可通过选择数字执行不同操作
  4. 使用fixedsetprecision确保金额显示两位小数
  5. 记录交易时间,提高交易历史的实用性

4.9 深度探索

4.9.1 位域

        位域(bit field)允许将数据成员存储在指定的二进制位数中,节省内存空间

代码语言:javascript
复制
#include <iostream>
using namespace std;

// 使用位域存储状态信息
struct Flags {
    // 每个成员后面的数字表示占用的位数
    unsigned int isActive : 1;    // 1位:0或1
    unsigned int mode : 2;        // 2位:0~3
    unsigned int priority : 3;    // 3位:0~7
    unsigned int reserved : 26;   // 26位:预留(总32位)
};

int main() {
    Flags f;
    
    // 设置位域值
    f.isActive = 1;    // 激活状态
    f.mode = 2;        // 模式2
    f.priority = 5;    // 优先级5
    
    cout << "isActive: " << f.isActive << endl;
    cout << "mode: " << f.mode << endl;
    cout << "priority: " << f.priority << endl;
    
    // 位域结构体大小
    cout << "Flags结构体大小:" << sizeof(f) << "字节" << endl;  // 输出4字节(32位)
    
    return 0;
}
4.9.2 用构造函数定义类型转换

        构造函数可以定义从其他类型到当前类类型的隐式转换,使用explicit关键字可禁止隐式转换。

代码语言:javascript
复制
class Distance {
private:
    double meters;  // 以米为单位存储距离

public:
    // 普通构造函数
    Distance(double m) : meters(m) {}
    
    // explicit构造函数:禁止隐式转换
    explicit Distance(int cm) {
        meters = cm / 100.0;  // 厘米转米
    }
    
    double getMeters() const { return meters; }
    double getCentimeters() const { return meters * 100; }
};

// 接受Distance类型参数的函数
void printDistance(Distance d) {
    cout << "距离:" << d.getMeters() << "米 (" 
         << d.getCentimeters() << "厘米)" << endl;
}

int main() {
    // 隐式转换:double -> Distance(允许,因为构造函数未加explicit)
    Distance d1 = 10.5;  // 等价于 Distance d1(10.5)
    printDistance(20.3);  // 隐式转换:20.3 -> Distance
    
    // 下面代码会报错,因为Distance(int)是explicit的
    // Distance d2 = 1500;  // 错误:禁止int到Distance的隐式转换
    // printDistance(2000);  // 错误:禁止隐式转换
    
    // 显式转换是允许的
    Distance d3 = Distance(1500);  // 显式构造
    printDistance(Distance(2000));  // 显式转换
    
    return 0;
}
4.9.3 对象作为函数参数和返回值的传递方式

        对象作为参数或返回值时有三种传递方式:值传递、引用传递、指针传递。

代码语言:javascript
复制
#include <iostream>
using namespace std;

class MyClass {
private:
    int value;

public:
    // 构造函数
    MyClass(int v) : value(v) {
        cout << "构造函数:value = " << value << endl;
    }
    
    // 复制构造函数
    MyClass(const MyClass& other) : value(other.value) {
        cout << "复制构造函数:value = " << value << endl;
    }
    
    // 析构函数
    ~MyClass() {
        cout << "析构函数:value = " << value << endl;
    }
    
    void setValue(int v) { value = v; }
    int getValue() const { return value; }
};

// 1. 值传递:会调用复制构造函数
void passByValue(MyClass obj) {
    obj.setValue(20);
    cout << "值传递函数内:" << obj.getValue() << endl;
}

// 2. 引用传递:不会调用复制构造函数
void passByReference(MyClass& obj) {
    obj.setValue(30);
    cout << "引用传递函数内:" << obj.getValue() << endl;
}

// 3. 指针传递:不会调用复制构造函数
void passByPointer(MyClass* obj) {
    obj->setValue(40);
    cout << "指针传递函数内:" << obj->getValue() << endl;
}

// 返回值传递:会调用复制构造函数(编译器可能优化)
MyClass returnByValue() {
    MyClass temp(50);
    return temp;  // 返回临时对象
}

int main() {
    MyClass obj(10);
    cout << "调用函数前:" << obj.getValue() << endl;
    
    // 测试值传递
    passByValue(obj);
    cout << "值传递后:" << obj.getValue() << endl;  // 仍为10(修改的是副本)
    
    // 测试引用传递
    passByReference(obj);
    cout << "引用传递后:" << obj.getValue() << endl;  // 变为30(修改的是原对象)
    
    // 测试指针传递
    passByPointer(&obj);
    cout << "指针传递后:" << obj.getValue() << endl;  // 变为40(修改的是原对象)
    
    // 测试返回值传递
    MyClass obj2 = returnByValue();
    cout << "返回值传递后:" << obj2.getValue() << endl;  // 50
    
    return 0;
}

4.10 小结

本章深入讲解了 C++ 面向对象程序设计的核心概念 —— 类与对象,主要内容包括:

  1. 面向对象四大特性:抽象、封装、继承、多态是 OOP 的核心思想,为模块化和复用提供支持。
  2. 类与对象基础:类是对象的模板,定义了属性和方法;对象是类的实例,通过成员访问运算符.使用。
  3. 构造与析构函数:负责对象的初始化和资源清理,包括默认构造、复制构造、移动构造等特殊形式。
  4. 类的组合:体现 "has-a" 关系,一个类包含其他类的对象作为成员,需注意构造和析构顺序。
  5. UML 类图:用于可视化类的结构和关系,是设计阶段的重要工具。
  6. 结构体与联合体:结构体默认 public 访问,适合数据集合;联合体共享内存,适合存储互斥数据。
  7. 枚举类型:定义命名常量集合,提高代码可读性,C++11 引入带作用域的枚举。
  8. 综合应用:通过银行账户管理程序实践了类的设计和使用。
  9. 深度主题:位域、类型转换、对象传递方式等高级特性,帮助理解底层机制。

        掌握类与对象是学好 C++ 的关键,它们为后续学习继承、多态、模板等高级特性奠定了基础。建议多编写代码实践,深入理解面向对象的思维方式。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本章知识思维导图
  • 4.1 面向对象程序设计的基本特点
    • 4.1.1 抽象
    • 4.1.2 封装
    • 4.1.3 继承
    • 4.1.4 多态
  • 4.2 类和对象
    • 4.2.1 类的定义
    • 4.2.2 类成员的访问控制
    • 4.2.3 对象
    • 4.2.4 类的成员函数
    • 4.2.5 程序实例:点类的定义与使用
  • 4.3 构造函数和析构函数
    • 4.3.1 构造函数
    • 4.3.2 默认构造函数
    • 4.3.3 委托构造函数
    • 4.3.4 复制构造函数
    • 4.3.5 析构函数
    • 4.3.6 移动构造函数
    • 4.3.7 default、delete 函数
    • 4.3.8 程序实例:构造函数与析构函数综合示例
  • 4.4 类的组合
    • 4.4.1 组合
    • 4.4.2 前向引用声明
  • 4.5 UML 图形标识
    • 4.5.1 UML 简介
    • 4.5.2 UML 类图
  • 4.6 结构体和联合体
    • 4.6.1 结构体
    • 4.6.2 联合体
  • 4.7 枚举类型 ——enum
  • 4.8 综合实例 —— 个人银行账户管理程序
    • 4.8.1 类的设计
    • 4.8.2 源程序及说明
  • 4.9 深度探索
    • 4.9.1 位域
    • 4.9.2 用构造函数定义类型转换
    • 4.9.3 对象作为函数参数和返回值的传递方式
  • 4.10 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档