当前位置: 首页 > article >正文

嵌入式八股文学习笔记——C++学习笔记面向对象相关

文章目录

    • 1. 构造函数的特性
      • 为什么构造函数不能声明为虚函数?
    • 2. C++空类的默认成员函数
      • 默认生成的六个成员函数:
    • 3. 面向对象与面向过程的区别
      • 四个关键区别
    • 4. 面向对象的四大基本特性
      • 1. 抽象(Abstraction)
      • 2. 继承(Inheritance)
      • 3. 封装(Encapsulation)
      • 4. 多态(Polymorphism)
    • 5. 友元(Friend)详解
      • 友元的概念
      • 友元的类型
      • 示例代码
      • 友元的特点
    • 6. C++默认参数机制
      • 默认参数的基本用法
      • 默认参数的规则
      • 默认参数的实际应用
      • 最佳实践

1. 构造函数的特性

为什么构造函数不能声明为虚函数?

这是C++初学者常见的疑问,了解其原因有助于深入理解C++的对象模型和多态机制。

主要原因:

  1. 动态绑定机制冲突

    • 虚函数依赖于动态绑定机制,即根据对象的实际类型(而非指针或引用的类型)来确定调用哪个函数实现
    • 而构造函数执行时,对象尚未完全构建,动态类型未确定
    • 构造函数是对象创建过程的一部分,对象的动态类型只有在构造过程结束后才能确定
  2. 父类初始化问题

    • 创建派生类对象时,首先调用基类的构造函数
    • 如果构造函数可以是虚函数,则编译器会试图根据"对象的实际类型"选择调用哪个实现
    • 但此时派生类部分尚未构造完成,无法正确进行虚函数的动态绑定

因此,构造函数必须是非虚的,这是C++语言设计的必然结果,因为虚函数的多态特性在对象构造阶段无法正常发挥作用。

2. C++空类的默认成员函数

当我们定义一个空类(没有显式定义任何成员函数和变量)时,C++编译器会自动生成一些默认成员函数,确保基本操作可以正常进行。

默认生成的六个成员函数:

  1. 默认构造函数

    • 无参构造函数,用于创建类的实例
    • 即使未显式定义,编译器也会自动生成
  2. 复制构造函数

    • 用于创建对象的副本
    • 形如 ClassName(const ClassName& obj)
  3. 析构函数

    • 在对象生命周期结束时调用
    • 即使空类没有需要清理的成员,也会生成
  4. 赋值运算符重载函数

    • 形如 ClassName& operator=(const ClassName& obj)
    • 用于一个对象向另一个对象赋值
  5. 取址运算符重载函数

    • 形如 ClassName* operator&()
    • 返回对象的地址
  6. const取址运算符重载函数

    • 形如 const ClassName* operator&() const
    • 返回对象的常量地址

这些默认函数会在需要时被编译器生成,通常会直接调用相应的内存管理操作。

3. 面向对象与面向过程的区别

理解面向对象和面向过程的区别,对于掌握C++编程思想至关重要。C++同时支持这两种编程范式,了解它们的不同有助于选择合适的设计方法。

四个关键区别

  1. 出发点不同

    • 面向对象:将现实问题抽象为对象,注重对象间的关系和交互
    • 面向过程:将问题分解为功能模块,强调过程的抽象和模块化
  2. 层次逻辑关系不同

    • 面向对象:通过类的层次结构表示继承与扩展关系,更贴近现实世界
    • 面向过程:以模块为单位,通过模块间的层次结构表达过程关系
  3. 数据处理与控制程序方式不同

    • 面向对象:数据与代码封装在一起,通过成员函数访问,事件驱动控制
    • 面向过程:全局变量处理数据,通过明确的调用关系控制流程
  4. 分析设计与编码转换方式不同

    • 面向对象:从分析、设计到编码遵循统一模型,保持一致性
    • 面向过程:各阶段有明确分工,分析与设计独立于实现

4. 面向对象的四大基本特性

面向对象编程的核心在于这四个基本特性,它们共同构成了C++面向对象编程的基石。

1. 抽象(Abstraction)

定义:从复杂现实中提取关键特征,忽略无关细节的过程。

表现

  • 过程抽象:对操作的抽象
  • 数据抽象:对数据及其操作的抽象

作用

  • 简化复杂系统,将其分解为相关对象
  • 每个对象完成特定任务,提高系统的可维护性

2. 继承(Inheritance)

定义:允许创建新类(派生类)并继承现有类(基类)的属性和方法。

表现

  • 派生类可以修改或扩展基类功能
  • 继承表达"一般与特殊"的关系

作用

  • 提高代码复用性
  • 建立类之间的层次结构

3. 封装(Encapsulation)

定义:将对象的状态(数据)与行为(方法)结合,通过定义好的接口对外提供服务。

表现

  • 隐藏内部实现细节
  • 只允许通过接口访问和修改对象状态

作用

  • 保护数据安全
  • 减少系统复杂性
  • 提高可维护性

4. 多态(Polymorphism)

定义:允许不同类的对象对相同消息做出不同响应。

表现

  • 编译时多态:函数重载、运算符重载
  • 运行时多态:通过虚函数实现,在运行时动态确定调用的函数

作用

  • 增强代码灵活性
  • 根据对象实际类型选择合适实现
  • 支持接口编程而非实现编程

5. 友元(Friend)详解

友元是C++特有的机制,允许类的私有和保护成员被其他函数或类访问,是对封装性的一种补充。

友元的概念

在封装原则下,类的私有成员仅能被成员函数访问。但有时我们需要让特定外部函数或类访问私有成员。友元机制正是为此而设计,它允许指定的函数或类访问该类的私有和保护成员。

友元的类型

  1. 友元函数

    • 普通函数可访问某类的私有或保护成员
    • 在类内部使用friend关键字声明
  2. 友元类

    • 一个类的成员函数可访问另一类的私有或保护成员
    • 整个类被声明为友元

示例代码

友元函数示例

#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;
}

友元的特点

  1. 控制访问范围:允许特定函数或类访问私有成员,不必公开访问权限
  2. 友元不继承:如果A是B的友元,B的子类不自动成为A的友元
  3. 单向关系:友元关系不具有对称性,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

默认参数的规则

  1. 默认参数从右向左

    • 如果某参数有默认值,则其右侧所有参数必须也有默认值
    • 错误示例:void func(int a = 1, int b, int c = 3); // 编译错误
  2. 声明与定义分离

    • 建议在函数声明中提供默认参数值
    • 函数定义中不应重复给出默认值
    • 正确示例:
      // 声明
      void delay(int loops = 1000);
      // 定义
      void delay(int loops) {
          // 函数体
      }
      
  3. 避免重载引起的二义性

    • 当使用默认参数的函数与其他重载函数参数数量相同时,可能引发二义性
    • 示例:
      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;
}

最佳实践

  1. 在函数声明中定义默认参数,定义中不要重复
  2. 默认参数应该是稳定的、常用的值
  3. 避免与函数重载一起使用引发二义性
  4. 在修改默认参数时要注意对现有代码的影响


http://www.kler.cn/a/599550.html

相关文章:

  • 质检LIMS系统在临床试验机构的实践 临床试验的LIMS应用突破
  • Java实习生面试题(2025.3.23 be)
  • 安宝特分享|AR智能装备赋能企业效率跃升
  • redis7.4.2单机配置
  • CentOS 7 更换 yum 源(阿里云)+ 扩展 epel 源
  • 蓝桥杯备考:图的遍历
  • linux去掉绝对路径前面部分和最后的/符号
  • Proteus8打开Proteus7文件(.DSN格式)的方法
  • PyTorch Lightning工业级训练实战
  • Python 迭代器与生成器:深入理解与实践
  • dsPIC33CK64MC105 Curiosity Nano|为高性能数字电源与电机控制而生
  • 软件公司高新技术企业代办:机遇与陷阱并存-优雅草卓伊凡
  • 刷机维修进阶教程-----adb禁用错了系统app导致无法开机 如何保数据无损恢复机型
  • BigEvent项目后端学习笔记(二)文章分类模块 | 文章分类增删改查全流程解析(含优化)
  • python多线程和多进程的区别有哪些
  • Spring Boot整合Activiti工作流详解
  • C++|面试准备二(常考)
  • 【差分隐私相关概念】约束下的列联表边缘分布计算方法
  • 以mysql 为例, 在cmd 命令行连接数据,操作数据库,关闭数据库的详细步骤
  • 【C++进阶学习】第三讲----多态的概念和使用