C++ 能否成為你新的指令碼語言?

類別: IT

一些背景

第一個我真正喜愛的程式語言是 C。我花了不少時間才找到它:當我還是一個孩子,我就開始在珍貴的ZX Spectrum上使用 Z80 彙編。那些日子是你能夠真正掌握你的電腦的時候,你不需要蘋果,谷歌,微軟或者其他任何人的允許就能寫一個程式。我在漂亮的128K ZX Spectrum +2上學習了在超出 CPU 定址空間時對記憶體塊分頁。直到我進入大學,我才擁有一臺 IBM PC 機 。我玩遍了電子表格,試圖修復現存的 Fortran 程式,用土耳其字元給鍵盤驅動打補丁,還學了點 Pascal 語言。之後,在我就職於土耳其中央銀行期間,我又學習了 SQL 和 APL

我一直對 C 語言有所耳聞,但我一直沒有接觸到一款C語言編譯器。直到我到康奈爾大學擁有了一個Unix 賬號之後,我編譯了我人生中的第一個 hello.c 檔案,不久之後,我有了第一臺電腦……我在 DOS 分割槽上安裝了 DJGPP 編譯器,構建了我人生中第一個 Linux 核心(我第一個發行版是 Debian),並且開始學習 C 語言。Plauger 的 "Standard C Library" 是我最喜歡的書。

當我開始享受用 C 程式設計的時候,C++ 已經廣泛應用超過十年了。所以,我下一步使用 C++ 看起來是很自然的一步了。

除了……好吧,除了 C++ 是一片混亂。那段時間,所有人都被繼承層次深深吸引了,每個人都在編寫精心設計的字串類。大多數硬碟都太慢,不能在有限的時間內編譯出可靠的 C++ 庫(好吧,我有點誇張了),大部分 CPU 都在試圖例項化模板中融化了,大部分人,那些假裝 C++ 程式員的 C 程式員們,差點就把 malloc 的返回值給扔了。

在那時,我正忙於試圖建立定製化的網路經濟實驗,看起來 Java 似乎很有優勢。至少,它不需要麻煩地拼湊出一個對話方塊。產生少量的 socket 連線,並且使你的應用編譯和執行在多種多樣的系統上。當然,AWT Swing 都很醜陋且笨重,但對我的目的來說,那沒關係。

但是,僅僅是因為不能在實驗室之外執行我的實驗(因為在實驗室已經配置了所有電腦,java應用程式執行不會有問題)。所以我快速的把FreeBSD部署到了一臺擁有100Mhz奔騰處理器,16Mb記憶體,在角落裡收集灰塵的機器上,並搭建了一個擁有perl模組(mod_perl)的Apache伺服器,然後就能工作了。那就是我愛上Perl的時候。

那份愛完全起源於實用的原因,我並不是認為Perl特別的完美,並且那時候我認為包括其他許多語言都不是很完美,他們中的每一個都有自己的瑕疵。

Perl總是能減少我必須解決的特別問題的工作的數量,有些是因為語言特性,但大部分是因為 CPAN

舉例來說,作為一個 Perl 程式員,解析 HTML 作為 HTML 是一個解決方案。我必須決定,要麼就構造整個樹, 或者使用流化的方式。在某些情況下,前者是具有優勢的,但後者的好處是可以使記憶體的需求降至最低,即使是在這個年代,如果你處理 HTML 文件以兆位元組方式還是可以奏效的。不論哪種方式,這些工具都不會在無效的 HTML 上被卡住,並在非 XML 的有效 HTML 上運作良好。

還有,Perl 提供可移植性。如果我不需要作業系統特定的功能,不用任何修改地方,我的 perl 程式碼就可以執行。

當我寫了一些類,併為它們做了封裝,也不會有複雜的架構。

C++ 涅槃

在過去的數年,C++如獲新生。許多聰明人已經開始意識到需要向C++程式員提供同時涵蓋 work of the ISO committee boost的構建模組。

在真實環境下,仍然有90%的菜鳥生成C++程式員就是沒有意識到new是一個合法符號的C程式員。在這方面,C++與Perl非常相似:大部分人寫過Perl程式碼的人也沒有意識到Perl不是C、Java、Python、shell、Awk或者其他你可以列舉的語言。

但是,當你看到新C++標準中的新東西,以及編譯器不斷實現這些特性的新聞時,我們無法抑制住內心的興奮和好奇。

單詞計算練習

這是一個簡單的練習,使用 C++ 或者 Perl 並且不依賴外部庫,所以這是一個很好的起點。

這是 Perl 版本,供您參考:

#!/usr/bin/env perl

use strict;
use warnings;

run(\@ARGV);

sub run {
    my $argv = shift;
    my @counts;

    for my $file ( @$argv ) {
        my $count = -1;
        eval {
            $count = word_count($file);
            1;
        } or warn "$@";

        push @counts, {
            file => $file,
            word_count => $count,
        };
    }

    for my $result (@counts) {
        printf "%s: %d words\n", $result->{file}, $result->{word_count};
    }
}

sub word_count {
    my $file = shift;
    my %words;

    open my $fh, '<', $file
        or die "Cannot open '$file': $!";

    while (my $line = <$fh>) {
        my @words = split ' ', $line;
        $words{ $_ } += 1 for @words;
    }

    close $fh;

    my $word_count;
    $word_count += $_ for values %words;
    return $word_count;
}

而且,這是我最大的付出在轉化 Perl 到現代風格的 C++ 上面。我沒有嘗試寫特別搞笑的程式碼:只是和 Perl 一樣,我把重點放在寫程式碼上面,使得我感到非常自然,同時確保兩個程式都做大致相同的事情。

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <numeric>
#include <unordered_map>
#include <string>
#include <vector>

using std::accumulate;
using std::cerr;
using std::cout;
using std::endl;
using std::ifstream;
using std::make_pair;
using std::pair;
using std::strerror;
using std::string;
using std::unordered_map;
using std::vector;

int word_count(const char *const file) noexcept(false);

int main(int argc, char *argv[]) {
    vector< pair<string, int> > counts {};

    for (auto i = 1; i < argc; i += 1) {
        try {
            counts.push_back(make_pair(argv[i], word_count(argv[i])));
        } catch (const string& e) {
            cerr << e << endl;
            counts.push_back(make_pair(argv[i], -1));
        }
    }

    for (auto& result : counts) {
        cout << result.first << ": " << result.second << " words" << endl;
    }

    return 0;
}

int
word_count(const char *const file) noexcept(false) {
    errno = 0;
    ifstream fp(file);
    {
        // Does fp.fail() preserve errno?
        int save_errno = errno;
        if (fp.fail()) {
            throw("Cannot open '" + string(file) + "': " + strerror(save_errno));
        }
    }

    unordered_map<string, int> word_count {};
    string word;

    while (fp >> word) {
        word_count[word] += 1;
    }

    fp.close();

    return accumulate(
        word_count.cbegin(),
        word_count.cend(),
        0,
        [](int sum, auto& el) { return sum += el.second; }
    );
}

20 行程式碼用於 #include 和 using 宣告可能看起來有點多,但是我擡眼 using namespace std,也討厭不斷地輸入 std::... 更多的是因為我喜歡較短的程式碼行。

首先要注意的是沒有看得見的顯式的記憶體分配。容器集裝箱管理自己的記憶體。

第二,這是一個大問題:我們有自動匯入(autovivification)!

unordered_map<string, int> word_count {};
string word;

while (fp >> word) {
    word_count[word] += 1;
}

第三,我們有 lambda 表示式:

return accumulate(
    word_count.cbegin(),
    word_count.cend(),
    0,
    [](int sum, auto& el) { return sum += el.second; }
);

在這背後,accumulate 將內部變數初始化為 0,並呼叫一個匿名函式,其最後一個引數指定為當前值,以及word_count的下一個元素。

現在,我不得不承認,我不知道這些特性是如何實現的,但是 Microsoft Visual C++ 2015 RC 成功執行了,微軟似乎終於趕上了在該領域的最新發展。

那麼現在呢?

然而,並非所有事情都是那麼美好。雖然 boost libraries 庫上填補了很多空白,在構建模組時標準庫也十分給力,但是它很難與用 Perl 和 CAPN 混合編寫的那種跨平臺、可移植程式碼時的便利性相提並論。

舉個例子,我能到處找得到一個與平臺無關的庫,這個庫可以為我解析或者建立 Excel 檔案而不需要 Excel 應用?並且這個庫可以像 clang,g++ 和 cl 那樣方便編譯?這顯然找不到。

我確實非常感謝那些工作在標準委員會和那些開發編譯器和庫的人,因為有了他們我在寫 C++ 程式碼時不是那麼費力。

這讓我感到十分舒服,與此同時我又能夠盡情去折騰我的電腦。

正因為如此,我很感激。

你可以在 /r/cpp評論這篇文章

C++ 能否成為你新的指令碼語言?原文請看這裡

推薦文章