Skip to content

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

注意:答案直接写在答题纸上,答在试卷上无效,考试后答题纸和试卷一同上交

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

  1. C++ 语言和 C 语言都是面向对象的程序设计语言。
查看答案与解析

答案:×

解析: C 语言是面向过程的程序设计语言,不支持面向对象的特性(封装、继承、多态)。C++ 在 C 语言基础上增加了面向对象的特性,因此 C++ 是面向对象的语言,但 C 语言不是。


难度: ⭐ 考点: #面向对象 #C++与C的区别

💡 学习锦囊

📖 相关公式与知识点:

  • C 语言:面向过程,核心是函数
  • C++:既支持面向过程也支持面向对象,核心是类和对象
  • 面向对象三大特征:封装、继承、多态

易错点

  • C++ 兼容 C 语言,但 C++ 不等于"C 语言加面向对象"——C++ 还增加了模板、异常处理、命名空间等特性
🔄 举一反三
  1. 下列语言中,属于面向对象程序设计语言的是 ( )
    • A. C
    • B. C++
    • C. Fortran
    • D. 汇编语言
    查看练习答案与解析

    答案:B 解析:C++、Java、Python 等支持面向对象特性。C 和 Fortran 是面向过程语言,汇编语言是低级语言。

  1. 面向对象方法具有封装性、继承性和多态性。
查看答案与解析

答案:√

解析: 封装性、继承性和多态性是面向对象程序设计的三大基本特征。

  • 封装:将数据和操作数据的方法绑定在一起,隐藏内部实现细节
  • 继承:子类可以继承父类的属性和方法,实现代码复用
  • 多态:同一接口,不同实现,允许用统一的方式操作不同类型的对象

难度: ⭐ 考点: #面向对象三大特征 #封装 #继承 #多态

💡 学习锦囊

📖 相关公式与知识点:

  • 封装:通过类实现,private/protected/public 控制访问
  • 继承:class Derived : public Base { };
  • 多态:虚函数 + 基类指针/引用实现运行时多态

思路分析

记住口诀:"封装继承多态"——面向对象三要素。

🔄 举一反三
  1. 下列关于面向对象特征的描述中,错误的是 ( )
    • A. 封装可以隐藏对象的内部实现细节
    • B. 继承可以实现代码复用
    • C. 多态要求必须有虚函数
    • D. 封装、继承、多态是面向对象的三大基本特征
    查看练习答案与解析

    答案:C 解析:多态不一定要有虚函数。编译时多态(静态多态)通过函数重载和模板实现,不需要虚函数。只有运行时多态(动态多态)才需要虚函数。

  1. 编译 C++ 源程序时,出现了警告错(Warning)也可以生成可执行文件。
查看答案与解析

答案:√

解析: 编译器的诊断信息分为两类:

  • 错误(Error):严重问题,阻止编译,无法生成目标文件和可执行文件
  • 警告(Warning):潜在问题,不阻止编译,可以生成可执行文件

警告虽然不影响编译,但通常表示代码中存在潜在问题,建议修复。


难度: ⭐ 考点: #编译过程 #错误与警告

💡 学习锦囊

📖 相关公式与知识点:

  • Error:编译失败,无法生成可执行文件
  • Warning:编译成功,可生成可执行文件,但代码可能有隐患

思路分析

Error = 必须修复;Warning = 建议修复但不阻止编译。

🔄 举一反三
  1. 下列哪种情况会产生编译错误(Error)而非警告(Warning)?
    • A. 将 int 赋值给 float
    • B. 使用未初始化的变量
    • C. 缺少分号
    • D. 隐式类型转换可能丢失精度
    查看练习答案与解析

    答案:C 解析:缺少分号是语法错误(Error),编译器无法继续解析。A、B、D 都是潜在问题,通常产生 Warning。

  1. 引用是一种变量,它也有值和地址值。
查看答案与解析

答案:×

解析: 引用(Reference)不是一种独立的变量,而是已有变量的别名。

  • 引用没有独立的地址值,它与被引用的变量共享同一个地址
  • 引用没有独立的值,它与被引用的变量共享同一个值
  • 对引用取地址 &ra 得到的是被引用变量的地址
cpp
int a = 10;
int& ra = a;  // ra 是 a 的别名
// &ra == &a(地址相同)
// ra == a(值相同)

难度: ⭐ 考点: #引用 #引用与指针的区别

💡 学习锦囊

📖 相关公式与知识点:

  • 引用声明:int& ra = a;(必须初始化)
  • 引用是别名,不是独立变量
  • 引用与被引用变量共享地址和值
  • 引用 vs 指针:引用必须初始化、不能重绑定、没有独立地址

思路分析

引用 = 别名,不是变量。它没有自己的地址,与被引用变量共用同一地址。

易错点

  • 认为引用有自己的地址——引用与被引用变量地址相同
  • 混淆引用和指针——指针是独立变量,有自己的地址和值
🔄 举一反三
  1. 下列关于引用的描述中,正确的是 ( )
    • A. 引用可以不初始化
    • B. 引用可以被重新绑定到另一个变量
    • C. 引用是变量的别名,与变量共享地址
    • D. 引用和指针完全等价
    查看练习答案与解析

    答案:C 解析:引用必须在声明时初始化(A 错误);引用一旦绑定不能更改(B 错误);引用是别名,与被引用变量共享地址(C 正确);引用和指针不同,引用不是独立变量(D 错误)。

  1. 指针是变量,它具有的值是某个变量或对象的地址值,它还具有一个地址值,这两个地址值是相等的。
查看答案与解析

答案:×

解析: 指针变量有两个相关的地址值,但它们不相等

  • 指针的值:指针存储的地址值,即它指向的变量或对象的地址
  • 指针的地址:指针变量自身在内存中的存储地址
cpp
int a = 10;
int* p = &a;
// p 的值 = &a(a 的地址)
// &p 的值 = p 自身的地址
// &p ≠ p(两者不相等)

难度: ⭐ 考点: #指针 #指针的值与地址

💡 学习锦囊

📖 相关公式与知识点:

  • 指针的值:p——指向的变量的地址
  • 指针的地址:&p——指针变量自身的地址
  • p&p
  • 指针是独立变量,有自己的地址

思路分析

指针 = 变量 → 有自己的地址(&p)→ 存储的是别人的地址(p)→ 两者不同。

🔄 举一反三
  1. 下列代码中,p&p 的关系是什么?
    cpp
    int x = 5;
    int* p = &x;
    查看练习答案与解析

    答案px 的地址(&x),&p 是指针变量 p 自身的内存地址。两者不相等。

  1. 成员函数与一般函数一样可以重载、内联和设置参数的默认值。
查看答案与解析

答案:√

解析: 成员函数具有一般函数的特性,可以:

  • 重载:同一个类中可以有多个同名但参数不同的成员函数
  • 内联:在类体内定义的成员函数自动成为内联函数
  • 设置默认参数:成员函数可以设置参数默认值

难度: ⭐ 考点: #成员函数 #函数重载 #内联函数 #默认参数

💡 学习锦囊

📖 相关公式与知识点:

  • 成员函数重载:同类中同名不同参
  • 内联成员函数:类体内定义自动内联
  • 默认参数:从右向左设置

思路分析

成员函数 = 定义在类中的函数 → 具有普通函数的所有特性 + 可以访问类的私有成员。

🔄 举一反三
  1. 下列关于成员函数的描述中,错误的是 ( )
    • A. 成员函数可以重载
    • B. 成员函数可以是静态的
    • C. 成员函数不能设置默认参数
    • D. 成员函数可以是内联的
    查看练习答案与解析

    答案:C 解析:成员函数可以设置默认参数,与普通函数规则相同。

  1. 静态数据成员必须在构造函数的成员初始化列表中进行初始化。
查看答案与解析

答案:×

解析: 静态数据成员不能在构造函数的初始化列表中初始化。静态数据成员属于类本身,不属于任何对象,必须在类外单独初始化。

cpp
class Example {
    static int count;  // 类内声明
};
int Example::count = 0;  // 类外初始化(必须)

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

💡 学习锦囊

📖 相关公式与知识点:

  • 静态成员声明:static int count;(类内)
  • 静态成员初始化:int ClassName::count = 0;(类外)
  • 静态常量整型可以在类内初始化:static const int N = 10;

思路分析

静态成员 → 属于类 → 不属于对象 → 不能在构造函数中初始化 → 必须在类外初始化。

易错点

  • 在构造函数初始化列表中初始化静态成员——编译错误
  • 忘记在类外定义静态成员——链接错误
🔄 举一反三
  1. 下列关于静态成员的描述中,正确的是 ( )
    • A. 静态数据成员可以在构造函数中初始化
    • B. 静态数据成员必须在类外初始化
    • C. 静态数据成员每个对象各有一份
    • D. 静态成员函数可以访问非静态成员
    查看练习答案与解析

    答案:B 解析:静态数据成员属于类,必须在类外初始化(A 错误,B 正确);静态成员被所有对象共享(C 错误);静态成员函数没有 this 指针,不能直接访问非静态成员(D 错误)。

  1. 任何对象都可以使用 delete 运算符来释放。
查看答案与解析

答案:×

解析:delete 运算符只能用于释放通过 new 动态分配的对象。对于自动存储(栈上)的对象,不能使用 delete,它们会在离开作用域时自动销毁。

cpp
int* p = new int(10);
delete p;  // 正确:p 是 new 分配的

int x = 10;
delete &x;  // 错误!x 是栈上的变量

难度: ⭐ 考点: #delete运算符 #动态内存管理

💡 学习锦囊

📖 相关公式与知识点:

  • new 分配的内存必须用 delete 释放
  • new[] 分配的内存必须用 delete[] 释放
  • 栈上的局部变量自动管理,不需要 delete

思路分析

new ↔ delete 配对使用。不是 new 创建的,不能用 delete 释放。

🔄 举一反三
  1. 下列代码中,哪些是正确的?
    cpp
    int* p1 = new int(5);
    int* p2 = new int[10];
    int x = 3;
    • A. delete p1;
    • B. delete p2;
    • C. delete &x;
    • D. delete[] p2;
    查看练习答案与解析

    答案:A 和 D 解析p1new int 分配,delete p1 正确;p2new int[] 分配,必须用 delete[] p2(D 正确,B 错误);x 是栈变量,不能 delete(C 错误)。

  1. 派生类只继承基类中的公有成员和保护成员,而不继承私有成员。
查看答案与解析

答案:×

解析: 派生类继承了基类的所有成员,包括私有成员。只是私有成员在派生类中不可直接访问,但它们确实存在。派生类可以通过基类的公有或保护成员函数间接访问基类的私有成员。


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

💡 学习锦囊

📖 相关公式与知识点:

  • 派生类继承基类的所有成员(包括私有)
  • 私有成员在派生类中存在但不可直接访问
  • 可通过基类的公有/保护方法间接访问私有成员

思路分析

"继承"≠"可访问"。私有成员被继承了,但不可直接访问。

易错点

  • 认为"不继承私有成员"——实际是继承了但不可访问
🔄 举一反三
  1. 下列关于继承的描述中,正确的是 ( )
    • A. 派生类不继承基类的私有成员
    • B. 派生类继承基类的所有成员,但私有成员不可直接访问
    • C. 派生类可以直接访问基类的保护成员
    • D. 公有继承时,基类的保护成员在派生类中变为私有
    查看练习答案与解析

    答案:B 和 C 解析:B 正确,派生类继承所有成员,私有成员不可直接访问;C 正确,保护成员在派生类中可以访问;A 错误;D 错误,公有继承时保护成员仍为保护成员。

  1. 使用成员函数方法和使用友元函数的方法所定义的重载运算符函数的参数个数是不相同的。
查看答案与解析

答案:√

解析:

  • 成员函数方式:参数个数 = 运算符操作数个数 - 1(this 隐含第一个操作数)
  • 友元函数方式:参数个数 = 运算符操作数个数

以双目运算符 + 为例:

cpp
// 成员函数:1个参数
Complex operator+(const Complex& c);

// 友元函数:2个参数
friend Complex operator+(const Complex& a, const Complex& b);

难度: ⭐⭐ 考点: #运算符重载 #成员函数vs友元函数

💡 学习锦囊

📖 相关公式与知识点:

  • 成员函数重载:参数 = 操作数 - 1(隐含 this
  • 友元函数重载:参数 = 操作数
  • =()[]-> 只能用成员函数重载

思路分析

成员函数有 this → 少一个参数;友元函数没有 this → 多一个参数。

🔄 举一反三
  1. 用成员函数重载双目运算符 - 时,参数个数是多少?用友元函数呢?
    查看练习答案与解析

    答案:成员函数 1 个参数,友元函数 2 个参数 解析:双目运算符有 2 个操作数。成员函数隐含 this,只需 1 个参数;友元函数需要 2 个参数。

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

  1. 下列关于对象的描述中,错误的是 ( )
    • A. 对象是类的一个实例
    • B. 对象是属性和行为的封装体
    • C. 对象就是 C 语言中的结构变量
    • D. 对象是现实世界中客观存在的某种实体
查看答案与解析

答案:C

解析: C 语言的结构变量只包含数据,不包含行为(函数);C++ 的对象将数据和行为封装在一起。这是 C 结构体和 C++ 类的本质区别。


难度: ⭐ 考点: #对象 #C结构体与C++类的区别

💡 学习锦囊

📖 相关公式与知识点:

  • 类 = 数据 + 函数 + 访问控制
  • C 结构体 = 只有数据,没有函数
  • 对象 = 类的实例

思路分析

C 结构体 ≠ C++ 对象。结构体只有数据,对象有数据和行为。

🔄 举一反三
  1. C++ 中的 struct 和 class 的主要区别是什么?
    查看练习答案与解析

    答案:默认访问权限不同。struct 的成员默认是 public,class 的成员默认是 private。

  1. 下列关于创建一个 int 型变量的引用,正确的是 ( )
    • A. int a(3), &ra = a;
    • B. int a(3), &ra = &a;
    • C. double d(3.1); int &rd = d;
    • D. int a(3), ra = a;
查看答案与解析

答案:A

解析:

  • A 正确:&ra = a 声明 ra 为 a 的 int 引用
  • B 错误:&ra = &a 试图将 int* 赋给 int&
  • C 错误:int 引用不能绑定到 double 变量
  • D 错误:ra = a 是创建 int 变量,不是引用

难度: ⭐ 考点: #引用声明 #类型匹配

💡 学习锦囊

📖 相关公式与知识点:

  • 引用声明:类型& 引用名 = 变量名;
  • 引用必须初始化,且类型必须匹配

思路分析

引用声明格式:int& ra = a;& 在类型后面)。

🔄 举一反三
  1. const int& r = 10; 是否合法?
    查看练习答案与解析

    答案:合法 解析:const 引用可以绑定到常量,编译器会创建一个临时变量来存储 10,然后 r 绑定到该临时变量。非 const 引用不能绑定到常量。

  1. 下列关于设置函数默认的参数值的描述中,错误的是 ( )
    • A. 可对函数的部分参数或全部参数设置默认值
    • B. 在有函数说明时,默认值应设置在函数说明时,而不是定义时
    • C. 设置函数默认参数值时,只可用常量不可用含有变量的表达式
    • D. 设置函数参数默认值应从右向左设置
查看答案与解析

答案:C

解析: 默认参数值可以是常量,也可以是含有变量的表达式(如全局变量),只要在编译时能确定值即可。C 选项说"只可用常量"是错误的。


难度: ⭐⭐ 考点: #默认参数 #函数声明与定义

💡 学习锦囊

📖 相关公式与知识点:

  • 默认参数从右向左设置
  • 声明和定义分离时,默认值放在声明中
  • 默认值可以是常量、全局变量、表达式

思路分析

默认参数规则:右向左设、声明中写、可以是表达式。

🔄 举一反三
  1. 下列函数声明中,正确的是 ( )
    • A. void f(int a = 1, int b, int c = 3);
    • B. void f(int a, int b = 2, int c = 3);
    • C. void f(int a = 1, int b = 2, int c);
    • D. void f(int a, int b = 2, int c);
    查看练习答案与解析

    答案:B 解析:默认参数必须从右向左连续设置,不能有间隔。

  1. 下列选择重载函数的不同实现的判断条件中,错误的是 ( )
    • A. 参数类型不同
    • B. 参数个数不同
    • C. 参数顺序不同
    • D. 函数返回值不同
查看答案与解析

答案:D

解析: 仅返回值类型不同不能构成重载,因为调用时可能不使用返回值,编译器无法区分。


难度: ⭐ 考点: #函数重载 #重载条件

💡 学习锦囊

📖 相关公式与知识点:

  • 重载条件:同名 + 不同形参列表(类型/个数/顺序)
  • 返回值类型不能区分重载

思路分析

重载看参数,不看返回值。

🔄 举一反三
  1. 下列哪组函数能构成重载?
    • A. int f(int x);double f(int x);
    • B. void f(int x);void f(int x, int y);
    查看练习答案与解析

    答案:B 解析:B 参数个数不同,构成重载。A 仅返回值不同,不构成重载。

  1. 执行下列说明语句,其调用构造函数的次数为 ( )
cpp
A a[5], *p[2];

其中,A 是一个类名。

  • A. 5
  • B. 6
  • C. 7
  • D. 10
查看答案与解析

答案:A

解析:

  • A a[5]:对象数组,5 个对象,调用 5 次构造函数
  • A *p[2]:指针数组,2 个指针,不调用构造函数

难度: ⭐⭐ 考点: #对象数组 #指针数组 #构造函数调用

💡 学习锦囊

📖 相关公式与知识点:

  • A a[5]:对象数组,5 次构造
  • A *p[2]:指针数组,0 次构造

思路分析

区分"对象数组"和"指针数组":对象数组创建对象,指针数组只创建指针。

🔄 举一反三
  1. 执行 A *p = new A[3]; 后,构造函数被调用了几次?
    查看练习答案与解析

    答案:3 次 解析new A[3] 动态分配了 3 个 A 对象,每个调用一次构造函数。

  1. 下列描述中,错误的是 ( )
    • A. 基类的 protected 成员在 public 派生类中仍然是 protected 成员
    • B. 基类的 private 成员在 public 派生类中是不可访问的
    • C. 基类 public 成员在 private 派生类中是 private 成员
    • D. 基类 public 成员在 protected 派生类中仍是 public 成员
查看答案与解析

答案:D

解析: 基类 public 成员在 protected 派生类中变为 protected 成员,不是 public。

基类成员public 继承protected 继承private 继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可访问不可访问不可访问

难度: ⭐⭐ 考点: #继承方式 #访问控制

💡 学习锦囊

📖 相关公式与知识点:

  • public 继承:保持基类成员访问级别不变
  • protected 继承:基类 public 和 protected 变为 protected
  • private 继承:基类 public 和 protected 变为 private

思路分析

继承方式 = 派生类中基类成员的最高访问级别。

🔄 举一反三
  1. 在 protected 继承中,基类的 protected 成员在派生类中的访问级别是什么?
    查看练习答案与解析

    答案:protected 解析:protected 继承中,基类的 public 和 protected 成员都变为 protected。

  1. 派生类构造函数的成员初始化列表中,不能包含的初始化项是 ( )
    • A. 基类的构造函数
    • B. 基类的子对象
    • C. 派生类的子对象
    • D. 派生类自身的数据成员
查看答案与解析

答案:B

解析: 基类的子对象属于基类,应由基类的构造函数负责初始化,派生类不能直接初始化基类的子对象。每个类负责初始化自己的直接成员。


难度: ⭐⭐ 考点: #派生类构造函数 #初始化列表 #子对象初始化

💡 学习锦囊

📖 相关公式与知识点:

  • 初始化列表可包含:基类构造函数、派生类成员对象、派生类数据成员
  • 初始化列表不可包含:基类的成员对象(由基类构造函数负责)

思路分析

每个类只负责初始化自己的直接成员。基类的子对象由基类构造函数处理。

🔄 举一反三
  1. 派生类初始化列表中直接初始化基类的私有数据成员是否合法?
    查看练习答案与解析

    答案:不合法 解析:基类的私有成员在派生类中不可访问,不能在初始化列表中直接初始化。应通过调用基类构造函数来初始化。

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

答案:D

解析: C++ 中不可重载的运算符:. .* :: ?: sizeof typeid


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

💡 学习锦囊

📖 相关公式与知识点:

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

思路分析

记忆口诀:"点星域问大类型"——不可重载。

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

    答案:C 解析:赋值运算符 = 必须用成员函数重载。

  1. 下列的成员函数中,纯虚函数是 ( )
    • 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

🔄 举一反三
  1. virtual void f() {}virtual void f() = 0; 的区别是什么?
    查看练习答案与解析

    答案:前者是虚函数(类可以实例化),后者是纯虚函数(类成为抽象类,不能实例化)。

  1. 下列关于动态联编的描述中,错误的是 ( )
    • A. 动态联编是函数联编的一种方式,它是在运行时来选择联编函数的
    • B. 动态联编又可称为动态多态性,它是 C++ 语言中多态性的一种重要形式
    • C. 函数重载和运算符重载都属于动态联编
    • D. 动态联编只是用来选择虚函数的
查看答案与解析

答案:C

解析: 函数重载和运算符重载属于静态联编(编译时确定),不是动态联编。动态联编只涉及虚函数。


难度: ⭐⭐ 考点: #动态联编 #静态联编 #多态

💡 学习锦囊

📖 相关公式与知识点:

  • 静态联编(编译时):函数重载、运算符重载、模板
  • 动态联编(运行时):虚函数 + 基类指针/引用

思路分析

重载 = 静态联编;虚函数 = 动态联编。

🔄 举一反三
  1. 下列哪种情况属于动态联编?
    • A. 函数重载
    • B. 通过基类指针调用虚函数
    • C. 运算符重载
    • D. 模板实例化
    查看练习答案与解析

    答案:B 解析:只有通过基类指针/引用调用虚函数时才发生动态联编。

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

  1. 下列程序是求 100 之内的能被 7 整除的自然数之和。
cpp
#include <iostream>
int main()
{
    int sum;
    __(1)__
    for (int i(1); i <= 100; i++)
        if (__(2)__)
            sum += i;
    cout << sum << endl;
}
查看答案与解析

答案: (1) sum = 0; (2) i % 7 == 0

解析:

  • (1) 累加变量必须初始化为 0
  • (2) 判断整除:i % 7 == 0

难度: ⭐ 考点: #变量初始化 #取余运算

💡 学习锦囊

📖 相关公式与知识点:

  • 累加变量必须初始化为 0
  • 整除判断:n % m == 0

思路分析

累加求和 → 初始化 sum=0 → 循环判断整除 → 累加。

🔄 举一反三
  1. 如何修改程序,求 100 之内既能被 3 整除又能被 5 整除的自然数之和?
    查看练习答案与解析

    答案:将条件改为 if (i % 3 == 0 && i % 5 == 0)

  1. 完成下列类的定义。
cpp
class A
{
public:
    A() { a = 0; }
    __(3)__
    int Geta(__(4)__ &m);
private:
    int a;
};

int Geta(__(5)__ &m) { return m.a; }
查看答案与解析

答案: (3) ~A() {} (4) const A (5) const A

解析:

  • (3) 类应有析构函数 ~A() {}
  • (4)(5) Geta 函数参数为 const A&,const 引用传参,同类成员函数可访问私有成员

难度: ⭐⭐ 考点: #析构函数 #类外定义成员函数 #同类对象访问私有成员

💡 学习锦囊

📖 相关公式与知识点:

  • 同类对象的成员函数可以访问该类任何对象的私有成员
  • const A& 参数:避免拷贝,不修改参数

思路分析

类外定义 → 需要类名限定 → 同类可访问私有成员 → const 引用传参。

🔄 举一反三
  1. 为什么 Geta 函数可以访问 m.a(m 的私有成员)?
    查看练习答案与解析

    答案:因为 Geta 是类 A 的成员函数,C++ 规定同一个类的成员函数可以访问该类任何对象的私有成员。访问控制是类级别的,不是对象级别的。

  1. 完成下列程序,使得该程序输出结果如下:
7,8
4,5
6
cpp
#include <iostream>
using namespace std;

class B
{
public:
    B(int i, int j) { b1 = i; b2 = j; }
    void Print() { cout << b1 << ',' << b2 << endl; }
private:
    int b1, b2;
};

class A
{
public:
    __(6)__
    void Print() { __(7)__ cout << a << endl; }
private:
    B b;
    int a;
};

int main()
{
    B b(7, 8);
    b.Print();
    A a(4, 5, 6);
    a.Print();
}
查看答案与解析

答案: (6) A(int i, int j, int k) : b(i, j) { a = k; } (7) b.Print();

解析:

  • (6) A 构造函数接收 3 个参数,用前两个初始化 B 成员 b,第三个赋给 a
  • (7) A 的 Print 先调用 b.Print() 输出 4,5,再输出 a=6

难度: ⭐⭐ 考点: #成员对象初始化 #初始化列表

💡 学习锦囊

📖 相关公式与知识点:

  • 成员对象必须在初始化列表中初始化
  • 构造顺序:成员对象先于包含类构造

思路分析

A 包含 B → A 构造时需初始化 B → 初始化列表 b(i, j) → A 的 Print 调用 b.Print()。

🔄 举一反三
  1. 如果 B 类没有默认构造函数,A 类的构造函数必须怎样写?
    查看练习答案与解析

    答案:必须在初始化列表中显式调用 B 的带参构造函数初始化 b,否则编译错误。

  1. 完成下列程序,使得该程序输出结果如下:
number of instance:1
number of instance:2
number of instance:1
number of instance:0
cpp
#include <iostream>
using namespace std;

class Process
{
    static int instance;
public:
    Process() { __(8)__ }
    ~Process() { __(9)__ }
    static void print() { cout << "number of instance:" << instance << endl; }
};

__(10)__

int main()
{
    Process *p1, *p2;
    p1 = new Process; p1->print();
    p2 = new Process; p2->print();
    delete p1; Process::print();
    delete p2; Process::print();
    return 0;
}
查看答案与解析

答案: (8) instance++; (9) instance--; (10) int Process::instance = 0;

解析:

  • (8) 构造函数中 instance 加 1
  • (9) 析构函数中 instance 减 1
  • (10) 静态成员在类外初始化为 0

难度: ⭐⭐ 考点: #静态成员 #对象计数 #构造与析构

💡 学习锦囊

📖 相关公式与知识点:

  • 对象计数模式:构造 +1,析构 -1
  • 静态成员必须在类外初始化

思路分析

对象计数模式:构造 +1,析构 -1,静态成员存储计数。

🔄 举一反三
  1. 如果将 Process *p1, *p2; 改为 Process p1, p2;(栈上创建),程序输出结果是否相同?
    查看练习答案与解析

    答案:相同 解析:栈上创建对象同样调用构造函数(instance++),离开作用域时自动调用析构函数(instance--)。

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

  1. 阅读该程序,给出程序的输出结果。(5 分)
cpp
#include <iostream>

int add(int, int = 5);

int main()
{
    int a(8);
    cout << "sum1=" << add(a) << endl;
    cout << "sum2=" << add(a, add(a)) << endl;
    cout << "sum3=" << add(a, add(a, add(a))) << endl;
}

int add(int i, int j)
{
    return i + j;
}
查看答案与解析

答案:

sum1=13
sum2=21
sum3=29

解析:

  • add(a) = add(8, 5) = 13
  • add(a, add(a)) = add(8, add(8,5)) = add(8, 13) = 21
  • add(a, add(a, add(a))) = add(8, add(8, add(8,5))) = add(8, add(8,13)) = add(8, 21) = 29

难度: ⭐⭐ 考点: #默认参数 #函数嵌套调用

💡 学习锦囊

📖 相关公式与知识点:

  • 默认参数在调用时自动填充
  • 嵌套调用从内向外计算

思路分析

从内向外逐层计算。

🔄 举一反三
  1. 如果将默认参数改为 int j = 3sum2 的值是多少?
    查看练习答案与解析

    答案add(8, add(8, 3)) = add(8, 11) = 19

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

class A
{
public:
    A() { a1 = a2 = 0; cout << "Default constructor called." << endl; }
    A(int i, int j);
    ~A() { cout << "Destructor called." << endl; }
    void Print() { cout << "a1=" << a1 << ",a2=" << a2 << endl; }
private:
    int a1, a2;
};

A::A(int i, int j)
{
    a1 = i; a2 = j;
    cout << "Constructor called." << endl;
}

int main()
{
    A a, b(5, 8);
    a.Print();
    b.Print();
}
查看答案与解析

答案:

Default constructor called.
Constructor called.
a1=0,a2=0
a1=5,a2=8
Destructor called.
Destructor called.

解析:

  • A a;:默认构造,输出 Default constructor called.,a1=0, a2=0
  • A b(5, 8);:带参构造,输出 Constructor called.,a1=5, a2=8
  • a.Print();:输出 a1=0,a2=0
  • b.Print();:输出 a1=5,a2=8
  • 析构逆序:先 b 后 a,各输出 Destructor called.

难度: ⭐⭐ 考点: #构造函数 #析构函数 #构造与析构顺序

💡 学习锦囊

📖 相关公式与知识点:

  • 析构顺序与构造顺序相反
  • 局部对象在离开作用域时自动析构

思路分析

创建对象 → 匹配构造函数 → 离开作用域 → 逆序析构。

🔄 举一反三
  1. 如果在 main 中添加 A c = b;,会输出什么?
    查看练习答案与解析

    答案:调用默认拷贝构造函数(不输出构造信息),析构时多一个 Destructor called.

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

class A
{
public:
    A(int i, int j) { a1 = i; a2 = j; }
    void Print() { cout << a1 << ',' << a2 << endl; }
private:
    int a1, a2;
};

class B
{
public:
    B() : b(0) {}
    B(int i) : b(i) {}
    void Print() { cout << b << endl; }
private:
    int b;
};

class C : public A
{
public:
    C(int i, int j, int k, int l) : A(i, j), c(l) {}
    void Print()
    {
        A::Print();
        b.Print();
        cout << c << endl;
    }
private:
    B b;
    int c;
};

int main()
{
    A a(7, 8);
    a.Print();
    B b(9);
    b.Print();
    C c(4, 5, 6, 7);
    c.Print();
}
查看答案与解析

答案:

7,8
9
4,5
0
7

解析:

  • A a(7,8); a.Print(); → 输出 7,8
  • B b(9); b.Print(); → 输出 9
  • C c(4,5,6,7);:A(4,5),成员 b 调用默认构造(b=0),c=7。注意参数 k=6 未使用!
  • c.Print()A::Print()4,5b.Print()0cout << c7

难度: ⭐⭐⭐ 考点: #继承 #组合 #初始化列表 #成员对象默认构造

💡 学习锦囊

📖 相关公式与知识点:

  • 未在初始化列表中出现的成员对象,调用默认构造函数
  • 作用域限定符 A::Print() 调用基类版本

思路分析

C 的构造:A(4,5) → b 默认构造(b=0) → c=7。参数 k=6 未使用。

易错点

  • 认为参数 k=6 会赋给成员 b——实际 b 调用的是默认构造函数
🔄 举一反三
  1. 如果将 C 的构造函数改为 C(int i, int j, int k, int l) : A(i, j), b(k), c(l) {},输出结果是什么?
    查看练习答案与解析

    答案7,8 / 9 / 4,5 / 6 / 7(b.Print() 输出 6)

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

class B
{
public:
    virtual void fun1() { cout << "B::fun1()." << endl; }
    virtual void fun2() { cout << "B::fun2()." << endl; }
    void fun3() { cout << "B::fun3()." << endl; }
    void fun4() { cout << "B::fun4()." << endl; }
};

class D : public B
{
public:
    void fun1() { cout << "D::fun1()." << endl; }
    void fun2() { cout << "D::fun2()." << endl; }
    void fun3() { cout << "D::fun3()." << endl; }
    void fun4() { cout << "D::fun4()." << endl; }
};

int main()
{
    B* pb;
    D d;
    pb = &d;
    pb->fun1();
    pb->fun2();
    pb->fun3();
    pb->fun4();
}
查看答案与解析

答案:

D::fun1().
D::fun2().
B::fun3().
B::fun4().

解析:

函数基类是否 virtual绑定方式调用版本
fun1动态D::fun1()
fun2动态D::fun2()
fun3静态B::fun3()
fun4静态B::fun4()

难度: ⭐⭐⭐ 考点: #虚函数 #动态绑定 #静态绑定

💡 学习锦囊

📖 相关公式与知识点:

  • 虚函数 → 动态绑定(运行时根据对象类型决定)
  • 非虚函数 → 静态绑定(编译时根据指针类型决定)

思路分析

判断基类中是否为 virtual → 是则动态绑定 → 否则静态绑定。

易错点

  • 认为派生类中重写了就一定是多态——必须基类声明 virtual
🔄 举一反三
  1. 如果将 void fun3() 也加上 virtualpb->fun3() 的输出是什么?
    查看练习答案与解析

    答案D::fun3().(动态绑定,调用 D 的版本)

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

class C
{
    friend void print(C c);
public:
    C(int i) : x(i) {}
    C() : x(0) { cout << "Default constructor called." << x << endl; }
    C(const C& c) { x = c.x; cout << "Copy constructor called." << x << endl; }
    ~C() { cout << "Destructor called." << x << endl; }
private:
    int x;
};

void print(C c) { cout << c.x << endl; }

int main()
{
    C p[4];
    int n = 1;
    for (int i = 0; i < 4; i++)
        p[i] = C(n++);
    for (i = 0; i < 4; i++)
        print(p[i]);
}
查看答案与解析

答案:

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
Copy constructor called.1
1
Destructor called.1
Copy constructor called.2
2
Destructor called.2
Copy constructor called.3
3
Destructor called.3
Copy constructor called.4
4
Destructor called.4
Destructor called.1
Destructor called.2
Destructor called.3
Destructor called.4

解析:

第一步:C p[4]; 创建 4 个 C 对象,调用默认构造函数,各输出 Default constructor called.0(4 次)

第二步:for (i=0; i<4; i++) p[i] = C(n++); 每次循环:

  • C(n++) 创建临时对象(带参构造,无输出)
  • 赋值给 p[i](赋值运算,无输出)
  • 临时对象析构,输出 Destructor called.n(n 为 1,2,3,4)

此时 p[0].x=1, p[1].x=2, p[2].x=3, p[3].x=4

第三步:for (i=0; i<4; i++) print(p[i]); 每次调用 print(p[i])

  • 值传递参数,调用拷贝构造函数,输出 Copy constructor called.x
  • 输出 c.x 的值
  • 参数对象析构,输出 Destructor called.x

第四步:main 结束 p[4] 数组析构(逆序),输出 Destructor called.4 Destructor called.3 Destructor called.2 Destructor called.1


难度: ⭐⭐⭐ 考点: #构造函数 #拷贝构造函数 #析构函数 #值传递 #临时对象

💡 学习锦囊

📖 相关公式与知识点:

  • 值传递参数会调用拷贝构造函数
  • 临时对象在语句结束时析构
  • 赋值操作不调用拷贝构造(调用赋值运算符)
  • 数组析构逆序

思路分析

注意每个对象的生命周期:默认构造 → 赋值(临时对象析构)→ 值传递(拷贝构造+析构)→ main 结束(数组析构)。

易错点

  • 忘记值传递会调用拷贝构造函数
  • 忘记临时对象在赋值后立即析构
  • 混淆赋值和拷贝构造
🔄 举一反三
  1. 如果将 void print(C c) 改为 void print(C& c),输出会有什么变化?
    查看练习答案与解析

    答案:不再调用拷贝构造函数和参数析构。每次 print 调用只输出 c.x 的值,没有 Copy constructor called. 和对应的 Destructor called.

五、编程题(每题 10 分,共计 20 分)

  1. 按下列要求编程:定义一个描述矩形的类 Rectangle,包括的数据成员有宽(width)和长(length),并实现如下功能函数: (1)矩形对象初始化; (2)计算矩形周长; (3)计算矩形面积; (4)改变矩形大小。
查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

class Rectangle
{
private:
    double width, length;

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

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

    double area()
    {
        return width * length;
    }

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

int main()
{
    Rectangle r(3, 4);
    cout << "周长: " << r.perimeter() << endl;
    cout << "面积: " << r.area() << endl;
    r.resize(5, 6);
    cout << "改变后周长: " << r.perimeter() << endl;
    cout << "改变后面积: " << r.area() << endl;
    return 0;
}

解析:

第一步:设计数据成员 矩形需要宽(width)和长(length),使用 double 类型。

第二步:实现构造函数 带默认参数的构造函数:Rectangle(double w = 0, double l = 0)

第三步:实现各功能函数

  • 周长 = 2 × (宽 + 长)
  • 面积 = 宽 × 长
  • 改变大小:修改 width 和 length

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

💡 学习锦囊

📖 相关公式与知识点:

  • 类设计步骤:数据成员 → 构造函数 → 功能函数
  • 周长公式:P = 2(w + l)
  • 面积公式:A = w × l

思路分析

类的设计步骤:确定数据成员 → 设计构造函数 → 实现各功能函数 → 编写主函数测试。

🔄 举一反三
  1. 为 Rectangle 类添加一个成员函数 bool isSquare(),判断矩形是否为正方形。
    查看练习答案与解析

    答案

    cpp
    bool isSquare()
    {
        return width == length;
    }

    解析:正方形的宽和高相等。注意浮点数比较在实际应用中应考虑精度误差。

  1. String 是一个用于存储字符串的类。根据下列 String 类定义,编程完成 String 类的具体实现:
cpp
#include <iostream>
using namespace std;

class String
{
    friend ostream& operator<<(ostream& out, const String& s);
private:
    char* str;
public:
    String(char s[]);
    ~String();
    String(const String& s);
    String& operator=(const String& s);
    char& operator[](int index);
};
查看答案与解析

答案:

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

String::String(char s[])
{
    str = new char[strlen(s) + 1];
    strcpy(str, s);
}

String::~String()
{
    delete[] str;
}

String::String(const String& s)
{
    str = new char[strlen(s.str) + 1];
    strcpy(str, s.str);
}

String& String::operator=(const String& s)
{
    if (this == &s)
        return *this;
    delete[] str;
    str = new char[strlen(s.str) + 1];
    strcpy(str, s.str);
    return *this;
}

char& String::operator[](int index)
{
    return str[index];
}

ostream& operator<<(ostream& out, const String& s)
{
    out << s.str;
    return out;
}

解析:

第一步:分析类的结构 String 类包含 char* str 指针成员,需要实现深拷贝。

第二步:各函数实现要点

函数关键点
构造函数new char[strlen(s)+1] 分配内存,strcpy 复制
析构函数delete[] str 释放内存
拷贝构造函数深拷贝:分配新内存并复制内容
operator=自赋值检查 → 释放旧内存 → 分配新内存 → 复制
operator[]返回 str[index] 的引用
operator<<友元函数,输出字符串

第三步:注意事项

  • strlen 不包含 \0,分配内存需要 +1
  • 赋值运算符必须检查自赋值
  • 深拷贝确保每个对象有独立的内存

难度: ⭐⭐⭐ 考点: #深拷贝 #运算符重载 #动态内存管理 #字符串处理

💡 学习锦囊

📖 相关公式与知识点:

  • Rule of Three:拷贝构造 + 赋值运算符 + 析构函数
  • strlen(s) + 1:+1 为 \0 终止符预留空间
  • strcpy:复制包括 \0 的完整字符串
  • 赋值运算符:自赋值检查 → 释放旧资源 → 分配新资源 → 复制

思路分析

类有指针成员 → Rule of Three → 深拷贝 + 深赋值 + 析构释放。

易错点

  • 忘记 +1\0 预留空间
  • 忘记自赋值检查
  • operator= 忘记释放旧内存导致内存泄漏
🔄 举一反三
  1. 为 String 类添加一个成员函数 int length(),返回字符串长度。
    查看练习答案与解析

    答案

    cpp
    int length()
    {
        return strlen(str);
    }

    解析:使用 strlen 获取字符串长度(不包含 \0)。

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