読者です 読者をやめる 読者になる 読者になる

水面下の夢

競プロやイラストに興味があります.メインブログがここ.ソシャゲの話はこっち(http://ameblo.jp/0n0-yumechi/).ブログアイコンはYaQ(@8_9_00)さんから.

C++の文字列処理関係と正規探索(未完)について

はじめに


この記事は初心者 C++er Advent Calendar 2015 22日目の記事になります.
初心者 C++er Advent Calendar 2015 - Adventar


そして一日遅刻してしまったのです… 大変申し訳無い. (内容的に良くないなと思ったので, 書き直しました)

内容


読んでいると,なーにが初心者なんだ… みんなレベル高すぎでしょ…
と思ったので,私はすごく基礎の基礎に返って,文字列や正規探索の話をすることにしました.


最後の参考文献に書いてある本の内容を元に書いております.


関係のあるクラス,関数たち

   <cctype>:文字クラス判定関数
   <string>:文字列関連処理
   <ragex>:正規探索
   <cstring>:C言語スタイルの文字列の支援関数

なんか表記がバグる… のでテキスト表記.



あと,cctype,cstringは本記事では扱いません.


初心者にもわかりやすい,Stringを中心に進めていきます.


cctypeについて

スペースとか,アルファベットとか,数字であるか,大文字であるか,などを判定できるみたい.
あと大文字と小文字を変換する関数もあるみたい.


競プロerの人は使うのかも?


stringについて


Javaなどでは馴染みの深い,文字列型.C言語ではchar型のポインタなどを使わなければいけないので,非常に便利.というか無いと生きていけないと思うんですがそれは.


参考文献では,stringを用いた実装とchar *型を用いた実装の2つを比較しており,以下の様な議論をしている.

実装 利点 欠点
string とにかく明瞭,サイズを取ってくるのが速い(O(1)) 短い文字列を大量に扱う場合,アロケーションの処理の関係で遅くなることがある(らしい)*1
char * メモリ管理上の効率性が良い? ポインタの管理をプログラマが指定しないといけない(事故る),サイズ取ってくるのが遅い(下手くそな実装ではO(N))


私の経験論からのお話ですが,初心者には動的にメモリを確保して,ポインタを管理して,使い終わったらメモリを解放して,ということは大変難しい操作だと感じております.


加えて,経験者であっても,メモリの確保,開放に関するミスはうっかりやらかしてしまうこともある操作です.というか,いちいち書かないといけないのホントダメじゃでは….


というわけで,stringを積極的に使っていきましょう!(初心者にはポインタは大変かつ,正しく動かない原因!)


string のコンストラクタはいっぱいあります(知らないものばっかりだ…)一部を取り上げておきます.*2

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s1("a");
    string s2(10, 'a');
    string s3(15, 'b');
    string s4(s2+s3, 7, 5);
    string s5 = s1;
    s5 += "ccccc";
    
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
    cout << s4 << endl;
    cout << s5 << endl;
}

実行結果

a
aaaaaaaaaa
bbbbbbbbbbbbbbb
aaabb


基本演算子で,辞書順でそれが前か後ろか,ということも比較できます.自分で実装する必要が無いので,楽でいいですね!!


同じ文字列かどうかも == で判断できます!

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s2(10, 'a');
    string s3(15, 'b');
    string s4(s2+s3, 7, 5);
    
    cout << (s3 > s4 ? "S3が先" : "S4が先") << endl;
    cout << (s2 == "aaaaaaaaaa" ? ("s2は" + s2) : ("s2は" + s2 + "じゃない")) << endl;
}
S3が先
s2はaaaaaaaaaa


用意されているメソッドはたくさんあるんですが, その中でもお役立ちなものをピックアップ

  • s.empty():文字列が空かどうかを判断する(返り値boolean型)
  • s.erase(i):i文字目以降をすべて削除する
  • s.erase(i, j): i文字目からj文字分削除する
  • s.erase(str.begin()+i):i文字目のみ削除する
  • s.find(s2): 文字列sから文字列s2を探す, 見つかる場合はその位置を返す(int型), 見つからない場合はstring::nposが返る(-1)
  • s.insert(i, s2): 文字列s2を文字列sのi番目に挿入
  • s.length(): sの長さを求める(返り値int型, s.size()も同様の動作)
  • s.replace(i, length, s2): 文字列sのi番目からlengthの長さ分,s2に置き換える(replaceは引数の渡し方が多すぎるので, ぜひ調べてみてください)
  • s.substr(i): sのi番目以降の文字列を返す(string型)
  • s.substr(i, j): sのi番目から長さj分の文字列を返す(string型)
  • s.clear(): 文字列を空にする(サイズは0になるが, 容量(確保されているメモリ)は0ではない点に注意)
#include <iostream>
#include <string>

using namespace std;

int main() {
    string s = "hoge";
    string s2 = "";
    
    // empty and clear test
    cout << [](string str){return str.empty() ? "空です" : "なにか入ってます";}(s) << endl; // ラムダ式使うテスト    
    s.clear();
    cout << (s.empty() ? "空です" : "なにか入ってます") << endl;
    
    // erase test
    s = "hogehoge";
    s.erase(6);
    cout << s << endl;
    s.erase(3, 1);
    cout << s << endl;
    
    // find test
    s = "hogehoge";
    cout << s.find("oge") << endl;
    cout << s.find("huga") << endl;
    
    // insert test
    s = "hogehoge";
    s.insert(4, "CCCCCC");
    cout << s << endl;
    
    // length test
    s = "hoge";
    cout << s.size() << endl;
    cout << s.length() << endl;
    s += "mu";
    cout << s.size() << endl;
    cout << s.length() << endl;
    
    // replace test
    s = "hogehoge";
    s.replace(1, 2, "!!!!!");
    cout << s << endl;
    s2 = "??????";
    s.replace(5, 3, s2);
    cout << s2 << endl;
    
    // substr test
    s = "hogehoge";
    s2 = s.substr(5);
    cout << s2 << endl;
    s2 = s.substr(1, 1);
    cout << s2 << endl;
}


実行結果

なにか入ってます
空です
hogeho
hogho
1
18446744073709551615
hogeCCCCCChoge
4
4
6
6
h!!!!!ehoge
??????
oge
o


一番最初だけ覚えたてのラムダ式を書いてみました(笑)


あと,アクセス方法としては

  • s[i]:i文字目を取ってくる.範囲のチェック無し.
  • s.at(i):i文字目を取ってくる.範囲のチェックあり.(range-errorの例外)
  • s.front():s[0]が取れる
  • s.back():s[s.size()-1]が取れる


back()は知らなかったのですが,毎回毎回size書くよりは綺麗に書けそうですね.今後活用して行きたいです.


他にも文字列の追加,削除にこのようなものがあります.

  • s.push_back(c):文字cを追加する
  • s.pop_back():sの最後の文字を削除する.
  • s += x:sの末尾にxを追加する.よく見る形なので,是非覚えましょう. s.append(x); も同じ動作です.
  • s = s1 + s2:s1の末尾にs2を追加した文字列を作成する.

正規探索


とりあえず,やってみますか.

(まだ書いてるところ)

いろいろ間に合っていないので, 後日加筆させていただきます><


後学に向けて

  • charとwcharの違い.加えて,stringとwstringの違い
  • アロケータの実用的な使い方について
  • stringの内部的な実装(企画書とかをあたってみる?)


と,いいつつも,C++を書くことを強いられているわけではないので,暇ができた時にちょくちょくやってみようかなあ程度.モチベーションは高くない.

*1: 情報ソースがほしい,あと私はアロケータに関してメモリの確保,開放を担当するもの程度の認識しかないので,詳しくはよくわからない

*2: そもそもこの書き方を知らず string s1 = "hoge"; とかしてました