杰瑞科技汇

C面向对象程序设计教程答案哪里找?

C++基础与面向对象入门

这个模块是C++面向对象编程的基石,重点在于理解C++与C的区别以及基本语法。

C面向对象程序设计教程答案哪里找?-图1
(图片来源网络,侵删)

核心概念

  1. 从C到C++的扩展

    • cin / cout:替代 scanf / printf,类型更安全,使用更方便(<<>> 操作符)。
    • new / delete:替代 malloc / free,用于动态内存分配,new会自动调用构造函数,delete会自动调用析构函数。
    • 引用:变量的别名,必须在定义时初始化,不能为空,常用于函数参数,避免值拷贝,提高效率。
    • const关键字:定义常量,或修饰函数参数/返回值,承诺不修改其内容。
  2. 命名空间

    • 解决命名冲突问题,将全局标识符封装在不同的“命名空间”中。
    • 使用 using namespace std;using std::cout; 来简化调用。
  3. 函数重载

    • 同一作用域内,可以定义多个同名函数,但它们的参数列表(参数个数、类型、顺序)必须不同
    • 编译器会根据调用时传入的参数列表来决定调用哪个函数。

典型例题及代码解析

例题1:编写一个函数重载示例,分别实现两个整数和两个浮点数的加法。

C面向对象程序设计教程答案哪里找?-图2
(图片来源网络,侵删)
#include <iostream>
// 函数重载:两个整数相加
int add(int a, int b) {
    std::cout << "调用 int add(int, int)" << std::endl;
    return a + b;
}
// 函数重载:两个浮点数相加
double add(double a, double b) {
    std::cout << "调用 double add(double, double)" << std::endl;
    return a + b;
}
int main() {
    int result_int = add(10, 20);        // 编译器选择 int add(int, int)
    double result_double = add(3.14, 2.71); // 编译器选择 double add(double, double)
    std::cout << "整数和: " << result_int << std::endl;
    std::cout << "浮点数和: " << result_double << std::endl;
    return 0;
}

解析

  • add(10, 20) 传递的是两个 int 类型,所以匹配第一个 add 函数。
  • add(3.14, 2.71) 传递的是两个 double 类型,所以匹配第二个 add 函数。
  • 关键点:函数重载只看参数列表,与返回值类型无关。

类与对象

这是面向对象的核心,封装了数据和操作数据的方法。

核心概念

  1. :用户自定义的数据类型,是创建对象的蓝图,包含:
    • 成员变量:描述对象的属性。
    • 成员函数:描述对象的行为。
  2. 对象:类的实例,是实实在在的变量。
  3. 访问限定符
    • public:公有成员,可以在类的外部访问。
    • private:私有成员,只能在类的内部访问。封装的实现主要依靠将数据设为 private
    • protected:保护成员,在类的内部和其派生类中可以访问。
  4. 构造函数
    • 与类同名,没有返回值。
    • 在创建对象时由系统自动调用
    • 用于初始化对象的成员变量。
    • 如果没有显式定义,编译器会提供一个默认的构造函数(无参,函数体为空)。
  5. 析构函数
    • 在类名前加 符号,没有返回值,没有参数。
    • 在对象生命周期结束时(如离开作用域)由系统自动调用
    • 通常用于释放对象在构造函数中申请的资源(如动态内存)。

典型例题及代码解析

例题2:设计一个 Student 类,包含姓名、年龄和学号,并实现构造函数和显示信息的方法。

#include <iostream>
#include <string>
class Student {
private: // 数据成员设为私有,实现封装
    std::string name;
    int age;
    int id;
public: // 成员函数设为公有,提供外部接口
    // 构造函数,用于初始化
    Student(std::string n, int a, int i) {
        name = n;
        age = a;
        id = i;
        std::cout << "学生 " << name << " 的构造函数被调用" << std::endl;
    }
    // 析构函数
    ~Student() {
        std::cout << "学生 " << name << " 的析构函数被调用" << std::endl;
    }
    // 显示学生信息的方法
    void displayInfo() {
        std::cout << "姓名: " << name << ", 年龄: " << age << ", 学号: " << id << std::endl;
    }
};
int main() {
    {
        Student s1("张三", 20, 1001);
        s1.displayInfo();
    } // s1 在这里离开作用域,析构函数被调用
    Student s2("李四", 21, 1002);
    s2.displayInfo();
    return 0;
}
// 程序结束时,s2 的析构函数被调用

解析

C面向对象程序设计教程答案哪里找?-图3
(图片来源网络,侵删)
  • Student 类将 name, age, id 设为 private,外部代码无法直接修改它们,保证了数据的安全性。
  • 通过 publicStudent 构造函数和 displayInfo 方法,提供了安全的创建和访问对象的接口。
  • main 函数中,当对象 s1 离开其作用域()时,其析构函数自动执行。s2 的析构函数在 main 函数结束时执行。

面向对象三大特性

封装

  • 定义:将数据和操作数据的方法捆绑在一起,并对外部隐藏对象的内部细节。
  • 实现:通过 classpublic/private 访问限定符实现。
  • 好处:隐藏实现细节,提供清晰的接口,降低耦合度,提高安全性。

继承

  • 定义:允许一个类(子类/派生类)继承另一个类(父类/基类)的属性和方法。
  • 目的:实现代码复用,建立类之间的层次关系。
  • 语法class 子类 : 访问限定符 父类 { ... };
    • public 继承:最常用,父类的 public 成员在子类中为 publicprotected 成员在子类中为 protected
    • protected 继承:父类的 publicprotected 成员在子类中都变为 protected
    • private 继承:父类的 publicprotected 成员在子部中都变为 private
  • 基类构造函数调用:子类构造函数负责初始化自己的成员,同时必须显式或隐式地调用基类的构造函数来初始化从基类继承来的成员。

典型例题及代码解析

例题3:定义一个 Person 基类和一个 Student 派生类,Student 继承自 Person

#include <iostream>
#include <string>
// 基类
class Person {
protected: // 改为protected,以便派生类可以访问
    std::string name;
public:
    Person(std::string n) : name(n) {
        std::cout << "Person 构造函数" << std::endl;
    }
    void display() {
        std::cout << "姓名: " << name << std::endl;
    }
};
// 派生类
class Student : public Person { // public继承
private:
    int id;
public:
    // Student的构造函数必须调用Person的构造函数
    Student(std::string n, int i) : Person(n), id(i) { // 显式调用基类构造函数
        std::cout << "Student 构造函数" << std::endl;
    }
    void displayStudentInfo() {
        display(); // 调用基类的公有方法
        std::cout << "学号: " << id << std::endl;
    }
};
int main() {
    Student s("王五", 1003);
    s.displayStudentInfo();
    return 0;
}

解析

  • Student 类通过 public 继承获得了 Person 类的 name 属性和 display 方法。
  • Student 的构造函数通过初始化列表 Person(n) 调用了 Person 的构造函数,这是初始化基类部分的必要步骤。
  • Student 对象可以调用 display() 方法,因为它继承自 Person

多态

  • 定义:不同的对象对同一消息(函数调用)做出不同的响应。
  • 条件
    1. 必须有继承关系
    2. 子类必须重写(override)父类的虚函数。
    3. 必须通过基类指针或引用来调用虚函数。
  • 关键字
    • virtual:在基类中声明函数为虚函数,表示这个函数可以在派生类中被重写。
    • override (C++11):在派生类中使用,明确表示要重写基类的虚函数,如果基类没有该函数,编译器会报错,增加代码健壮性。
    • final:防止类被继承,或防止虚函数被进一步重写。

典型例题及代码解析

例题4:使用多态来处理不同的几何图形(如圆形、矩形)。

#include <iostream>
// 基类
class Shape {
public:
    // 声明虚函数,= 0 是纯虚函数,使这个类成为抽象类
    // 抽象类不能被实例化,只能作为基类
    virtual double getArea() const = 0; 
    // 虚析构函数,确保通过基类指针删除对象时,派生类的析构函数也能被调用
    virtual ~Shape() {
        std::cout << "Shape 析构函数" << std::endl;
    }
};
// 派生类:圆形
class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    // 重写基类的纯虚函数
    double getArea() const override {
        return 3.14159 * radius * radius;
    }
    ~Circle() {
        std::cout << "Circle 析构函数" << std::endl;
    }
};
// 派生类:矩形
class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    // 重写基类的纯虚函数
    double getArea() const override {
        return width * height;
    }
    ~Rectangle() {
        std::cout << "Rectangle 析构函数" << std::endl;
    }
};
// 使用基类指针的函数,体现多态
void printArea(const Shape* shape) {
    std::cout << "面积为: " << shape->getArea() << std::endl;
}
int main() {
    Circle c(5.0);
    Rectangle r(4.0, 6.0);
    // 基类指针指向派生类对象
    Shape* shape1 = &c;
    Shape* shape2 = &r;
    printArea(shape1); // 调用 Circle::getArea()
    printArea(shape2); // 调用 Rectangle::getArea()
    // 必须手动释放内存,因为 new 和 delete 要配对使用
    delete shape1;
    delete shape2;
    return 0;
}

解析

  • Shape 是一个抽象类,因为它包含纯虚函数 getArea() = 0,我们不能创建 Shape 的对象。
  • CircleRectangle 继承自 Shape重写getArea() 函数。
  • printArea 函数接收一个 Shape 类型的指针,当传入 &c 时,shape->getArea() 实际调用的是 Circle::getArea();当传入 &r 时,调用的是 Rectangle::getArea(),这就是多态
  • 虚析构函数至关重要!Shape 的析构函数不是虚的,delete shape1; 只会调用 Shape 的析构函数,而不会调用 Circle 的析构函数,导致 Circle 对象的资源泄漏。

其他重要OOP特性

  1. 静态成员

    • 静态成员变量:属于整个类,而不是某个对象,所有对象共享同一份数据,必须在类外初始化。
    • 静态成员函数:没有 this 指针,只能访问静态成员变量,可以通过类名或对象名调用。
  2. 友元

    • friend 关键字允许一个类或函数访问另一个类的 privateprotected 成员。
    • 破坏了封装性,应谨慎使用。
  3. 运算符重载

    • 允许用户自定义运算符(如 , , <<, >>)在自定义类型上的行为。
    • 通常重载为成员函数或友元函数。

常见面试/考试题及答案

问1:structclass 在C++中有何区别?

  • 默认访问权限不同:struct 的成员默认是 public 的;class 的成员默认是 private 的。
  • 默认继承权限不同:struct 默认是 public 继承;class 默认是 private 继承。
  • 本质上,它们几乎可以互换使用,区别主要在于设计哲学:struct 通常用于表示简单的数据聚合体(Plain Old Data),而 class 用于实现具有复杂行为的对象。

问2:什么是虚函数?什么是纯虚函数?它们有什么用?

  • 虚函数:使用 virtual 关键字声明的成员函数,主要作用是实现运行时多态,当通过基类指针或引用调用虚函数时,程序会根据指针实际指向的对象类型来决定调用哪个版本的函数,而不是根据指针的类型。
  • 纯虚函数:在虚函数声明后加上 = 0,如 virtual void f() = 0;,它没有函数体,所在的类成为抽象类
  • 作用
    • 虚函数:为多态提供接口,允许派生类定制自己的行为。
    • 纯虚函数:强制派生类必须重写该函数,用于定义一个“接口基类”,它只描述了“能做什么”,而不关心“怎么做”。

问3:什么是虚析构函数?为什么它很重要?

  • 虚析构函数:在基类的析构函数前加上 virtual 关键字。
  • 重要性:当使用基类指针指向一个派生类对象,并通过 delete 删除该指针时,如果基类的析构函数是虚函数,则会形成一个完整的析构链:先调用派生类的析构函数,再调用基类的析构函数,这样可以确保派生类对象分配的资源被正确释放,避免内存泄漏,如果基类析构函数不是虚函数,则只会调用基类的析构函数,导致派生类的析构函数不被调用,造成资源泄漏。

问4:深拷贝和浅拷贝有什么区别?如何实现深拷贝?

  • 浅拷贝:默认的拷贝构造函数和赋值运算符执行的就是浅拷贝,它简单地复制成员变量的值,如果成员变量是指针,那么拷贝后两个对象的指针将指向同一块内存,当其中一个对象被销毁时,delete 了这个指针,另一个对象的指针就变成了“野指针”,再次使用会导致程序崩溃。
  • 深拷贝:不仅复制指针的值,还复制指针指向的内容,拷贝后,两个对象拥有两块独立但内容相同的内存,一个对象的销毁不会影响另一个对象。
  • 如何实现自定义拷贝构造函数和赋值运算符重载函数,在这些函数中,为指针成员手动分配新的内存,并将原指针指向的内容复制过来。

例题:实现一个简单的 MyString 类,展示深拷贝。

class MyString {
private:
    char* data;
    size_t length;
public:
    // 构造函数
    MyString(const char* str = "") {
        length = strlen(str);
        data = new char[length + 1]; // +1 for '\0'
        strcpy(data, str);
    }
    // 深拷贝构造函数
    MyString(const MyString& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data); // 复制内容
        std::cout << "深拷贝构造函数被调用" << std::endl;
    }
    // 深拷贝赋值运算符
    MyString& operator=(const MyString& other) {
        // 1. 检查自赋值
        if (this == &other) {
            return *this;
        }
        // 2. 释放旧资源
        delete[] data;
        // 3. 分配新资源并复制
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
        std::cout << "深拷贝赋值运算符被调用" << std::endl;
        // 4. 返回自身
        return *this;
    }
    // 析构函数
    ~MyString() {
        delete[] data; // 释放动态分配的内存
        std::cout << "析构函数被调用" << std::endl;
    }
    // ... 其他成员函数
};

希望这份详尽的指南能帮助您更好地学习和掌握C++面向对象程序设计!祝您学习顺利!

分享:
扫描分享到社交APP
上一篇
下一篇