C++ 期末考试模拟试卷考试说明:本试卷共 100 分,考试时间 90 分钟。包含单项选择题、多项选择题、判断题、程序阅读题和编程题。难度适中,覆盖 C++ 核心基础知识点。


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

  1. 在 C++ 中,函数的参数传递方式不包括以下哪一种?
    A. 值传递
    B. 引用传递 C. 指针传递
    D. 直接传递
    答案:D
    解析:C++ 中函数参数传递的基本方式包括值传递、引用传递和指针传递(指针传递本质上是值传递地址的副本)。“直接传递”并非标准术语。

  2. 下列哪个标识符是合法的?
    A. 2var
    B. myvar
    C. _isw
    D. float
    答案:C
    解析:C++ 标识符命名规则:以字母或下划线开头,后续字符可以是字母、数字或下划线。 2var 以数字开头不合法;my-var 包含非法字符 -float 是关键字,不能作为用户标识符。

  3. 已知 char* const name = “chen”;,以下说法正确的是?
    A. name 指向的字符串内容可以修改 B. name 指针本身可以指向其他地址
    C. 可以使用 name = “wang”; 进行赋值
    D. name 是一个常量指针,指针本身的值(指向的地址)不可变
    答案:D
    解析char* const name 声明了一个常量指针,指针本身(存储的地址)是常量,初始化后不能再指向其他地址,但其所指向的字符串内容(如果内存区域可写)理论上可以修改。不过,用字符串字面量初始化指针,该字符串通常存储在只读区,试图修改内容可能导致运行时错误。

  4. 关于函数重载,下列说法错误的是?
    A. 重载函数的函数名必须相同
    B. 重载函数必须有不同的返回值类型 C. 重载函数必须有不同的参数列表(类型、个数或顺序)
    D. 构造函数可以重载 答案:B
    解析:函数重载的依据是函数签名,即函数名和参数列表。仅返回值类型不同不足以构成重载,编译器无法区分。

  5. 以下关于内联函数(inline)的描述,正确的是?
    A. 内联函数适用于函数体较小、调用频繁的场景,旨在减少函数调用的开销 B. 包含复杂循环或递归的函数适合声明为内联
    C. 内联函数的定义必须在调用点之前
    D. 使用 inline 关键字一定能保证编译器进行内联展开 答案:A
    解析:内联是对编译器的建议,旨在用函数体替换调用点以提升效率,适用于简单、短小的函数。对于复杂的函数,编译器可能忽略内联建议。

  6. 在 C++ 中,用于定义引用的关键字是?
    A. ref
    B. &
    C. pointer
    D. deref
    答案:B
    解析:引用是变量的别名,在声明时使用 & 符号,例如 int &ref = var;

  7. 当派生类对象被一个基类指针删除,且基类中没有虚析构函数时,会导致?
    A. 仅派生类的析构函数被调用
    B. 仅基类的析构函数被调用 C. 基类和派生类的析构函数都会被调用 D. 程序行为是未定义的 答案:B
    解析:如果基类析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,派生类的析构函数不会被调用,可能导致资源泄漏。这是多态行为的一部分,基类析构函数应为虚函数以确保正确清理。

  8. 下列表达式中,值为 0 的是?
    A. 3 % 5
    B. 3 / 5.0
    C. 3 / 5
    D. 5 / 3
    答案:C
    解析:在 C++ 中, 3 / 5 是整数除法,结果取整为 0。 3 % 5 是取模,结果为 3。 3 / 5.0 是浮点数除法,结果为 0.6。

  9. 以下哪个关键字用于异常处理中抛出异常?
    A. try
    B. catch
    C. throw
    D. finally
    答案:C
    解析throw 用于主动抛出一个异常。try 定义可能抛出异常的代码块,catch 用于捕获和处理异常。C++ 标准中没有 finally 关键字。

  10. this 指针的含义是?
    A. 指向当前类的指针 B. 指向当前对象的指针
    C. 指向基类对象的指针
    D. 指向派生类对象的指针
    答案:B
    解析this 是一个隐含的指针,在每个非静态成员函数内部可用,它指向调用该成员函数的那个对象本身。


二、 多项选择题(每题 3 分,共 15 分。全部选对得满分,漏选得部分分,错选不得分)

  1. 在 C++ 中,以下哪些是有效的基本数据类型?
    A. int
    B. float
    C. double
    D. char
    E. string
    答案:A, B, C, D
    解析int(整型)、float(单精度浮点型)、double(双精度浮点型)、char(字符型)是 C++ 内置的基本数据类型。string 是标准库 (std) 中定义的类类型,并非基本数据类型。

  2. 关于拷贝构造函数,以下哪些情况会被调用?
    A. 用一个对象初始化另一个同类型对象时(如 MyClass obj2 = obj1;
    B. 对象作为函数参数,以值传递方式传入函数时
    C. 对象作为函数返回值,以值传递方式从函数返回时 D. 通过赋值运算符 = 给已存在对象赋值时
    答案:A, B, C
    解析:拷贝构造函数用于创建一个新对象并将其初始化为同类型另一对象的副本。场景 A 是直接初始化;场景 B 和 C 涉及对象的复制(传值)。场景 D 调用的是赋值运算符重载函数 (operator=),而非拷贝构造函数。

  3. 以下关于 C++ 继承的描述,正确的有?
    A. 派生类可以继承基类的所有成员 B.派生类可以添加新的成员变量和成员函数
    C. private 继承下,基类的 publicprotected 成员在派生类中变为 private
    D. 派生类不能直接访问基类的 private 成员 答案:B, C, D
    解析:B 正确,继承的核心目的之一是扩展功能。C 正确,描述了私有继承的访问权限变化。D 正确,基类的私有成员对派生类不可见。A 错误,基类的私有成员和构造函数/析构函数等不被继承。

  4. 下列哪些是 C++ 标准模板库 (STL) 的组成部分?
    A. 容器 (Containers)
    B. 迭代器 (Iterators)
    C. 算法 (Algorithms)
    D. 函数对象 (Functors)
    答案:A, B, C, D
    解析:STL 的六大组件包括:容器、迭代器、算法、函数对象、适配器和分配器。题目中列出的四项都是其核心组件。

  5. 以下关于动态内存管理的说法,正确的有?
    A. newdelete 必须配对使用 B. new[]delete[] 必须配对使用 C. 使用 delete 释放 new[] 分配的数组是安全的
    D. 未释放动态分配的内存会导致内存泄漏 答案:A, B, D
    解析:A 和 B 是 C++ 动态内存管理的基本原则。C 错误,用 delete 释放 new[] 分配的数组,或者用 delete[] 释放 new 分配的单个对象,行为是未定义的,通常会导致程序崩溃。D 正确,动态分配的内存必须手动释放,否则在程序结束前会一直占用,造成内存泄漏。


三、 判断题(每题 1 分,共 10 分。正确打√,错误打×)

  1. 在 C++ 中,const 对象只能调用 const 成员函数。 ( )
    解析const 对象保证其自身状态不被修改,因此只能调用那些承诺不修改对象状态的 const 成员函数。

  2. 虚函数必须是类的成员函数。 ( )
    解析:虚函数是实现运行时多态的机制,它依赖于对象的虚函数表,因此只能是类的非静态成员函数。

  3. 一个类可以有多个析构函数。 ( × )
    解析:析构函数是特殊的成员函数,每个类有且只有一个,其名称为类名前加 ~,且无参数、无返回值。

  4. 引用在定义后可以改变其绑定的变量。 ( × )
    解析:引用在定义时必须初始化,且一旦绑定到一个变量,在其生命周期内就不能再绑定到其他变量。

  5. switch 语句的 case 标签后的表达式必须是整型或枚举类型。 ( )
    解析:这是 C++ 语法规定,switch 的控制表达式和 case 标签值必须是整型或枚举类型。

  6. 静态成员函数没有 this 指针。 ( )
    解析:静态成员函数属于类而非特定对象,因此它没有指向调用对象的 this 指针。

  7. 函数模板的实例化是由编译器在编译时完成的。 ( )
    解析:模板是编译期多态的工具。编译器根据调用时提供的具体类型,在编译时生成对应的函数代码。

  8. #define PI 3.14159 定义的常量具有类型安全检查。 ( × )
    解析#define 是预处理指令,进行简单的文本替换,没有类型信息,也不进行类型检查。应优先使用 const 常量。

  9. 派生类对象的地址可以隐式转换为基类指针。 ( )
    解析:这是公有继承下的“向上转型”(upcasting),是安全的,也是实现多态的基础。

  10. std::vector 在尾部插入元素的时间复杂度总是 O(1)。 ( × )
    解析std::vector 在尾部插入元素(push_back)的平摊时间复杂度是 O(1)。但在需要重新分配内存(扩容)的那次插入操作中,时间复杂度是 O(n),因为它需要将原有元素复制到新内存。


四、 程序阅读与分析题(共 25 分)

  1. (6分) 分析以下程序的运行结果,并解释涉及的多态行为。

    #include <iostream>
    using namespace std;
    
    class Base {
    public:
        virtual void show() { cout << "Base show" << endl; }
        void print() { cout << "Base print" << endl; }
    };
    
    class Derived : public Base {
    public:
        void show() override { cout << "Derived show" << endl; }
        void print() { cout << "Derived print" << endl; }
    };
    
    int main() {
        Base* ptr = new Derived();
        ptr->show();
        ptr->print();
        delete ptr;
        return 0;
    }
    

    答案

    Derived show Base print ```
    **解析**:
    *   `ptr->show()`:`show()` 是虚函数。虽然 `ptr` 是 `Base*` 类型,但它实际指向一个 `Derived` 对象。根据 C++ 的多态机制,通过基类指针调用虚函数时,会调用指针所指向的**实际对象类型**(`Derived`)的版本。因此输出 “Derived show”。
    *   `ptr->print()`:`print()` 是**非虚函数**。对于非虚函数,调用在**编译时**根据指针的**静态类型**(`Base*`)确定。因此,无论 `ptr` 实际指向什么,都调用 `Base::print()`,输出 “Base print”。
    
    
  2. (9分) 阅读以下程序,请填充空白处,使程序能统计命令行第一个参数中数字字符的个数。

    #include <cstring>
    using namespace std;
    
    int main(int argc, char* argv[]) {
        if (argc < 2) {
            cout << "Usage: " << argv[0] << " <string>" << endl;
            return 1;
        }
    
        char* str = argv[1];
        int count = 0;
    
        for (int i = 0; str[i] != '\0'; ++i) {
            if (str[i] >= '0' && str[i] <= '9') {
                ___________; // (1) 计数增加 }
        }
    
        cout << "The number of digits is: " << ___________ << endl; // (2) 输出结果
        return 0;
    }
    

    答案
    (1) count++count = count + 1
    (2) count
    解析

    • main(int argc, char* argv[]) 是带命令行参数的 main 函数标准写法,argc 是参数个数,argv 是参数字符串数组。
    • 程序首先检查是否有足够的参数(argc < 2)。
    • argv[1] 是第一个用户输入的参数(字符串)。
    • 循环遍历字符串直到结束符 \0
    • 判断字符是否为数字(ASCII 码在 '0''9' 之间)。
    • 空(1)处,当遇到数字字符时,计数器 count 应加1。
    • 空(2)处,输出最终的统计结果 count
  3. (10分) 分析以下涉及构造函数、拷贝构造和赋值的程序,写出输出结果。

    #include <iostream>
    using namespace std;
    
    class Test {
        int id;
    public:
        Test(int i) : id(i) { cout << "Obj " << id << " created." << endl; }
        Test(const Test& t) : id(t.id + 10) { cout << "Obj " << id << " copied from " << t.id << "." << endl; }
        Test& operator=(const Test& t) {
            id = t.id + 100;
            cout << "Obj assigned from " << t.id << ", now id is " << id << "." << endl;
            return *this;
        }
        ~Test() { cout << "Obj " << id << " destroyed." << endl; }
    };
    
    int main() {
        Test a(1);
        Test b = a; // (1)
        Test c(2);
        c = a;      // (2)
        return 0;
    }
    

    答案

    Obj 1 created.
    Obj 11 copied from 1.
    Obj 2 created.
    Obj 101 assigned from 1, now id is 101.
    Obj 101 destroyed.
    Obj 11 destroyed.
    Obj 1 destroyed.
    

    解析

    1. Test a(1);:调用带参构造函数,创建对象 aid=1
    2. Test b = a;:这是初始化,调用拷贝构造函数。根据拷贝构造函数定义,新对象的 id = a.id + 10 = 11
    3. Test c(2);:调用带参构造函数,创建对象 cid=2
    4. c = a;:这是赋值操作,调用赋值运算符重载函数。根据定义,c.id = a.id + 100 = 101
    5. 程序结束,对象按创建相反的顺序析构:c (id=101), b (id=11), a (id=1)。

五、 编程题(共 30 分)

  1. (15分) 编写一个简单的 String 类,要求实现以下功能:

    • 使用 char* 动态分配内存来存储字符串。
    • 实现默认构造函数、带参构造函数、拷贝构造函数、赋值运算符重载和析构函数(深拷贝)。
    • 实现一个 getLength() 成员函数,返回字符串长度。
    • main 函数中演示深拷贝与浅拷贝的区别。
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class MyString {
    private:
        char* m_data; // 指向字符串的指针
        int m_size;   // 字符串长度(可选,方便计算)
    public:
        // 1. 默认构造函数
        MyString() : m_data(nullptr), m_size(0) {
            cout << "Default Constructor called." << endl;
        }
    
        // 2. 带参构造函数 (从C风格字符串构造)
        MyString(const char* str) {
            cout << "Parameterized Constructor called for: \"" << str << "\"" << endl;
            if (str) {
                m_size = strlen(str);
                m_data = new char[m_size + 1]; // +1 for '\0'
                strcpy(m_data, str);
            } else {
                m_size = 0;
                m_data = nullptr;
            }
        }
    
        // 3. 拷贝构造函数 (深拷贝)
        MyString(const MyString& other) {
            cout << "Copy Constructor called (from id/size: " << other.m_size << ")" << endl;
            m_size = other.m_size;
            if (other.m_data) {
                m_data = new char[m_size + 1];
                strcpy(m_data, other.m_data);
            } else {
                m_data = nullptr;
            }
        }
    
        // 4. 赋值运算符重载 (深拷贝)
        MyString& operator=(const MyString& other) {
            cout << "Copy Assignment called (from id/size: " << other.m_size << ")" << endl;
            if (this != &other) { // 防止自赋值 // 释放原有内存
                delete[] m_data;
                // 分配新内存并复制内容
                m_size = other.m_size;
                if (other.m_data) {
                    m_data = new char[m_size + 1];
                    strcpy(m_data, other.m_data);
                } else {
                    m_data = nullptr;
                }
            }
            return *this; // 返回当前对象的引用 }
    
        // 5. 析构函数 ~MyString() {
            cout << "Destructor called for: ";
            if (m_data) {
                cout << "\"" << m_data << "\"";
            } else {
                cout << "(empty)";
            }
            cout << endl;
            delete[] m_data; // 释放动态内存
        }
    
        // 6. 获取字符串长度
        int getLength() const {
            return m_size;
        }
    
        // 7. 打印字符串 (辅助函数)
        void print() const {
            if (m_data) {
                cout << m_data;
            } else {
                cout << "(empty)";
            }
        }
    };
    
    int main() {
        cout << "=== Demonstrating Deep Copy ===" << endl;
        MyString str1("Hello");
        MyString str2 = str1; // 调用拷贝构造函数
        MyString str3;
        str3 = str1;          // 调用赋值运算符
    
        cout << "
    

str1: ";
str1.print();
cout << ", Length: " << str1.getLength() << endl;

    cout << "str2: ";
    str2.print();
    cout << ", Length: " << str2.getLength() << endl;

    cout << "str3: ";
    str3.print();
    cout << ", Length: " << str3.getLength() << endl;

    // 修改 str2 的内容,验证深拷贝
    // 注意:为了演示,这里直接操作私有成员,实际应提供修改接口。
    // 假设我们通过某种方式修改了str2的内部数据...
    cout << "

(假设修改了str2的内部数据,str1和str3应不受影响)" << endl;

    cout << "

=== End of main ===" << endl;
return 0;
// str3, str2, str1 依次析构 }
```
解析:本题核心是实现“深拷贝”。拷贝构造函数和赋值运算符重载中,必须为 m_data 重新分配内存并复制字符串内容,而不是简单地复制指针值(浅拷贝)。这样,每个 MyString 对象都拥有自己独立的字符串副本,修改其中一个不会影响其他对象。析构函数必须使用 delete[] 释放动态数组。

  1. (15分) 使用 STL 容器和算法完成以下任务:
  • 创建一个 std::vector<int>,包含以下数字:{5, 2, 8, 1, 9, 3, 7, 4, 6, 2, 8}
    * 使用 std::sort 对向量进行升序排序,并输出排序后的结果。
    * 使用 std::uniquevector::erase 移除连续的重复元素,并输出去重后的结果。(注意:std::unique 只移除相邻的重复项,因此通常先排序)
    * 使用 std::find 查找数字 7 是否在向量中,并输出查找结果。
```cpp
#include <iostream>
#include <vector>
#include <algorithm> // for sort, unique, find
using namespace std;

void printVector(const vector<int>& vec, const string& msg) {
    cout << msg;
    for (int num : vec) {
        cout << num << " ";
    }
    cout << endl;
}

int main() {
    // 1. 创建并初始化vector vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6, 2, 8};
    printVector(numbers, "Original vector: ");

    // 2. 排序 sort(numbers.begin(), numbers.end());
    printVector(numbers, "Sorted vector:   ");

    // 3. 移除连续重复元素 // unique 将不重复的元素移到前面,并返回新的逻辑结尾的迭代器 auto last = unique(numbers.begin(), numbers.end());
    // 使用 erase 删除从新结尾到原结尾的重复元素 numbers.erase(last, numbers.end());
    printVector(numbers, "Unique vector: ");

    // 4. 查找元素 int target = 7;
    auto it = find(numbers.begin(), numbers.end(), target);
    if (it != numbers.end()) {
        cout << "Number " << target << " found at position (index): "
             << distance(numbers.begin(), it) << endl;
    } else {
        cout << "Number " << target << " not found." << endl;
    }

    // 额外:计算元素个数和总和
    cout << "Size of unique vector: " << numbers.size() << endl;
    int sum = 0;
    for (int n : numbers) { sum += n; }
    cout << "Sum of elements: " << sum << endl;

    return 0;
}
```
**解析**:本题考察 STL 的基本应用。
*   `std::sort`:默认进行升序排序,可以接受自定义比较函数。
*   `std::unique`:移除**相邻**的重复元素,返回指向不重复序列末尾后一个位置的迭代器。它不改变容器大小,需要配合 `erase` 使用。
*   `std::find`:在给定范围内线性查找指定值,返回指向该元素的迭代器;若未找到,返回 `end()` 迭代器。
* `distance`:计算两个迭代器之间的距离(元素个数)。
*   范围 `for` 循环 (`for (int num : vec)`):C++11 特性,用于遍历容器。

参考来源

 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐