博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++11(14) 简易推荐小记~
阅读量:4224 次
发布时间:2019-05-26

本文共 5037 字,大约阅读时间需要 16 分钟。

  之前了解过一些C++新标准的内容,觉得很不错,在此写篇小记,简易推荐一下~

 

  容器内元素操作是个很普通的需求,工作中应是屡见不鲜,这里假设有个list容器,存储的是一系列int,表达的意思就算作是年龄吧,新年将近,大家的年龄也都会不情愿的涨上一岁,简单用C++表达一下,大概是这个样子:

#ifndef __TEST_1_H__#define __TEST_1_H__#include 
#include
void add_one(int& val) { ++val;}void add(std::list
& l) { std::list
::iterator beg = l.begin(); std::list
::iterator end = l.end(); for (std::list
::iterator iter = beg; iter != end; ++iter) { add_one(*iter); }}void print_one(const int& value) { std::cout << value << " ";}void print(const std::list
& l) { std::list
::const_iterator beg = l.begin(); std::list
::const_iterator end = l.end(); for (std::list
::const_iterator iter = beg; iter != end; ++iter) { print_one(*iter); } std::cout << std::endl;}void test() { iint ages[] = { 25, 25, 25, 25, 25, 36 }; std::list
l; for (int i = 0; i < sizeof(ages) / sizeof(int); ++i) { l.push_back(ages[i]); } print(l); add(l); print(l);}#endif

  简单看看,似乎写的还行:代码格式统一,函数命名也相对明确,参数上使用了(常量)引用来传递,效率上应该不错,访问容器元素使用了迭代器,模式上很经典呀~

  

  不过仔细再看,那几个迭代器的声明还是略显冗长了一些,list容器的初始化也不是那么简明,更大的一个问题是,代码没啥通用性,容器类型换一下,代码大抵得重写,而且内容都是重复的~

 

  好吧,既然问题找到了,那就来尝试改善一下:迭代器的声明可以用typedef简化,不过更好的自然是直接回避迭代器声明,这里我们刚好可以借助std::for_each来达到目的,list的初始化可以改用迭代器版本的构造函数,可以节省不少代码,至于通用性的问题,模版几乎都是标准答案~

 

  一阵改造之后,代码大概成了这样:

#ifndef __TEST_2_H__#define __TEST_2_H__#include 
#include
#include
#include
template
void add_one(T& val) { ++val;}template
void add(Container& container) { std::for_each(container.begin(), container.end(), add_one
);}template
void print_one(const T& value) { std::cout << value << " ";}template
void print(const Container& container) { std::for_each(container.begin(), container.end(), print_one
); std::cout << std::endl;}void test() { int ages[] = { 25, 25, 25, 25, 25, 36 }; std::list
l(ages, ages + sizeof(ages) / sizeof(int)); print(l); add(l); print(l); std::vector
v(ages, ages + sizeof(ages) / sizeof(int)); print(v); add(v); print(v);}#endif

  改造后的代码感觉已经不错了,没有冗长的迭代器声明,没有累赘的初始化过程,通用性也不错,容器换做vector,代码一样工作~

 

  那么问题来了:上面的代码还能更简洁吗?

 

  答案是可以!

 

  先上代码:

#ifndef __TEST_3_H__#define __TEST_3_H__#include 
#include
#include
#include
void test() { auto add_one = [](auto& val){ ++val; }; auto add = [&add_one](auto& container) { std::for_each(std::begin(container), std::end(container), add_one); }; auto print_one = [](const auto& val) { std::cout << val << " "; }; auto print = [&print_one](const auto& container) { std::for_each(std::begin(container), std::end(container), print_one); std::cout << std::endl; }; std::list
l = { 25, 25, 25, 25, 25, 36 }; print(l); add(l); print(l); std::vector
v = { 25, 25, 25, 25, 25, 36 }; print(v); add(v); print(v); int a[] = { 25, 25, 25, 25, 25, 36 }; print(a); add(a); print(a);}#endif

  不太了解C++新标准的同学现在可能已经在心里暗骂了:什么鬼?!不急,咱们一行行来看:

 

  auto add_one = [](autoval){ ++val; };

 

  auto 本来便是C++中的一个关键字,用于自动变量的声明(虽然我从来也没用过),在C++11中,它的作用(之一)变成了自动类型推导,还记得最早的那个迭代器声明吗:

 

  std::list<int>::const_iterator beg = l.begin();

 

  使用auto的话只要这么写就行了,很舒服:

 

  auto beg = l.begin();

 

  所以这里我们就是定义了一个自动类型推导的add_one变量,至于后面那个诡异的初始化表达式:

 

  [](autoval){ ++val; }

 

  其实是C++11新引入的Lambda表达式,用以方便的就地定义匿名函数对象,以上面的代码为例来简单说明一下:

 

  [] 中用于定义捕获子句,至于什么是捕获子句,我们暂时不管,反正这里我们什么都没填~

 

  (auto& val)则是参数列表,这个对于我们就很亲切熟悉了,至于为什么参数写成auto&,而不是int&之类的方式,其实是使用了C++14中新定义的通用Lambda功能,个人认为可以理解为定义模版,即 auto& val 可以看作T& val,用于匹配不同类型~

 

  至于{ ++val; }就是函数体了,没啥好说的,一目了然~

 

  OK,现在为止,add_one的定义就清楚了,简单来说,它其实就是一个接受单个参数的函数对象~

  

  add_one搞明白了,那么add自然也大概清楚了:

 

  auto add = [&add_one](autocontainer) {

      // 省略的函数体

  };

 

  唯一不同的是,add的捕获子句中并不是空的,而是 &add_one,什么意思呢?其实就是以引用的方式来捕获add_one,引用我们明白,但是所谓捕获是啥意思呢?简单来说,其实就是让后面我们定义函数体的时候可以访问被捕获的变量,拿add来说,我们需要在它的函数体中访问先前定义的add_one,所以事先捕获一下,就这么简单一下~

 

  到这里,add的定义也清楚了,只有一个小小的细节,就是我们在add的函数体中使用了std::begin(container)std::end(container),而没有直接调用 container.begin() 和 container.end(),原因其实还是为了通用性:std::beginstd::end C++11以来加入的新特性,考虑之前第一次修改后的代码,虽然也使用了模版增强其通用性,但是由于直接调用了container.begin() 和 container.end()使其不能支持没有定义begin/end成员函数的容器,尤其是其不支持数组,有时候确实很不方便,而使用std::beginstd::end就不存在这个问题了:其对标准库容器的支持自不必说,新标准还为数组重载了std::beginstd::end,对于其他类型容器,你也大可以自己重载实现它们,而外部的逻辑代码则都是调用std::beginstd::end,一致性很好 !

 

  至此,add_one, and的定义都已明了,print_oneprint也如出一辙,最后值得一提的便是容器新的初始化方式了:

 

  std::list<int> l = { 25, 25, 25, 25, 25, 36 };

 

  这里我们用到了C++11以来新增的初始化列表,简单来说就是,新标准的标准库容器都新增了一个以initializer_list为参数的构造函数,上述表达式中的{ 25, 25, 25, 25, 25, 36 }会被构造为一个initializer_list<int>并传入list的构造函数,之后便是一般的初始化流程了~可以看到,初始化列表的引入让容器的初始化变得非常简洁,并且对于非标准库的容器,你也可以为它定义以initializer_list为参的构造函数,同样可以使用上面的初始化方式~

 

  至此,我们使用更少的代码,更简洁易读的表达出了程序逻辑,并且程序的通用性更强,而且程序的效率并没有任何损失,Cool~

 

  C++新标准还远远不止上面提到的内容,像nullptroverridefinal等新加入的关键字也很贴心,更有像智能指针、Move语义等强大的工具加盟,当然了,另有一些个人感觉颇为晦涩的东西引入(memory order etc.),但是管他呢,慢慢了解便是,总体上,个人强烈建议有兴趣的童鞋了解学习C++新标准,就是个很好的开始~

 

  Happy Coding With New C++ :)

转载地址:http://dszqi.baihongyu.com/

你可能感兴趣的文章
Oracle wallet 配置 说明
查看>>
Oracle smon_scn_time 表 说明
查看>>
VBox fdisk 不显示 添加的硬盘 解决方法
查看>>
Secure CRT 自动记录日志 配置 小记
查看>>
RMAN RAC 到 单实例 duplicate 自动分配通道 触发 ORA-19505 错误
查看>>
mysql 随机分页的优化
查看>>
DB2快速创建测试库
查看>>
SD卡驱动分析--基于高通平台
查看>>
[图文] Seata AT 模式分布式事务源码分析
查看>>
pm 源码分析
查看>>
kmsg_dump
查看>>
Getting a Result from an Activity
查看>>
Allowing Other Apps to Start Your Activity
查看>>
dev/mem
查看>>
pfn_valid 源码分析
查看>>
dev/kmem 和dev/mem的区别
查看>>
test-definitions/blob/master/auto-test/bigdata/bigdata.sh
查看>>
/test-definitions/blob/master/auto-test/blktrace/blktrace.sh
查看>>
test-definitions/blob/master/auto-test/blogbench/blogbench.sh
查看>>
test-definitions/blob/master/auto-test/boost/boost.sh
查看>>