【嵌入式八股1】C++:面向对象基础
1. 概述
面向对象编程(OOP)是一种编程范式,区别于传统的面向过程编程。它通过抽象出类来封装数据和方法,从而提高代码的复用性、可维护性和可扩展性。面向对象编程的三大核心特性是封装、继承和多态。
2. 封装
封装是面向对象编程的基础,它将对象的属性(成员变量)和方法(成员函数)封装到一个类中。封装的好处包括:
- 数据隐藏:通过访问控制(
public
、protected
、private
)来限制对类成员的访问,保护数据不被外部直接修改。 - 代码复用:封装后的类可以在不同的程序中重复使用。
- 易于维护:修改类的内部实现不会影响外部代码。
class Box {
private:
double width; // 私有成员变量
public:
void setWidth(double wid) { // 公有成员函数
width = wid;
}
double getWidth() const {
return width;
}
};
3. 继承
继承是面向对象编程的重要特性,它允许一个类(子类)继承另一个类(父类)的属性和方法。继承的主要目的是提高代码的复用性,并保留类与类之间的关系。
3.1 访问控制
public
继承:父类的public
和protected
成员在子类中保持原有的访问权限。protected
继承:父类的public
和protected
成员在子类中变为protected
。private
继承:父类的public
和protected
成员在子类中变为private
。
class Animal {
public:
void eat() { cout << "Eating..." << endl; }
};
class Dog : public Animal { // public继承
public:
void bark() { cout << "Barking..." << endl; }
};
3.2 注意事项
- 子类不能直接访问父类的
private
成员,但可以通过父类的protected
或public
成员函数间接访问。 - 继承方式不会改变父类成员的原有属性,只是在子类中的访问权限可能发生变化。
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; // 访问私有成员
}
#牛客激励计划#一些八股模拟拷打Point,万一有点用呢