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