プログラミング論II

プログラミング論
文字列
http://www.ns.kogakuin.ac.jp/~ct13140/Prog/
概要
• 文字列処理
– 煩雑な処理が多いが,本質的には簡単.
O-2
文字と文字コード
• 文字には,"文字コード"という数字が割り当てら
れている.
• 計算機はこの文字コード(通常ASCIIコード)を
用いて文字を処理する.
文字
'0' '1' '2' '3' '4' 'A' 'B' 'C' 'a' 'b'
文字
コード 30
16進数
文字
コード 48
10進数
31
32
33
34
41
42
43
61
62
49
50
51
52
65
66
67
97
98
O-3
C言語におけるchar型変数
• char型変数には,文字コードを格納できる.
• 文字コードは整数なので,char型は整数を格納
する変数.
– 実は,int型とchar型は極めて似ている.
– 唯一の違いは,大きさ.charは必ず1バイト.
intの大きさは処理系に依る.多くの場合4バイト.
O-4
C言語における文字
• 文字は'で囲って記述する.
• 'A'は,Aの文字コード.つまり65.
char ch;
ch = 'A';
と
char ch;
ch = 65;
は同一.
O-5
C言語における文字
char ch;
ch = 'A'; /* 'A' は65 */
printf("%d\n", ch);
実行結果
ch = 65;
printf("%d\n", ch);
65
65
char型は,整数型変数に過ぎない.
O-6
char型とprintf
• 文字コードが意味する「文字」をprintfで出力
するには,%cを使用する.
char ch;
ch = 'A';
printf("ch = %d\n", ch);
実行結果
printf("ch = %c\n", ch);
ch = 65
ch = A
O-7
char型変数と文字コード
• 'A'~'Z'や,'a'~'z'や,'0'~'9'は,連
続した文字コードが割り当てられている.
– 例:A, B, C, Dは,65, 66, 67, 68.
文字
'0' '1' '2' '3' '4' 'A' 'B' 'C' 'a' 'b'
文字
コード 30
16進数
文字
コード 48
10進数
31
32
33
34
41
42
43
61
62
49
50
51
52
65
66
67
97
98
O-8
char型変数と文字コード
この時点で,
chは65である.
void main(){
char ch;
この時点で,
chは66である.
ch = 'A';
printf("ch = %d, %c\n", ch, ch);
ch++;
printf("ch = %d, %c\n", ch, ch);
}
実行結果 ch = 65, A
ch = 66, B
O-9
文字コードの演算
• i文字目のアルファベットを表示
void main(){
int i; char ch;
i = 2;
ch = 'A' + i;
printf("%d %c\n", ch, ch);
}
'A'の文字コードは65
'C'の文字コードは67
実行結果 67 C
O-10
文字コードの演算
• 数字の文字(コード)とint型整数の変換
void main(){
int i; char ch;
i = 2;
ch = '0' + i;
printf("%d %c\n", ch, ch);
}
'0'の文字コードは48
'2'の文字コードは50
実行結果 50 2
O-11
文字コードの演算
• 'C'はアルファベットの何文字目か?
void main(){
int i; char ch;
ch = 'C';
i = ch - 'A';
printf("%d\n", i);
}
'A'の文字コードは65
'C'の文字コードは67
実行結果 2
O-12
文字コードの演算
• 数字の文字(コード)とint型整数の変換
void main(){
int i; char ch;
ch = '2';
i = ch – '0';
printf("%d\n", i);
}
'0'の文字コードは48
'2'の文字コードは50
実行結果 2
O-13
練習 0
• 'A'から'Z'を出力するには?
O-14
文字列
• 文字列は,"で囲む.
• "Hello!"などは,文字列.
• C言語では,文字(char型)の配列を用いて,文
字列を表現する.
O-16
文字列
• 文字列の最後には,「文字列の終端」を意味する
'¥0'がついている.
– これを用い,文字列がどこで終わるのかを判断する.
– よって,「文字の長さ+1」のchar型変数を用意する必
要がある.(後述)
– '¥0'の代わりにNULLを用いても良い.
•正確には,'¥0'とNULLと0(ゼロ)は全て等しい.
•char型は,整数型なので整数を代入する.当然,
0(ゼロ)も代入可能.
O-17
文字列
char txt[10];
txt[0] = 'H';
txt[1] = 'e';
txt[2] = 'l';
txt[3] = 'l';
txt[4] = 'o';
txt[5] = '\0';
文字列は,
char型(整数型)変数の配列.
文字コードの配列.
最後に'¥0'がつく.
txt[0]
'H'
txt[1]
'e'
txt[2]
'l'
txt[3]
'l'
txt[4]
'o'
txt[5] '\0'
txt[6]
txt[7]
txt[8]
txt[9]
O-18
文字列
char txt[6];
txt[0] = 'H';
txt[1] = 'e';
txt[2] = 'l';
txt[3] = 'l';
txt[4] = 'o';
txt[5] = '\0';
txt[0]
'H'
txt[1]
'e'
txt[2]
'l'
txt[3]
'l'
txt[4]
'o'
txt[5] '\0'
5文字の文字列を扱うには,長さ6のchar型の配列が必要.
長さ6のchar型配列には,5文字格納可能.
長さnのchar型配列には,n-1文字格納可能.
理由は,'¥0'の格納にchar型1個消費するので.
O-19
文字列とprintf
• 文字列をprintfで表示するには,%sを使用.
• 引数に「配列の先頭アドレス」を指定.
void main(){
char txt[10];
txt[0]='H'; txt[1]='e';
txt[2]='l'; txt[3]='l';
txt[4]='o'; txt[5]='\0';
printf("[ %s ]\n", txt);
}
実行結果 [ Hello ]
O-20
文字列とprintf
• 文字列をprintfで表示するには,%sを使用.
• 引数に「配列の先頭アドレス」を指定.
• 動作の詳細
– 与えられたアドレスに格納されている情報(整数)が,
'¥0'であるか調べる.
– '¥0'であったら終了.
– '¥0'で無ければ,格納されている整数を文字コード
と理解して,該当する文字を出力.
– 着目するアドレスを1個進めて,最初に戻る.
O-21
文字列とprintf
char txt[10];
txt[0]='H'; txt[1]=…
printf("%s", txt);
txtは,配列txt[10]の先頭のアドレス.
printfで%sに txt[0]のアドレスを
与えると,そこから'¥0'に出会うまで
文字を出力していく.
txt[0]
'H'
txt[1]
'e'
txt[2]
'l'
txt[3]
'l'
txt[4]
'o'
txt[5] '\0'
txt[6]
txt[7]
txt[8]
実行結果 Hello
txt[9]
O-22
文字列とprintf
char txt[10];
txt[0]='H'; txt[1]=…
printf("%s", txt+1);
txt+1は,txt[1]のアドレス.
printfで%sに txt[1]のアドレスを
与えると,そこから'¥0'に出会うまで
文字を出力していく.
txt[0]
'H'
txt[1]
'e'
txt[2]
'l'
txt[3]
'l'
txt[4]
'o'
txt[5] '\0'
txt[6]
txt[7]
txt[8]
実行結果 ello
txt[9]
O-23
文字列とprintf
char txt[10];
txt[0]='H'; txt[1]=…
txt[2] = '\0';
printf("%s", txt);
txtは,txt[0]のアドレス.
printfで%sに txt[0]のアドレスを
与えると,そこから'¥0'に出会うまで
文字を出力していく.
'¥0'に出会うと,処理は終了.
その後ろに文字があっても関係ない.
実行結果 He
txt[0]
'H'
txt[1]
'e'
txt[2] '\0'
txt[3]
'l'
txt[4]
'o'
txt[5] '\0'
txt[6]
txt[7]
txt[8]
txt[9]
O-24
文字列とprintf
char a[4],b[4];
b[0]='H'; b[1]='e';
b[2]='l'; b[3]='l';
b[4]='o'; b[5]='\0';
配列の大きさを超えて書き込むと
別の値(この例ではa[0],a[1])
の値を破壊してしまうので注意.
b[0]
'H'
b[1]
'e'
b[2]
'l'
b[3]
'l'
a[0]
'o'
a[1]
'\0'
a[2]
a[3]
O-25
文字列終端の'\0'を忘れると
char txt[10];
(スタック)メモリには,
最初はゴミの値が
入っている.
例えば,前回使ったときに
格納した値など.
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
O-26
文字列終端の'\0'を忘れると
char txt[10];
例えば,
このような値が
入っていたと
する.
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
8
7
6
5
4
3
2
1
0
1
O-27
文字列終端の'\0'を忘れると
char txt[10];
txt[0]='H'; txt[1]='e';
txt[2]='l'; txt[3]='l';
txt[4]='o';
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
72
101
108
108
111
3
2
1
0
1
txt[0]~txt[4]に
値が格納される.
O-28
文字列終端の'\0'を忘れると
char txt[10];
txt[0]='H'; txt[1]=…
printf("%s", txt);
txt[0]の位置から'\0'に出会うまで,
文字を出力していく.
この例では,偶然赤い位置のメモリに
0が入っていたので,
この位置で出力が停止される.
'\0'と0(ゼロ)は
全く同じであることに注意.
実行結果 Hello???
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
72
101
108
108
111
3
2
1
0
1
?の位置には
分けの分からない
文字が表示される.
O-29
文字列終端の'\0'を忘れると
char txt[10];
(スタック)メモリには,
最初はゴミの値が
入っている.
例えば,前回使ったときに
格納した値など.
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
ゴミ
O-30
文字列終端の'\0'を忘れると
char txt[10];
例えば,
このような値が
入っていたと
する.
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
13
12
11
10
9
8
7
6
5
4
3
2
1
0
1
O-31
文字列終端の'\0'を忘れると
char txt[10];
txt[0]='H'; txt[1]='e';
txt[2]='l'; txt[3]='l';
txt[4]='o';
txt[0]~txt[4]に
値が格納される.
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
72
101
108
108
111
8
7
6
5
4
3
2
1
0
1
O-32
文字列終端の'\0'を忘れると
char txt[10];
txt[0]='H'; txt[1]=…
printf("%s", txt);
txt[0]の位置から'\0'に出会うまで,
文字を出力していく.
もし,txt[9]の次のアドレスが,
アクセスを許されていないメモリだったら
プログラムはOS(Windowsなど)により
強制的に終了させられたりする.
txt[0]
txt[1]
txt[2]
txt[3]
txt[4]
txt[5]
txt[6]
txt[7]
txt[8]
txt[9]
72
101
108
108
111
8
7
6
5
4
3
2
1
0
1
O-33
大文字アルファベットかの調査
char ch;
ch = …;
printf("%cは", ch);
if( 'A'<=ch && ch<='Z'){
printf("大文字アルファベットである.\n");
} else {
printf("大文字アルファベットでない.\n");
}
O-34
大文字を小文字に変換
char ch; int n;
ch = 'X';
if( 'A'<=ch && ch<='Z'){
n = ch – 'A';
}
nには,
「chがアルファベットの何文字目か」が入る.
もしchが'A'なら,nは0.
もしchが'B'なら,nは1.
もしchが'C'なら,nは2.
'D'なら3,'E'なら4.
O-35
大文字を小文字に変換
char ch; int n;
ch = 'X';
if( 'A'<=ch && ch<='Z'){
n = ch – 'A';
ch = n + 'a';
}
nが0のとき,chには'a'(aの文字コード)が入る.
nが1のとき,chには'b'が入る.
なぜなら,「aの文字コード+1」は「bの文字コード」.
nが2のとき,chには'c'が入る.
3のとき'd',4のとき'e'が入る.
O-36
大文字を小文字に変換
char ch;
ch = 'X';
if( 'A'<=ch && ch<='Z'){
ch = ch – 'A' + 'a';
printf("%c\n", ch);
}
まとめると,上記のプログラムで良い.
O-37
数字であるかの調査
char ch;
ch = …;
if( '0'<=ch && ch<='9'){
printf("%cは数字である.\n", ch);
} else {
printf("%cは数字でない.\n", ch);
}
O-38
char型配列初期化
文字列の初期化
char *p = "Hello";
printf("%s\n", p);
実行結果 Hello
900 901 902 903 904 905
番地 番地 番地 番地 番地 番地
C言語中に
"Hello"と書くと,
メモリ上の6バイトが
割かれ,そこに
'H' 'e' 'l'
'l' 'o' '\0'
と格納される.
(このメモリは
読めるが書けない
ことが多い)
'H' 'e' 'l' 'l' 'o' '\0'
O-39
char型配列初期化
文字列の初期化
char *p = "Hello";
printf("%s\n", p);
実行結果 Hello
900 901 902 903 904 905
番地 番地 番地 番地 番地 番地
"Hello"は,
そのメモリの先頭アドレス
を意味する.
左の例では,
pにその先頭アドレス
が代入される.
よって,pは"900番地"
'H' 'e' 'l' 'l' 'o' '\0'
O-40
char型配列初期化
文字列の初期化
char p[] = "Hello";
printf("%s\n", p);
配列の長さが6と
決まっているので省略できた.
「長さは決まっている」ことに注意
p[0]p[1]p[2]p[3]p[4]p[5]
100 101 102 103 104 105
番地 番地 番地 番地 番地 番地
'H' 'e' 'l' 'l' 'o' '\0'
実行結果 Hello
char型配列の長さを
省略して配列宣言し,
"と"で囲んだ文字列で
初期化を行うと,
(省略した)配列の長さは
自動的に適切なものとなる.
つまり,「文字列の長さ+1」
となる.
char p[6]となり,
それぞれにはH,e,l,l,oが
代入される.
これは,
初期化の時のみ可能.
O-41
char型配列初期化
以下はNG.
char p[];
p[] = "Hello";
p = "Hello";
900 901 902 903 904 905
番地 番地 番地 番地 番地 番地
'H' 'e' 'l' 'l' 'o' '\0'
配列の長さが不明.
コンパイル時にエラー
p[]は,
配列の添え字が書いて
無いのでNG.
コンパイル時にエラー
pは,
配列p[]の先頭アドレス
という固定値(900).
読めるが,書けない.
代入はNG.
コンパイルエラー
O-42
char型配列初期化
以下はNG.
char p[10];
p[] = "Hello";
p = "Hello";
900 901 902 903 904 905
番地 番地 番地 番地 番地 番地
'H' 'e' 'l' 'l' 'o' '\0'
これはOK.通常の配列宣言
p[]は,
配列の添え字が書いて
無いのでNG.
コンパイル時にエラー
pは,
配列p[]の先頭アドレス
という固定値.
読めるが,書けない.
代入はNG.
O-43
char型配列初期化
以下はNG.
char p[10];
p[] = "Hello";
p = "Hello";
配列p[10]に,
'H','e','l',
'l','o','\0'の
6個を一括して
代入することはできない!
900 901 902 903 904 905
番地 番地 番地 番地 番地 番地
'H' 'e' 'l' 'l' 'o' '\0'
O-44
文字列のコピー:strcpy
配列txt[10]に,
'H','e','l','l','o','\0'
の6個を一括してコピーするには,
strcpyを用いる.
#include <string.h>
void main(){
char txt[10];
900
strcpy(txt, "Hello"); 番地
printf("%s¥n", txt);
'H'
}
901 902 903 904 905
番地 番地 番地 番地 番地
'e' 'l' 'l' 'o' '\0'
一括コピー
実行結果
Hello
100 101 102 103 104 105 106 107 108 109
番地 番地 番地 番地 番地 番地 番地 番地 番地 番地
'H' 'e' 'l' 'l' 'o' '\0'
O-45
文字列のコピー:strcpy
#include <string.h>
void main(){
char a[10], b[10];
strcpy(a, "Hello");
strcpy(b, a);
printf("%s¥n", b);
}
a[0]~a[5]の内容が
b[0]~b[5]に
一括コピーされる.
実行結果
Hello
O-46
文字列の長さを調べる:strlen
char txt[] = "Hello";
int n;
n = strlen(txt);
printf("%d\n", n);
実行結果
5
#include <string.h>
が必要です.
O-47
文字列の長さを調べる:strlen
#include <string.h>
char txt[] = "こんにちは";
int n;
n = strlen(txt);
日本語1文字は,
printf("%d\n", n);
2バイトである.
実行結果
10
#include <string.h>
が必要です.
O-48
文字列の結合:strcat
char t0[100] = "Hello";
char t1[100] = "World!";
strcat(t0, t1);
printf("%s\n", t0);
実行結果
HelloWorld!
#include <string.h>
が必要です.
O-49
文字列の比較:strcmp
文字列の大小を答える.
strcmp( a , b );
もし a<b なら「負の数」を返す.
もし a==b なら「0(ゼロ)」を返す.
もし a>b なら「正の数」を返す.
文字列の大小は,「辞書順」で決める.
O-50
文字列の比較:strcmp
文字列の大小を答える.
char t0[100] = "abc";
char t1[100] = "def";
int n;
n = strcmp( t0, t1);
printf("%d\n", n);
実行結果
-1
#include <string.h>
が必要です.
O-51
文字列の比較:strcmp
文字列の大小を答える.
char t0[100] = "ghi";
char t1[100] = "def";
int n;
n = strcmp( t0, t1);
printf("%d\n", n);
実行結果
1
#include <string.h>
が必要です.
O-52
文字列の比較:strcmp
文字列の大小を答える.
char t0[100] = "ABC";
char t1[100] = "abc";
int n;
n = strcmp( t0, t1);
printf("%d\n", n);
実行結果
-1
#include <string.h>
が必要です.
O-53
文字列を数字に変換:atoi
char txt[4] = "123";
int x, y;
x = atoi(txt);
y = x - 111;
printf("y = %d\n", y);
実行結果
y = 12
#include <stdlib.h>
が必要です.
O-54
文字列を数字に変換(自作)0/2
char txt[10] =
int i, n=0;
for(i=0; i<10;
if(txt[i] !=
n = n*10 +
} else {
break;
}
}
printf("%d\n",
"123";
i++){
'\0' ){
txt[i]-'0';
n);
O-55
文字列を数字に変換(自作)1/2
txt[10] は '1','2','3'.
最初のtxt[0]を読むと'1'. (これは49)
'1'-'0'で,int型の1が得られる.
txt[1]を読むと'2'.
1*10 + 2  12.
txt[2]を読むと'3'.
12*10 + 3  123.
O-56
文字列コピーを自作してみる(い)
void string_cpy(char *a, char *b){
int len, i;
len = strlen(b);
for(i=0; i<len+1; i++){
*(a+i) = *(b+i);
}
}
void main(){
char t0[100];
char t1[100] = "abc";
実行結果
string_cpy(t0, t1);
printf("%s\n", t0);
abc
}
O-57
文字列コピーを自作してみる(ろ)
void string_cpy(char *a, char *b){
int i=0;
while ( *(b+i) != '\0' ){
*(a+i) = *(b+i);
i++;
}
*(a+i) = '\0';
}
void main(){
char t0[100];
実行結果
char t1[100] = "abc";
string_cpy(t0, t1);
abc
printf("%s\n", t0);
}
O-58
文字列コピーを自作してみる(は)
• その前にif文.
if文は,()の中身が「0(ゼロ)なら偽と見なし,
0以外なら真とみなす」
if( 3 ){
printf("True\n");
} else {
printf("False\n"); 実行結果
True
}
O-59
文字列コピーを自作してみる(は)
• その前にif文.
if文は,()の中身が「0(ゼロ)なら偽と見なし,
0以外なら真とみなす」
if( 0 ){
printf("True\n");
} else {
printf("False\n"); 実行結果
False
}
O-60
文字列コピーを自作してみる(は)
• その前に比較演算子.
「3+2」は,「5」を返す.
同様に,比較演算子は値を返す.
もし真なら「1」を返す.
もし偽なら「0」を返す.
O-61
文字列コピーを自作してみる(は)
• その前に比較演算子.
「3+2」は,「5」を返す.
同様に,「3==3」は,「1」を返す.
int n;
n = (3==3);
printf("%d\n", n);
実行結果
1
O-62
文字列コピーを自作してみる(は)
• その前に比較演算子.
「3+2」は,「5」を返す.
同様に,「3==7」は,「0」を返す.
int n;
n = (3==7);
printf("%d\n", n);
実行結果
0
O-63
文字列コピーを自作してみる(は)
• その前に比較演算子.
「3+2」は,「5」を返す.
同様に,「3<7」は,「1」を返す.
int n;
n = (3<7);
printf("%d\n", n);
実行結果
1
O-64
文字列コピーを自作してみる(は)
• その前に比較演算子とif文.
'\0'は,整数の0(ゼロ)である.
これは,条件の真偽の「偽」でもある.
O-65
文字列コピーを自作してみる(は)
• その前に比較演算子とif文.
if( 1<2 ){ printf("T\n"); }
↓「1<2」は「1」である.
if( 1 ){ printf("T\n"); }
実行結果
T
O-66
文字列コピーを自作してみる(は)
void string_cpy(char *a, char *b){
while( *b ){
*a = *b;
a++;
b++;
}
*a = '\0';
}
void main(){
char t0[100];
char t1[100] = "abc";
string_cpy(t0, t1);
printf("%s\n", t0);
}
実行結果
abc
O-67
文字列コピーを自作してみる(に)
void string_cpy(char *a, char *b){
while( *b ){
*a++ = *b++;
}
*a = '\0';
}
void main(){
char t0[100];
char t1[100] = "abc";
string_cpy(t0, t1);
printf("%s\n", t0);
}
実行結果
abc
O-68
文字列コピーを自作してみる(ほ)
void string_cpy(char *a, char *b){
do {
*a++ = *b++;
} while( *(b-1) );
}
void main(){
char t0[100];
char t1[100] = "abc";
string_cpy(t0, t1);
printf("%s\n", t0);
}
実行結果
abc
O-69
練習 1
文字列の長さを調べるプログラムを自作せよ.
char txt[100]="hello";
??
O-70
補足
• C言語は「文字列処理が得意な言語」ではない.
– RubyやPerlなどが,文字列処理が得意.
• Javaはさほど得意ではないが,C言語よりはマシ
O-72