C++14并没有太大的改动,就连官方说明中也指出,C++14相对于C++11来说是一个比较小的改动,但是在很大程度上完善了C++11,所以可以说C++14就是在C++11标准上的查漏补缺。
C++14 is a minor but important upgrade over C++11, and largely “completes C++11.”
C++14可以定义变量模板:
template<class T>
constexpr T pi = T(3.1415926535897932385L); // variable template
int main()
{
std::cout << pi<double> << std::endl;
std::cout << pi<float> << std::endl;
std::cout << pi<int> << std::endl;
return 0;
}
运行结果:
如果想在类内部定义变量模板,那么需要定义静态变量,同时也可以对模板变量进行特化。
struct Limits
{
template<typename T>
static const T min; // 声明静态成员模板
};
template<typename T>
const T Limits::min = { }; // 定义静态成员模板,全部使用默认值
// 下面三个是模板变量的特化
template<>
const float Limits::min<float> = 4.5;
template<>
const double Limits::min<double> = 5.5;
template<>
const std::string Limits::min<std::string> = "hello";
int main()
{
std::cout << Limits::min<int> << std::endl;//0
std::cout << Limits::min<float> << std::endl;//4.5
std::cout << Limits::min<double> << std::endl;//5.5
std::cout << Limits::min<std::string> << std::endl;//hello
return 0
}
C++14引入了通用lambda表达式,可以使用auto关键字作为参数类型和返回类型,使得lambda表达式更加灵活。
通用lambda表达式的语法如下:
[ captures ] ( auto&&... params ) -> decltype(auto) { body }
其中,captures是lambda表达式的捕获列表,params是lambda表达式的参数列表,decltype(auto)表示返回类型会根据body自动推导出来。
例如,以下代码展示了一个使用通用lambda表达式的例子:
int main()
{
auto add = [](auto x, auto y)
{
return x + y;
};
std::cout << add(1, 2) << std::endl; // 输出 3
std::cout << add(1.5, 2.5) << std::endl; // 输出 4
return 0;
}
在这个例子中,lambda表达式的参数类型和返回类型都使用了auto关键字,使得它可以接受不同类型的参数,并返回相应的结果。
C++14中,常量表达式是指在编译时可以计算出结果的表达式,它可以用于声明常量、数组大小、枚举值等。
C++14中新增了一些常量表达式的规则:
示例:
constexpr int factorial(int n)
{
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main()
{
constexpr int n = 5;
int arr[factorial(n)]; // 使用常量表达式计算数组大小
return 0;
}
在上面的例子中,函数factorial被声明为常量表达式,并用于计算数组arr的大小。由于n是一个编译时常量,因此可以在编译时计算出factorial(n)的值,从而确定数组的大小。
C++14引入了二进制字面量,允许程序员使用二进制表示法来表示整数值。
二进制字面量以前缀0b或0B开头,后面跟着一串二进制数字。例如,0b101010表示十进制数42。
二进制字面量提供了一种简单而方便的方法来表示位模式,这对于编写低级别的系统代码或进行位运算非常有用。
示例:
int main()
{
int a = 0b101010;
std::cout << a << std::endl; //输出42
return 0;
}
我们定义一个比较大的值的时候,有时候会很难一眼看出来他是多少,但是c++14之后,就可以对数字添加分隔符了,这使得大数字的可读性变得更高了。
比如下面两个定义1亿的方式,第二个明显会比第一个可读性高很多。
int main()
{
// 一亿
int val1 = 100000000;
int val2 = 100'000'000;
std::cout << val1 << " " << val2 << std::endl;
return 0;
}
注意,数字分隔符并不会对数字本身有任何的影响,只是对数字可读性的增强。
在C++14中,可以使用auto关键字和初始化列表来实现数组大小的自动推导。具体来说,可以使用以下语法:
auto arr = {1, 2, 3, 4}; // 自动推导为std::initializer_list
在这个例子中,编译器会自动推导出arr的类型为std::initializer_list
int arr[] = {1, 2, 3, 4}; // 数组大小为4
需要注意的是,这种自动推导方式只适用于静态数组,而对于动态数组来说,还需要使用new运算符手动分配内存。另外,由于std::initializer_list是一个轻量级的容器,因此它的性能可能不如普通数组。
C++14引入了deprecated标记符,它可以用来标记有此属性的名字或实体被弃用,被弃用了以后你仍然可以使用,但是编译时会有警告信息。所以你在新版本库中弃用了某个函数,就可以使用deprecated告诉用户尽量不要使用。
其语法如下,第一个只是告诉编译器我们弃用了这个名字,第二个是可以传入一个字面量信息,此信息会在Warning时展示出来:
[[deprecated]]
[[deprecated( "A string" )]]
弃用 class & struct & union & enum
class & struct & union & enum 的弃用属性声明方式如下,都是放在中间:
class [[deprecated]] C{};
struct [[deprecated]] S{};
union [[deprecated]] U{};
enum [[deprecated]] E{A};
弃用别名 typedef & using:
[[deprecated]] typedef wchar_t* WSTR;
using STR [[deprecated]] = char*;
注意using和typedef声明弃用属性时位置是不一样的。
弃用变量(包括静态成员和非静态成员)
[[deprecated]] int a;
class C{
public:
[[deprecated]] int b;
[[deprecated]] static int c;
};
弃用函数
声明如下:
[[deprecated]]
void Func()
{
std::cout << "hello world!" << std::endl;
}
int main()
{
Func();
return 0;
}
编译的时候会有警告信息:
弃用命名空间
声明如下:
namespace [[deprecated]] NS
{
void func();
}
弃用枚举项
声明如下,弃用谁在其后面声明。
enum E{A,B,C[[deprecated]]};
我们这里只弃用了枚举项C,没有弃用A和B。
C++14中的std::make_unique是一个函数模板,用于创建一个std::unique_ptr对象并将其初始化为一个新对象。它接受一个可变参数列表和一个构造函数的参数列表,用于在创建新对象时传递给构造函数。
make_unique的语法如下:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);
其中,T是要创建的对象的类型,Args是传递给构造函数的参数列表。make_unique返回一个std::unique_ptr对象,该对象拥有指向新对象的所有权。
使用make_unique可以避免手动创建std::unique_ptr对象并进行new操作,从而避免了内存泄漏和错误的可能性。它还可以提高代码的可读性和简洁性。
下面是一个使用make_unique创建一个动态分配的对象的例子:
#include
#include
class MyClass
{
public:
MyClass(int value) : m_value(value)
{
std::cout << "MyClass constructor called with value: " << m_value << std::endl;
}
~MyClass()
{
std::cout << "MyClass destructor called with value: " << m_value << std::endl;
}
private:
int m_value;
};
int main()
{
auto ptr = std::make_unique<MyClass>(42);
return 0;
}
在这个例子中,我们使用make_unique创建了一个动态分配的MyClass对象,并将其初始化为值42。当程序退出main函数时,指向该对象的指针ptr将被自动销毁,并调用MyClass的析构函数。
需要注意的是,make_unique不能用于创建数组,因为std::unique_ptr不支持动态数组。如果需要创建动态数组,应该使用std::vector或std::array。
std::exchange是C++14中引入的一个函数模板,它定义在头文件中。这个函数的作用是交换一个对象的值并返回其旧值。
std::exchange的函数原型如下:
template<class T, class U = T>
T exchange(T& obj, U&& new_value);
其中,T是要交换值的对象的类型,obj是要交换值的对象的引用,new_value是新值,U是新值的类型。
这个函数的作用是将obj的值用new_value替换,并返回obj原来的值。这个操作是原子的,所以在多线程环境中使用是安全的。
下面是一个使用std::exchange的例子:
#include
#include
int main()
{
int x = 1;
int y = std::exchange(x, 2);
std::cout << "x = " << x << ", y = " << y << std::endl; //输出x = 2, y = 1
return 0;
}
在这个例子中,我们使用std::exchange将x的值从1替换成2,并将原来的值1赋给了y。
C++14中的std::integer_sequence是一个模板类,用于创建一个整数序列。它可以用于编写与模板参数数量和类型无关的代码,例如元编程和模板元函数。
std::integer_sequence有两个模板参数:第一个是整数类型(通常是std::size_t),第二个是整数序列的长度。例如,std::integer_sequence
std::make_integer_sequence模板函数可以用来创建一个整数序列。它接受一个整数类型和一个整数序列长度作为参数,并返回一个std::integer_sequence对象。例如,std::make_integer_sequence
std::index_sequence是std::integer_sequence的特化版本,其中第一个模板参数固定为std::size_t。它通常用于访问元组中的元素,因为元组中的元素是按照索引顺序存储的。
使用std::integer_sequence和std::make_integer_sequence可以实现可变参数模板的参数展开,例如:
template<typename... Ts>
void foo(Ts... args)
{
bar(std::make_index_sequence<sizeof...(Ts)>{}, args...);
}
template<typename... Ts, std::size_t... Is>
void bar(std::index_sequence<Is...>, Ts... args)
{
// 访问args中的元素,例如:
int x = std::get<Is>(std::make_tuple(args...));
}
在上面的例子中,foo函数接受任意数量和类型的参数,并将它们传递给bar函数。bar函数使用std::index_sequence来访问args中的元素。
#include
#include
template<typename... Ts, std::size_t... Is>
void print_helper(const std::tuple<Ts...>& tpl, std::index_sequence<Is...>)
{
((std::cout << std::get<Is>(tpl) << ' '), ...);
std::cout << '\n';
}
template<typename... Ts>
void print(Ts... args)
{
std::tuple<Ts...> tpl(args...);
print_helper(tpl, std::make_index_sequence<sizeof...(Ts)>());
}
template<typename... Ts>
void foo(Ts... args)
{
print(args...);
}
int main()
{
foo(1, 2.5, "hello"); // 输出:1 2.5 hello
return 0;
}
在上面的例子中,print_helper函数使用std::index_sequence来展开std::tuple中的元素,并将它们打印到控制台上。print函数创建一个std::tuple对象,并使用std::make_index_sequence来创建一个std::index_sequence对象,然后将它们传递给print_helper函数。foo函数使用print函数来打印参数。
关键字 constexpr 是在 C++11 中引入的,并在 C++14 中进行了改进。 它表示 constant(常数)表达式。 与 const 一样,它可以应用于变量:如果任何代码试图 modify(修改)该值,将引发编译器错误。 与 const 不同,constexpr 也可以应用于函数和类 constructor(构造函数)。 constexpr 指示值或返回值是 constant(常数),如果可能,将在编译时进行计算,这个在很多时候可以提高程序在运行时的效率。
在C++11中,constexpr要求非常严格,这就导致了constexpr的并不是那么易用。
C++11中:
C++14中对constexpr函数的扩展主要包括以下几个方面:
总的来说,C++14中对constexpr函数的扩展使得这种函数更加灵活和实用,可以用于更多的场景,提高代码的可读性和可维护性,但仍然需要在编译期间就可以计算出全部内容。
C++14中引入了变长参数模板的扩展,可以使用类似于函数参数的语法来定义模板参数列表。这个特性被称为“参数包扩展”或“参数模板扩展”。
参数模板扩展允许在模板参数列表中使用省略号(…)来表示一个可变数量的模板参数。这些参数被称为“参数包”,可以在模板定义中使用。
例如,下面的代码定义了一个可变参数模板,用于在编译时计算一组数字的总和:
template<typename... Args>
int sum(Args... args)
{
return (args + ...);
}
在这个例子中,省略号表示Args是一个可变数量的模板参数。在函数体中,使用了折叠表达式(fold expression)来计算所有参数的总和。
使用参数模板扩展可以极大地简化代码,特别是在处理不同数量的参数时。例如,可以定义一个可变参数模板来打印任意数量的值:
template<typename... Args>
void print(Args... args)
{
(std::cout << ... << args) << '\n';
}
在这个例子中,省略号表示Args是一个可变数量的模板参数。在函数体中,使用了折叠表达式来将所有参数输出到标准输出。
C++14的内容还是比较少的,但是有一些特性还是非常实用的,比如数字分隔符和lambda表达式的一些特性扩展,在平时写代码中经常会用到的。