TechFULの中の人

triple-four’s blog

C++の文字列と仲よくなろう

初めまして!
学祭に向けて奮闘中のサバといいます。
TechFULでganariya氏、Siiiec氏と同時にアルバイトを始めた魚です。

自分のただでさえ少ない知識はだいぶC++に偏っているので、今回はそちらについて書いていきたいと思います。


初めに

TechFULの難しい問題を解くときは、解法に悩み、目一杯頭を使うはずです。 だからこそ、コーディングにはなるべく頭を使わず、極限まで楽するべきだと自分は考えます!
今回はプロコン関係でよく使えるであろう方法を紹介していきます。
数が多くなってしまったので、一気に覚えるのではなく、 何かに引っかかったらこのページに戻って確認、といった形が一番いいと思います!

std::stringが初耳な方へ

C++には string という文字列専用の型が用意してあります。
string ヘッダ内の std 名前空間内にあります。 (名前空間は住所みたいなものです。)
「え?char配列でええやん」って思った方はとりあえず使ってみましょう。きっと手放せなくなります。

# include <string>   // std::string を使うため
# include <iostream> // std::cout   を使うため

int main()
{
    std::string s;   // 文字列型変数sを宣言、自動的に空文字列で初期化!
    s = "文字列!!";  // s に"文字列!!"を代入、バッファオーバーフローを気にする必要がない!
    std::cout << s;  // 出力、coutを使えば面倒な書式設定は不要!

    return 0;
}

出力

文字列!!

基本の用法

初期化

組み込み型と同じく=で代入初期化ができます。
もちろん(){}でも!

std::string s = "qwerty";   // qwerty
std::string s2{ "qwerty" }; // qwerty

コピー・代入

= 演算子を使います。組み込み型と同じ感覚です!

std::string origin = "I'm original string.";
std::string copyed;
copyed = origin; // コピー
origin = "I'm changed.";
std::cout << copyed; // I'm original string.

長さ

size() または length() メンバ関数を使います。どちらも同じです。
strlen()と異なり、オーダーは O(1) と高速です!

std::string s = "123456879";
std::cout << s.size(); // 9

比較

文字列が等しいか否かは ==!= によって判定できます。

std::string str = "str";
std::string str2 = "str";
std::string strr = "strr";

std::cout << (str == "str") << std::endl; // 1 (true の意)
std::cout << (str == str2 ) << std::endl; // 1 (true の意)
std::cout << (str == strr ) << std::endl; // 0 (false の意)
std::cout << (str != strr ) << std::endl; // 1 (true の意)

結合

+, +=で足し算をするように文字列を結合できます。
もちろんバッファオーバーフローを気にする必要もありません!

std::string s = "abc";
std::string t = "def";
auto u = s + t; // abcdef
s += u + "ghi";
std::cout << s << std::endl; // abcabcdefghi
std::cout << t << std::endl; // def
std::cout << u << std::endl; // abcdef

添え字アクセス

配列と同様、[]で文字にアクセスできます。
もちろんO(1)!

std::string s = "abc";
std::cout << s[0];  // a

入出力

入力:スペースまたは改行文字区切り

std::cin を使いましょう! (iostream ヘッダ)
>>の向きは、変数に値を突っ込む向きです!

標準入力

qwerty asdf
zxcv

コード

std::string s, t, u;
std::cin >> s >> t >> u;  // 入力をスペース・改行区切りで取得
std::cout << s << std::endl; // qwerty
std::cout << t << std::endl; // asdf
std::cout << u << std::endl; // zxcv

入力:一行まるごと

std::getline を使いましょう!

標準入力

qwerty asdf
zxcv

コード

std::string s, t;
std::getline(std::cin, s);
std::getline(std::cin, t);
std::cout << s << std::endl; // qwerty asdf
std::cout << t << std::endl; // zxcv

出力

std::cout を使いましょう!(iostream ヘッダ)
<<の向きは変数から値を取り出す向きです!

std::cout << std:string{"print to standard out"}; // print to standard out

出力:Cの関数で

c_str()または、data()メンバ関数を使いましょう!
どちらも同じ動作です。const char * 型で内部文字列を返してくれます!
cin, cout はどうしても動作が遅いので、よりプロコン向きな方法といえます。

std::string s{"like a char array string"};
printf("%s", s.c_str()); // like a char array string

変換

std::string に変換

std::to_string() 関数を使いましょう。 ただし char 型には要注意 です。

int    i{};
double d{ 3.14 };
char   c{ 'c' };
unsigned long long ull{ 100000000000000 };

std::string s;
s = std::to_string(i); // "0"
s = std::to_string(d); // "3.14"
s = std::to_string(c); // 99 int型として文字列に変換されてしまう…
s = c; // "c" 代入なら文字として変換される!
s = std::to_string(ull); // "100000000000000"

std::string に変換(2)

std::ostringstream を使う方法もあります。 こちらは cout のような動作をします!

# include <sstream> // for ostringstream

int    i{};
double d{ 3.14 };
char   c{ 'c' };
unsigned long long ull{ 100000000000000 };

std::ostringstream oss{};
oss << i << d << c << ull;

std::string s = oss.str(); // 03.14c100000000000000

std::string から変換

std::sto●●関数を使う方法があります。 ただし char 型には要注意 です。

int    i = stoi(std::string{ "0"    }); // 0
double d = stod(std::string{ "3.14" }); // 3.14
char   c = std::string{ "c" }.front();  // stoc はありません!
unsigned long long ull = stoull( "100000000000000" ); // 100000000000000    (std::stringを暗黙的に生成してそれを変換)

std::string から変換(2)

out があれば in もあり、std::istringstream でも変換できます。
cin と同じような動作ですね!
入力から一行を取得し、内部で処理する際に役に立ちます!

int    i;
double d;
char   c;
unsigned long long ull;

std::string str{"0 3.14 c 100000000000000"};
std::istringstream iss{str};
iss >> i >> d >> c >> ull;

std::cout << i << std::endl; // 0
std::cout << d << std::endl; // 3.14
std::cout << c << std::endl; // c
std::cout << ull;            // 100000000000000

積極的に使おう!

空文字列判定

empty() メンバ関数を使います。
s == "" よりも一般的に高速、しかも書きやすいし明確!

std::string s{};
if(s.empty()) 
    std::cout << "sは空です!" // sは空です!

範囲for文

添え字の初期化・条件文・インクリメントの記述が面倒に感じたら使いましょう!
全要素に対して操作しているのが明確です!

std::string s {"abc"};
for(auto c : s)
    std::cout << c  << ",";  // a,b,c,

部分文字列

substr()メンバ関数は一部をコピーし、取り出してくれます。

std::string str{ "I'm std::string." };
std::cout << str.substr(4, 3); // 添え字4の位置から3つを取り出す -> std

検索

find()メンバ関数は一致する文字列を検索し、その先頭の添え字を返してくれます。
存在しない場合は std::string::npos (static メンバ変数)と等しい値を返します。
第二引数にはオフセットも指定できます。find系はたくさんあるので調べてみてください!

std::string str{ "I'm std::string." };
size_t pos   = str.find (std::string{"st"});  // 先頭から検索
auto   rpos  = str.rfind("st");  // 末尾から検索
auto   nopos = str.find ("not contained"); // 存在しない文字列を検索

if (pos   != std::string::npos) std::cout << pos;        // 4
if (rpos  != str.npos         ) std::cout << rpos;       // 9
if (nopos == str.npos         ) std::cout << "not found";// not found

ちょっとしたテクニック

文字列の反転

反転した文字列が欲しいならコンストラクに作ってもらいましょう!
std::reverseを使う方法もあります!

std::string origin{ "original" };
std::string reversed{ origin.rbegin(), origin.rend() }; // 反転された文字列を新規作成
std::cout << origin;    // original
std::cout << reversed;  // lanigiro

std::reverse(origin.begin(), origin.end()); // 元の文字列自体を反転
std::cout << origin;    // lanigiro

同じ文字を連続させた文字列

こちらもコンストラクにお任せ!
ただし必ず()初期化で!

size_t count = 5;
std::cout << std::string(count, '*'); // *****

文字列の分割

区切り文字が char 一文字なら std::getline が使えます。
残念ながら区切り文字が文字列の場合は自分で実装する必要があります。

std::string str{"aa,bb,,gg,hh"};
std::istringstream iss{str};  // <sstream> ヘッダ
std::string buff{};
char separator = ',';
while (std::getline(iss, buff, separator)) // std::getlineは失敗(終端に到達)するとfalseを返す
{
    if(!buff.empty()) // 空文字も含まれる
        std::cout << buff << std::endl;
}

生文字列リテラル

エスケープシーケンスでぐちゃぐちゃになった文字列に効果的!!
そのまんまの文字列として扱えます。
***の部分は、都合のいい文字列(""、"****"、"qwerty"等々)を使ってください!

string rawString = R"***(C:\Directory\FilePath.txt "" " "
also line separator ! 
)***";
std::cout <<  rawString;
出力
C:\Directory\FilePath.txt "" " "
also line separator ! 

最後に

自分が思い出せたものを煩雑な解説とともにベタベタと貼りましたので、見辛いところはご容赦ください。
「こんなやり方もあったんだ、今度思い出したら使ってみよう!」と思っていただけるととても嬉しいです!
ではまた!!