課題3の最後の問題までできていれば、main()
関数の中で
calendar()
関数を呼び出す際の引数を変更するだけで、
好きな「年」「月」のカレンダを表示できるようになっているはずである。
しかし、いちいちプログラムをコンパイルするのは汎用性に欠けるし、
C 言語やコンパイル等について良く知らない人に使ってもらえるとは期待できない。
仕上げとして、コンパイルし直すことなく、プログラムの振舞いを変える方法として、 コマンドラインの引数を使うことを考える。
そのため、今まで出してこなかった、文字や文字列について考えることとする。
C 言語では、「文字」を「'」(シングルクォート)でくくって表現する。
/* print_char.c */ #include <stdio.h> int main() { char a; a = 'A'; printf("a = \"%c\"\n", a); }
変数の型のところで、文字という意味の character から名前がついたと説明した
整数型の変数である、char
型が使用されている。
なぜ、「文字」なのに整数なのだろうか?
そこで、関数printf()
を使って様子を探ってみることにする。
関数printf()
で書式指定子を使い分けると、同じ値から(見た目が)異なる出力を得られることを学習した。
例えば、次のプログラムは、(10進数で)48から64までの数字を、それぞれ、10進数、8進数、16進数で出力するものである。
/* test_char.c */ #include <stdio.h> int main() { int i; printf(" %%3d %%4o %%2x\n"); for (i = 48; i < 64; i = i + 1) { printf("%3d %4o %2x\n", i, i, i); } }
書式指定子として 「…対応する文字が書き込まれる」と printf 関数のマニュアルに記述されている "%c" を追加してみると何が表示されるであろうか?
#include <stdio.h> int main() { int i; printf(" %%3d %%4o %%2x %%c\n"); for (i = 48; i < 64; i = i + 1) { printf("%3d %4o %2x %c\n", i, i, i, i); } }
[tacha@proge1]~/kadai04% ./test_char %3d %4o %2x %c 48 60 30 0 49 61 31 1 50 62 32 2 ... 63 77 3f ?
48 から 57の「数値」が、'0' から '9' の「数字」に対応している事がわかるだろうか?
じつは、コンピュータ内部ではすべての「文字」について、番号が割り振られている。 この番号の事を文字コードと呼ぶ。 一般に良く使われる文字コードとして ASCIIと呼ばれるものがある。 これは American Standard Code for Information Interchange (情報交換用米国標準コード)の略であり、 アメリカで定められた「文字」と「番号」の対応を定めたものであり、次のようになっている。
0x00 | 0x10 | 0x20 | 0x30 | 0x40 | 0x50 | 0x60 | 0x70 | |
0 | 空白 | 0 | @ | P | ` | p | ||
1 | ! | 1 | A | Q | a | q | ||
2 | " | 2 | B | R | b | r | ||
3 | # | 3 | C | S | c | s | ||
4 | $ | 4 | D | T | d | t | ||
5 | % | 5 | E | U | e | u | ||
6 | & | 6 | F | V | f | v | ||
7 | ' | 7 | G | W | g | w | ||
8 | ( | 8 | H | X | h | x | ||
9 | ) | 9 | I | Y | i | y | ||
a | * | : | J | Z | j | z | ||
b | + | ; | K | [ | k | { | ||
c | , | < | L | \ | l | | | ||
d | - | = | M | ] | m | } | ||
e | . | > | N | ^ | n | ~ | ||
f | / | ? | O | _ | o | DEL |
ASCII では、英語のアルファベットの大文字小文字、数字、記号等だけが 定義されている。 上の表の中で空欄になっている部分には「コントロールコード」と呼ばれる 制御用コード(改行や、タブ等も含まれる)が定義されている。 表に現れるのはコントロールコードを含めても128種類しかない。
変数のところで、整数型の一番小さなもの(256種類の表現が可能な型)にchar
型があったのは、
「文字」として ASCII が仮定されているため、128種類表現できれば十分であることに由来する。
逆に、文字の文字コードは、最初に述べた 'A'
のような表現を
"%d" などの整数に変換するような書式指定子を用いて出力すれば良いことになる。
print_char.c
を修正し、
変数 a
の内容を文字として出力するだけでなく、10進数、16進数としても出力するようなプログラムを作成せよ。
また、結果が ASCII 表と一致することも確認せよ。
上で述べたように、文字をプログラム中で使用するには、「'」(シングルクォート)で括る。 この時に括ることができるのは、1文字だけである。 2文字以上くくると、GCC では警告が表示される。
複数の文字からなる「文字列」を表現するためには、「"」(ダブルクォート)でくくる必要がある。
BASIC 等の言語と異なり、C には、文字列型という型は存在しない。
文字列は「文字」が複数連なっているのであるから、変数としては char
型の配列を用いる。
また、C では、文字列の終端を \0
というコードで示すことになっている。
この \0
は、NULL 文字(ナル文字と読む)と呼ばれるコントロールシーケンスの一つで、
ASCII では 0 という値を持つ。
そのため、文字列を変数として扱う際には、最低限入力したい「文字列の長さ + 1」個の要素を持つ配列を用意しなければならない。
NULL がないと、「文字列」を扱う関数は正しく動かなくなる上に、
確保した領域が少ないにも関わらず NULL を代入すると、
プログラムのほかの部分のデータが破壊されることになるため慎重に考えること。
「Hello」という文字列を char
型の配列 a
に代入した場合の様子は次のようになる。
また、次のようなプログラムで確認することもできる。
/* string_test.c */ /* 文字列とは ? */ #include <stdio.h> int main() { char a[6] = "Hello"; int i; printf("a = %s\n", a); for (i = 0; i < 6; ++i) { printf("a[%d] = %c\n", i, a[i]); } a[0] = 'h'; printf("a = %s\n", a); }
新しく出てきた printf
の変換指定子 %s
は
char 型の配列(本当はポインタ)を引数に取り、中身を文字列として表示する。
a[5]
の中身は何もないように見えるが、次のように実行すると
何かが入っていることは確認できる。
[tacha@proge1]~/kadai04% ./string_test | cat -v表示される
^@
は NULL 文字を意味している。
また、最後から2行目では、a[0] に 'h' という文字を代入しているので、最後の printf では 「hello」と表示されることになる。
プログラムの最後の部分で、a[2] に \0 を 代入した後、printf("a = %s\n", a);
で何が表示されるだろうか?
先週の最後に簡単に触れたインクリメント(increment)演算子 ++
について少し詳しくまとめておく。
インクリメント演算子は、変数の値を「1単位」増加させるために利用する。 したがって、
と
は、ほとんど同じ意味になる。
逆に変数の値を「1単位」減らす為の演算子として、デクリメント(decrement)演算子 --
が用意されている。
インクリメント/デクリメント演算子は、
という形式のほかに、
という形式でも利用できる。 区別する場合には、それぞれ変数の後ろ(前)にあるので、「後置(前置)インクリメント演算子」と呼ぶ。 どちらも 変数 i の中身は最終的に 1単位増えるのであるが、式として考えた場合には結果が異なるので注意が必要である。 次のプログラムを実行して、どう違うかを考えてみよ。
/* pre_post_test.c */ /* インクリメント演算子の前置と後置の違い */ #include <stdio.h> int main() { int i, j; i = 5; j = i++; printf("i=%d, j=%d\n", i, j); i = 5; j = ++i; printf("i=%d, j=%d\n", i, j); }
後置演算子の場合には、まず式の値を評価してから、インクリメントが行われるのに対し、
前置演算子の場合には、インクリメントしてから式の評価が行われる。
for
文での利用のように、式の値を使っていないのであれば、どちらを使っても結果は変わらないが、
式の値を使うような場合には、どちらを使うべきか良く考えなければならない。
最初はあまり内容について突っ込まずに、次のプログラムを実行してみよう。
/* arg_test.c */
/* コマンドライン引数について */
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
for(i = 0; i < argc; ++i) {
printf("argv[%d] = <%s>\n", i, argv[i]);
}
}
出来上がったプログラムを実行する際に、 プログラム名だけではなく様々な余分な情報を空白で区切ってつけてみよう。
[tacha@proge1]~/kadai04% ./arg_test [tacha@proge1]~/kadai04% ./arg_test test1 arg2 foo bar hoge
今までのプログラムと大きく異なるのは、main
関数に
引数がついたところである。
main
の引数にはプログラムが実行された際の情報がセットされる。
最初の引数は、パラメタの数(+1)の情報であり、int 型である。
2番目の引数は、パラメタの内容であり、char 型のポインタの配列である。
ポインタについては、今日は触れないので、あまり気にせず、「配列のようなもの」と思っていれば良い。
パラメタの最初には、実行したプログラム自身の名前が設定されていることに注意。
これにより、なんという名前で実行されたかという情報を得ることができる。
また、その結果、argc
の値は、パラメタの数より1多いことになる。
コマンドライン引数は、その変数の型や printf
での変換指定子からも判るように文字列である。
与えられた文字列が表現する10進数数値を int
型として得るためには atoi
関数を利用する。
なお、与えられた引数の中に10進数として解釈できないものが現れた場合、その先は評価されない。
atoi
関数は、stdlib.h
で宣言されているので利用するには
次のように stdlib.h
も include する必要がある。
#include <stdio.h> #include <stdlib.h>
問題 3-2 で作成した mycal6.c を変更して、mycal7.c を作成せよ。
ただし、atoi
関数を使って、コマンドライン引数で、
表示するカレンダの年月を指定できるようにせよ。
たとえば、
と実行すると2006年11月のカレンダを表示できるようになればよい。 余裕があれば、引数が2個以外の場合のエラー処理を付け加えよ。
4日目 | 表紙 | 6日目 |