Skip to content

C++基础

看完下面网站的1-6章内容↓ https://www.dotcpp.com/course/cpp/

C++ stl容器学习

C++的STL(Standard Template Library,标准模板库)是C++标准库的核心部分,它是一套通用的、高效的模板类和函数库,用于处理数据结构和算法。STL的设计目标是提供一种通用的、可复用的编程工具,帮助程序员更高效地编写代码,同时减少重复劳动。

最常用的是 - std::vector - std::list - std::map

教程网站↓ https://zhuanlan.zhihu.com/p/564057584

C++标准学习

C++有不同的版本标准如 - C++ 98 - C++ 11 :最常用的版本 - C++ 20 - 其他版本

不同的标准可能在语法,函数API,功能上有一些差距 合格的C++工程师应该了解常用的API的版本差异 跳转文档

嵌入式开发中C++替代C的常用语法

nullptr

问题: 在 C 语言中,NULL 通常被定义为 0 或 (void*)0,这可能导致类型不明确和潜在的错误。例如:

void foo(int);
void foo(char*);

foo(NULL);  // 调用 foo(int) 还是 foo(char*)?

由于 NULL 可能是 0,编译器可能调用 foo(int),而不是预期的 foo(char*)。

解决: C++11 引入了 nullptr,它是一个明确的空指针常量,类型为 std::nullptr_t,可以隐式转换为任何指针类型,但不能转换为整数类型。这消除了歧义:

foo(nullptr);  // 明确调用 foo(char*)

枚举enum

枚举是 C++ 中的一种用户定义类型,用于定义一组命名的常量,提高代码的可读性和可维护性。C++ 中有两种枚举类型:普通枚举和强类型枚举。


1. 普通枚举(C-style enum)

定义

enum Color {
    Red,    // 默认值为 0
    Green,  // 默认值为 1
    Blue    // 默认值为 2
};

特点

  1. 隐式转换为整数: 枚举值可以直接转换为整数。
   Color c = Red;
   int value = c;  // value 的值为 0
  1. 作用域污染: 枚举值在全局作用域内,可能与其他枚举或变量名冲突。
   enum Color { Red, Green, Blue };
   enum TrafficLight { Red, Yellow, Green };  // 错误:Red 和 Green 重复定义

C++普通枚举与C语言的不同

  • C语言不使用typedef的枚举类型在声明变量时需要加上enum关键字
enum Color c = Green;  // C语言需要写 enum 关键字
Color c = Green;  // C++不需要写 enum 关键字

C语言通过使用typedef也可以不加关键字

#include <stdio.h>

typedef enum {
    Red,
    Green,
    Blue
} Color;

int main() {
    Color c = Green;  // 直接使用 Color
    printf("Color value: %d\n", c);  // 输出 1
    return 0;
}

2. 强类型枚举(enum class,C++11 引入)

定义

enum class Color {
    Red,    // 默认值为 0
    Green,  // 默认值为 1
    Blue    // 默认值为 2
};

特点

  1. 强类型: 枚举值不会隐式转换为整数,必须显式转换。
   Color c = Color::Red;
   int value = static_cast<int>(c);  // 必须显式转换
  1. 作用域限定: 枚举值的作用域限定在枚举类型内,避免名称冲突。
   enum class Color { Red, Green, Blue };
   enum class TrafficLight { Red, Yellow, Green };  // 合法,不会冲突

constexpr

问题: 在 C 语言中,常量表达式通常通过 #define 或 const 定义,但这些方式在编译时无法保证表达式的常量性,且无法用于需要编译时常量的场合(如数组大小、模板参数等)。

解决: C++11 引入了 constexpr,允许在编译时计算表达式的值,确保其常量性。constexpr 可以用于变量、函数和对象构造函数,这种方式比 #define 更加安全和灵活,因为 constexpr 受类型检查和作用域限制,你不用担心你在某个文件中定义的constexpr变量和某个角落的变量名称冲突了

#define PI 3.14159  // C 风格的常量定义

constexpr double pi = 3.14159;  // C++11 风格的常量定义

constexpr int square(int x) {
    return x * x;
}

int arr[square(3)];  // 数组大小为编译时常量


类型转换 cast

C++ 引入了四种类型转换运算符,分别用于替代 C 语言中的不同类型转换场景。这些转换运算符提供了更明确的语义和更强的类型检查,从而减少了潜在的错误。

static_cast

用途: - 用于相关类型之间的转换,如数值类型转换、指针类型转换(在继承体系中的向上或向下转换)。 - 不能用于无关类型之间的转换(如 int* 到 double*)。

替代 C 语言中的用法: - 替代 C 语言中的隐式类型转换或显式 (type) 转换 - 用于枚举类型的转换

  double d = 3.14;
  int i = (int)d;  // C 风格
  ```
  ```cpp
  double d = 3.14;
  int i = static_cast<int>(d);  // C++ 风格

示例:

int a = 10;
double b = static_cast<double>(a);  // 数值类型转换

enum class Color { Red, Green, Blue };
enum class Fruit { Apple, Orange, Banana };

Color color = Color::Red;
int colorValue = static_cast<int>(color);  // 枚举类型转换为整数

int fruitValue = 1;
Fruit fruit = static_cast<Fruit>(fruitValue);  // 整数转换为枚举类型

const_cast

用途: - 用于添加或移除 const 或 volatile 修饰符。 - 通常用于修改指针或引用的 const 属性。

替代 C 语言中的用法: - 替代 C 语言中通过强制转换修改 const 属性的用法。

  const int* p = &x;
  int* q = (int*)p;  // C 风格
  const int* p = &x;
  int* q = const_cast<int*>(p);  // C++ 风格

示例:

const char* str = "Hello, World!";
char* modifiableStr = const_cast<char*>(str);  // 移除 const 属性
modifiableStr[0] = 'h';  // 修改字符串的第一个字符

reinterpret_cast

用途: - 用于无关类型之间的低级别转换,如指针类型之间的转换、指针与整数之间的转换。 - 不进行类型检查,可能导致未定义行为。

替代 C 语言中的用法: - 替代 C 语言中通过强制转换进行的低级别类型转换。

  int* p = (int*)0x1234;  // C 风格
  ```
  ```cpp
  int* p = reinterpret_cast<int*>(0x1234);  // C++ 风格

示例:

int a = 10;
int* p = &a;
char* c = reinterpret_cast<char*>(p);  // 将 int* 转换为 char*

dynamic_cast

用途: - 用于多态类型之间的向下转换(从基类指针或引用转换为派生类指针或引用)。 - 在运行时检查转换的安全性,如果转换失败,返回 nullptr(对于指针)或抛出 std::bad_cast 异常(对于引用)。

替代 C 语言中的用法: - C 语言中没有直接对应的功能,因为 C 语言不支持运行时类型检查。 - 在 C 语言中,通常需要手动维护类型信息来实现类似功能。

示例:

Base* base = new Derived();
Derived* derived = dynamic_cast<Derived*>(base);  // 安全的向下转换
if (derived) {
    // 转换成功
} else {
    // 转换失败
}

auto

auto 关键字让编译器根据变量的初始化表达式自动推导其类型。例如:

auto x = 10;       // x 的类型被推导为 int
auto y = 3.14;     // y 的类型被推导为 double
auto z = "hello";  // z 的类型被推导为 const char*

注意滥用auto会导致代码可读性下降 建议只在接收函数返回值时使用auto 例如

auto time=xTaskGetTickCount();//使用auto
TickType_t time=xTaskGetTickCount();//不使用auto

namespace

在C++中,namespace(命名空间)的引入主要是为了解决C语言中的名称冲突问题。在C语言中,所有的全局变量、函数和类型都共享同一个全局命名空间,当项目规模较大或使用多个库时,很容易出现名称冲突。namespace通过将相关的代码组织到一个命名空间中,避免了不同库或模块之间的名称冲突。

示例

假设在C语言中,有两个库分别定义了名为print的函数:

// 库A
void print(const char* message) {
    printf("Library A: %s\n", message);
}

// 库B
void print(const char* message) {
    printf("Library B: %s\n", message);
}

当这两个库一起使用时,编译器会报错,因为print函数被重复定义。

使用命名空间解决冲突

在C++中,可以通过namespace将这两个库的print函数分别放在不同的命名空间中,从而避免冲突:

// 库A
namespace LibraryA {
    void print(const char* message) {
        std::cout << "Library A: " << message << std::endl;
    }
}

// 库B
namespace LibraryB {
    void print(const char* message) {
        std::cout << "Library B: " << message << std::endl;
    }
}

在使用时,可以通过命名空间来区分这两个print函数:

int main() {
    LibraryA::print("Hello from Library A");
    LibraryB::print("Hello from Library B");
    return 0;
}

输出结果

Library A: Hello from Library A
Library B: Hello from Library B

通过使用namespace,C++有效地避免了名称冲突问题,使得代码的组织和管理更加清晰和灵活。