ppt file

プログラミング入門2
第6回
基本型、文字列
情報工学科 篠埜 功
今日の内容
• 基本型、文字列について
基本型
• これまでに紹介した基本型はint型、double型
である。その他にもさまざまな基本型があり、
整数型、浮動小数点型に分類できる。
整数型
整数型は 、char型, short int型, int型, long int型の4
種類に分かれる(C99ではlong long int型が加わり、5
種類)。char型が一番小さく、long int型が一番大きい
(具体的に何バイトかは定められておらず、処理系
によって異なる)。
それぞれの型は、符号付きと符号無しでそれぞれ2
つずつある。 型指定子(signedかunsigned)で指定す
る。char型以外は、型指定子を与えない場合は符号
付きになる。char型は、型指定子を与えない場合は、
符号付きか符号無しかのいずれかである(処理系依
存)。
扱う数の範囲に応じて適切な型を選択して用いる。
char型が符号付きかどうか判定
(打ち込んで確認)
#include <stdio.h>
#include <limits.h>
int main (void) {
printf ("この処理系のchar型は");
if (CHAR_MIN)
printf("符号つきです。\n");
else
printf("符号無しです。\n");
return 0;
}
整数型
整数型は、結局、
–
–
–
–
–
–
–
–
signed char型
unsinged char型
signed short int型
unsigned short int型
signed int型
unsigned int 型
signed long int型
unsigned long int型
の8個となる。(long long intを入れれば10個。)
符号について(1)
符号無し整数型(unsigned char, unsigned short int,
unsigned int, unsigned long int)においては、ビット
列を普通の2進数として解釈する。
符号について(2)
符号付き整数型(signed char, signed short int, signed
int, signed long int,)においては、符号を表すために1
ビットを用いる(符号ビットという)。符号ビットが0の場
合には、残りのビットを普通の2進数として解釈する。
符号ビットが1の場合の解釈は以下の3通りのいずれ
かである(処理系依存)。
• 符号ビットを0に変えた場合のビット列が表す数
にマイナスを付けた数を表す
• 2の補数表現
• 1の補数表現
例外について
演算の結果が、表現可能な値の範囲を超え
る場合(overflow)や、0での除算などの場合、
例外(exception)が発生する。
ただし、符号無し整数型の演算では、表現可
能な値の範囲を超えた場合、表現可能な最大
値+1で割った余りになると定められている(の
でオーバーフローは発生しない)。
文字について
プログラム中では、1文字をクォートで囲むと、その
文字に対応するint型の数を表す。
(例) ‘a’ はaという文字に対応するint型の数を表す。
(char型ではないことに注意。C++ではchar型だ
が。)演習室の環境では、’a’はint型の97を表す。
(補足)文字はchar型で表すと無駄がないが、int型で表
しておくと、例えばファイルから文字を1文字ずつ読み
取って変数に代入する場合、ファイルの終端に来た時
にEOF(int型、値はどの文字とも異なる。普通は-1。)が
返され、それを変数に代入し、その値がEOFと等しいか
どうか判定するといったプログラムを書けるというメリッ
トがある。
(補足)文字列はchar型の並びである(後述)。
printfでの文字の表示方法
printf関数で文字を表示する場合、変換指定子を%cにする。引数
にはint型を受け取り、それをunsigned char型に変換(256で割っ
た(非負の)余りに変換)し、それに対応する文字を表示する。
#include <stdio.h>
int main (void) {
printf ("%c\n", 'a');
printf ("%d\n", 'a');
printf ("%c\n", 97);
printf (“%c\n”, 97 + 256);
printf (“%c\n”, 97 – 256);
return 0;
}
a
97
a
a
a
が表示される。
文字の8進表記
ある文字に対応する数が分かっているとき、そ
の数で直接書きたい場合がある。その時に使う
のが文字の8進表記である。
たとえば、’a’と書く代りに、(演習室の環境で
は)’\141’と書くことができる。ただし、プログラ
ムの可搬性が低下するので使わない方がよい。
よく使うのは、ヌル文字を表す場合で、’\0’と書
く。’\0’は、対応する数が0である文字(ヌル文字
という)に対応する数(すなわち0)を表している。
0と直接書いても同じ意味だが、0よりも’\0’の方
が、文字を表している数だということが見た目
に分かりやすい。
8進逆斜線表記の構文
8進逆斜線表記の構文1
\ octal-digit
8進逆斜線表記の構文2
\ octal-digit octal-digit
8進逆斜線表記の構文3
\ octal-digit octal-digit octal-digit
ただし、octal-digitは0 1 2 3 4 5 6 7のいずれかを表す。
つまり、バックスラッシュのあとに1桁から3桁の8進数を書いた
ものが8進逆斜線表記である。
8進逆斜線表記はクォートおよびダブルクォートの中でのみ用
いる。8進逆斜線表記をクォートで囲んだとき、それは、その8
進数の表している数(int型)を表す。つまり、文字を表すための
数であるということを見た目に分かりやすくするために用意さ
れている構文である。
変数への格納
#include <stdio.h>
int main (void) {
int x;
x = 'a';
printf ("%c\n", x);
printf ("%d\n", x);
x = x+1;
printf ("%c\n", x);
printf (“%d\n”, x);
return 0;
}
a
97
文字はよくint型の変数に格納
される(EOFが格納できるように
するため)。もちろんchar型の変
数に入れてもよい。
ただしあとで説明するように、
文字列はchar型の配列に格納
される。
b
98
1を足したものを%cで表示す
るとbが表示される。
整数型の範囲について
(打ち込んで確認)
C言語の処理系は、limits.hにおいて、それぞれの
型の最大値、最小値をマクロとして提供する。
#include <stdio.h>
#include <limits.h>
int main (void) {
printf ("char : %dから%dまで\n", CHAR_MIN, CHAR_MAX);
printf (“short int : %dから%dまで\n", SHRT_MIN, SHRT_MAX);
printf ("int : %dから%dまで\n", INT_MIN, INT_MAX);
printf ("long int : %ldから%ldまで\n", LONG_MIN, LONG_MAX);
return 0;
}
long intの変換指定子には%ldを用いる。
整数型のまとめ
• 整数型はある一定の範囲の整数を表現する
ための型である。
• 扱う数値が負にならないことが分かっていれ
ば、符号無しの型を使うと、同じビット数で、よ
り大きな範囲の数を扱うことができる。
浮動小数点型
浮動小数点型は小数を表すための型であり、
– float型
– double型
– long double型
の3つがある。この演習では説明しない。
文字列とは(1)
文字(char型)の並びであって、‘\0’(ヌル文字 null
character、値は0。ナル文字と読む場合もある。)までを文
字列と呼んでいる。(’L’や’\0’などはint型だが、char型に
変換されていると思って読んでください)
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
‘\0’ ‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
これらは文字(char型)の並びである。文字列は、文字
の並びの中で、’\0’が最後(だけ)にあるものである。
文字列とは(2)
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
この部分は文字列である。
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
この部分も文字列である。
一番右端が’\0’であり、それ以外に’\0’を含ん
でいなければそれは文字列である。
文字列とは(3)
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
この部分は文字列である(空文字列という)。
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
この部分も文字列である。
一番右端が’\0’であり、それ以外に’\0’を含ん
でいなければそれは文字列である。
char型の配列
文字列はchar型の配列を宣言し、各要素に文
字を1文字ずつ格納して最後にヌル文字を格
納することによって作成できる。
char型の配列型の変数は
char a [7];
のように宣言する。これはchar型の要素を7個
持つ配列aを宣言している。この宣言下で
a[0] = ‘L’;
という代入文が実行されると、’L’(int型の数)が
暗黙の型変換(第2回講義スライド参照)により
char型に変換されてからa[0]に代入される。
printf関数での文字列の表示方法
#include <stdio.h>
int main (void) {
char a [7];
a[0] = ‘L’;
a[1] = ‘i’;
a[2] = ‘n’;
a[3] = ‘\0’;
a[4] = ‘u’;
a[5] = ‘x’;
a[6] = ‘\0’;
printf (“%s\n”, &a[0]);
return 0;
}
文字列の表示には、変
換指定子に%sを用い、
引数にchar型へのポイ
ンタ(詳しくは後日説明)
を与える。そのポインタ
が指しているところから、
ヌル文字の手前までを
画面に表示する。
&a[0]は配列aの先頭要素
へのポインタを表す。先頭
要素は’L’であり、Linが表
示される。
printf関数での文字列の表示方法
(打ち込んで確認)
#include <stdio.h>
int main (void) {
char a [7];
a[0] = ‘L’;
a[1] = ‘i’;
a[2] = ‘n’;
a[3] = ‘\0’;
a[4] = ‘u’;
a[5] = ‘x’;
a[6] = ‘\0’;
printf (“%s\n”, &a[1]);
return 0;
}
&a[1]は配列aの2番目
要素へのポインタを表
す。2番目の要素は’i’
であり、inが画面に表
示される。
注意
• 文字列の表示にはprintfの変換指定子とし
て%sを使う。このとき、引数に与えられるポイ
ンタが指す先はchar型の並びであることが前
提なので、もしint型の配列に文字列を入れて
いた場合には正常に表示されなくなる。
間違った例
#include <stdio.h>
int main (void) {
int a [7];
a[0] = ‘L’;
a[1] = ‘i’;
a[2] = ‘n’;
a[3] = ‘\0’;
a[4] = ‘u’;
a[5] = ‘x’;
a[6] = ‘\0’;
printf (“%s\n”, &a[0]);
return 0;
}
このプログラムだと、意
図した通りには表示され
ない。(具体的にどう表
示されるかはint型の表
現に依存。)
(さらに補足)
&a[0]はintへのポインタ型であ
り、char型へのポインタではな
いので、
$ gcc -W -Wall test.c
のようにオプションを付けてコン
パイルすればwarningが出る。
解説
この演習室の環境では、’L’はint型の76であり、
さきほどのint型の配列aは、メモリ上では
76
0
a[0]
0
0 105 0
0
0 110 …
a[1]
…
のように並んでいる。すなわち、
‘L’ ‘\0’ ‘\0’ ‘\0’ ‘i’ ‘\0’ ‘\0’ ‘\0’ ‘n’
…
ということである。つまり、この演習室の環境では’L’し
か表示されなくなる。
ポインタについて
(後日、ポインタの回にも説明する)
番地
100
101
102
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
a[2]
a[5]
a[0] a[1]
103
a[3]
104
a[4]
105
106
a[6]
&a[0]は配列aの先頭要素へのポインタを表
す。&a[0]の値は、配列aの先頭要素の番地
である。この場合、100である。&a[1]は配列
aの2番目の要素へのポインタであり、101
である。&a[2]は102である。以下同様。
printf関数における変換指定子の%sについて
番地
100
101
102
‘L’
‘i’
‘n’ ‘\0’ ‘u’
‘x’ ‘\0’
a[2]
a[5]
a[0] a[1]
103
a[3]
104
a[4]
105
106
a[6]
printf (“%s”, &a[1]);
が実行されると、101番地から、ヌル文字の
1つ手前までの文字が画面に出力される。
つまり、inが表示される。
printf (“%s”, &a[4]);
だと、uxが表示される。
補足
printf関数において変換指定子として%cを使う場合、引数
の型はint型と書いたが、char型でもよい。もしchar型の式
が与えられた場合は、規定の実引数拡張(default
argument promotion)により、自動的にint型に変換される
(printfの引数の個数は可変なので)。これは本講義の範囲
外とし、説明はしない。
(参考) printfの変換指定子でfloat型用のものがないのも規
定の実引数拡張による。
改行文字
改行文字は’\n’で表す。
‘\n’自体はint型の数だが、char型の変数やchar型
の配列の要素に代入するときは自動的にchar型の
数に変換されてから代入される(暗黙の型変換)。
文字列リテラル
ダブルクォート ” で囲んだものを文字列リテラルという。
(例)”abc”など。
これは、char型の並び(配列)であり、最後にヌル文字
が追加されたものである。
(例)“abc\0de”は文字列リテラルである。最後にヌル文
字が追加されるので、以下のchar型の並び(長さ7の
char型の配列)となる。
‘a’
‘b’
‘c’ ‘\0’ ‘d’
‘e’ ‘\0’
例(打ち込んで確認)
#include <stdio.h>
int main (void) {
printf ("abc\0de");
return 0;
}
これを実行すると、abcが表示される(deは表示さ
れない)。
文字の読み込み
1文字の読み込みは、scanf関数で変換指定子とし
て%cを用いて行う。
#include <stdio.h>
int main (void) {
char a;
printf ("Input characters: ");
scanf ("%c", &a);
printf ("%c\n", a);
return 0;
}
%dや%sの場合と違
い、%cの場合は入力
文字が改行、空白、タ
ブなどの場合も読み込
まれる。
文字列の読み込み
文字列の読み込みは、scanf関数で変換指定子と
して%sを用いて行う。
#include <stdio.h>
int main (void) {
char a[7];
printf ("Input characters: ");
scanf ("%s", &a[0]);
printf ("%s\n", &a[0]);
return 0;
}
scanfは、入力文
字中に空白文字
があると、空白
文字の手前まで
が配列に格納さ
れる。
scanf関数における変換指定子の%sについて
番地
100
101
102
103
104
105
‘a’
‘b’
‘c’
‘d’
‘e’ ‘\0’
a[0] a[1]
a[2]
a[3]
a[4]
a[5]
106
a[6]
scanf (“%s”, &a[0]);
が実行されると、例えばabcdeを入力した場合、100番地から104
番地までa,b,c,d,eが順番に格納され、105番地にヌル文字が格納
される。最後にヌル文字が格納されるので、配列のサイズ-1の長
さを超えて入力してはいけない。それ以上入力したらあふれた部
分も書きこまれる(バッファーオーバーフロー)。 サーバープログ
ラム等でこのようなコードが用いられていると攻撃の対象になる
ので対処が必要だが、本講義の範囲外とする。
gets関数
空白がある文字列はgets関数を用いると読み込ませることが
できる。gets関数は改行文字まで読み込み、改行文字をヌル
文字に置き換えて配列に格納する。これも配列のサイズを超
えた場合にも書きこまれる。(fgets関数を用いるとこれに対処
できるが、本講義の範囲外とする。)
getsが使われているとgccが警告を出すが、本講義では無視
することとする。
#include <stdio.h>
int main (void) {
char a[10];
gets (&a[0]);
printf ("%s\n", &a[0]);
return 0;
}
Printfによるデバッグ
プログラム実行時にセグメントエラー等で停止した場合、gdbな
どのデバッガを使ってデバッグをするのが一般的だが、もっと
原始的なやり方として、printfを何カ所かに挿入することによっ
てどの位置で停止したかを突き止めるというやり方もある。そ
の際、停止する場所の範囲を半分ずつ狭めていくと効率がよ
い。場所が特定されたら、次に何が間違っているのかを考え
ていくことになる。
Printfでデバッグする際の注意事項を次のページに記載する。
Printfによるデバッグの注意事項
printfが実行されても、すぐ画面に文字が出力され
るとは限らず、内部に文字列が溜まったままにな
る場合がある。対処法としては、printfの後に、
fflush(stdout);
を入れればよい。これにより、内部に溜まった文字
列が(あれば)画面に出てくる。
次のページに具体例を示す。
例
#include <stdio.h>
int main (void) {
printf ("abc");
while (1);
return 0;
}
#include <stdio.h>
int main (void) {
printf ("abc");
fflush(stdout);
while (1);
return 0;
}
左のようなプログラムを実行したとき、
abcという文字列が画面に表示されな
い場合がある。そのような場合、
fflush(stdout);という関数呼び出しを
下のプログラムのように追加すると、
強制的に画面に書き出される。
基本課題1
英文をキーボードから入力し、その英文の長さを
表示するプログラムを作成せよ。ただし、英字以
外の文字(空白やピリオド等)も文字数にカウント
する。 gets関数を使ってよいものとするが、参考
課題1のようにしてもよい。配列を使う場合は十分
な長さの配列を宣言して使用せよ。
[実行例]
英文を入力して下さい: This is a pen.
あなたが入力した英文の文字数は14です。
基本課題2
キーボードから英文を入力として受け取り、逆順に
表示するプログラムを書け。十分な長さの配列を宣
言して使用せよ。
[実行例]
[sasano@localhost 2011]$ ./a.out
英文を入力してください: This is a pen.
.nep a si sihT
[sasano@localhost 2011]$
発展課題1
英文をキーボードから入力し、その英文中の単語の数を表示
するプログラムを作成せよ。英字以外の文字(空白、ピリオド、
コンマ、クエスチョンマーク等)は区切り文字とし、単語数には
カウントしない。
[実行例]
% ./a.out
英文を入力してください: This is a pen.
単語数は4です。
% ./a.out
英文を入力してください: Is this a pen?
単語数は4です。
% ./a.out
英文を入力してください: How are you? --- I'm fine, thank you.
単語数は8です。
発展課題2
英文をキーボードから入力し、英字a-zの大文字だけ
を表示するプログラムを書け。
[実行例]
[sasano@localhost 2011]$ ./a.out
文字列を入力: Unidentified Flying Object
UFO
[sasano@localhost 2011]$
発展課題3
キーボードから英文を入力として受け取り、それが回
文であるかどうかを判定するプログラムを作成せよ。
ただし、英字a-zの大文字小文字は区別せず、かつ英
字以外の記号(空白、エクスクラメーションマーク等)
は無視するものとする。
[実行例]
英文を入力してください: So many dynamos!
So many dynamos! は回文です。
(補足)dynamoは発電機。dynamosはdynamoの複数形
(注意)十分な長さの配列を宣言して用いること。
発展課題4
文字列を2つキーボードから受け取り、1つ目の文字
列が2つ目の文字列の(連続した)部分文字列になっ
ているかどうかを判定するプログラムを作成せよ。た
だし、入力文字列中に空白やタブはないものとする。
[実行例1]
1つ目の文字列: def
2つ目の文字列: abcdefg
defはabcdefgの部分文字列です。
[実行例2]
1つ目の文字列: cef
2つ目の文字列: abcdefg
cefはabcdefgの部分文字列ではありません。
アルファベットの文字の判定
アルファベットの文字の大小の判定は、islower, isupper
関数で行う。アルファベット文字かどうかは、islower,
isupperのいずれも偽になるかどうかで判定できる。
#include <ctype.h>
#include <stdio.h>
void hantei (int c) {
if (islower(c))
printf ("%cは小文字です\n", c);
else if (isupper (c))
printf ("%cは大文字です\n", c);
else
printf ("%cはアルファベットではありません。\n", c);
}
/* 続き */
int main (void) {
hantei ('a');
hantei ('A');
hantei ('+');
return 0;
}
大文字から小文字への変換
大文字から小文字への変換はtolower関数を用いる。
tolower関数は、int型を引数に受け取り、それが大文字
に対応する数の場合、その小文字に対応するint型の
数を返す。大文字以外の場合はそれをそのまま返す。
ctype.hを読み込む必要がある。
#include <ctype.h>
#include <stdio.h>
int main (void) {
printf ("%c\n", tolower ('A'));
printf ("%c\n", tolower ('a'));
printf ("%c\n", tolower ('+'));
return 0;
}
(補足)char配列の初期化について
char a [1000] = {‘\0’};
char b [1000] = {‘\0’};
のようにchar配列の初期化をすることによって、
すべての要素がヌル文字で初期化されるので便
利がよい。(文字列のコピーなどの場合に最後の
ヌル文字の扱いがやりやすくなるので)
参考課題1
空白がある(かもしれない)文字列をキーボードから
受け取り、表示するプログラムをgets関数を使用せ
ずに作成せよ。
[実行例]
[sasano@localhost 2011]$ ./a.out
文字列を入力してください: This is a pen.
入力した文字列はThis is a pen.です。
(ヒント)
配列を使って、scanfで変換指定子の%cを用いてwhile文の
中で改行文字を読むまで1文字ずつ順番に配列の各要素に
格納し、改行文字をヌル文字に置き換えればよい。その後、
printfで変換指定子の%sを使って表示させればよい。(表示
部分も一文字ずつwhile文で表示させることもできる。)
参考課題1 解答例
#include <stdio.h>
int main (void) {
char s[100], c;
int i;
printf("文字列を入力してください: ");
for (i=0; i<99; i=i+1) {
scanf ("%c", &s[i]);
if (s[i] == '\n')
break;
}
s[i] = '\0';
printf ("入力した文字列は%sです。\n", &s[0]);
return 0;
}
参考課題2
キーボードから英語の文字列を1つ入力として
受け取り、その中に英語の大文字をすべて小
文字に変換したものを画面に表示するプログラ
ムを作成せよ。英語の大文字以外の文字はそ
のまま表示せよ。
[実行例]
文字列を入力してください: This is a pen.
this is a pen.
(注意)十分な長さの配列を宣言して用いること。
参考課題2 解答例
#include <stdio.h>
int main (void) {
char s[100], c;
int i;
printf("文字列を入力してください: ");
for (i=0; i<99; i=i+1) {
scanf ("%c", &s[i]);
if (s[i] == '\n')
break;
if (isupper(s[i]))
s[i] = tolower (s[i]);
}
s[i] = '\0';
printf ("%s\n", &s[0]);
return 0;
}