c++14新特性

1. 变量模板

        变量模板定义一族变量或静态数据成员。

template < 形参列表 > 变量声明(1)
template < 形参列表 > requires 约束 变量声明(2)(C++20 起)
变量声明变量的声明。声明的变量名成为模板名。
形参列表非空的模板形参的逗号分隔列表,每项是非类型形参类型形参模板形参,或任何上述的形参包之一。
约束约束表达式,限制这个变量模板所能接受的模板形参

从变量模板实例化的变量被称为被实例化变量,从静态数据成员模板实例化的变量被称为被实例化静态数据成员。变量模板可以通过处于命名空间作用域中的模板声明引入,其中 变量声明 声明一个变量。

template<class T>
constexpr T pi = T(3.1415926535897932385L); // 变量模板
 
template<class T>
T circular_area(T r) // 函数模板
{
    return pi<T> * r * r; // pi<T> 是变量模板实例化
}

在类作用域中使用时,变量模板声明一个静态数据成员模板。

using namespace std::literals;
struct matrix_constants
{
    template<class T>
    using pauli = hermitian_matrix<T, 2>; // 别名模版
 
    template<class T> // 静态数据成员模板
    static constexpr pauli<T> sigmaX = {{0, 1}, {1, 0}};
 
    template<class T>
    static constexpr pauli<T> sigmaY = {{0, -1i}, {1i, 0}};
 
    template<class T>
    static constexpr pauli<T> sigmaZ = {{1, 0}, {0, -1}};
};

与其他静态成员一样,静态数据成员模板的需要一个定义。这种定义可以在类定义外提供。处于命名空间作用域的静态数据成员的模板声明也可以是类模板的非模板数据成员的定义:

struct limits
{
    template<typename T>
    static const T min; // 静态数据成员模板的声明
};
 
template<typename T>
const T limits::min = {}; // 静态数据成员模板的定义
 
template<class T>
class X
{
    static T s; // 类模板的非模板静态数据成员的声明
};
 
template<class T>
T X<T>::s = 0; // 类模板的非模板静态数据成员的定义

除非变量模板被显式特化或显式实例化,否则在变量模板的特化在要求变量定义存在的语境中被引用,或定义存在与否影响程序语义时,即表达式(可能不使用定义)对常量求值需要该变量时,隐式实例化它。

如果有表达式需要某变量进行常量求值,那么变量定义存在与否会影响程序语义,即使不要求常量求值表达式,或常量表达式求值不使用该定义。

在 C++14 引入变量模板前,参数化变量通常实现为类模板的静态数据成员,或返回所需值的 constexpr 函数模板。变量模板不能用作模板模板实参

定义一个变量并使用数据转换(类型转换)是一种常见的编程方式,但与变量模板有一些区别:

  1. 通用性: 变量模板允许你通过模板参数来生成多个不同类型的变量,从而在不同的上下文中使用。这使得代码更具通用性和可扩展性,因为你可以为多个类型生成相应的变量。相比之下,直接定义变量并使用数据转换通常只适用于特定的一种数据类型。
  2. 模板化: 变量模板是一种模板化的方式来生成变量,它遵循 C++ 的模板机制,这意味着你可以使用模板特化、部分特化等技术来定制化生成的变量,以满足不同的需求。而使用数据转换时,你必须显式地执行类型转换,这可能会在代码中引入不必要的重复。
  3. 编译时计算: 变量模板通常用于在编译时生成值,因此可以在编译阶段进行类型检查和计算。这有助于提高代码的性能和安全性。而数据转换可能在运行时进行,可能会引入一些运行时开销和类型错误的风险。
  4. 抽象性: 变量模板可以在更高的抽象层次上操作数据,使代码更具表达力和可读性。它允许你以更自然的方式描述某个值与特定类型之间的关系,而不必显式进行类型转换。

        变量模板提供了一种更灵活、通用和模板化的方式来生成变量,适用于需要在不同类型上工作的情况。当需要为多个类型生成特定的变量或值时,变量模板是一种更优雅和强大的选择。

2. 泛型lambda

        构造闭包:能够捕获作用域中的变量的无名函数对象。C++14添加了两个新功能:参数推断(auto)、参数初始化后捕获(可以在[]对某个新参数进行赋值)。

#include <iostream>

void Test() {
  int a = 0;
  auto F1 = [a = 3](auto y) { return a * y; };
  std::cout << "F1: " << F1(10) << std::endl;

  int b = 0;
  int c = 20;
  auto F2 = [=, a = 10, b = 20](auto y) { return a + b + c + y; };
  std::cout << "F2: " << F2(10) << std::endl;

  int d = 50;
  auto F3 = [=, a = std::move(d), b = 20](auto y) { return a + b + c + y; };
  std::cout << "F3: " << F3(10) << std::endl;

  auto F4 = [](auto y) -> auto{ return y + 10 + 20; };
  std::cout << "F4: " << F4(10) << std::endl;
}

int main() {
  Test();
  return 0;
}

执行输出

F1: 30
F2: 60
F3: 100
F4: 40

3. constexpr限制放宽

        在c++14前,constexpr基本功能如下:

  • constexpr修饰变量,要求变量必须可以在编译器推导出来
  • constexpr修饰函数(返回值),函数内除了可以包含using和typedef指令以及static_asssert断言外,只能包含一条return语句
  • constexpr同时可以修饰构造函数,但也会要求使用这个构造函数的时候,可以在编译器就把相关的内容全推导出来

        在c++14中,允许使用循环、if、switch等语句,最终还是在编译期间获得字面常量。

4. 二进制字面量

        二进制字面量 是字符序列 0b 或字符序列 0B 后随一或多个二进制数字(01)。在c++11中使用也不提示编译错误(和编译器实现有关),正式加入c++标准在c++14中提出。

#include <iostream>

void Test() {
  int a = 0b01010101;
  int b = 0B01010111;
  std::cout << std::hex << "a = 0x" << a << ", b = 0x" << b << std::endl;
}

int main() {
  Test();
  return 0;
}

执行结果

a = 0x55, b = 0x57

5. 数位分隔符

        数字之间可插入作为分隔符的可选的单引号(`)。编译器会忽略它们。

#include <iostream>

void Test() {
  unsigned long long l1 = 18446744073709550592ull;       // C++11
  unsigned long long l2 = 18'446'744'073'709'550'592llu; // C++14
  unsigned long long l3 = 1844'6744'0737'0955'0592uLL;   // C++14
  unsigned long long l4 = 184467'440737'0'95505'92LLU;   // C++14

  std::cout << "l1 = " << l1 << std::endl;
  std::cout << "l2 = " << l2 << std::endl;
  std::cout << "l3 = " << l3 << std::endl;
  std::cout << "l4 = " << l4 << std::endl;
}

int main() {
  Test();
  return 0;
}

执行结果

l1 = 18446744073709550592
l2 = 18446744073709550592
l3 = 18446744073709550592
l4 = 18446744073709550592

6. 函数返回类型推导

        函数声明引入函数名和它的类型。函数定义将函数名/类型与函数体关联起来。

如果函数声明的声明说明符序列 包含关键词 auto,那么尾随返回类型可以省略,且编译器将从返回语句中所用的表达式的类型推导出它。如果返回类型没有使用 decltype(auto),那么推导遵循模板实参推导的规则进行:

int x = 1;
auto f() { return x; }        // 返回类型是 int
const auto& f() { return x; } // 返回类型是 const int&

如果返回类型是 decltype(auto),那么返回类型是将返回语句中所用的表达式包裹到 decltype 中时所得到的类型:

int x = 1;
decltype(auto) f() { return x; }   // 返回类型是 int,同 decltype(x)
decltype(auto) f() { return (x); } // 返回类型是 int&,同 decltype((x))

(注意:“const decltype(auto)&”是错误的,decltype(auto) 必须独自使用)

如果有多条返回语句,那么它们必须推导出相同的类型:

auto f(bool val)
{
    if (val) return 123; // 推导出返回类型 int
    else return 3.14f;  // 错误:推导出返回类型 float
}

如果没有返回语句或返回语句的实参是 void 表达式,那么所声明的返回类型,必须要么是 decltype(auto),此时推导返回类型是 void,要么是(可有 cv 限定的)auto,此时推导的返回类型是(具有相同 cv 限定的)void。

auto f() {}              // 返回 void
auto g() { return f(); } // 返回 void
auto* x() {}             // 错误: 不能从 void 推导 auto*

一旦在函数中见到一条返回语句,那么从该语句推导的返回类型就可以用于函数的剩余部分,包括其他返回语句:

auto sum(int i)
{
    if (i == 1)
        return i;              // sum 的返回类型是 int
    else
        return sum(i - 1) + i; // OK,sum 的返回类型已知
}

如果返回语句使用花括号包围的初始化器列表,那么就不能推导:

auto func() { return {1, 2, 3}; } // 错误

虚函数协程 (C++20 起)不能使用返回类型推导:

struct F
{
    virtual auto f() { return 2; } // 错误
};

除了用户定义转换函数以外的函数模板可以使用返回类型推导。即使返回语句中的表达式并非待决,推导也在实例化时发生。这种实例化并不处于 SFINAE 的目的的立即语境中。

template<class T>
auto f(T t) { return t; }
typedef decltype(f(1)) fint_t;    // 实例化 f<int> 以推导返回类型
 
template<class T>
auto f(T* t) { return *t; }
void g() { int (*p)(int*) = &f; } // 实例化两个 f 以确定返回类型,
                                  // 选择第二个模板重载

使用返回类型推导的函数或函数模板的重声明或特化必须使用同一返回类型占位符:

auto f(int num) { return num; }
// int f(int num);            // 错误:返回类型未使用占位符
// decltype(auto) f(int num); // 错误:占位符不同
 
template<typename T>
auto g(T t) { return t; }
template auto g(int);     // OK:返回类型是 int
// template char g(char); // 错误:不是主模板 g 的特化

反过来也一样:不使用返回类型推导的函数或函数模板的重声明或特化不能使用返回类型占位符:

int f(int num);
// auto f(int num) { return num; } // 错误:不是 f 的重声明
 
template<typename T>
T g(T t) { return t; }
template int g(int);      // OK:特化 T 为 int
// template auto g(char); // 错误:不是主模板 g 的特化

显式实例化声明本身并不会实例化使用返回类型推导的函数模板:

template<typename T>
auto f(T t) { return t; }
extern template auto f(int); // 不会实例化 f<int>
 
int (*p)(int) = f; // 实例化 f<int> 以确定它的返回类型,
                   // 但仍需要在程序的别处出现显式实例化的定义

7. [[deprecated]]标记

        表示已经弃用的函数,编译时会提示警告信息。

#include <iostream>

[[deprecated]] void Test1() { std::cout << "Test1" << std::endl; }

void Test() { Test1(); }

int main() {
  Test();
  return 0;
}

编译警告

../C11Project/main.cpp: In function ‘void Test()’:
../C11Project/main.cpp:16:20: warning: ‘void Test1()’ is deprecated [-Wdeprecated-declarations]
   16 | void Test() { Test1(); }
      |               ~~~~~^~
../C11Project/main.cpp:14:21: note: declared here
   14 | [[deprecated]] void Test1() { std::cout << "Test1" << std::endl; }
      |                     ^~~~~

8. make_unique

        c++14新增std::unique_ptr智能指针包装器接口实现。

#include <iostream>
#include <memory>

void Test() {
  std::unique_ptr<int> ptr = std::make_unique<int>(10);
  std::cout << *ptr << std::endl; // 10
}

int main() {
  Test();
  return 0;
}

9. std::shared_timed_mutex & std::shared_lock

  shared_timed_mutex 类是一种同步原语,能用于保护数据免受多个线程同时访问。与其他促进独占访问的互斥体类型相反,它拥有两个访问层次:

  • 共享 - 多个线程能共享同一互斥体的所有权。
  • 独占 - 仅一个线程能占有互斥体。

        共享互斥体通常用于多个读线程能同时访问同一资源而不导致数据竞争,但只有一个写线程能访问的情形。

#include <mutex>
#include <shared_mutex>

class R {
  mutable std::shared_timed_mutex mut;

public:
  R &operator=(const R &other) {
    // 写入,独占
    std::unique_lock<std::shared_timed_mutex> lhs(mut, std::defer_lock);
    // 读取,共享
    std::shared_lock<std::shared_timed_mutex> rhs(other.mut, std::defer_lock);
    std::lock(lhs, rhs);
    return *this;
  }
};

void Test() { R r; }

int main() {
  Test();
  return 0;
}

10. 其他

  • std::exchange
#include <iostream>
#include <string>
#include <utility>

void Test() {
  std::string s1 = "hello";
  std::string s2 = "world";
  std::exchange(s1, s2); // 将s2值赋值给s1,不改变s2值
  std::cout << s1 << ", " << s2 << std::endl;  // world, world
}

int main() {
  Test();
  return 0;
}

        std::exchange 可以在实现移动赋值运算符移动构造函数时使用:

struct S
{
    int n;
 
    S(S&& other) noexcept : n{std::exchange(other.n, 0)} {}
 
    S& operator=(S&& other) noexcept
    {
        n = std::exchange(other.n, 0); // 移动 n,并于 other.n 留下零
                                       // (注意:自我移动赋值时,n 不会改变)
        return *this;
    }
};
  • std::integer_sequeuce 

        类模板 std::integer_sequence 表示一个编译时的整数序列。在用作函数模板的实参时,能推导参数包 Ints 并将它用于包展开。

#include <iostream>
#include <string>
#include <utility>

template <typename T, T... ints>
void Print(int id, std::integer_sequence<T, ints...> int_seq) {
  std::cout << id << ") 大小为 " << int_seq.size() << " 的序列: ";
  ((std::cout << ints << ' '), ...);
  std::cout << '\n';
}

void Test() {
  Print(1, std::integer_sequence<unsigned, 9, 2, 5, 1, 9, 1, 6>{});
  Print(2, std::make_integer_sequence<int, 12>{});
  Print(3, std::make_index_sequence<10>{});
  Print(4, std::index_sequence_for<std::ios, float, signed>{});
}

int main() {
  Test();
  return 0;
}

执行结果

1) 大小为 7 的序列: 9 2 5 1 9 1 6 
2) 大小为 12 的序列: 0 1 2 3 4 5 6 7 8 9 10 11 
3) 大小为 10 的序列: 0 1 2 3 4 5 6 7 8 9 
4) 大小为 3 的序列: 0 1 2 
  • std::quoted

        允许插入或提取带引号字符串,例如在 CSV 或 XML 中出现的那些格式。

#include <iomanip>
#include <iostream>
#include <sstream>
 
void default_delimiter()
{
    const std::string in = "std::quoted() quotes this string and embedded \"quotes\" too";
    std::stringstream ss;
    ss << std::quoted(in);
    std::string out;
    ss >> std::quoted(out);
 
    std::cout << "默认分隔符的情形:\n"
                 "读取为 [" << in << "]\n"
                 "存储为 [" << ss.str() << "]\n"
                 "写出为 [" << out << "]\n\n";
}
 
void custom_delimiter()
{
    const char delim{'$'};
    const char escape{'%'};
 
    const std::string in = "std::quoted() quotes this string and embedded $quotes$ $too";
    std::stringstream ss;
    ss << std::quoted(in, delim, escape);
    std::string out;
    ss >> std::quoted(out, delim, escape);
 
    std::cout << "自定义分隔符的情形:\n"
                 "读取为 [" << in << "]\n"
                 "存储为 [" << ss.str() << "]\n"
                 "写出为 [" << out << "]\n\n";
}
 
int main()
{
    default_delimiter();
    custom_delimiter();
}

执行输出

默认分隔符的情形:
读取为 [std::quoted() quotes this string and embedded "quotes" too]
存储为 ["std::quoted() quotes this string and embedded \"quotes\" too"]
写出为 [std::quoted() quotes this string and embedded "quotes" too]
 
自定义分隔符的情形:
读取为 [std::quoted() quotes this string and embedded $quotes$ $too]
存储为 [$std::quoted() quotes this string and embedded %$quotes%$ %$too$]
写出为 [std::quoted() quotes this string and embedded $quotes$ $too]
学习并掌握C++2.0(11+14+17+20)的新特性,学习线程及线程池的应用 ---------------------------------------------------给小白学员的3年学习路径及计划技术方面分三块:1.纯开发技术方向2.音视频流媒体专业方向3.项目实战---------------------------------------------------1.纯开发技术方向(1) C++必须要过硬(至少学会10本经典好书)(2) 系统级编程(Windows、Linux),必须特别熟练系统API,灵活运用(3) 框架与工具(Qt、MFC):必须精通其中一种。(4) 架构与设计模式:需要提升一个高度,不再是简单的编码,而是思维模式。(5) 驱动级别(如果有兴趣,可以深入到驱动级:包括Windows、Linux)(6) 最好学习点Java+Html+javascript等WEB技术。2.音视频流媒体专业方向(1) 音视频流媒体基础理论:   必须认真学会,否则看代码就是看天书(2) 编解码方向:精通h.264,h.265(hevc), 包括理论和各个开源库(ffmpeg,libx264,libx265,...)。(3) 直播方向:  精通各种直播协议(rtsp,rtmp,hls,http-flv,...), 钻研各个开源库(live555,darwin,srs,zlmediakit,crtmpserver,...)(4) 视频监控:  理论+开源库(onvif+281818)(EasyMonitor、iSpy、ZoneMinder(web)、...) 3.项目实战(1) Qt项目:  至少要亲手练习10个实战项目(网络服务器、多线程、数据库、图像处理、多人聊天、等等)(2)音视频项目:包括编解码、视频监控、直播等各个方向,都需要亲手实战项目,包括视频服务器、后台管理系统、前端播放器(多端)---------------------------------------------------  第1章 C++11新特性 41). nullptr关键字与新语法 42). auto和decltype类型推导 6 auto讲解 6 auto示例 7 decltype 83). for区间迭代 94). 初始化列表 105). 模板增强 11外部模板 11类型别名模板 12默认模板参数 126). 构造函数 13委托构造 13继承构造 147). Lambda 表达式 158). 新增容器 20std::array 20std::forward_list 21无序容器 22元组 std::tuple 239). 正则表达式 2610). 语言级线程支持 28多线程库简介 2811). 右值引用和move语义 31右值引用和move语义 32转移左值 3412). constexpr 35第2章 C++14新特性 36Lambda 函数 36类型推导 37返回值类型推导(Return type deduction) 37泛型lambda 39[[弃用的]]  [[deprecated]]属性 40二进制数字和数字分隔符 41第3章 C++17新特性 42安装GCC10.2 42安装msys2-x86_64-20200720 42更新镜像 42更新软件库 43安装 MinGW64 等必要的软件 43环境变量Path 43编译命令 43constexpr 44typename 45折叠表达式 47结构化绑定 48条件分支语句初始化 49聚合初始化 50嵌套命名空间 52lambda表达式捕获*this的值 53改写/继承构造函数 54用auto作为非类型模板参数 55__has_include 56fallthrough 57nodiscard 57maybe_unused 58第4章 C++20新特性 59编译命令 59concept 59typename 60explicit 61constinit 62位域变量的默认成员初始化 62指定初始化 63基于范围的for循环初始化 64放宽基于范围的for循环,新增自定义范围方法 65嵌套内联命名空间 66允许用圆括弧的值进行聚合初始化 67unicode字符串字面量 68允许转换成未知边界的数组 68likely和unlikely 69第5章 C++2.0(11/14/17/20)总结与分析 705.1 C语言C++ 715.2 语言可用性的强化 725.2.1 常量 725.2.2 变量及其初始化 735.2.3 类型推导 745.2.4 控制流 765.2.5 模板 775.2.6 面向对象 815.3 语言运行期的强化 835.3.1 Lambda 表达式 835.3.2 右值引用 865.4 容器 885.4.1 线性容器 885.4.2 无序容器 895.4.3 元组 895.5 智能指针与内存管理 905.5.1 RAII 与引用计数 905.5.2 std::shared_ptr 905.5.3 std::unique_ptr 915.5.4 std::weak_ptr 91第6章 C++2.0多线程原理与实战 93什么是并发 93并发的方式 93为什么使用并发 95线程简介 96创建线程的三种方式 971. 通过函数 972.通过类对象创建线程 993.通过lambda表达式创建线程 101thread线程的使用 101互斥量与临界区 105期物Future 111条件变量 112原子操作 114内存模型 118第7章 C++2.0线程池原理与实战 120线程与线程池的基本原理 1201)、线程 1202)、线程的生命周期 1213)、什么是单线程和多线程 1214)、线程池 1225)、四种常见的线程池 123线程池的架构与流程 123线程池代码实战 125    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值