1.核心语言特性
1.auto类型推导
auto 关键字允许编译器自动推导变量的类型,简化了复杂类型的声明
// 基础类型推导
auto i = 42; // 自动推导为 int
auto str = "hello"; // 自动推导为 const char*
auto pi = 3.14159; // 自动推导为 double
// 容器类型推导
std::vector<int> numbers{1, 2, 3};
auto iter = numbers.begin(); // 自动推导为 vector<int>::iterator
// 在复杂类型中的应用
std::map<std::string, std::vector<int>> complex_map;
for (const auto& [key, value] : complex_map) { // 结构化绑定配合 auto
// 使用 key 和 value
}
// 当类型名称冗长或复杂时使用
// 当类型显而易见时,可以显式声明
// 迭代器和 lambda 表达式时推荐使用
2.decltype类型推导
用于推导表达式的类型,常用于泛型编程和后置返回类型
// 基础用法
int x = 42;
decltype(x) y = 100; // y 的类型与 x 相同
// 在模板中的应用
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) { // 后置返回类型
return t + u;
}
// 与 auto 配合使用
template<typename Container>
auto getValue(Container& c) -> decltype(c.front()) {
return c.front();
}
// 用于需要依赖表达式类型的场景
// 在模板编程中推导返回类型
// 需要保持类型一致性时使用
3.lambda表达式
lambda 表达式提供了定义匿名函数对象的方式,使代码更加简洁和灵活
// 基本语法:[捕获列表](参数列表) -> 返回类型 { 函数体 }
// 简单示例
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4) << std::endl; // 输出:7
// 捕获外部变量
int multiplier = 10;
auto multiply = [multiplier](int x) { return x * multiplier; }; // 值捕获
auto multiply_ref = [&multiplier](int x) { return x * multiplier; }; // 引用捕获
// 在算法中的应用
std::vector<int> numbers = {3, 1, 4, 1, 5, 9};
std::sort(numbers.begin(), numbers.end(),
[](int a, int b) { return a > b; }); // 自定义排序规则
// 泛型 lambda(C++14)
auto generic_lambda = [](auto x, auto y) { return x + y; };
// 短小的函数逻辑适合使用 lambda
// 注意捕获变量的生命周期
// 复杂逻辑建议使用命名函数
4.范围for循环
// 基础容器遍历
std::vector<int> nums = {1, 2, 3, 4, 5};
for (const auto& num : nums) {
std::cout << num << " ";
}
// 自定义类型遍历
class NumberRange {
public:
NumberRange(int start, int end) : start_(start), end_(end) {}
class iterator {
// 迭代器实现
};
iterator begin() { return iterator(start_); }
iterator end() { return iterator(end_); }
private:
int start_, end_;
};
// 使用
for (auto num : NumberRange(1, 5)) {
std::cout << num << " ";
}
5.nullptr关键字
nullptr 是类型安全的指针空值常量,用来替代传统的 NULL 宏
// 更安全的指针空值表示
void* ptr = nullptr; // 明确表示空指针
std::shared_ptr<int> smart_ptr = nullptr; // 用于智能指针初始化
// 函数重载场景
void process(int i) { std::cout << "整数版本" << std::endl; }
void process(int* p) { std::cout << "指针版本" << std::endl; }
process(nullptr); // 明确调用指针版本
// process(NULL); // 可能导致编译错误,因为 NULL 可能被解释为 0
// 完全替代 NULL 的使用
// 指针初始化时使用
// 在条件判断中使用 if (ptr != nullptr)
6.统一初始化语法
为解决初始化方式不统一、隐式类型转换意外出错等问题,C++使用花括号 {}
提供了统一的初始化语法
// 以前的基本数据类型初始化
int a = 10; // 赋值初始化
int a(10); // 构造函数初始化
// 以前的类类型初始化
std::string str("hello"); // 构造函数初始化
std::string str = "hello"; // 赋值初始化
// 上述容易让开发者产生混淆
// 统一后
// 基本类型初始化
int n{42}; // 直接初始化
std::string str{"hello"}; // 类对象初始化
// 容器初始化
std::vector<int> vec{1, 2, 3, 4, 5}; // 列表初始化
std::map<std::string, int> map{
{"one", 1},
{"two", 2}
};
// 自定义类的初始化
class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {}
private:
int x_{0}; // 成员默认初始化
int y_{0};
};
Point p{10, 20}; // 统一初始化语法
// 优先使用花括号初始化
// 注意防止窄化转换
// 类成员使用默认初始化
7.委托构造函数
允许构造函数调用同一个类的其他构造函数
class Person {
public:
// 主构造函数
Person(const std::string& name, int age, const std::string& address)
: name_(name), age_(age), address_(address) {}
// 委托构造函数
Person(const std::string& name, int age)
: Person(name, age, "未知") {}
Person(const std::string& name)
: Person(name, 0) {}
private:
std::string name_;
int age_;
std::string address_;
};
// 减少构造函数代码重复
// 保持初始化逻辑一致性
// 避免构造函数循环委托
8.右值引用和移动语义
两者提供了资源转移而非拷贝的能力
// 移动构造函数
class Buffer {
public:
Buffer(size_t size) : data_(new char[size]), size_(size) {}
// 移动构造函数
Buffer(Buffer&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
// 移动赋值运算符
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
private:
char* data_;
size_t size_;
};
// 使用示例
Buffer createBuffer(size_t size) {
return Buffer(size); // 返回时触发移动构造
}
// 大对象传递时考虑使用移动语义
// 实现移动语义时注意处理原对象状态
// 移动构造函数和移动赋值运算符应声明为 noexcept
2.智能指针
1.定义与分类
C++中的一个类模板,它能够自动管理动态分配的内存,确保在适当的时候自动释放资源。它是RAII(资源获取即初始化)技术的一个具体实现
主要分类为:shared_ptr(共享式智能指针)、unique_ptr(独占式智能指针)、weak_ptr(弱引用智能指针)
2.shared_ptr
允许多个指针指向同一个对象。通过引用计数机制来管理对象的生命周期,当最后一个指向对象的shared_ptr被销毁时,对象会被自动删除
// 基本使用
shared_ptr<int> sp1 = make_shared<int>(10);
shared_ptr<int> sp2 = sp1; // 引用计数+1
// 引用计数
cout << sp1.use_count(); // 输出2
// 自定义删除器
shared_ptr<int> sp3(new int[10], [](int* p) { delete[] p; });
// 循环引用问题
class Node {
shared_ptr<Node> next; // 可能造成循环引用
};
3.unique_ptr
同一时刻只能有一个指针指向给定的对象。它对于被管理的对象具有唯一的所有权,不允许复制,但可以移动(std::move())
// 基本使用
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = make_unique<int>(20); // 推荐方式
// 所有权转移
unique_ptr<int> ptr3 = std::move(ptr1); // ptr1变为nullptr
// 不允许复制
// unique_ptr<int> ptr4 = ptr2; // 编译错误
// 自定义删除器
unique_ptr<FILE, decltype(&fclose)> fp(fopen("test.txt", "r"), fclose);
4.weak_ptr
是shared_ptr的助手,它指向shared_ptr管理的对象但不增加引用计数。主要用于解决shared_ptr循环引用的问题
void explainCircularReference() {
// 1. 初始创建
shared_ptr<Node> node1 = make_shared<Node>(1); // node1引用计数=1
shared_ptr<Node> node2 = make_shared<Node>(2); // node2引用计数=1
// 2. 建立相互引用
node1->next = node2; // node2引用计数+1=2
// 因为现在有两个shared_ptr指向node2:
// a. 原始的node2
// b. node1->next
node2->prev = node1; // node1引用计数+1=2
// 因为现在有两个shared_ptr指向node1:
// a. 原始的node1
// b. node2->prev
// 3. 函数结束时
// node1和node2的局部变量被销毁,各自引用计数-1
// 但由于相互引用,引用计数仍然为1
// node1被node2->prev引用
// node2被node1->next引用
} // 引用计数永远不为0,无法释放内存
// 解决方案:使用weak_ptr
class Node {
public:
shared_ptr<Node> next;
weak_ptr<Node> prev; // 使用weak_ptr
int data;
Node(int val) : data(val) {}
~Node() {
cout << "Node " << data << " destroyed" << endl;
}
};
void demonstrateWeakPtr() {
// 1. 创建节点
shared_ptr<Node> node1 = make_shared<Node>(1); // 引用计数=1
shared_ptr<Node> node2 = make_shared<Node>(2); // 引用计数=1
// 2. 建立连接
node1->next = node2; // node2引用计数+1=2
node2->prev = node1; // 使用weak_ptr,node1引用计数不变=1
// 3. 访问weak_ptr指向的对象
if (auto ptr = node2->prev.lock()) { // 检查weak_ptr是否有效
cout << "Node2's prev points to: " << ptr->data << endl;
}
// 4. 函数结束时
// node1的局部变量销毁,引用计数-1=0,node1被删除
// node2的局部变量销毁,引用计数-1=1
// node1被删除后,node2->prev自动失效
// node2的引用计数最终变为0,也被删除
} // 两个节点都能正确释放
5.常用用法
// 容器使用
vector<shared_ptr<int>> vec;
vec.push_back(make_shared<int>(10));
// 类成员
class Resource {
unique_ptr<int> data;
public:
Resource() : data(make_unique<int>(0)) {}
};
// 函数返回
unique_ptr<int> createInt() {
return make_unique<int>(42);
}
6.注意事项
// 避免原始指针转换
int* raw = new int(10);
shared_ptr<int> sp(raw);
// shared_ptr<int> sp2(raw); // 危险:双重释放
// 使用make_函数
auto sp = make_shared<int>(10); // 比 shared_ptr<int>(new int(10)) 更安全
// 及时释放
{
shared_ptr<int> sp = make_shared<int>(42);
} // sp离开作用域自动释放
3.并发编程
1.std::thread
提供了创建和管理线程的标准方式
#include <thread>
#include <iostream>
// 基本线程创建
void worker(int id) {
std::cout << "线程 " << id << " 正在执行\n";
}
int main() {
std::thread t1(worker, 1); // 创建线程
std::thread t2([](){
std::cout << "Lambda线程正在执行\n";
});
t1.join(); // 等待线程完成
t2.join();
return 0;
}
2.std::mutex
提供互斥锁机制,保护共享资源
#include <mutex>
class ThreadSafeCounter {
public:
void increment() {
std::lock_guard<std::mutex> lock(mutex_); // RAII方式加锁
++count_;
}
int get() const {
std::lock_guard<std::mutex> lock(mutex_);
return count_;
}
private:
mutable std::mutex mutex_;
int count_{0};
};
3.std::condition_variable
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool done = false;
void producer() {
// 生产10个数据
for (int i = 0; i < 10; ++i) {
{ // 作用域锁
std::lock_guard<std::mutex> lock(mtx); // 自动加锁解锁
data_queue.push(i); // 将数据放入队列
} // 离开作用域,自动解锁
cv.notify_one(); // 通知一个等待的消费者
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产耗时
}
// 生产结束,标记完成
{
std::lock_guard<std::mutex> lock(mtx);
done = true; // 设置结束标志
}
cv.notify_all(); // 通知所有消费者,生产已结束
}
// 消费者
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx); // 可以手动解锁的锁
// 等待条件:队列非空或生产结束
cv.wait(lock, [] {
return !data_queue.empty() || done;
});
// 如果生产结束且队列为空,退出循环
if (done && data_queue.empty()) {
break;
}
// 获取并处理数据
int value = data_queue.front();
data_queue.pop();
lock.unlock(); // 提前解锁,让其他消费者能够访问队列
std::cout << "消费: " << value << std::endl;
}
}
4.std::future/promise
异步编程中的数据传递
std::promise<int> promise;
std::future<int> future = promise.get_future();
// 异步任务
std::thread worker([&promise] {
// 模拟耗时计算
std::this_thread::sleep_for(std::chrono::seconds(2));
promise.set_value(42);
});
// 主线程等待结果
std::cout << "等待结果...\n";
std::cout << "结果: " << future.get() << std::endl;
worker.join();
// 使用 async
auto future2 = std::async(std::launch::async, [] {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
});
4.标准库增加
1.std::tuple
处理异构数据集合
// 创建元组
auto student = std::make_tuple("张三", 20, 3.8);
// 获取元素
std::cout << std::get<0>(student) << std::endl; // 姓名
std::cout << std::get<1>(student) << std::endl; // 年龄
// 结构化绑定(C++17)
auto [name, age, gpa] = student;
// 合并元组
auto info1 = std::make_tuple("张三", 20);
auto info2 = std::make_tuple(3.8, "计算机系");
auto full_info = std::tuple_cat(info1, info2);
2.std::optional
处理可能不存在的值
#include <optional>
std::optional<std::string> getUserName(bool hasName) {
if (hasName) {
return "张三";
}
return std::nullopt;
}
// 使用示例
auto name = getUserName(true);
if (name) {
std::cout << "名字: " << *name << std::endl;
}
// 提供默认值
std::cout << name.value_or("未知用户") << std::endl;
5.模板元编程
模板元编程是在编译期进行的计算和类型操作,主要用于:
- 编译期计算
- 类型转换和判断
- 代码生成
1.计算示例
// 1.编译期计算阶乘
template<unsigned N>
struct Factorial {
static constexpr unsigned value = N * Factorial<N-1>::value;
};
// 递归终止
template<>
struct Factorial<0> {
static constexpr unsigned value = 1;
};
// 使用
constexpr auto result = Factorial<5>::value; // 5! = 120
// 2.斐波那契数列
template<unsigned N>
struct Fibonacci {
static constexpr unsigned value =
Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};
template<>
struct Fibonacci<0> {
static constexpr unsigned value = 0;
};
template<>
struct Fibonacci<1> {
static constexpr unsigned value = 1;
};
2.类型操作
// 1.判断类型是否为指针
template<typename T>
struct IsPointer {
static constexpr bool value = false;
};
// 偏特化
template<typename T>
struct IsPointer<T*> {
static constexpr bool value = true;
};
// 2.移除引用
template<typename T>
struct RemoveReference {
using type = T;
};
template<typename T>
struct RemoveReference<T&> {
using type = T;
};
template<typename T>
struct RemoveReference<T&&> {
using type = T;
};
3.注意事项
- 编译时间:复杂的模板元编程会增加编译时间、应适度使用,避免过度复杂化
- 可读性:模板元编程代码往往难以理解、需要良好的文档和注释
- 调试难度:编译期错误信息复杂、调试工具支持有限
- 维护成本:代码复杂度高、修改需要谨慎
6.性能优化
1. 移动语义
- 通过移动而非拷贝来转移资源所有权
- 避免不必要的深拷贝操作
- 使用右值引用(&&)实现
class String {
public:
// 移动构造函数
String(String&& other) noexcept
: data_(other.data_) {
other.data_ = nullptr; // 源对象置空
}
// 移动赋值运算符
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
other.data_ = nullptr;
}
return *this;
}
private:
char* data_;
};
// 使用场景
String createString() {
return String("hello"); // 自动触发移动 -- 使用右值版本:String(String&& other) noexcept
}
std::vector<String> vec;
vec.push_back(String("hello")); // 移动而非拷贝
2.完美转发
- 保持参数的值类别(左值/右值)
- 通过 std::forward 实现
- 用于泛型编程
template<typename T>
void wrapper(T&& param) {
// 完美转发参数
process(std::forward<T>(param));
}
// 多参数版本
template<typename... Args>
void wrapper(Args&&... args) {
process(std::forward<Args>(args)...);
}
// 实现原理
void wrapper(T&& param) // T&& 这里是通用引用,不是右值引用
// 使得当传入左值时,T 被推导为左值引用类型;当传入右值时,T 被推导为普通类型
// 假设有个 int 类型变量 x
int x = 42;
wrapper(x); // T 被推导为 int&,arg 类型为 int&
wrapper(42); // T 被推导为 int,arg 类型为 int&&
// 当 T 为 int& 时(传入左值):
// T&& => int& && => int& // 折叠为左值引用
// 当 T 为 int 时(传入右值):
// T&& => int&& => int&& // 保持为右值引用
3.CRTP
CRTP (Curiously Recurring Template Pattern,奇异递归模板模式) 主要用于实现静态多态
// 代码示例
// CRTP实现静态多态
template<typename Derived>
class Shape {
public:
double area() {
return static_cast<Derived*>(this)->computeArea();
}
};
class Circle : public Shape<Circle> {
public:
Circle(double r) : radius(r) {}
double computeArea() { return 3.14 * radius * radius; }
private:
double radius;
};
通俗来讲,想象你在设计一个游戏,里面有不同类型的角色(战士、法师、弓箭手),他们都需要一个"攻击"的功能
// 传统方式 -- 虚函数
class 角色基类 {
public:
virtual void 攻击() = 0; // 每次调用都要查表,比较慢
};
class 战士 : public 角色基类 {
void 攻击() override { /* 挥剑 */ }
};
// CRTP方式
template<typename 具体角色>
class 角色基类 {
public:
void 攻击() {
// 直接知道要调用哪个具体角色的攻击方法,不需要查表
static_cast<具体角色*>(this)->具体攻击();
}
};
class 战士 : public 角色基类<战士> {
public:
void 具体攻击() { /* 挥剑 */ }
};
// CRTP方式就像是提前做好了"攻击招式说明书",不用临时翻书查找,但是一旦写好就不能临时(动态)改变了
7.其他
本号文章仅为个人收集总结,强烈欢迎大佬与同好指误或讨论 ^^