Appearance
《C++面向对象程序设计》期末试卷B (精选02)
一、判断题(对的打"√",错的打"×")(每题 1 分,共计 10 分)
- 编译 C++ 源程序时,出现了警告错(Warning)也可以生成可执行文件。
查看答案与解析
答案:√
解析: C++ 编译器的诊断信息分两类:
- Error(错误):严重违反语法规则,编译器无法生成目标代码
- Warning(警告):潜在问题但不影响代码生成,编译器仍可生成可执行文件
常见警告:未使用的变量、隐式类型转换、有符号/无符号比较等。
难度: ⭐ 考点: #编译过程 #Warning与Error
💡 学习锦囊
📖 相关公式与知识点:
- Error → 编译失败,无目标文件
- Warning → 编译成功,可生成可执行文件
- 编译选项:
-Wall开启警告,-Werror将警告视为错误
易错点
- 误以为 Warning 也会阻止编译
- 实际项目应尽量消除所有警告
🔄 举一反三
- 下列说法正确的是( )
- A. 有 Warning 就不能生成 .obj 文件
- B. 有 Error 就不能生成可执行文件
- C. Warning 和 Error 都不影响编译
- D. Warning 比 Error 更严重
查看练习答案与解析
答案:B 解析:Error 是致命错误,阻止可执行文件生成。Warning 不影响编译和生成。
- 标识符规定大小写字母没有区别。
查看答案与解析
答案:×
解析: C++ 是严格区分大小写的语言。value、Value、VALUE 是三个不同的标识符。关键字全部为小写:int、class、return。
难度: ⭐ 考点: #标识符 #大小写敏感
💡 学习锦囊
📖 相关公式与知识点:
- 标识符规则:字母、数字、下划线组成,不能以数字开头
- C++ 区分大小写:
abc≠Abc≠ABC
易错点
- 大小写混淆是初学者最常见的编译错误来源
🔄 举一反三
- 下列标识符中,与
myFunc表示同一变量的是( )- A.
MyFunc - B.
myfunc - C.
MYFUNC - D. 以上都不是
查看练习答案与解析
答案:D 解析:C++ 严格区分大小写,四个标识符各不相同。
- A.
- 数组中所有元素的类型都是相同的。
查看答案与解析
答案:√
解析: 数组是同一类型数据元素的有序集合。定义数组时必须指定元素类型,所有元素都必须是该类型。需要存储不同类型数据应使用结构体或类。
难度: ⭐ 考点: #数组 #数据类型一致性
💡 学习锦囊
📖 相关公式与知识点:
- 数组:
类型名 数组名[长度],元素类型相同、内存连续、下标从 0 开始 - 对比结构体:成员可以是不同类型
思路分析
数组的核心特征是"同类型元素的有序集合",这是数组与结构体的本质区别。
🔄 举一反三
- 若需存储学生的姓名、年龄和成绩,应使用( )
- A. 数组
- B. 结构体
- C. 指针
- D. 引用
查看练习答案与解析
答案:B 解析:不同类型数据应使用结构体。数组要求元素类型相同。
- 创建引用时要用一个同类型的变量进行初始化。
查看答案与解析
答案:√
解析: 引用是已存在变量的别名,创建时必须满足:①必须初始化 ②类型必须匹配。
int a = 10;
int &ref = a; // ✅ 同类型初始化
double &ref2 = a; // ❌ 类型不匹配
int &ref3; // ❌ 必须初始化2
3
4
例外:const 引用可绑定到不同类型(创建临时变量)。
难度: ⭐ 考点: #引用 #引用初始化 #类型匹配
💡 学习锦囊
📖 相关公式与知识点:
- 引用声明:
类型 &引用名 = 变量名 - 引用特性:必须初始化、不可重绑定、与原变量共享内存
易错点
- 非 const 引用不能绑定到右值
- 引用不能像指针那样先声明后赋值
🔄 举一反三
- 下列引用声明中,正确的是( )
- A.
int &ref; - B.
int &ref = 10; - C.
int a; int &ref = a; - D.
int a; double &ref = a;
查看练习答案与解析
答案:C 解析:A 缺少初始化,B 绑定到右值,C 同类型初始化正确,D 类型不匹配。
- A.
- 静态数据成员必须在构造函数的成员初始化列表中进行初始化。
查看答案与解析
答案:×
解析: 静态数据成员不能在构造函数初始化列表中初始化。静态成员属于类而非对象,必须在类体外单独初始化。
class MyClass {
static int count; // 类内声明
};
int MyClass::count = 0; // 类体外初始化2
3
4
特例:static const int 可在类内初始化。
难度: ⭐⭐ 考点: #静态数据成员 #初始化方式
💡 学习锦囊
📖 相关公式与知识点:
- 静态成员初始化:
类型 类名::静态成员名 = 值;(类体外) - 特例:
static const int可类内初始化 - 静态成员属于类,所有对象共享一份
易错点
- 静态成员不能出现在构造函数初始化列表中
- 静态成员不计入 sizeof 对象大小
🔄 举一反三
- 下列静态成员初始化方式,正确的是( )
- A. 在构造函数体内赋值
- B. 在构造函数初始化列表中初始化
- C. 在类体外用
类名::成员名初始化 - D. 不需要初始化
查看练习答案与解析
答案:C 解析:静态成员必须在类体外用
类型 类名::成员名 = 值初始化。
- 常数据成员在常成员函数中的值是不允许改变的,而在非常成员函数中是允许改变的。
查看答案与解析
答案:×
解析: 常数据成员无论在常成员函数还是非常成员函数中,其值都不允许改变。const 修饰的数据成员一旦初始化就不可修改,这是 const 的基本语义保证。
class MyClass {
const int value;
public:
MyClass(int v) : value(v) {}
void normalFunc() { /* value = 10; ❌ */ }
void constFunc() const { /* value = 10; ❌ */ }
};2
3
4
5
6
7
难度: ⭐⭐ 考点: #常数据成员 #const语义
💡 学习锦囊
📖 相关公式与知识点:
- 常数据成员:
const 类型 成员名,必须在初始化列表中初始化 - 常数据成员的"常"是绝对的,任何函数都不能修改
- 常成员函数不能修改任何非 mutable 数据成员
易错点
- 常数据成员的不可修改性与函数是否为 const 无关
- 常成员函数不能修改非 mutable 数据成员(不仅是常数据成员)
🔄 举一反三
- 下列说法正确的是( )
- A. 常成员函数只能访问常数据成员
- B. 非常成员函数可以修改常数据成员
- C. 常数据成员在任何成员函数中都不可修改
- D. 常成员函数可以修改非常数据成员
查看练习答案与解析
答案:C 解析:常数据成员在任何函数中都不可修改(C 对)。常成员函数可访问所有成员(A 错),不能修改非 mutable 数据成员(D 错)。
- 成员函数与一般函数一样可以重载和设置参数的默认值,但不能设置为内联函数。
查看答案与解析
答案:×
解析: 成员函数可以设置为内联函数。方式有两种:
- 隐式内联:类体内定义的成员函数自动成为内联函数
- 显式内联:使用
inline关键字声明
class MyClass {
public:
void func1() { /* ... */ } // 隐式内联
inline void func2(); // 显式声明
};
inline void MyClass::func2() { /* ... */ }2
3
4
5
6
难度: ⭐ 考点: #内联函数 #成员函数 #inline
💡 学习锦囊
📖 相关公式与知识点:
- 内联函数:编译时在调用处展开,减少函数调用开销
- 类体内定义 → 自动内联
inline只是建议,编译器可忽略
易错点
- 成员函数完全可以是内联函数
- 递归函数、含复杂逻辑的函数不适合内联
🔄 举一反三
- 下列关于内联函数的说法,错误的是( )
- A. 类体内定义的成员函数默认是内联的
- B. 内联函数一定会被编译器展开
- C.
inline关键字可以声明内联函数 - D. 内联函数适用于短小频繁调用的函数
查看练习答案与解析
答案:B 解析:
inline只是建议,编译器可能不展开(如函数体过大、含递归等)。
- 定义对象指针时也要调用构造函数。
查看答案与解析
答案:×
解析: 定义对象指针时不会调用构造函数。指针本身只是一个存储地址的变量,定义指针并不创建对象。
MyClass *p; // 不调用构造函数
MyClass obj; // 调用构造函数
MyClass *p2 = new MyClass; // new 创建对象时调用构造函数2
3
难度: ⭐⭐ 考点: #对象指针 #构造函数调用时机
💡 学习锦囊
📖 相关公式与知识点:
MyClass *p→ 不调用构造函数MyClass obj→ 调用构造函数new MyClass→ 调用构造函数
易错点
- 定义指针 ≠ 创建对象
MyClass *p = new MyClass中是new MyClass调用构造函数
🔄 举一反三
- 下列哪种情况不会调用构造函数?( )
- A.
MyClass obj; - B.
MyClass *p = new MyClass; - C.
MyClass *p; - D.
MyClass arr[3];
查看练习答案与解析
答案:C 解析:C 仅定义指针不调用构造函数。A、B、D 都会创建对象调用构造函数。
- A.
- 派生类只继承基类中的公有成员和保护成员,而不继承私有成员。
查看答案与解析
答案:×
解析: 派生类继承基类的所有成员(包括公有、保护和私有成员),只是私有成员在派生类中不可直接访问。
class Derived : public Base {
void func() {
pub = 1; // ✅ 可访问
prot = 2; // ✅ 可访问
// priv = 3; // ❌ 不可直接访问,但确实被继承了
}
};2
3
4
5
6
7
私有成员虽被继承(存在于派生类对象中,占内存),但只能通过基类的公有/保护成员函数间接访问。
难度: ⭐⭐ 考点: #继承 #访问控制 #私有成员继承
💡 学习锦囊
📖 相关公式与知识点:
| 基类成员 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可直接访问 | 不可直接访问 | 不可直接访问 |
- 关键区别:继承 ≠ 可访问
易错点
- "不继承私有成员"是常见误解,实际是"继承但不可直接访问"
- 私有成员仍存在于派生类对象中,占内存
🔄 举一反三
- 关于继承,下列说法正确的是( )
- A. 派生类不继承基类私有成员,不占内存
- B. 派生类继承所有成员,但私有成员不可直接访问
- C. 保护继承时,基类公有成员仍为公有
- D. 私有继承时,基类保护成员变为公有
查看练习答案与解析
答案:B 解析:派生类继承所有成员包括私有成员(A 错、B 对)。保护继承时公有成员变为保护(C 错)。私有继承时保护成员变为私有(D 错)。
- 静态联编和动态联编都是在编译时进行的,二者的区别仅是前者对非虚函数,后者对虚函数。
查看答案与解析
答案:×
解析: 静态联编和动态联编的根本区别在于联编时机不同:
- 静态联编(早绑定):在编译时确定函数调用
- 动态联编(晚绑定):在运行时确定函数调用,通过虚函数表实现
动态联编不是在编译时进行的,而是在运行时根据对象的实际类型确定调用哪个函数。
难度: ⭐⭐ 考点: #静态联编 #动态联编 #多态
💡 学习锦囊
📖 相关公式与知识点:
- 静态联编:编译时绑定,根据指针/引用的静态类型确定
- 动态联编:运行时绑定,根据对象的实际类型确定
- 动态联编条件:虚函数 + 基类指针/引用调用
易错点
- 动态联编在运行时进行,不是编译时
- 用对象直接调用虚函数是静态联编
🔄 举一反三
- 下列关于联编的说法,正确的是( )
- A. 所有虚函数调用都是动态联编
- B. 动态联编在运行时进行,静态联编在编译时进行
- C. 非虚函数也可以实现动态联编
- D. 联编时机对程序行为没有影响
查看练习答案与解析
答案:B 解析:用对象直接调用虚函数是静态联编(A 错)。动态联编在运行时、静态联编在编译时(B 对)。非虚函数只能静态联编(C 错)。
二、单项选择题(每题 2 分,总计 20 分)
- 下列关于类的描述中,错误的是( )。
- A. 类就是 C 语言中的结构类型
- B. 类是创建对象的模板
- C. 类是抽象数据类型的实现
- D. 类是具有共同行为的若干对象的统一描述体
查看答案与解析
答案:A
解析: 类不仅仅是 C 语言中的结构类型。类具有结构体所没有的核心特性:封装性(访问控制)、成员函数、继承与多态。虽然 C++ 中 struct 和 class 功能几乎相同,但从概念上讲,"类就是结构类型"忽略了面向对象的本质特征。
难度: ⭐ 考点: #类与结构体 #封装 #面向对象
💡 学习锦囊
📖 相关公式与知识点:
- class 默认 private,struct 默认 public
- 类的三大特性:封装、继承、多态
易错点
- C++ 中 struct 也可有成员函数和访问控制
- "类就是结构类型"忽略了 OOP 特征
🔄 举一反三
- 关于 class 和 struct,下列说法正确的是( )
- A. struct 不能有成员函数
- B. class 不能有数据成员
- C. class 默认 private,struct 默认 public
- D. 两者完全不能互换
查看练习答案与解析
答案:C 解析:C++ 中 struct 和 class 唯一区别是默认访问权限。
- 下列关于构造函数的描述中,错误的是( )。
- A. 构造函数可以重载
- B. 构造函数名同类名
- C. 带参数的构造函数具有类型转换作用
- D. 构造函数是系统自动调用的
查看答案与解析
答案:C
解析: "带参数的构造函数具有类型转换作用"说法过于宽泛。只有单参数构造函数才具有隐式类型转换作用,多参数构造函数不具有此功能。可用 explicit 禁止隐式转换。
难度: ⭐⭐ 考点: #构造函数 #类型转换 #单参数构造函数
💡 学习锦囊
📖 相关公式与知识点:
- 单参数构造函数 → 可隐式类型转换
explicit关键字 → 禁止隐式转换
易错点
- 不是所有带参数的构造函数都有类型转换作用
- 建议对单参数构造函数加
explicit
🔄 举一反三
- 下列哪个声明可以防止隐式类型转换?( )
- A.
MyClass(int n); - B.
explicit MyClass(int n); - C.
MyClass(int a, int b); - D.
MyClass();
查看练习答案与解析
答案:B 解析:
explicit禁止隐式类型转换。 - A.
- 下列关于析构函数的描述中,错误的是( )。
- A. 析构函数的函数体都为空
- B. 析构函数是用来释放对象的
- C. 析构函数是系统自动调用的
- D. 析构函数是不能重载的
查看答案与解析
答案:A
解析: 析构函数的函数体不一定为空。析构函数常用于执行清理工作,如释放动态内存、关闭文件等。
~MyClass() {
delete[] data; // 函数体不为空
}2
3
难度: ⭐ 考点: #析构函数 #资源释放
💡 学习锦囊
📖 相关公式与知识点:
- 析构函数:
~类名(),无参数、无返回值、不可重载 - 析构顺序:与构造顺序相反
- RAII 原则:资源获取即初始化,析构时释放
易错点
- 有动态分配资源时必须自定义析构函数
- 忘记释放动态内存导致内存泄漏
🔄 举一反三
- 下列情况中,必须自定义析构函数的是( )
- A. 类中只有 int 类型成员
- B. 类中使用 new 分配了内存
- C. 类中没有数据成员
- D. 类中只有静态成员
查看练习答案与解析
答案:B 解析:使用 new 分配内存必须在析构函数中用 delete 释放。
- 下列关于静态成员的描述中,错误的是( )。
- A. 静态成员都是使用 static 来说明的
- B. 静态成员是属于类的,不是属于某个对象的
- C. 静态成员只可以用类名加作用域运算符来引用,不可用对象引用
- D. 静态数据成员的初始化是在类体外进行的
查看答案与解析
答案:C
解析: 静态成员既可以用类名引用,也可以用对象引用。
MyClass::count = 1; // ✅ 用类名引用(推荐)
obj.count = 2; // ✅ 用对象引用(也可以)2
推荐使用类名引用,因为静态成员属于类而非对象。
难度: ⭐⭐ 考点: #静态成员 #访问方式
💡 学习锦囊
📖 相关公式与知识点:
- 静态成员访问:
类名::静态成员或对象.静态成员 - 静态成员函数:没有 this 指针,只能访问静态成员
易错点
- 推荐用类名引用静态成员
- 静态成员函数不能声明为 const
🔄 举一反三
- 下列关于静态成员函数的描述,正确的是( )
- A. 静态成员函数有 this 指针
- B. 静态成员函数只能访问静态成员
- C. 静态成员函数不能通过类名调用
- D. 静态成员函数可以声明为 const
查看练习答案与解析
答案:B 解析:静态成员函数没有 this 指针(A 错),只能访问静态成员(B 对),可通过类名调用(C 错),不能声明为 const(D 错)。
- 下列关于友元函数的描述中,错误的是( )。
- A. 友元函数不是成员函数
- B. 友元函数只可访问类的私有成员
- C. 友元函数的调用方法同一般函数
- D. 友元函数可以是另一类中的成员函数
查看答案与解析
答案:B
解析: 友元函数可以访问类的所有成员(包括公有、保护和私有成员),而不是"只可访问私有成员"。
难度: ⭐⭐ 考点: #友元函数 #访问权限
💡 学习锦囊
📖 相关公式与知识点:
- 友元函数可访问所有成员(public、protected、private)
- 友元关系是单向的、不可传递的
易错点
- 友元函数可访问所有成员,不仅仅是私有成员
- 友元关系不是相互的,也不能继承
🔄 举一反三
- 若类 A 声明类 B 为友元,则下列说法正确的是( )
- A. 类 A 的成员函数可访问类 B 的私有成员
- B. 类 B 的成员函数可访问类 A 的私有成员
- C. A 和 B 互相可访问对方私有成员
- D. 类 B 的子类也可访问类 A 的私有成员
查看练习答案与解析
答案:B 解析:友元关系是单向的,A 声明 B 为友元意味着 B 可访问 A 的私有成员。友元关系不能继承。
void Set(A &a);是类 A 中一个成员函数的说明,其中A &a的含义是( )。- A. 类 A 的对象引用 a 作该函数的参数
- B. 类 A 的对象 a 的地址值作函数的参数
- C. 表达式变量 A 与变量 a 按位与作函数参数
- D. 指向类 A 对象指针 a 作函数参数
查看答案与解析
答案:A
解析:A &a 表示声明了一个类 A 的引用参数 a。在参数声明中,& 表示引用,不是取地址运算符,也不是按位与运算符。
区分 & 的三种含义:①声明中→引用 ②表达式中→取地址 ③位运算中→按位与
难度: ⭐ 考点: #引用参数 #&运算符含义
💡 学习锦囊
📖 相关公式与知识点:
- 引用参数:
类型 &参数名,传递对象的别名 - 指针参数:
类型 *参数名,传递对象的地址
思路分析
在函数参数声明中看到 &,首先想到"引用"。
🔄 举一反三
- 下列函数参数声明中,属于引用传递的是( )
- A.
void func(A a) - B.
void func(A *a) - C.
void func(A &a) - D.
void func(A **a)
查看练习答案与解析
答案:C 解析:A 值传递,B 指针传递,C 引用传递,D 指向指针的指针传递。
- A.
- 下列关于运算符 new 的描述中,错误的是( )。
- A. 它可以创建对象或变量
- B. 它可以创建对象数组或一般类型数组
- C. 用它创建对象或对象数组时要调用相应的构造函数
- D. 用它创建的对象可以不用 delete 运算符释放
查看答案与解析
答案:D
解析: 用 new 创建的对象必须用 delete 释放,否则导致内存泄漏。new 在堆上分配内存,不会自动释放。
难度: ⭐ 考点: #new运算符 #内存管理 #delete
💡 学习锦囊
📖 相关公式与知识点:
new/delete必须配对new[]/delete[]必须配对- 建议使用智能指针代替裸指针
易错点
delete释放new[]分配的内存是未定义行为delete后指针不会自动置空(悬空指针)
🔄 举一反三
- 下列关于 new/delete 的描述,正确的是( )
- A.
delete可释放new[]分配的内存 - B.
new分配的内存会自动释放 - C.
new[]必须与delete[]配对使用 - D.
delete后指针自动变为 nullptr
查看练习答案与解析
答案:C 解析:
new[]与delete[]必须配对。A、B、D 均错误。 - A.
- 派生类的对象可以直接访问的基类成员是( )。
- A. 公有继承的公有成员
- B. 保护继承的公有成员
- C. 私有继承的公有成员
- D. 公有继承的保护成员
查看答案与解析
答案:A
解析: 派生类对象(外部代码)可直接访问的基类成员,必须是经过继承后仍为 public 的成员。
- A:✅ 公有继承下,基类公有成员仍为公有
- B:❌ 保护继承下,基类公有成员变为保护
- C:❌ 私有继承下,基类公有成员变为私有
- D:❌ 保护成员外部不可访问
难度: ⭐⭐ 考点: #继承方式 #访问权限
💡 学习锦囊
📖 相关公式与知识点:
- 只有 public 继承的 public 成员才能被外部对象直接访问
- 保护成员和私有成员在任何继承方式下都不能被外部对象直接访问
易错点
- 保护成员即使在公有继承下也不能被外部对象直接访问
🔄 举一反三
- 若有
class D : private B,则 D 的成员函数可访问 B 中的( )- A. 公有成员
- B. 保护成员
- C. 公有和保护成员
- D. 公有、保护和私有成员
查看练习答案与解析
答案:C 解析:私有继承下,D 的成员函数可访问 B 的公有和保护成员,但不能访问 B 的私有成员。
- 下列运算符中,不可以重载的是( )。
- A. &&
- B. &
- C. [ ]
- D. ::
查看答案与解析
答案:D
解析: C++ 中不可重载的运算符:. .* :: ?: sizeof typeid
::(作用域解析运算符)不可重载,&&、&、[] 都可以重载。
难度: ⭐ 考点: #运算符重载 #不可重载运算符
💡 学习锦囊
📖 相关公式与知识点:
- 不可重载运算符:
..*::?:sizeoftypeid - 运算符重载不能改变优先级、结合性和操作数个数
易错点
.和::是最常考的不可重载运算符
🔄 举一反三
- 下列运算符中,不可以重载的是( )
- A.
-> - B.
new - C.
. - D.
()
查看练习答案与解析
答案:C 解析:成员访问运算符
.不可重载。->、new、()都可重载。 - A.
- 下列的成员函数中,纯虚函数是( )。
- A.
virtual void f1() = 0 - B.
void f1() = 0 - C.
virtual void f1() {} - D.
virtual void f1() == 0
查看答案与解析
答案:A
解析: 纯虚函数的标准格式:virtual 返回类型 函数名(参数列表) = 0;
- A:✅ 标准纯虚函数
- B:❌ 缺少
virtual - C:❌ 有函数体,是普通虚函数
- D:❌
== 0是比较运算,语法错误
难度: ⭐ 考点: #纯虚函数 #抽象类 #virtual
💡 学习锦囊
📖 相关公式与知识点:
- 纯虚函数:
virtual 返回类型 函数名(参数) = 0; - 含纯虚函数的类是抽象类,不能实例化
= 0是纯虚函数标志,不是赋值
易错点
- 纯虚函数必须同时有
virtual和= 0 - 抽象类不能创建对象,但可定义指针和引用
🔄 举一反三
- 下列关于纯虚函数和抽象类的描述,正确的是( )
- A. 抽象类可以创建对象
- B. 含有虚函数的类就是抽象类
- C. 纯虚函数必须在派生类中实现才能创建派生类对象
- D. 纯虚函数不能有函数体
查看练习答案与解析
答案:C 解析:抽象类不能创建对象(A 错),含虚函数不一定是抽象类(B 错),纯虚函数必须有派生类实现才能创建对象(C 对),纯虚函数可以有函数体(D 错)。
三、程序填空题(每空 2 分,共计 20 分)
- 实现下列求字符串长度的函数。

查看答案与解析
答案:(根据图片内容,典型字符串长度函数填空)
① const char *str 或 char *str(函数参数声明) ② *str != '\0' 或 *str(循环条件) ③ str++(指针移动) ④ count 或 len(返回值)
解析: 求字符串长度的核心思路是遍历字符串直到遇到结束符 '\0'。
int strlen(const char *str) {
int len = 0;
while (*str != '\0') {
len++;
str++;
}
return len;
}2
3
4
5
6
7
8
难度: ⭐⭐ 考点: #字符串 #指针遍历 #strlen
💡 学习锦囊
📖 相关公式与知识点:
- C 风格字符串以
'\0'结尾 - 字符串长度不包含
'\0' while (*str)等价于while (*str != '\0')
易错点
sizeof("hello")= 6(含'\0'),strlen("hello")= 5
🔄 举一反三
- 编写函数实现字符串复制功能(类似 strcpy)
查看练习答案与解析
答案:
cppvoid strcpy(char *dest, const char *src) { while (*src) *dest++ = *src++; *dest = '\0'; }1
2
3
4解析:逐字符复制,最后添加
'\0'。
- 完成下列类的定义。



查看答案与解析
答案:(根据图片内容,典型类定义填空)
典型填空涉及:构造函数初始化列表、成员函数实现、this 指针使用、静态成员初始化等。
解析: 类定义填空题通常考查:
- 构造函数:使用初始化列表初始化成员
- this 指针:区分成员变量与参数同名
- 静态成员:类体外初始化
- 常成员函数:
const修饰
class MyClass {
int x, y;
public:
MyClass(int x, int y) : x(x), y(y) {}
int getX() const { return x; }
void setX(int x) { this->x = x; }
};2
3
4
5
6
7
难度: ⭐⭐ 考点: #类定义 #构造函数 #this指针
💡 学习锦囊
📖 相关公式与知识点:
- 初始化列表:
类名(参数) : 成员1(值1), 成员2(值2) {} - this 指针:指向当前对象,区分同名变量
- const 成员函数:不能修改数据成员
易错点
- 初始化列表顺序应与成员声明顺序一致
- const 成员函数不能修改非 mutable 数据成员
🔄 举一反三
- 下列构造函数初始化列表写法,正确的是( )
- A.
MyClass(int a) : x(a) {} - B.
MyClass(int a) { x = a; } - C.
MyClass(int a) : this->x(a) {} - D.
MyClass(int a) : x = a {}
查看练习答案与解析
答案:A 解析:初始化列表使用
成员名(值)格式。B 是函数体内赋值,C 不能用this->,D 语法错误。 - A.
- 完成下列 point 类的定义,实现计算两点间欧氏距离的功能。
class point {
int x, y;
public:
point(int x = 0, int y = 0) {
① ;
② ;
}
double distance(point p) {
return ③ ;
}
};
int main() {
point p1, p2(3, 4);
double dist;
④ ;
cout << dist << endl;
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
查看答案与解析
答案: ① this->x = x ② this->y = y ③ sqrt((double)((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y))) ④ dist = p1.distance(p2)
解析:
①②:构造函数中参数名与成员名相同,需用 this 指针区分。
③:欧氏距离公式 $d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}$。x、y 是当前对象坐标,p.x、p.y 是参数对象坐标。
④:调用 p1 的 distance 成员函数,传入 p2 计算距离。
运行结果:5(p1(0,0) 到 p2(3,4) 的距离为 $\sqrt{9+16} = 5$)
难度: ⭐⭐ 考点: #this指针 #成员函数 #欧氏距离
💡 学习锦囊
📖 相关公式与知识点:
- 欧氏距离:$d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}$
- this 指针:指向当前对象,区分同名变量
sqrt函数需包含<cmath>
易错点
- 参数名与成员名相同时必须用 this 指针
- 整数运算注意类型转换
🔄 举一反三
- 若 point 类坐标改为 double 类型,distance 函数应如何修改?
查看练习答案与解析
答案:
cppdouble distance(point p) { return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y)); }1
2
3解析:坐标改为 double 后无需额外类型转换,运算自动按 double 进行。
四、阅读程序题(共计 30 分)
- 阅读该程序,给出程序的输出结果。(5 分)
#include <iostream>
void print(int);
void print(char);
void print(char *);
int main() {
int a(800);
print(a);
print('a');
print("break");
}
void print(int i) {
cout << i << endl;
}
void print(char i) {
cout << i << endl;
}
void print(char *i) {
cout << i << endl;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
查看答案与解析
答案:
800
a
break2
3
解析: 本题考查函数重载的匹配规则。
print(a):a 是 int 类型(值为 800),精确匹配void print(int i),输出800print('a'):'a'是 char 类型,精确匹配void print(char i),输出aprint("break"):"break"是字符串常量(char* 类型),匹配void print(char *i),输出break
难度: ⭐ 考点: #函数重载 #参数匹配 #重载解析
💡 学习锦囊
📖 相关公式与知识点:
- 函数重载:同名函数,参数列表不同
- 匹配优先级:精确匹配 > 类型提升 > 标准转换 > 用户定义转换
易错点
print('a')匹配 char 版本而非 int 版本(精确匹配优先)- 返回类型不同不能构成重载
🔄 举一反三
- 若增加
void print(double i)函数,则print(3.14f)会调用哪个版本?查看练习答案与解析
答案:调用
void print(double i)解析:3.14f是 float 类型,float 提升为 double,匹配print(double)版本。
- 阅读该程序,给出程序的输出结果。(5 分)
#include <iostream>
class Point {
public:
Point(int i, int j) { X = i; Y = j; }
Point(Point &rp);
~Point() { cout << "Destructor called.\n"; }
int Xcoord() { return X; }
int Ycoord() { return Y; }
private:
int X, Y;
};
Point::Point(Point &rp) {
X = rp.X;
Y = rp.Y;
cout << "Copy Constructor called.\n";
}
int main() {
Point p1(6, 9);
Point p2(p1);
Point p3 = p2, p4(0, 0);
p4 = p1;
cout << "p3 = (" << p3.Xcoord() << "," << p3.Ycoord() << ")\n";
cout << "p4 = (" << p4.Xcoord() << "," << p4.Ycoord() << ")\n";
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
查看答案与解析
答案:
Copy Constructor called.
Copy Constructor called.
p3 = (6,9)
p4 = (6,9)
Destructor called.
Destructor called.
Destructor called.
Destructor called.2
3
4
5
6
7
8
解析:
Point p1(6, 9):普通构造函数,无输出Point p2(p1):拷贝构造 →Copy Constructor called.Point p3 = p2:拷贝构造(初始化)→Copy Constructor called.Point p4(0, 0):普通构造函数,无输出p4 = p1:赋值操作,不调用拷贝构造函数- 输出坐标
- 析构顺序:p4 → p3 → p2 → p1(后构造的先析构)
难度: ⭐⭐ 考点: #拷贝构造函数 #析构顺序 #初始化与赋值
💡 学习锦囊
📖 相关公式与知识点:
- 拷贝构造调用时机:初始化新对象、值传递、返回对象
Point p3 = p2是初始化,调用拷贝构造p4 = p1是赋值,不调用拷贝构造- 析构顺序与构造顺序相反
易错点
- 初始化(
=声明)vs 赋值(=操作)的区别 - 析构顺序与构造顺序相反(栈的后进先出)
🔄 举一反三
- 若函数
void func(Point p)被调用func(p1),会输出什么?查看练习答案与解析
答案:
Copy Constructor called.和Destructor called.解析:值传递时实参初始化形参调用拷贝构造函数,函数结束时形参析构。
- 阅读该程序,给出程序的输出结果。(5 分)
#include <iostream>
class B {
public:
B(int i, int j) { b1 = i; b2 = j; }
void Print() { cout << b1 << "," << b2 << endl; }
private:
int b1, b2;
};
class C {
public:
C(int i) { c = i; }
void Print() { cout << c << endl; }
private:
int c;
};
class A {
public:
A(int i, int j, int k, int l) : c(i), b(j, k) { a = l; }
void Print() {
b.Print();
c.Print();
cout << a << endl;
}
private:
B b;
C c;
int a;
};
int main() {
B b(7, 8);
b.Print();
C c(9);
c.Print();
A a(4, 5, 6, 7);
a.Print();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
查看答案与解析
答案:
7,8
9
5,6
4
72
3
4
5
解析:
B b(7, 8)→b.Print()输出7,8C c(9)→c.Print()输出9A a(4, 5, 6, 7):初始化列表c(4), b(5, 6),a = 7- 成员 c:C(4),c.c = 4
- 成员 b:B(5, 6),b.b1 = 5, b.b2 = 6
- 成员 a = 7
a.Print():b.Print()→5,6,c.Print()→4,cout << a→7
难度: ⭐⭐ 考点: #类的组合 #成员初始化列表 #子对象构造
💡 学习锦囊
📖 相关公式与知识点:
- 类的组合:一个类的对象作为另一个类的成员(子对象)
- 构造顺序:按成员声明顺序构造,与初始化列表顺序无关
思路分析
分析 A(int i, int j, int k, int l) : c(i), b(j, k) { a = l; }:
- 参数 i → c 的构造函数 → c.c = 4
- 参数 j, k → b 的构造函数 → b.b1=5, b.b2=6
- 参数 l → a = 7
易错点
- 初始化列表中参数的对应关系要仔细分析
- 成员构造顺序取决于声明顺序,不是初始化列表顺序
🔄 举一反三
- 若将 A 的构造函数改为
A(int i, int j) : c(i), b(j, j) { a = i + j; },创建A a(3, 5)后a.Print()输出什么?查看练习答案与解析
答案:
5,5 3 81
2
3解析:c 初始化为 C(3),c.c=3;b 初始化为 B(5,5);a = 3+5 = 8。
- 阅读该程序,给出程序的输出结果。(5 分)
#include <iostream>
class A {
public:
virtual void fun() { cout << "A::fun() called.\n"; }
};
class B : public A {
void fun() { cout << "B::fun() called.\n"; }
};
void ffun(A *pa) {
pa->fun();
}
int main() {
A *pa = new A;
ffun(pa);
B *pb = new B;
ffun(pb);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
查看答案与解析
答案:
A::fun() called.
B::fun() called.2
解析:
ffun(pa):pa 指向 A 对象,pa->fun()调用 A::fun()ffun(pb):pb 指向 B 对象,传入 ffun 时转为 A*,但实际指向 B 对象。fun() 是虚函数,动态联编调用 B::fun()
关键:虚函数通过基类指针调用时,根据指针实际指向的对象类型选择函数版本,实现运行时多态。
难度: ⭐⭐ 考点: #虚函数 #多态 #动态联编
💡 学习锦囊
📖 相关公式与知识点:
- 动态联编条件:虚函数 + 基类指针/引用调用
- 虚函数表(vtable):存储虚函数地址,运行时查找
易错点
- 如果 fun() 不是虚函数,两次都调用 A::fun()
- 只有通过指针/引用调用虚函数才是动态联编
🔄 举一反三
- 若将
virtual关键字去掉,程序输出什么?查看练习答案与解析
答案:
A::fun() called. A::fun() called.1
2解析:去掉 virtual 后,调用根据指针的静态类型(A*)确定,两次都调用 A::fun()。
- 阅读该程序,给出程序的输出结果。(10 分)
#include <iostream>
class C {
public:
C(int i) { c = i; }
C() { c = 0; cout << "Default constructor called." << c << endl; }
~C() { cout << "Destructor called." << c << endl; }
void Print() { cout << c << endl; }
private:
int c;
};
int main() {
C *p;
p = new C[4];
int n = 1;
for (int i = 0; i < 4; i++)
p[i] = C(n++);
for (i = 0; i < 4; i++)
p[i].Print();
delete []p;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
查看答案与解析
答案:
Default constructor called.0
Default constructor called.0
Default constructor called.0
Default constructor called.0
Destructor called.1
Destructor called.2
Destructor called.3
Destructor called.4
1
2
3
4
Destructor called.4
Destructor called.3
Destructor called.2
Destructor called.12
3
4
5
6
7
8
9
10
11
12
13
14
15
16
解析:
第一步:p = new C[4] 创建 4 个对象数组,每个调用默认构造函数 C(),输出 4 行 Default constructor called.0。
第二步:p[i] = C(n++) 每次循环:C(n++) 创建临时对象(调用 C(int) 构造函数,无输出),赋值给 p[i],临时对象析构输出 Destructor called.X。
- i=0:C(1) → p[0].c=1,析构输出
Destructor called.1 - i=1:C(2) → p[1].c=2,析构输出
Destructor called.2 - i=2:C(3) → p[2].c=3,析构输出
Destructor called.3 - i=3:C(4) → p[3].c=4,析构输出
Destructor called.4
第三步:p[i].Print() 输出 1 2 3 4
第四步:delete []p 按逆序析构:Destructor called.4 → 3 → 2 → 1
难度: ⭐⭐⭐ 考点: #构造函数 #析构函数 #临时对象 #new与delete
💡 学习锦囊
📖 相关公式与知识点:
new 类名[n]:创建 n 个对象数组,每个调用默认构造函数- 临时对象:
C(n++)创建的临时对象在语句结束时析构 delete[]:按逆序析构数组中的对象
思路分析
分析此类程序的关键步骤:
- 识别每次对象创建调用的构造函数
- 注意临时对象的创建与销毁时机
- 追踪每个对象的成员变量值
- 注意析构顺序(与构造顺序相反)
易错点
p[i] = C(n++)是赋值操作,不是拷贝构造- 临时对象
C(n++)在赋值完成后立即析构 delete[]的析构顺序是逆序的- 默认构造函数有输出,而
C(int)构造函数没有输出
🔄 举一反三
- 若将
C(int i) { c = i; }改为C(int i) { c = i; cout << "Parameter constructor called." << c << endl; },程序输出会有什么变化?查看练习答案与解析
答案:在每次创建临时对象时多输出一行
Parameter constructor called.X解析:每个临时对象创建时都会调用C(int)构造函数并输出,析构时机不变。 - 若将
p[i] = C(n++)改为p[i] = C(n)(去掉n++),程序输出有什么变化?查看练习答案与解析
答案:所有临时对象都是 C(1),析构输出 4 次
Destructor called.1,Print 输出 4 个1,delete[] 输出 4 次Destructor called.1解析:n 始终为 1,所有 p[i].c 都为 1。
五、编程题(每题 10 分,共计 20 分)
- 按下列要求编程:
定义一个描述日期的类 Date,包括的数据成员有年(year)、月(month)和日(day),并实现如下功能函数:
(1)日期对象初始化; (2)以 year/month/day 形式输出日期; (3)判断闰年; (4)改变日期。
查看答案与解析
答案:
#include <iostream>
using namespace std;
class Date {
private:
int year, month, day;
public:
Date(int y = 2000, int m = 1, int d = 1) : year(y), month(m), day(d) {}
void Print() {
cout << year << "/" << month << "/" << day << endl;
}
bool IsLeapYear() {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
void SetDate(int y, int m, int d) {
year = y;
month = m;
day = d;
}
};
int main() {
Date d(2024, 2, 29);
d.Print();
cout << "Is leap year: " << (d.IsLeapYear() ? "Yes" : "No") << endl;
d.SetDate(2023, 3, 15);
d.Print();
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
解析:
(1)日期对象初始化:使用带默认参数的构造函数 Date(int y = 2000, int m = 1, int d = 1),支持无参和有参两种初始化方式。
(2)输出日期:Print() 函数以 year/month/day 格式输出。
(3)判断闰年:IsLeapYear() 函数,闰年规则:
- 能被 4 整除且不能被 100 整除,或能被 400 整除
- 公式:
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
(4)改变日期:SetDate() 函数修改年、月、日。
难度: ⭐⭐ 考点: #类定义 #构造函数 #成员函数 #闰年判断
💡 学习锦囊
📖 相关公式与知识点:
- 闰年判断规则:
- 普通闰年:能被 4 整除但不能被 100 整除(如 2024 年)
- 世纪闰年:能被 400 整除(如 2000 年)
- 非闰年:能被 100 整除但不能被 400 整除(如 1900 年)
- 构造函数初始化列表:
Date(int y, int m, int d) : year(y), month(m), day(d) {} - 带默认参数的构造函数可兼作默认构造函数
思路分析
设计日期类的步骤:
- 确定数据成员:year、month、day
- 设计构造函数:带默认参数,方便不同方式创建对象
- 实现各功能函数:Print、IsLeapYear、SetDate
- 注意闰年的判断逻辑要完整
易错点
- 闰年判断不能只看能否被 4 整除,还需排除能被 100 整除但不能被 400 整除的年份
- 1900 年不是闰年(能被 100 整除但不能被 400 整除)
- 2000 年是闰年(能被 400 整除)
🔄 举一反三
- 在 Date 类中增加一个函数
int DaysOfMonth(),返回当前月份的天数(考虑闰年)。查看练习答案与解析
答案:
cppint 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]; }1
2
3
4
5解析:使用数组存储每月天数,2 月需特殊处理闰年情况。
- 根据下列 Vector 类定义,编程完成 Vector 类的具体实现:
#include <iostream>
class Vector {
friend ostream& operator << (ostream& out, const Vector& v);
private:
int* data;
int size;
public:
Vector(int v[]);
~Vector();
Vector(const Vector &v);
Vector& operator=(const Vector &v);
int& operator[](int index);
};2
3
4
5
6
7
8
9
10
11
12
13
查看答案与解析
答案:
#include <iostream>
using namespace std;
class Vector {
friend ostream& operator << (ostream& out, const Vector& v);
private:
int* data;
int size;
public:
Vector(int v[]) : size(5) {
data = new int[size];
for (int i = 0; i < size; i++)
data[i] = v[i];
}
~Vector() {
delete[] data;
}
Vector(const Vector &v) : size(v.size) {
data = new int[size];
for (int i = 0; i < size; i++)
data[i] = v.data[i];
}
Vector& operator=(const Vector &v) {
if (this != &v) {
delete[] data;
size = v.size;
data = new int[size];
for (int i = 0; i < size; i++)
data[i] = v.data[i];
}
return *this;
}
int& 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;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
解析:
(1)构造函数 Vector(int v[]):从数组初始化,动态分配内存并复制数据。
(2)析构函数 ~Vector():释放动态分配的内存 delete[] data。
(3)拷贝构造函数 Vector(const Vector &v):深拷贝,重新分配内存并复制数据。必须实现深拷贝,否则两个对象的 data 指向同一内存,析构时会重复释放。
(4)赋值运算符 operator=:
- 自赋值检查:
if (this != &v) - 释放原有内存:
delete[] data - 重新分配并复制
- 返回
*this支持链式赋值
(5)下标运算符 operator[]:返回引用,支持读写操作。
(6)输出运算符 operator<<:友元函数,访问私有成员,输出向量元素。
难度: ⭐⭐⭐ 考点: #动态内存管理 #深拷贝 #运算符重载 #拷贝构造函数
💡 学习锦囊
📖 相关公式与知识点:
- 深拷贝 vs 浅拷贝:含动态内存的类必须实现深拷贝
- 拷贝构造函数:创建新对象时调用
- 赋值运算符:已存在对象赋值时调用
- 自赋值检查:
if (this != &v)防止自赋值导致错误 - 三法则(Rule of Three):如果需要自定义析构函数、拷贝构造函数或赋值运算符中的任何一个,通常需要自定义全部三个
思路分析
实现 Vector 类的关键步骤:
- 构造函数:动态分配内存,复制数据
- 析构函数:释放动态内存
- 拷贝构造:深拷贝,重新分配内存
- 赋值运算:先释放旧内存,再深拷贝,注意自赋值检查
- 下标运算:返回引用支持读写
- 输出运算:友元函数访问私有成员
易错点
- 赋值运算符必须检查自赋值,否则
delete[] data后再访问v.data会出错 - 拷贝构造函数必须深拷贝,浅拷贝会导致析构时重复释放内存
- 赋值运算符中必须先释放旧内存再分配新内存
operator[]返回引用才能支持v[i] = 10这样的写操作
🔄 举一反三
- 若在 Vector 类中增加一个默认构造函数
Vector() : data(NULL), size(0) {},赋值运算符需要修改吗?查看练习答案与解析
答案:不需要修改。当前赋值运算符的实现已经正确处理了
delete[] data(对 NULL 指针delete[]是安全的)和重新分配内存的逻辑。 解析:C++ 标准规定delete[] NULL是安全的,不会产生错误。 - 若将拷贝构造函数改为浅拷贝(
data = v.data; size = v.size;),程序会出现什么问题?查看练习答案与解析
答案:会导致两个对象的 data 指向同一块内存,当两个对象析构时,同一块内存会被
delete[]两次,导致未定义行为(double free)。 解析:这就是为什么含动态内存的类必须实现深拷贝。