C++从0到1
左值和右值:
左值 | 左值是可以位于赋值操作左边的表达式。意味着左值代表一个对象的身份内存中的具体位置。可以被取地址,并且可以位于赋值操作的左边或右边 |
右值 | 右值是不能位于赋值操作左边的表达式。右值代表一个对象的值,通常是一个临时对象。右值不能被取地址,通常只能位于赋值操作的右边 |
C++11 | 纯右值(Pure Rvalue):这是传统的右值,如临时对象或字面量。 将亡值(Xvalue):这是一种特殊的右值,它表示一个即将被移动的对象。在C++11中,移动语义允许资源(如动态内存)从将亡值转移到另一个对象,而不需要进行复制。 |
左值引用 | 可以理解为对左值的引用,那么右边的就需要可取地址,或者用const引用形式,但const只能通过引用读取输出,不能修改数据 |
右值引用 | 可以理解为对右值的引用。对一个临时对象或即将销毁的对象不想赋值时的引用, int&& ref = 10;右值引用不能绑定到左值上,但可以绑定到右值上。右值引用通常用于移动语义和性能优化。 |
移动语义与完美转发:
移动语义和完美转发是C++11引入的两个重要特性,目的是提高性能,减少不必要的对象复制和临时对象的创建。
移动语义允许你从一个函数返回一个大对象时不进行复制,而是移动它转移所有权,是一种优化资源管理机制。这是通过使用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函数来实现的,主要用于在模板中转发函数参数。它的主要目的是实现高效的参数传递,以保持参数的值类别(左值或右值),并避免不必要的拷贝。
移动语义 | 完美转发 |
| |
列表初始化:
列表初始化是C++11引入的一种新的初始化方式,它允许使用大括号 {} 来初始化各种类型的对象包括基本数据类型、类对象、数组、标准库容器等。列表初始化的好处:简洁性,类型安全:可以防止窄化转换(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对象p,x=1,y=2 |
标准容器 | 防止窄化转换 int x = {3.5};// 错误:不能从double到int窄化转换: | |
std::vector<int> 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_ptr<A> ptr1(new A()); // 创建 unique_ptr// std::unique_ptr<A> ptr2 = ptr1; // 错误:不能拷贝std::unique_ptr<A> 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_ptr<A> ptr1(new A()); // 创建 shared_ptr{std::shared_ptr<A> 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_ptr<A> ptr1(new A()); // 创建 shared_ptrstd::weak_ptr<A> 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 的 std::get<N>(); 它提供了一种安全且类型安全的方式来访问特定索引的元素。 |
兼容性 | 与 STL 兼容: std::array 可以与标准模板库(STL)无缝协作,这使得在需要容器的场合下,使用 std::array 可以更轻松地利用 STL 的强大功能。 |
内存性能 | 内存分配: std::array 在栈上分配内存(与普通数组相同),通常比动态分配更高效,避免了动态内存管理(如使用 new 和 delete)的开销。 |
复制和赋值 | : std::array 可以很方便地进行复制和赋值操作,符合现代 C++ 的行为。 |
int main() {// 创建一个 std::array,大小为 5(模板需要大小参数),元素类型为 intstd::array<int, 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); 这个函数接受一个已经构造好的对象, 并将其复制(或移动)到 vector 的末尾。 | 需要先创建一个对象,然后将其“推入”到 vector 中。这意味着可能会发生一次复制或移动操作,那这就会影响性能 |
emplace_back | template <class... Args> 并直接在 vector 的末尾构造这个对象, 避免了不必要的复制或移动开销。 | 直接在 vector 内部构造对象,因此没有复制或移动的开销,性能更好。它采用构造对象所需的所有参数,并在内部使用这些参数创建对象。 |
std::vector<std::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 都是用于存储键值对数据结构的容器,主要区别如下
map | unordered_map |
map 是基于红黑树实现的,保持元素有序。 | unordered_map 是基于哈希表实现的,不保持元素的顺序。 |
map 的查找、插入和删除操作的时间复杂度为 O(log n),因为它需要维护元素的顺序。 | unordered_map 的平均查找、插入和删除操作时间复杂度为 O(1),但在最坏情况下可能会降至 O(n)(例如,哈希冲突非常严重)。 通常使用更多内存来存储哈希表的桶和处理冲突。 |
使用场景: 需要有序数据:较少的元素数量。 | 需要频繁快速查找、插入和删除元素,且不关心顺序, 大规模数据集:在处理非常大的数据集时, |
迭代器与指针:
在 C++ 中,迭代器和指针都是用于访问和遍历数据结构(如数组、链表、容器等)的工具
指针 | 迭代器 |
是一种直接指向内存地址的变量,可以用来直接访问该地址上的数据。 语法:int* p = &var;(指向变量 var 的指针) | 是一种抽象的数据类型,用于遍历容器(如 STL 中的容器:vector、list、map 等)。 迭代器与容器的具体实现无关,可以被看作是指向容器元素的一个“代理”。 语法:std::vector<int>::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
RAII(Resource 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_guard | unique_lock |
简单且轻量级的所管理类,在构造时自动锁定互斥体(mutex),在析构时自动解锁互斥体。不可以显示解锁也不支持锁的转移(命周期完全与作用域绑定) | 提供更灵活的锁定功能。允许显示的锁定与解锁操作,和锁的转移(不可赋值),可以在在构造后手动锁定和解锁互斥体。可以在需要时临时解锁,再重新锁定。 |
| |
都是用于管理互斥量(mutex)的类,它们可以帮助我们避免死锁和资源泄漏。尽管它们都用于锁定互斥体 |
thread:
thread 是用于创建和管理线程的一个类。在使用 std::thread 时,通常会用到 join 和 detach 函数来处理线程的生命周期。
join | detach |
join 方法会使调用线程(通常是主线程)等待被调用的线程完成执行。当一个线程调用 join 时,它会阻塞,直到被调用线程结束。其实是一种同步机制,调用 join 后,线程会处于“可合并”状态,直到其执行完毕。一旦线程被 join,该线程的资源会被清理(即被销毁) | detach 方法会将线程与调用线程分离,使调用线程不再等待该线程的完成。被分离的线程在后台运行直到完成,独立于调用线程的生命周期,并且会在线程结束时自动清理资源。调用 detach 后,主线程和它无法再通信程,也不能再调用 join。 如果主线程在 detach 线程完成之前退出,程序将会终止,可能会导致未定义行为。尽量避免在使用 detach 的线程中访问主线程的资源,以避免悬空指针或数据竞争等问题。 |
|
那如何设计一个线程安全的类:涉及到多线程编程的几个核心概念,包括互斥量、条件变量和原子操作等。
#include <iostream>
#include <mutex>
#include <thread>
//引入标准库
class ThreadSafeCounter {
public:ThreadSafeCounter() : count(0) {}// 增加计数void increment() {std::lock_guard<std::mutex> lock(mutex_); // 加锁++count; // 修改共享数据}// 获取计数int getCount() {std::lock_guard<std::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 <vector>std::atomic<int> counter(0); // 声明一个原子变量
void increment() {for (int i = 0; i < 1000; ++i) {counter++; // 原子加法}
}
int main() {const int num_threads = 2;std::vector<std::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 会以安全的方式处理拷贝,确保数据不会被错误覆盖。可能需要先检查重叠情况,所以效率会低一些 |
|
还有一个专门针对字符串赋值的函数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::function<void()> 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 表达式:是一种在C++11引入的方式,用于定义匿名函数。
适用场景:1)简洁性:在需要快速定义小函数时,使用Lambda更加简洁。
2)临时用途:适用于临时计算,避免创建单独的函数。
3)捕获上下文:可以捕获周围的变量,可以在函数体内直接使用这些变量。
int main() {std::vector<int> 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::function<void(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++中的一种强大特性,它允许编写与类型无关的代码,使得同一段代码可以处理不同类型的数据。模板分为函数模板和类模板
函数模板 | 类模板 |
函数模板允许定义一个通用的函数,能够处理不同类型的参数。 | 类模板允许定义一个通用的类,可以处理不同类型的成员变量。 |
优点: |
栈与堆内存
栈内存 | 堆内存 | |
分配方式 | 由编译器自动管理。在函数调用时分配,并在函数结束时释放。存储局部变量和函数参数。 | 由程序员手动管理。使用 new 关键字分配,使用 delete 释放。适用于动态分配内存,需要在程序运行时确定大小。 |
生命周期 | 生命周期与函数调用的生存期相同。 当函数返回时,所有栈内存被自动释放。 | 生命周期由程序员控制。 需要手动释放,若未释放将导致内存泄漏。 |
存储限制 | 一般较小,通常为几MB(取决于系统) 适合存储小型对象 | 较大,通常受限于系统物理内存。 可用于存储大型或不确定大小的对象 |
访问 | 访问速度较快,因为栈是连续的内存区域,存储的变量是直接可访问的 | 访问速度较慢,内存分配和释放涉及更复杂的管理,需要通过指针访问 |
栈内存:自动管理,存储局部变量,速度快,存储空间小,生命周期短。 |
相关文章:

C++从0到1
左值和右值: 左值左值是可以位于赋值操作左边的表达式。意味着左值代表一个对象的身份内存中的具体位置。可以被取地址,并且可以位于赋值操作的左边或右边右值右值是不能位于赋值操作左边的表达式。右值代表一个对象的值,通常是一个临时对象…...

VMware Tools 安装和配置
1. 使用 ISO 映射文件,并且选择.iso文件 2. 启动虚拟机,如果 VMware Tools 是灰色的,那么卸载 open-vm-tools(不要重装),重新启动虚拟机。卸载可以参考:重装 open-vm-tools-CSDN博客 3. 拷贝挂载…...

云原生化 - 基础镜像(简约版)
根据最佳实践的建议,每个容器只做一件事,并尽可能减少基础镜像的内容,这里列举一些常用的基础镜像,可供选择。(完整版请查阅这里) 基础镜像推荐 1. BusyBox 描述:一个包含了许多常用UNIX工具的…...

云计算相关
文章目录 一、虚拟化1、虚拟化技术概述特点 2、虚拟化与云化3、计算虚拟化分类与作用常见的计算服务架构 4、存储虚拟化5、网络虚拟化 二、行业管理规章制度1、服务器管理制度访问控制变更管理备份与恢复监控与审计 2、操作系统安全管理规范更新与补丁管理用户账号管理防火墙与…...

人机协作:科技与人类智慧的融合
随着科技的飞速发展,越来越多的领域开始借助人工智能(AI)和自动化技术来提升工作效率。人机协作(Human-Machine Collaboration)这一概念逐渐成为现代技术进步的核心。它不仅改变了我们的工作方式,也在重新定…...

keras yolo8目标检测
是从coco数据集提取其中的veh_ids[3,6,8,10] labels[car,bus,truck,traffic light]来做目标检测,分别表示汽车,公交车,卡车,交通灯,用的backbone keras_cv.models.YOLOV8Backbone.from_preset( "yolo_v8_m_backbone_coco" ),不用预训练…...

PowerJob做定时任务调度
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、区别对比二、使用步骤1. 定时任务类型2.PowerJob搭建与部署 前言 提示:这里可以添加本文要记录的大概内容: PowerJob是基于java开…...

前后端分离开发YApid
开头先声明以下,这篇主要用于概念的介绍…… 在当今的互联网应用开发中,前后端分离逐渐成为主流的开发模式。相比于传统的前后端混合开发,这种新模式在灵活性、可维护性和团队协作等方面具有显著优势。 前后端混合开发 在前后端混合开发模式…...

leetcode 22.括号生成
思路:dfs回溯 其实这道题看起来很像栈,但考虑到多种可能方案输出,我们需要用dfs来做。 乍一看好像没啥思路。我们可以从括号的特点入手,括号我们知道都是成对存在的,那么无论多少对括号,其实第一个符号肯…...

如何启动一个OpenSearch
创建两个集群,标注 不含备用节点 选择集群版本和配置集群版本 冷热存储和专用主节点这个按需开启 然后是网络,是否使用自定义域名,集群开在VPC还是公网上。 选择是否开启认证: 访问策略,其实就是资源策略 维护时段…...

自媒体工具箱 v1.0,支持涂抹加水印、无水印下载、加水印、消除原声、视频压缩
自媒体工具箱,打开App,非常干净清爽,只有视频工具板块,总共有11个功能,包括涂抹加水印、无水印下载、加水印、消除原声、视频压缩等功能。...

python 自学总结
# 这是一个代码的注释 注释可以使用# 一般而言#号后面要加空格表示规范,多行注释使用“”“ ”“”符合包裹 # type()这个是判断变量类型# 变量的形式 meny 50 # 自己定义叫标识符 不可以使用关键字 不可以用字母开头 规范性 下划线命名法 英文字母全部小写 …...

Java - WebSocket
一、WebSocket 1.1、WebSocket概念 WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2…...

【大模型】RMS Normalization原理及实现
1.RMS Normalization的原理 说RMS Normalization之前,先讲Layer Normalization 和 Batch Normalization。 BN和LN是两种常见的归一化方法。它们的目的是帮助模型训练更稳定、收敛更快。BN是在Batch维度上进行归一化,即对同一batch中每个特征维度的值进行…...

视觉检测系统实时识别工地安全帽佩戴情况
在建筑工地上,工人佩戴安全帽是确保施工安全的基本措施。然而,工人有时因疏忽或其他原因未能及时佩戴安全帽,这可能导致严重的安全隐患。传统的人工监督往往无法实现对工地的全覆盖或全天候监控,效率低下,容易出现漏检…...

【element-tiptap】报错Duplicate use of selection JSON ID cell at Selection.jsonID
我是下载了element-tiptap 给出的示例项目,在本地安装依赖、运行报错了, 报错截图: 在项目目录下找 node_modules/tiptap-extensions/node-modules,把最后的 node-modules 目录名字修改一下,例如修改为 node-modules–…...

STM32工程环境搭建(库函数开发)
目录 1、移植固件库&标准库 2、新建工程 以STM32f401作为例子进行环境搭建 1、移植固件库&标准库 ①桌面创建工程文件夹并且提取内核文件 用户文件:用户自己编写的程序文件 .c .h文件 .c文件:具体函数功能源代码 .h文件:宏定义…...

大数据新视界 --大数据大厂之大数据如何重塑金融风险管理:精准预测与防控
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

【C# 网络编程】基本概念
基本概念 OSI模型 背景: 为了统一和兼容不同计算机厂商的网络体系结构概念,国际标准化组织(ISO)在1979年提出了OSI参考模型(Open System Interconnection,) 结构 物理层(Physica…...

系统架构设计师-下午案例题(2018年下半年)
1.某文化产业集团委托软件公司开发一套文化用品商城系统,业务涉及文化用品销售、定制、竞拍和点评等板块,以提升商城的信息化建设水平。该软件公司组织项目组完成了需求调研,现已进入到系统架构设计阶段。考虑到系统需求对架构设计决策的影响,项目组先列出了可能影响系统架…...

StarRocks报错:Getting analyzing error. Detail message: Unknown database ‘你的库名‘.
在starrocks上进行业务sql压力测试的时候,当并发提高到一定阈值就会报下面这个错误 背景描述:starrocks上有一张明细主表,维度表均是通过创建外部mysql catalog的方式使用的。 java.sql.SQLSyntaxErrorException: Getting analyzing error.…...

【原创教程】电气电工23:电气柜的品牌及常用型号
电气电工要清楚常用的电气柜品牌及型号,对于电器柜的选择,现在我们一般常用的品牌有3个。分别是好夫满、上海上海桐赛电气和南京巴哈曼电气,还有一种就是网上订制。 一、好夫满系列电气箱 好夫满有很多种类的机箱,EB精巧控制箱系列、KL接线箱系列、BKL不锈钢接线箱系列、…...

AI引起用人格局变动,个人如何应对这一趋势
大家好,我是Shelly,一个专注于输出AI工具和科技前沿内容的AI应用教练,体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具,拥抱AI时代的到来。 人工智能的发展带来的就业结构变革…...

小程序项目实践(一)--项目的初始化以及前期的准备工作
目录 1.起步 1.1 uni-app 简介 1.2 开发工具 1.2.1 下载 HBuilderX 1.2.2 安装 HBuilderX 1.2.3 安装 scss/sass 编译 1.2.4 快捷键方案切换 1.2.5 修改编辑器的基本设置 1.3 新建 uni-app 项目 1.4 目录结构 1.5 把项目运行到微信开发者工具 1.6 使用 Git 管理项目 …...

宝藏CSS样式网站,开发一些酷炫的特效
一、Uiverse 地址:Uiverse | The Largest Library of Open-Source UI elementshttps://uiverse.io/ 项目包含了我们常用到的一些组件,例如:按钮Button、多选框Checkbox、胶囊按钮Switch、加载特效Loading、输入框Input、表单Form、提示框To…...

vscode报错No module named ‘Crypto‘
背景 在Windows系统下,使用vscode的Run Code命令执行程序时,会报错 from Crypto.Cipher import AES ModuleNotFoundError: No module named Crypto有可能是因为当前操作系统存在两个版本的Python,而安装的Crypto仅对应于其中的一个Python版本…...

机器学习中的多模态学习:用C/C++实现高效模型
引言 多模态学习(Multimodal Learning)是一种机器学习技术,它旨在整合多种数据类型(例如图像、文本、音频、传感器数据等)来提升模型的预测精度和泛化能力。其应用领域包括情感分析、多模态推荐系统、智能驾驶、语音识…...

Java 运行机制及运行过程
Java的运行机制是基于Java虚拟机(Java Virtual Machine,JVM)的。Java程序在运行时,首先需要将源代码通过Java编译器编译为字节码文件(.class文件),然后由JVM解释执行或通过即时编译器࿰…...

IC开发——数字电路设计简介
1. 前言 我们说的数字电路,一般是指逻辑数字电路,即通过逻辑门组合成的电路,也即我们常说的逻辑IC。IC除了逻辑IC之外,还有模拟IC,存储IC等。 IC设计,需要学习数字电路,需要学习Verilog/VHDL等…...

openmmlab实现图像超分辨率重构
文章目录 前言一、图像超分辨率重构简介二、mmmagic实现图像超分 前言 超分辨率重构技术,作为计算机视觉领域的一项重要研究课题,近年来受到了广泛关注。随着科技的飞速发展,人们对图像质量的要求越来越高,尤其是在智能手机、监控…...