河南工程学院计算机学院
首页 / IT资讯 / 正文
C++程序员福音!何时编码像写Python一样酸爽 阅读量:2020年12月18日

C++是C语言的继承,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计,因而C++就适应的问题规模而论,大小由之。

Python是一种代表简单主义思想的语言,它能够使我们专注于解决问题而不是去搞明白语言本身,阅读Python程序就感觉像是在读英语一样。对于C++程序员而言,何时编码能像写Python一样酸爽呀!

作为一名勤劳的开发者,怎么能就这样安于现状呢?让我们尝试实现一下Python中内置函数range和filter:


  • range可创建一个整数列表,一般用在 for 循环中;

  • filter将谓词应用于序列,并返回谓词针对其返回true的结果;


序列是Python中的一个术语,代表可迭代的内容,例如列表[6,7,8],元组(9, 0, 1)或字符串“ abc”。看到没?Python中的序列是不是很强大?而不是C++中经常使用的std::vector 。

写在之前的话

在开始之前,我必须得先说几句:

  1. 我在示例中使用了Eric Niebler 的range-v3库,是在C++ 20的基础上进行二次开发。

  2. 我不喜欢编程语言之间谁好谁坏的无味之争,所以在评论中,我不会对相关评论进行回复。

range

在以下示例中,我首先显示注释掉的python表达式,然后显示相应的C ++调用。

// range.cpp


#include


#include <range/v3/all.hpp>


#include



std::vector range(int begin, int end, int stepsize = 1) {


    std::vector result{};


    if (begin < end) {                                     // (5)


        auto boundary = [end](int i){ return i < end; };


        for (int i: ranges::views::iota(begin) | ranges::views::stride(stepsize)


                                               | ranges::views::take_while(boundary)) {


            result.push_back(i);


        }


    }


    else {                                                 // (6)


        begin++;


        end++;


        stepsize *= -1;


        auto boundary = [begin](int i){ return i < begin; };


        for (int i: ranges::views::iota(end) | ranges::views::take_while(boundary)


                                             | ranges::views::reverse


                                             | ranges::views::stride(stepsize)) {


            result.push_back(i);


        }


    }


    return result;


}



int main() {


    std::cout << std::endl;



    // range(1, 50)                                       // (1)


    auto res = range(1, 50);


    for (auto i: res) std::cout << i << " ";



    std::cout << "\\n\\n";



    // range(1, 50, 5)                                    // (2)


    res = range(1, 50, 5);


    for (auto i: res) std::cout << i << " ";



    std::cout << "\\n\\n";



    // range(50, 10, -1)                                  // (3)


    res = range(50, 10, -1);


    for (auto i: res) std::cout << i << " ";



    std::cout << "\\n\\n";



    // range(50, 10, -5)                                  // (4)


    res = range(50, 10, -5);


    for (auto i: res) std::cout << i << " ";



    std::cout << "\\n\\n";



}

在查看输出时,第(1)--(4)行中的调用应该比较容易阅读的。

range调用的前两个参数代表创建整数的开始和结束,包括开始但不包括结束(左开右闭),步长默认为1。当间隔[begin,end ]减小时,步长应为负,否则会得到一个空列表或一个空std::vector。

特别要说明下的是,我使用的函数range::views::stride不属于C++ 20。stride(n)返回给定范围的第n个元素。如果说有人知道怎么基于C++来优雅实现,可以在评论区中留言告诉我哦。

filter

在Python3中filter和map都是惰性的(生成的是迭代器,还需要进行迭代),返回为生成器。为了获得跟Python2一样的使用效果,在Python3的周围进行列表强转以供filter和lambda调用。

filter(lambda i: (i % 2) == 1 , range(1, 10))       # Python 2



list(filter(lambda i: (i % 2) == 1, range(1, 10))) # Python 3

这两个调用产生相同的列表:[1、3、5、7、9 ]。

是不是很方便呀?C++程序员表示也想要,轮子继续造起来:

// filter.cpp



#include "range.hpp"                          // (1)


#include


#include


#include <range/v3/all.hpp>


#include


#include


#include


#include



template       // (2)


auto filter(Func func, Seq seq) {



    typedef typename Seq::value_type value_type;


    std::vector<value_type> result{};


    for (auto i : seq | ranges::views::filter(func)) result.push_back(i);



    return result;


}



int main() {



    std::cout << std::endl;



    // filter(lambda i: (i % 3) == 0 , range(20, 50))     // (3)


    auto res = filter([](int i){ return (i % 3) == 0; }, range(20, 50) );


    for (auto v: res) std::cout << v << " ";



                                                          // (4)


    // filter(lambda word: word[0].isupper(), ["Only", "for", "testing", "purpose"])


    std::vector myStrings{"Only", "for", "testing", "purpose"};


    auto res2 = filter([](const std::string& s){ return static_cast(std::isupper(s[0])); }, myStrings);



    std::cout << "\\n\\n";



    for (auto word: res2) std::cout << word << std::endl;



    std::cout << std::endl;



                                                          // (5)


    // len(filter(lambda line: line[0] == "#", open("/etc/services").readlines()))


    std::ifstream file("/etc/services", std::iOS::in);


    std::vector lines;


    std::string line;


    while(std::getline(file, line)){


        lines.push_back(line);


    }


    std::vector commentLines = filter([](const std::string& s){  return s[0] == '#'; }, lines);



    std::cout << "Comment lines: " << commentLines.size() << "\\n\\n";



}

在解释程序之前,我们来看看输出:

上述的过滤器功能(第2行)应该很比较好理解吧。我只是将序列的每个元素应用于可调用函数,并在std::vector中具体实现这些元素。

如果忽略掉Python和C++中实现lambda的方法不同,整个过滤器filter的调用是不是非常相似?

map

map的实现要比filter复杂得多。有如下原因:首先map可以改变输入序列的类型;其次我的map实现触发了GCC错误报告;如果调试好了,我会在后续的文章中进行发表。

C++不仅拥有计算机高效运行的实用性特征,同时还致力于提高大规模程序的编程质量与程序设计语言的问题描述能力。