C プログラミング入門 基幹 7 ( 水 5) 1 12: コマンドライン引数 Linux にログインし 以下の講義ページを開いておくこと http://www-it.sci.waseda.ac.jp/teachers/w48369 2/CPR1/ 2017-07-05
まとめ : ポインタを使った処理 2 内容呼び出し元の変数を書き換える文字列を渡す 配列を渡すファイルポインタ複数の値を返す大きな領域を確保する 説明第 9 回第 10 回第 10 回第 11 回第 11 回
今回の内容 3 コマンドライン引数の取り扱い シェルから引数 ( オプション ) を受け取る 技術的には二重ポインタ (double pointer) である ポインタへのポインタ 秋期の C プログラミング で使うが この講義ではあまり踏み込まない 文字列から数値への変換 コマンドライン引数は単なる文字列なので 数値として扱うには変換が必要
ポインタと文字列と配列の復習 4
復習 : アドレスとポインタ 5 メモリ上の位置を表す値 型を持つ 変数 a のアドレスは &a ポインタ変数に格納できる アドレス値のことをポインタとも呼ぶことがある int a 100 { int a = 100; int *p = &a; int *p 矢印が指す位置のアドレスを持っている printf("%d", *p);
復習 : アドレス演算と読み書き 6 デリファレンス演算子 * で アドレスが指す位置の内容を読み書きできる 添え字演算子 [] で ポインタの指す位置をずらして読み書きできる ポインタ p にはアドレスが入っている ポインタ p に対して p+1 は型の大きさ 1 つ分だけ動かしたアドレス ポインタ p に対して *(p+n) と p[n] は同じ p[0] == *(p+0) == *p == -23 p -23 85 矢印が指す位置のアドレスを持っている p[1] == *(p+1) == 85 メモリ上のデータをどんな値とみなすかは ポインタの型で決まる
復習 : 配列とポインタ 7 配列変数名は 式中で配列の先頭へのポインタとなる 配列変数を a とすると a そのものがアドレス &a[0] (0 番要素のアドレス ) と同じ &a と書いてもよい int a[4] 365 それぞれ型が異なる場合があるが詳細は省略する ポインタに関する専門書を参照 p a[0] == *a == p[0] == *p == 365 p = &a という代入をした場合
復習 : 文字列 ( ポインタ ) 8 文字列 = メモリ上の文字 (char 型の値 ) が並んでいる領域 の先頭へのポインタ なぜ char 型なのかは歴史的な事情による 日本語を含む場合でも 基本的には char でよい 文字列リテラルを書くと その文字列がシステム領域に用意され その先頭ポインタを表す 文字列の終端は null 文字 ('\0') である なので null-terminated string と呼ばれる ほかに ASCIIZ とか C string とも呼ばれる { const char *s = "Hello world!\n"; s 'H' 'e' 'l' 'd' '!' '\n' '\0' ローカル変数 文字列データはシステム領域にある
復習 : 文字列 ( 配列 ) 9 配列を文字列リテラルで初期化すると その文字数 + 1 の大きさの配列が生成される 末尾に null 文字 ('\0') が自動的に付加される { char s[] = "Hello world!\n"; char s[] 'H' 'e' 'l' 'd' '!' '\n''\0' ローカル変数
コマンドライン引数処理 10
コマンドライン引数 11 シェルでコマンド名の後ろに書く文字列 ホワイトスペースで分割される ( トークン化 ) コマンドは受け取った引数を処理する tokenize [user@host]$ gcc src.c -o src -Wall -Wextra この場合 5 個の引数を gcc というプログラムに渡している [user@host]$./src hello world C 言語で書いた自作のプログラムに引数を渡した場合 どのように処理すればいいのか?
コマンドライン引数の受け取り方 12 main 関数として以下のプロトタイプを使う int main(int argc, char** argv); cf. 今までのは int main(void); 引数名は何でもよいが慣用的に argc, argv または ac, av が使われる それぞれ argument count と argument vector ( 引数の列 ) という意味 第 2 引数の書き方として char **argv char *argv[] のどちらでも 文法上は同じである 後者の書き方をする人もいるので覚えておく
argv の内容 13 文字列へのポインタの配列 最後に null ポインタで終わる./prog hello world 100 と実行した場合 規格では argv[0] に実行したコマンドが必ず入るとは定められていないが 多くの処理系でこうなる char **argv argv[4] == NULL argv[0][0] main 関数の自動変数の領域 システムのメモリ領域 argv[0] argv[1] たとえば argv[1] が "hello" という文字列だと思えばよい '.' '/' 'p' 'r' 'o' 'g' '\0' 'h' 'e' 'l' 'l' 'o' '\0' 'w' 'o' 'r' 'l' 'd' '\0' '1' '0' '0' '\0'
argc の意味 14 null ポインタの入っている要素の番号を表す n 個の引数を指定すると argc == n+1./prog hello world 100 と実行した場合 char **argv argv[argc] == NULL この例の場合 argc == 4 つまり 指定した引数の個数 + 1 main 関数の自動変数の領域 argv[0][0] システムのメモリ領域 argv[0] argv[1] '.' '/' 'p' 'r' 'o' 'g' '\0' 'h' 'e' 'l' 'l' 'o' '\0' 'w' 'o' 'r' 'l' 'd' '\0' '1' '0' '0' '\0'
例題 : 引数をすべて表示する 15 argv[i] を i = 1,..., argc-1 まで表示 int main(int argc, char **argv) { int i; } printf("%d arguments:\n", argc-1); for(i = 0; i < argc; ++i) { printf("[%d] == \"%s\"\n", i, argv[i]); } return 0; プログラム名が arg の場合 [user@host]$./arg hello 123 2 arguments [0] == "./arg" [1] == "hello" [2] == "123" argc までループさせない なぜなら argv[argc] == NULL なので表示できない
例題 : 引数をすべて表示する ( 別の書き方 ) 16 argv はポインタ変数であり 直接移動させることもできる int main(int argc, char **argv) { printf("%d arguments:\n", argc-1); } for( ; *argv!= NULL; ++argv) { printf("\"%s\"\n", *argv); } return 0; NULL が現れるまで動かすので argc 初期化条件は空 は必要ない プログラム名が arg の場合 [user@host]$./arg hello 123 2 arguments "./arg" "hello" "123" *argv は argv[0] と同じであり argv 自体を動かしていくと *argv が表す文字列が変わっていく ポインタを動かすだけでは何番目かがわからない 必要なら変数を用意してカウントする
難しいと思う人は 17 とりあえず argv[i] が i 番目の引数 と考えるだけで OK ただし i は 1 からカウント 最低限 p. 12 のプログラムが使えればよい
コマンドライン引数の注意 18 引数はあくまでも文字列である たとえば 100 と書いても "100" という文字列でしかない 数値として扱うには標準ライブラリ関数で変換する ( 次のスライドで説明 ) 引数が空文字列になる場合もある たとえば./prog "" abc と書いて実行すると argv[1] は空文字列 argv[2] は "abc" 引数はシステム領域に作られるので (const はついていないが ) 書き換えてはいけない
文字列から数値へ変換する 19
文字列 数値の変換 20 "100" という 3 文字 (+ 終端 null) の文字列を int 型の 100 に変換したい そうしないと ループとか計算で使えない 代表的な 2 つの方法を紹介 1. sscanf 2. ato*, strto* 一族 // 例えば argv[1] == "3" だとして int i; int n = argv[1]; // 当然できない for(i = 0; i < n; ++i) {...
文字列を数値に変換する (1) sscanf 21 sscanf() は 文字列を解析して変数に値を書きこむ int main(int argc, char **argv) { int i; 整数として変換する } printf("%d arguments:\n", argc-1); for(i = 1; i < argc; ++i) { int v = -1; sscanf(argv[i], "%d", &v); printf("[%d] == \"%s\" (%d)\n", i, argv[i], v); } return 0; 変換対象の文字列 プログラム名が arg の場合 [user@host]$./arg hello 123 abc 3.14 4 arguments [1] == "hello" (-1) [2] == "123" (123) [3] == "abc" (-1) [4] == "3.14" (3) 整数として変換できない文字列だった場合は何もしない 整数として変換できるところまで使われる
文字列を数値に変換する (1) sscanf 22 sscanf() は 文字列を解析して変数に値を書きこむ int main(int argc, char **argv) { int i; double として変換する } printf("%d arguments:\n", argc-1); for(i = 1; i < argc; ++i) { double v = -1; sscanf(argv[i], "%lf", &v); printf("[%d] == \"%s\" (%f)\n", i, argv[i], v); } return 0; l ( エル ) は不要 プログラム名が arg の場合 [user@host]$./arg hello 123 abc 3.14 4 arguments [1] == "hello" (-1.000000) [2] == "123" (123.000000) [3] == "abc" (-1.000000) [4] == "3.14" (3.140000) 小数の値として解釈されている
文字列を数値に変換する (2) 変換関数 23 文字列を数値に変換する関数として <stdlib.h> に以下の 2 種類がある atox() は簡単に使えるが 変換に失敗したかどうかを判断できない 関数名変換する型備考 atoi int 範囲外の値だった場合の戻り値は未定義 atol long int 変換に失敗した場合は 0 を返す atof double float ではない ( 関数名は ASCII to x という意味 ASCII は文字コードのこと ) strtol long int 変換に変換に失敗した場合は 0 を返す strtoul unsigned long int 失敗した位置をポインタとして得られる strtol, strtoul は基数 ( 何進法表記か ) を strtod double 指定する ( 関数名は string to x という意味 )
文字列を数値に変換する (2) 変換関数 24 なるべく右の strtox() を使うべき { // 整数に変換する例 int x; char *s = "2014.2"; x = atoi(s); printf("\"%s\" == %d\n", s, x); { 10 進数を指定 int x; char *s = "2014.2"; char *p; x = strtol(s, &p, 10); printf("\"%s\" == %d", s, x); p は変換に失敗した最初の位置である もし 文字列の先頭と同じなら 1 文字も解釈できなかったことになる atoi, strtol のプロトタイプ // 変換が完全に失敗した場合 if(str == p) {... 変換の失敗位置が必要なければ NULL を渡してもよい #include <stdlib.h> int atoi(const char *str); long strtol(const char *str, char **str_end, int base );