慈城旅游网站建设策划书,企业网页设计多少钱,沈阳定制型网站建设,广州市企业网站制作公司左值和右值#xff1a; 
左值左值是可以位于赋值操作左边的表达式。意味着左值代表一个对象的身份内存中的具体位置。可以被取地址#xff0c;并且可以位于赋值操作的左边或右边右值右值是不能位于赋值操作左边的表达式。右值代表一个对象的值#xff0c;通常是一个临时对象…左值和右值 
左值左值是可以位于赋值操作左边的表达式。意味着左值代表一个对象的身份内存中的具体位置。可以被取地址并且可以位于赋值操作的左边或右边右值右值是不能位于赋值操作左边的表达式。右值代表一个对象的值通常是一个临时对象。右值不能被取地址通常只能位于赋值操作的右边C11纯右值Pure Rvalue这是传统的右值如临时对象或字面量。 将亡值Xvalue这是一种特殊的右值它表示一个即将被移动的对象。在C11中移动语义允许资源如动态内存从将亡值转移到另一个对象而不需要进行复制。左值引用可以理解为对左值的引用那么右边的就需要可取地址或者用const引用形式但const只能通过引用读取输出不能修改数据右值引用可以理解为对右值的引用。对一个临时对象或即将销毁的对象不想赋值时的引用 int ref  10;右值引用不能绑定到左值上但可以绑定到右值上。右值引用通常用于移动语义和性能优化。 
移动语义与完美转发 
移动语义和完美转发是C11引入的两个重要特性目的是提高性能减少不必要的对象复制和临时对象的创建。 
移动语义允许你从一个函数返回一个大对象时不进行复制而是移动它转移所有权是一种优化资源管理机制。这是通过使用std::move函数来告诉编译器你希望对象被移动而不是复制节省时间和内存。std::move 是一个标准库函数其定义在 utility 头文件中。它的作用是将一个左值转换为一个右值。为了支持移动语义通常需要定义 移动构造函数 和 移动赋值运算符。默认原对象不在使用原对象到底能不能用取决于你的移动构造函数和移动赋值函数如何实现如果还是拷贝则原对象还可以使用如果原对象内存里废弃掉新对象使用原对象内存那原对象不可以用 
#include iostream
#include utility // for std::move
class MyClass {
public:MyClass(int size) : data(new int[size]), size(size) {std::cout  Resource acquired\n;}// 移动构造函数MyClass(MyClass other) noexcept : data(other.data), size(other.size) {other.data  nullptr; // 置空其他对象的资源以避免双重释放other.size  0;std::cout  Resource moved\n;}~MyClass() {delete[] data; // 释放资源std::cout  Resource released\n;}public:int* data;int size;
};int main() {MyClass obj1(10); // 创建一个对象MyClass obj2  std::move(obj1); // 移动 obj1 的资源到 obj2// 在此之后obj1 的资源已被转移制造新的内容不能再使用 obj1return 0;
}完美转发允许你在模板函数中完全保留函数参数的左值和右值属性。这是通过模板中的forward函数来实现的主要用于在模板中转发函数参数。它的主要目的是实现高效的参数传递以保持参数的值类别左值或右值并避免不必要的拷贝。 
移动语义完美转发 #include iostream
#include vector
#include utility // for std::move
std::vectorint returnByValue(std::vectorint vec) {return vec; // 按值返回可能会发生复制
}
std::vectorint returnByMove(std::vectorint vec) {return std::move(vec); // 使用移动语义返回
}
int main() {std::vectorint largeVector;// 填充大对象for (int i  0; i  1000000; i) {largeVector.push_back(i);}// 按值返回可能会发生复制std::vectorint copied  returnByValue(largeVector);// 使用移动语义返回避免了复制std::vectorint moved  returnByMove(std::move(largeVector));return 0;
}   #include iostream
#include utility 
// 包含 std::forward// 一个示例函数接受一个整数参数
void process(int i) {std::cout  Lvalue:   i  std::endl;
}
void process(int i) {std::cout  Rvalue:   i  std::endl;
}
// 完美转发的模板函数
templatetypename T
void forwardToProcess(T arg) {// 使用 std::forward 将参数转发process(std::forwardT(arg)); 
}
int main() {int x  10;// 使用左值调用forwardToProcess(x);   // Lvalue: 10// 使用右值调用forwardToProcess(20);  // Rvalue: 20return 0;
}列表初始化 
列表初始化是C11引入的一种新的初始化方式它允许使用大括号 {} 来初始化各种类型的对象包括基本数据类型、类对象、数组、标准库容器等。列表初始化的好处简洁性类型安全可以防止窄化转换narrowing conversion即不会自动转换数据类型减少错误。统一性可以用于各种数据结构使得初始化方式统一。   
基础类型数组结构体和类对象 int a{5}; 或者int a{5}; double b{3.14}; int arr[]{1, 2, 3, 4};  struct Point { int x; int y; }; Point p{1, 2}; // 初始化一个Point对象px1y2 标准容器 防止窄化转换 int x  {3.5};// 错误不能从double到int窄化转换 std::vectorint vec{1, 2, 3, 4}; 如果尝试使用列表初始化从一个较大的类型如 double转换为较小的类型如 int编译器会报错。  
智能指针 
智能指针包括std::unique_ptr、std::shared_ptr和std::weak_ptr 
1. std::unique_ptr 原理std::unique_ptr 是一个独占所有权的智能指针利用RAII模式自动管理动态分配的资源意味着同一时间只能有一个 unique_ptr 指向某个对象。当 unique_ptr 被销毁时它所管理的内存会自动释放避免内存泄漏。不允许被复制因为他的拷贝构造被声明为delete可以进行所以权转移可以避免双重释放问题。 当你需要唯一拥有一个对象的所有权且不需要共享它时可以使用 unique_ptr。常用于动态分配的对象确保它们会在不再需要时被清理。 
class A {
public:A() { std::cout  A Constructor\n; }~A() { std::cout  A Destructor\n; }
};int main() {std::unique_ptrA ptr1(new A()); // 创建 unique_ptr// std::unique_ptrA ptr2  ptr1; // 错误不能拷贝std::unique_ptrA ptr2  std::move(ptr1); // 转移所有权return 0;
} 
2. std::shared_ptr 原理std::shared_ptr 允许多个指针共享同一个对象的所有权。它使用引用计数来追踪有多少个 shared_ptr 指向同一个对象。只有当最后一个 shared_ptr 被销毁后所管理的对象才会被释放。 使用场景适用于需要多个对象共享同一个资源的情况比如在多个地方需要引用同一个对象但不希望它立即被销毁。 
class A {
public:A() { std::cout  A Constructor\n; }~A() { std::cout  A Destructor\n; }
};
int main() {std::shared_ptrA ptr1(new A()); // 创建 shared_ptr{std::shared_ptrA ptr2  ptr1; // 共用同一个对象std::cout  Usage Count:   ptr1.use_count()  \n; //2 输出使用计数} // ptr2 超出作用域使用计数减少std::cout  Usage Count:   ptr1.use_count()  \n; //1 仍然可用return 0;
} 
shared_ptr 本身是线程安全的具体来说它的引用计数操作是线程安全的因为对于引用计数做了原子级操作。这意味着你可以在多个线程中安全地读取和复制同一个 shared_ptr。然而shared_ptr 对所指向的对象的访问并不是线程安全的。如果多个线程同时访问和修改同一个对象你仍然需要使用其他同步机制如互斥锁来保证线程安全。 
3. std::weak_ptr 原理std::weak_ptr 是一种不控制所有权的智能指针主要用于解决 shared_ptr 之间的循环引用问题。通过 weak_ptr 可以观察到 shared_ptr 指向的对象但不增加引用计数当 shared_ptr 被销毁时weak_ptr 不会影响其生命周期。 使用场景当你需要引用一个 shared_ptr 指向的对象但不希望阻止它被销毁例如在缓存或观察者模式中使用。 
class A {
public:A() { std::cout  A Constructor\n; }~A() { std::cout  A Destructor\n; }
};int main() {std::shared_ptrA ptr1(new A()); // 创建 shared_ptrstd::weak_ptrA weakPtr  ptr1; // 创建 weak_ptrstd::cout  Use Count:   ptr1.use_count()  \n; //1 输出使用计数if (auto sharedPtr  weakPtr.lock()) { // 检查对象是否仍然存在std::cout  Object is alive\n; //Y} else {std::cout  Object is no longer alive\n;}ptr1.reset(); // 释放 shared_ptr 指向的对象if (auto sharedPtr  weakPtr.lock()) {std::cout  Object is alive\n;} else {std::cout  Object is no longer alive\n; //Y}return 0;
} 
static 
1. 静态变量局部静态变量 定义在函数内部定义的变量可以使用static修饰。 特点该变量在函数调用之间保持其值不会在每次调用时重新初始化。只在第一次调用函数时初始化一次。 使用场景当需要在函数中记住某个状态且不想每次调用时都初始化时。 
void countCalls() {static int callCount  0; // 只初始化一次callCount;std::cout  function called   callCount   times.  std::endl;
}int main() {countCalls(); // 输出 1countCalls(); // 输出 2countCalls(); // 输出 3return 0;
} 
2. 静态全局变量或函数 定义在文件顶部声明的变量使用static修饰。 特点该变量只能在声明它的文件内访问其他文件无法访问。 使用场景有助于限制变量的作用域使其只在当前文件中可见从而避免与其他文件中的同名变量冲突。 
3. 静态成员变量、函数 定义在类中声明的变量、函数使用static修饰。 特点属于类而非类的实例所有实例共享同一个静态变量。而函数不能访问类的非静态成员只能访问静态成员。必须在类外进行初始化。 使用场景当你希望跟踪与类相关的状态而与特定实例无关时。 
class A {
public:static int instanceCount; // 声明静态成员变量A() {instanceCount; // 每次创建实例时增加计数}static void displayMessage() {std::cout  Hello from static function!  std::endl;}
};
int A::instanceCount  0; // 定义并初始化静态成员变量
int main() {A obj1;A obj2;A::displayMessage(); // 调用静态成员函数std::cout  Number of instances:   A::instanceCount  std::endl; // 输出 2return 0;
}STL 
std::array 
std::array 是 C 标准库中的一个容器它封装了一个固定大小的数组。 
类型安全固定大小: std::array 的大小在编译时确定不能在运行时改变它的大小这增加了类型安全性。 类型信息: 与普通数组相比std::array 维护了数组元素的类型信息减少了由于数据类型不匹配而导致的错误。更好的接口 成员函数: std::array 提供了许多方便的成员函数例如 size()、at()、front() 和 back() 等这些函数增强了数组的易用性和可读性。 迭代器: std::array 支持迭代器可以使用范围基于的 for 循环和 STL 算法如 std::sortstd::copy 等。 使用 std::array 的 std::getN(); 它提供了一种安全且类型安全的方式来访问特定索引的元素。 兼容性与 STL 兼容: std::array 可以与标准模板库STL无缝协作这使得在需要容器的场合下使用 std::array 可以更轻松地利用 STL 的强大功能。内存性能内存分配: std::array 在栈上分配内存与普通数组相同通常比动态分配更高效避免了动态内存管理如使用 new 和 delete的开销。复制和赋值: std::array 可以很方便地进行复制和赋值操作符合现代 C 的行为。 
int main() {// 创建一个 std::array大小为 5(模板需要大小参数)元素类型为 intstd::arrayint, 5 arr  {1, 2, 3, 4, 5};// 访问元素std::cout  第一元素:   arr.at(0)  std::endl; // 使用 at() 访问或进行越界检查std::cout  数组大小:   arr.size()  std::endl; // 输出数组大小// 遍历数组也可以使用迭代器for (const auto element : arr) {std::cout  element   ; // 输出每个元素}std::cout  std::endl;return 0;
} 
vector: 
vector 是一个动态数组类。实现了自动增长、随机访问等功能。提供了一些方便的成员函数来管理元素如 push_back、pop_back、erase 等。vector一些方法的区别 
1、 size 和 capacity 的区别 size: 指当前 vector 中实际存储的元素数量通过 vector.size() 方法。 capacity: 指 vector 在不需要重新分配内存的情况下可以容纳的最大元素数量vector.capacity() 方法。如果一个 vector 的 capacity 是 10则在添加更多元素之前它可以存储最多 10 个元素。超过这个数量时vector 会自动扩展。 2、resize 和 reserve 的区别 resize: 改变 vector 的 size使其包含指定数量的元素。如果新 size 比当前 size 大vector 将添加新元素如果没有指定值则新元素默认初始化为0如果新 size 比当前 size 小vector 将删除多余的元素。 
3 、push_back 和 emplace_back 
末尾添加元素函数原型差别push_back void push_back(const T value);  void push_back(T value); 这个函数接受一个已经构造好的对象 并将其复制或移动到 vector 的末尾。 需要先创建一个对象然后将其“推入”到 vector 中。这意味着可能会发生一次复制或移动操作,那这就会影响性能emplace_back template class... Args void emplace_back(Args... args); 这个函数接受构造对象所需的参数 并直接在 vector 的末尾构造这个对象 避免了不必要的复制或移动开销。 直接在 vector 内部构造对象因此没有复制或移动的开销性能更好。它采用构造对象所需的所有参数并在内部使用这些参数创建对象。 
std::vectorstd::string vec;
std::string str  Hello, World!;
vec.push_back(str); // 复制操作
// 或者
vec.push_back(Hello, World!); // 临时对象的复制
vec.emplace_back(Hello, World!); // 直接在 vector 内部构造 
map 和 unordered_map 
在C中map 和 unordered_map 都是用于存储键值对数据结构的容器主要区别如下 
mapunordered_mapmap 是基于红黑树实现的保持元素有序。unordered_map 是基于哈希表实现的不保持元素的顺序。map 的查找、插入和删除操作的时间复杂度为 O(log n)因为它需要维护元素的顺序。unordered_map 的平均查找、插入和删除操作时间复杂度为 O(1)但在最坏情况下可能会降至 O(n)例如哈希冲突非常严重。 通常使用更多内存来存储哈希表的桶和处理冲突。使用场景 需要有序数据较少的元素数量。需要频繁快速查找、插入和删除元素且不关心顺序 大规模数据集在处理非常大的数据集时 
迭代器与指针 
在 C 中迭代器和指针都是用于访问和遍历数据结构如数组、链表、容器等的工具 
指针迭代器是一种直接指向内存地址的变量可以用来直接访问该地址上的数据。 语法int* p  var;指向变量 var 的指针是一种抽象的数据类型用于遍历容器如 STL 中的容器vector、list、map 等。 迭代器与容器的具体实现无关可以被看作是指向容器元素的一个“代理”。 语法std::vectorint::iterator it  vec.begin();vec 是一个 vector 容器指针是基本数据类型包含内存地址。迭代器是类类型的对象可以有多种实现如随机访问迭代器、双向迭代器等通常重载了许多操作符还可以包含额外的信息和功能。直接操控内存可以进行算术运算如移动到下一个地址。 可以指向任何类型的数据。如果不小心使用指针可能会导致悬挂指针、内存泄漏和越界访问等问题。提供统一的接口来访问不同类型的容器。 通常不允许进行算术运算保持了对容器的抽象。由于抽象层次的提高迭代器使用时更安全。例如STL 提供的迭代器会处理边界条件。 指针使用不好会产生野指针和悬挂指针 
野指针悬挂指针是指向不明的、不确定的或已删除的对象的指针原本合法但指向内存被释放了或者重新分配。现在的指向已非想要的结果 解决声明后显示初始化使用智能指针 解决delete之后赋值nulllptr使用智能指针 多态 
多态是一种面向对象编程中的基本概念它允许不同的对象以相同的方式响应相同的消息或方法调用。在C中多态使得一个接口可以被不同的类实现从而提高代码的灵活性和可扩展性。多态其实分为两种类型编译时多态静态多态主要通过函数重载和运算符重载实现。在编译阶段编译器决定调用哪个函数或操作符。运行时多态动态多态主要通过虚函数和继承实现通过基类指针或引用来调用子类的重写函数实现。在运行时根据对象的实际类型决定调用哪个函数。 
虚函数 
虚函数是C中实现多态的一种方式。虚函数是在基类中使用关键字 virtual 声明的成员函数。派生类重写虚函数通过虚函数基类指针或引用可以调用派生类中的重写函数。程序在运行时根据对象的实际类型选择调用哪个函数。 
实现虚函数的关键在于虚函数表vtable和虚函数表指针vptr每个包含虚函数的类都有一个虚函数表Vtable这是一个指针数组存放该类的虚函数地址。对于每个对象实例会有一个指向所在类的虚函数表的指针内存中除了成员变量外额外一个虚指针称为虚指针Vptr。 
构造函数不可以是虚函数构造函数主要用于初始化对象的状态。当一个类的对象被创建时构造函数会被调用。编译器首先分配内存然后调用构造函数来初始化对象。虚函数的机制依赖于虚函数表而虚函数表的建立需要在调用构造函数之后才能完成如果为虚函数会导致对象初始化明确对象的类型和多态机制矛盾对象类型在构造时尚未确定。。 
析构函数需要是虚函数继承体系目的确保在删除对象时通过将基类的析构函数声明为虚函数基类指向派生类调用时派生类的析构函数先被调用然后再析构父类确保所有资源都被正确释放防止资源泄漏。 
explicit、delete、default 
explicit是一个关键字它主要用于构造函数的声明。使用explicit可以避免某些不必要的类型转换确保代码更加安全和可读。隐式转换在C中如果构造函数只有一个参数编译器可以自动将该参数的类型转换为对象类型。这种行为有时会导致意想不到的错误。   
class A
{
public:explicit A(int value){ // 加上explicit// 构造函数代码}
};
void function(A obj)
{// 函数代码
}
int main()
{A obj1(10); // 合法的构造// A obj2  20; // 不合法编译错误function(A(30)); // 合法// function(40); // 不合法编译错误return 0;
}使用explicit可以使代码意图更加明确。程序员在创建对象时需要清楚地指定要创建的对象类型而不是依赖于编译器的隐式转换。适用于单个参数的构造函数对于多个参数的构造函数通常不需要使用explicit因为它们不支持隐式转换。 
delete和default关键词用于管理类的构造函数、析构函数和拷贝/移动操作。delete 用于删除对象的某个特殊成员函数的默认实现表示这个特定操作不能被使。目地控制资源管理如果一个类管理动态分配的资源如内存复制对象可能会导致资源重复释放的潜在问题。举例单例模式通常需要防止对象被复制以保证单例的唯一性。 
default 用于显式地请求编译器生成某些特殊成员函数的默认实现。自定义类当你创建一个自定义类并希望使用编译器生成的成员函数时可以明确地使用 default。 
final 
final关键字主要用于控制类和虚函数的继承行为确保某些类或函数不能被继承或重写。它通常和override关键字一起使用可以显示指出该函数时覆盖基类的某个虚函数且不允许再被派生类覆盖 
类型转换 
static_cast静态转换用于在相关类型之间进行转换比如基本数据类型、类层次结构中的基类和派生类之间的转换。 特点: 检查基本的类型安全。可用于简单类型转换比如 int 转 double。对于类类型可用于向上转换基类指针/引用指向派生类和向下转换派生类指针/引用指向基类但向下转换需要确保类型安全。dynamic_cast动态转换: 主要用于在类层次结构中安全地进行向下转换确保转换的是有效类型。 特点: 需要类为多态即至少有一个虚函数。如果转换失败返回 nullptr对于指针或抛出 std::bad_cast对于引用const_cast常量转换 用于在常量和非常量之间转换尤其是当你需要去掉指针或引用的常量性质时。 特点: 只能用于添加或去掉 const 或 volatile 限定符。不建议在未理解代码意图的情况下使用因为这可能导致未定义行为。reinterpret_cast重解释转换: 用于非常规的类型转换允许你将一种类型的指针或引用转换为另一种完全不相关的类型。 特点: 基本上不进行任何类型安全检查。常用于低级别操作比如将指针转换为整型或者在与硬件相关的编程中。 
volatile 
volatile 关键字是一个用于类型修饰的关键字主要用于告诉编译器特定的变量可能会被异步地改变。1当一个变量被声明为 volatile编译器在访问该变量时不会进行优化。这意味着每次读取该变量的值时编译器都会从其实际内存位置重新读取而不是使用缓存的值。2用于多线程编程在多线程程序中一个线程可能会修改某个变量而另一个线程需要读取这个变量。将该变量声明为 volatile 可以确保一个线程读取的值是最新的防止因编译器优化导致读取到过期值。 3与硬件寄存器交互在嵌入式系统中某些变量可能与硬件寄存器直接关联。使用 volatile 可以确保程序不会因为优化而漏掉对这些寄存器的读写操作。 
volatile 关键字并不能替代线程同步机制如锁它只保证了变量的可见性并不保证操作的原子性。在多线程程序中除了使用 volatile还应该考虑使用其他同步手段如互斥锁mutex等以确保程序的正确性和安全性。 
RAII 
RAIIResource Acquisition Is Initialization是一种在 C 中管理资源如内存、文件句柄、线程等的技术。这种设计模式确保了资源能够在对象的生命周期内被有效管理并在对象被销毁时自动释放资源。 
RAII 的基本原则 资源获取: 当对象被创建时它负责获取和管理所需的资源。 资源释放: 当对象的生命周期结束即对象被销毁时它的析构函数会自动释放那些资源。 异常安全: RAII 确保即使在异常发生的情况下资源也会被正确释放。 RAII 的实现步骤定义类: 创建一个类该类负责管理特定的资源。 构造函数: 在构造函数中获取和分配资源。析构函数: 在析构函数中释放资源。 
class Resource {
public:// 构造函数分配资源Resource() {data  new int[10];  // 动态分配内存std::cout  Resource acquired.\n;}// 析构函数释放资源~Resource() {delete[] data;  // 释放内存std::cout  Resource released.\n;}private:int* data;  // 资源指针
};int main() {{Resource res;  // 创建 Resource 对象获取资源// 在这里可以使用资源例如填充数据等}  // 资源对象的生命周期结束自动释放资源// 这里Resource 对象已经被销毁资源也被释放return 0;
} 
简化资源管理: 不必手动释放资源降低内存泄漏的风险。 异常安全: 任何时候只要对象被销毁资源就会被释放避免了异常情况带来的资源泄露。 清晰的语义: 对象的生命周期与资源的管理相结合使得代码更加清晰易懂。 两种RAII形式的锁管理类lock_guard和unique_lock 
lock_guardunique_lock简单且轻量级的所管理类在构造时自动锁定互斥体mutex在析构时自动解锁互斥体。不可以显示解锁也不支持锁的转移命周期完全与作用域绑定提供更灵活的锁定功能。允许显示的锁定与解锁操作和锁的转移不可赋值可以在在构造后手动锁定和解锁互斥体。可以在需要时临时解锁再重新锁定。 #include iostream
#include mutex
#include threadstd::mutex mtx;void printMessage() {std::lock_guardstd::mutex lock(mtx); // 自动锁定std::cout  Hello from thread!  std::endl;
} // 自动解锁int main() {std::thread t1(printMessage);std::thread t2(printMessage);t1.join();t2.join();return 0;
}  std::mutex mtx;
void printMessage() {std::unique_lockstd::mutex lock(mtx); // 锁定std::cout  Hello from thread!  std::endl;lock.unlock(); // 手动解锁std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 做一些事情lock.lock(); // 重新锁定std::cout  Thread finished work!  std::endl;
} int main() {std::thread t1(printMessage);std::thread t2(printMessage);t1.join();t2.join();return 0;
}  都是用于管理互斥量mutex的类它们可以帮助我们避免死锁和资源泄漏。尽管它们都用于锁定互斥体 
thread 
thread 是用于创建和管理线程的一个类。在使用 std::thread 时通常会用到 join 和 detach 函数来处理线程的生命周期。 
joindetachjoin 方法会使调用线程通常是主线程等待被调用的线程完成执行。当一个线程调用 join 时它会阻塞直到被调用线程结束。其实是一种同步机制调用 join 后线程会处于“可合并”状态直到其执行完毕。一旦线程被 join该线程的资源会被清理即被销毁 detach 方法会将线程与调用线程分离使调用线程不再等待该线程的完成。被分离的线程在后台运行直到完成独立于调用线程的生命周期并且会在线程结束时自动清理资源。调用 detach 后主线程和它无法再通信程也不能再调用 join。 如果主线程在 detach 线程完成之前退出程序将会终止可能会导致未定义行为。尽量避免在使用 detach 的线程中访问主线程的资源以避免悬空指针或数据竞争等问题。  #include iostream
#include thread
#include chrono
void task() {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟工作std::cout  Task completed!  std::endl;
}
int main() {// 使用 joinstd::thread t1(task);t1.join(); // 等待线程 t1 完成std::cout  Thread t1 has joined.  std::endl;// 使用 detachstd::thread t2(task);t2.detach(); // 线程 t2 在后台运行std::cout  Thread t2 has detached.  std::endl;// 等待一段时间让 t2 有机会完成std::this_thread::sleep_for(std::chrono::seconds(3));std::cout  Main thread ended.  std::endl;return 0;
}那如何设计一个线程安全的类涉及到多线程编程的几个核心概念包括互斥量、条件变量和原子操作等。 
#include iostream
#include mutex
#include thread
//引入标准库
class ThreadSafeCounter {
public:ThreadSafeCounter() : count(0) {}// 增加计数void increment() {std::lock_guardstd::mutex lock(mutex_); // 加锁count; // 修改共享数据}// 获取计数int getCount() {std::lock_guardstd::mutex lock(mutex_); // 加锁return count; // 返回共享数据}
private:int count; // 共享数据std::mutex mutex_; // 互斥量
};
void incrementCounter(ThreadSafeCounter counter) {for (int i  0; i  1000; i) {counter.increment();}
}int main() {ThreadSafeCounter counter;std::thread t1(incrementCounter, std::ref(counter));std::thread t2(incrementCounter, std::ref(counter));t1.join(); // 等待线程t1完成t2.join(); // 等待线程t2完成std::cout  Final count:   counter.getCount()  std::endl; // 输出最终计数return 0;
}原子操作代替互斥锁 
#include iostream
#include atomic
#include thread
#include vectorstd::atomicint counter(0); // 声明一个原子变量
void increment() {for (int i  0; i  1000; i) {counter; // 原子加法}
}
int main() {const int num_threads  2;std::vectorstd::thread threads;// 创建多个线程for (int i  0; i  num_threads; i) {threads.emplace_back(increment);}// 等待所有线程完成for (auto t : threads) {t.join();}std::cout  Final counter value:   counter.load()  std::endl; // 输出结果return 0;
}原子操作一个操作是原子的即它要么完全执行要么完全不执行不会受到其他线程的干扰。可以使用std::atomic修饰基本类型它通过CPU提供的原子指令来实现这些不可分割的操作现在CPU会提供一组指令比如CMPXCHG,XADD等原子操作的读或写虽然在某些场景下可以替代锁比如一些基本的计算器或标志位但复杂场景下锁还是较优选择。 
memcpy 和 memmove   
memcpy 和 memmove 都是 C 和 C 中用于内存拷贝的函数 
memcpy memmove  void* memcpy(void* dest, const void* src, size_t n); 功能从源地址 src 拷贝 n 字节到目标地址 dest。  void* memmove(void* dest, const void* src, size_t n); 也从源地址 src 拷贝 n 字节到目标地址 dest但可以处理重叠的内存区 不支持重叠的内存区域。如果源和目标区域重叠使用 memcpy 可能导致未定义行为例如若源地址在目标地址之前拷贝的内容可能会被覆盖导致错误的结果。不处理重叠效率就高支持重叠的内存区域。如果源和目标区域重叠memmove 会以安全的方式处理拷贝确保数据不会被错误覆盖。可能需要先检查重叠情况所以效率会低一些 #include cstring
#include iostream
int main() {char src[]  Hello, World!;char dest[20];// 使用 memcpystd::memcpy(dest, src, 13);std::cout  Using memcpy:   dest  std::endl;// 创建一个重叠的情况char overlap[]  Hello, World!;std::memmove(overlap  7, overlap, 6); // 把Hello,移动到World!前面std::cout  Using memmove with overlap:   overlap  std::endl;return 0;
}还有一个专门针对字符串赋值的函数strcpy: 用于复制一个字符串包括结束的空字符 \0。   char* strcpy(char* dest, const char* src);  它会将 src 指向的字符串复制到 dest 中并在最后添加一个空字符来标识字符串的结束。它适用于处理以空字符结尾的字符串否则可能导致缓冲区溢出如果 src 的长度超过了 dest 的分配空间。为了安全起见可以使用 strncpy指定最大拷贝长度。  
function、bind和lambda 
function、bind和lambda都是处理函数和可调用对象的重要工具。 
1、std::function是C标准库中的一个类模板用于封装任何可调用对象如普通函数、函数指针、成员函数、Lambda 表达式等。 适用场景1) 存储函数指针当你需要将不同的函数存储在同一个容器中时可以使用std::function。 2)  回调机制可以用于实现回调函数提供灵活性。3)多态性允许将不同类型的可调用对象统一处理。 
void sayHello() {std::cout  Hello, World!  std::endl;
}int main() {std::functionvoid() func  sayHello; // 使用std::functionfunc(); // 调用return 0;
} 
2、std::bind 允许你绑定或固定函数的参数生成新的可调用对象。 适用场景1参数绑定当你想提前固定某些参数或改变参数的顺序时。 2适配器模式将一个函数转变为另一个可以接受不同参数的形式。 3简化代码在需要传递函数作为参数时可以减少需要传递的参数数量。 
void printSum(int a, int b) {std::cout  Sum:   a  b  std::endl;
}int main() {auto boundFunc  std::bind(printSum, std::placeholders::_1, 10); // 绑定第二个参数boundFunc(5); // 只需提供一个参数return 0;
} 3、 Lambda 表达式是一种在C11引入的方式用于定义匿名函数。 适用场景1简洁性在需要快速定义小函数时使用Lambda更加简洁。 2临时用途适用于临时计算避免创建单独的函数。 3捕获上下文可以捕获周围的变量可以在函数体内直接使用这些变量。 
int main() {std::vectorint numbers  {1, 2, 3, 4, 5};// 使用Lambda表达式打印每个数字std::for_each(numbers.begin(), numbers.end(), [](int n) {std::cout  n  std::endl;});return 0;
} 
回调函数 
回调函数是指作为参数传递给另一个函数的函数。实际上是把函数的调用权从一个地方转移到另一个地方可以在某些事件发生或特定条件满足时调用这个回调函数。回调函数在C中非常有用主要用于处理异步操作、事件通知和自定义行为。 
异步编程回调函数允许程序在等待某些操作如文件读取、网络请求等完成时继续执行其他操作。当操作完成时程序会调用回调函数来处理结果。 
事件驱动编程在图形用户界面GUI中程序通过事件如按钮点击驱动而回调函数可以定义对这些事件的响应。 
解耦合回调函数使程序的不同部分之间的耦合度降低。可以在不修改主逻辑的情况下轻松更改或添加功能。 
代码复用通过使用回调函数可以将通用逻辑与具体实现分离从而更好地复用代码。 
// 定义一个函数类型的别名
using Callback  std::functionvoid(int);
// 一个接受回调函数的函数
void performOperation(int value, Callback callback) {// 执行某些操作value * 2; // 将值乘以2// 调用回调函数callback(value);
}// 一个简单的回调函数
void myCallback(int result) {std::cout  Callback called with result:   result  std::endl;
}int main() {// 调用performOperation并传入myCallback作为回调performOperation(5, myCallback);return 0;
} 
模板 
C模板是C中的一种强大特性它允许编写与类型无关的代码使得同一段代码可以处理不同类型的数据。模板分为函数模板和类模板 
函数模板类模板 函数模板允许定义一个通用的函数能够处理不同类型的参数。 template typename T
T add(T a, T b) {return a  b;
}   类模板允许定义一个通用的类可以处理不同类型的成员变量。 template typename T
class Box {
private:T item;
public:void setItem(T item) {this-item  item;}T getItem() {return item;}
};   优点 代码重用通过模板可以避免重复编写对不同类型的相似代码增加代码的重用性与通用性。 类型安全使用模板可以在编译时确保类型安全减少运行时错误因此在一些情况下性能可能更高。 简化接口通过模板可以提供统一的接口来处理不同类型简化了用户的使用。缺点 编译时间由于模板代码在编译时实例化可能导致编译时间显著增加。 代码膨胀每种不同的实例化类型都会生成一份代码可能导致可执行文件的大小增加。 错误信息复杂如果模板代码出现错误编译器生成的错误信息通常比较复杂难以理解。 调试难度模板代码的调试可能比较困难尤其是当使用了深层次的模板嵌套和复杂的类型推导时。 可能的限制某些情况下模板可能会受到特定类型特性如拷贝构造函数、赋值操作符等的限制。  
栈与堆内存 
栈内存堆内存分配方式由编译器自动管理。在函数调用时分配并在函数结束时释放。存储局部变量和函数参数。由程序员手动管理。使用 new 关键字分配使用 delete 释放。适用于动态分配内存需要在程序运行时确定大小。生命周期生命周期与函数调用的生存期相同。 当函数返回时所有栈内存被自动释放。生命周期由程序员控制。 需要手动释放若未释放将导致内存泄漏。存储限制一般较小通常为几MB取决于系统 适合存储小型对象较大通常受限于系统物理内存。 可用于存储大型或不确定大小的对象访问访问速度较快因为栈是连续的内存区域存储的变量是直接可访问的访问速度较慢内存分配和释放涉及更复杂的管理需要通过指针访问 栈内存自动管理存储局部变量速度快存储空间小生命周期短。 堆内存手动管理用于动态分配速度较慢存储空间大生命周期长。