前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++11(14) 简易推荐小记~

C++11(14) 简易推荐小记~

作者头像
用户2615200
发布2018-08-02 16:58:12
3710
发布2018-08-02 16:58:12
举报

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

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

#ifndef __TEST_1_H__
#define __TEST_1_H__

#include <iostream>
#include <list>

void add_one(int& val) {
	++val;
}

void add(std::list<int>& l) {
	std::list<int>::iterator beg = l.begin();
	std::list<int>::iterator end = l.end();
	for (std::list<int>::iterator iter = beg; iter != end; ++iter) {
		add_one(*iter);
	}
}

void print_one(const int& value) {
	std::cout << value << " ";
}

void print(const std::list<int>& l) {
	std::list<int>::const_iterator beg = l.begin();
	std::list<int>::const_iterator end = l.end();
	for (std::list<int>::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<int> 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 <iostream>
#include <list>
#include <vector>
#include <algorithm>

template<typename T>
void add_one(T& val) {
	++val;
}

template<typename Container>
void add(Container& container) {
	std::for_each(container.begin(), container.end(), add_one<Container::value_type>);
}

template<typename T>
void print_one(const T& value) {
	std::cout << value << " ";
}

template<typename Container>
void print(const Container& container) {
	std::for_each(container.begin(), container.end(), print_one<Container::value_type>);
	std::cout << std::endl;
}

void test() {
	int ages[] = { 25, 25, 25, 25, 25, 36 };
	std::list<int> l(ages, ages + sizeof(ages) / sizeof(int));

	print(l);
	add(l);
	print(l);

	std::vector<int> v(ages, ages + sizeof(ages) / sizeof(int));

	print(v);
	add(v);
	print(v);
}

#endif

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

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

  答案是可以!

  先上代码:

#ifndef __TEST_3_H__
#define __TEST_3_H__

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>

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<int> l = { 25, 25, 25, 25, 25, 36 };

	print(l);
	add(l);
	print(l);

	std::vector<int> 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 = [](auto& val){ ++val; };

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

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

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

auto beg = l.begin();

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

  [](auto& val){ ++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](auto& container) {

      // 省略的函数体

  };

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

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

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

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

  Happy Coding With New C++ :)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015年12月07日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档