← 练习册目录

第 5 章 类与对象

red wenzi · 2026-09-15 · 编程语言 · C++ · 练习册 · 15 题
📝 本章练习:15 题(A 识别 / B 理解 / C 改错 / D 写程序)。先自己做完再看答案——A、B 档不翻书做,C、D 档必须真的编译运行。

5.1 构造函数与析构函数

A1识别判断题:Point p{1.0, 2.0}; 直接在栈上创建了一个真实对象,离开作用域自动销毁。

答:______________

✅ 查看答案与解析

答案 对

解析:C++ 默认应该在栈上建对象:Point p{1.0, 2.0}; 没有任何运行时代价,也不需要 new。Java 的 new 习惯要改过来。

A2识别判断题:析构函数不能有参数、不能重载、不能抛异常。

答:______________

✅ 查看答案与解析

答案 对

解析:析构函数由编译器自动调用(栈对象出作用域、delete 堆对象、临时对象结束),不能抛异常,否则程序终止。

B1理解选择题:类的 const 成员变量必须在哪初始化?

构造函数体里赋值

初始化列表里

声明之后随便哪里

不能初始化

答:______________

✅ 查看答案与解析

答案 B

解析:const 成员、引用成员、没有默认构造函数的类类型成员,必须在初始化列表里初始化;写在函数体里是赋值,对它们不可能。

C1应用改错题:下面构造函数有误——成员 name_ 声明在后,却用它初始化先声明的成员。
class Buffer {
    std::vector<int> data_;
    std::string name_;
public:
    Buffer() : name_("buf"), data_(10, 0) { }  // 实际初始化顺序按声明顺序!
✅ 查看答案与解析

答案 把初始化列表顺序改成和声明顺序一致:

解析:成员初始化顺序由声明顺序决定,和列表书写顺序无关。编译器有 -Wreorder 专门提醒这件事。

    Buffer() : data_(10, 0), name_("buf") { }
D1创造写程序:写一个 Point 类(double x, y + 构造函数 + 求到原点距离的 dist() 成员函数),创建对象并输出距离。

提示:dist 用 std::sqrt(x*x + y*y),需要 #include <cmath>;dist 加 const 修饰。

✅ 查看答案与解析

答案 参考答案:

解析:struct 默认公有成员,适合数据打包;const 成员函数承诺不修改对象。

#include <iostream>
#include <cmath>
struct Point {
    double x = 0.0;
    double y = 0.0;
    double dist() const { return std::sqrt(x * x + y * y); }
};
int main() {
    Point p{3.0, 4.0};
    std::cout << p.dist() << '\n';   // 5
    return 0;
}

5.2 拷贝控制

A1识别判断题:默认的拷贝构造是逐成员拷贝。成员都是 int、std::string、std::vector 时,默认行为是正确的,不用写任何东西。

答:______________

✅ 查看答案与解析

答案 对

解析:零法则:先用会管理资源的成员,能不写裸指针就不写,大部分内存管理问题自然消失。

A2识别判断题:类里含有裸指针或自己管理的资源时,默认拷贝会复制指针值,导致两个对象指向同一块内存、析构释放两次。

答:______________

✅ 查看答案与解析

答案 对

解析:这就是浅拷贝问题。这种类需要自己写深拷贝:拷贝构造、拷贝赋值、析构(以及移动构造、移动赋值)——即五法则。

B1理解选择题:IntArray b = a;(IntArray 是含裸指针的类)会调用什么?

移动构造

拷贝构造

默认构造

拷贝赋值

答:______________

✅ 查看答案与解析

答案 B

解析:用已存在的 a 初始化新对象 b,调用拷贝构造。IntArray c = std::move(a); 才调用移动构造。

C1应用改错题:下面类缺了拷贝控制,发生双重释放。
class IntArray {
public:
    IntArray(std::size_t n) : size_(n), data_(new int[n]{}) { }
    ~IntArray() { delete[] data_; }
    // 缺拷贝构造、拷贝赋值、移动构造、移动赋值
private:
    std::size_t size_;
    int* data_;
};
✅ 查看答案与解析

答案 补上五法则的其余四个函数(拷贝构造做深拷贝、拷贝赋值处理自赋值、移动构造/移动赋值窃取资源),或者把 data_ 换成 std::vector<int>。

解析:如果一个类需要自己写析构,那它通常也需要拷贝构造、拷贝赋值、移动构造、移动赋值。最省事:不写裸指针。

D1创造写程序:写一个简单的 Counter 类:构造时 ++instances、析构时 --instances,用静态成员记录存活对象数;main 里创建 3 个对象输出计数。

提示:静态成员变量要在类外定义一次:int Counter::instances = 0;(或 C++17 用 inline static)。

✅ 查看答案与解析

答案 参考答案:

解析:静态成员属于类、所有对象共享;静态成员函数用 ClassName:: 访问。

#include <iostream>
class Counter {
public:
    Counter() { ++instances; }
    ~Counter() { --instances; }
    static int count() { return instances; }
private:
    static int instances;
};
int Counter::instances = 0;
int main() {
    Counter a, b, c;
    std::cout << Counter::count() << '\n';   // 3
    { Counter d; }
    std::cout << Counter::count() << '\n';   // 3(d 已析构)
    return 0;
}

5.3 运算符重载与继承

A1识别判断题:排序用的比较器必须是严格弱序,相等时返回 false;写成 return a.score <= b.score; 会让 std::sort 行为未定义。

答:______________

✅ 查看答案与解析

答案 对

解析:严格弱序:相等必须返回 false。写成 <= 或 >= 会让 sort 走进未定义行为,典型症状是段错误或元素丢失。

A2识别判断题:多态基类的析构函数必须是虚的(virtual)。

答:______________

✅ 查看答案与解析

答案 对

解析:否则 Shape* p = new Circle(); delete p; 只调用 Shape 的析构,Circle 的资源就泄漏了。

B1理解选择题:Shape s = Circle(1.0);(Shape 是基类,Circle 是派生类)会发生什么?

正常多态调用

对象切片:Circle 被切成 Shape,多态信息丢失

编译错误

运行时报错

答:______________

✅ 查看答案与解析

答案 B

解析:按值传递基类会发生对象切片,多态信息全部丢失。多态必须通过指针或引用使用。

C1应用改错题:派生类想覆盖基类函数,但下面的写法编译通过、行为却不对(没覆盖成功)。
class Shape {
public:
    virtual double area() const = 0;
};
class Circle : public Shape {
public:
    double area() { return 3.14 * r_ * r_; }   // 少了 const!
private:
    double r_;
};
✅ 查看答案与解析

答案 加 const 和 override:

解析:签名不完全一致(少 const)就不是覆盖,而是新增了一个隐藏函数。override 关键字能帮编译器检查出来。

    double area() const override { return 3.14 * r_ * r_; }
D1创造写程序:定义 Student 结构体(name、score),重载 operator<(按分数降序),把 3 个学生放进 vector 排序并输出。

提示:std::sort 需要 operator< 或自定义比较器。

✅ 查看答案与解析

答案 参考答案:

解析:operator< 返回 score > o.score 实现降序;记住严格弱序要求。

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
struct Student {
    std::string name;
    int score;
    bool operator<(const Student& o) const { return score > o.score; }
};
int main() {
    std::vector<Student> v{{"Ann", 90}, {"Bob", 95}, {"Cid", 88}};
    std::sort(v.begin(), v.end());
    for (const auto& s : v) { std::cout << s.name << ' ' << s.score << '\n'; }
    return 0;
}
// 输出:Bob 95 / Ann 90 / Cid 88
📚 相关概念:编译与链接 · STL · map / set · 迭代器