IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11后面引入的新特性(一) -> 正文阅读

[C++知识库]C++11后面引入的新特性(一)

?最近工作中,遇到一些问题,使用C++11实现起来会更加方便,而线上的生产环境还不支持C++11,于是决定新年开工后,在组内把C++11推广开来,整理以下文档,方便自己查阅,也方便同事快速上手。(对于异步编程十分实用的Future/Promise以及智能指针等,将不做整理介绍,组内使用的框架已经支持并广泛使用了,用的是自己公司参考boost实现的版本)目前C++版本已经C++23,本次我们主要介绍c++11版本引入的新特性

?

本次是一次较大的改动,我?将要对上面一些特殊特性,重要的加以详细说明和用法

  1. ?nullptr

nullptr 出现的目的是为了替代 NULL。

在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。

C++ 不允许直接将 void * 隐式转换到其他类型,但如果 NULL 被定义为 ((void*)0),那么当编译char *ch = NULL;时,NULL 只好被定义为 0。

而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,考虑:
?

void foo(char *);
void foo(int);

对于这两个函数来说,如果 NULL 又被定义为了 0 那么 foo(NULL); 这个语句将会去调用 foo(int),从而导致代码违反直观。

为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。

nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。

当需要使用 NULL 时候,养成直接使用 nullptr的习惯。

? ? ? 2. 类型推导

C++11 引入了 auto 和 decltype 这两个关键字实现了类型推导,让编译器来操心变量的类型

?对于变量,指定要从其初始化器自动推导出其类型。

对于函数,指定要从其 return 语句推导出其返回类型。

(C++14 起)

对于非类型模板形参,指定要从实参推导出其类型。

(C++17 起)

?

auto
auto 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 register 并存。在传统 C++ 中,如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。而随着 register 被弃用,对 auto 的语义变更也就非常自然了。

使用 auto 进行类型推导的一个最为常见而且显著的例子就是迭代器。在以前我们需要这样来书写一个迭代器:

for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)

而有了 auto 之后可以:?

// 由于 cbegin() 将返回 vector<int>::const_iterator 
// 所以 itr 也应该是 vector<int>::const_iterator 类型
for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);
// 在其所调用的函数返回引用的情况下
// 函数调用的完美转发必须用 decltype(auto)
template<class F, class... Args>
decltype(auto) PerfectForward(F fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}
 

decltype

decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。

检查实体的声明类型,或表达式的类型和值类别。

语法

decltype (?实体?)(1)(C++11 起)
decltype (?表达式?)(2)(C++11 起)

在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
有时候,我们可能需要计算某个表达式的类型,例如:

auto x = 1;
auto y = 2;
decltype(x+y) z;

从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:

template<typename T, typename U>
auto add(T x, U y) {
    return x+y;
}
#include <iostream>
#include <type_traits>
 
struct A { double x; };
const A* a;
 
decltype(a->x) y;       // y 的类型是 double(其声明类型)
decltype((a->x)) z = y; // z 的类型是 const double&(左值表达式)
 
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) // 返回类型依赖于模板形参
{                                     // C++14 开始可以推导返回类型
    return t+u;
}
 
int main() 
{
    int i = 33;
    decltype(i) j = i * 2;
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
 
    std::cout << "i 和 j 的类型相同吗?"
              << (std::is_same_v<decltype(i), decltype(j)> ? "相同" : "不同") << '\n';
 
    auto f = [](int a, int b) -> int//lambdab没有捕获任何参数,入参a,b 返回int类型
    {
        return a * b;
    };
 
    decltype(f) g = f; // lambda 的类型是独有且无名的
    i = f(2, 2);
    j = g(3, 3);
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
}

运行代码后,此时用的编译器Gcc9.2(2a)?

i 和 j 的类型相同吗?相同
i = 33, j = 66
i = 4, j = 9

Lambda表达式

C++11新标准新增的一项重要功能就是lambda表达式,所谓lambda就是表示一个可调用的代码单元,也可以说是一个可调用对象,还可以理解为一个没有命名的内联函数。Lambda的组成结构与函数很相似,它拥有一个返回类型,一个形参列表,一个函数体。Lambda也可以定义在函数内部。它的组成结构如下:

[capture list] (parameter list) -> return type { function body}

Lambda表达式称为匿名函数,所谓匿名函数,有以下两方面的含义

  • Lambda表达式是函数的一种,从功能上看,Lambda表达式和函数的作用完全一样(虽然Lambda表达式实质是一个类),使用Lambda表达式完成的功能,也可以使用普通函数来完成;
  • Lambda表达式是匿名的,即没有名字,而普通函数必须有函数名;其实,Lambda表达式也是可以命名的,然后通过名字来调用Lambda表达式,所以,Lambda表达式可以匿名,但不是必须匿名。

既然功能和普通函数一样,那么C++11为什么还要引入Lambda表达式呢?相比普通函数,Lambda表达式有以下优点

捕获的举例如下:

  • Lambda可以就地定义,比函数更方便,比如,我们可以直接在函数内部定义Lambda表达式

    void fun(){
        auto add = [] (int x, int y) { return x + y; };// 定义Lambda表达式
        int a = add(1,2);
        int b = add(a,3);
    }

  • Lambda表达式的作用域更容易控制,有助于减少命名冲突
    上述实例中,add的作用域仅限于fun函数内部,如果我们定义add为普通函数,那么add就是全局函数了,可能和其他函数名冲突。

  • Lambda表达式可以自动捕获上下文中的变量,比普通函数更方便

    void fun(){
        int y=1;
        auto add = [=] (int x) { return x + y; };// Lambda中可以直接使用外部变量y,安值捕获
        int a = add(1);
    }

    上述代码中,变量y属于add外定义的变量,但是add依旧可以直接使用变量y,而普通函数做不到这个功能,普通函数要想使用变量y,则需要通过参数传递把y传递过去,多麻烦啊?下文会更详细的说明捕获变量的用法。

  • lamdba.jpg

    按照上图中的标号,具体解释如下:

  • 标号1:指定捕获列表,所谓捕获,是把Lambda表达式之外定义的变量,捕获到Lambda表达式内部,这样Lambda内部可以直接引用这些变量,省去参数传递的过程。

    捕获分为两种方式:

  • 按值捕获,捕获到Lambda表达式内部的变量是副本,注意,按值捕获的变量默认是不能修改的,可以使用mutable关键字突破这个限制,见下文标号3.
  • 按引用捕获,捕获到Lambda表达式内部的变量是引用,修改变量会影响外部的同名变量
  • [],空捕获列表,不捕获任何变量,此时引用外部变量则会提示编译错误
  • [=],默认按值捕获全部变量
  • [&],默认按引用捕获全部变量
  • [=,&x,&y],默认按值捕获全部变量,但是变量x,变量y按引用捕获
  • [&,=x,=y],默认按引用捕获全部变量,但是变量x,变量y按值捕获
  • [&,x,y],效果同上,即变量名前面没有写=或者&时,默认为按值捕获
  • [=,x,y],编译出错,变量x,变量y按值捕获,和默认按值捕获全部变量重复
  • [x,y],只按值捕获变量x和变量y
  • [&x,&y],只按引用捕获变量x和变量y
  • [x,&y],只按值捕获变量x,按引用捕获变量y
  • [=x,=y],编译出错,应为[x,y]
  • [this],捕获this指针,然后在Lambda表达式内部就可以直接引用类成员了

? ?标号2:函数参数

  • 用法和普通函数一样

  • auto add = [] (int x, int y) { return x + y; };

    add有两个参数,将来调用add时请传递两个int变量

  • ?

    标号3:mutable,用来突破不能修改按值捕获变量的限制

    如下代码,按值捕获了变量x,在Lambda表达式内部,是不能修改x的值的

    ?

    int x = 1;
    auto f=[x](){x++;};// 编译错误,不能修改x的值
    f();

    为了突破上面的限制,添加mutable即可编译成功

  •     int x = 1;
    	auto f = [x]()mutable{x++; };
        f();
    
       //另一个种表达方式
        int x = 1;
    	auto f = [x]()mutable->int{x++; };
    

    注意,即使Lambda表达式内部修改了x的值,但是依旧不影响Lambda表达式外部的x的值,两者是相互独立的。

    标号4:throw关键字,和C++中throw用法保持一致

    标号5:Lambda表达式返回值的类型

    标号6:函数内容;注意函数最后面,需要添加一个;分号

Lambda表达式的实质

Lambda实质是类,通过下面的例子可以很多认识到Lambda表达式和普通函数的不同

    int x = 1;
	auto f = [x]()mutable
	{
		x++;
		cout << "函数里面:" << x << endl;
	};

	cout << "函数外面" << x << endl;
	f();
	f();

	cout << "调用两次之后" << x << endl;

?上述代码中,第二次调用f是,f内部的变量x保留了上次的值。其实,Lambda实质是类,而f是类的实例,x是f的成员变量,多次调用f,调用的是同一个实例,这是和普通函数本质不同的地方。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-08 13:38:17  更:2021-12-08 13:40:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:03:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码