Skip to content

《C++面向对象程序设计(甲)》期末试卷B (精选04)

一、单项选择题(每题 2 分,总计 20 分)

  1. 面向对象程序设计中的数据隐藏指的是( )
    • A. 输入数据必须输入保密口令
    • B. 数据经过加密处理
    • C. 对象内部数据和代码合并在一起
    • D. 对象内部数据结构的不可访问性
查看答案与解析

答案:D

解析: 数据隐藏(Data Hiding)是面向对象封装性的核心概念,指对象内部的数据结构对外部是不可访问的,外部只能通过对象提供的公有接口(成员函数)来操作数据。

  • A:❌ 保密口令是安全机制,不是数据隐藏
  • B:❌ 加密处理是数据安全手段,不是数据隐藏
  • C:❌ 数据与代码合并是封装的表现形式,但不是数据隐藏的定义
  • D:✅ 数据隐藏的本质是内部数据结构对外不可见

难度: ⭐ 考点: #数据隐藏 #封装性 #面向对象

💡 学习锦囊

📖 相关公式与知识点:

  • 封装 = 数据隐藏 + 公有接口
  • 访问控制:private 成员对外隐藏,public 成员对外可见
  • 数据隐藏提高了代码的安全性和可维护性

易错点

  • 数据隐藏 ≠ 数据加密
  • 封装不仅包括数据隐藏,还包括将数据与操作绑定在一起
🔄 举一反三
  1. 下列关于封装的描述,错误的是( )
    • A. 封装将数据与操作数据的函数绑定在一起
    • B. 封装通过访问控制实现数据隐藏
    • C. 封装意味着所有数据都必须加密存储
    • D. 封装提高了代码的可维护性
    查看练习答案与解析

    答案:C 解析:封装不要求数据加密,而是通过 private 等访问控制实现数据隐藏。

  1. 一个内联函数 Fun,使用 int 类型的参数,求其平方并返回,返回值也为 int 类型,下列定义正确的是( )
    • A. int Fun(int x){return x*x;}
    • B. inline int Fun(int x){return x*x;}
    • C. int Fun inline(int x){return x*x;}
    • D. int Fun(int x){inline return x*x;}
查看答案与解析

答案:B

解析: 内联函数的正确声明格式:inline 返回类型 函数名(参数列表) { 函数体 }

  • A:❌ 缺少 inline 关键字,是普通函数
  • B:✅ 标准的内联函数定义格式
  • C:❌ inline 放在函数名和参数列表之间,语法错误
  • D:❌ inline 不能放在 return 语句前

难度: ⭐ 考点: #内联函数 #inline关键字

💡 学习锦囊

📖 相关公式与知识点:

  • 内联函数声明:inline 返回类型 函数名(参数) { 函数体 }
  • 类体内定义的成员函数自动成为内联函数
  • inline 是建议,编译器可能忽略

易错点

  • inline 必须放在返回类型之前
  • 类体外定义的成员函数需要显式加 inline
🔄 举一反三
  1. 下列内联函数定义正确的是( )
    • A. inline int max(int a, int b) { return a > b ? a : b; }
    • B. int max inline(int a, int b) { return a > b ? a : b; }
    • C. int max(int a, int b) inline { return a > b ? a : b; }
    • D. int max(inline int a, inline int b) { return a > b ? a : b; }
    查看练习答案与解析

    答案:A 解析inline 必须在返回类型之前。B 中 inline 放在函数名和参数之间,C 中 inline 放在函数体前,D 中 inline 放在参数前,均为语法错误。

  1. 下面关于重载函数的叙述中正确的是( )
    • A. 重载函数必须具有不同的返回值类型
    • B. 重载函数的形参个数必须不同
    • C. 重载函数必须有不同的形参列表
    • D. 重载函数的函数名可以不同
查看答案与解析

答案:C

解析: 函数重载的核心规则:同名函数,参数列表必须不同(参数个数、类型或顺序不同)。

  • A:❌ 返回值类型不同不能构成重载
  • B:❌ 形参个数可以相同,只要类型不同即可
  • C:✅ 重载函数必须有不同的形参列表
  • D:❌ 重载函数的函数名必须相同
cpp
void func(int a);        // 1
void func(double a);     // 2 - 参数类型不同,合法重载
void func(int a, int b); // 3 - 参数个数不同,合法重载
int func(int a);         // ❌ 仅返回值不同,不合法

难度: ⭐ 考点: #函数重载 #重载规则

💡 学习锦囊

📖 相关公式与知识点:

  • 重载条件:函数名相同,参数列表不同
  • 参数列表不同:个数不同、类型不同、顺序不同
  • 返回值类型、默认参数不参与重载区分

易错点

  • 仅返回值类型不同不能构成重载
  • 仅默认参数不同不能构成重载
🔄 举一反三
  1. 下列函数声明中,能与 void func(int a) 构成重载的是( )
    • A. int func(int a)
    • B. void func(int b)
    • C. void func(double a)
    • D. void func(int a, int b = 0)
    查看练习答案与解析

    答案:C 解析:A 仅返回值不同,B 参数完全相同(形参名不影响),C 参数类型不同,D 默认参数不参与重载区分。

  1. 下列关于纯虚函数的描述中,错误的是( )
    • A. 在基类中只有声明,通常没有函数体定义
    • B. 可以使用包含纯虚函数的类来创建对象
    • C. 当需要使用包含纯虚函数的基类的派生类创建对象时,须在派生类中给出该函数定义
    • D. 包含纯虚函数的类称为抽象类
查看答案与解析

答案:B

解析: 包含纯虚函数的类是抽象类不能创建对象

  • A:✅ 纯虚函数在基类中通常只声明不定义(C++ 允许在类外为纯虚函数提供定义,但通常不提供)
  • B:❌ 抽象类不能实例化,这是错误的说法
  • C:✅ 派生类必须实现纯虚函数才能创建对象
  • D:✅ 含纯虚函数的类称为抽象类

难度: ⭐ 考点: #纯虚函数 #抽象类

💡 学习锦囊

📖 相关公式与知识点:

  • 纯虚函数:virtual 返回类型 函数名(参数) = 0;
  • 抽象类:包含至少一个纯虚函数的类
  • 抽象类不能创建对象,但可以定义指针和引用

易错点

  • 抽象类不能实例化,这是最常见的考点
  • 抽象类可以有构造函数(供派生类调用)
🔄 举一反三
  1. 关于抽象类,下列说法正确的是( )
    • A. 抽象类不能有构造函数
    • B. 抽象类不能有数据成员
    • C. 抽象类不能创建对象
    • D. 抽象类不能定义指针
    查看练习答案与解析

    答案:C 解析:抽象类可以有构造函数和数据成员,可以定义指针和引用,但不能创建对象。

  1. 下列关于析构函数的描述中正确的是( )
    • A. 析构函数可以重载
    • B. 析构函数可以是虚函数
    • C. 析构函数名与类名相同
    • D. 析构函数的返回类型为 void
查看答案与解析

答案:B

解析:

  • A:❌ 析构函数不能重载(无参数,只能有一个)
  • B:✅ 析构函数可以是虚函数,且基类析构函数通常应声明为虚函数
  • C:❌ 析构函数名是 ~类名,不是与类名完全相同
  • D:❌ 析构函数没有返回类型(连 void 也不能写)
cpp
class Base {
public:
    virtual ~Base() {}  // 虚析构函数
};

难度: ⭐⭐ 考点: #析构函数 #虚析构函数

💡 学习锦囊

📖 相关公式与知识点:

  • 析构函数:~类名(),无参数、无返回值、不可重载
  • 虚析构函数:基类析构函数声明为 virtual,确保派生类对象正确析构
  • 经验法则:有虚函数的基类应声明虚析构函数

易错点

  • 析构函数名是 ~类名,不是与类名完全相同
  • 析构函数没有返回类型,void 也不能写
🔄 举一反三
  1. 若基类析构函数不是虚函数,delete 基类指针指向派生类对象时会发生什么?( )
    • A. 正常析构派生类和基类部分
    • B. 只析构基类部分,派生类部分泄漏
    • C. 编译错误
    • D. 只析构派生类部分
    查看练习答案与解析

    答案:B 解析:非虚析构函数下,delete 基类指针只调用基类析构函数,派生类部分资源无法释放。

  1. 下列关于纯虚函数的描述中,正确的是( )
    • A. 纯虚函数是一种特殊的虚函数,它是个空函数
    • B. 具有纯虚函数的类称为虚基类
    • C. 一个基类中说明有纯虚函数,其派生类一定要实现该纯虚函数
    • D. 具有纯虚函数的类不能创建类对象
查看答案与解析

答案:D

解析:

  • A:❌ 纯虚函数不一定是空函数,可以有默认实现
  • B:❌ 具有纯虚函数的类称为抽象类,不是虚基类
  • C:❌ 派生类可以不实现纯虚函数,此时派生类也是抽象类
  • D:✅ 抽象类不能创建对象

难度: ⭐ 考点: #纯虚函数 #抽象类

💡 学习锦囊

📖 相关公式与知识点:

  • 抽象类 ≠ 虚基类(虚基类用于解决菱形继承问题)
  • 派生类可以不实现纯虚函数,但此时派生类仍是抽象类
  • 纯虚函数可以有函数体:virtual void f() = 0 { /* ... */ }

易错点

  • "虚基类"和"抽象类"是两个完全不同的概念
  • 派生类不是"一定要"实现纯虚函数
🔄 举一反三
  1. 若类 B 继承自抽象类 A 但不实现 A 的纯虚函数,则( )
    • A. 编译错误
    • B. B 也是抽象类,不能创建 B 的对象
    • C. B 可以创建对象,调用纯虚函数时崩溃
    • D. B 自动成为普通类
    查看练习答案与解析

    答案:B 解析:派生类不实现纯虚函数时,派生类仍是抽象类,不能创建对象。

  1. 复制初始化构造函数的作用是( )
    • A. 进行数据类型的转换
    • B. 用对象调用成员函数
    • C. 用对象初始化对象
    • D. 用一般类型的数据初始化对象
查看答案与解析

答案:C

解析: 复制初始化构造函数(拷贝构造函数)的作用是用已有对象初始化新创建的对象

cpp
MyClass obj1;
MyClass obj2(obj1);    // 拷贝构造:用 obj1 初始化 obj2
MyClass obj3 = obj1;   // 拷贝构造:用 obj1 初始化 obj3

难度: ⭐ 考点: #拷贝构造函数 #对象初始化

💡 学习锦囊

📖 相关公式与知识点:

  • 拷贝构造函数:类名(const 类名 &obj)
  • 调用时机:①用对象初始化对象 ②值传递 ③返回对象
  • 默认拷贝构造函数执行浅拷贝

易错点

  • 拷贝构造是"初始化"新对象,不是"赋值"已有对象
  • obj2 = obj1 是赋值操作,不调用拷贝构造函数
🔄 举一反三
  1. 下列哪种情况会调用拷贝构造函数?( )
    • A. MyClass a; MyClass b; b = a;
    • B. MyClass a; MyClass b(a);
    • C. MyClass *p = new MyClass;
    • D. MyClass a; MyClass &b = a;
    查看练习答案与解析

    答案:B 解析:A 是赋值操作,B 是拷贝构造,C 是普通构造,D 是引用不调用任何构造函数。

  1. 所谓多态性是指( )
    • A. 不同的对象调用不同名称的函数
    • B. 不同的对象调用相同名称的函数
    • C. 一个对象调用不同名称的函数
    • D. 一个对象调用不同名称的对象
查看答案与解析

答案:B

解析: 多态性(Polymorphism)的核心含义:不同的对象在接收到相同名称的函数调用时,表现出不同的行为

cpp
class Shape {
public:
    virtual void draw() = 0;
};
class Circle : public Shape {
    void draw() { /* 画圆 */ }
};
class Square : public Shape {
    void draw() { /* 画方 */ }
};
// 不同对象调用同名函数 draw(),表现不同行为

难度: ⭐ 考点: #多态性 #虚函数

💡 学习锦囊

📖 相关公式与知识点:

  • 多态 = 同一接口,不同实现
  • 静态多态:函数重载、模板(编译时)
  • 动态多态:虚函数(运行时)

易错点

  • 多态强调"不同对象"对"同名函数"的不同响应
🔄 举一反三
  1. C++ 中实现运行时多态的关键机制是( )
    • A. 函数重载
    • B. 模板
    • C. 虚函数
    • D. 内联函数
    查看练习答案与解析

    答案:C 解析:虚函数 + 基类指针/引用实现运行时多态。函数重载和模板是编译时多态。

  1. 要将类 A 说明是类 B 的虚基类,正确的描述是( )
    • A. class virtual B:public A
    • B. class B:virtual public A
    • C. virtual class B:public A
    • D. class B:public A virtual
查看答案与解析

答案:B

解析: 虚基类的正确声明格式:class 派生类 : virtual 继承方式 基类

cpp
class B : virtual public A { };  // ✅ A 是 B 的虚基类

virtual 关键字放在继承方式之前。


难度: ⭐ 考点: #虚基类 #virtual继承

💡 学习锦囊

📖 相关公式与知识点:

  • 虚基类声明:class D : virtual public B { };
  • 虚基类用于解决菱形继承中的二义性问题
  • 虚基类由最远派生类负责初始化

易错点

  • virtual 在继承方式之前,不是之后
  • 虚基类 ≠ 抽象类
🔄 举一反三
  1. 下列关于虚基类的描述,正确的是( )
    • A. 虚基类就是抽象类
    • B. 虚基类用于解决多继承中的成员二义性
    • C. 虚基类不能有数据成员
    • D. 虚基类的构造函数不会被调用
    查看练习答案与解析

    答案:B 解析:虚基类解决菱形继承中共同基类成员的多份拷贝问题。

  1. 标准模板库(STL)所涉及的 4 个最主要的基本组件是( )
    • A. 容器、迭代器、算法、函数模板
    • B. 类模板、运算符重载函数、容器、算法
    • C. 容器、算法、迭代器、函数对象
    • D. 类、对象、迭代器、函数
查看答案与解析

答案:C

解析: STL 的四大核心组件:

  1. 容器(Containers):存储数据的模板类(vector、list、map 等)
  2. 算法(Algorithms):操作数据的模板函数(sort、find、copy 等)
  3. 迭代器(Iterators):连接容器和算法的桥梁,类似指针
  4. 函数对象(Functors):重载了 operator() 的类对象

难度: ⭐ 考点: #STL #容器 #迭代器 #算法

💡 学习锦囊

📖 相关公式与知识点:

  • STL 四大组件:容器、算法、迭代器、函数对象
  • 容器:vector、list、deque、set、map 等
  • 迭代器:输入、输出、前向、双向、随机访问
  • 函数对象:重载 operator() 的类

易错点

  • 函数模板不是 STL 的四大组件之一
  • 函数对象(Functor)≠ 函数指针
🔄 举一反三
  1. STL 中连接容器和算法的桥梁是( )
    • A. 容器
    • B. 算法
    • C. 迭代器
    • D. 函数对象
    查看练习答案与解析

    答案:C 解析:迭代器是容器和算法之间的桥梁,使算法可以独立于容器类型工作。

二、程序填空题(每空 2 分,总计 20 分)

  1. 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
2,1
4,3
cpp
#include <iostream>
class A {
    int a;
public:
    A(int i = 0) { a = i; }
    int Geta() { return a; }
};

class B {
    A a;
    int b;
public:
    B(int i = 0, int j = 0) : __(1)__ {}
    void display() { cout << a.Geta() << ',' << b << endl; }
};

int main() {
    B b[2] = { B(1, 2), B(3, 4) };
    for (int i = 0; i < 2; i++)
        __(2)__
}
查看答案与解析

答案: (1)a(j), b(i)

解析: 输出 2,1 对应 B(1,2):a.Geta()=2, b=1 → a 用 j=2 初始化,b 用 i=1 初始化 输出 4,3 对应 B(3,4):a.Geta()=4, b=3 → a 用 j=4 初始化,b 用 i=3 初始化

所以初始化列表为 a(j), b(i)

(2)b[i].display()

解析: 遍历数组调用每个元素的 display() 函数输出结果。


难度: ⭐⭐ 考点: #成员初始化列表 #对象数组 #成员函数调用

💡 学习锦囊

📖 相关公式与知识点:

  • 初始化列表:成员名(初始值)
  • 对象数组成员访问:数组名[下标].成员函数()
  • 分析输出反推初始化参数

思路分析

根据输出反推初始化列表的步骤:

  1. 观察输出 2,14,3
  2. 对应输入 B(1,2)B(3,4)
  3. a 的值 = 第二个参数,b 的值 = 第一个参数
  4. 所以初始化列表为 a(j), b(i)
🔄 举一反三
  1. 若初始化列表改为 a(i), b(j),则 B(1,2) 的输出是什么?
    查看练习答案与解析

    答案1,2解析:a 用 i=1 初始化,b 用 j=2 初始化。

  1. 下面程序中 A 是抽象类。请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
B1 called
B2 called
cpp
#include <iostream>
class A {
public:
    __(3)__ ;
};

class B1 : public A {
public:
    void display() { cout << "B1 called" << endl; }
};

class B2 : public A {
public:
    void display() { cout << "B2 called" << endl; }
};

void show( __(4)__ ) {
    p->display();
}

int main() {
    B1 b1;
    B2 b2;
    A *p[2] = { &b1, &b2 };
    for (int i = 0; i < 2; i++)
        show(p[i]);
}
查看答案与解析

答案: (3)virtual void display() = 0 (4)A *p

解析:

(3):A 是抽象类,需要声明纯虚函数。display() 在派生类中被重写,所以基类中声明为 virtual void display() = 0

(4):show 函数接收基类指针 A *p,通过指针调用虚函数 display(),实现多态。


难度: ⭐⭐ 考点: #纯虚函数 #抽象类 #多态

💡 学习锦囊

📖 相关公式与知识点:

  • 抽象类 = 含纯虚函数的类
  • 纯虚函数:virtual 返回类型 函数名(参数) = 0;
  • 多态三要素:继承、虚函数、基类指针/引用

易错点

  • 纯虚函数必须有 virtual= 0
  • show 函数的参数类型必须与 p 数组的元素类型一致(A*)
🔄 举一反三
  1. 若将 show 函数的参数改为 B1 *p,则 show(p[1])(p[1] 指向 B2 对象)会怎样?
    查看练习答案与解析

    答案:编译错误,B2* 不能隐式转换为 B1* 解析:B1 和 B2 是兄弟类,指针类型不兼容。

  1. 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
Name:王小明
Grade:90
cpp
#include <iostream>
#include <cstring>
class Person {
    char name[20];
public:
    Person(char *s) { strcpy(name, s); }
    void display() { cout << "Name:" << name << endl; }
};

class Student : public Person {
    int grade;
public:
    Student(char *s, int g) : __(5)__ { grade = g; }
    void display() {
        __(6)__
        cout << "Grade:" << grade << endl;
    }
};

int main() {
    Student s("王小明", 90);
    s.display();
}
查看答案与解析

答案: (5)Person(s) (6)Person::display();

解析:

(5):Student 继承自 Person,需要在初始化列表中调用基类构造函数 Person(s) 来初始化 name。

(6):Student::display() 需要先调用基类的 display() 输出姓名,再输出成绩。使用 Person::display() 显式调用基类版本。


难度: ⭐⭐ 考点: #继承 #基类构造函数调用 #作用域解析

💡 学习锦囊

📖 相关公式与知识点:

  • 派生类构造函数初始化列表调用基类构造:派生类(参数) : 基类(参数) { }
  • 显式调用基类成员:基类名::成员名
  • 派生类对象构造顺序:基类 → 派生类

易错点

  • 基类构造函数只能在初始化列表中调用
  • 派生类中调用同名基类函数需用 基类名::函数名()
🔄 举一反三
  1. 若 Student 的构造函数改为 Student(char *s, int g) { Person(s); grade = g; },是否正确?
    查看练习答案与解析

    答案:不正确。Person(s) 在函数体内会创建一个临时 Person 对象,而不是初始化基类部分。 解析:基类构造必须在初始化列表中调用,函数体内的 Person(s) 是创建临时对象。

  1. 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为 5
cpp
#include <iostream>
class Integer {
    int x;
public:
    Integer(int a = 0) { x = a; }
    void display() { cout << x << endl; }
    __(7)__ ;
};

Integer Max(Integer a, Integer b) {
    if ( __(8)__ )
        return a;
    return b;
}

int main() {
    Integer a(3), b(5), c;
    c = Max(a, b);
    c.display();
}
查看答案与解析

答案: (7)friend Integer Max(Integer a, Integer b) (8)a.x > b.x

解析:

(7):Max 函数需要访问 Integer 类的私有成员 x,因此必须声明为友元函数。格式:friend 返回类型 函数名(参数列表);

(8):Max 函数比较两个 Integer 对象的 x 值,返回较大的。条件为 a.x > b.x


难度: ⭐⭐ 考点: #友元函数 #私有成员访问

💡 学习锦囊

📖 相关公式与知识点:

  • 友元函数声明:friend 返回类型 函数名(参数);
  • 友元函数可以访问类的私有成员
  • 友元关系是单向的、不可传递的

易错点

  • 友元函数不是成员函数,不能用 对象.友元函数() 调用
  • 友元声明放在类内部,但函数定义在类外部
🔄 举一反三
  1. 若将 Max 改为成员函数 Integer Max(Integer b),应如何实现?
    查看练习答案与解析

    答案

    cpp
    Integer Max(Integer b) {
        return x > b.x ? *this : b;
    }

    解析:成员函数可直接访问当前对象的 x,通过 b.x 访问参数对象的私有成员(同类对象间可互相访问私有成员)。

  1. 请在下面的横线处填上适当内容,以使类的定义完整。
cpp
class Array {
    int *ptr;
    int size;
public:
    Array() { size = 0; ptr = NULL; }
    Array(int n) { size = n; ptr = new int[size]; }
    Array( __(9)__ ) {  // 复制初始化构造函数
        size = a.size;
        ptr = new int[size];
        for (int i = 0; i < size; i++)
            __(10)__ ;  // 将源对象的动态数组内容复制到目标对象
    }
};
查看答案与解析

答案: (9)const Array &a (10)ptr[i] = a.ptr[i]

解析:

(9):拷贝构造函数的参数必须是本类对象的引用,通常加 const 防止修改源对象。格式:const Array &a

(10):深拷贝的核心操作——逐元素复制动态数组内容。ptr[i] = a.ptr[i]


难度: ⭐⭐ 考点: #拷贝构造函数 #深拷贝 #动态内存

💡 学习锦囊

📖 相关公式与知识点:

  • 拷贝构造函数:类名(const 类名 &obj)
  • 深拷贝:重新分配内存并复制数据
  • 浅拷贝:只复制指针值,两个对象指向同一内存

易错点

  • 拷贝构造函数参数必须是引用(否则无限递归)
  • 含动态内存的类必须实现深拷贝
🔄 举一反三
  1. 若将拷贝构造函数改为浅拷贝 Array(const Array &a) { ptr = a.ptr; size = a.size; },会出现什么问题?
    查看练习答案与解析

    答案:两个对象的 ptr 指向同一内存,析构时重复释放导致未定义行为。 解析:浅拷贝导致 double free 错误。

三、阅读程序题(每题 6 分,总计 30 分)

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
class Test {
private:
    int num;
public:
    Test(int n = 0) { num = n; num++; }
    ~Test() { cout << "Destructor is active,number=" << num << endl; }
};

int main() {
    Test x[2];
    cout << "Exiting main" << endl;
}
查看答案与解析

答案:

Exiting main
Destructor is active,number=1
Destructor is active,number=1

解析:

  1. Test x[2]:创建 2 个对象的数组,每个调用默认构造函数 Test(0)

    • num = 0,然后 num++num = 1
    • 两个对象的 num 都是 1
  2. cout << "Exiting main" << endl;:输出 Exiting main

  3. main 函数结束,数组 x 析构(逆序):

    • x[1] 析构:Destructor is active,number=1
    • x[0] 析构:Destructor is active,number=1

难度: ⭐⭐ 考点: #构造函数 #析构函数 #对象数组

💡 学习锦囊

📖 相关公式与知识点:

  • 类名 数组名[n]:创建 n 个对象,每个调用默认构造函数
  • 析构顺序与构造顺序相反
  • 默认参数 n=0 使 Test() 等价于 Test(0)

易错点

  • 注意 num++ 使最终值为 1 而非 0
  • 析构顺序是逆序的
🔄 举一反三
  1. 若将 Test x[2] 改为 Test x[2] = { Test(5), Test(10) },输出是什么?
    查看练习答案与解析

    答案

    Exiting main
    Destructor is active,number=11
    Destructor is active,number=6

    解析:x[0].num=5→6,x[1].num=10→11。析构逆序:先 11 后 6。

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
class A {
public:
    virtual void fun(int data) { cout << "class A:" << data << endl; }
    void fun(char *str) { cout << "class A:" << str << endl; }
};

class B : public A {
public:
    void fun() { cout << "class B" << endl; }
    void fun(int data) { cout << "class B:" << data << endl; }
    void fun(char *str) { cout << "class B:" << str << endl; }
};

int main() {
    A *pA;
    B b;
    pA = &b;
    pA->fun(1);
    pA->fun("Hello");
}
查看答案与解析

答案:

class B:1
class A:Hello

解析:

  1. pA->fun(1):fun(int) 在基类 A 中声明为 virtual,派生类 B 重写了它。通过基类指针调用虚函数 → 动态联编 → 调用 B::fun(int) → 输出 class B:1

  2. pA->fun("Hello"):fun(char*) 在基类 A 中没有 virtual 关键字,是普通函数。通过基类指针调用 → 静态联编 → 根据指针静态类型 A* 调用 A::fun(char*) → 输出 class A:Hello

关键区别:virtual 决定动态联编 vs 静态联编。


难度: ⭐⭐⭐ 考点: #虚函数 #动态联编 #静态联编 #函数隐藏

💡 学习锦囊

📖 相关公式与知识点:

  • 虚函数(virtual)→ 动态联编(运行时根据实际对象类型决定)
  • 非虚函数 → 静态联编(编译时根据指针/引用类型决定)
  • 派生类中与基类虚函数同名的函数自动成为虚函数

易错点

  • 只有声明为 virtual 的函数才走动态联编
  • fun(char*) 不是虚函数,所以调用基类版本
🔄 举一反三
  1. 若将 A::fun(char *str) 也声明为 virtual,则 pA->fun("Hello") 输出什么?
    查看练习答案与解析

    答案class B:Hello解析:两个 fun 都是虚函数,都走动态联编,调用 B 的版本。

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
int main() {
    cout.fill('*');
    cout.width(10);
    cout << "123.45" << endl;
    cout.width(8);
    cout << "123.45" << endl;
    cout.width(4);
    cout << "123.45" << endl;
}
查看答案与解析

答案:

****123.45
**123.45
123.45

解析: 本题考查 cout.fill()cout.width() 的用法。

  1. cout.fill('*'):设置填充字符为 *持续有效直到被重新设置
  2. cout.width(10):设置输出宽度为 10,"123.45" 占 6 个字符,左侧填充 4 个 *****123.45
  3. cout.width(8):宽度 8,"123.45" 占 6 个字符,左侧填充 2 个 *fill('*') 仍然有效)→ **123.45
  4. cout.width(4):宽度 4 < 字符串长度 6,不填充 → 123.45

注意:fill() 设置的填充字符持续有效,不会因为 width() 的重设而恢复默认。width() 只对下一次输出有效。


难度: ⭐⭐ 考点: #cout.fill #cout.width #格式化输出

💡 学习锦囊

📖 相关公式与知识点:

  • cout.width(n):设置输出宽度为 n,只对紧接着的一次输出有效
  • cout.fill(ch):设置填充字符为 ch,持续有效直到再次设置
  • 默认对齐:右对齐,左侧填充

易错点

  • width() 只生效一次,之后恢复默认宽度 0
  • fill() 持续生效
  • width(n) 的 n ≤ 实际字符数时,不填充
🔄 举一反三
  1. 若在第三次输出前调用 cout.fill('0'),第三次输出是什么?
    查看练习答案与解析

    答案123.45(width(4) < 6,不填充) 解析:width(4) 小于字符串长度 6,不进行填充。

  1. 从键盘输入 10,给出程序的输出结果。
cpp
#include <iostream>
#include <vector>
#include <iterator>
using namespace std;

int main() {
    vector<int> v;
    int i, j, n;
    cin >> n;
    for (i = 1; i <= n; i++) {
        if (i % 2 != 0) {
            v.push_back(i);
        }
    }
    vector<int>::iterator p;
    for (p = v.begin(); p != v.end(); p++)
        cout << *p << " ";
}
查看答案与解析

答案:

1 3 5 7 9

解析: 输入 n = 10,循环 i 从 1 到 10:

  • i % 2 != 0 筛选奇数:1, 3, 5, 7, 9
  • v.push_back(i) 将奇数依次添加到 vector 末尾
  • 使用迭代器遍历输出所有奇数

难度: ⭐⭐ 考点: #vector #迭代器 #push_back

💡 学习锦囊

📖 相关公式与知识点:

  • vector<int> v:动态数组
  • v.push_back(x):在末尾添加元素
  • 迭代器遍历:for (p = v.begin(); p != v.end(); p++)
  • *p 解引用获取元素值

易错点

  • push_back 添加元素,不是替换
  • 迭代器 end() 指向最后一个元素之后的位置
🔄 举一反三
  1. 若将条件改为 if (i % 2 == 0),输入 10,输出是什么?
    查看练习答案与解析

    答案2 4 6 8 10解析:筛选偶数 2, 4, 6, 8, 10。

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
using namespace std;

class Sample {
private:
    int i;
    static int count;
public:
    Sample();
    void display();
};

Sample::Sample() {
    i = 0;
    count++;
}

void Sample::display() {
    cout << "i=" << i++ << ",count=" << count << endl;
}

int Sample::count = 0;

int main() {
    Sample a, b;
    a.display();
    b.display();
}
查看答案与解析

答案:

i=0,count=2
i=0,count=2

解析:

  1. Sample a, b:创建两个对象,每个调用构造函数:

    • a:i=0, count++ → count=1
    • b:i=0, count++ → count=2
    • count 是静态成员,所有对象共享
  2. a.display()

    • i=0(a 的 i),然后 i++ → a.i=1
    • count=2(静态成员,值为 2)
  3. b.display()

    • i=0(b 的 i),然后 i++ → b.i=1
    • count=2(静态成员,值仍为 2)

关键:静态成员 count 在所有对象间共享,值为构造的对象总数 2。


难度: ⭐⭐ 考点: #静态数据成员 #静态成员初始化

💡 学习锦囊

📖 相关公式与知识点:

  • 静态成员属于类,所有对象共享一份
  • 静态成员初始化:类型 类名::成员名 = 值;(类体外)
  • 静态成员在对象创建前就已存在

易错点

  • 静态成员 count 的值是 2(两个对象都构造完毕),不是 1
  • 每个对象有自己的 i,但共享同一个 count
🔄 举一反三
  1. 若在 b.display() 后再调用 a.display(),输出什么?
    查看练习答案与解析

    答案i=1,count=2解析:a.i 在第一次 display() 后变为 1,count 仍为 2。

四、编程题(每题 15 分,总计 30 分)

  1. 按下列要求编程,实现类的定义,并在主函数中测试这个类。

定义一个描述日期的类 Date,包括的数据成员有年(year)、月(month)和日(day),并实现如下功能函数: (1)日期对象初始化; (2)设置日期; (3)以 year/month/day 形式输出日期; (4)判断闰年。

查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

class Date {
private:
    int year, month, day;
public:
    Date(int y = 1900, int m = 1, int d = 1) {
        year = y;
        month = m;
        day = d;
    }

    void setDate(int y, int m, int d) {
        year = y;
        month = m;
        day = d;
    }

    void outputDate() {
        cout << year << "/" << month << "/" << day << endl;
    }

    bool isLeapYear() {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
};

int main() {
    Date d;
    d.outputDate();                    // 1900/1/1
    d.setDate(2015, 3, 5);
    d.outputDate();                    // 2015/3/5
    if (d.isLeapYear())
        cout << "a leap year." << endl;
    else
        cout << "not a leap year." << endl;
}

解析:

(1)日期对象初始化:使用带默认参数的构造函数 Date(int y = 1900, int m = 1, int d = 1),支持无参和有参初始化。

(2)设置日期setDate() 函数修改年、月、日。

(3)输出日期outputDate()year/month/day 格式输出。

(4)判断闰年isLeapYear() 函数,闰年规则:

  • 能被 4 整除且不能被 100 整除,能被 400 整除
  • 2015 年不是闰年,输出 not a leap year.

难度: ⭐⭐ 考点: #类定义 #构造函数 #成员函数 #闰年判断

💡 学习锦囊

📖 相关公式与知识点:

  • 闰年判断:(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
  • 带默认参数的构造函数可兼作默认构造函数
  • 类设计步骤:确定数据成员 → 设计构造函数 → 实现功能函数

易错点

  • 1900 年不是闰年(能被 100 整除但不能被 400 整除)
  • 2000 年是闰年(能被 400 整除)
🔄 举一反三
  1. 在 Date 类中增加一个函数 int daysOfMonth(),返回当前月份的天数(考虑闰年)。
    查看练习答案与解析

    答案

    cpp
    int daysOfMonth() {
        int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        if (month == 2 && isLeapYear()) return 29;
        return days[month];
    }

    解析:使用数组存储每月天数,2 月需特殊处理闰年。

  1. 根据下列 Vector 类定义,编程完成 Vector 类的具体实现:
cpp
class Vector {
    friend ostream &operator << (ostream &out, const Vector &v);
private:
    int *data;
    int size;
public:
    Vector();
    Vector(int a[], int n);
    Vector(const Vector &s);
    ~Vector();
    Vector &operator=(const Vector &v);
    int &operator[](int index);
};
查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

class Vector {
    friend ostream &operator << (ostream &out, const Vector &v);
private:
    int *data;
    int size;
public:
    Vector();
    Vector(int a[], int n);
    Vector(const Vector &s);
    ~Vector();
    Vector &operator=(const Vector &v);
    int &operator[](int index);
};

Vector::Vector() {
    data = NULL;
    size = 0;
}

Vector::Vector(int a[], int n) {
    size = n;
    data = new int[size];
    for (int i = 0; i < size; i++)
        data[i] = a[i];
}

Vector::Vector(const Vector &s) {
    size = s.size;
    data = new int[size];
    for (int i = 0; i < size; i++)
        data[i] = s.data[i];
}

Vector::~Vector() {
    if (data != NULL)
        delete[] data;
}

Vector &Vector::operator=(const Vector &v) {
    if (this == &v)
        return *this;
    if (size != v.size) {
        delete[] data;
        size = v.size;
        data = new int[size];
    }
    for (int i = 0; i < size; i++)
        data[i] = v.data[i];
    return *this;
}

int &Vector::operator[](int index) {
    return data[index];
}

ostream &operator << (ostream &out, const Vector &v) {
    for (int i = 0; i < v.size; i++)
        out << v.data[i] << ";";
    return out;
}

解析:

(1)默认构造函数 Vector():初始化 data 为 NULL,size 为 0。

(2)带参构造函数 Vector(int a[], int n):从数组初始化,动态分配内存并复制数据。

(3)拷贝构造函数 Vector(const Vector &s):深拷贝,重新分配内存并复制数据。必须深拷贝,否则两个对象的 data 指向同一内存,析构时重复释放。

(4)析构函数 ~Vector():释放动态分配的内存 delete[] data。注意检查 data 是否为 NULL。

(5)赋值运算符 operator=

  • 自赋值检查:if (this == &v) return *this;
  • 大小不同时释放旧内存,重新分配
  • 逐元素复制
  • 返回 *this 支持链式赋值

(6)下标运算符 operator[]:返回引用,支持读写操作。

(7)输出运算符 operator<<:友元函数,访问私有成员,输出向量元素。


难度: ⭐⭐⭐ 考点: #动态内存管理 #深拷贝 #运算符重载 #拷贝构造函数 #三法则

💡 学习锦囊

📖 相关公式与知识点:

  • 三法则(Rule of Three):如果需要自定义析构函数、拷贝构造函数或赋值运算符中的任何一个,通常需要自定义全部三个
  • 深拷贝 vs 浅拷贝:含动态内存的类必须深拷贝
  • 自赋值检查:if (this == &v) 防止自赋值导致错误
  • operator[] 返回引用才能支持 v[i] = 10 这样的写操作

思路分析

实现含动态内存的类的关键步骤:

  1. 构造函数:动态分配内存,复制数据
  2. 析构函数:释放动态内存(检查 NULL)
  3. 拷贝构造:深拷贝,重新分配内存
  4. 赋值运算:先释放旧内存,再深拷贝,注意自赋值检查
  5. 下标运算:返回引用支持读写
  6. 输出运算:友元函数访问私有成员

易错点

  • 赋值运算符必须检查自赋值,否则 delete[] data 后再访问 v.data 会出错
  • 拷贝构造函数必须深拷贝,浅拷贝会导致析构时重复释放内存
  • 赋值运算符中先释放旧内存再分配新内存
  • operator[] 返回引用才能支持写操作
  • 析构函数中 delete[] NULL 是安全的,但检查一下更清晰
🔄 举一反三
  1. 若在 Vector 类中增加一个 resize(int newSize) 函数,应如何实现?
    查看练习答案与解析

    答案

    cpp
    void resize(int newSize) {
        int *newData = new int[newSize];
        int copySize = (newSize < size) ? newSize : size;
        for (int i = 0; i < copySize; i++)
            newData[i] = data[i];
        delete[] data;
        data = newData;
        size = newSize;
    }

    解析:分配新内存,复制数据,释放旧内存,更新指针和大小。

  2. 若将拷贝构造函数改为浅拷贝(data = s.data; size = s.size;),程序会出现什么问题?
    查看练习答案与解析

    答案:两个对象的 data 指向同一内存,析构时同一内存被 delete[] 两次,导致未定义行为(double free)。 解析:这就是为什么含动态内存的类必须实现深拷贝。

你正在阅读的是会员专属文档,💕 限时特惠进行中
你尚未登录,目前新用户可获3天体验会员,去登录