本文章是由機器翻譯。

與 c + + Windows

使用規則運算式與現代 C++

Kenny Kerr

Kenny Kerr"C + + 是一種用於開發和使用優雅的和高效率的抽象的語言"。— Bjarne Stroustrup

此報價單的 c + + 建立者從真的總結了愛的語言。我要結合的語言功能和程式設計樣式認為最合適的任務制定優雅解決我的問題。

C + + 11 介紹了一長串的功能,本身就很令人興奮,但如果你看到的都是孤立的功能的清單,然後你就要錯過。這些功能的組合是 c + + 將成巨擘,許多人已成長為欣賞。我要向您顯示如何使用正則運算式與現代 c + + 來說明這一點。標準的 C + + 11 介紹了功能強大的正則運算式庫,但如果你使用它在隔離 — 使用傳統的 c + + 程式設計風格 — 你可能會覺得有點無聊。不幸的是,這是大部分的 C + + 11 庫往往會採取的方式。然而,這種方法有一些優點。如果你要找一個簡明的例子,使用一些新的庫,它將受驚不會被迫在同一時間理解大量的新的語言功能。仍然,c + + 語言和庫功能的組合真的將變成生產性的程式設計語言 c + +。

若要保持側重于 c + + 和不正則運算式的例子,我一定要使用非常簡單的模式。您可能想知道為什麼我使用正則運算式的這種小小的問題,但它有助於避免迷路的運算式處理技工。下面是一個簡單的例子:我想要匹配的名稱可能會在哪裡的名稱的字串格式化KennyKerr"或"克爾,Kenny我需要確定的第一名和家庭名稱,然後,一些以一致的方式,將它們列印出來。首先是目標字串:

char const s[] = "Kerr, Kenny";

為了簡單起見,我將堅持 char 字串和我會避免使用標準庫 basic_string 類除了演示某些匹配結果。 沒有什麼毛病 basic_string,但我發現我做用正則運算式往往是針對記憶體映射檔的工作大多。 將這些檔的內容複寫到字串物件只會減慢我的應用程式。 標準庫的正則運算式支援是漠不關心和樂意過程無需關注他們如何管理字元的序列。

接下來我需要的是一個匹配物件:

auto m = cmatch {};

這真的是一個匹配項的集合。 Cmatch 是一個 match_results 類範本,為 char 字串的專業。 此時,匹配"集合"是空的:

ASSERT(m.empty());

我還需要一對字串來得到的結果:

string name, family;

我現在可以調用 RegEx_match 函數:

if (regex_match(s, m, regex { R"((\w+) (\w+))" }))
{
}

此函數將嘗試與反對整個字元序列模式匹配。 這是與 RegEx_search 函數是很高興自己能搜索匹配在字串內的任何點。 我只創造了 RegEx 物件"內聯"為簡潔起見,但這不是沒有代價。 如果你要重複匹配該正則運算式,您可能更好一次創建 RegEx 物件,然後抱它為您的應用程式的生活。 前面的模式匹配使用的KennyKerr"格式的名稱。 假設這匹配,可以只複製出子字串:

name   = m[1].str();
family = m[2].str();

下標運算子返回指定的 sub_match 物件。 索引為零表示匹配作為一個整體,雖然隨後索引精確定位確定了正則運算式中的任何組。 Match_results 的 sub_match 物件,也不會創建或分配一個子字串。 相反,他們劃定範圍的字元指標或反覆運算器到開頭和結尾匹配或生產通常半開放範圍在標準庫受到青睞的子匹配項。 在這種情況下,我明確打電話的 str 方法上每個 sub_match,作為字串物件棄置站台的每一個子匹配項。

處理第一次可能的格式。 第二,我需要對 RegEx_match 的替代模式與另一個調用 (從技術上講,你可以匹配這兩種格式使用單個運算式,但這不是重點):

else if (regex_match(s, m, regex { R"((\w+), (\w+))" }))
{
  name   = m[2].str();
  family = m[1].str();
}

這種模式匹配使用的"克爾、Kenny格式的名稱。 我不得不扭轉,指數計算,在此正則運算式中表示的第一批的通知標識的家族名稱,而第二個標識的第一個名稱。 這是關於它的 RegEx_match 函數。 圖 1提供了參考的完整清單。

圖 1 RegEx_match 引用示例

char const s[] = "Kerr, Kenny";
auto m = cmatch {};
string name, family;
if (regex_match(s, m, regex { R"((\w+) (\w+))" }))
{
  name   = m[1].str();
  family = m[2].str();
}
else if (regex_match(s, m, regex { R"((\w+), (\w+))" }))
{
  name   = m[2].str();
  family = m[1].str();
}
else
{
  printf("No match\n");
}

我不知道你,但在代碼圖 1 看起來對我單調乏味。 當然功能強大且靈活的正則運算式庫時,它不是特別是優雅。 我需要知道的關於 match_results 和 sub_match 物件。 我需要記住此"集合"如何進行索引以及如何提取結果。 我可以避免製作副本,但它很快就會成為繁重。

已經用過一些新的 c + + 語言功能,您可能會或可能不會前,但什麼都不應該過於令人震驚。 現在我想要顯示了如何使用 variadic 範本來真的來裝點您的正則運算式的使用方式。 而不潛水和更多的語言功能,我要開始向你們展示一個簡單的抽象來簡化文本處理,以便我可以保留這實際和優雅。

第一,我就會定義一個簡單類型,代表的是不是一定是 null 終止的字元序列。 在這裡是帶鋼類:

struct strip
{
  char const * first;
  char const * last;
    strip(char const * const begin,
          char const * const end) :
      first { begin },
      last  { end }
    {}
    strip() : strip { nullptr, nullptr } {}
};

無疑是眾多可能重用,但我發現它有助於避免太多的依賴關係時產生的簡單抽象的類。

脫衣舞班沒做很多,但我會增加它的一組非成員函數。 我會開始與一對函數來籠統地定義範圍:

auto begin(strip const & s) -> char const *
{
  return s.first;
}
auto end(strip const & s) -> char const *
{
  return s.last;
}

雖然不是嚴格必要到此示例中,我發現這種技術提供了值得的一致性與標準庫容器和演算法措施。 我會回來的開始和結束函數在一個時刻。 接下來是 make_strip helper 函數:

template <unsigned Count>
auto make_strip(char const (&text)[Count]) -> strip
{
  return strip { text, text + Count - 1 };
}

試圖創建一個條帶從字串文本時此函數很有用。 例如,我可以初始化一個條帶,如下所示:

auto s = make_strip("Kerr, Kenny");

下一步,經常是有用來確定的長度或地帶的大小:

auto size(strip const & s) -> unsigned
{
  return end(s) - begin(s);
}

在這裡你可以看到我很簡單地重用的開始和結束的職能,以避免在地帶成員上的依賴項。 我可以保護地帶類的成員。 另一方面,很有説明,以便能夠操縱它們直接從內一種演算法。 不過,我不需要採取一個硬依賴項,我不會。

很明顯,它已經夠簡單,從地帶創建標準的字串:

auto to_string(strip const & s) -> string
{
  return string { begin(s), end(s) };
}

這可能用場如果的一些結果罌粟花的原始的字元序列。 這輪基本地帶處理作業。 我可以初始化一個條帶,並確定其大小 — 的開始和結束的職能,因為我可以用一系列-為語句來逐一查看它的字元:

auto s = make_strip("Kenny Kerr");
for (auto c : s)
{
  printf("%c\n", c);
}

當我第一次寫了脫衣舞班時,我希望我可以打電話給其成員"開始"和"結束"而不是"第一"和"最後一個"。麻煩是,編譯器,當面臨著一系列-的發言,第一次嘗試查找可能作為函式呼叫的適當成員。 如果目標範圍或序列中不包含任何成員調用的開始和結束,然後編譯器會尋找一個適當的對在封閉範圍內。 麻煩的是如果編譯器找到成員調用的開始和結束,但他們不是合適,它不會嘗試看得更遠。 這看起來可能近視,但 c + + 具有複雜的名稱查找規則,和別的會使它更加困惑和不一致。

帶類是一個簡單的小構造,但它不會做的事情本身多。 我現在就會將它組合與正則運算式庫,以產生一種優雅的抽象。 我想要隱藏的匹配物件,單調乏味的運算式處理部分的力學。 這是 variadic 範本的進來。 瞭解 variadic 範本的關鍵實現你可以在其他中分開的第一個參數。 這通常會導致在編譯時遞迴。 我可以定義一個 variadic 範本,將匹配物件解壓縮到後續參數:

template <typename...
Args>
auto unpack(cmatch const & m,
            Args & ...
args) -> void
{
  unpack<sizeof...(Args)>(m, args...);
}

"Typename......"指示 Args 是範本參數包。 相應"..."中的類型參數指示該參數是一個函數參數包。 "Sizeof......"運算式確定參數包中的元素數。 "......"之後 args 最後告訴編譯器,擴大到它的序列的元素的參數包。

每個參數的類型可能不同,但在這種情況下,每個將非 const 地帶的引用。 我正在使用 variadic 的範本,因此可以支援未知的參數數目。 到目前為止的解包功能似乎不是遞迴。 它將轉發到另一個解包函數與其他範本參數及其參數:

template <unsigned Total, typename...
Args>
auto unpack(cmatch const & m,
            strip & s,
            Args & ...
args) -> void
{
  auto const & v = m[Total - sizeof...(Args)];
  s = { v.first, v.second };
  unpack<Total>(m, args...);
}

然而,此解包函數將從其餘部分後的匹配物件的第一個參數分隔開。 這就是編譯時遞迴在行動中。 假設 args 參數包不是空的它調用本身與參數的其餘部分。 最終的參數序列變為空和第三種解包功能需要處理這一結論:

template <unsigned>
auto unpack(cmatch const &) -> void {}

此函數不會做任何事。 它只是承認參數包可能為空的事實。 以前的解包功能到拆包的匹配物件按住鍵。 第一次的解包功能捕獲原始的參數包中的元素數。 這是必要的因為每個遞迴呼叫將有效地產生新的參數包一個遞減的大小。 請注意如何我減去的參數包從原來的總大小。 鑒於此總或穩定的大小,可以索引到匹配集合中檢索單個子匹配項並將其各自邊界複製到 variadic 參數

那照顧拆包的匹配物件。 雖然不是必需的我仍然覺得有説明,如果它不直接需要隱藏的匹配物件本身 — 例如,如果它有只訪問所需的匹配首碼和尾碼。 我想把整件事情,提供一個更簡單的匹配抽象:

template <typename...
Args>
auto match(strip const & s,
           regex const & r,
           Args & ...
args) -> bool
{
  auto m = cmatch {};
  if (regex_match(begin(s), end(s), m, r))
  {
    unpack<sizeof...(Args)>(m, args...);
  }
    return !m.empty();
}

此函數也是 variadic 的範本,但本身不是遞迴。 它只是將轉發到最初的解包功能用於處理其參數。 它還負責提供本地匹配物件的和定義的地帶的搜索序列的開始和結束的 helper 函數。 可以編寫幾乎完全相同的功能,以適應而不是 RegEx_match RegEx_search。 我現在可以重寫該示例從圖 1 遠得更簡單:

auto const s = make_strip("Kerr, Kenny");
strip name, family;
if (match(s, regex { R"((\w+) (\w+))"  }, name,   family) ||
    match(s, regex { R"((\w+), (\w+))" }, family, name))
{
  printf("Match!
\n");
}

如何反覆運算? 解包功能還用場處理反覆運算搜索的匹配結果。 想像與規範"Hello 世界"在各種語言的字串:

auto const s =
  make_strip("Hello world/Hola mundo/Hallo wereld/Ciao mondo");

我可以匹配每一個與下面的正則運算式:

auto const r = regex { R"((\w+) (\w+))" };

正則運算式庫提供 RegEx_iterator 來逐一查看匹配的連絡人,但直接使用反覆運算器可以變得單調乏味。 一個選項是寫一個 for_each 函數,為每個匹配項調用一個謂詞:

template <typename F>
auto for_each(strip const & s,
              regex const & r,
              F callback) -> void
{
  for (auto i = cregex_iterator { begin(s), end(s), r };
       i != cregex_iterator {};
       ++i)
  {
    callback(*i);
  }
}

然後,我可以調用此函數與 lambda 運算式進行解包的每個匹配:

for_each(s, r, [] (cmatch const & m)
{
  strip hello, world;
  unpack(m, hello, world);
});

這當然有效,但總是發現它令人沮喪的是我不能輕鬆地打破這種類型的迴圈結構。 範圍-為語句提供了更方便的替代方案。 我會首先通過定義編譯器將會認識到執行一系列簡單反覆運算器範圍-for 迴圈:

template <typename T>
struct iterator_range
{
  T first, last;
  auto begin() const -> T { return first; }
  auto end() const -> T { return last; }
};

我現在可以編寫一個簡單的 for_each 函數,只是返回 iterator_range:

auto for_each(strip const & s,
              regex const & r) -> iterator_range<cregex_iterator>
{
  return
  {
    cregex_iterator { begin(s), end(s), r },
    cregex_iterator {}
  };
}

編譯器將照顧生產反覆運算,我可以簡單地寫一系列-與句法開銷最低發言、 早打破自己的選擇:

for (auto const & m : for_each(s, r))
{
  strip hello, world;
  unpack(m, hello, world);
  printf("'%.*s' '%.*s'\n",
         size(hello), begin(hello),
         size(world), begin(world));
}

在主控台顯示預期的結果:

'Hello' 'world'
'Hola' 'mundo'
'Hallo' 'wereld'
'Ciao' 'mondo'

C + + 11 和超越提供振興現代樣式使您可以產生優雅的和高效率的抽象的程式設計的 c + + 軟體發展的機會。 正則運算式語法可以自卑即使最有經驗的開發人員。 為什麼不花幾分鐘開發一個更優雅抽象? 至少你的任務的 c + + 部分將是一種樂趣 !

Kenny Kerr 是設在加拿大的一位作者為 Pluralsight 和微軟最有價值球員一個電腦程式員。在他博客 kennykerr.ca 你可以跟著他在 Twitter 上 twitter.com/kennykerr