M - 1.12.文字列と文字 Editorial /

Time Limit: 0 msec / Memory Limit: 0 KB

前のページ | 次のページ

キーポイント

  • 文字列を扱うにはstring型を使う
  • 文字を扱うにはchar型を使う
  • 文字列変数.size()で文字列の長さを取得できる
  • 文字列変数.at(i)i文字目にアクセスできる
  • 文字列変数.at(i)i添え字という
  • 添字は0から始まる
  • 添字の値が正しい範囲内に無いと実行時エラーになる

文字列(string型)

abchelloのように、文字が順番に並んでいるもののことを文字列といいます。

1.04.変数と型で説明したとおり、C++で文字列を扱うにはstring型を使います。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str1, str2;
  cin >> str1;
  str2 = ", world!";

  cout << str1 << str2 << endl;
}
入力
Hello
実行結果
Hello, world!

文字列の長さ

文字列の長さ(文字数)は文字列変数.size()で取得できます。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "Hello";
  cout << str.size() << endl;
}
実行結果
5

この書き方は「メンバ関数」という機能を使ったものですが、詳しくは後の章で扱います。ここではとりあえず文字列変数.size()とすると文字列の長さが取得できることを覚えておきましょう。

i番目の文字

次のように書くとi文字目が取得できます。

文字列.at(i)

このiのことを添字と言います。

添字は0から始まる

添字は0から始まることに注意しましょう。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "hello";
  cout << str.at(0) << endl; // h
  cout << str.at(4) << endl; // o
}
実行結果
h
o

str.at(0)で1文字目、str.at(4)で5文字目を取得しています。

"hello"という文字列の場合、添字の値と文字の対応は次の表のとおりです。

添字 0 1 2 3 4
文字 h e l l o

ループのカウンタ変数を0から始めるのと同じように、添字が0から始まることにも慣れていきましょう。


文字(char型)

string型は「文字列型」ですが、「文字型」というのもあります。それはchar型です。
string型と違い、char型は一文字のデータしか保持することができません。

string型を表すために" "で囲ったように、char型を表すためには' 'で囲います。

#include <bits/stdc++.h>
using namespace std;

int main() {
  char c = 'a';
  cout << c << endl; // a
}
実行結果
a

文字列変数.at(i)の型

文字列変数.at(i)で取得されるデータはchar型です。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "Hello";

  char c = str.at(0); // char型の値が得られる

  cout << c << endl; // H
}
実行結果
H

文字列の書き換えと比較

文字列の一部を書き換えるときや比較をするときはchar型を使う必要があります。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "Hello";

  str.at(0) = 'M'; // char型の'M'
  cout << str << endl; // Mello

  if (str.at(1) == 'e') {
    cout << "AtCoder" << endl;
  }
}
実行結果
Mello
AtCoder

応用

ループ構文との組み合わせ

文字列はループ構文を組み合わせることで様々な処理が記述できるようになります。
次のプログラムでは、入力された文字に何文字'O'が含まれているかを数えています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str;
  cin >> str;

  int count = 0;
  for (int i = 0; i < str.size(); i++) {
    if (str.at(i) == 'O') {
      count++;
    }
  }

  cout << count << endl;
}
入力
LOOOOL
実行結果
4

実行環境によっては次のような警告文が表示されることがありますが、動作には影響がないのでこれに関してはひとまず気にしなくて良いです。
warning: comparison of integer expressions of different signedness: 'int' and 'std::__cxx11::basic_string<char>::size_type' {aka 'long unsigned int'} [-Wsign-compare]
この警告が出る理由に関しては3.01.数値型の「符号なし整数のオーバーフロー」で説明します。


注意点

範囲外エラー

添字の値が正しい範囲内に無いと実行時エラーになります。
次のプログラムは"hello"という5文字の文字列(有効な添字の値は04)に対し、at(10)で存在しない文字数目にアクセスしようとしているため、実行時エラーが発生します。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string x = "hello";
  cout << x.at(10) << endl;
}
実行時エラー
terminate called after throwing an instance of 'std::out_of_range'
  what():  basic_string::at: __n (which is 10) >= this->size() (which is 5)
終了コード
134

添字が範囲外のときのエラーでは、1行目の最後で'std::out_of_range'(範囲外)というエラーメッセージが表示されることが特徴です。
2行目の__n (which is 10) >= this->size() (which is 5)では、「添字の値(10) 文字列のサイズ(5)」であるためエラーが発生したということを伝えています。

全角文字の扱い

string型やchar型は全角文字を正しく扱えません。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string s = "こんにちは";
  cout << s.at(0) << endl;

  char c = 'あ';
  cout << c << endl;
}
実行結果
�
�

どちらの出力も文字化けしてしまっています。

C++で全角文字を扱う場合、string型ではない別の文字列型(u32string等)が用いられます。
しかし、それらの扱いはstring型より面倒な部分があり、この教材の目的はC++を使いこなすことではないので詳しくは説明しません。

文字列のメンバ関数や演算子を利用するとき

.size()や後述する==演算子等を利用する場合、一度変数に格納するか、"文字列"s.size()のように" "の末尾にsをつける必要があります。
単に"文字列".size()と書いた場合はコンパイルエラーになります。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "Hello";
  cout << str.size() << endl; // 5
  cout << "Hello"s.size() << endl; // 5(sを末尾につける)
  cout << "Hello".size() << endl; // できない
}
コンパイルエラー
./Main.cpp: In function ‘int main()’:
./Main.cpp:8:19: error: request for member ‘size’ in ‘"Hello"’, which is of non-class type ‘const char [6]’
   cout << "Hello".size() << endl; // できない
                   ^

細かい話

細かい話なので、飛ばして問題を解いても良いです。

文字列の比較

string型にも数値型のような比較演算子が定義されています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string s1 = "ABC";
  string s2 = "ABC";
  string s3 = "XY";

  if (s1 == s2) {
    cout << "ABC == ABC" << endl;
  }
  if (s1 < s3) {
    cout << "ABC < XY" << endl;
  }
}
実行結果
ABC == ABC
ABC < XY

string型に定義されている比較演算子は次の表の通りです。

演算子 意味
== 2つの文字列が完全に一致している
!= 2つの文字列に違いがある
<, <=, >, >= 辞書順による比較

「辞書順」というのは簡単に言うと「辞書に載っている順番」のことです。
上の例ではs1 = "ABC"s2 = "XY"よりも先に載っているので、s1 < s3 == trueとなります。

ただし、C++の順序では'0''9''A''Z''a''z'の順になっていることに注意しましょう。
つまり、"012"s < "ABC"s == trueであり"ABC"s < "xyz"s == trueです。

「辞書順」の定義について詳しく知りたい人はABC007 B - 辞書式順序の問題文を参照してください。

文字列の連結

+演算子を使うと文字列同士を連結できます。+=を使うこともできます。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string hello = "Hello";

  // +演算子による連結
  cout << hello + ", world!" << endl; // Hello, world!

  // +=演算子による連結
  hello += ", AtCoder!";
  cout << hello << endl; // Hello, AtCoder!
}
実行結果
Hello, world!
Hello, AtCoder!

ただし、数値型のように-,*,/,%はできないので注意しましょう。

stringとcharの比較

string型とchar型は==で比較できません。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "a";
  char c = 'a';
  bool b = str == c; // できない
}
コンパイルエラー
./Main.cpp: In function ‘int main()’:
./Main.cpp:7:16: error: no match for ‘operator==’ (operand types are ‘std::string {aka std::basic_string<char>}’ and ‘char’)
   bool b = str == c; // できない
                ^
この後延々とエラー文が続く...

文字列と文字の連結

string型とchar型を+すると、文字列同士と同様に連結できます。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string str = "Hello";

  char c = str.at(0);
  cout << str + c << endl; // HelloH
}
実行結果
HelloH

char型の変数への入力

char型の変数にcinで入力すると一文字ずつ取り出すことができます。

#include <bits/stdc++.h>
using namespace std;

int main() {
  char a, b;
  cin >> a >> b;

  cout << a << endl;
  cout << b << endl;
}
入力
OK
実行結果
O
K

エスケープシーケンス

「改行」などの特殊な文字をプログラム中で表現する場合、エスケープシーケンスを利用します。
次のプログラムでは改行を表すエスケープシーケンスである\nを使い、こんにちはAtCoderの間で改行して出力をしています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  cout << "こんにちは\nAtCoder";
}
実行結果
こんにちは
AtCoder

基本的には\nだけ覚えておけば良いですが、他にも代表的なエスケープシーケンスとして以下のものがあります。

エスケープシーケンス 説明
\" 二重引用符 "
\' 引用符 '
\\ バックスラッシュ(または半角円記号) \
\t タブ(水平タブ)
\r 復帰(一部の実行環境では改行に用いる)

行単位での入力

cinを使うと空白や改行区切りの入力を簡単に扱えますが、空白で区切らずに行単位で入力を受け取りたいこともあります。
その場合はgetlineを使います。

次のプログラムは2行の入力を行単位で受け取っています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  string s, t;
  getline(cin, s); // 変数sで入力を一行受け取る
  getline(cin, t); // 変数tで入力を一行受け取る

  cout << "一行目 " << s << endl;
  cout << "二行目 " << t << endl;
}

入力

I have a pen.
I have an apple.

実行結果

一行目 I have a pen.
二行目 I have an apple.

getline関数は次の形式で利用します。

getline(cin, 文字列変数);

この書き方は「関数」という機能を使ったものです。詳しくは1.14.STLの関数で扱います。


問題

リンク先の問題を解いてください。

ABCの問題

ここまで学んだ内容だけで、おそらく全てのAtCoder Beginner ContestのA問題を解くことができます。
その中から文字列を扱う問題を一部紹介します。

文字列の知識があれば解けるB問題も紹介します。こちらは難易度が高めなので、 解けない場合は2.01.ループの書き方2.02.多重ループを読んでから再挑戦してみてください。なお、この問題は1.13.配列の知識があると少し解きやすくなります。

前のページ | 次のページ