c
记录一些自己不会的 和理解不够深的

image-20250513220710273

  1. 存储方式

    • C风格字符串通过字符数组(char[])表示,以'\0'(空字符)结尾。
    • C++中使用std::string类来表示字符串,更加抽象和方便。
  2. 动态内存管理

    • C风格字符串需要手动进行内存分配和释放,使用如malloc()new来分配内存,并使用free()delete释放内存。
    • C++的std::string类自动管理内存,通过构造函数和析构函数自动处理内存,无需手动分配或释放内存。
  3. 字符串操作

    • C风格字符串的操作需要使用一系列的字符串处理函数(如strcpy()strcat()strlen()等),这些函数需要将字符串指针作为参数。
    • C++的std::string类提供了丰富的成员函数,如append()length()find()等,可以直接操作字符串对象,更加方便和易用。
  4. 安全性

    • C风格字符串没有提供越界检查,需要开发人员自行保证字符串的正确性。

      • C++的std::string类具有越界检查功能,可以避免缓冲区溢出等安全问题。

      image-20250513220823443

    连接字符串常用

    image-20250513220915980

    向量的概念和优势

    1. 动态数组容器
      • 向量是C++标准库提供的一种动态数组容器,使用前需要包含头文件<vector>
    2. 线性存储
      • 向量是一种线性容器,可以存储同一类型的元素。
    3. 顺序存储
      • 向量中的元素按照它们在向量中的顺序进行存储。
    4. 随机访问
      • 向量支持随机访问,可以通过索引访问向量中的元素。
    5. 动态调整大小
      • 向量的大小可以动态调整,可以根据需要动态添加或删除元素。
    6. 优势
      • 向量具有动态大小、随机访问、内存管理简单、较好的可扩展性等优势。

    向量的创建和初始化

    1. 创建空向量

      • 使用std::vector<int> myVector;创建一个空的整型向量。
    2. 初始化向量

      • 可以通过初始化列表、使用迭代器、使用默认值等方式初始化向量。

      image-20250513221111990

    image-20250513221125703

image-20250513221153145

指针的声明和初始化

  1. 声明与初始化语法

    • 语法格式为:<数据类型>* <指针名称> = <空指针或已存在变量地址>;

    • 示例:声明一个指向整数的指针并初始化为空指针。

      1
      int *ptr = nullptr;
  2. 内存分配

    • 在使用指针之前,确保为指针分配了合适的内存空间或者将其初始化为有效的内存地址。

    • 示例:使用动态内存分配为指针分配内存。

      1
      int *ptr = new int;
  3. 释放内存

    • 使用动态分配的内存后,要记得使用delete运算符释放该内存,以避免内存泄漏。

    • 示例:释放之前分配的内存。

      1
      delete ptr;

当然,这里有一个使用newdelete运算符进行动态内存分配和释放的实际例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

int main() {
// 动态分配一个整数的内存
int* dynamicInt = new int; // 分配内存并初始化为0(对于基本数据类型)

// 给动态分配的内存赋值
*dynamicInt = 42;

// 输出动态分配内存中的值
std::cout << "The value of dynamicInt is: " << *dynamicInt << std::endl;

// 使用完动态分配的内存后,释放它
delete dynamicInt;

// 释放后,尝试访问dynamicInt将会导致未定义行为
// *dynamicInt = 100; // 这行代码如果被执行,将会导致错误

std::cout << "Memory has been freed." << std::endl;

return 0;
}

在这个例子中:

  1. 使用new int为一个整数动态分配了内存,并将返回的指针赋值给dynamicInt
  2. 通过解引用指针*dynamicInt给这块内存赋值为42。
  3. 输出这块内存中的值。
  4. 使用delete dynamicInt释放了之前分配的内存。
  5. 释放内存后,dynamicInt指针不再指向有效的内存区域,如果再次访问它将会导致未定义行为,可能是程序崩溃或其他不可预知的结果。

请注意,对于数组,应该使用delete[]来释放内存:

1
2
3
int* dynamicIntArray = new int[5]; // 动态分配一个整数数组的内存
// ... 使用数组
delete[] dynamicIntArray; // 释放数组内存

正确地管理内存是非常重要的,因为它可以防止内存泄漏和其他内存相关的问题。在现代C++中,通常推荐使用智能指针(如std::unique_ptrstd::shared_ptr),因为它们可以自动管理内存,从而减少内存泄漏的风险。

是的,std::unique_ptrstd::shared_ptr是C++11及以后版本中引入的智能指针,它们可以帮助自动管理内存,减少内存泄漏的风险。使用这些智能指针,通常不需要(也不应该)手动调用delete来释放内存。智能指针会在适当的时候自动释放它们所管理的资源。

std::unique_ptr

std::unique_ptr代表独占所有权的智能指针,意味着同一时间只能有一个std::unique_ptr指向特定资源。当std::unique_ptr被销毁时(例如,当它离开作用域或被重新赋值时),它会自动释放所管理的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <memory>

void functionUsingUniquePtr() {
std::unique_ptr<int> ptr(new int(10));
std::cout << "Value: " << *ptr << std::endl;
// 当ptr离开作用域时,它所管理的内存将自动被释放
}

int main() {
functionUsingUniquePtr();
return 0;
}

在这个例子中,当ptr离开functionUsingUniquePtr函数的作用域时,它所管理的内存会自动被释放。

std::shared_ptr

std::shared_ptr代表共享所有权的智能指针,允许多个std::shared_ptr实例共同拥有同一资源。资源的释放是在最后一个拥有该资源的std::shared_ptr被销毁或被赋值为其他资源时自动进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <memory>

std::shared_ptr<int> createSharedPtr() {
return std::make_shared<int>(20);
}

int main() {
std::shared_ptr<int> ptr1 = createSharedPtr();
std::shared_ptr<int> ptr2 = ptr1; // 现在ptr1和ptr2共享同一个资源

std::cout << "Value: " << *ptr1 << std::endl;

// ptr1和ptr2离开作用域时,它们所管理的内存不会被释放,因为引用计数不为0
return 0;
}

在这个例子中,ptr1ptr2共享同一个资源。当main函数结束时,这两个智能指针都会被销毁,因为它们的引用计数都变为0,所以它们管理的内存会被自动释放。

总的来说,使用std::unique_ptrstd::shared_ptr可以大大简化内存管理,减少内存泄漏和其他内存管理错误的风险。然而,智能指针也有其开销,因此在性能敏感的应用中,需要权衡使用智能指针的便利性和性能开销。

指针的简单使用

  1. 变量地址获取

    • 使用&运算符可以获取变量的地址,并将其赋值给指针。

    • 示例:获取变量的地址并赋值给指针。

      1
      2
      int var = 10;
      int *ptr = &var;
  2. 解引用

    • 使用*运算符可以解引用指针,即访问指针所指向的变量的值。

    • 示例:通过指针访问变量的值。

      1
      std::cout << *ptr;  // 输出:10
  3. 动态内存分配

    • 使用new运算符可以动态分配内存,返回指向该内存的指针。

    • 示例:动态分配内存并初始化。

      1
      int *ptr = new int(20);
  4. 数组操作

    • 指针可以用于数组的访问和操作。

    • 示例:使用指针遍历数组。

      1
      2
      3
      4
      5
      int arr[] = {1, 2, 3, 4, 5};
      int *ptr = arr;
      for (int i = 0; i < 5; ++i) {
      std::cout << *(ptr + i) << " "; // 输出:1 2 3 4 5
      }
  5. 函数参数传递

    • 指针可以被用来在函数之间传递参数,使得函数可以直接修改指向对象的值。

    • 示例:通过指针修改变量的值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      void modify(int *ptr) {
      *ptr = 30;
      }
      int main() {
      int var = 10;
      modify(&var);
      std::cout << var; // 输出:30
      return 0;
      }

C++容器与智能指针

image-20250514182018774

image-20250514182052165

image-20250514182157447

image-20250514182150823

数组(固定大小的连续存储空间)

  • 声明和定义
    • 使用声明符号[]来声明数组,并指定数组的大小。
    • 数组的大小必须是常量表达式,可以在编译时确定。
    • 数组的元素类型可以是任意的基本类型(如整数、浮点数、字符等),也可以是自定义的类型。
  • 数组元素的访问
    • 数组元素通过索引访问,索引从0开始,到数组大小减1结束。
    • 使用方括号[]运算符和元素的索引来访问数组元素。
  • 数组的初始化
    • 可以在声明数组时同时进行初始化,或者在后续的操作中对数组元素进行赋值。
    • 使用花括号{}来提供初始值。可以使用花括号列表初始化整个数组,或者使用索引逐个初始化数组元素。
  • 数组的遍历
    • 可以使用循环结构(如for循环)来遍历数组,以访问和处理数组中的每个元素。

向量(Vector,动态数组,可变大小的连续存储空间)

  • 声明和定义向量
    • 使用std::vector类模板来声明和定义向量。
    • 指定向量中元素的类型作为模板参数。
  • 向量的操作
    • 向量提供了一系列成员函数和操作符来进行元素的插入、删除、访问和修改等操作。
    • 通过成员函数和操作符,可以获取向量的大小、清空向量、判断向量是否为空等。
  • 向量的遍历
    • 可以使用循环结构(如for循环)来遍历向量,以访问和处理向量中的每个元素。

列表(List,双向链表结构)

  • 声明和定义列表
    • 使用std::list类模板来声明和定义列表。
    • 指定列表中元素的类型作为模板参数。
  • 列表的操作
    • 列表提供了一系列成员函数来进行元素的插入、删除、访问和修改等操作。
    • 通过成员函数,可以获取列表的大小、清空列表、判断列表是否为空等。
  • 列表的遍历
    • 可以使用迭代器或循环结构(如for循环)来遍历列表,以访问和处理列表中的每个元素。

这些容器各有特点和适用场景,选择哪种容器取决于具体的应用需求。例如,如果需要频繁地在容器中间插入或删除元素,列表可能是更好的选择;如果需要快速随机访问元素,向量或数组可能更合适。

image-20250514182631401

映射(Map)容器概述

  • 定义:映射(Map)是一种键值对的容器,用于存储和管理键值对(key-value)数据。
  • 特点:映射是C++标准库中的一种容器,提供了方便的操作和管理键值对数据的方法。
  • 键的唯一性:映射类似于字典的概念,其中键是唯一的,可以用于快速查找和访问对应的值。
  • 头文件:映射类定义在<map>头文件中,需要引入该头文件才能使用映射。

声明和定义映射

  • 使用std::map类模板:使用std::map类模板来声明和定义映射。
  • 模板参数:指定键和值的类型作为模板参数。

映射的操作

  • 成员函数和操作符:映射提供了一系列成员函数和操作符来进行键值对的插入、删除、查找和访问等操作。
  • 获取映射大小:通过成员函数,可以获取映射的大小。
  • 清空映射:可以清空映射,即删除映射中的所有键值对。
  • 判断映射是否为空:可以判断映射是否为空。

映射的遍历

  • 迭代器或循环结构:可以使用迭代器或循环结构(如for循环)来遍历映射,以访问和处理映射中的每个键值对。

示例代码

  • 声明映射

    1
    2
    std::map<std::string, int> studentGrades; // 声明一个字符串到整数的映射
    std::map<int, std::string> phoneBook; // 声明一个整数到字符串的映射
  • 插入键值对

    1
    2
    studentGrades["Alice"] = 90; // 插入一个键值对
    studentGrades["Bob"] = 85;
  • 访问键值对

    1
    std::cout << studentGrades["Alice"] << std::endl; // 访问键对应的值
  • 遍历映射

    1
    2
    3
    for (std::map<std::string, int>::iterator it = studentGrades.begin(); it != studentGrades.end(); ++it) {
    std::cout << it->first << " " << it->second << std::endl; // 输出键值对
    }

常见容器的分类

image-20250514183545638

1. 顺序容器(Sequence Container)

  • 存储方式:顺序容器按照元素的插入顺序来存储元素,元素在容器中的位置由插入顺序决定。
  • 访问方式:可以使用迭代器(iterator)或下标运算符来访问容器中的元素。
  • 示例容器:包括向量(vector)、双向链表(list)、双端队列(deque)等。

2. 有序容器(Ordered Container)

  • 存储方式:有序容器按照一定的排序准则对元素进行排序,并在插入时维持元素的有序性。
  • 访问方式:可以使用迭代器或下标运算符来访问容器中的元素(元素的顺序是根据排序准则来决定的)。
  • 示例容器:包括集合(set)、映射(map)、多重集合(multiset)、多重映射(multimap)等。

其他区别

  • 适用场景
    • 顺序容器适用于需要保留元素插入顺序的场景。
    • 有序容器适用于需要根据排序准则进行存储和访问的场景。
  • 插入和删除操作
    • 顺序容器的插入和删除操作可能会导致元素在内存中的重新分配和移动。
    • 有序容器则需要保持元素的有序性,因此插入和删除操作可能更复杂。
  • 查找操作
    • 顺序容器的查找操作通常需要遍历整个容器,时间复杂度为O(n)。
    • 有序容器可以利用内部的排序结构进行快速的查找,时间复杂度为O(log n)。

总结

  • 顺序容器:适用于需要保留元素插入顺序的场景,如向量、列表、双端队列。
  • 有序容器:适用于需要根据排序准则进行存储和访问的场景,如集合、映射、多重集合、多重映射。
  • 性能差异:顺序容器的查找操作通常较慢,而有序容器可以利用排序结构进行快速查找。

image-20250514183612141

image-20250514183634954

遍历容器元素

使用迭代器遍历容器元素

  • 迭代器:是一种能够遍历容器元素的对象,类似于指针。
  • 获取迭代器:容器类通常提供begin()end()成员函数来获取迭代器的起始和结束位置。
  • 遍历方法:可以使用循环结构(如whilefor循环)和迭代器逐个访问容器元素。

使用范围-for循环遍历容器元素

  • 范围-for循环:是C++11引入的语法,可以简化容器元素的遍历。
  • 自动类型推断:使用auto关键字来推断迭代器的类型,并使用范围-for循环对容器进行遍历。

容器的遍历和常用操作

插入和删除元素

  • 插入元素
    • 顺序容器中使用push_back()push_front()函数,分别在容器尾部和头部插入元素。
    • 有序容器中使用insert()函数,在容器指定位置插入元素。
  • 删除元素
    • 顺序容器中使用pop_back()pop_front()函数,分别删除在容器尾部和头部的元素。
    • 使用erase()函数,删除有序容器和映射中指定位置或者指定键的元素。

查找和替换元素

  • 查找元素
    • 有序容器和映射中,使用find()函数在容器中查找指定元素,并返回该元素的迭代器。
    • 有序容器和映射中,使用count()函数计算容器中指定元素的个数。
  • 替换元素
    • 可以使用迭代器来直接修改容器中的元素。
    • 使用下标运算符[]直接修改容器中的元素。

容器大小和容量的操作

  • 容器大小
    • 使用容器的成员函数size()函数获取容器中的元素个数。
    • 使用容器的成员函数empty()函数检查容器是否为空,容器中没有元素则返回true,否则返回false。
  • 容器容量
    • 使用容器的成员函数capacity(),获取容器重新分配内容之前可以容纳的元素个数。
    • 使用容器的成员函数reserve(),为容器设置预留的容量,可以避免频繁的重新分配内存。

总结

这些操作涵盖了C++容器的基本使用,包括遍历、插入、删除、查找、替换以及获取容器的大小和容量等。通过这些操作,可以有效地管理和操作容器中的数据。使用迭代器和范围-for循环可以方便地遍历容器,而各种成员函数则提供了对容器元素的增删改查等操作。理解这些基本概念和操作对于在C++中有效地使用容器至关重要。

image-20250518220951020

公有成员

  • 定义:通过公有访问修饰符public修饰的成员变量或成员函数。
  • 访问权限
    • 公有成员变量:可以被类的对象和类外部代码直接访问和修改。
    • 公有成员函数:可以被类的对象和类外部代码调用。

保护成员

  • 定义:通过保护访问修饰符protected修饰的成员变量或成员函数。
  • 访问权限
    • 保护成员变量:只能被类的派生类访问和修改。
    • 保护成员函数:只能被类的派生类调用。

私有成员

  • 定义:通过私有访问修饰符private修饰的成员变量或成员函数。
  • 访问权限
    • 私有成员变量:只能被类的成员函数访问和修改。
    • 私有成员函数:只能被类的其他成员函数调用。

总结

  • 公有成员:具有最宽松的访问权限,可以被类的对象和类外部代码访问。
  • 保护成员:具有中等的访问权限,只能被类的派生类访问。
  • 私有成员:具有最严格的访问权限,只能被类的成员函数访问。

image-20250518220805025

函数覆盖的概念和使用

  • 函数覆盖:派生类重写(覆盖)基类中的虚函数,以实现特定行为。这是实现运行时多态性的一种机制。
  • 使用关键字
    • 在基类中声明虚函数时,使用关键字virtual来标识。
    • 在派生类中重写(覆盖)基类中的虚函数。

使用override和final关键字

  • override关键字
    • 在C++中,overridefinal是两个关键字,用于对虚函数进行重写和类进行继承的修饰。
    • override关键字帮助我们确保正确地进行函数重写。
    • final关键字防止派生和重写。
    • 用法
      • override关键字可用于派生类中对基类虚函数的重写,以确保正确地进行函数覆盖。
      • 在派生类中使用override关键字标识对基类中虚函数的重写,可以帮助我们在编译时捕获一些常见的错误,如函数签名不匹配的情况。
      • 如果派生类中的函数声明使用override关键字,但实际上并没有重写基类中的虚函数,编译器将会发出错误提示。
  • final关键字
    • final关键字用于修饰类、虚函数或成员函数,表示它们是最终版本,禁止进一步的派生或重写。
    • 用法
      • 在类声明中使用final关键字修饰类,表示该类是最终类,不能再被继承。
      • 在虚函数声明中使用final关键字修饰虚函数,表示该虚函数不能再被派生类重写。
      • 在成员函数声明中使用final关键字修饰成员函数,表示该成员函数不能在派生类中被重写。

总结来说,overridefinal关键字在C++中用于确保类的继承和函数的重写符合预期,避免错误和歧义。

image-20250514183659851

image-20250514183713078

C++常用总结

  1. 文件操作

    • 文件操作通常涉及创建、读取、写入和删除文件。在C++中,可以使用标准库中的<fstream>头文件提供的类,如std::ifstream(输入文件流)、std::ofstream(输出文件流)和std::fstream(输入输出文件流)来进行文件操作。

​ 文件指针的操作

在C++中,文件指针用于定位和控制文件中的读写位置。以下是文件指针操作的主要方法:

获取文件指针的位置

  • **tellg()**:用于获取输入文件流的当前读取位置。
  • **tellp()**:用于获取输出文件流的当前写入位置。

设置文件指针的位置

  • **seekg(pos)**:将输入文件流的读取位置设置为相对于文件开头的pos位置。
  • **seekp(pos)**:将输出文件流的写入位置设置为相对于文件开头的pos位置。
  • **seekg(offset, dir)seekp(offset, dir)**:在当前位置的基础上相对于offset进行偏移,dir可以是以下常量之一:
    • **std::ios::beg**:相对于文件开头进行偏移。
    • **std::ios::cur**:相对于当前位置进行偏移。
    • **std::ios::end**:相对于文件末尾进行偏移。

检查文件指针的有效性

  • **good()**:成员函数检查文件指针是否有效。如果文件指针有效,则返回true;否则返回false
  1. 空指针的处理

    • 空指针是指没有指向任何对象的指针。在C++中,空指针通常用nullptr表示。处理空指针时,需要检查指针是否为空,避免解引用空指针,这会导致程序崩溃。可以使用条件语句来检查指针是否为nullptr

image-20250514221542740

空指针的判断和使用

  • 判断空指针

    • 使用条件判断语句将目标指针与nullptr进行比较。如果指针为空(即没有指向任何对象),则条件为真;否则为假。

    • 示例代码:

      1
      2
      3
      if (ptr == nullptr) {
      // 指针为空的处理逻辑
      }
  • 空指针的特性

    • 空指针没有有效的内存地址,访问空指针指向的对象是不安全的,可能导致程序崩溃或未定义行为。
    • nullptr赋值给指针,可以将指针显式地设置为空指针。

避免空指针的错误

  • 检查动态分配的内存

    • 在动态分配内存后,应该检查分配的指针是否为空,以确保内存分配成功。如果分配失败,指针将为nullptr

    • 示例代码:

      1
      2
      3
      4
      int* ptr = new int;
      if (ptr == nullptr) {
      // 内存分配失败的处理逻辑
      }
  • 使用指针前检查

    • 在使用指针之前,始终检查指针是否为空,并尽量避免访问空指针。

    • 示例代码:

      1
      2
      3
      if (ptr != nullptr) {
      // 安全地使用指针
      }

总结

空指针是C++编程中常见的问题,处理不当可能导致程序崩溃或未定义行为。以下是一些关键点:

  1. 判断空指针:使用条件判断语句将指针与nullptr比较,确保指针不为空后再使用。

  2. 显式设置空指针:将nullptr赋值给指针,显式地将指针设置为空。

  3. 检查动态内存分配:在动态分配内存后,检查指针是否为空,确保内存分配成功。

  4. 使用前检查指针:在每次使用指针之前,检查指针是否为空,避免访问空指针。

  5. 常用版本特性

    • 这可能指的是C++语言的不同版本中引入的新特性。例如,C++11引入了自动类型推断(auto)、范围for循环、智能指针等。了解不同版本的新特性可以帮助开发者编写更现代、更高效的代码。

    image-20250514221601991

    C++11 的特性和改进

    1. 自动类型推导
      • 引入了关键字 auto,可以根据初始化表达式的类型自动推导变量的类型。这简化了代码编写,特别是在处理复杂类型时。
    2. 统一的初始化语法
      • 引入了初始化列表语法 {},可以用于初始化数组、容器、结构体等各种类型的对象。这种语法提供了一种统一且直观的方式来初始化对象。
    3. 范围-based for 循环
      • 引入了新的循环语法 for (element : sequence),用于遍历容器、数组等序列中的元素。这种循环语法使代码更加简洁和易读。
    4. 空指针常量
      • 引入了关键字 nullptr,用于表示空指针。它可以用来替代旧的表示空指针的 NULL 或者 0,提供了更强的类型安全。
    5. 强类型枚举
      • 引入了新的枚举语法,允许为枚举类型指定底层类型,并提供了更强的类型检查。这使得枚举类型更加安全和灵活。
    6. Lambda 表达式
      • 引入了匿名函数的概念,允许在代码中定义小型的匿名函数。Lambda 表达式可以捕获外部变量,并可以作为函数对象使用,这在编写回调函数和处理事件时非常有用。
    7. 智能指针
      • 引入了三种智能指针类型:std::unique_ptrstd::shared_ptrstd::weak_ptr。这些智能指针类型简化了动态内存管理,自动管理内存的分配和释放,减少了内存泄漏的风险。
    8. 并发编程支持
      • 引入了线程库、互斥量、条件变量等多线程和并发编程的支持。这些特性使得C++能够更好地支持并发编程,方便开发者编写并发程序。

    总结

    C++11标准引入了大量新特性和改进,这些特性不仅提高了代码的可读性和安全性,还简化了代码编写和内存管理。通过使用这些新特性,开发者可以编写更简洁、更安全、更高效的代码,充分利用C++语言的强大功能。同时,这些特性也使得C++语言在现代软件开发中更具竞争力,适用于各种应用场景。

  6. 常见的编码技巧和最佳实践

    • 编码技巧和最佳实践包括代码的组织结构、命名约定、内存管理、错误处理等。例如,使用有意义的变量名、避免魔法数字、编写可重用的代码、进行异常处理等。这些实践有助于提高代码的可读性、可维护性和性能。

    image-20250514221828341

image-20250514221850561

image-20250514221906985

image-20250514221943742

  1. 一些友善的建议

    • 这可能包括编程时的一些软技能建议,如团队合作、代码审查、持续学习等。友善的建议有助于提高开发者的工作效率和团队的整体表现。

image-20250514222009032

image-20250514222021860

image-20250514222048606

车载事业部C++代码规范培训

image-20250514222156466

image-20250515212602980

image-20250515215400176

1. 预处理宏

主要目的是避免宏带来的问题,给出了一些规范和建议:

  • 不要在 .h 文件中定义宏。
    • 避免头文件被多处引用时意外影响其它代码。
  • 在马上要使用时才进行 #define,使用后要立即 #undef。
    • 这样可以减少宏对其它代码的影响,做到用完即丢。
  • 不要只是对已经存在的宏使用 #undef,选择一个不会冲突的名称。
    • 避免冲突和潜在的命名污染。
  • 不要试图使用展开后会导致 C++ 构造不稳定的宏,否则至少要附上文档说明其行为。
    • 指的是有些宏展开后可能对 C++ 构造(如类、函数等)产生不可预期影响,因此要谨慎使用并注明。
  • 不要用 ## 处理函数、类和变量的名字。
    • 这是宏拼接符,容易导致名字混淆,可读性和可维护性差。

总结:

这些规范旨在减少预处理宏带来的副作用,提高代码的可维护性和安全性。


2. 整型变量的定义

主要讲述整型变量的选择,以及推荐用法:

  • 优先用 C++ 内建类型 int。

  • 需要不同大小时建议用 <stdint.h> 里的精确整型 如

    1
    int16_t  int32_t  int32_t
    • 这样可以明确表达变量的位数,提升代码可移植性和准确性。
  • 如果变量可能超过 int 能表示的范围,比如大于 2^31(2GiB),就应该直接用 64 位变量 int64_t。

    • 这样可以避免溢出和数据错误。
  • 即使值不会超过 int 表示的范围,在计算过程中也可能溢出。

    • 所以宁可用大类型,不要“拿不准时用更大的类型”。

<stdint.h> 介绍:

  • 里面定义了 int16_tuint32_tint64_t 等精确大小的整型。
  • 如果需要保证整型大小,可以用这些类型代替 shortunsigned long long 等。

总结

  • 宏的使用要谨慎,避免污染、冲突、难以维护。
    • 整型变量建议用精确类型,防止溢出和类型不确定的问题,提升代码的健壮性和可移植性

DTS

一、什么是DTS

DTS是一种描述硬件设备信息的文件格式,主要用于Linux内核中。它以文本的形式描述硬件设备的属性、连接关系等信息,然后通过设备树编译器(DTC)将其编译成设备树二进制文件(.dtb),供内核在启动时加载和解析,从而实现对硬件设备的识别和初始化。

二、需要了解DTS的情况

  1. 使用Linux内核的嵌入式开发
    • 如果你开发的嵌入式系统使用Linux内核,那么了解DTS是非常重要的。因为Linux内核从3.5版本开始,对于许多嵌入式平台(如ARM、RISC-V等)都推荐使用设备树来描述硬件。例如,在开发基于ARM架构的嵌入式设备时,你需要通过DTS文件来告诉内核硬件设备的详细信息,如GPIO引脚的用途、外设的地址范围、时钟配置等。只有这样,内核才能正确地初始化和驱动硬件设备。
    • 例如,对于一个带有多功能GPIO引脚的嵌入式开发板,你可能需要在DTS文件中指定某个GPIO引脚用于I2C通信,而另一个引脚用于普通数字输入输出。如果DTS文件配置错误,可能会导致硬件设备无法正常工作。
  2. 硬件平台的移植和定制开发
    • 当你需要将Linux内核移植到一个新的硬件平台或者对现有硬件平台进行定制开发时,DTS文件的编写和修改是必不可少的。因为不同的硬件平台有不同的硬件架构和设备配置,你需要通过DTS文件来适配这些差异。比如,当你将一个Linux内核版本从一个开发板移植到另一个具有不同外设的开发板时,就需要修改DTS文件,以确保内核能够正确识别和管理新开发板上的硬件设备。
  3. 驱动开发
    • 对于嵌入式系统中的驱动开发,DTS文件提供了硬件设备的接口信息。驱动开发者可以通过DTS文件获取硬件设备的寄存器地址、中断号等关键信息,从而编写出能够正确与硬件设备交互的驱动程序。例如,开发一个SPI设备的驱动时,驱动代码需要从DTS文件中获取SPI控制器的设备树节点信息,包括SPI设备的片选号、时钟速率等参数,才能实现对SPI设备的有效控制。

三、不需要了解DTS的情况

  1. 使用非Linux内核的嵌入式开发
    • 如果你的嵌入式系统使用的是其他操作系统(如RTOS,即实时操作系统,像FreeRTOS、μC/OS-II等)或者无操作系统,那么DTS文件通常不会被用到。因为这些系统通常采用不同的方式来管理硬件设备,例如通过直接在代码中硬编码硬件设备的地址和配置信息,而不是依赖于像DTS这样的设备树描述机制。
  2. 使用封装良好的开发平台和硬件抽象层
    • 在一些嵌入式开发平台中,硬件厂商可能已经提供了高度封装的硬件抽象层(HAL)或者中间件,这些抽象层直接隐藏了硬件设备的具体细节,包括DTS文件的使用。在这种情况下,开发者可以直接调用抽象层提供的接口函数来操作硬件设备,而无需关心DTS文件。例如,一些基于STM32微控制器的开发平台,通过STM32 HAL库封装了硬件操作,开发者可以直接使用库函数来控制GPIO、定时器等外设,而无需编写DTS文件。

QNX共享内存

image-20250521153325554

一、什么是QNX共享内存

QNX是一个基于微内核的实时操作系统,其设计目标是提供高效的进程间通信(IPC)和资源管理机制。共享内存是QNX中一种重要的进程间通信方式,允许多个进程共享同一块物理内存区域,从而实现高效的数据交换和通信。

在QNX中,共享内存的使用通常涉及以下几个关键概念:

  1. 共享内存段(Shared Memory Segment):这是共享内存的基本单位,由一个或多个进程创建和访问。
  2. 内存映射(Memory Mapping):通过内存映射机制,将共享内存段映射到进程的地址空间中,使得进程可以像访问普通内存一样访问共享内存。
  3. 同步机制(Synchronization):由于多个进程可能同时访问共享内存,因此需要使用同步机制(如信号量、互斥锁等)来避免数据竞争和不一致性问题。

image-20250521153608341

image-20250521153633809

QNX共享内存的优缺点

  1. 优点
    • 高效性:共享内存是一种非常高效的进程间通信方式,因为它避免了数据的多次复制和传输。多个进程可以直接访问同一块物理内存,从而大大提高了数据交换的效率。
    • 灵活性:QNX的共享内存机制提供了灵活的控制方式,可以通过多种系统调用和库函数来创建、管理和访问共享内存。同时,还可以结合同步机制来实现复杂的并发控制。
    • 实时性:在实时系统中,共享内存可以快速地传递数据,满足实时性要求。例如,在高精度的工业控制系统中,共享内存可以用于实时传递传感器数据和控制指令。
  2. 缺点
    • 同步复杂性:由于多个进程可能同时访问共享内存,因此需要使用同步机制来避免数据竞争和不一致性问题。同步机制的使用增加了编程的复杂性,需要开发者仔细设计和实现。
    • 安全性问题:共享内存的访问权限需要严格控制,否则可能会导致安全问题。例如,如果一个恶意进程访问了共享内存,可能会篡改数据或导致系统崩溃。因此,在使用共享内存时,需要确保只有授权的进程才能访问共享内存。
    • 内存管理复杂性:共享内存的生命周期管理需要特别注意。如果某个进程意外崩溃或退出,可能会导致共享内存无法被正确释放,从而造成内存泄漏。因此,需要在程序设计中仔细处理共享内存的创建、使用和销毁过程。

是否需要了解QNX共享内存

是否需要了解QNX共享内存,取决于你的开发需求和目标平台:

  1. 需要了解的情况
    • 如果你正在开发基于QNX操作系统的嵌入式系统,尤其是涉及到多进程协作、实时数据交换或资源管理的场景,那么了解QNX共享内存是非常重要的。掌握共享内存的使用方法可以帮助你设计出高效、可靠的系统架构,提高系统的性能和实时性。
    • 例如,在开发一个实时监控系统时,多个进程需要实时交换图像数据和传感器数据。通过使用QNX共享内存,可以快速地传递这些数据,同时结合同步机制确保数据的一致性和完整性。
  2. 不需要了解的情况
    • 如果你的嵌入式系统不使用QNX操作系统,或者你的开发场景中不需要使用共享内存机制(例如,系统中只有一个进程,或者数据交换量非常小),那么了解QNX共享内存可能不是必要的。
    • 另外,如果你使用的是封装良好的开发框架或中间件,这些框架可能已经隐藏了共享内存的实现细节,你只需要调用相关的接口函数即可实现进程间通信,而无需深入了解共享内存的底层机制。

例子

image-20250521154241355

image-20250521154302419

一个使用共享内存(Shared Memory)和互斥锁(Mutex)在QNX操作系统中进行进程间通信(IPC)的示例。下面我将详细解释这个示例的代码和运行结果。

示例代码解释

shmemcreator.c

这个程序创建共享内存和一个信号量,并在共享内存中写入一些文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "shmemcreator.h"

char *programname = "shmemcreator";

int main(int argc, char *argv[])
{
int fd;
shmem_t *ptr;
pthread_mutexattr_t myattr;
pthread_mutex_t myshmmutex;

pthread_mutexattr_init(&myattr);
pthread_mutexattr_setpshared(&myattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&ptr->myshmemmutex, &myattr);

pthread_mutex_lock(&ptr->myshmemmutex);
strcpy(ptr->text, "Text by shmemcreator.c"); /* write to the shared memory */

printf("%s: Shared memory created and semaphore initialized to 0.\n",
programname);
printf("%s: Wrote text '%s' to shared memory.\n",
programname, ptr->text);
printf("%s: Sleeping for 20 seconds. While this program is sleeping\n",
programname);
printf("%s: run 'example_shmem_user'\n", programname, programname);
sleep(20);

printf("%s: Woke up. Now unlocking the mutex.\n", programname);

pthread_mutex_unlock(&ptr->myshmemmutex);

close(fd); // Closing the file descriptor
munmap(ptr, sizeof(shmem_t)); // removing the mapping
shm_unlink("/myshmemobject"); // Delete the shared memory object

return (EXIT_SUCCESS);
}

shmemuser.c

这个程序打开共享内存对象,等待信号量,访问共享内存并读取其中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "shmemcreator.h"

char *programname = "shmemuser";

int main(int argc, char *argv[])
{
int fd;
shmem_t *ptr;

fd = shm_open("/myshmemobject", O_RDWR, S_IRWXU);
if (fd == -1)
{
printf("%s: error opening the shared memory object: %s\n",
programname, strerror(errno));
exit(EXIT_FAILURE);
}

ptr = mmap(0, sizeof(shmem_t),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

printf("%s: Waiting on the mutex. Run 'pidin'. I should be MUTEX_blocked.\n",
programname);

pthread_mutex_lock(&ptr->myshmemmutex);

printf("%s: Got the mutex, now accessing shared memory\n", programname);
printf("%s: Shared memory contains '%s'\n", programname, ptr->text);

pthread_mutex_unlock(&ptr->myshmemmutex);

close(fd);
munmap(ptr, sizeof(shmem_t));
return (EXIT_SUCCESS);
}

运行结果如下:

1
2
3
4
5
6
7
8
9
10
# ./shmemcreator
shmemcreator: Shared memory created and semaphore initialized to 0.
shmemcreator: Wrote text 'Text by shmemcreator.c' to shared memory
shmemcreator: Sleeping for 20 seconds. While this program is sleeping
shmemcreator: run 'example_shmem_user'

# ./shmemuser
shmemuser: Waiting on the mutex. Run 'pidin'. I should be MUTEX_blocked.
shmemuser: Got the mutex, now accessing shared memory
shmemuser: The shared memory contains 'Text by shmemcreator.c'

运行结果解释

shmemcreator 程序

  • 创建共享内存和信号量,初始化为0。
  • 写入文本 "Text by shmemcreator.c" 到共享内存。
  • 打印共享内存创建和信号量初始化的信息。
  • 打印写入共享内存的文本。
  • 打印程序将要休眠20秒的信息。
  • 休眠20秒。
  • 解锁信号量。
  • 关闭文件描述符,解除映射,删除共享内存对象。

shmemuser 程序

  • 打开共享内存对象。
  • 将共享内存对象映射到进程地址空间。
  • 等待信号量(此时会被阻塞,因为 shmemcreator 程序已经锁定了信号量)。
  • 获取信号量后,访问共享内存并读取其中的内容。
  • 打印读取到的共享内存内容。
  • 解锁信号量。
  • 关闭文件描述符,解除映射。

运行 shmemcreator 程序后,它会创建共享内存,写入文本,然后休眠20秒。在此过程中,运行 shmemuser 程序,它会等待信号量,因为 shmemcreator 程序已经锁定了信号量。20秒后,shmemcreator 程序解锁信号量,shmemuser 程序获取信号量,访问共享内存并读取其中的内容。

这个示例展示了如何在QNX操作系统中使用共享内存和互斥锁进行进程间通信。shmemcreator 程序创建共享内存并写入数据,然后休眠一段时间。shmemuser 程序等待信号量,获取信号量后访问共享内存并读取数据。通过这种方式,可以实现多个进程之间的高效数据交换。

通俗解释QNX程序

想象你和朋友们在做一个团队项目,需要共享一些信息。为了避免混乱,你们决定用一个特别的“共享笔记本”来记录信息。这个笔记本就像电脑里的“共享内存”,大家都能看和写。

  1. 创建共享笔记本
    • 你(shmemcreator程序)首先创建了这个共享笔记本,并写下了第一条信息:“Text by shmemcreator.c”。
    • 然后你锁上了笔记本(使用互斥锁),这样其他人就不能同时写入,避免信息混乱。
    • 你告诉其他人,你要休息20秒(程序休眠20秒),在这期间他们可以开始读取笔记本。
  2. 读取共享笔记本
    • 你的朋友(shmemuser程序)看到你锁上了笔记本,就等着(等待互斥锁)。
    • 20秒后,你回来解锁了笔记本,告诉你的朋友可以看了。
    • 你的朋友打开笔记本,看到了你写的信息:“Text by shmemcreator.c”,然后他把这条信息告诉了团队里的其他人。
  3. 结束工作
    • 你和你的朋友都完成了工作,你把笔记本收起来,这样其他人就不能再次访问它了。

运行结果

  • 你先创建了共享内存,写入了信息,然后休息了20秒。
  • 在这期间,你的朋友尝试读取共享内存,但因为你锁定了它,所以他必须等待。
  • 20秒后,你解锁了共享内存,你的朋友读取了里面的信息,并告诉了团队其他人。

20秒等待主要是为了演示和测试的目的:

  1. 演示互斥锁的效果shmemcreator程序通过锁定互斥锁来确保在它休眠的20秒内,shmemuser程序能够体验到等待互斥锁释放的过程。这展示了互斥锁的同步机制,即一个程序在访问共享资源时如何等待另一个程序释放锁。(你有足够的时间完成所有与笔记本相关的操作)

  2. 测试共享内存的访问:通过在写入数据后休眠,shmemcreator程序确保了shmemuser程序在其休眠期间运行并尝试读取共享内存。这样可以测试shmemuser程序是否能够正确地等待互斥锁,以及在互斥锁释放后是否能够成功读取共享内存中的数据。(其他人不会在你完成之前干扰笔记本中的内容。)

  3. 模拟实际应用场景:在实际应用中,可能存在一个程序需要在完成数据写入后,让另一个程序来处理这些数据的情况。通过休眠20秒,shmemcreator程序模拟了这种场景,即在一个程序完成数据处理后,另一个程序开始处理这些数据。(当你解锁笔记本时,其他人可以安全地访问最新的、完整的信息。)

  4. 为什么你的朋友不能自己打开笔记本?

    • 因为你在笔记本上加了一把锁(这就像是电脑里的互斥锁)。这把锁是为了防止大家同时写入笔记本,造成信息混乱。所以,只有你(或者有钥匙的人)能打开这个锁,其他人必须等待。
  5. 为什么一定要等20秒后才能打开给他看?

    • 你告诉团队,你要休息20秒。在这期间,你的朋友不能打开笔记本,因为笔记本被锁住了。这20秒就像是你处理一些事情的时间,比如写一些重要的信息到笔记本里。
  6. 如果我打开了,不就是两个人一起看了吗?

    • 当你20秒后回来,你打开了笔记本的锁,这时候你的朋友就可以看了。但是,即使他看了,也只有一个人能同时写入笔记本,因为每次只能有一个人拿着钥匙(解锁)。这样,其他人就不会同时写入,造成混乱。

C语言编码规范

image-20250521164427381

image-20250521164537900

image-20250521164502982

标识符命名规则

  1. 组成
    • 只能包含字母、数字和下划线。
    • 不能以数字开头。
    • 下划线不能出现在开头或结尾。
  2. 目的
    • 让代码更易读。
    • 避免混淆,例如 variable_namevariable___name
  3. 命名原则
    • 使用完整的英文描述。
    • 遵循“最小长度 & 最大信息”原则,谨慎使用缩写。
  4. 说明
    • 如果缩写,需在代码中统一使用。例如,num 代表 number,则全程使用 num

这样命名标识符可以让代码更清晰、更一致。

image-20250521164739687

规则

  • 文件名必须全部小写。
  • 单词之间用下划线分隔。
  • 文件后缀只能是 .h.c

正确示例

  • bw_media_scan_service.c
  • bw_media_scan_service.h

错误示例

  • MediaScanner.inc (文件名不对)
  • mediascanner.cc (文件名和后缀都不对)
  • Media_Scanner.hpp (文件名和后缀都不对)

image-20250521165229163

image-20250521165317163

image-20250521165423756

image-20250521165442890

image-20250521165606544

image-20250521171346648

智能汽车技术培训-操作系统基础

image-20250521171816673

image-20250521171850212