【编程基础】C++初学者需掌握的10个C++特性(中)

Strongly-typed enums 强类型枚举

传统的C++枚举类型存在一些缺陷:它们会将枚举常量暴露在外层作用域中(这可能导致名字冲突,如果同一个作用域中存在两个不同的枚举类型,但是具有相同的枚举常量就会冲突),而且它们会被隐式转换为整形,无法拥有特定的用户定义类型。

在C++11中通过引入了一个称为强类型枚举的新类型,修正了这种情况。强类型枚举由关键字enum class标识。它不会将枚举常量暴露到外层作用域中,也不会隐式转换为整形,并且拥有用户指定的特定类型(传统枚举也增加了这个性质)。

enumclassOptions {None, One, All};
Options o = Options::All;

Smart Pointers 智能指针

已经有成千上万的文章讨论这个问题了,所以我只想说:现在能使用的,带引用计数,并且能自动释放内存的智能指针包括以下几种:

· unique_ptr: 如果内存资源的所有权不需要共享,就应当使用这个(它没有拷贝构造函数),但是它可以转让给另一个unique_ptr(存在move构造函数)。

· shared_ptr: 如果内存资源需要共享,那么使用这个(所以叫这个名字)。

· weak_ptr: 持有被shared_ptr所管理对象的引用,但是不会改变引用计数值。它被用来打破依赖循环(想象在一个tree结构中,父节点通过一个共享所有权的引用(chared_ptr)引用子节点,同时子节点又必须持有父节点的引用。如果这第二个引用也共享所有权,就会导致一个循环,最终两个节点内存都无法释放)。

另一方面,auto_ptr已经被废弃,不会再使用了。

什么时候使用unique_ptr,什么时候使用shared_ptr取决于对所有权的需求,我建议阅读以下的讨论:http://stackoverflow.com/questions/15648844/using-smart-pointers-for-class-members

以下第一个例子使用了unique_ptr。如果你想把对象所有权转移给另一个unique_ptr,需要使用std::move(我会在最后几段讨论这个函数)。在所有权转移后,交出所有权的智能指针将为空,get()函数将返回nullptr。

voidfoo(int*  p)

{

std::cout << *p  << std::endl;

}

std::unique_ptr<int>  p1(newint(42));

std::unique_ptr<int>  p2 = std::move(p1); // transfer ownership

 

if(p1)

foo(p1.get());

 

(*p2)++;

 

if(p2)

foo(p2.get());

第二个例子展示了shared_ptr。用法相似,但语义不同,此时所有权是共享的。

voidfoo(int*  p)

{

}

voidbar(std::shared_ptr<int>  p)

{

++(*p);

}

std::shared_ptr<int>  p1(newint(42));

std::shared_ptr<int>  p2 = p1;

 

bar(p1);

foo(p2.get());

第一个声明和以下这行是等价的:

auto p3 =  std::make_shared<int>(42);

make_shared<T>是一个非成员函数,使用它的好处是可以一次性分配共享对象和智能指针自身的内存。而显示地使用shared_ptr构造函数来构造则至少需要两次内存分配。除了会产生额外的开销,还可能会导致内存泄漏。在下面这个例子中,如果seed()抛出一个错误就会产生内存泄漏。

voidfoo(std::shared_ptr<int>  p, intinit)

{

*p = init;

}

foo(std::shared_ptr<int>(newint(42)), seed());

如果使用make_shared就不会有这个问题了。第三个例子展示了weak_ptr。注意,你必须调用lock()来获得被引用对象的shared_ptr,通过它才能访问这个对象。

auto p =  std::make_shared<int>(42);

std::weak_ptr<int> wp  = p;

 

{

auto sp = wp.lock();

std::cout << *sp  << std::endl;

}

 

p.reset();

 

if(wp.expired())

std::cout <<  "expired"<< std::endl;

如果你试图锁定(lock)一个过期(指被弱引用对象已经被释放)的weak_ptr,那你将获得一个空的shared_ptr.

Lambdas

匿名函数(也叫lambda)已经加入到C++中,并很快异军突起。这个从函数式编程中借来的强大特性,使很多其他特性以及类库得以实现。你可以在任何使用函数对象或者函子(functor)或std::function的地方使用lambda。你可以从这里(http://msdn.microsoft.com/en-us/library/dd293603.aspx)找到语法说明。

std::vector<int> v;

v.push_back(1);

v.push_back(2);

v.push_back(3);

 

std::for_each(std::begin(v),  std::end(v), [](intn) {std::cout << n  << std::endl;});

 

auto is_odd = [](intn) {returnn%2==1;};

auto pos =  std::find_if(std::begin(v), std::end(v), is_odd);

if(pos != std::end(v))

std::cout << *pos  << std::endl;

更复杂的是递归lambda。考虑一个实现Fibonacci函数的lambda。如果你试图用auto来声明,就会得到一个编译错误。

auto fib = [&fib](intn) {returnn < 2 ? 1 : fib(n-1) + fib(n-2);};
error C3533: 'auto &': a  parameter cannot have a type that contains 'auto'

error C3531: 'fib': a symbol  whose type contains 'auto' must have an initializer

error C3536: 'fib': cannot  be used before it is initialized

error C2064: term does not  evaluate to a function taking 1 arguments

问题出在auto意味着对象类型由初始表达式决定,然而初始表达式又包含了对其自身的引用,因此要求先知道它的类型,这就导致了无穷递归。解决问题的关键就是打破这种循环依赖,用std::function显式的指定函数类型:

std::function<int(int)>  lfib = [&lfib](intn) {returnn < 2 ? 1 : lfib(n-1) + lfib(n-2);};

非成员begin()和end()

也许你注意到了,我在前面的例子中已经用到了非成员begin()和end()函数。他们是新加入标准库的,除了能提高了代码一致性,还有助于更多地使用泛型编程。它们和所有的STL容器兼容。更重要的是,他们是可重载的。所以它们可以被扩展到支持任何类型。对C类型数组的重载已经包含在标准库中了。

我们还用上一个例子中的代码来说明,在这个例子中我打印了一个数组然后查找它的第一个偶数元素。如果std::vector被替换成C类型数组。代码可能看起来是这样的:

intarr[] =  {1,2,3};

std::for_each(&arr[0],  &arr[0]+sizeof(arr)/sizeof(arr[0]), [](intn)  {std::cout << n << std::endl;});

 

auto is_odd = [](intn) {returnn%2==1;};

auto begin = &arr[0];

auto end =  &arr[0]+sizeof(arr)/sizeof(arr[0]);

auto pos = std::find_if(begin,  end, is_odd);

if(pos != end)

std::cout << *pos  << std::endl;

如果使用非成员的begin()和end()来实现,就会是以下这样的:

intarr[] =  {1,2,3};

std::for_each(std::begin(arr),  std::end(arr), [](intn) {std::cout << n  << std::endl;});

 

auto is_odd = [](intn) {returnn%2==1;};

auto pos =  std::find_if(std::begin(arr), std::end(arr), is_odd);

if(pos != std::end(arr))

std::cout << *pos  << std::endl;

这基本上和使用std::vecto的代码是完全一样的。这就意味着我们可以写一个泛型函数处理所有支持begin()和end()的类型。

static_assert和 type traits

static_assert提供一个编译时的断言检查。如果断言为真,什么也不会发生。如果断言为假,编译器会打印一个特殊的错误信息。

template<typenameIterator>

voidbar(Iterator  begin, Iterator end)

{

std::for_each(begin, end,  [](intn) {std::cout << n << std::endl;});

 

auto is_odd = [](intn) {returnn%2==1;};

auto pos =  std::find_if(begin, end, is_odd);

if(pos != end)

std::cout << *pos  << std::endl;

}

 

template<typenameC>

voidfoo(C c)

{

bar(std::begin(c),  std::end(c));

}

 

template<typenameT, size_tN>

voidfoo(T(&arr)[N])

{

bar(std::begin(arr),  std::end(arr));

}

 

intarr[] =  {1,2,3};

foo(arr);

 

std::vector<int> v;

v.push_back(1);

v.push_back(2);

v.push_back(3);

foo(v);

static_assert和type traits一起使用能发挥更大的威力。type traits是一些class,在编译时提供关于类型的信息。在头文件<type_traits>中可以找到它们。这个头文件中有好几种class:helper class,用来产生编译时常量。type traits class,用来在编译时获取类型信息,还有就是type transformation class,他们可以将已存在的类型变换为新的类型。

感谢作者冯上(@治不好你我就不是兽医 ),本文转自伯乐在线

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-05-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏你不就像风一样

Java性能优化之编程技巧总结

程序的性能受代码质量的直接影响。在本文中,主要介绍一些代码编写的小技巧和惯例,这些技巧有助于在代码级别上提升系统性能。

581
来自专栏菩提树下的杨过

python: 序列化/反序列化及对象的深拷贝/浅拷贝

一、序列化/反序列化 python中内置了很多序列化/反序列化的方式,最常用的有json、pickle、marshal这三种,示例用法如下: import js...

2886
来自专栏程序员互动联盟

java到底和C++有啥区别?

作为一名C++程序员,我们早已掌握了面向对象程序设计的基本概念,而且Java的语法无疑是非常熟悉的。事实上,Java本来就是从C++衍生出来的。 然而,C++和...

3246
来自专栏数据结构与算法

UOJ#52. 【UR #4】元旦激光炮(交互)

一种很显然的$log^2n$的做法:首先在$a$中二分,然后再$b,c$中二分。这样可以得到$60$分的好成绩。

462
来自专栏Pythonista

time模块

在Python中,通常有这三种方式来表示时间,时间戳,元组(struct_time)、格式化的时间字符串:

502
来自专栏aCloudDeveloper

百练OJ 1017 2801

一、1017填箱子      这个题题目读起来有些晦涩,如果按题目给的条件很难找到突破口,需要事先计算一下每种CP(产品)在没装满的情况下还需要的其他CP数。(...

16410
来自专栏Golang语言社区

golang 几种字符串的连接方式

最近在做性能优化,有个函数里面的耗时特别长,看里面的操作大多是一些字符串拼接的操作,而字符串拼接在 golang 里面其实有很多种实现。 实现方法 直接使用运算...

2454
来自专栏一“技”之长

Objective-C 中变量的作用域 原

被这个关键字修饰的变量是受保护的,只有在声明变量的这个类中和它的子类中,可以访问。

802
来自专栏量化投资与机器学习

【精心解读】用pandas处理大数据——节省90%内存消耗的小贴士

本文我们讨论 pandas 的内存使用,展示怎样简单地为数据列选择合适的数据类型,就能够减少 dataframe 近 90% 的内存占用。

1.5K5
来自专栏鸿的学习笔记

合理选择数据结构

写程序很重要的一点是选择合理的数据结构,不合适的数据结构在如今高性能计算机盛行的情况下,小数据量体现不出什么来,但是在超大数据的时候, 你所面临的困境将会无穷的...

552

扫描关注云+社区