Skip to content

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

一、判断题(对的打"√",错的打"×")(每题 1 分,共计 10 分)

  1. cout 的默认输出对象是键盘,cin 的默认输入对象是屏幕。
查看答案与解析

答案:×

解析: cout 的默认输出对象是屏幕(标准输出流),cin 的默认输入对象是键盘(标准输入流)。题目将两者颠倒了。

  • cout = character output,输出到屏幕
  • cin = character input,从键盘输入

难度: ⭐ 考点: #输入输出流 #cout #cin

💡 学习锦囊

📖 相关公式与知识点:

  • cout:标准输出流对象,默认输出到屏幕(stdout)
  • cin:标准输入流对象,默认从键盘读取(stdin)
  • cerr:标准错误流,无缓冲输出到屏幕
  • clog:标准日志流,有缓冲输出到屏幕

易错点

  • 容易混淆 cin/cout 的方向,记住:in = 输入(从键盘读),out = 输出(到屏幕写)
🔄 举一反三
  1. 下列关于 C++ 输入输出的描述,正确的是( )
    • A. cin 是输出流对象
    • B. cout 是输入流对象
    • C. cerr 是无缓冲的标准错误流
    • D. clog 是无缓冲的标准日志流
    查看练习答案与解析

    答案:C 解析:cin 是输入流对象(A 错),cout 是输出流对象(B 错),cerr 是无缓冲的标准错误流(C 对),clog 是有缓冲的标准日志流(D 错)。

  1. 使用 new 运算符创建数组时,可以为该数组指定初始值。
查看答案与解析

答案:√

解析: 在 C++11 及以后标准中,使用 new 运算符创建数组时,可以通过列表初始化指定初始值:

cpp
int *p = new int[5];        // 不指定初始值(传统写法)
int *p = new int[5]();      // 值初始化为0(C++11起)
int *p = new int[5]{1,2,3}; // 列表初始化(C++11起),未指定的元素初始化为0

注意:在 C++98/03 标准中,new 创建数组确实不能指定初始值,此题答案取决于考试所基于的 C++ 标准版本。


难度: ⭐⭐ 考点: #new运算符 #动态内存分配 #数组初始化

💡 学习锦囊

📖 相关公式与知识点:

  • new 类型[大小]:分配数组,不能指定初始值(传统C++)
  • new 类型(值):分配单个对象,可指定初始值
  • delete[] 指针:释放 new 分配的数组

易错点

  • new int[5]new int(5) 的区别:前者分配5个int的数组,后者分配1个int并初始化为5
  • 释放数组必须用 delete[],不能用 delete
🔄 举一反三
  1. 下列关于 new 运算符的描述,正确的是( )
    • A. new 创建的对象一定在栈上
    • B. new 失败时一定返回 NULL
    • C. new 可以自动计算所需内存大小
    • D. new 和 malloc 完全等价
    查看练习答案与解析

    答案:C 解析:new 在堆上分配内存(A 错);new 失败时默认抛出 bad_alloc 异常(B 错);new 根据类型自动计算所需内存大小(C 对);new 会调用构造函数,malloc 不会(D 错)。

  1. 内联函数的定义必须出现在第一次调用内联函数之前。
查看答案与解析

答案:√

解析: 内联函数的定义必须在第一次调用之前出现。编译器需要将内联函数的代码直接嵌入到调用处(内联展开),如果编译器在调用点还看不到函数定义,就无法进行内联展开。


难度: ⭐⭐ 考点: #内联函数 #inline #编译优化

💡 学习锦囊

📖 相关公式与知识点:

  • 内联函数:编译时将函数体嵌入调用处,减少函数调用开销
  • 定义位置:必须在第一次调用之前
  • 与宏定义的区别:内联函数有类型检查,宏定义没有

易错点

  • 内联函数只是对编译器的建议,编译器可以选择不内联
  • 在类体内定义的成员函数默认为内联函数
🔄 举一反三
  1. 下列关于内联函数的描述,错误的是( )
    • A. 内联函数的调用不需要栈操作
    • B. 内联函数可以包含循环语句
    • C. 内联函数的定义必须出现在调用之前
    • D. 内联函数一定会被编译器内联展开
    查看练习答案与解析

    答案:D 解析inline 只是对编译器的建议,编译器有权决定是否真正内联。

  1. 在用 class 定义一个类时,数据成员和成员函数默认的访问权限是 public。
查看答案与解析

答案:×

解析:class 关键字定义类时,默认访问权限是 private,不是 public。classstruct 的唯一区别就是默认访问权限不同:class 默认 private,struct 默认 public。


难度: ⭐ 考点: #class关键字 #访问权限 #class与struct的区别

💡 学习锦囊

📖 相关公式与知识点:

  • class 默认访问权限:private
  • struct 默认访问权限:public
  • 三种访问权限:public、protected、private

易错点

  • 面试/考试常考:class 默认 private,struct 默认 public
🔄 举一反三
  1. 下列关于 class 和 struct 的描述,正确的是( )
    • A. class 不能定义成员函数
    • B. struct 不能使用继承
    • C. class 默认继承方式是 private
    • D. struct 不能使用模板
    查看练习答案与解析

    答案:C 解析:class 和 struct 都可以定义成员函数、使用继承、使用模板。class 的默认继承方式是 private,struct 的默认继承方式是 public。

  1. 构造函数可以设置默认参数。
查看答案与解析

答案:√

解析: 构造函数可以设置默认参数,与普通函数的默认参数规则一致。需要注意:带全部默认参数的构造函数与无参构造函数不能同时存在,否则产生二义性。


难度: ⭐ 考点: #构造函数 #默认参数 #函数重载

💡 学习锦囊

📖 相关公式与知识点:

  • 构造函数默认参数:与普通函数默认参数规则相同
  • 默认参数必须从右向左连续设置
  • 注意避免与重载构造函数产生二义性

易错点

  • 带全部默认参数的构造函数与无参构造函数不能同时存在
🔄 举一反三
  1. 下列关于构造函数默认参数的描述,错误的是( )
    • A. 构造函数的所有参数都可以设置默认值
    • B. 默认参数必须从右向左连续设置
    • C. 带全部默认参数的构造函数可以和无参构造函数共存
    • D. 默认参数可以在声明中指定
    查看练习答案与解析

    答案:C 解析:带全部默认参数的构造函数与无参构造函数不能共存,否则产生二义性。

  1. 类的析构函数的作用是对象的初始化。
查看答案与解析

答案:×

解析: 析构函数的作用是对象销毁时的清理工作,不是初始化。初始化是构造函数的作用。

  • 构造函数:对象创建时调用,用于初始化
  • 析构函数:对象销毁时调用,用于清理(释放内存、关闭文件等)

难度: ⭐ 考点: #析构函数 #构造函数 #对象生命周期

💡 学习锦囊

📖 相关公式与知识点:

  • 构造函数:对象创建时自动调用,完成初始化
  • 析构函数:对象销毁时自动调用,完成清理工作
  • 析构函数名:~类名(),无参数,无返回值

易错点

  • 析构函数不能重载,构造函数可以重载
  • 析构函数的作用是"清理"而非"初始化"
🔄 举一反三
  1. 下列关于析构函数的描述,正确的是( )
    • A. 析构函数可以有参数
    • B. 析构函数可以重载
    • C. 析构函数在对象销毁时自动调用
    • D. 析构函数可以有返回值
    查看练习答案与解析

    答案:C 解析:析构函数无参数、不能重载、无返回值,在对象销毁时自动调用。

  1. 只有常成员函数才可以操作常对象。
查看答案与解析

答案:√

解析: 常对象(const 对象)只能调用常成员函数(const 成员函数)。因为常对象的成员数据不能被修改,而非常成员函数可能修改成员数据,编译器无法保证安全性。


难度: ⭐⭐ 考点: #常对象 #常成员函数 #const修饰符

💡 学习锦囊

📖 相关公式与知识点:

  • 常对象:const ClassName obj;,其成员数据不可修改
  • 常成员函数:void func() const;,承诺不修改成员数据
  • 常对象只能调用常成员函数
  • mutable 关键字修饰的成员变量可以在 const 函数中修改

易错点

  • 常对象可以调用构造函数和析构函数(它们不受 const 限制)
🔄 举一反三
  1. 下列关于 const 成员函数的描述,错误的是( )
    • A. const 成员函数不能修改非 mutable 成员
    • B. const 对象可以调用非 const 成员函数
    • C. const 成员函数和非 const 成员函数可以构成重载
    • D. const 成员函数的 this 指针类型是 const ClassName*
    查看练习答案与解析

    答案:B 解析:const 对象不能调用非 const 成员函数。

  1. 模板类与类模板的意义完全相同。
查看答案与解析

答案:×

解析:

  • 类模板:是一个模板,是用于生成类的蓝图/框架。如 template<typename T> class Stack {...}
  • 模板类:是类模板经过实例化后生成的具体的类。如 Stack<int>
cpp
template<typename T>    // 类模板
class Stack { T data[100]; };
Stack<int> intStack;    // 模板类(实例化后)

难度: ⭐⭐ 考点: #类模板 #模板类 #模板实例化

💡 学习锦囊

📖 相关公式与知识点:

  • 类模板:模板定义,含模板参数
  • 模板类:类模板实例化后的具体类

易错点

  • 类模板 → 模板(未实例化);模板类 → 具体类(已实例化)
🔄 举一反三
  1. 下列描述正确的是( )
    • A. 类模板和模板类是同一概念
    • B. 类模板实例化后得到模板类
    • C. 模板类实例化后得到类模板
    • D. 类模板不能被实例化
    查看练习答案与解析

    答案:B 解析:类模板是模板定义,实例化后得到具体的模板类。

  1. 若类 Y 是类 X 的私有派生类,类 Z 是类 Y 的公有派生类,则类 Z 不能访问类 X 的公有成员和保护成员。
查看答案与解析

答案:√

解析: 私有继承的传递效应:

  1. 类 Y 私有继承类 X → X 的 public/protected 成员在 Y 中变为 private
  2. 类 Z 公有继承类 Y → Y 的 private 成员在 Z 中不可访问

因此类 Z 无法访问类 X 的公有成员和保护成员。


难度: ⭐⭐⭐ 考点: #私有继承 #继承访问控制 #成员访问权限

💡 学习锦囊

📖 相关公式与知识点:

  • 公有继承:基类 public→public,protected→protected
  • 保护继承:基类 public/protected→protected
  • 私有继承:基类 public/protected→private

思路分析

分析继承链中的访问权限,需要逐层推导。

易错点

  • 私有继承使所有可继承成员变为 private,后续派生类无法再访问
🔄 举一反三
  1. 若类 Y 是类 X 的保护派生类,类 Z 是类 Y 的公有派生类,则类 Z 能否访问类 X 的公有成员?
    查看练习答案与解析

    答案:能 解析:Y 保护继承 X → X 的 public/protected 在 Y 中变为 protected → Z 公有继承 Y → Y 的 protected 在 Z 中仍为 protected → Z 可以访问。

  1. C++ 中设置虚基类的目的是实现运行时的多态。
查看答案与解析

答案:×

解析: 虚基类的目的是解决菱形继承中的二义性问题,不是实现运行时多态。

  • 虚基类:解决多继承中同一基类被间接多次继承导致的二义性
  • 虚函数:实现运行时多态

两者都用了 virtual 关键字,但用途完全不同。


难度: ⭐⭐ 考点: #虚基类 #虚函数 #菱形继承 #多态

💡 学习锦囊

📖 相关公式与知识点:

  • 虚基类:class Derived : virtual public Base,解决菱形继承二义性
  • 虚函数:virtual void func(),实现运行时多态
  • 菱形继承:A → B, A → C, B+C → D,D 中有两份 A 的成员
  • 虚基类使 D 中只保留一份 A 的成员

易错点

  • 虚基类 ≠ 虚函数,两者用途完全不同
🔄 举一反三
  1. 下列关于 virtual 关键字的描述,正确的是( )
    • A. 虚基类和虚函数的作用相同
    • B. 虚基类用于解决多继承中的二义性
    • C. 虚函数用于解决菱形继承问题
    • D. virtual 只能用于成员函数
    查看练习答案与解析

    答案:B 解析:虚基类解决多继承二义性(B 对),虚函数实现运行时多态(C 错),virtual 还可用于虚基类声明(D 错)。

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

  1. 下列关于面向对象概念的描述中,错误的是( )
    • A.面向对象方法比面向过程方法更加先进
    • B.面向对象方法中使用了一些面向过程方法中没有的概念
    • C.面向对象方法替代了结构化程序设计方法
    • D.面向对象程序设计方法要使用面向对象的程序设计语言
查看答案与解析

答案:C

解析: 面向对象方法并没有替代结构化程序设计方法,两者各有适用场景,在实际开发中常常结合使用。

  • A 正确:面向对象方法确实更先进
  • B 正确:引入了类、对象、继承、多态等概念
  • C 错误:面向对象没有替代结构化程序设计,两者互补共存
  • D 正确:需要使用支持面向对象特性的语言

难度: ⭐ 考点: #面向对象 #面向过程 #程序设计方法

💡 学习锦囊

📖 相关公式与知识点:

  • 面向对象三大特征:封装、继承、多态
  • 面向过程:以函数为中心
  • 面向对象:以对象为中心
  • 两者关系:互补而非替代

易错点

  • "替代"一词过于绝对
🔄 举一反三
  1. 下列关于程序设计方法的描述,正确的是( )
    • A. 面向对象方法完全优于面向过程方法
    • B. 面向对象方法中不包含任何面向过程成分
    • C. 面向对象和面向过程可以结合使用
    • D. C 语言不能编写任何面向对象的程序
    查看练习答案与解析

    答案:C 解析:两种方法各有适用场景(A 错),面向对象中类的方法本身就是面向过程的(B 错),两者可以结合使用(C 对)。

  1. 在函数的引用调用中,函数的实参和形参分别应是( )
    • A.变量值和变量
    • B.地址值和指针
    • C.变量名和引用
    • D.地址值和引用
查看答案与解析

答案:C

解析:

  • A:变量值和变量 → 值调用
  • B:地址值和指针 → 指针调用
  • C:变量名和引用 → 引用调用
  • D:地址值和引用 → 不是标准引用调用
cpp
void swap(int &a, int &b) { int t = a; a = b; b = t; }
swap(x, y);  // 实参是变量名,形参是引用

难度: ⭐ 考点: #引用调用 #参数传递 #引用

💡 学习锦囊

📖 相关公式与知识点:

  • 值调用:实参→形参复制,形参修改不影响实参
  • 引用调用:形参是实参的引用(别名),修改形参即修改实参
  • 指针调用:传递地址,通过解引用修改实参

思路分析

区分三种参数传递方式:看形参类型——普通变量是值调用,& 是引用调用,* 是指针调用。

🔄 举一反三
  1. 下列函数声明中,属于引用调用的是( )
    • A. void f(int x)
    • B. void f(int *x)
    • C. void f(int &x)
    • D. void f(const int x)
    查看练习答案与解析

    答案:C 解析:A、D 是值调用,B 是指针调用,C 是引用调用。

  1. void Set(A &a); 是类 A 中一个成员函数的说明,其中 A &a 的含义是( )
    • A.类 A 的对象引用 a 作该函数的参数
    • B.类 A 的对象 a 的地址值作函数的参数
    • C.表达式变量 A 与变量 a 按位与作函数参数
    • D.指向类 A 对象指针 a 作函数参数
查看答案与解析

答案:A

解析:A &a 表示 a 是类 A 对象的引用& 在声明中表示引用,不是按位与(C 错),不是取地址(B 错),不是指针(D 错)。


难度: ⭐ 考点: #引用 #成员函数参数 #&运算符

💡 学习锦囊

📖 相关公式与知识点:

  • A &a:引用声明,a 是 A 类型对象的别名
  • A *a:指针声明
  • & 三种含义:引用声明、取地址、按位与

易错点

  • & 在类型声明中和在表达式中的含义完全不同
🔄 举一反三
  1. 设有声明 void f(int &x),下列调用正确的是( )
    • A. f(10)
    • B. f(&10)
    • C. int a = 5; f(a)
    • D. int a = 5; f(&a)
    查看练习答案与解析

    答案:C 解析:引用参数必须绑定到左值(变量),不能绑定到常量(A、B 错)。f(a) 正确(C 对),f(&a) 传递地址类型不匹配(D 错)。

  1. 下列关于常成员的描述中,错误的是( )
    • A.常成员是用关键字 const 说明的
    • B.常成员有常数据成员和常成员函数两种
    • C.常数据成员的初始化是在类体内定义它时进行的
    • D.常数据成员的值是不可以改变的
查看答案与解析

答案:C

解析: 常数据成员不能在类体内定义时初始化,必须通过构造函数的初始化列表进行初始化。

cpp
class MyClass {
    const int x;      // 常数据成员
public:
    MyClass() : x(10) {}  // ✅ 必须通过初始化列表初始化
};

难度: ⭐⭐ 考点: #常数据成员 #const #初始化列表

💡 学习锦囊

📖 相关公式与知识点:

  • 常数据成员:const 类型 名;,必须在初始化列表中初始化
  • 常成员函数:void func() const;,不能修改非 mutable 成员
  • 引用成员也必须在初始化列表中初始化

易错点

  • 常数据成员和引用成员都必须在初始化列表中初始化
🔄 举一反三
  1. 下列代码中,正确初始化常数据成员的是( )
    • A. Test() { a = 10; }
    • B. Test() : a(10) {}
    • C. 类体内 const int a = 10;
    • D. Test(int x) { a = x; }
    查看练习答案与解析

    答案:B 解析:常数据成员必须在初始化列表中初始化(B 对)。A、D 在函数体中赋值不是初始化,C 传统 C++ 不合法。

  1. 下列关于 this 的描述中,错误的是( )
    • A.this 是一个由系统自动生成的指针
    • B.this 指针是指向对象的
    • C.this 指针在用对象引用成员函数时系统创建的
    • D.this 指针只能隐含使用,不能显式使用
查看答案与解析

答案:D

解析: this 指针可以显式使用。常见场景:this->member 显式访问成员,return *this; 返回当前对象(链式调用)。


难度: ⭐⭐ 考点: #this指针 #成员函数 #隐式与显式使用

💡 学习锦囊

📖 相关公式与知识点:

  • this 指针:隐含于每个非静态成员函数中,指向调用该函数的对象
  • 类型:ClassName *const this(常指针)
  • const 成员函数中:const ClassName *const this
  • 常见显式使用:区分同名参数与成员、链式调用

易错点

  • this 是常指针,不能改变指向
  • 静态成员函数没有 this 指针
🔄 举一反三
  1. 下列关于 this 指针的描述,正确的是( )
    • A. this 指针可以被修改指向其他对象
    • B. 静态成员函数中可以使用 this 指针
    • C. this 指针的类型取决于是否为 const 成员函数
    • D. 全局函数中可以使用 this 指针
    查看练习答案与解析

    答案:C 解析:this 是常指针(A 错),静态成员函数没有 this(B 错),const 成员函数中 this 为 const ClassName *const(C 对),全局函数没有 this(D 错)。

  1. 下列关于运算符重载的描述中,错误的是( )
    • A.运算符重载不改变优先级
    • B.运算符重载后,原来运算符操作不可再用
    • C.运算符重载不改变结合性
    • D.运算符重载函数的参数个数与重载方式有关
查看答案与解析

答案:B

解析: 运算符重载后,原来的运算符操作仍然可用。重载只是增加了运算符的新功能,并不覆盖原有功能。例如重载了 + 用于自定义类后,int + int 仍然正常工作。


难度: ⭐⭐ 考点: #运算符重载 #重载规则

💡 学习锦囊

📖 相关公式与知识点:

  • 运算符重载规则:不改变优先级、结合性、操作数个数
  • 不能创建新运算符
  • 不能重载的运算符:. .* :: ?: sizeof

易错点

  • = () [] -> 只能通过成员函数重载
🔄 举一反三
  1. 下列运算符中,不能被重载的是( )
    • A. +
    • B. =
    • C. ::
    • D. ()
    查看练习答案与解析

    答案:C 解析:: 不能被重载。不能重载的运算符:. .* :: ?: sizeof

  1. 下列关于派生类的描述中,错误的是( )
    • A.派生类至少有一个基类
    • B.一个派生类可以作另一个派生类的基类
    • C.派生类的构造函数中应包含直接基类的构造函数
    • D.派生类默认的继承方式是 public
查看答案与解析

答案:D

解析: 派生类使用 class 关键字定义时,默认的继承方式是 private,不是 public。

cpp
class Derived : Base {};        // 默认 private 继承
struct Derived : Base {};       // 默认 public 继承

难度: ⭐ 考点: #派生类 #继承方式 #默认继承

💡 学习锦囊

📖 相关公式与知识点:

  • class 默认继承方式:private
  • struct 默认继承方式:public

易错点

  • 与 class 默认成员访问权限为 private 一致,class 默认继承方式也是 private
🔄 举一反三
  1. 下列代码中,继承方式为 private 的是( )
    • A. class D : public B {};
    • B. class D : protected B {};
    • C. class D : B {};
    • D. struct D : B {};
    查看练习答案与解析

    答案:C 解析:class 未指定继承方式时默认为 private。

  1. 下列运算符中,不可以重载的是( )
    • A.&&
    • B.&
    • C.[ ]
    • D..*
查看答案与解析

答案:D

解析:.*(成员指针访问运算符)不能被重载。C++ 中不能重载的运算符:. .* :: ?: sizeof


难度: ⭐ 考点: #运算符重载 #不可重载运算符

💡 学习锦囊

📖 相关公式与知识点:

  • 不可重载运算符:. .* :: ?: sizeof
  • 只能成员函数重载:= () [] ->

思路分析

记忆口诀:"点星域问sizeof"

🔄 举一反三
  1. 下列运算符中,只能通过成员函数重载的是( )
    • A. +
    • B. <<
    • C. =
    • D. ==
    查看练习答案与解析

    答案:C 解析= () [] -> 只能通过成员函数重载。

  1. 下列的成员函数中,纯虚函数是( )
    • A.virtual void f1() = 0;
    • B.void f1() = 0;
    • C.virtual void f1() {} = 0;
    • D.含非法标记的声明
查看答案与解析

答案:A

解析: 纯虚函数的正确声明格式:virtual 返回类型 函数名(参数列表) = 0;

  • A 正确:标准纯虚函数声明
  • B 错误:缺少 virtual
  • C 错误:纯虚函数声明中不能同时包含函数体 {}= 0(纯虚函数可以在类外单独定义函数体,但声明中不能同时出现)
  • D 错误:非法声明

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

💡 学习锦囊

📖 相关公式与知识点:

  • 纯虚函数:virtual 返回类型 函数名(参数) = 0;
  • 含纯虚函数的类为抽象类,不能创建对象
  • 派生类必须实现所有纯虚函数才能成为具体类

易错点

  • = 0 必须出现在声明中
🔄 举一反三
  1. 下列关于纯虚函数的描述,正确的是( )
    • A. 纯虚函数可以有函数体
    • B. 含纯虚函数的类可以创建对象
    • C. 纯虚函数不需要 virtual 关键字
    • D. 纯虚函数必须在基类中实现
    查看练习答案与解析

    答案:A 解析:C++11 起纯虚函数可以有函数体(A 对),含纯虚函数的类是抽象类不能创建对象(B 错),必须有 virtual(C 错),不需要在基类中实现(D 错)。

  1. 下列关于抽象类的描述中,错误的是( )
  • A.抽象类中至少应该有一个纯虚函数
  • B.抽象类可以定义对象指针和对象引用
  • C.抽象类通常用作类族中最顶层的类
  • D.抽象类的派生类不再是抽象类
查看答案与解析

答案:D

解析: 抽象类的派生类不一定不再是抽象类。如果派生类没有实现基类中的所有纯虚函数,则派生类仍然是抽象类。


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

💡 学习锦囊

📖 相关公式与知识点:

  • 抽象类:含至少一个纯虚函数的类
  • 抽象类不能创建对象,但可以定义指针和引用
  • 派生类必须实现所有纯虚函数才能成为具体类

易错点

  • 只有实现了所有纯虚函数的派生类才不是抽象类
🔄 举一反三
  1. 下列关于抽象类的描述,正确的是( )
    • A. 抽象类不能有构造函数
    • B. 抽象类不能有数据成员
    • C. 抽象类的派生类如果实现了所有纯虚函数,则可以创建对象
    • D. 抽象类只能有一个纯虚函数
    查看练习答案与解析

    答案:C 解析:抽象类可以有构造函数(A 错)、数据成员(B 错),可以有多个纯虚函数(D 错),派生类实现所有纯虚函数后成为具体类可创建对象(C 对)。

三、程序填空题(每空 2 分,共计 12 分)

  1. 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
Base's cons.
Derived's cons.
Derived's des.
Base's des.
cpp
#include <iostream>
class Base {
public:
    Base() {cout << "Base's cons." << endl;}
    (1)______________________
};
class Derived : public Base {
public:
    Derived() {cout << "Derived's cons." << endl;}
    (2)______________________
};
int main() {
    Base *Ptr = (3)______________________
    delete ptr;
}
查看答案与解析

答案:

(1) virtual ~Base() {cout << "Base's des." << endl;}

(2) ~Derived() {cout << "Derived's des." << endl;}

(3) new Derived;

解析:

根据输出分析:

  1. Base's cons. → 创建 Derived 对象时先调用 Base 构造函数
  2. Derived's cons. → 再调用 Derived 构造函数
  3. Derived's des. → delete 时先调用 Derived 析构函数
  4. Base's des. → 再调用 Base 析构函数

(1) 定义 Base 的虚析构函数(必须加 virtual,否则通过基类指针 delete 派生类对象时不会调用 Derived 的析构函数),(2) 定义 Derived 析构函数,(3) 创建 Derived 对象。


难度: ⭐⭐ 考点: #构造与析构顺序 #虚析构函数 #基类指针

💡 学习锦囊

📖 相关公式与知识点:

  • 构造顺序:基类构造 → 派生类构造
  • 析构顺序:派生类析构 → 基类析构
  • 虚析构函数:virtual ~Base() {},确保通过基类指针 delete 派生类对象时正确调用派生类析构函数

易错点

  • 如果 Base 的析构函数不是虚函数,delete ptr 只会调用 Base 的析构函数,造成内存泄漏
🔄 举一反三
  1. 如果 Base 的析构函数不是 virtual,执行 Base *p = new Derived; delete p; 会怎样?
    查看练习答案与解析

    答案:只会调用 Base 的析构函数,不会调用 Derived 的析构函数,造成内存泄漏。 解析:没有虚析构函数时,delete p 根据指针类型静态绑定调用 Base 的析构函数。

  1. 实现下列求字符串长度的函数。
cpp
int strlen(char *str) {
    int len;
    len = 0;
    while (*str) {
        (3)______________________
        (4)______________________
    }
    return len;
}
查看答案与解析

答案:

(3) len++;

(4) str++;

解析:

  • while (*str):当 str 指向的字符不为 '\0' 时继续循环
  • (3) len++:每遍历一个字符,长度计数器加 1
  • (4) str++:指针后移,指向下一个字符

难度: ⭐ 考点: #指针运算 #字符串遍历 #strlen实现

💡 学习锦囊

📖 相关公式与知识点:

  • 字符串以 '\0' 结尾
  • *str 获取指针指向的字符
  • str++ 指针后移一个字符位置

思路分析

字符串长度 = 非结束符字符的个数。遍历字符串,计数加1,指针后移。

🔄 举一反三
  1. 实现字符串复制函数 void strcpy(char *dest, char *src)
    查看练习答案与解析

    答案

    cpp
    void strcpy(char *dest, char *src) {
        while (*src) { *dest = *src; dest++; src++; }
        *dest = '\0';
    }

    解析:遍历源字符串逐字符复制,最后添加结束符。更简洁写法:while (*dest++ = *src++);

  1. 下面程序通过把类 Distance 声明为类 Point 的友元类来实现计算两点之间的距离。请在下面程序的横线处填上适当字句,以使程序完整。
cpp
#include <iostream>
#include <cmath>
using namespace std;
class Point {
    double X, Y;
public:
    Point(double x, double y) {X = x; Y = y;}
    (5)______________________
};
class Distance {
public:
    double Dis(Point& p1, Point& p2);
};
double Distance::Dis(Point& p1, Point& p2) {
    double t;
    (6)______________________
    return t;
}
int main() {
    Point p(10, 10), q(20, 20);
    Distance d;
    cout << d.Dis(p, q) << endl;
}
查看答案与解析

答案:

(5) friend class Distance;

(6) t = sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));

解析:

(5) 声明 Distance 为 Point 的友元类,使 Distance 可以访问 Point 的私有成员 X 和 Y。

(6) 使用两点间距离公式:$d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}$

代入 p(10,10) 和 q(20,20):$d = \sqrt{(10-20)^2 + (10-20)^2} = \sqrt{200} \approx 14.14$


难度: ⭐⭐ 考点: #友元类 #两点间距离 #私有成员访问

💡 学习锦囊

📖 相关公式与知识点:

  • 友元类声明:friend class ClassName;
  • 友元类的所有成员函数都可以访问该类的私有成员
  • 两点间距离公式:$d = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2}$

易错点

  • 友元关系是单向的
  • 友元关系不能继承
  • 友元破坏封装性,应谨慎使用
🔄 举一反三
  1. 如果不用友元类,还有什么方式可以让 Distance 访问 Point 的私有成员?
    查看练习答案与解析

    答案:提供公有的 getter 函数。 解析

    cpp
    class Point {
        double X, Y;
    public:
        double getX() const { return X; }
        double getY() const { return Y; }
    };

    然后使用 p1.getX() 代替 p1.X,保持封装性。

四、阅读程序题(共计 30 分)

  1. 阅读该程序,给出程序的输出结果。(6 分)
cpp
#include <iostream>
class Test {
    static int n;
public:
    Test() {n += 2;}
    ~Test() {n -= 3;}
    static int GetNum() {return n;}
};
int Test::n = 1;
int main() {
    Test t, *p;
    p = &t;
    cout << "n=" << p->GetNum() << endl;
    p = new Test;
    delete p;
    cout << "n=" << Test::GetNum() << endl;
}
查看答案与解析

答案:

n=3
n=2

解析:

  1. int Test::n = 1; → n = 1
  2. Test t; → 构造函数 n += 2 → n = 3
  3. p = &t; → p 指向 t(不调用构造函数)
  4. p->GetNum() → 输出 n=3
  5. p = new Test; → 构造函数 n += 2 → n = 5
  6. delete p; → 析构函数 n -= 3 → n = 2
  7. Test::GetNum() → 输出 n=2

难度: ⭐⭐ 考点: #静态成员变量 #构造与析构 #对象生命周期

💡 学习锦囊

📖 相关公式与知识点:

  • 静态成员变量:属于类,所有对象共享一份
  • 必须在类外初始化:类型 类名::变量名 = 初始值;

思路分析

追踪静态变量:每次构造 +2,每次析构 -3,按执行顺序逐步计算。

易错点

  • Test t, *p; 只创建了一个对象 t,指针 p 不调用构造函数
  • p = &t; 不创建新对象
🔄 举一反三
  1. 如果将 delete p; 这行去掉,程序输出什么?
    查看练习答案与解析

    答案

    n=3
    n=5

    解析:去掉 delete p 后不调用析构函数,n 不减 3,为 5。但会导致内存泄漏。

  1. 阅读该程序,给出程序的输出结果。(6 分)
cpp
#include <iostream>
class A {
    int num;
public:
    A() {num = 0; cout << "A default constructor" << endl;}
    A(int n) {num = n; cout << "A constructor,num=" << num << endl;}
    ~A() {cout << "A destructor,num=" << num << endl;}
};
int main() {
    A a, *p;
    p = new A(2);
    a = *p;
    delete p;
    cout << "Exiting main" << endl;
}
查看答案与解析

答案:

A default constructor
A constructor,num=2
A destructor,num=2
Exiting main
A destructor,num=2

解析:

  1. A a; → 默认构造,num=0,输出 A default constructor
  2. p = new A(2); → 带参构造,num=2,输出 A constructor,num=2
  3. a = *p; → 赋值操作(非拷贝构造),a.num 变为 2,无输出
  4. delete p; → 析构,num=2,输出 A destructor,num=2
  5. 输出 Exiting main
  6. a 离开作用域 → 析构,a.num=2,输出 A destructor,num=2

难度: ⭐⭐⭐ 考点: #构造函数 #析构函数 #赋值与拷贝 #对象生命周期

💡 学习锦囊

📖 相关公式与知识点:

  • 赋值操作 vs 拷贝构造:a = *p 是赋值,A a(*p) 是拷贝构造
  • 赋值不调用构造函数,只执行逐成员复制

易错点

  • a = *p 是赋值不是拷贝构造,不产生构造函数输出
  • 赋值后 a.num 变为 2,析构时输出 num=2
🔄 举一反三
  1. 如果将 a = *p; 改为 A a2(*p);,程序输出有什么变化?
    查看练习答案与解析

    答案:多一个 a2 的析构输出:

    A default constructor
    A constructor,num=2
    A destructor,num=2
    Exiting main
    A destructor,num=2
    A destructor,num=2

    解析:a2 通过拷贝构造创建(num=2),在 main 结束后析构。

  1. 阅读该程序,给出程序的输出结果。(6 分)
cpp
#include <iostream>
class S {
public:
    S() { PC = 0; }
    S(S &s) { PC = s.PC; for(int i = 0; i < PC; i++) elements[i] = s.elements[i]; }
    void Empty() { PC = 0; }
    int IsEmpty() { return PC == 0; }
    int IsMemberOf(int n);
    int Add(int n);
    void Print();
private:
    int elements[100], PC;
};
int S::IsMemberOf(int n) {
    for(int i = 0; i < PC; i++)
        if(elements[i] == n) return 1;
    return 0;
}
int S::Add(int n) {
    if(IsMemberOf(n)) return 1;
    else if(PC == 100) return 0;
    else { elements[PC++] = n; return 1; }
}
void S::Print() {
    cout << '{';
    for(int i = 0; i < PC - 1; i++)
        cout << elements[i] << ',';
    if(PC > 0) cout << elements[PC - 1];
    cout << '}' << endl;
}
int main() {
    S a;
    cout << a.IsEmpty() << endl;
    a.Print();
    S b;
    for(int i = 1; i <= 5; i++) b.Add(i);
    b.Print();
    cout << b.IsMemberOf(3) << endl;
    cout << b.IsEmpty() << endl;
    for(i = 6; i <= 10; i++) b.Add(i);
    S c(b);
    c.Print();
}
查看答案与解析

答案:

1
{}
{1,2,3,4,5}
1
0
{1,2,3,4,5,6,7,8,9,10}

解析:

  1. S a; → PC=0
  2. a.IsEmpty() → PC==0 返回 1,输出 1
  3. a.Print() → PC=0,输出 {}
  4. S b; → PC=0
  5. for(i=1; i<=5; i++) b.Add(i) → 添加 1,2,3,4,5,PC=5
  6. b.Print() → 输出 {1,2,3,4,5}
  7. b.IsMemberOf(3) → 3 在集合中,返回 1,输出 1
  8. b.IsEmpty() → PC=5≠0,返回 0,输出 0
  9. for(i=6; i<=10; i++) b.Add(i) → PC=10
  10. S c(b) → 拷贝构造,复制 b 的所有元素
  11. c.Print() → 输出 {1,2,3,4,5,6,7,8,9,10}

难度: ⭐⭐⭐ 考点: #集合类 #拷贝构造函数 #成员函数

💡 学习锦囊

📖 相关公式与知识点:

  • 集合类 S:用数组存储元素,PC 记录元素个数
  • Add:先检查是否已存在,不存在则添加(保证元素不重复)
  • Print:按 {e1,e2,...,en} 格式输出

易错点

  • Add 函数中元素已存在时返回 1 但不重复添加
  • Print 函数最后一个元素后不加逗号
  • PC=0 时 Print 输出 {}
🔄 举一反三
  1. 如果在 S c(b); 之后执行 c.Add(3); c.Print();,输出什么?
    查看练习答案与解析

    答案{1,2,3,4,5,6,7,8,9,10}解析:3 已存在,Add 不重复添加,集合内容不变。

  1. 阅读该程序,给出程序的输出结果。(6 分)
cpp
#include <iostream>
class Format {
public:
    virtual void header() {cout << "This is a head" << endl;}
    virtual void footer() {cout << "This is a footer" << endl;}
    virtual void body() {cout << "This is a body" << endl;}
    void display() {header(); body(); footer();}
};
class MyFormat : public Format {
public:
    void header() {cout << "This is my header" << endl;}
    void footer() {cout << "This is my footer" << endl;}
};
int main() {
    Format *p;
    p = new Format;
    p->display();
    delete p;
    p = new MyFormat;
    p->display();
    delete p;
}
查看答案与解析

答案:

This is a head
This is a body
This is a footer
This is my header
This is a body
This is my footer

解析:

  1. p = new Format; p->display();

    • p 指向 Format 对象,调用 Format 的虚函数
    • 输出:This is a head / This is a body / This is a footer
  2. p = new MyFormat; p->display();

    • p 指向 MyFormat 对象,虚函数动态绑定
    • header():MyFormat 重写了 → This is my header
    • body():MyFormat 未重写 → This is a body(基类版本)
    • footer():MyFormat 重写了 → This is my footer

难度: ⭐⭐ 考点: #虚函数 #运行时多态 #动态绑定

💡 学习锦囊

📖 相关公式与知识点:

  • 虚函数:virtual void func(),支持运行时多态
  • 动态绑定:通过基类指针/引用调用虚函数时,根据实际对象类型选择函数版本
  • 未重写的虚函数:派生类使用基类的版本

思路分析

分析虚函数调用的关键:看指针实际指向的对象类型,而非指针的声明类型。

易错点

  • display() 不是虚函数,但内部调用的虚函数仍会动态绑定
  • MyFormat 只重写了 header() 和 footer(),body() 使用基类版本
🔄 举一反三
  1. 如果将 Format 类中的 virtual void header() 的 virtual 去掉,p = new MyFormat; p->display(); 的输出会怎样?
    查看练习答案与解析

    答案

    This is a head
    This is a body
    This is my footer

    解析:header() 不再是虚函数,p->header() 根据指针类型(Format*)静态绑定调用 Format::header()。footer() 仍是虚函数,正常动态绑定。

  1. 阅读该程序,给出程序的输出结果。(6 分)
cpp
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
void DisplayVector(vector<T>& vecInput) {
    for(typename vector<T>::iterator iElement = vecInput.begin();
        iElement != vecInput.end(); ++iElement)
        cout << *iElement << ' ';
    cout << endl;
}
int main() {
    vector<int> vecIntegers;
    vecIntegers.push_back(50);
    vecIntegers.push_back(1);
    vecIntegers.insert(vecIntegers.begin(), 987);
    vecIntegers.insert(vecIntegers.begin(), 1001);
    cout << "Vector contains " << vecIntegers.size() << " elements: ";
    DisplayVector(vecIntegers);
    vecIntegers.pop_back();
    cout << "After a call to pop_back()" << endl;
    cout << "Vector contains " << vecIntegers.size() << " elements: ";
    DisplayVector(vecIntegers);
    return 0;
}
查看答案与解析

答案:

Vector contains 4 elements: 1001 987 50 1
After a call to pop_back()
Vector contains 3 elements: 1001 987 50

解析:

  1. vector<int> vecIntegers; → 空向量 {}
  2. push_back(50)
  3. push_back(1)
  4. insert(begin(), 987) → 在开头插入 987 →
  5. insert(begin(), 1001) → 在开头插入 1001 →
  6. 输出:Vector contains 4 elements: 1001 987 50 1
  7. pop_back() → 删除末尾元素 1 →
  8. 输出:After a call to pop_back()
  9. 输出:Vector contains 3 elements: 1001 987 50

难度: ⭐⭐ 考点: #STL容器 #vector #迭代器 #模板函数

💡 学习锦囊

📖 相关公式与知识点:

  • push_back(val):在末尾添加元素
  • pop_back():删除末尾元素
  • insert(pos, val):在指定位置插入元素
  • begin():返回指向首元素的迭代器
  • size():返回元素个数

思路分析

注意 insert(begin(), val) 是在开头插入,每次插入都会使已有元素后移。push_back 在末尾添加。

易错点

  • insert 的第一个参数是迭代器位置,不是索引
  • pop_back() 删除的是最后一个元素
🔄 举一反三
  1. 如果在上述程序中,在 pop_back() 之后执行 vecIntegers.insert(vecIntegers.begin() + 1, 500);,vector 的内容是什么?
    查看练习答案与解析

    答案:{1001, 500, 987, 50} 解析:pop_back 后 vector 为 {1001, 987, 50},begin() + 1 指向第二个位置(987 前),插入 500 后为 {1001, 500, 987, 50}。

五、编程题(共计 28 分)

  1. 按下列要求编程(12 分)

定义一个描述矩形的类 Rectangle,包括的数据成员有宽(width)和长(length),并实现如下功能函数:

(1)矩形对象初始化; (2)计算矩形周长; (3)计算矩形面积; (4)改变矩形大小; (5)重载插入运算符(<<),实现矩形的长、宽、周长和体积的输出。

查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

class Rectangle {
private:
    double width;
    double length;

public:
    Rectangle(double w = 0, double l = 0) : width(w), length(l) {}

    double Perimeter() const {
        return 2 * (width + length);
    }

    double Area() const {
        return width * length;
    }

    void Resize(double w, double l) {
        width = w;
        length = l;
    }

    friend ostream& operator<<(ostream& os, const Rectangle& r);
};

ostream& operator<<(ostream& os, const Rectangle& r) {
    os << "Length: " << r.length << endl;
    os << "Width: " << r.width << endl;
    os << "Perimeter: " << r.Perimeter() << endl;
    os << "Area: " << r.Area() << endl;
    return os;
}

int main() {
    Rectangle r(3, 4);
    cout << r << endl;
    r.Resize(5, 6);
    cout << r << endl;
    return 0;
}

解析:

  1. 构造函数Rectangle(double w = 0, double l = 0) 实现初始化,带默认参数
  2. 周长Perimeter() 返回 $2 \times (width + length)$
  3. 面积Area() 返回 $width \times length$
  4. 改变大小Resize(double w, double l) 修改 width 和 length
  5. 重载 <<:使用友元函数实现,输出长、宽、周长和面积(题目说"体积",但矩形是2D图形,应为面积)

难度: ⭐⭐ 考点: #类的定义 #运算符重载 #友元函数 #构造函数

💡 学习锦囊

📖 相关公式与知识点:

  • 矩形周长:$P = 2 \times (w + l)$
  • 矩形面积:$A = w \times l$
  • 重载 << 运算符:必须使用友元函数,返回 ostream& 支持链式调用
  • 构造函数初始化列表:Rectangle(double w, double l) : width(w), length(l) {}

思路分析

重载 << 的标准模式:friend ostream& operator<<(ostream& os, const ClassName& obj),返回 os 引用以支持链式调用。

易错点

  • << 重载必须返回 ostream& 引用
  • << 重载不能用成员函数(左操作数是 ostream)
  • 题目说"体积",但矩形是二维图形,应为"面积"
🔄 举一反三
  1. 如果要给 Rectangle 类添加重载 >> 运算符(从键盘输入长和宽),如何实现?
    查看练习答案与解析

    答案

    cpp
    friend istream& operator>>(istream& is, Rectangle& r) {
        is >> r.length >> r.width;
        return is;
    }

    解析>> 重载也使用友元函数,第二个参数不能是 const(需要修改),返回 istream& 支持链式输入。

  1. 设计一个 double 类型的数组类模板(CDblArray),要求 CDblArray 可以进行如下操作:(16 分)

(1)数组对象中存储元素的个数可以在定义的时候任意设定; (2)可以重新设置数组对象中存储元素的个数(Resize); (3)可以通过下标运算符返回数组元素,并通过异常机制,处理下标越界异常; (4)可以利用已知数组对象对另一个数组对象赋值和初始化; (5)可以返回当前数组对象中存储元素的个数(Size)。

查看答案与解析

答案:

cpp
#include <iostream>
#include <stdexcept>
using namespace std;

template<typename T>
class CDblArray {
private:
    T *data;
    int size;

public:
    CDblArray(int s = 0) : size(s) {
        if(size > 0)
            data = new T[size];
        else
            data = nullptr;
    }

    CDblArray(const CDblArray& other) : size(other.size) {
        if(size > 0) {
            data = new T[size];
            for(int i = 0; i < size; i++)
                data[i] = other.data[i];
        } else {
            data = nullptr;
        }
    }

    ~CDblArray() {
        delete[] data;
    }

    CDblArray& operator=(const CDblArray& other) {
        if(this != &other) {
            delete[] data;
            size = other.size;
            if(size > 0) {
                data = new T[size];
                for(int i = 0; i < size; i++)
                    data[i] = other.data[i];
            } else {
                data = nullptr;
            }
        }
        return *this;
    }

    void Resize(int newSize) {
        delete[] data;
        size = newSize;
        if(size > 0) {
            data = new T[size];
            for(int i = 0; i < size; i++)
                data[i] = T();
        } else {
            data = nullptr;
        }
    }

    T& operator[](int index) {
        if(index < 0 || index >= size)
            throw out_of_range("Index out of range!");
        return data[index];
    }

    const T& operator[](int index) const {
        if(index < 0 || index >= size)
            throw out_of_range("Index out of range!");
        return data[index];
    }

    int Size() const {
        return size;
    }
};

int main() {
    CDblArray<double> arr(5);
    for(int i = 0; i < arr.Size(); i++)
        arr[i] = i * 1.5;

    CDblArray<double> arr2 = arr;   // 拷贝构造
    CDblArray<double> arr3;
    arr3 = arr;                      // 赋值

    arr2.Resize(10);
    cout << "arr2 size: " << arr2.Size() << endl;

    try {
        cout << arr[10] << endl;     // 越界访问
    } catch(const out_of_range& e) {
        cout << "Exception: " << e.what() << endl;
    }

    return 0;
}

解析:

  1. (1) 任意设定元素个数:构造函数 CDblArray(int s = 0) 接受大小参数
  2. (2) ResizeResize(int newSize) 重新分配内存
  3. (3) 下标运算符 + 异常operator[] 检查越界,抛出 out_of_range 异常
  4. (4) 赋值和初始化:拷贝构造函数和赋值运算符重载
  5. (5) SizeSize() 返回当前元素个数

难度: ⭐⭐⭐ 考点: #类模板 #动态内存管理 #运算符重载 #异常处理 #拷贝构造 #赋值运算符

💡 学习锦囊

📖 相关公式与知识点:

  • 类模板:template<typename T> class ClassName
  • 动态内存管理:new/delete 配对使用
  • 拷贝构造函数:深拷贝,避免浅拷贝问题
  • 赋值运算符重载:注意自赋值检查和内存释放
  • 异常处理:throw out_of_range("msg") + try/catch
  • 三法则(Rule of Three):如果定义了析构函数、拷贝构造函数、赋值运算符之一,则应定义全部三个

思路分析

设计数组类模板的关键步骤:

  1. 数据成员:指针 + 大小
  2. 构造/析构:分配/释放内存
  3. 拷贝构造/赋值:深拷贝
  4. 下标运算符:边界检查 + 异常
  5. Resize:重新分配内存

易错点

  • 赋值运算符重载必须检查自赋值(if(this != &other)
  • 赋值前必须释放原有内存(delete[] data
  • 拷贝构造和赋值必须实现深拷贝
  • Resize 会丢失原有数据(如需保留,应先复制再释放)
🔄 举一反三
  1. 如果要实现 Resize 时保留原有数据,应该如何修改?
    查看练习答案与解析

    答案

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

    解析:先分配新内存,复制原有数据(取新旧大小的较小值),新空间初始化为默认值,再释放旧内存,更新指针和大小。

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