Appearance
《C++面向对象程序设计》期末试卷B (精选01)
一、判断题(对的打"√",错的打"×")(每题 1 分,共计 10 分)
- 编译 C++ 源程序时,出现了警告错(Warning)也可以生成可执行文件。
查看答案与解析
答案:√
解析: C++ 编译过程中,错误分为两类:
- 错误(Error):严重的语法或语义错误,编译器无法生成目标代码,必须修正后才能继续。
- 警告(Warning):不太严重的问题,编译器可以继续编译并生成可执行文件,但建议修正。
例如,未使用的变量、隐式类型转换等都会产生警告,但不会阻止可执行文件的生成。
难度: ⭐ 考点: #编译过程 #警告与错误
💡 学习锦囊
📖 相关公式与知识点:
- Error:致命错误,编译终止,无法生成可执行文件
- Warning:非致命警告,编译继续,可生成可执行文件
- 常见警告:未使用变量、隐式类型转换、缺少返回值等
易错点
- 误以为有警告就不能生成可执行文件
- 实际开发中应尽量消除所有警告,保持代码质量
🔄 举一反三
- 下列关于 C++ 编译错误的说法,正确的是( )
- A. 有 Warning 就不能生成可执行文件
- B. 有 Error 也可以生成可执行文件
- C. Warning 不影响可执行文件的生成
- D. Warning 和 Error 的效果完全相同
查看练习答案与解析
答案:C 解析:Warning 是非致命警告,不影响可执行文件生成;Error 是致命错误,编译终止,无法生成可执行文件。二者效果不同。
- 标识符规定大小写字母没有区别。
查看答案与解析
答案:×
解析: C++ 是区分大小写的语言。标识符中的大小写字母被视为不同的字符。
例如:
myVar、MyVar、MYVAR是三个完全不同的标识符int是关键字,而Int、INT不是关键字
难度: ⭐ 考点: #标识符 #大小写敏感
💡 学习锦囊
📖 相关公式与知识点:
- C++ 标识符命名规则:由字母、数字、下划线组成,不能以数字开头
- C++ 严格区分大小写:
abc≠Abc≠ABC - 关键字全部为小写:
int、class、return等
易错点
- 误以为 C++ 像某些语言一样不区分大小写
- 混淆大小写导致的变量未定义错误是最常见的编译错误之一
🔄 举一反三
- 下列标识符中,与其他三个不同的是( )
- A.
Student - B.
student - C.
STUDENT - D.
student
查看练习答案与解析
答案:A(或 B 或 C,取决于参照物) 解析:C++ 区分大小写,
Student、student、STUDENT是三个不同的标识符。A、B、C 各不相同,D 与 B 相同。 - A.
- 数组中所有元素的类型都是相同的。
查看答案与解析
答案:√
解析: 数组是同一类型数据元素的集合。定义数组时必须指定元素类型,且所有元素都必须是该类型。
例如:
int arr[5]:所有元素都是 int 类型double scores[10]:所有元素都是 double 类型
难度: ⭐ 考点: #数组 #数据类型
💡 学习锦囊
📖 相关公式与知识点:
- 数组定义:
类型名 数组名[长度] - 数组特点:元素类型相同、内存连续、下标从 0 开始
- 与结构体对比:结构体可以包含不同类型的成员
思路分析
数组的核心特征就是"同类型元素的有序集合",如果需要存储不同类型的数据,应使用结构体或类。
🔄 举一反三
- 下列关于数组的描述,错误的是( )
- A. 数组元素在内存中连续存放
- B. 数组下标从 0 开始
- C. 数组中可以存放不同类型的元素
- D. 数组名代表数组首元素的地址
查看练习答案与解析
答案:C 解析:数组中所有元素必须是同一类型,不能存放不同类型的元素。A、B、D 都是数组的正确特性。
- 创建引用时要用一个同类型的变量进行初始化。
查看答案与解析
答案:√
解析: 引用(Reference)是已存在变量的别名,创建引用时必须用同类型的变量进行初始化,且初始化后不能再引用其他变量。
例如:
int a = 10; int &ref = a;✅ 正确,ref 是 a 的引用int a = 10; double &ref = a;❌ 错误,类型不匹配int &ref;❌ 错误,引用必须初始化
难度: ⭐ 考点: #引用 #引用初始化
💡 学习锦囊
📖 相关公式与知识点:
- 引用声明:
类型 &引用名 = 变量名 - 引用特性:必须初始化、初始化后不可改变引用对象、与原变量共享内存
- 引用与指针的区别:引用无空引用、必须初始化、不可重引用
易错点
- 引用必须在声明时初始化,不能像指针那样先声明后赋值
- 引用类型必须与被引用变量类型一致(const 引用除外)
🔄 举一反三
- 下列关于引用的描述,错误的是( )
- A. 引用必须在声明时初始化
- B. 引用初始化后可以改变引用的对象
- C. 引用是变量的别名
- D. 引用与被引用变量共享同一内存地址
查看练习答案与解析
答案:B 解析:引用一旦初始化,就不能再引用其他对象。对引用重新赋值只是改变被引用变量的值,而不是改变引用的对象。
- 静态数据成员必须在构造函数的成员初始化列表中进行初始化。
查看答案与解析
答案:×
解析: 静态数据成员不能在构造函数的成员初始化列表中初始化。静态数据成员属于类,不属于某个对象,必须在类体外进行初始化。
class MyClass {
static int count; // 类内声明
};
int MyClass::count = 0; // 类体外初始化2
3
4
只有常量整型静态数据成员可以在类内直接初始化:
class MyClass {
static const int SIZE = 10; // 常量整型可以在类内初始化
};2
3
难度: ⭐⭐ 考点: #静态数据成员 #类体外初始化
💡 学习锦囊
📖 相关公式与知识点:
- 静态数据成员:属于类,所有对象共享一份
- 初始化方式:在类体外用
类型 类名::静态成员名 = 值初始化 - 特例:
static const int类型可以在类内初始化
易错点
- 静态数据成员不能在构造函数初始化列表中初始化
- 静态数据成员不能在类体内一般性初始化(const 整型除外)
- 静态常量整型成员可以类内初始化,但其他类型不行
🔄 举一反三
- 下列关于静态数据成员初始化的说法,正确的是( )
- A. 静态数据成员可以在构造函数中初始化
- B. 静态数据成员必须在类体外初始化
- C. 静态数据成员可以不初始化
- D. 静态数据成员只能在 main 函数中初始化
查看练习答案与解析
答案:B 解析:静态数据成员属于类而非对象,必须在类体外单独初始化。不能在构造函数中初始化(A 错),必须初始化(C 错),不限于 main 函数(D 错)。
- 常数据成员在常成员函数中的值是不允许改变的,而在非常成员函数中是允许改变的。
查看答案与解析
答案:×
解析: 常数据成员(const 数据成员)无论在常成员函数还是非常成员函数中,其值都不允许改变。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
8
9
10
11
难度: ⭐⭐ 考点: #常数据成员 #const #成员函数
💡 学习锦囊
📖 相关公式与知识点:
- 常数据成员:
const 类型 成员名,必须在构造函数初始化列表中初始化 - 常成员函数:
返回类型 函数名() const,不能修改任何数据成员 - 非常成员函数:也不能修改常数据成员,因为 const 语义是"只读"
易错点
- 常数据成员的"常"是绝对的,任何函数都不能修改
- 常成员函数不能修改任何数据成员(不仅是常数据成员)
- 常数据成员只能通过构造函数初始化列表初始化
🔄 举一反三
- 下列关于 const 成员的描述,正确的是( )
- A. 常数据成员可以在非常成员函数中修改
- B. 常成员函数可以修改非常数据成员
- C. 常数据成员在任何函数中都不能修改
- D. 常数据成员可以在构造函数体中赋值
查看练习答案与解析
答案:C 解析:常数据成员一旦初始化就不可修改,无论在什么函数中(A 错、C 对)。常成员函数不能修改任何数据成员(B 错)。常数据成员必须在初始化列表中初始化,不能在构造函数体中赋值(D 错)。
- 成员函数与一般函数一样可以重载和设置参数的默认值,但不能设置为内联函数。
查看答案与解析
答案:×
解析: 成员函数可以设置为内联函数。设置方式有两种:
- 隐式内联:在类体内定义的成员函数自动成为内联函数
- 显式内联:在类体外定义时使用
inline关键字
class MyClass {
public:
void func1() { /* ... */ } // 隐式内联
inline void func2(); // 显式声明内联
};
inline void MyClass::func2() { /* ... */ } // 类体外定义内联2
3
4
5
6
7
难度: ⭐ 考点: #内联函数 #成员函数 #inline
💡 学习锦囊
📖 相关公式与知识点:
- 内联函数:编译时在调用处展开函数体,减少函数调用开销
- 类体内定义的成员函数默认为内联函数
- 使用
inline关键字可显式声明内联函数 - 内联适用于函数体短小、频繁调用的函数
易错点
- 成员函数完全可以是内联函数
inline只是对编译器的建议,编译器可以忽略- 递归函数、含循环的函数不适合内联
🔄 举一反三
- 下列关于内联函数的描述,错误的是( )
- A. 类体内定义的成员函数默认是内联的
- B. 使用 inline 关键字可以声明内联函数
- C. 内联函数一定会被编译器展开
- D. 成员函数可以设置为内联函数
查看练习答案与解析
答案:C 解析:
inline只是对编译器的建议,编译器可能不展开(如函数体过大、含递归等)。A、B、D 都是正确的描述。
- 定义对象指针时也要调用构造函数。
查看答案与解析
答案:×
解析: 定义对象指针时不会调用构造函数。指针本身只是一个存储地址的变量,定义指针并不创建对象。
MyClass *p; // 不调用构造函数,p 只是一个指针变量
MyClass obj; // 调用构造函数,创建对象
MyClass *p2 = new MyClass; // 调用构造函数,new 创建了对象2
3
只有创建对象时才调用构造函数,定义指针只是定义了一个指向对象的地址变量。
难度: ⭐⭐ 考点: #对象指针 #构造函数调用时机
💡 学习锦囊
📖 相关公式与知识点:
- 对象指针:
类名 *指针名,不创建对象,不调用构造函数 - 创建对象:
类名 对象名或new 类名,调用构造函数 - 指针使用前必须指向有效的对象
易错点
- 定义指针 ≠ 创建对象
MyClass *p不调用构造函数MyClass *p = new MyClass中,new MyClass调用构造函数,p只是指向该对象
🔄 举一反三
- 下列哪种情况会调用构造函数?( )
- A.
MyClass *p; - B.
MyClass obj; - C.
MyClass *p = &obj;(obj 已存在) - D.
extern MyClass obj;
查看练习答案与解析
答案:B 解析:只有 B 创建了对象,调用构造函数。A 定义指针不调用,C 让指针指向已存在的对象不调用,D 是外部声明不调用。
- A.
- 派生类只继承基类中的公有成员和保护成员,而不继承私有成员。
查看答案与解析
答案:×
解析: 派生类继承基类的所有成员(包括公有、保护和私有成员),只是私有成员在派生类中不可直接访问。
class Base {
public:
int pub;
protected:
int prot;
private:
int priv; // 被继承但不可直接访问
};
class Derived : public Base {
void func() {
pub = 1; // ✅ 可访问
prot = 2; // ✅ 可访问
// priv = 3; // ❌ 不可直接访问,但确实被继承了
}
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
私有成员虽然被继承,但只能通过基类的公有/保护成员函数间接访问。
难度: ⭐⭐ 考点: #继承 #访问控制 #私有成员
💡 学习锦囊
📖 相关公式与知识点:
- 继承方式与访问权限:
| 基类成员 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可直接访问 | 不可直接访问 | 不可直接访问 |
- 关键区别:继承 ≠ 可访问
易错点
- "不继承私有成员"是常见误解,实际上是"继承但不可直接访问"
- 私有成员仍然存在于派生类对象中,占用内存
🔄 举一反三
- 下列关于继承的描述,正确的是( )
- A. 派生类不继承基类的私有成员
- B. 派生类继承基类的所有成员,但私有成员不可直接访问
- C. 私有继承时,基类的公有成员在派生类中仍为公有
- D. 保护继承时,基类的公有成员在派生类中变为私有
查看练习答案与解析
答案:B 解析:派生类继承基类所有成员,私有成员不可直接访问(B 对、A 错)。私有继承时基类公有成员变为私有(C 错)。保护继承时基类公有成员变为保护(D 错)。
- 静态联编和动态联编都是在编译时进行的,二者的区别仅是前者对非虚函数,后者对虚函数。
查看答案与解析
答案:×
解析: 静态联编在编译时确定函数调用,动态联编在运行时确定函数调用。二者的根本区别在于联编时机不同,而非仅是函数类型不同。
- 静态联编(早绑定):编译时确定调用哪个函数,效率高但灵活性低
- 动态联编(晚绑定):运行时根据对象实际类型确定调用哪个函数,通过虚函数和基类指针/引用实现
Base *p = new Derived;
p->func(); // 动态联编,运行时确定调用 Derived::func()2
难度: ⭐⭐ 考点: #静态联编 #动态联编 #多态 #虚函数
💡 学习锦囊
📖 相关公式与知识点:
- 静态联编:编译时绑定,通过函数名、参数类型确定调用
- 动态联编:运行时绑定,通过虚函数表(vtable)确定调用
- 动态联编条件:虚函数 + 基类指针/引用调用
易错点
- 动态联编是在运行时进行的,不是编译时
- 动态联编必须同时满足:虚函数 + 基类指针/引用调用
- 用对象调用虚函数仍是静态联编
🔄 举一反三
- 下列关于联编的描述,正确的是( )
- A. 静态联编和动态联编都在编译时进行
- B. 动态联编在运行时进行,通过虚函数实现
- C. 所有虚函数调用都是动态联编
- D. 非虚函数也可以实现动态联编
查看练习答案与解析
答案:B 解析:动态联编在运行时进行(A 错、B 对)。用对象直接调用虚函数是静态联编(C 错)。非虚函数只能静态联编(D 错)。
二、单项选择题(每题 2 分,总计 20 分)
- 下列关于类的描述中,错误的是( )。
- A. 类就是 C 语言中的结构类型
- B. 类是创建对象的模板
- C. 类是抽象数据类型的实现
- D. 类是具有共同行为的若干对象的统一描述体
查看答案与解析
答案:A
解析: 类不仅仅是 C 语言中的结构类型。类具有结构体所没有的重要特性:封装性(私有成员)、成员函数、继承与多态。
C++ 中的 struct 和 class 的唯一区别是默认访问权限不同(struct 默认 public,class 默认 private),但从概念上讲,类比 C 语言的结构类型丰富得多。
难度: ⭐ 考点: #类与结构体 #封装 #面向对象
💡 学习锦囊
📖 相关公式与知识点:
- C++ 中 class 与 struct 的区别:默认访问权限不同
- 类的三大特性:封装、继承、多态
- 类 = 数据成员 + 成员函数 + 访问控制
易错点
- C++ 中 struct 也可以有成员函数和访问控制,与 class 功能几乎相同
- 但从概念上讲,"类就是结构类型"的说法忽略了面向对象的本质特征
🔄 举一反三
- 在 C++ 中,关于 class 和 struct 的说法,正确的是( )
- A. class 不能包含数据成员
- B. struct 不能包含成员函数
- C. class 默认成员为 private,struct 默认成员为 public
- D. class 和 struct 完全没有区别
查看练习答案与解析
答案:C 解析:C++ 中 class 和 struct 的唯一区别是默认访问权限:class 默认 private,struct 默认 public。两者都可以包含数据成员和成员函数。
- 下列关于构造函数的描述中,错误的是( )。
- A. 构造函数可以重载
- B. 构造函数名同类名
- C. 带参数的构造函数具有类型转换作用
- D. 构造函数是系统自动调用的
查看答案与解析
答案:C
解析: "带参数的构造函数具有类型转换作用"这个说法过于宽泛。只有单参数的构造函数才具有隐式类型转换作用,多参数的构造函数不具有此功能。
MyClass obj = 10; // ✅ 单参数构造函数的隐式转换其他选项均正确:A 构造函数可重载,B 构造函数名同类名,D 构造函数自动调用。
难度: ⭐⭐ 考点: #构造函数 #类型转换 #单参数构造函数
💡 学习锦囊
📖 相关公式与知识点:
- 单参数构造函数的隐式转换:
MyClass obj = 10; - 使用
explicit关键字可禁止隐式转换 - 拷贝构造函数也是单参数构造函数(const 引用参数)
易错点
- 不是所有带参数的构造函数都有类型转换作用
- 多参数构造函数不具有隐式类型转换功能
🔄 举一反三
- 下列哪种构造函数具有隐式类型转换作用?( )
- A. 无参构造函数
- B. 单参数构造函数
- C. 多参数构造函数
- D. 析构函数
查看练习答案与解析
答案:B 解析:只有单参数构造函数具有隐式类型转换作用。
- 下列关于析构函数的描述中,错误的是( )。
- A. 析构函数的函数体都为空
- B. 析构函数是用来释放对象的
- C. 析构函数是系统自动调用的
- D. 析构函数是不能重载的
查看答案与解析
答案:A
解析: 析构函数的函数体不一定为空。析构函数常用于执行清理工作,如释放动态分配的内存、关闭文件等。
~MyClass() {
delete[] data; // 函数体不为空
}2
3
难度: ⭐ 考点: #析构函数 #资源释放
💡 学习锦囊
📖 相关公式与知识点:
- 析构函数特性:
~类名()、无参数、无返回值、不可重载、自动调用 - 析构函数常见用途:释放 new 分配的内存、关闭文件等
- 析构顺序:与构造顺序相反
易错点
- 析构函数体可以为空,也可以包含清理代码
- 如果类中有动态分配的资源,必须自定义析构函数释放
🔄 举一反三
- 下列关于析构函数的描述,正确的是( )
- A. 析构函数可以有参数
- B. 一个类可以有多个析构函数
- C. 析构函数在对象销毁时自动调用
- D. 析构函数可以有返回值
查看练习答案与解析
答案:C 解析:析构函数无参数(A 错)、不可重载(B 错)、无返回值(D 错)、在对象销毁时自动调用(C 对)。
- 下列关于静态成员的描述中,错误的是( )。
- A. 静态成员都是使用 static 来说明的
- B. 静态成员是属于类的,不是属于某个对象的
- C. 静态成员只可以用类名加作用域运算符来引用,不可用对象引用
- D. 静态数据成员的初始化是在类体外进行的
查看答案与解析
答案:C
解析: 静态成员既可以用类名引用,也可以用对象引用。
MyClass::count = 1; // ✅ 用类名引用
obj.count = 2; // ✅ 用对象引用(也可以)2
推荐使用类名引用,因为静态成员属于类而非对象。
难度: ⭐⭐ 考点: #静态成员 #访问方式
💡 学习锦囊
📖 相关公式与知识点:
- 静态成员访问方式:
类名::静态成员或对象.静态成员 - 静态成员函数:没有
this指针,只能访问静态成员
易错点
- 虽然可以用对象引用静态成员,但推荐用类名引用
- 静态成员函数不能访问非静态成员(没有 this 指针)
🔄 举一反三
- 下列关于静态成员函数的描述,正确的是( )
- A. 静态成员函数有 this 指针
- B. 静态成员函数只能访问静态成员
- C. 静态成员函数不能通过类名调用
- D. 静态成员函数可以访问非静态成员
查看练习答案与解析
答案:B 解析:静态成员函数没有 this 指针(A 错),因此只能访问静态成员(B 对、D 错),可以通过类名调用(C 错)。
- 下列关于友元函数的描述中,错误的是( )。
- A. 友元函数不是成员函数
- B. 友元函数只可访问类的私有成员
- C. 友元函数的调用方法同一般函数
- D. 友元函数可以是另一类中的成员函数
查看答案与解析
答案:B
解析: 友元函数可以访问类的所有成员(包括公有、保护和私有成员),而不是"只可访问私有成员"。
void friendFunc(MyClass &obj) {
obj.pub = 1; // ✅ 可访问公有成员
obj.prot = 2; // ✅ 可访问保护成员
obj.priv = 3; // ✅ 可访问私有成员
}2
3
4
5
难度: ⭐⭐ 考点: #友元函数 #访问权限
💡 学习锦囊
📖 相关公式与知识点:
- 友元函数可以访问类的所有成员(public、protected、private)
- 友元关系是单向的、不可传递的
易错点
- 友元函数可以访问所有成员,不仅仅是私有成员
- 友元关系不是相互的
🔄 举一反三
- 下列关于友元的描述,正确的是( )
- A. 友元关系是相互的
- B. 友元关系可以继承
- C. 友元函数可以访问类的所有成员
- D. 友元函数是类的成员函数
查看练习答案与解析
答案:C 解析:友元关系是单向的(A 错),不可继承(B 错),友元函数可以访问类的所有成员(C 对),友元函数不是成员函数(D 错)。
void Set(A &a);是类 A 中一个成员函数的说明,其中A &a的含义是( )。- A. 类 A 的对象引用 a 作该函数的参数
- B. 类 A 的对象 a 的地址值作函数的参数
- C. 表达式变量 A 与变量 a 按位与作函数参数
- D. 指向类 A 对象指针 a 作函数参数
查看答案与解析
答案:A
解析:A &a 表示声明了一个类 A 的引用参数 a。& 在参数声明中表示引用,不是取地址运算符,也不是按位与运算符。
难度: ⭐ 考点: #引用参数 #引用与指针区别
💡 学习锦囊
📖 相关公式与知识点:
- 引用参数:
类型 &参数名,传递对象的别名 - 指针参数:
类型 *参数名,传递对象的地址
思路分析
区分 & 的三种含义:
- 声明中:引用(
int &ref = a) - 表达式中:取地址(
&a) - 位运算中:按位与(
a & b)
🔄 举一反三
- 下列函数参数声明中,哪个是引用传递?( )
- 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[]必须配对使用- 内存泄漏:
new后未delete
易错点
- 用
delete释放new[]分配的数组会导致未定义行为 - 忘记
delete会导致内存泄漏
🔄 举一反三
- 下列关于 new/delete 的描述,正确的是( )
- A.
delete可以释放new[]分配的内存 - B.
new分配的内存会自动释放 - C.
new[]必须与delete[]配对使用 - D.
delete后指针自动变为 NULL
查看练习答案与解析
答案:C 解析:
new[]与delete[]必须配对(C 对)。delete不能正确释放new[]的内存(A 错),new分配的内存不会自动释放(B 错),delete后指针不会自动变为 NULL(D 错)。 - A.
- 派生类的对象可以直接访问的基类成员是( )。
- A. 公有继承的公有成员
- B. 保护继承的公有成员
- C. 私有继承的公有成员
- D. 公有继承的保护成员
查看答案与解析
答案:A
解析: 派生类的对象(外部代码)可以直接访问的基类成员,必须是经过继承后仍为 public 访问权限的成员。
- A:✅ 公有继承下,基类公有成员仍为公有,外部可访问
- B:❌ 保护继承下,基类公有成员变为保护,外部不可访问
- C:❌ 私有继承下,基类公有成员变为私有,外部不可访问
- D:❌ 保护成员外部不可访问
难度: ⭐⭐ 考点: #继承方式 #访问权限 #公有继承
💡 学习锦囊
📖 相关公式与知识点:
- 只有 public 继承的 public 成员才能被外部对象直接访问
- 判断依据:成员经过继承后的最终访问权限是否为 public
易错点
- 保护成员即使在公有继承下也不能被外部对象直接访问
- 保护继承和私有继承都会降低基类成员的访问权限
🔄 举一反三
- 若有
class D : protected B,则 D 的对象可以访问 B 中的( )- A. 公有成员
- B. 保护成员
- C. 公有和保护成员
- D. 以上都不可以
查看练习答案与解析
答案:D 解析:保护继承下,基类的公有成员变为保护成员,保护成员仍为保护成员。保护成员不能被外部对象访问。
- 下列运算符中,不可以重载的是( )。
- A. &&
- B. &
- C. [ ]
- D. ::
查看答案与解析
答案:D
解析: C++ 中不可重载的运算符:. .* :: ?: sizeof typeid
- A:
&&✅ 可重载 - B:
&✅ 可重载 - C:
[]✅ 可重载 - D:
::❌ 不可重载(作用域解析运算符)
难度: ⭐ 考点: #运算符重载 #不可重载运算符
💡 学习锦囊
📖 相关公式与知识点:
- 不可重载的运算符:
..*::?: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; - 含纯虚函数的类是抽象类,不能实例化
- 派生类必须实现所有纯虚函数才能成为具体类
易错点
- 纯虚函数必须有
virtual和= 0 = 0不是赋值,是纯虚函数的标志
🔄 举一反三
- 下列关于纯虚函数和抽象类的描述,正确的是( )
- A. 抽象类可以创建对象
- B. 纯虚函数必须在派生类中实现才能创建派生类对象
- C. 含有虚函数的类就是抽象类
- D. 纯虚函数不能在派生类中实现
查看练习答案与解析
答案:B 解析:抽象类不能创建对象(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和strlen的区别:sizeof包含'\0',strlen不包含
🔄 举一反三
- 编写函数实现字符串复制功能(类似 strcpy)
查看练习答案与解析
答案:
cppvoid strcpy(char *dest, const char *src) { while (*src) { *dest = *src; dest++; src++; } *dest = '\0'; }1
2
3
4
5
6
7
8解析:逐字符复制,直到遇到
'\0',最后添加'\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) : a(a) {} - B.
MyClass(int a) { a = a; } - C.
MyClass(int a) : this->a(a) {} - D.
MyClass(int a) : a = a {}
查看练习答案与解析
答案:A 解析:初始化列表使用
成员名(值)格式(A 对)。B 中a = a是参数自赋值。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 指针区分
- 整数运算可能丢失精度,注意类型转换
🔄 举一反三
- 若要计算三维空间中两点的距离,distance 函数应如何修改?
查看练习答案与解析
答案:
cppdouble distance(point3d p) { return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z)); }1
2
3解析:三维欧氏距离公式为 $d = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2 + (z_1-z_2)^2}$。
四、阅读程序题(共计 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"是字符串常量,匹配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() →A::fun() called.ffun(pb):pb 指向 B 对象,传入 ffun 时转为 A*,但实际指向 B 对象。fun() 是虚函数,动态联编调用 B::fun() →B::fun() called.
关键:虚函数通过基类指针调用时,根据指针实际指向的对象类型选择函数版本。
难度: ⭐⭐ 考点: #虚函数 #多态 #动态联编
💡 学习锦囊
📖 相关公式与知识点:
- 动态联编条件:虚函数 + 基类指针/引用调用
- 虚函数表(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] 使用 new C[4] 创建 4 个对象数组,每个元素调用默认构造函数 C(),输出 4 行 Default constructor called.0。
第二步:for (int i = 0; i < 4; i++) p[i] = C(n++); 每次循环:
C(n++)创建一个临时对象,调用C(int)构造函数(无输出)- 将临时对象赋值给
p[i](默认赋值操作,拷贝成员 c 的值) - 临时对象生命周期结束,调用析构函数,输出
Destructor called.X
循环过程:
- i=0:临时对象 C(1),赋值后析构 →
Destructor called.1,p[0].c = 1 - i=1:临时对象 C(2),赋值后析构 →
Destructor called.2,p[1].c = 2 - i=2:临时对象 C(3),赋值后析构 →
Destructor called.3,p[2].c = 3 - i=3:临时对象 C(4),赋值后析构 →
Destructor called.4,p[3].c = 4
第三步:for (i = 0; i < 4; i++) p[i].Print(); 输出:1 2 3 4
第四步:delete []p 释放数组,按逆序调用析构函数:
Destructor called.4Destructor called.3Destructor called.2Destructor called.1
难度: ⭐⭐⭐ 考点: #构造函数 #析构函数 #临时对象 #new与delete
💡 学习锦囊
📖 相关公式与知识点:
new 类名[n]:创建 n 个对象数组,每个调用默认构造函数- 临时对象:
C(n++)创建的临时对象在语句结束时析构 delete[]:按逆序析构数组中的对象- 赋值操作
p[i] = C(n++):不调用拷贝构造,而是调用默认的operator=
思路分析
分析此类程序的关键步骤:
- 识别每次对象创建调用的构造函数
- 注意临时对象的创建与销毁时机
- 追踪每个对象的成员变量值
- 注意析构顺序(与构造顺序相反)
易错点
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; },程序输出会有什么变化?查看练习答案与解析
答案:在每次创建临时对象时多输出一行:
Default constructor called.0 Default constructor called.0 Default constructor called.0 Default constructor called.0 Parameter constructor called.1 Destructor called.1 Parameter constructor called.2 Destructor called.2 Parameter constructor called.3 Destructor called.3 Parameter constructor called.4 Destructor called.4 1 2 3 4 Destructor called.4 Destructor called.3 Destructor called.2 Destructor called.11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20解析:每个临时对象创建时都会调用
C(int)构造函数并输出,析构时机不变。 - 若将
p[i] = C(n++)改为p[i] = C(n)(去掉n++),程序输出有什么变化?查看练习答案与解析
答案:
Default constructor called.0 Default constructor called.0 Default constructor called.0 Default constructor called.0 Destructor called.1 Destructor called.1 Destructor called.1 Destructor called.1 1 1 1 1 Destructor called.1 Destructor called.1 Destructor called.1 Destructor called.11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16解析:n 始终为 1,每次创建的临时对象都是 C(1),赋值后析构输出
Destructor called.1,所有 p[i].c 都为 1。