现代C++ —易学和易用性质

本文详细介绍了C++11中的新特性,包括自动类型推导的`auto`和`decltype`,基于范围的for循环简化代码,`nullptr`作为安全的空指针替代,以及Lambda表达式的使用,包括其捕获列表、返回值和`mutable`关键字的应用。通过实例展示了Lambda表达式的灵活性和高效性。
摘要由CSDN通过智能技术生成

1、自动类型推导

auto与decltype

2、基于范围的 for 循环

#include <iostream>
#include <string>
#include <map>
using namespace std;

int main(void)
{
    map<int, string> m{
        {1, "lucy"},{2, "lily"},{3, "tom"}
    };

    // 基于范围的for循环方式
    for (auto& it : m)
    {
        cout << "id: " << it.first << ", name: " << it.second << endl;
    }

    // 普通的for循环方式
    for (auto it = m.begin(); it != m.end(); ++it)
    {
        cout << "id: " << it->first << ", name: " << it->second << endl;
    }

    return 0;
}

  1. 使用普通的 for 循环方式(基于迭代器)遍历关联性容器, auto 自动推导出的是一个迭代器类型,需要使用迭代器的方式取出元素中的键值对(和指针的操作方法相同):
    1. it->first
      it->second
  2. 使用基于访问的 for 循环遍历关联性容器,auto 自动推导出的类型是容器中的 value_type,相当于一个对组(std::pair)对象,提取键值对的方式如下:
    1. it.first
      it.second

对应基于范围的 for 循环来说,冒号后边的表达式只会被执行一次。在得到遍历对象之后会先确定好迭代的范围,基于这个范围直接进行遍历。如果是普通的 for 循环,在每次迭代的时候都需要判断是否已经到了结束边界。

 3、nullptr

        C++ 中,void * 类型无法隐式转换为其他类型的指针,此时使用 0 代替 ((void *)0),用于解决空指针的问题。这个 0(0x0000 0000)表示的就是虚拟地址空间中的 0 地址,这块地址是只读的。

        函数重载时,NULL 和 0 无法区分。

        nullptr 专用于初始化空类型指针,不同类型的指针变量都可以使用 nullptr 来初始化。nullptr 无法隐式转换为整形但是可以隐式匹配指针类型。在 C++11 标准下,相比 NULL 和 0,使用 nullptr 初始化空指针可以令我们编写的程序更加健壮。

4、Lambda 表达式

 基本用法

lambda 表达式有如下的一些优点:

  1. 声明式的编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
  2. 简洁:避免了代码膨胀和功能分散,让开发更加高效。
  3. 在需要的时间和地点实现功能闭包,使程序更加灵活。

lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda 表达式的语法形式简单归纳如下: 

[capture](params) opt -> ret {body;};

其中 capture 是捕获列表params 是参数列表opt 是函数选项ret 是返回值类型body 是函数体。

  •  捕获列表 []: 捕获一定范围内的变量
  • 参数列表 (): 和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。
    • auto f = [](){return 1;}	// 没有参数, 参数列表为空
      auto f = []{return 1;}		// 没有参数, 参数列表省略不写
  • opt 选项, 不需要可以省略
    • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
    • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw ();
  • 返回值类型:在 C++11 中,lambda 表达式的返回值是通过返回值后置语法来定义的
  • 函数体:函数的实现,这部分不能省略,但函数体可以为空。

捕获列表

lambda 表达式的捕获列表可以捕获一定范围内的变量,具体使用方式如下:

  • [] - 不捕捉任何变量
  • [&] - 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
  • [=] - 捕获外部作用域中所有变量,并作为副本在函数体内使用 (按值捕获)
    • 拷贝的副本在匿名函数体内部是只读的,不能修改值
  • [=, &foo] - 按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
  • [bar] - 按值捕获 bar 变量,同时不捕获其他变量
  • [&bar] - 按引用捕获 bar 变量,同时不捕获其他变量
  • [this] - 捕获当前类中的 this 指针
    • ​​​​​​​让 lambda 表达式拥有和当前类成员函数同样的访问权限
    • 如果已经使用了 & 或者 =, 默认添加此选项

#include <iostream>
#include <functional>
using namespace std;

class Test
{
public:
    void output(int x, int y)
    {
        // 捕获列表是[],没有捕获外部变量,不能使用类成员 m_number
        auto x1 = [] {return m_number; };                      // error

        //以值拷贝的方式捕获所有外部变量(如果已经使用了 & 或者 =,默认捕获this)
        auto x2 = [=] {return m_number + x + y; };             // ok

        //正确,以引用的方式捕获所有外部变量
        auto x3 = [&] {return m_number + x + y; };             // ok

        //正确,捕获 this 指针,可访问对象内部成员
        auto x4 = [this] {return m_number; };                  // ok

        /错误,捕获 this 指针,可访问类内部成员,没有捕获到变量 x,y,因此不能访问。
        auto x5 = [this] {return m_number + x + y; };          // error

        //正确,捕获 this 指针,按值捕获 变量 x、y
        auto x6 = [this, x, y] {return m_number + x + y; };    // ok
        
        //正确,捕获 this 指针,并且可以修改对象内部变量的值
        auto x7 = [this] {return m_number++; };                // ok
    }
    int m_number = 100;
};

在匿名函数内部,需要通过 lambda 表达式的捕获列表控制如何捕获外部变量,以及访问哪些变量。默认状态下 lambda 表达式无法修改通过复制方式捕获外部变量,如果希望修改这些外部变量,需要通过引用的方式进行捕获

 返回值

很多时候,lambda 表达式的返回值是非常明显的,因此在 C++11 中允许省略 lambda 表达式的返回值。

// 完整的lambda表达式定义
auto f = [](int a) -> int
{
    return a+10;  
};

// 忽略返回值的lambda表达式定义
auto f = [](int a)
{
    return a+10;  
};

一般情况下,不指定 lambda 表达式的返回值,编译器会根据 return 语句自动推导返回值的类型,但需要注意的是 labmda表达式不能通过列表初始化自动推导出返回值类型

// ok,可以自动推导出返回值类型
auto f = [](int i)
{
    return i;
}

// error,不能推导出返回值类型
auto f1 = []()
{
    return {1, 2};	// 基于列表初始化推导返回值,错误
}

函数选项

函数选项 mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)

 被mutable修饰的lambda表达式,就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。

int a = 0;
auto f1 = [=] {return a++; };              // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; };     // ok,没参数也要写参数列表

为什么通过值拷贝的方式捕获的外部变量是只读的?

  1. lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
  2. 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。
  3. mutable 选项的作用就在于取消 operator () 的 const 属性。

 因为 lambda 表达式在 C++ 中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:

#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
    // 包装可调用函数
    std::function<int(int)> f1 = [](int a) -> int {return a; };
    // 绑定可调用函数
    std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);

    // 函数调用
    cout << f1(100) << endl;
    cout << f2(200) << endl;
    
    //对于没有捕获任何变量的 lambda 表达式,还可以转换成一个普通的函数指针:
    using func_ptr = int(*)(int);
    // 没有捕获任何外部变量的匿名函数
    func_ptr f = [](int a)
    {
        return a;  
    };
    // 函数调用
    f(1314);

    return 0;
}


本书主要是依照计算机本科专业的实际教学需要来编排内容的。虽然内容的条理可能不及某些专业的C++工具书那么清晰,但是这样的次序让初学者比较容易上手。毕竟那些工具书是面向一些已经掌握C++或有较高的高级语言程序设计基础的读者。编写此书的主旨就是不要一下子把什么都说出来,而是一点一点循序渐进地增长读者的能力。这样,读者就不会一下子被那么多难以接受的概念吓住,以至于失去了继续学习的信心。<br><br>本书的主要论述对象是Microsoft Visual C++,对于以前的C语言和Borland C++不作讨论,以免初学者把各种概念混淆起来,也有效降低了学习的压力。对于一些C++中存在却不常用的内容,本书一般一笔带过或不予提及。因为这些内容在应试方面不作要求,在实际使用上也可以由其他方法代替。但是,如果你是一位初学者,那么就请务必要看到本书的每一个角落。你所遗落的一句话就有可能是一个知识的关键点。<br><br>本书的内容有四个特点:<br><br>1、 粗体字:读者必须掌握理解的内容,也是每个知识点的精髓或要点。很多初学者容易犯的错误也在粗体字中予以提醒。<br><br>2、 试试看:把一些可能与一般情况不符甚至矛盾的情况列举出来,鼓励读者上机试验,以得到深刻的结论。这些结论可能对以后的学习有所帮助。所以建议所有有条件的读者务必去试试看。对于没有条件的读者,则需要牢记本书给出的结论。<br><br>3、 算法时间:向大家介绍一些程序设计的常用算法。其实很多时候一个程序就是把这些算法以不同形式搭建起来。能够掌握这些算法不论是对阅读别人的代码还是自己设计程序都有着很大的帮助。<br><br>4、 习题:帮助大家巩固已经学习的知识。有些题型则是符合应试的要求。从难度上来说,都算适中。如果读者已经掌握了章节中的知识,那么做这些习题也不会有什么困难。<br><br>本书的定位是C++程序设计的教学辅导书,而不是C++的工具书或语法书。如果你想要了解更多深层的内容,请查阅C++的专业工具书。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心之所向便是光v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值