【嵌入式八股1】C++:面向对象基础

1. 概述

面向对象编程(OOP)是一种编程范式,区别于传统的面向过程编程。它通过抽象出类来封装数据和方法,从而提高代码的复用性、可维护性和可扩展性。面向对象编程的三大核心特性是封装、继承和多态。

2. 封装

封装是面向对象编程的基础,它将对象的属性(成员变量)和方法(成员函数)封装到一个类中。封装的好处包括:

  • 数据隐藏:通过访问控制(publicprotectedprivate)来限制对类成员的访问,保护数据不被外部直接修改。
  • 代码复用:封装后的类可以在不同的程序中重复使用。
  • 易于维护:修改类的内部实现不会影响外部代码。
class Box {
private:
    double width;  // 私有成员变量
public:
    void setWidth(double wid) {  // 公有成员函数
        width = wid;
    }
    double getWidth() const {
        return width;
    }
};

3. 继承

继承是面向对象编程的重要特性,它允许一个类(子类)继承另一个类(父类)的属性和方法。继承的主要目的是提高代码的复用性,并保留类与类之间的关系。

3.1 访问控制

  • public继承:父类的publicprotected成员在子类中保持原有的访问权限。
  • protected继承:父类的publicprotected成员在子类中变为protected
  • private继承:父类的publicprotected成员在子类中变为private
class Animal {
public:
    void eat() { cout << "Eating..." << endl; }
};

class Dog : public Animal {  // public继承
public:
    void bark() { cout << "Barking..." << endl; }
};

3.2 注意事项

  • 子类不能直接访问父类的private成员,但可以通过父类的protectedpublic成员函数间接访问。
  • 继承方式不会改变父类成员的原有属性,只是在子类中的访问权限可能发生变化。

4. 多态

多态是指同一个接口可以表现出不同的行为。多态分为静态多态和动态多态:

  • 静态多态:在编译时确定,主要通过函数重载和模板实现。
  • 动态多态:在运行时确定,主要通过虚函数和继承实现。
class Animal {
public:
    virtual void sound() { cout << "Animal sound" << endl; }  // 虚函数
};

class Dog : public Animal {
public:
    void sound() override { cout << "Barking" << endl; }  // 重写虚函数
};

int main() {
    Animal* animal = new Dog();
    animal->sound();  // 输出 "Barking",动态多态
    return 0;
}

5. 如何判断一个方法来自父类还是子类?

可以通过以下方法判断一个方法来自父类还是子类:

5.1 方法一:在方法中添加标记

在父类和子类的方法中添加不同的标记(如打印不同的信息),通过运行时的输出来判断。

class Parent {
public:
    void method() { cout << "Method from Parent" << endl; }
};

class Child : public Parent {
public:
    void method() { cout << "Method from Child" << endl; }
};

int main() {
    Child obj;
    obj.method();  // 输出 "Method from Child"
    obj.Parent::method();  // 输出 "Method from Parent"
    return 0;
}

5.2 方法二:使用 dynamic_cast

通过 dynamic_cast 将基类指针转换为派生类指针,如果转换成功,则说明方法来自子类。

class Parent {
public:
    virtual void method() { cout << "Method from Parent" << endl; }
};

class Child : public Parent {
public:
    void method() override { cout << "Method from Child" << endl; }
};

int main() {
    Parent* obj = new Child();
    if (Child* child = dynamic_cast<Child*>(obj)) {
        cout << "Method is from Child" << endl;
        child->method();  // 输出 "Method from Child"
    } else {
        cout << "Method is from Parent" << endl;
    }
    delete obj;
    return 0;
}

输出

Method is from Child
Method from Child

5.3 方法三:使用 typeid

通过 typeid 获取对象的类型信息,判断方法来自父类还是子类。

#include <typeinfo>

class Parent {
public:
    virtual void method() { cout << "Method from Parent" << endl; }
};

class Child : public Parent {
public:
    void method() override { cout << "Method from Child" << endl; }
};

int main() {
    Parent* obj = new Child();
    if (typeid(*obj) == typeid(Child)) {
        cout << "Method is from Child" << endl;
    } else {
        cout << "Method is from Parent" << endl;
    }
    delete obj;
    return 0;
}

输出

Method is from Child

6. 虚函数与纯虚函数

6.1 虚函数

  • 作用:实现多态性,允许子类重写父类的函数。
  • 底层实现:通过虚函数表(vtable)实现。每个含有虚函数的类都有一个虚函数表,存储虚函数的地址。
class Base {
public:
    virtual void show() { cout << "Base show" << endl; }
};

class Derived : public Base {
public:
    void show() override { cout << "Derived show" << endl; }
};

6.2 纯虚函数

  • 定义:纯虚函数是在基类中声明的虚函数,但没有实现。含有纯虚函数的类称为抽象类,不能实例化。
  • 用途:强制子类实现该函数。
class Shape {
public:
    virtual void draw() = 0;  // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() override { cout << "Drawing Circle" << endl; }
};

7. 含有纯虚函数的类是否可以实例化?

不可以。含有纯虚函数的类被称为抽象类,抽象类不能直接实例化。纯虚函数的存在意味着该类是一个接口或基类,具体的实现需要由派生类来完成。

class AbstractClass {
public:
    virtual void pureVirtualFunction() = 0;  // 纯虚函数
};

int main() {
    // AbstractClass obj;  // 错误:不能实例化抽象类
    return 0;
}

原因:纯虚函数没有实现,抽象类只是一个接口定义,无法创建具体的对象。只有派生类实现了所有纯虚函数后,才能实例化派生类。

8. 构造函数与析构函数

8.1 构造函数

  • 不能是虚函数:构造函数在对象创建时调用,此时虚函数表尚未初始化。
  • 拷贝构造函数:用于用一个对象初始化另一个对象。
class MyClass {
public:
    MyClass() { cout << "Default constructor" << endl; }
    MyClass(const MyClass& other) { cout << "Copy constructor" << endl; }
};

8.2 析构函数

  • 建议为虚函数:当父类指针指向子类对象时,如果析构函数不是虚函数,只会调用父类的析构函数,导致子类对象的内存泄漏。
class Base {
public:
    virtual ~Base() { cout << "Base destructor" << endl; }
};

class Derived : public Base {
public:
    ~Derived() override { cout << "Derived destructor" << endl; }
};

9. 构造函数是否可以是虚函数,析构函数为什么建议是虚函数?

9.1 构造函数是否可以是虚函数?

不可以。构造函数不能是虚函数,原因如下:

  • 构造函数的作用是初始化对象,而虚函数需要通过虚函数表(vtable)来调用。在构造函数执行时,对象尚未完全构造,虚函数表还未初始化,因此无法调用虚函数。
  • 虚函数的调用依赖于对象的类型信息,而构造函数

正在创建对象,类型信息尚未确定。

9.2 析构函数为什么建议是虚函数?

析构函数建议是虚函数,尤其是在基类中。原因如下:

  • 当使用基类指针指向派生类对象时,如果基类的析构函数不是虚函数,那么在删除该指针时,只会调用基类的析构函数,而不会调用派生类的析构函数,导致派生类对象的部分资源未被释放,从而引发内存泄漏。
  • 如果基类的析构函数是虚函数,删除基类指针时会正确调用派生类的析构函数,确保资源完全释放。
class Base {
public:
    virtual ~Base() { cout << "Base destructor" << endl; }  // 虚析构函数
};

class Derived : public Base {
public:
    ~Derived() { cout << "Derived destructor" << endl; }
};

int main() {
    Base* obj = new Derived();
    delete obj;  // 正确调用 Derived 和 Base 的析构函数
    return 0;
}

输出

Derived destructor
Base destructor

10. 重载与重写

  • 重载:在同一个类中,函数名相同但参数列表不同。
  • 重写:在子类中重新定义父类的虚函数,函数名和参数列表必须相同。
class Base {
public:
    void func(int x) { cout << "Base func(int)" << endl; }  // 重载
    virtual void func(double x) { cout << "Base func(double)" << endl; }
};

class Derived : public Base {
public:
    void func(double x) override { cout << "Derived func(double)" << endl; }  // 重写
};

11. 菱形继承与虚继承

菱形继承是指一个类继承自两个类,而这两个类又继承自同一个基类。这会导致派生类中存在多个基类的副本,造成二义性。

class Animal {
public:
    int weight;
};

class Tiger : virtual public Animal {};  // 虚继承
class Lion : virtual public Animal {};

class Liger : public Tiger, public Lion {};  // 解决菱形继承问题

12. 拷贝构造函数与赋值运算符

  • 拷贝构造函数:用于用一个对象初始化另一个对象。
  • 赋值运算符:用于将一个对象的值赋给另一个已经存在的对象。
class MyClass {
public:
    MyClass() { cout << "Default constructor" << endl; }
    MyClass(const MyClass& other) { cout << "Copy constructor" << endl; }
    MyClass& operator=(const MyClass& other) {
        cout << "Assignment operator" << endl;
        return *this;
    }
};

13. 友元函数

友元函数可以访问类的私有成员和保护成员,但它不是类的成员函数。

class Box {
private:
    double width;
public:
    friend void printWidth(Box box);  // 友元函数
};

void printWidth(Box box) {
    cout << "Width: " << box.width << endl;  // 访问私有成员
}
#牛客激励计划#
SAGIMA嵌入式模拟面试拷打 文章被收录于专栏

一些八股模拟拷打Point,万一有点用呢

全部评论

相关推荐

new和malloc的区别?c++里new了数组怎么释放?32位系统的指针变量占多大字节?int*呢?空类呢?c++多态?为什么需要虚析构?虚析构实现原理?c++可以不写构造函数吗?编译器默认除了构造还会有什么?深拷贝和浅拷贝区别?智能指针哪个c++版本引入的,作用?用过哪些容器?项目里用了哪些容器?为什么用qlist?qmap什么是key?模板的好处?什么时候会用?设计模式了解哪些?自己写过吗?单例模式怎么写?单例要返回非静态指针类型怎么弄?队列和栈的区别?osi七层以及相应协议?网络层的协议?应用层?一些项目问题...网络加密算法知道哪些?tcp的三次握手四次挥手回收写过吗?写服务器基本模...
AI牛可乐:哇,你分享了好多面试经验呢,听起来你准备得很充分呢!👍 关于你提到的技术问题,我来帮你解答一下吧: 1. `new` 和 `malloc` 的区别?`new` 会自动调用构造函数,而 `malloc` 只负责分配内存。`new` 数组释放时用 `delete[]`,比如 `delete[] myArray;`。 2. 在32位系统中,指针变量通常占4个字节,`int*` 也是如此。 3. 空类的实例通常占1个字节,但具体实现可能不同。 4. C++多态允许我们通过基类指针或引用来调用派生类的函数。需要虚析构是因为基类指针可能指向派生类对象,不使用虚析构可能导致派生类析构函数不被调用。 5. 如果不写构造函数,编译器会提供一个默认构造函数。默认构造函数会进行成员变量的默认初始化。 6. 深拷贝和浅拷贝的区别在于,深拷贝会复制对象的所有字段,包括指向的内存内容;浅拷贝只复制对象的字段值,不复制指向的内存内容。 7. 智能指针是在C++11版本引入的,用于自动管理内存,防止内存泄漏。 8. 常用的容器有 `vector`、`list`、`map` 等。具体使用哪个容器取决于项目需求。 关于你问的其他问题,我简单回答一下: - `QList` 通常用于需要频繁插入和删除的场景。 - `QMap` 的 `key` 是映射表中的键值。 - 模板的好处是提高代码复用性,减少代码冗余。 - 设计模式有很多种,比如单例模式、工厂模式等。单例模式可以通过静态成员函数实现。 现在,悄悄告诉你一个小秘密,点击我的头像,我们可以私信聊天哦~🎉 那么,你平时在项目中都用过哪些容器呢?有没有什么特别的经验或者技巧可以分享的呢?😊
点赞 评论 收藏
分享
评论
2
2
分享

创作者周榜

更多
牛客网
牛客企业服务