第一章:现代C++基础语法增强
📚 章节概述
本章介绍现代C++(C++11及以后版本)中对基础语法的重要增强。这些特性让C++代码更加简洁、安全和高效。掌握这些基础特性是学习后续高级特性的重要基础。
🎯 学习目标
学完本章后,您将能够:
- 使用
auto关键字进行自动类型推导 - 掌握范围for循环的各种用法
- 理解并使用统一初始化语法
- 正确使用
nullptr替代传统的NULL - 理解右值引用和移动语义的基础概念
- 使用
constexpr进行编译时计算 - 运用
static_assert进行编译时断言 - 掌握
decltype类型推导 - 使用委托构造函数简化类设计
- 理解
=default和=delete的用途
1. auto关键字 - 自动类型推导
📖 理论讲解
auto关键字让编译器根据初始化表达式自动推导变量类型,减少了代码冗余,提高了代码的可维护性。
💡 核心概念
- 类型推导:编译器根据右侧表达式推导左侧变量类型
- 简化声明:避免书写复杂的类型名称
- 模板友好:在模板代码中特别有用
🔍 使用场景
// 1. 简单类型推导
auto x = 42; // int
auto y = 3.14; // double
auto name = "C++"; // const char*
// 2. 复杂类型推导
std::vector<std::string> vec = {"hello", "world"};
auto it = vec.begin(); // std::vector<std::string>::iterator
// 3. 函数返回值推导
auto result = some_complex_function();
// 4. Lambda表达式
auto lambda = [](int a, int b) { return a + b; };
⚠️ 注意事项
auto必须立即初始化- 推导的是值类型,不是引用类型
- 对于引用,需要显式使用
auto& - 对于指针,
auto和auto*效果相同
🎯 最佳实践
- 使用场景:当类型名很长或很复杂时使用
- 避免滥用:对于简单类型,明确写出类型更清晰
- 配合IDE:现代IDE会显示推导出的类型
2. 范围for循环 (Range-based for loop)
📖 理论讲解
范围for循环提供了一种简洁的方式来遍历容器和数组,减少了传统for循环的样板代码。
💡 核心概念
- 简化语法:
for (element : container) - 自动迭代:编译器自动处理迭代器
- 类型推导:可以配合
auto使用
🔍 使用场景
// 1. 遍历容器
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (const auto& num : numbers) {
std::cout << num << " ";
}
// 2. 修改元素
for (auto& num : numbers) {
num *= 2; // 修改原始元素
}
// 3. 遍历数组
int arr[] = {1, 2, 3, 4, 5};
for (int value : arr) {
std::cout << value << " ";
}
// 4. 带索引的遍历(C++20风格)
for (auto [index, value] : enumerate(numbers)) {
std::cout << index << ": " << value << std::endl;
}
⚠️ 注意事项
- 使用
const auto&避免不必要的拷贝 - 使用
auto&当需要修改元素时 - 不要在循环中修改容器结构
3. 初始化列表 (Initializer List)
📖 理论讲解
统一初始化语法提供了一致的初始化方式,使用花括号{}来初始化各种类型的对象。
💡 核心概念
- 统一语法:所有类型都可以用
{}初始化 - 防止窄化转换:编译时检查类型安全
- 初始化列表构造:支持
std::initializer_list
🔍 使用场景
// 1. 基本类型初始化
int x{42};
double y{3.14};
// 2. 容器初始化
std::vector<int> vec{1, 2, 3, 4, 5};
std::map<std::string, int> m{{"apple", 1}, {"banana", 2}};
// 3. 数组初始化
int arr[]{1, 2, 3, 4, 5};
// 4. 类对象初始化
class Point {
public:
int x, y;
Point(int x, int y) : x(x), y(y) {}
};
Point p{10, 20};
// 5. 防止窄化转换
int safe{3.14}; // 编译错误!防止double到int的窄化
🎯 最佳实践
- 优先使用:尽量使用统一初始化语法
- 避免歧义:注意
{}和()的区别 - 类型安全:利用防止窄化转换的特性
4. nullptr - 空指针字面值
📖 理论讲解
nullptr是C++11引入的空指针字面值,用来替代传统的NULL,提供了类型安全的空指针表示。
💡 核心概念
- 类型安全:
nullptr有自己的类型std::nullptr_t - 重载友好:能正确匹配指针重载函数
- 现代标准:是现代C++的推荐做法
🔍 使用场景
// 1. 指针初始化
int* ptr = nullptr;
// 2. 函数重载区分
void func(int value);
void func(int* ptr);
func(0); // 调用func(int),可能不是想要的
func(nullptr); // 明确调用func(int*)
// 3. 条件判断
if (ptr != nullptr) {
// 安全使用ptr
}
// 4. 返回值
int* find_element() {
// 未找到时返回nullptr
return nullptr;
}
⚠️ 注意事项
- 不要混用
NULL和nullptr nullptr可以转换为任何指针类型- 不能直接转换为整数类型
5. 右值引用和移动语义基础
📖 理论讲解
右值引用(&&)是C++11引入的新特性,支持移动语义,可以避免不必要的拷贝操作,提高程序性能。
💡 核心概念
- 左值 vs 右值:左值有名字且可寻址,右值是临时对象
- 移动语义:转移资源所有权而非拷贝
- 完美转发:保持参数的值类别
🔍 使用场景
// 1. 移动构造函数
class Resource {
private:
int* data;
size_t size;
public:
// 移动构造函数
Resource(Resource&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 移动赋值运算符
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
// 2. std::move的使用
Resource r1(100);
Resource r2 = std::move(r1); // 移动而非拷贝
🎯 最佳实践
- 实现移动语义:为资源管理类实现移动构造和移动赋值
- 使用std::move:明确表示移动意图
- noexcept标记:移动操作应该标记为noexcept
6. constexpr - 编译时常量表达式
📖 理论讲解
constexpr关键字表示表达式或函数可以在编译时求值,提供了编译时计算的能力。
💡 核心概念
- 编译时计算:在编译期间计算结果
- 性能优化:避免运行时计算开销
- 模板友好:可用作模板参数
🔍 使用场景
// 1. constexpr变量
constexpr int max_size = 100;
constexpr double pi = 3.14159;
// 2. constexpr函数
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// 3. 编译时计算
constexpr int result = factorial(5); // 编译时计算
// 4. constexpr构造函数
class Point {
int x_, y_;
public:
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int x() const { return x_; }
constexpr int y() const { return y_; }
};
constexpr Point origin(0, 0);
⚠️ 注意事项
constexpr函数必须能在编译时求值- 不是所有调用都必须在编译时进行
- C++14放宽了
constexpr函数的限制
7. static_assert - 编译时断言
📖 理论讲解
static_assert提供编译时断言功能,可以在编译期间检查条件,如果条件不满足则产生编译错误。
💡 核心概念
- 编译时检查:在编译阶段进行断言
- 模板安全:确保模板参数满足要求
- 平台检查:检查平台相关的假设
🔍 使用场景
// 1. 基本断言
static_assert(sizeof(int) == 4, "int must be 4 bytes");
// 2. 模板约束
template<typename T>
class MyClass {
static_assert(std::is_arithmetic_v<T>, "T must be arithmetic type");
// ...
};
// 3. 编译时条件检查
constexpr bool is_64bit = sizeof(void*) == 8;
static_assert(is_64bit, "This code requires 64-bit platform");
// 4. C++17简化语法
static_assert(sizeof(long) >= 8); // 可以省略错误消息
🎯 最佳实践
- 提供清晰错误信息:写明断言失败的原因
- 模板中使用:确保模板参数类型正确
- 平台适配:检查平台相关假设
8. decltype - 类型推导
📖 理论讲解
decltype关键字可以推导表达式的类型,常用于模板编程和复杂类型声明。
💡 核心概念
- 表达式类型推导:根据表达式推导类型
- 保持类型属性:保持const、引用等属性
- 模板编程:在模板中推导类型
🔍 使用场景
// 1. 基本用法
int x = 42;
decltype(x) y = x; // y的类型是int
// 2. 复杂表达式
std::vector<int> vec;
decltype(vec.begin()) it = vec.begin();
// 3. 函数返回类型推导
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// 4. 变量模板
template<typename T>
decltype(auto) get_value() {
static T value{};
return value;
}
⚠️ 注意事项
decltype保持引用和const属性- 对于变量名,
decltype返回变量类型 - 对于表达式,
decltype可能返回引用类型
9. 委托构造函数
📖 理论讲解
委托构造函数允许一个构造函数调用同一个类的另一个构造函数,减少代码重复。
💡 核心概念
- 代码复用:避免构造函数中的重复代码
- 初始化链:构造函数可以委托给其他构造函数
- 清晰设计:让类的初始化逻辑更清晰
🔍 使用场景
class Circle {
private:
double radius_;
std::string color_;
public:
// 主构造函数
Circle(double radius, const std::string& color)
: radius_(radius), color_(color) {
std::cout << "Circle created: radius=" << radius_
<< ", color=" << color_ << std::endl;
}
// 委托构造函数
Circle() : Circle(1.0, "red") {} // 委托给主构造函数
Circle(double radius) : Circle(radius, "blue") {} // 委托给主构造函数
Circle(const std::string& color) : Circle(1.0, color) {} // 委托给主构造函数
};
🎯 最佳实践
- 避免循环委托:确保委托链不形成循环
- 选择合适的主构造:让最完整的构造函数作为主构造函数
- 简化初始化:用委托减少重复的初始化代码
10. =default 和 =delete
📖 理论讲解
=default和=delete提供了显式控制编译器生成特殊成员函数的方法。
💡 核心概念
- =default:显式要求编译器生成默认实现
- =delete:显式禁止某个函数
- RAII安全:更好地控制对象的生命周期
🔍 使用场景
class NonCopyable {
public:
NonCopyable() = default; // 使用编译器生成的默认构造函数
// 禁止拷贝
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
// 允许移动
NonCopyable(NonCopyable&&) = default;
NonCopyable& operator=(NonCopyable&&) = default;
~NonCopyable() = default;
};
class Singleton {
private:
Singleton() = default;
public:
// 禁止拷贝和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
static Singleton& instance() {
static Singleton inst;
return inst;
}
};
🎯 最佳实践
- 明确意图:使用
=default明确表示使用默认实现 - 禁用危险操作:使用
=delete禁止不安全的操作 - RAII设计:配合智能指针设计更安全的类
🧪 实践练习
练习1:auto的使用
// 使用auto简化以下代码
std::vector<std::map<std::string, int>>::iterator it = container.begin();
std::function<int(int, int)> calculator = [](int a, int b) { return a + b; };
练习2:范围for循环
// 将传统for循环改写为范围for循环
std::vector<std::string> words = {"hello", "modern", "cpp"};
for (size_t i = 0; i < words.size(); ++i) {
std::cout << words[i] << std::endl;
}
练习3:移动语义
// 为以下类实现移动构造函数和移动赋值运算符
class Buffer {
private:
char* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new char[s]) {}
~Buffer() { delete[] data; }
// 添加移动构造函数和移动赋值运算符
};
📝 本章小结
本章介绍了现代C++的基础语法增强,这些特性让C++代码更加:
- 简洁:
auto和范围for循环减少了样板代码 - 安全:
nullptr、static_assert提供了更好的类型安全 - 高效:移动语义避免了不必要的拷贝
- 表达力强:统一初始化和委托构造函数让代码意图更清晰
这些基础特性是学习后续高级特性的重要基础,建议通过大量练习来掌握它们的使用场景和最佳实践。


3822

被折叠的 条评论
为什么被折叠?



