文字 | Programming Place Plus 新C++編

トップページ新C++編

このページの概要

このページでは、「文字」に着目します。これまでに文字列は登場しましたが、基本的には文字列のまま扱ってきました。文字列は文字の集まりですから、1つ1つの文字に注目して処理することもできます。

以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。



文字

文字列の中に含まれている1つの文字に目を向けてみましょう。以前「文字列の入力」のページで、文字列内の1つの文字にアクセスする方法を取り上げています。姓と名を入力してもらい、イニシャルを出力するというプログラムでした。

#include <iostream>
#include <string>

int main()
{
    std::cout << "Please enter the your fullname.\n";
    std::string family_name {};
    std::string first_name {};
    std::cin >> family_name >> first_name;

    std::cout << "Hello, " << family_name[0] << "." << first_name[0] << ".\n";
}

実行結果:

Please enter the your fullname.
Saitou Ken   <-- 入力した文字列
Hello, S.K.

文字列内の文字にアクセスするには、次のようにしました。

文字列[先頭から数えて何文字目かをあらわす整数]

「何文字目か」に関して、文字列の先頭が 0 であることに注意が必要です。また、文字列の実際の文字数を超えてしまわないように注意が必要でした(「文字列の入力」のページを参照)。

このときはまだ代入を説明していなかったので、直通で std::cout に渡していました。そこでまずは、文字列内の文字を変数に取り出す方法を見ていくことにします。

char型

文字を変数であつかうときには、変数の型を、文字を扱うための型(文字型 (character types))にします。文字型にも種類がありますが、もっとも基本的なものは char型 (char type) です。

詳しい型の分類については、APPENDIX を参照してください。

char型の変数を宣言する構文は次のとおりです。

char 変数名 {初期値};
char 変数名 {};
char 変数名 = 初期値;

char型は、1文字をあつかうための型です。std::string型とちがって、2文字以上をあつかうことはできませんし、0文字というわけにもいきません。

初期値を明示せず、{} とした場合のデフォルト値\0 という文字です。この文字の意味は後で取り上げます

char型は std::string型とちがって、#include <string> の記述は不要です。

char型の変数に文字を代入する構文は次のようになります。「代入」のページで説明した構文と同じです。

char型の変数 = 文字

実際のプログラムで確認してみます。

#include <iostream>
#include <string>

int main()
{
    std::string s {"abcde"};

    // std::string に含まれる文字を使って初期化
    char c {s[0]};
    std::cout << c << "\n";

    // ほかの char型の変数を使って初期化
    char c2 {c};
    std::cout << c2 << "\n";

    // std::string に含まれる文字を代入
    c = s[1];
    std::cout << c << "\n";

    // ほかの char型の変数から代入
    c = c2;    
    std::cout << c << "\n";
}

実行結果:

a
a
b
a

文字列内の文字にアクセスする s[0] という構文さえ理解していれば、特に難しくはないはずです。char型の値を出力するときには、これまでどおり、std::cout に渡してやればいいです。

なお、文字列はたとえ中身が1文字だったとしても、char型の変数に与えることはできません。これは、文字と文字列では型が異なるためです。たとえば、c = "a"; のような代入はできません。

あまり見かけない書き方ですが、文字列の 0番目の文字を代入するという意味の c = "a"[0]; ならできます。

文字の入力を受け取る

なお、std::cin で文字の入力を受け取る場合も、これまでどおり std::cin >> c; という感じのコードが使えます。しかし、1文字だけの入力を受け取りたい場合でも、std::string型の変数で受け取るのが無難です。1

#include <iostream>
#include <string>

int main()
{
    // これでもできるが・・・
    char c {};
    std::cin >> c;
    std::cout << c << "\n";

    // 1文字だけ受け取るつもりでも、std::string を使う
    std::string s {};
    std::cin >> s;
    std::cout << s[0] << "\n";
}

実行結果:

x  <-- 入力した文字
x
x  <-- 入力した文字
x

char型の変数で受け取るときに、xyz のような2文字以上の入力が行われると、yz が取り残され、次回の std::cin の処理に影響を与えてしまいます。std::string型の変数で受け取っておけば xyz がすべて変数に代入されますから、本当に用がある最初の文字 s[0] だけを使い、s[1]s[2] の部分を無視しておけば済みます。

std::string に文字を代入する

ここまでは、文字列から文字を取り出す方向でした。その反対に、文字を文字列(std::string)に渡すことも可能です。

#include <iostream>
#include <string>

int main()
{
    std::string s {"abcde"};
    char c {s[0]};

    // 文字を使って std::string を初期化
    std::string s2 {c};
    std::cout << s2 << "\n";

    // 文字を std::string に代入
    s[1] = c;
    std::cout << s << "\n";

    // 文字を std::string に代入
    s[2] = s[1];
    std::cout << s << "\n";
}

実行結果:

a
aacde
aaade

ここで行っている代入は、文字列の変数の一部分だけを書き換えているということです。

代入」のページで代入を学んだとき、左辺側には変数名を置くように説明しました。今回のサンプルプログラムでは、s[1] = c というように、変数名(s)だけでなく、何番目であるかをあらわす記述([1])も加わっています。このように、代入式の左辺に置けるものは、正確にいえば変数名だけではありません。

【上級】char型の変数c があるとして、std::string s {c}; のように、std::string型の変数の初期化に文字を使うことはできますが、これは std::initializer_list を引数にとるコンストラクタによるものであり、{} を使ったリスト初期化でなければなりません。std::string s (c); とか std::string s = c; はエラーです。

文字リテラル

まず文字列があって、その中に含まれている文字を扱うことはできるようになりました。しかし、文字列は登場せず、最初から1つの文字だけで登場するケースもありえます。文字だけを使うためには、文字のリテラルを記述する方法を知っておく必要があります。

文字のリテラル(文字リテラル (character literal))は、'a' のように、1つの文字を ''(シングルクォーテーション)で囲って記述します。

文字リテラルは当然ながら char型です。

char c = 'a';

【C言語プログラマー】C言語では文字リテラルの型は int型です。sizeof('a') の結果がC言語とは異なることに注意してください。

【上級】文字リテラルには、接頭辞を伴って L'x'u'x'U'x' と書くものもあるため、'x' を、通常の文字リテラル (ordinary character literal) といって区別する場合があります。

【上級】実は 'ab' のように、文字リテラルに2文字以上の文字を含めることは可能で、マルチ文字リテラル (multicharactar literal) と呼ばれます。しかしこの場合、int型として扱われるうえ、その値は実装に依存します。避けたほうがいいでしょう。2

#include <iostream>

int main()
{
    char c {'a'};
    std::cout << c << "\n";

    c = 'A';
    std::cout << c << "\n";
}

実行結果:

a
A

文字コード

コンピュータは、文字のデータを整数にしてあつかっています。これは、文字コード (character code) と呼ばれる仕組みです。

文字コードは、文字の種類ごとに異なる整数を割り当てたものです。たとえば、「a」が 10、「b」が 11、「c」が 12、「d」が 13 ・・・といったように割り当てておけば、文字のデータを整数で表現できます。このルールでは、「11、10、13」という整数の並びは「bad」を意味しています。

文字コードにはさまざまな種類があって、国際規格などで明確にルールが決められています(さきほど挙げた対応関係は架空のものです)。文字と整数の対応関係は文字コードの種類によって異なっています。また、定義されている文字の種類(つまり、どんな文字が使えるのか)にも違いがあります。プログラミングでよく登場する ASCII(American Standard Code for Information Interchange。読み方はアスキー。ASCIIコードとも)では、アルファベットや数字は含まれていますが、ひらがなやカタカナ、漢字といった日本語で使う文字がまったく含まれていません。

C++ のソースファイルで使う文字コードの種類は定められておらず、環境(コンパイラ)によって異なります。そのため、ソースファイル上で使える文字の種類も環境によって異なります。それでは困りそうですが、以下に挙げる 96種類の文字は基本ソース文字セット (basic source character set) と呼ばれ、必ず使用できます。5

a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '

スペース
水平タブ
垂直タブ
フォームフィード
改行

基本ソース文字セットは、ASCII に含まれている文字だけで構成されていますが、ASCII そのものではありません。たとえば、ASCII にはある @ が、基本ソース文字セットにはありません。

このように、基本ソース文字セットには日本語の文字が含まれていません。だからといって C++ では日本語が使えないということではなく、コンパイラが使用を許していれば使えます。日本向けに作られているコンパイラならばまず間違いなく使えるでしょうし、そうでないなら使えない可能性も高いということになります。

ただし、日本語の文字をソースファイル上で使えるからといって、日本語を使ったプログラムが簡単に作れるということでもありません。たとえば、ソースコードには書ける文字が、出力結果には正しく表示されない現象に悩まされることがあります。日本語を正しくあつかうプログラムを作るにはそれなりの難しさがあるので、当面は日本語を扱わないことにします。

コメントとして日本語を使うことは、結局、ソースファイルに日本語の文字を書いていることになりますから、やはりコンパイラが許すかどうかによります。しかし、コメントとして使うだけであれば、あまりトラブルになることはないので、今後もコメントには日本語の文字も使うことにします(滅多に見かけないような漢字や記号を使うと、コンパイルエラーが出るなどの問題が起こるかもしれません)。


文字の正体は整数なので、文字を int型の変数に渡すことは、プログラマーが本当に意図したことかどうかは別にして、問題のない行為です。

#include <iostream>

int main()
{
    int value = 'a';   // OK. だが変数の型が int なのは意図したこと?
    std::cout << value << "\n";

    value = 'x';       // OK. だが代入先の変数の型は int だが意図したこと?
    std::cout << value << "\n";
}

実行結果:

97
120

文字で初期化したり代入したりしていますが、相手方の変数は int型になっています。int型なので、出力すると文字ではなく整数が出力されます。これが意図したことならば問題ありませんが、多くの場合はそうではないでしょう。

ここでは 97、120 という整数が出力されましたが、文字と整数の対応関係は、使われている文字コード次第です(とはいえ、ASCII で表現できる範囲の文字ならば、まず間違いなく同じ結果が得られるはずです)。

代入」のページで書いたとおり、型が異なっていても代入が許されるケースもありますが、動作を理解していない限りは原則として避けたほうが無難です。代入(や初期化)は型を気にしながら行うようにしてください。

また、「代入」のページで、std::string型の変数に整数を代入するコードがコンパイルエラーにならないことを確認しましたが、それも同じ理由です。

#include <iostream>
#include <string>

int main()
{
    std::string message {};

    // std::string型の変数に整数が代入できる
    message = 100;

    std::cout << message << "\n";
}

実行結果:

d

std::string型の変数に整数を代入できるのは、文字コードの表現を使って、文字を代入していることになるからです。

出力結果は d になりましたが、これはこの環境で使っている文字コードにおいての 100 が ‘d’ に対応しているからです。

このように、文字の正体は整数ですが、文字として扱いたいのならば int型を使わず、char型および文字リテラルを使って表現してください。

エスケープシーケンス

ここで、特別な意味をもった文字について触れておきます。

\n のように、「\ +1文字」で表される特別な文字の並びではじまる記法を、エスケープシーケンス (escape sequence) といいます。エスケープシーケンスは、そもそも文字として書きようがない機能を表現したり、C++ の文法の都合上、リテラルの中に記述できない文字を記述したりするためにあります。

エスケープシーケンスには、次のものがあります。3

エスケープシーケンス 意味
\n 改行
\t タブ(水平タブ)
\v 垂直タブ
\b 位置を1文字分戻す
\r 復帰(位置を現在の行の頭にする)
\f フォームフィード(改ページ)
\a アラート(ベル)
\\ バックスラッシュ(日本語環境では、円マークで表示されることが多い)
\? 疑問符(?)
\’ シングルクォーテーション(’)
\" ダブルクォーテーション(")
\o (o は 8進数の数字) 8進コードによる文字の表現
\xh (h は 16進数の数字) 16進コードによる文字の表現

char型の変数を宣言するとき、初期値を明示せずに {} としたときの値は \0 ですが、これは上の表でいえば \o を使った表現です。要するに、文字コードによる表現で 0 に対応する文字を意味しています。

エスケープシーケンスは、(そうはみえませんが)1文字の扱いです。たとえば、\n はこれで1文字なので、文字リテラルにできますし、char型の変数に入れられます。

char c = '\n';  // OK

現時点で特に知っておくべきなのは、\n\t\\\?\'\" といったところでしょう。\r 辺りは、そのうち使う機会が出てきますが、ほかはあまり使う機会がないかもしれません。

\\\?\'\" はそれぞれ、\?'" といった文字を表すものです。なぜこんなものが必要かというと、\?'" にはそれぞれ特別な用途が割り当てられているからです。\ はエスケープシーケンスを表現するために使う必要がありますし、'" は文字リテラルや文字列リテラルの開始と終わりをあらわすために使われています。そのため普通の文字として \?'" を使いたいときには、エスケープシーケンスを使う必要があります。ただし、' は文字列リテラルの中ではそのまま使え("'")、" は文字リテラルの中ではそのまま使えます(‘"’)。

? についてはそのまま使っても問題ない場合が多いですが、\? とした方が安全です。

【上級】? は、使える文字の種類が少ない環境でソースコードを書けるようにするために、3つの記号を並べて代替するトライグラフ (trigraph) という表記方法で使われています。

【C++17】トライグラフの機能は実際にはほとんど使われておらず、C++17 で仕様ごと削除されています。4

次のプログラムでは、文字列リテラルの中で " を使おうとしており、コンパイルエラーになります。

#include <iostream>

int main()
{
    std::cout << "「"」\n";
}

このプログラムの場合、"「" を文字列リテラルとして認識してしまい、」\n" を余分な文字の並びとしてあつかってしまっています。"「"」\n" 全体を1つの文字列リテラルとして認識させるためには、エスケープシーケンスを使って、次のように書きます。

#include <iostream>

int main()
{
    std::cout << "「\"\n";
}

実行結果:

「"」

まとめ


新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。


参考リンク


練習問題

問題の難易度について。

★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。

問題1 (確認★)

int型、char型、std::string型の用途を説明してください。

解答・解説

問題2 (確認★)

'a'"a" の違いを説明してください。

解答・解説

問題3 (確認★)

次のプログラムの実行結果はどうなりますか?

#include <iostream>
#include <string>

int main()
{
    std::string s1 {"\\n"};
    std::string s2 {"\"\""};
    std::string s3 {"\'"};

    std::cout << s1 << "\n";
    std::cout << s2 << "\n";
    std::cout << s3 << "\n";
}

解答・解説

問題4 (確認★)

次のプログラムで、変数 c に入る文字は何ですか?

#include <iostream>
#include <string>

int main()
{
    std::string s {"\nHello\n"};
    char c {s[0]};
}

解答・解説

問題5 (基本★★)

文字の入力を受け取り、その文字を整数で表したときの値を出力するプログラムを作成してください。

解答・解説

問題6 (調査★★★)

ASCII の内容について調べてください。

解答・解説


解答・解説ページの先頭



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る