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
};
特点
- 隐式转换为整数: 枚举值可以直接转换为整数。
Color c = Red;
int value = c; // value 的值为 0
- 作用域污染: 枚举值在全局作用域内,可能与其他枚举或变量名冲突。
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
};
特点
- 强类型: 枚举值不会隐式转换为整数,必须显式转换。
Color c = Color::Red;
int value = static_cast<int>(c); // 必须显式转换
- 作用域限定: 枚举值的作用域限定在枚举类型内,避免名称冲突。
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++有效地避免了名称冲突问题,使得代码的组织和管理更加清晰和灵活。