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

63 篇文章 1 订阅

  之前了解过一些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 = [](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++ :)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值