C言語では char
型(文字型)(char
は character の略)というデータ型がある.これは1バイトのメモリサイズをもつデータ型である.コンピュータのメモリアクセスは,1バイトが最小の単位であるから,char
型は最小のメモリサイズを持つ型であると言える.
では,char
型を用いた簡単なプログラムを見てみよう.
#include <stdio.h> int main() { char x; /* char型変数 x を宣言 */ x = 'a'; /* x に文字'a'を代入 */ printf("%c\n", x); /* %c は文字を1文字表示する書式指定 */ return 0; }
a
このプログラムの x = 'a'
という代入文の右辺にある 'a'
は,
「aという文字」を表す char
型定数である.
このように,char
型定数は1文字をシングルクォーテーション「'」で括ったもので表される.
ここでの1文字とはアルファベットや数字や記号などの1バイト文字,いわゆる半角文字と言われる文字である.日本語(漢字やひらがなやカタカナ)を表す2バイト文字,いわゆる全角文字は「'あ'」のように「'」で括っても char
型定数とはならない.
上のプログラム中の printf("%c\n", x)
にある"%c\n" は,あとで説明する文字列というものであるが,その中の「\n
」は,
この2文字で「改行」を意味する1文字を表している.
このような「\
」で始まる2文字で1文字を表したものを,エスケープシーケンスあるいは特殊文字と呼ぶ.改行文字を x に代入するには次のようにする.
x = '\n';
この「\
」は,円記号「¥
」かバックシュラッシュ「\
」かのどちらかで表示される.
(端末の環境や使用するエディタ,そしてこの web ページを見ているときも
そのブラウザに依存して,どちらかの文字が表示される.
もしかすると,ブラウザによると,この web ページ内でも
混合して表示されているかもしれない.)
よく使われるエスケープシーケンスには次のものがある.
記号 | 意味 |
---|---|
\' | ' |
\" | " |
\\ | \ |
\t | タブ |
\n | 改行 |
\0 | ヌル文字 |
「'
」という文字は文字定数を表すために使用されているので,「'
」自身を意味する文字は「\'
」と表すことになっている.したがって,「'
」を表す文字定数はこれを「'
」で括った「'\''
」である.
「"
」という文字は文字列を表すために使用されているので,「"
」自身を意味する文字は「\"
」と表すことになっている.
「\
」という文字は特殊文字を表すために使用されているので,「\
」自身を意味する文字は「\\
」と表すことになっている.
表の最後にあるヌル文字「\0
」は,次節で説明する文字列の終端を表す文字として使用される.
char
型は文字を表す型ではあるが,その本質は1バイトで表された整数(-128〜127)を表す型である.したがって, 'a'
などの文字定数も整数としての意味を持つ.それを次のプログラムを実行して確認しよう.(%d
は整数値を10進数で表示することを指定する)
#include <stdio.h> int main() { printf("%d %d %d %d %d %d %d %d\n", 'a', 'b', 'c', '1', '2', '3', '\n', '\0'); return 0; }
97 98 99 49 50 51 10 0
このプログラムは,a, b, c, 1, 2, 3, 改行文字,ヌル文字
という文字がそれぞれ持つ整数値を表示する.その結果を見て,a, b, c
の数値,1, 2, 3
の数値が一つずつ増えていることと,ヌル文字(\0
)の数値が0
であることに注意しておこう.
各文字にどのような数値が割り振られているかは,システムによって異なる場合があるが, 現在のコンピュータではほとんどの場合,次のようなアスキーコード (ASCII Code) と呼ばれる数値が割り振られている.
00 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | |
---|---|---|---|---|---|---|---|---|
00 | \0 ヌル文字 | 空白 | 0 | @ | P | ` | p | |
01 | ! | 1 | A | Q | a | q | ||
02 | " | 2 | B | R | b | r | ||
03 | # | 3 | C | S | c | s | ||
04 | $ | 4 | D | T | d | t | ||
05 | % | 5 | E | U | e | u | ||
06 | & | 6 | F | V | f | v | ||
07 | \a アラームベル | ' | 7 | G | W | g | w | |
08 | \b バックスペース | ( | 8 | H | X | h | x | |
09 | \t 水平タブ | ) | 9 | I | Y | i | y | |
0A | \n 改行 | * | : | J | Z | j | z | |
0B | \v 垂直タブ | + | ; | K | [ | k | { | |
0C | \f 改ページ | , | < | L | \ | l | | | |
0D | \r 行先頭へ復帰 | - | = | M | ] | m | } | |
0E | . | > | N | ^ | n | ~ | ||
0F | / | ? | O | _ | o |
各文字に割り振られた数値を16進数で表したときの 1桁目と2桁目で分類して表にしている.
左にある数値が1桁目で,上段にある数値は2桁目である.
たとえば,K という文字は 0B の行の 40 の列にあるので,0B+40=4B すなわち10進数でいうと 16*4+11=75 という数値が割り振られている.
次のプログラムは 'a' から 'z' までの文字と,そのアスキーコードを十進数と十六進数とで表示する.
#include <stdio.h> int main() { char c; for (c = 'a'; c <= 'z'; c++) { printf("%8c%4d%4x", c, c, c); /* %4c は文字を4桁で表示(左側に空白が7個入る) %4d は整数値を十進数4桁で表示 %4x は整数値を十六進数4桁で表示 */ } printf("\n"); return 0; }
a 97 61 b 98 62 c 99 63 d 100 64 e 101 65 f 102 66 g 103 67 h 104 68 i 105 69 j 106 6a k 107 6b l 108 6c m 109 6d n 110 6e o 111 6f p 112 70 q 113 71 r 114 72 s 115 73 t 116 74 u 117 75 v 118 76 w 119 77 x 120 78 y 121 79 z 122 7a
C言語には文字列型という型はない.文字列は,char
型の配列で表される.ただし,単なる配列ではなく,「文字列の最後には終端を表す文字 '\0'
(ヌル文字)がついている」という約束の下に取り扱われる文字配列である.文字列の最後に付けられたヌル文字を終端文字と呼ぶ.
たとえば,次のように宣言された文字配列 s
があったとする.
char s[10];
その中身が次のようになっていたとする.
s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | s[9] |
---|---|---|---|---|---|---|---|---|---|
'K' | 'y' | 'o' | 's' | 'a' | 'n' | 'd' | 'a' | 'i' | '\0' |
このとき,この文字配列 s が表す文字列は "Kyosandai" である.
また,次のようになっているとき,
s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | s[9] |
---|---|---|---|---|---|---|---|---|---|
'K' | 'y' | 'o' | 's' | 'a' | 'n' | '\0' | 'a' | 'b' | 'c' |
この場合,s
は "Kyosan"
という文字列を表している.終端文字 '\0'
の後ろの要素の値は,s
が表す文字列には関係ない.
"Kyosan"
の文字数は6文字であるからといって,
char s[6];
と宣言された配列に,次のように文字を入れただけでは,終端文字がないので "Kyosan"
という文字列にはならない.
s[0] | s[1] | s[2] | s[3] | s[4] | s[5] |
---|---|---|---|---|---|
'K' | 'y' | 'o' | 's' | 'a' | 'n' |
したがって,"Kyosan"
という長さが6の文字列を表すためには,最低でも長さ7の文字型配列が必要となる.
長さ n の文字列を表すには,長さ n+1 の文字配列が必要 ということを,強調しておく.
プログラムソースコードの中に,ダブルクォーテーション「"
」で括られた文字列がある場合,
これを文字列リテラル(文字列定数)という(リテラルとは,書いてある文字そのものという意味).
たとえば,printf("Kyosan\n");
と書いてあれば,
この "Kyosan\n"
が文字列リテラルである.
文字列リテラル内の文字は,「\
」で始まる特殊文字を除いて,書いてある文字そのままの文字列を表す.
プログラム実行時には,文字列リテラルは,メモリ上のどこかの領域に確保された文字配列に収納されている,終端文字 '\0'
のついた文字列となる.さきほどの "Kyosan\n"
の例の場合,それは次のような内容の文字配列である.
'K' | 'y' | 'o' | 's' | 'a' | 'n' | '\n' | '\0' |
文字列リテラルの実体は文字配列ではあるが,その要素の値を読み出すことはできても,要素に値を代入することはできない(できるかも知れないが,しない方がよい).
文字配列を宣言すると同時に 次のように初期値を並べて与えて,初期化することができる.
char s[7] = {'K', 'y', 'o', 's', 'a', 'n', '\0'};
これと同様のことを,文字列リテラルを用いてより簡単に次のように書くこともできる.
char s[7] = "Kyosan";
この初期化により,文字配列 s の要素が次のように初期化される.
s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] |
---|---|---|---|---|---|---|
'K' | 'y' | 'o' | 's' | 'a' | 'n' | '\0' |
注意すべき点は,"Kyosan"
という文字列は終端文字を含めて7個の文字で表されるので,次のようにしては配列の要素数が足りないということである.
char s[6] = "Kyosan";
初期値として与える文字列の長さを数えることが面倒なときには,配列の要素数を指定せず,次のようにすればよい.
char s[] = "Kyosan";
コンパイラが "Kyosan"
の文字数を数えて,
それにふさわしい要素数の配列を宣言し初期化してくれる.すなわち,
上のものは次のものと同等である.
char s[7] = "Kyosan";
配列のサイズよりも定義文字列の長さが少なくてもよいが, その場合は,残りの要素は初期化されない.
char s[10] = "Kyosan";
s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | s[9] |
---|---|---|---|---|---|---|---|---|---|
'K' | 'y' | 'o' | 's' | 'a' | 'n' | '\0' |
文字列を出力(画面に表示)するには,標準ライブラリ関数 printf
を用いる.
第1引数の書式指定文字列に文字列を指定する %s
を書き,第2引数以降に表示したい文字列を与えればよい.
#include <stdio.h> int main() { char str1[] = "Kyoto"; char str2[] = "Sangyo"; char str3[] = "University"; printf("%s %s %s\n", str1, str2, str3); return 0; }
Kyoto Sangyo University
文字列を扱ういくつかのサンプルプログラムを作ってみよう.
次は,文字列 str
にある文字数(終端文字は含まない)を数えるプログラムである.
#include <stdio.h> int main() { char str[12] = "Kyosandai"; int n; n = 0; while (str[n] != '\0') { /* str[n] が終端文字(ヌル文字 '\0')でない間繰り返す */ n++; } printf("Length of \"%s\" is %d.\n", str, n); /* \" は文字「"」を表す */ return 0; }
Length of "Kyosandai" is 9.
プログラムは,まず始めに n
の値を 0
に初期化して,次の while
文で,str[n]
の値が終端文字(ヌル文字 '\0'
)でない間, n
を 1
ずつ増やし続ける.したがって,この while
文は文字列 str
の先頭から終端文字までに何文字あるかを変数 n
に数えることになる.そして,while
文が終了したとき,n
の値が文字列の文字数となっている.
また,この while
文の働きを次のように解釈することもできる.
「この while
文は str[n] != '\0'
であるとき回り続ける.言い換えると,while
文が終了したとき str[n] == '\0'
となっている.すなわち,この while
文は終了文字 '\0'
が何番目の要素にあるかを探しているのである.」
配列の添字が0から始まることに注意すると,配列 str の要素の値は次のようになっている.
s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | s[9] | s[10] | s[11] |
---|---|---|---|---|---|---|---|---|---|---|---|
'K' | 'y' | 'o' | 's' | 'a' | 'n' | 'd' | 'a' | 'i' | '\0' |
それを見ると,終端文字は要素 str[9]
にあり,その添字 9
がこの文字列に含まれる終端文字以外の文字数 9 に等しいことが分かる.
次のプログラムは, 文字配列 str1 にある文字列を,文字配列 str2 に複写する.
#include <stdio.h> int main() { char str1[30] = "Kyoto Sangyo University"; char str2[30]; int n; n = 0; while (str1[n] != '\0') { str2[n] = str1[n]; n++; } str2[n] = '\0'; printf("%s\n%s\n", str1, str2); return 0; }
Kyoto Sangyo University Kyoto Sangyo University
プログラムは,まず始めに n
の値を 0
に初期化して,次の while
文で,str1[n]
の値が終端文字(ヌル文字 '\0'
)でない間,str2
の n
番目の要素に str1
の n
番目の要素の値を代入してから n
を 1
だけ増やす,という作業を行い続ける.この while
文で str1
にある文字列の全ての文字(終端文字を含まない)が文字配列 str2
に複写される.
しかし,これでは str2
に終端文字がつけられていない.str2
に終端文字をつけるために,どこにつければ良いかを考えなければならいが,先ほどのサンプルプログラム1で見たとおり,この while
文が終了した時点で要素 str1[n]
の値が終端文字 '\0'
となっているので, str2
にも n
番目の要素 str2[n]
に終端文字を入れれば良いことがわかる.それゆえ,while
文の終了後に str2[n] = '\0';
という代入文がある.
終端文字を後から付け加えることをわざわざ書くのが嫌な人は, do - while 文を用いて,次のように書くこともできる.この場合, n の初期値は -1 で,文字配列の要素をコピーする前に n++ を実行することに注意しよう.
#include <stdio.h> int main() { char str1[30] = "Kyoto Sangyo University"; char str2[30]; int n; n = -1; do { n++; str2[n] = str1[n]; } while (str1[n] != '\0'); printf("%s\n%s\n", str1, str2); return 0; }
文字配列 str2
にある文字列を,文字配列 str1
にある文字列の末尾に付け加えて,最後にstr1
を表示するプログラムを作れ.
プログラムは2つのループからなることになる。一つ目は, str1 の終端を探すループである。 これは, str1 の文字数をカウントすることと同じ操作でできる。二つ目は, str2 の内容を str1 の末尾に複写するループである。 これは,サンプルプログラム2と同様であるが,複写元のインデックスと複写先のインデックスがずれているので注意が必要である。
#include <stdio.h> int main() { char str1[30] = "Kyoto San"; char str2[20] = "gyo University"; str1 の終端を求めるループ str2 を str1 の末尾に複写するループ printf("%s\n", str1); return 0; }
実行結果は次のようになる.
Kyoto Sangyo University
標準ライブラリ関数には,文字列を扱うための関数が多数用意されている. それらの中でよく使うものを載せておく. 授業中には全部は解説しないので,使用するときに読み返して自分で理解するように.
その前に,これらの関数を使う際には,次のインクルード命令をプログラムの先頭部分に書いておく必要があることを憶えておこう.
#include <string.h>
strlen(str)
は,文字列 str
の長さ(文字数)を整数値で返す.
#include <string.h> int main() { char str[30] = "Kyoto Sangyo University"; int len; len = strlen(str); printf("Length of \"%s\" is %d.\n", str, len); return 0; }
Length of "Kyoto Sangyou University" is 23.
strcpy(target, source)
は,文字配列 target
へ,
文字列 source
の内容を複写し,最後に終端文字を付ける
#include <string.h> int main() { char str1[30]; char str2[30] = "Kyoto Sangyo University"; strcpy(str1, str2); printf("%s\n", str1); return 0; }
Kyoto Sangyo University
strncpy(target, source, n)
は,文字配列 target
へ,
文字列 source
の内容を複写する.ただし,複写する文字数は最大 n
個であり,終端文字は複写されない.
#include <string.h> int main() { char str1[10]; char str2[30] = "Kyoto Sangyo University"; strncpy(str1, str2, 9); str1[9] = '\0'; /* 終端文字を付ける */ printf("%s\n", str1); return 0; }
このプログラムは "Kyoto Sangyo University" の最初の9文字だけを複写する.9文字を収納するためには,終端文字を含めて文字配列の長さは 10 必要であるので,
char str1[10];
と宣言されている.
Kyoto San
source
の文字数が n-1
以下だと,終端文字も複写される.しかし, strncpy
を用いるときは,そのことは忘れて,常に終端文字を後から付けるようにしたほうがいい.
strcat(target, source)
は文字列 target
の最後(終端文字 '\0'
のところから)に,
文字列 source
の内容を複写して付け加えて,最後に終端文字を付加する.
#include <string.h> int main() { char str1[30] = "Kyoto San"; char str2[30] = "gyo University"; strcat(str1, str2); printf("%s\n", str1); return 0; }
Kyoto Sangyo University
strncat(target, source, n)
は文字列 target
の最後(終端文字 '\0'
のところから)に,
文字列 source
の内容を,最大 n
文字だけ複写して付け加えて,最後に終端文字を付加する.
#include <string.h> int main() { char str1[30] = "Kyoto San"; char str2[30] = "gyo University"; strncat(str1, str2, 8); printf("%s\n", str1); return 0; }
Kyoto Sangyo Univ
strcmp(str1, str2)
は文字列 str1
と
文字列 str2
とを辞書式順序で比較して,その結果を整数値で返す.
文字列の内容が全く等しいときには 0 を,
str1 が小さいときには負の値を,
str1 が大きいときには正の値を返す.
#include <string.h> int main() { char str1[30] = "Kyosandai"; char str2[30] = "Kyoto Sangyo Daigaku"; int cmp; cmp = strcmp(str1, str2); printf("%d\n", cmp); return 0; }
-1
文字の比較は,それらに割り振られた数値 (アスキーコード表) の大きさで比較が行われる.
二つの文字列中の文字を先頭文字から比較していき,
最初に異なる文字に出会ったときのアスキーコードの大小が,文字列の大小となる.
(片方の文字列が他方の文字列の先頭部分に含まれるときには,長い文字列の方が大きくなる).
たとえば,つぎのようになる.
strcmp("abc", "abc") == 0
strcmp("abcd", "abdc") == -1
strcmp("abcd", "abcde") == -1
文字列の内容が一致しているかどうかの判定にも strcmp は用いられる.
if (strcmp(str1, str2) == 0) { printf("一致"); } else { printf("不一致"); }
strchr(str, chr)
は文字列 str
の中で
文字 chr
を探し,最初に見つかった位置をポインタで返す.
#include <string.h> int main() { char str[30] = "Kyoto Sangyo Daigaku"; char * p; p = strchr(str, 'a'); printf("%s\n", p); return 0; }
angyo Daigaku
ポインタについては,ポインタのページを参照すること.
strstr(str, keyword)
は文字列 str
の中で
文字列 keyword
に一致する部分を探し,最初に見つかった位置をポインタで返す.
#include <string.h> int main() { char str[30] = "Kyoto Sangyo Daigaku"; char *p; p = strstr(str, "ai"); printf("%s\n", p); return 0; }
aigaku
ポインタについては,ポインタのページを参照すること.
標準ライブラリ関数を用いて,キーボードから文字列を読み込み,各行の文字数を出力し, "end" が入力されたら終了するプログラムを作れ. 次の未完成プログラムコードを参考にしても良い.
#include ??? int main() { char str[1000]; 十分大きな文字配列を用意 int len; do { scanf("%s", str); 文字列を読み込んで str にしまう len = ??? ; 文字列長を求める(ライブラリ関数) printf("%d\n", len); } while ( ??? ) str が "end" に一致してなければ繰り返し(ライブラリ関数) return 0; }
abcde 5 Kyosandai 9 end 3
ヒント:文字列内容の一致の判定には strcmp を用いる.
次のソースコードに書き加えて,文字配列 str1
にある文字列を逆順にした文字列を,文字配列 str2
に複写して,最後に文字列 str2
を表示するプログラムを作れ.
#include <stdio.h> int main() { char str1[20] = "Kyosandai"; char str2[20]; ....... printf("%s\n", str2); return 0; }
実行結果が次のようになればよい.
iadnasoyK
次のソースコードに書き加えて,文字列 str 1中のアルファベットの大文字と小文字とを入れ替えて str2 に複写するプログラムを作れ.
#include <stdio.h> int main() { char str1[100] = "I am 10 years old. How old are you?"; char str2[100]; ........ printf("%s\n", str2); return 0; }
実行結果が次のようになればよい.
i AM 10 YEARS OLD. hOW OLD ARE YOU?
ヒント:アスキーコード表をよく見て,アルファベットの大文字,小文字を見分けてそれを入れ替えるプログラムを書く.