C++ 17 写法上已经很接近 python 了

2016-08-27 20:35:25 +08:00
 taowen

list 和 map

本节例子选自: https://gist.github.com/JeffPaine/6213790

对 python 这样的动态语言最直观的感受就是 list/map 两种数据结构打天下。 php 和 lua 甚至把这两个都合并成一种数据结构了。 毋庸置疑,学会如何使用 list 和 map 是基础中的基础。

for 循环

Python 版本

import unittest

class Test(unittest.TestCase):
    def test_foreach_on_lazy_range(self):
        for i in xrange(6):
            print i ** 2

C++ 版本

#include <catch_with_main.hpp>
#include <range/v3/all.hpp>

using namespace ranges;

TEST_CASE("foreach on lazy range") {
    for(const auto& x : view::ints(0, 6)) {
        std::cout << x * x << std::endl;
    }
}

注意到 const auto& 的写法,这个表示我对这个变量进行只读的使用。只要是能用 const 的地方就用 const ( http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#a-namerconst-immutableacon1-by-default-make-objects-immutable )。 为什么还需要加上 reference ?因为非 reference 的版本默认的语义我要拥有这个变量( make a copy )。而在 for 循环下我们显然是只打算使用这个变量, 而不是去拥有一份。为什么不用指针而用引用?因为指针可空, reference 不可空。

view::ints 是 range-v3 这个库提供的,作用等同于 xrange 。将来 range-v3 会成为标准库的一部分。

foreach

Python 版本

import unittest

class Test(unittest.TestCase):
    def test_foreach_on_list(self):
        colors = ['red', 'green', 'blue', 'yellow']
        for color in colors:
            print color

C++ 版本

#include <catch_with_main.hpp>

using namespace ranges;

TEST_CASE("foreach on list") {
    auto colors = {"red", "green", "blue", "yellow"};
    for(const auto& color : colors) {
        std::cout << color << std::endl;
    }
}

与 python 不同, c++没有所谓的默认的 list 类型。上面的写法是最简洁的写法。 colors 变量的实际类型 根据 GDB 是 std::initializer_list<const char*>。只有 begin , end , size 几个函数。实际上类似于 python 的 tuple 。 考虑到 python 的 list 类型是 mutable 的,所以更合适的实现是 std::vector 。

#include <catch_with_main.hpp>

using namespace ranges;

TEST_CASE("foreach on vector") {
    auto colors = std::vector<const char*>{"red", "green", "blue", "yellow"};
    for(const auto& color : colors) {
        std::cout << color << std::endl;
    }
}

foreach 倒序

Python 版本

import unittest

class Test(unittest.TestCase):

    def test_foreach_reversed(self):
        colors = ['red', 'green', 'blue', 'yellow']
        for color in reversed(colors):
            print(color)

C++ 版本

#include <catch_with_main.hpp>
#include <range/v3/all.hpp>

using namespace ranges;

TEST_CASE("foreach reversed") {
    auto colors = std::vector<const char*>{"red", "green", "blue", "yellow"};
    for(const auto& color : colors | view::reverse) {
        std::cout << color << std::endl;
    }
}

这里使用了 range-v3 的 view 组合,类似 unix pipe 的语法。

foreach 带下标

Python 版本

import unittest

class Test(unittest.TestCase):
    def test_foreach_with_index(self):
        colors = ['red', 'green', 'blue', 'yellow']
        for i, color in enumerate(colors):
            print(i, color)

C++ 版本

#include <catch_with_main.hpp>
#include <range/v3/all.hpp>

using namespace ranges;

TEST_CASE("foreach with index") {
    auto colors = std::vector<const char*>{"red", "green", "blue", "yellow"};
    for(const auto& [i, color] : view::zip(view::iota(0), colors)) {
        std::cout << i << " " << color << std::endl;
    }
}

view::iota这个的意思是产生一个从 n 开始的逐个加一的 view ,类似 python 里的 generator 。然后 zip 是把两个 view 逐个对应起来合并成一个 pair 的 view 。 然后const auto& [i, color]是 c++ 17 的 structured bindings 的写法,和 python 解开 tuple 里的元素的做法是如出一辙的。

zip

下面这个例子可以看得更清楚。 Python 版本

import unittest
import itertools

class Test(unittest.TestCase):
    def test_zip(self):
        names = ['raymond', 'rachel', 'matthew']
        colors = ['red', 'green', 'blue', 'yellow']
        for name, color in itertools.izip(names, colors):
            print(name, color)

izip 返回的是 generator 。 zip 返回都是 list 。 C++ 版本

#include <catch_with_main.hpp>
#include <range/v3/all.hpp>

using namespace ranges;

TEST_CASE("zip") {
    auto names = std::vector<const char*>{"raymond", "rachel", "matthew"};
    auto colors = std::vector<const char*>{"red", "green", "blue", "yellow"};
    for(const auto& [name, color] : view::zip(names, colors)) {
        std::cout << name << " " << color << std::endl;
    }
}

sorted

Python 版本

import unittest

class Test(unittest.TestCase):
    def test_sort(self):
        colors = ['red', 'green', 'blue', 'yellow']
        for color in sorted(colors):
            print(color)

C++ 版本

#include <catch_with_main.hpp>
#include <range/v3/all.hpp>

using namespace ranges;

TEST_CASE("sort") {
    auto colors = std::vector<std::string>{"red", "green", "blue", "yellow"};
    colors |= action::sort;
    for(const auto& color : colors) {
        std::cout << color << std::endl;
    }
}

这个例子里const char*换成了std::string,因为只有字符串类型才知道怎么比较,才能排序。 action::sort与 view 不同,它返回的是具体的 container ,而不再是 view 了。

如果要倒过来排序,再 python 中是这样的

import unittest

class Test(unittest.TestCase):
    def test_sort_reverse(self):
        colors = ['red', 'green', 'blue', 'yellow']
        for color in sorted(colors, reverse=True):
            print(color)

C++ 版本

#include <catch_with_main.hpp>
#include <range/v3/all.hpp>

using namespace ranges;

TEST_CASE("sort reverse") {
    auto colors = std::vector<std::string>{"red", "green", "blue", "yellow"};
    colors |= action::sort(std::greater<std::string>());
    for(const auto& color : colors) {
        std::cout << color << std::endl;
    }
}

Python 还支持指定属性去排序

import unittest

class Test(unittest.TestCase):

    def test_custom_sort(self):
        colors = ['red', 'green', 'blue', 'yellow']
        for color in sorted(colors, key=lambda e: len(e)):
            print(color)

C++ 版本

#include <catch_with_main.hpp>
#include <range/v3/all.hpp>

using namespace ranges;

TEST_CASE("custom sort") {
    auto colors = std::vector<std::string>{"red", "green", "blue", "yellow"};
    colors |= action::sort(std::less<std::string>(), [](const auto&e) {
        return e.size();
    });
    for(const auto& color : colors) {
        std::cout << color << std::endl;
    }
}

sort的第一个参数是 comparator ,第二个参数是 projector 。这里我们使用了一个 lambda 表达式,从字符串上取得其长度值,用长度去排序。


需要的编译环境

参见: https://taowen.gitbooks.io/modern-cpp-howto/content/unit-testing/chapter.html

37518 次点击
所在节点    Python
77 条回复
jeffersonpig
2016-08-29 08:46:57 +08:00
好吧原来我一直是个 C++门外汉
gougudingli
2016-08-29 08:57:12 +08:00
有些编程书,还在让读者装 VC++6.o 。心生疑惑: C++11 、 17 那么多版本,究竟是哪些人来维护和修改的?
stormpeach
2016-08-29 09:03:57 +08:00
@hitmanx 不能更同意,所以现在去学 rust 了。。。
stormpeach
2016-08-29 09:08:08 +08:00
看来又要出一本新的书了:《 effective range-v3 》。。。
lcc4376
2016-08-29 09:11:49 +08:00
上次寫 c++是 08 年的事
gotham
2016-08-29 09:16:55 +08:00
你是 sb 吗, TEST_CASE 这种写法出现十多年了。
joye1230
2016-08-29 09:31:25 +08:00
re 从零开始 c++
likebeta
2016-08-29 09:37:48 +08:00
@gotham 楼主再说 TEST_CASE ? 智商是硬伤
hei1000
2016-08-29 09:43:30 +08:00
搞得这么复杂,还是喜欢 C,尽管有缺陷,但也没看像 C++这样什么都往里面家
wubotao
2016-08-29 09:58:51 +08:00
好久不写 C++ 了。
bp0
2016-08-29 10:14:38 +08:00
算了,我已经不认识 C++了,还是默默的写 C 吧。
cppgohan
2016-08-29 10:25:02 +08:00
不说语言之争, 语言工具各有利弊. :)

view 是默认的命名空间吗? 略微有点奇怪.

好久没在生产环境写 C++, 从没在生产环境写过 C++11, 不知道这里哪些地方用了 C++17 的哪些特性? 楼主可以多分享分享, 还是挺有兴趣的.


至于 python, 主要是写起来简单, 不用考虑如同 c++那种更复杂完备的 lambda 语法, 常量引用这些.

比如你的例子中的排序, python 一行列表析取式就直接搞定了.
print [x for x in sorted(['red', 'green', 'blue', 'yellow'])]


这两年工作以做 android 开发 /java 语言为主, 过去的 c++底子有点秀逗了. python 也是更早自学敲敲打打的.

但是 python 捡起来相比 c++还是容易太多了.

C++对人的要求更高些, 暴露的更多细节, 如果经验攒的不够, 更容易写出更坏味道的代码.

就如同 steve yegge 说 java 语言: Java 语言有个优点, 它让那些烂程序员造不成太大的破坏性:).
taowen
2016-08-29 10:31:11 +08:00
@cppgohan 所以需要更多 http://clang.llvm.org/extra/clang-tidy/ 和 clion 这样的 IDE ,把经验变成静态检查。新的 GSL 规范利用静态检查来做所有权和边界安全。基本上相当于是利用静态检查在 c++的基础上发明一个子集的语言的感觉了。这门新的语言虽然还叫 c++,但是就不那么需要依赖程序员自律来保障安全了。
wizardforcel
2016-08-29 10:46:31 +08:00
我之前把一个 node 写的代码用 c++11 翻写了一遍,发现除了不支持字典的字面值,其它还好。
wangkun2012324
2016-08-29 16:31:02 +08:00
这样看来, Swift 还是不错的,哈哈哈
andypinet
2016-08-30 13:04:36 +08:00
c++ 23 估计可以更 python
lellansin
2016-08-31 12:18:19 +08:00
可啪

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/302179

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX