嵌入式八股文学习笔记——C++学习笔记面向对象相关
文章目录
- 1. 构造函数的特性
- 为什么构造函数不能声明为虚函数?
- 2. C++空类的默认成员函数
- 默认生成的六个成员函数:
- 3. 面向对象与面向过程的区别
- 四个关键区别
- 4. 面向对象的四大基本特性
- 1. 抽象(Abstraction)
- 2. 继承(Inheritance)
- 3. 封装(Encapsulation)
- 4. 多态(Polymorphism)
- 5. 友元(Friend)详解
- 友元的概念
- 友元的类型
- 示例代码
- 友元的特点
- 6. C++默认参数机制
- 默认参数的基本用法
- 默认参数的规则
- 默认参数的实际应用
- 最佳实践
1. 构造函数的特性
为什么构造函数不能声明为虚函数?
这是C++初学者常见的疑问,了解其原因有助于深入理解C++的对象模型和多态机制。
主要原因:
-
动态绑定机制冲突:
- 虚函数依赖于动态绑定机制,即根据对象的实际类型(而非指针或引用的类型)来确定调用哪个函数实现
- 而构造函数执行时,对象尚未完全构建,动态类型未确定
- 构造函数是对象创建过程的一部分,对象的动态类型只有在构造过程结束后才能确定
-
父类初始化问题:
- 创建派生类对象时,首先调用基类的构造函数
- 如果构造函数可以是虚函数,则编译器会试图根据"对象的实际类型"选择调用哪个实现
- 但此时派生类部分尚未构造完成,无法正确进行虚函数的动态绑定
因此,构造函数必须是非虚的,这是C++语言设计的必然结果,因为虚函数的多态特性在对象构造阶段无法正常发挥作用。
2. C++空类的默认成员函数
当我们定义一个空类(没有显式定义任何成员函数和变量)时,C++编译器会自动生成一些默认成员函数,确保基本操作可以正常进行。
默认生成的六个成员函数:
-
默认构造函数:
- 无参构造函数,用于创建类的实例
- 即使未显式定义,编译器也会自动生成
-
复制构造函数:
- 用于创建对象的副本
- 形如
ClassName(const ClassName& obj)
-
析构函数:
- 在对象生命周期结束时调用
- 即使空类没有需要清理的成员,也会生成
-
赋值运算符重载函数:
- 形如
ClassName& operator=(const ClassName& obj)
- 用于一个对象向另一个对象赋值
- 形如
-
取址运算符重载函数:
- 形如
ClassName* operator&()
- 返回对象的地址
- 形如
-
const取址运算符重载函数:
- 形如
const ClassName* operator&() const
- 返回对象的常量地址
- 形如
这些默认函数会在需要时被编译器生成,通常会直接调用相应的内存管理操作。
3. 面向对象与面向过程的区别
理解面向对象和面向过程的区别,对于掌握C++编程思想至关重要。C++同时支持这两种编程范式,了解它们的不同有助于选择合适的设计方法。
四个关键区别
-
出发点不同
- 面向对象:将现实问题抽象为对象,注重对象间的关系和交互
- 面向过程:将问题分解为功能模块,强调过程的抽象和模块化
-
层次逻辑关系不同
- 面向对象:通过类的层次结构表示继承与扩展关系,更贴近现实世界
- 面向过程:以模块为单位,通过模块间的层次结构表达过程关系
-
数据处理与控制程序方式不同
- 面向对象:数据与代码封装在一起,通过成员函数访问,事件驱动控制
- 面向过程:全局变量处理数据,通过明确的调用关系控制流程
-
分析设计与编码转换方式不同
- 面向对象:从分析、设计到编码遵循统一模型,保持一致性
- 面向过程:各阶段有明确分工,分析与设计独立于实现
4. 面向对象的四大基本特性
面向对象编程的核心在于这四个基本特性,它们共同构成了C++面向对象编程的基石。
1. 抽象(Abstraction)
定义:从复杂现实中提取关键特征,忽略无关细节的过程。
表现:
- 过程抽象:对操作的抽象
- 数据抽象:对数据及其操作的抽象
作用:
- 简化复杂系统,将其分解为相关对象
- 每个对象完成特定任务,提高系统的可维护性
2. 继承(Inheritance)
定义:允许创建新类(派生类)并继承现有类(基类)的属性和方法。
表现:
- 派生类可以修改或扩展基类功能
- 继承表达"一般与特殊"的关系
作用:
- 提高代码复用性
- 建立类之间的层次结构
3. 封装(Encapsulation)
定义:将对象的状态(数据)与行为(方法)结合,通过定义好的接口对外提供服务。
表现:
- 隐藏内部实现细节
- 只允许通过接口访问和修改对象状态
作用:
- 保护数据安全
- 减少系统复杂性
- 提高可维护性
4. 多态(Polymorphism)
定义:允许不同类的对象对相同消息做出不同响应。
表现:
- 编译时多态:函数重载、运算符重载
- 运行时多态:通过虚函数实现,在运行时动态确定调用的函数
作用:
- 增强代码灵活性
- 根据对象实际类型选择合适实现
- 支持接口编程而非实现编程
5. 友元(Friend)详解
友元是C++特有的机制,允许类的私有和保护成员被其他函数或类访问,是对封装性的一种补充。
友元的概念
在封装原则下,类的私有成员仅能被成员函数访问。但有时我们需要让特定外部函数或类访问私有成员。友元机制正是为此而设计,它允许指定的函数或类访问该类的私有和保护成员。
友元的类型
-
友元函数:
- 普通函数可访问某类的私有或保护成员
- 在类内部使用
friend
关键字声明
-
友元类:
- 一个类的成员函数可访问另一类的私有或保护成员
- 整个类被声明为友元
示例代码
友元函数示例:
#include <iostream>
using namespace std;
class MyClass {
private:
int secret;
public:
MyClass(int val) : secret(val) {}
// 声明友元函数
friend void showSecret(MyClass& obj);
};
// 友元函数定义,可以访问 MyClass 的私有成员 secret
void showSecret(MyClass& obj) {
cout << "The secret is: " << obj.secret << endl;
}
int main() {
MyClass obj(42);
showSecret(obj); // 直接访问私有成员
return 0;
}
友元类示例:
#include <iostream>
using namespace std;
class ClassB; // 前向声明 ClassB
class ClassA {
private:
int value;
public:
ClassA(int val) : value(val) {}
// 声明 ClassB 为友元类
friend class ClassB;
};
class ClassB {
public:
void showClassA(ClassA& obj) {
cout << "ClassA's value is: " << obj.value << endl; // 访问 ClassA 的私有成员
}
};
int main() {
ClassA objA(10);
ClassB objB;
objB.showClassA(objA); // 友元类访问 ClassA 的私有成员
return 0;
}
友元的特点
- 控制访问范围:允许特定函数或类访问私有成员,不必公开访问权限
- 友元不继承:如果A是B的友元,B的子类不自动成为A的友元
- 单向关系:友元关系不具有对称性,A声明B为友元,B不自动成为A的友元
6. C++默认参数机制
C++允许为函数参数指定默认值,当调用函数时如未提供参数,将使用默认值。
默认参数的基本用法
语法:
void function(int param1, int param2 = 10, int param3 = 20);
调用方式:
function(5); // param1=5, param2=10, param3=20
function(5, 15); // param1=5, param2=15, param3=20
function(5, 15, 25); // param1=5, param2=15, param3=25
默认参数的规则
-
默认参数从右向左:
- 如果某参数有默认值,则其右侧所有参数必须也有默认值
- 错误示例:
void func(int a = 1, int b, int c = 3);
// 编译错误
-
声明与定义分离:
- 建议在函数声明中提供默认参数值
- 函数定义中不应重复给出默认值
- 正确示例:
// 声明 void delay(int loops = 1000); // 定义 void delay(int loops) { // 函数体 }
-
避免重载引起的二义性:
- 当使用默认参数的函数与其他重载函数参数数量相同时,可能引发二义性
- 示例:
void func(int, int = 4); // 重载函数1 void func(int = 3, int = 4); // 重载函数2 func(7); // 错误:无法确定调用哪个重载
默认参数的实际应用
#include <iostream>
using namespace std;
// 带默认参数的函数声明
void printInfo(string name, int age = 18, string city = "Unknown");
int main() {
printInfo("Alice"); // 使用两个默认参数
printInfo("Bob", 25); // 使用一个默认参数
printInfo("Charlie", 30, "London"); // 不使用默认参数
return 0;
}
// 函数定义,不再提供默认值
void printInfo(string name, int age, string city) {
cout << "Name: " << name << ", Age: " << age << ", City: " << city << endl;
}
最佳实践
- 在函数声明中定义默认参数,定义中不要重复
- 默认参数应该是稳定的、常用的值
- 避免与函数重载一起使用引发二义性
- 在修改默认参数时要注意对现有代码的影响