c/c++ 经典问题(一)

什么是虚函数? 如何实现多态?

虚函数是通过vartual 关键字修饰的,可以被子类重新的函数。 虚函数就是实现多态的方法之一,还有模板也可以实现多态。
其中虚函数实现的是运行时多态,也就是说,在程序运行的时候,根据对象的实际类型来决定调用哪个函数。而模板实现的是编译时多态,也就是说,在编译的时候,根据模板参数的类型来生成不同的函数。运行时多态需要额外的内存空间和时间开销,但是更灵活和通用。编译时多态不需要额外的开销,但是更静态和受限。
举例说明以下是一个虚函数实现多态的例子:

// 基类
class Shape {
public:
    // 虚函数
    virtual void draw() {
        cout << "Drawing a shape" << endl;
    }
};

// 派生类
class Circle : public Shape {
public:
    // 重写虚函数
    void draw() override {
        cout << "Drawing a circle" << endl;
    }
};

// 派生类
class Square : public Shape {
public:
    // 重写虚函数
    void draw() override {
        cout << "Drawing a square" << endl;
    }
};

int main() {
    // 基类指针指向派生类对象
    Shape* s1 = new Circle();
    Shape* s2 = new Square();

    // 调用虚函数,根据对象的实际类型执行对应的函数
    s1->draw(); // Drawing a circle
    s2->draw(); // Drawing a square

    delete s1;
    delete s2;

    return 0;
}

以下是一个模板实现多态的例子:

// 模板类
template <typename T>
class Calculator {
public:
   // 模板成员函数,根据模板参数的类型执行不同的操作
   T add(T x, T y) {
       return x + y;
   }

   T subtract(T x, T y) {
       return x - y;
   }
};

int main() {
   // 实例化模板类,传入不同的类型参数
   Calculator<int> c1; 
   Calculator<double> c2;

   // 调用模板成员函数,根据对象的类型执行不同的操作
   cout << c1.add(10, 20) << endl; // 30 (integer addition)
   cout << c2.add(10.5, 20.5) << endl; // 31 (double addition)

   cout << c1.subtract(30, 10) << endl; // 20 (integer subtraction)
   cout << c2.subtract(30.5, 10.5) << endl; // 20 (double subtraction)

   return 0;
}
Virtual Functions and Runtime Polymorphism in C++. https://www.geeksforgeeks.org/virtual-functions-and-runtime-polymorphism-in-cpp/ 
(2) 18.2 — Virtual functions and polymorphism – Learn C++ - LearnCpp.com. https://www.learncpp.com/cpp-tutorial/virtual-functions/ 
(3) Mastering Polymorphism in C++: Understanding and Implementing Virtual .... https://dev.to/iflis7/mastering-polymorphism-in-c-understanding-and-implementing-virtual-functions-and-template-classes-ee5 

虚函数和模板的优缺点和使用场景的建议:

虚函数的优点是可以实现运行时多态,使得代码更灵活和通用,可以处理不同类型的对象。虚函数的缺点是需要额外的内存空间和时间开销,因为每个对象都需要一个虚函数表来存储虚函数地址,并且在运行时需要动态绑定。虚函数适合用于处理继承关系复杂或者不确定的情况,例如设计一个图形界面库,可以使用虚函数来实现不同类型的控件。
模板的优点是可以实现编译时多态,使得代码更高效和可重用,可以处理不同类型的数据。模板的缺点是需要更长的编译时间,因为每个模板参数都会生成一份代码,并且可能导致代码膨胀。模板也可能难以理解和调试。模板适合用于处理泛型编程或者元编程的情况,例如设计一个容器类或者算法类,可以使用模板来实现对任意类型数据的操作。

什么是智能指针?它们有什么优点和缺点?

智能指针是一种封装了原始指针的类对象,它可以自动管理指针所指向的内存,避免内存泄漏或悬空指针。智能指针有以下优点:

它可以实现RAII(资源获取即初始化)的编程范式,即在对象创建时获取资源,在对象销毁时释放资源。
它可以避免手动调用new和delete来分配和释放内存,简化了代码并减少了错误。
它可以支持多态和继承,因为它们重载了*和->运算符来访问所指向对象的成员。
智能指针也有以下缺点:

智能指针不能防止循环引用的问题,即如果两个智能指针相互引用,则它们都不会被销毁,导致内存泄漏。这时需要使用弱引用或者手动打破循环。
智能指针有不同的类型和语义,如std::unique_ptr, std::shared_ptr, std::weak_ptr等,需要根据不同的场景选择合适的类型,并注意它们之间的转换和赋值规则。
智能指针可能会影响程序的性能和内存占用,因为它们需要额外的空间来存储引用计数或者析构函数等信息,并且可能会增加函数调用开销或者锁竞争等问题。

什么是抽象类和接口?它们之间有什么区别?

抽象类是一种包含了纯虚函数(没有实现体)的类,它不能被实例化,只能作为基类被其他类继承。抽象类通常用来定义一个通用的接口或者行为,并由子类提供具体的实现。

接口是一种特殊的抽象类,它只包含了纯虚函数(没有数据成员),它也不能被实例化,只能被其他类实现。接口通常用来定义一个契约或者规范,并由实现类遵循这些规范。
抽象类和接口之间有以下区别:

抽象类可以包含非纯虚函数(有实现体)和数据成员(属性),而接口只能包含纯虚函数(没有数据成员)。
抽象类可以实现部分功能并留下一些待子类完成的功能(模板方法模式),而接口只能定义功能但不能提供任何实现。
抽象类可以继承自其他抽象类或者非抽象类,并且一个子类只能继承自一个父类(单继承),而接口可以继承自其他接口,并且一个子类可以实现多个接口(多继承)。

如何使用运算符重载?请举一个例子。

运算符重载是一种允许自定义类或者结构体使用内置运算符的特性,它可以提高代码的可读性和灵活性。运算符重载的实现是通过定义一个成员函数或者友元函数,以运算符作为函数名,并接受合适的参数和返回值。

例如,如果我们想要定义一个复数类,并且能够使用+运算符来实现复数的加法,我们可以这样写:

class Complex {
public:
    // 构造函数
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
    // +运算符重载(成员函数)
    Complex operator+(const Complex& c) const {
        return Complex(real + c.real, imag + c.imag);
    }
private:
    double real; // 实部
    double imag; // 虚部
};

// 测试代码
int main() {
    Complex c1(1.0, 2.0); // 创建一个复数对象c1
    Complex c2(3.0, 4.0); // 创建一个复数对象c2
    Complex c3 = c1 + c2; // 使用+运算符来实现复数的加法
    return 0;
}

什么是异常处理?如何使用try-catch-finally语句?

异常处理是一种用来处理程序中可能发生的错误或者异常情况的机制,它可以避免程序崩溃或者出现不可预期的结果。异常处理通常使用try-catch-finally语句来实现,其基本语法如下:

try {
    // 尝试执行可能出错的代码块
}
catch (exception_type e) {
    // 捕获并处理特定类型的异常e
}
catch (...) {
    // 捕获并处理所有其他类型的异常
}
finally {
    // 不管有没有发生异常,都会执行的代码块(可选)
}

例如,如果我们想要实现一个除法函数,并且能够处理除数为零或者其他非法输入的情况,我们可以这样写:

double divide(double a, double b) {
    try {
        if (b == 0) { // 如果除数为零,则抛出一个异常对象
            throw std::runtime_error("Divide by zero");
        }
        return a / b; // 否则正常返回商值
    }
    catch (std::runtime_error e) { // 捕获并处理std::runtime_error类型的异常e
        std::cout << "Error: " << e.what() << std::endl; // 打印错误信息
        return 0; // 返回一个默认值(也可以重新抛出异常或者终止程序)
    }
}

// 测试代码
int main() {
   double x = divide(10, 2);   // 正常调用,x = 5 
   double y = divide(10, 0);   // 异常调用,y = 0,并打印"Error: Divide by zero"
   return 0;
}

如何使用模板?请举一个例子。

模板是一种用来定义通用的类或者函数的特性,它可以让我们使用不同类型的参数来实例化类或者函数,从而提高代码的复用性和灵活性。模板通常使用template关键字来声明,并且在尖括号中指定一个或者多个类型参数。

例如,如果我们想要定义一个交换(swap)函数,它可以交换任意两个类型相同的变量的值,我们可以这样写:

template <typename T> // 声明一个类型参数T
void swap(T& a, T& b) { // 定义一个模板函数swap
    T temp = a; // 使用T类型的变量temp
    a = b;
    b = temp;
}

// 测试代码
int main() {
    int x = 10, y = 20; // 定义两个int类型的变量x和y
    swap(x, y); // 调用swap函数,此时T被推断为int
    cout << x << " " << y << endl; // 输出20 10

    string s1 = "Hello", s2 = "World"; // 定义两个string类型的变量s1和s2
    swap(s1, s2); // 调用swap函数,此时T被推断为string
    cout << s1 << " " << s2 << endl; // 输出World Hello

    return 0;
}

什么是STL?它包含哪些组件?

STL是C++的标准模板库(Standard Template Library),它是一组提供了常用数据结构和算法的模板类和模板函数,它可以帮助我们快速而高效地开发程序。STL主要包含以下四个组件:

容器(container):用来存储数据元素的类,如vector, list, map, set等。
迭代器(iterator):用来遍历容器中的元素的类,如begin(), end(), rbegin(), rend()等。
算法(algorithm):用来对容器中的元素进行操作或者处理的函数,如sort(), find(), count(), reverse()等。
适配器(adapter):用来修改或者扩展容器或者迭代器功能的类,如stack, queue, priority_queue等。
例如,如果我们想要使用STL来实现一个简单的程序,它可以从标准输入读取一些整数,并且按照从小到大的顺序输出它们,并统计每个数出现了多少次,我们可以这样写:

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v; // 创建一个空的vector容器
    int x;
    while (cin >> x) { // 从标准输入读取整数
        v.push_back(x); // 将整数插入到vector末尾
    }
    sort(v.begin(), v.end()); // 使用sort算法对vector进行排序
    map<int, int> m; // 创建一个空的map容器
    for (auto it = v.begin(); it != v.end(); ++it) { // 使用迭代器遍历vector中的元素
        cout << *it << " "; // 输出元素值
        m[*it]++; // 将元素值作为map的键,将出现次数作为map的值
    }
    cout << endl;
    for (auto it = m.begin(); it != m.end(); ++it) { // 使用迭代器遍历map中的键值对
        cout << it->first << ": " << it->second << endl; // 输出键和值
    }
    return 0;
}

如何实现继承和封装?

继承是一种表示类之间关系的特性,它可以让一个类(子类)继承另一个类(父类)的成员变量和成员函数,并且可以根据需要进行重写或者扩展。继承通常使用: 来声明,在子类名后面加上父类名来表示这是一个继承关系。

封装是一种隐藏数据和实现细节、提供公共接口和保护数据安全的特性,它可以让我们将类的成员变量和成员函数分为不同的访问级别,如public(公有)、private(私有)和protected(受保护)。封装通常使用class关键字来定义,并且在成员变量或者成员函数前面加上相应的访问修饰符来指定它们的访问级别。

例如,如果我们想要定义一个动物(animal)类,并且让狗(dog)和猫(cat)分别继承自动物类,并且重写动物类中定义的make_sound()函数,并且将动物类中的name变量设置为受保护的,我们可以这样写:

class Animal {
public:
    Animal(string n) : name(n) {} // 构造函数
    virtual void make_sound() const { // 虚函数
        cout << name << " makes a sound." << endl;
    }
protected:
    string name; // 名字
};

class Dog : public Animal { // 狗继承自动物
public:
    Dog(string n) : Animal(n) {} // 构造函数
    void make_sound() const override { // 重写虚函数
        cout << name << " barks." << endl;
    }
};

class Cat : public Animal { // 猫继承自动物
public:
   Cat(string n) : Animal(n) {}  // 构造函数 
   void make_sound() const override {  // 重写虚函数 
       cout << name << " meows."<< endl;
   }
};

如何使用构造函数和析构函数?

构造函数是一种用来初始化对象状态的特殊成员函数,它通常与类名相同,并且没有返回值。构造函数可以有不同数量或者类型的参数,从而形成不同的构造方式。构造函数也可以使用初始化列表(initializer list)来初始化成员变量。

析构函数是一种用来清理对象资源或者执行其他收尾工作的特殊成员函数,它通常以~为前缀,并且没有参数和返回值。析构函数在对象被销毁时自动调用。

例如,如果我们想要定义一个学生(student)类,并且能够使用不同方式创建学生对象,并且在销毁学生对象时打印一条信息,我们可以这样写:

class Student {
public:
   Student() : name("Unknown"), age(0), score(0) {}  // 默认构造函数 
   Student(string n, int a, double s) : name(n), age(a), score(s) {}  // 带参数构造函数 
   ~Student() {  // 析构函数 
       cout << "Student " << name << " is destroyed." << endl;
   }
private:
   string name;  // 姓名 
   int age;      // 年龄 
   double score; // 成绩 
};

// 测试代码 
int main() {
   Student s1;                // 使用默认构造函数创建学生对象s1 
   Student s2("Alice", 18, 90);     // 使用带参数构造函数创建学生对象s2 
   return 0;
}

什么是友元函数和友元类?它们有什么用途?

友元函数是一种可以访问类的私有或者受保护成员的非成员函数,它可以提高代码的效率和简洁性,但也会破坏类的封装性。友元函数通常使用friend关键字来声明,并且在类中指定哪些函数是它的友元。

友元类是一种可以访问另一个类的私有或者受保护成员的类,它可以实现一些特殊的功能或者关系,但也会破坏类的封装性。友元类通常使用friend关键字来声明,并且在一个类中指定哪些类是它的友元。

例如,如果我们想要定义一个点(point)类,并且让距离(distance)函数和线段(line)类能够访问点类中的x和y变量,我们可以这样写:

class Point {
public:
    Point(int x, int y) : x(x), y(y) {} // 构造函数
    friend double distance(const Point& p1, const Point& p2); // 声明距离函数为友元
    friend class Line; // 声明线段类为友元
private:
    int x; // x坐标
    int y; // y坐标
};

// 定义距离函数
double distance(const Point& p1, const Point& p2) {
    return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)); // 可以直接访问点对象的x和y变量
}

// 定义线段类
class Line {
public:
    Line(const Point& p1, const Point& p2) : start(p1), end(p2) {} // 构造函数
    double length() const { // 计算线段长度
        return distance(start, end); // 调用距离函数
    }
private:
    Point start; // 起点
    Point end;   // 终点 
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值