C++14 新特性
目录
- 函数返回类型推导
- 泛型 Lambda 表达式 (Generic Lambdas)
- Lambda 捕获表达式 (Lambda Capture Expressions)
- 变量模板 (Variable Templates)
[[deprecated]]
属性- 二进制字面量和数字分隔符
std::make_unique
- 编译期整数序列 (
std::integer_sequence
) - 总结
1. 函数返回类型推导
C++11 引入了 auto
关键字用于自动推导变量类型,C++14 则更进一步,允许函数返回类型也可以自动推导。
C++11:
// C++11 需要显式指定返回类型或使用 decltype
auto add(int a, int b) -> decltype(a + b) {
return a + b;
}
C++14:
// C++14 可以直接使用 auto 推导返回类型
auto add(int a, int b) {
return a + b;
}
示例:
#include <iostream>
auto multiply(double x, int y) {
return x * y;
}
int main() {
auto result = multiply(3.14, 2);
std::cout << "Result: " << result << std::endl; // 输出 Result: 6.28
return 0;
}
注意事项:
- 如果函数有多个
return
语句,它们的返回类型必须可以推导为同一类型。 - 返回类型推导也可以用于递归函数,但至少需要一个非递归的返回语句来启动推导。
- 不能用于虚函数,因为虚函数需要在编译时确定类型。
2. 泛型 Lambda 表达式 (Generic Lambdas)
C++11 的 Lambda 表达式已经很强大,C++14 则通过泛型 Lambda 进一步提升了其灵活性。泛型 Lambda 可以接受任意类型的参数,而无需显式指定。
C++11:
// C++11 需要为 Lambda 表达式指定参数类型
auto add = [](int a, int b) {
return a + b;
};
C++14:
// C++14 可以使用 auto 作为参数类型,实现泛型 Lambda
auto add = [](auto a, auto b) {
return a + b;
};
示例:
#include <iostream>
#include <string>
int main() {
auto genericLambda = [](auto x, auto y) {
return x + y;
};
std::cout << genericLambda(5, 3) << std::endl; // 输出 8
std::cout << genericLambda(2.5, 1.5) << std::endl; // 输出 4
std::cout << genericLambda(std::string("Hello, "), std::string("world!")) << std::endl; // 输出 "Hello, world!"
return 0;
}
3. Lambda 捕获表达式 (Lambda Capture Expressions)
C++11 的 Lambda 捕获只能捕获当前作用域中已存在的变量,C++14 则允许在捕获列表中创建新的变量,并对其进行初始化。
C++11:
int x = 10;
auto lambda = [x]() { // 只能捕获已存在的变量 x
return x * 2;
};
C++14:
auto lambda = [x = 10]() { // 可以创建并初始化新的变量 x
return x * 2;
};
// 移动捕获
auto lambda2 = [ptr = std::make_unique<int>(5)]() {
// 使用 ptr
};
示例:
#include <iostream>
#include <memory>
int main() {
auto initCapture = [value = 42]() {
return value;
};
std::cout << initCapture() << std::endl; // 输出 42
auto moveCapture = [ptr = std::make_unique<int>(10)]() {
return *ptr;
};
std::cout << moveCapture() << std::endl; // 输出 10
// ptr的所有权已经被移动到lambda表达式内部,所以这里不能再使用ptr
return 0;
}
优点:
- 可以捕获右值引用,并在lambda内部使用.
- 可以更灵活的修改外部变量, 而不影响外部变量本身的值.
4. 变量模板 (Variable Templates)
C++14 引入了变量模板,允许我们定义可以参数化的变量。
template<typename T>
constexpr T pi = T(3.1415926535897932385);
template<typename T>
T circularArea(T r) {
return pi<T> * r * r;
}
示例:
#include <iostream>
template<typename T>
constexpr T pi = T(3.1415926535897932385);
int main() {
std::cout << "Pi (float): " << pi<float> << std::endl; // 输出 Pi (float): 3.14159
std::cout << "Pi (double): " << pi<double> << std::endl; // 输出 Pi (double): 3.141592653589793
return 0;
}
5. [[deprecated]]
属性
C++14 引入了标准的 [[deprecated]]
属性,用于标记不推荐使用的函数、类、变量等。编译器在遇到被 [[deprecated]]
标记的实体时会发出警告。
[[deprecated("Use newFunction() instead")]]
void oldFunction() {
// ...
}
示例:
#include <iostream>
[[deprecated("This function is deprecated. Use newFunction() instead.")]]
void oldFunction() {
std::cout << "This is the old function." << std::endl;
}
void newFunction() {
std::cout << "This is the new function." << std::endl;
}
int main() {
oldFunction(); // 编译时会产生警告
newFunction();
return 0;
}
6. 二进制字面量和数字分隔符
- 二进制字面量: C++14 允许使用
0b
或0B
前缀来表示二进制字面量。 - 数字分隔符: C++14 允许在数字字面量中插入单引号
'
作为分隔符,以提高可读性。
int binary = 0b10101010;
long long largeNumber = 1'000'000'000'000;
double pi = 3.141'592'653'589'793'238'462;
示例:
#include <iostream>
int main() {
int binaryValue = 0b1101; // 二进制表示的 13
long long largeValue = 1'000'000'000; // 使用分隔符提高可读性
std::cout << "Binary value: " << binaryValue << std::endl; // 输出 Binary value: 13
std::cout << "Large value: " << largeValue << std::endl; // 输出 Large value: 1000000000
return 0;
}
7. std::make_unique
C++11 引入了 std::make_shared
来更安全地创建 std::shared_ptr
,C++14 则补充了 std::make_unique
来创建 std::unique_ptr
。
#include <memory>
auto ptr = std::make_unique<int>(42);
优点:
- 异常安全: 避免了手动
new
和delete
可能导致的内存泄漏。 - 简洁: 代码更简洁,减少了冗余。
- 防止隐式共享:
make_unique
生成的是unique_ptr
, 确保独占所有权.
示例:
#include <iostream>
#include <memory>
int main() {
// 使用 make_unique 创建 unique_ptr
auto intPtr = std::make_unique<int>(42);
std::cout << "Value: " << *intPtr << std::endl; // 输出 Value: 42
// unique_ptr 具有独占所有权,不能复制
// auto intPtr2 = intPtr; // 错误:尝试复制 unique_ptr
// 可以通过 std::move 转移所有权
auto intPtr2 = std::move(intPtr);
// 此时 intPtr 已经为空,intPtr2 指向原来的内存
if (intPtr) {
std::cout << "intPtr is not null" << std::endl;
} else {
std::cout << "intPtr is null" << std::endl; // 输出 intPtr is null
}
if (intPtr2) {
std::cout << "Value (intPtr2): " << *intPtr2 << std::endl; // 输出 Value (intPtr2): 42
}
return 0;
}
8. 编译期整数序列 (std::integer_sequence
)
std::integer_sequence
是一个编译期整数序列,通常与模板元编程一起使用,可以用来生成索引序列、参数包展开等。 主要包含: std::integer_sequence
: 表示一个编译时整数序列。 std::make_integer_sequence
: 生成一个 std::integer_sequence
。 std::index_sequence
: std::integer_sequence<std::size_t, ...>
的别名。 std::make_index_sequence
: 生成一个 std::index_sequence
。
template<typename... Args, std::size_t... Is>
void printArgs(const std::tuple<Args...>& tup, std::index_sequence<Is...>) {
(std::cout << ... << std::get<Is>(tup)) << std::endl;
}
std::tuple<int, double, std::string> tup(1, 2.5, "hello");
printArgs(tup, std::make_index_sequence<3>{}); // 依次打印 tuple 中的元素
示例
#include <iostream>
#include <tuple>
#include <utility>
// 使用 std::integer_sequence 展开 tuple
template<typename Tuple, std::size_t... Is>
void printTupleHelper(const Tuple& t, std::index_sequence<Is...>) {
((std::cout << std::get<Is>(t) << " "), ...); //C++17 折叠表达式
std::cout << std::endl;
}
template<typename... Args>
void printTuple(const std::tuple<Args...>& t) {
printTupleHelper(t, std::make_index_sequence<sizeof...(Args)>{});
}
int main() {
std::tuple<int, double, std::string> myTuple(1, 2.5, "Hello");
printTuple(myTuple); // 输出 1 2.5 Hello
return 0;
}
9. 总结
C++14虽然是一个相对较小的更新,但它带来的改进却非常实用,主要体现在以下几个方面:
- 提高代码的简洁性和可读性: 函数返回类型推导、泛型Lambda、数字分隔符等特性让代码更加简洁易懂。
- 增强泛型编程能力: 泛型Lambda、变量模板等特性使得泛型编程更加灵活强大。
- 提升开发效率:
std::make_unique
、编译期整数序列等特性简化了常见任务的实现。 - 更好的资源管理:
std::make_unique
,std::shared_timed_mutex
和std::shared_lock
等, 提供更好的资源管理工具。 - 更好的标记过时API:
[[deprecated]]
属性。
希望通过本节课的学习,大家能够掌握C++14的新特性,并在实际开发中灵活运用,写出更高效、更优雅的代码。
10. 作业
编写一个函数 processContainer,接受一个容器(如std::vector、std::list、std::set等)和一个泛型Lambda表达式作为参数。该函数使用给定的Lambda表达式处理容器中的每个元素。
具体要求: processContainer 函数的第一个参数可以是任意类型的容器,只要该容器支持迭代器。 processContainer 函数的第二个参数是一个泛型Lambda表达式,该Lambda表达式接受一个容器元素的引用作为参数(不需要修改可以传const引用),可以对元素进行任意操作(例如,打印、修改、计算等)。 演示如何使用不同的容器和不同的Lambda表达式来调用 processContainer 函数。 至少演示三种不同的使用情况: 打印容器内容 将容器内所有元素翻倍 (对于数字) 或者 转换为大写(对于 std::string) 计算容器中所有元素的总和(对于数字)或总长度(对于 std::string)
参考实现:
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
// 你的 processContainer 函数
template<typename Container, typename Callable>
void processContainer(Container& container, Callable operation)
{
for(auto&& element : container) //使用 auto&&, 可以兼容左值和右值
{
operation(element);
}
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::list<std::string> words = {"hello", "world", "generic", "lambda"};
// 示例1: 打印容器内容
processContainer(numbers, [](const auto& x){
std::cout << x << " ";
});
std::cout << std::endl;
processContainer(words, [](const auto& str){
std::cout<< str << " ";
});
std::cout << std::endl;
// 示例2: 将数字翻倍, 字符串转大写
processContainer(numbers, [](auto& x) {
x *= 2;
});
processContainer(words, [](auto& str)
{
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
});
// 打印翻倍/大写后的容器
processContainer(numbers, [](const auto& x){
std::cout << x << " ";
});
std::cout << std::endl;
processContainer(words, [](const auto& str){
std::cout<< str << " ";
});
std::cout << std::endl;
// 示例3: 计算数字的总和,字符串的总长度
int sum = 0;
processContainer(numbers, [&sum](const auto& x){
sum += x;
});
std::cout << "Sum: " << sum << std::endl;
size_t totalLength = 0;
processContainer(words, [&totalLength](const auto& str){
totalLength += str.length();
});
std::cout << "Total length: " << totalLength << std::endl;
return 0;
}