C/C++
勉強会
2007年6月11日目次
1 CUI 2 1.1 パイプとリダイレクト . . . 2 1.2 バッチファイル(シェルスクリプト). . . 3 1.3 コマンドライン引数とargc、argv . . . 4 2 配列とポインタ(前) 5 2.1 静的配列 . . . 5 2.2 ポインタ . . . 71
CUI
そろそろCUIのつまらなさに飽きてきた頃だろうと思うので、CUIのプログラムの少し は面白い使い方について話す。 1.1 パイプとリダイレクト これまで文字列の出力に使ってきたcoutを標準出力と呼び、入力に使ってきたcinを標 準入力と呼ぶ。標準入出力はファイルなどにリダイレクトできる。また、パイプを使って複 数のプログラムの入出力を接続することで、単純なプログラムの組み合わせから高度な仕事 をすることが可能となる。 1.1.1 リダイレクト redirect.cpp 1 #i n c l u d e <i o s t r e a m > 2 #i n c l u d e <s t r i n g > 3 4 u s i n g namespace s t d ; 5 6 i n t main (v o i d) 7 { 8 s t r i n g s ; 9 10 c i n >> s ; 11 c o u t << ” h e l l o , ” << s << e n d l ; 12 } 標準出力をファイルへリダイレクトする。出力先はコンソールでなくファイルになる。 g++ redirect.cpp ./a > hell.txt 次に標準入力をファイルへリダイレクトする。入力元はコンソールでなくファイルに なる。g++ redirect.cpp ./a < hell.txt 「>」と「<」がそれぞれ標準出力と標準入力のリダイレクトを表す。「>」で標準出力をリ ダイレクトすると、元からhell.txtというファイルがあった場合にその内容が消されてしま うが、「>>」を使うことで元の内容に付け足しするということもできる。 1.1.2 パイプ プログラムの標準出力と別のプログラムの標準入力をつなげることもできる。これを「パ イプ」と呼ぶ。サンプル別に作るのめんどかったからls *1とgrep *2で示す。 ls | grep hell 「|」を使うことでプログラムをつなげて起動できる。もちろん↓のようなこともできる。 ls | grep hell | ./a
1.2 バッチファイル(シェルスクリプト) バッチファイル(シェルスクリプト)を書くことで定型的な作業を楽に行うことができる。 batch.bat 1 REM 「 r e m」 「R E M」 で は じ ま る 行 は コ メ ン ト 2 REM 「e c h o 文 字 列 」 で 文 字 列 を 標 準 出 力 へ 吐 く と い う コ マ ン ド 3 e c h o hoge | a 4 e c h o hoge | a >hoge . txt 5 e c h o hage | a >>hoge . txt バッチファイルは変数や条件分岐、ループといった制御構造を持つ*3。その気があればあ る程度複雑な処理を書くこともでき、一種のプログラミング言語といってもよい。CUIで 何らかの仕事をするためには標準的なコマンドを幾つか覚えなくてはならないが、一度覚え てしまえばGUIよりも楽に作業できる場合がある。似たような複雑な処理を何度もする必 要がある場合、バッチファイルなどで手続を自動化するとよい。
*1Windows で「ls」と同等のコマンドは「dir」。grep は元々入ってない。 *2grep や sed は正規表現(regular expression)が使えるとより柔軟に使える。 *3詳しくはぐーぐる様に聞いてね
1.3 コマンドライン引数とargc、argv CUI のプログラムはコマンドライン引数(パラメータ)をとることができる。コマンド ライン引数はmain関数の引数としてプログラムへ起動時に渡される。 args.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 i n t main (i n t a r g c/∗ パ ラ メ ー タ の 数 ∗/, c h a r ∗ argv [ ]/∗ 内 容 ∗/) 6 { 7 c o u t << ”パラメータの数は” << a r g c << e n d l ; 8 9 f o r (i n t i =1; i < a r g c ; i ++) { 10 c o u t << a r g v [ i ] << e n d l ; 11 } 12 } main()の定義はこのように、引数をとらない定義とコマンドライン引数をとる場合の定 義とがある。プログラムがコマンドライン引数をとるかどうかに応じて、どちらを定義して もよい(両方はだめ)。 argc はコマンドラインへ入力された項目の数。そのプログラムのコマンド名自体を含む ので、最小で1となる。 argvはコマンドライン引数の内容。これはポインタへの配列である。ポインタと配列は 非常に強力な概念であり、C/C++の「メモリを直接扱える」という特徴と深くかかわって いる。詳細についてはこれから説明していく。
2
配列とポインタ(前)
2.1 静的配列 sarray.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 i n t main (v o i d) 6 { 7 c h a r a c h a r [ 1 6 ] ; // c h a rの1 6個 分 の 配 列 8 i n t a i n t [ 2 5 6 ] ; // i n tの2 5 6個 分 の 配 列 9 10 // 配 列 名[ x ]で 配 列 の x番 目 の 要 素 に ア ク セ ス 11 a c h a r [ 0 ] = ’A ’; // 最 初 の 要 素 は 「0番 目 」 12 a c h a r [ 4 ] = ’H ’; // な の で こ れ は 最 初 か ら 「5番 目 」 13 a c h a r [ 7 ] = ’O ’; // こ っ ち は 「8番 目 」 14 15 c o u t << a c h a r [ 0 ] << a c h a r [ 4 ] << a c h a r [ 7 ] << e n d l ; 16 17 f o r (i n t i =0; i < 2 5 6 ; i ++) { 18 a i n t [ i ] = 255 − i ; 19 } 20 f o r (i n t i =0; i < 2 5 6 ; i ++) { 21 c o u t << a i n t [ i ] << e n d l ; 22 } 23 }サンプルに示したintやchar以外に、stringや構造体など任意の型の配列を作ることも
できる。
型名 配列名[要素数];
配列のアクセスには0から始まる添え字([x])を使う。添え字が 0から始まるというこ
とは、char a[3]の最後の要素はa[2]ということなので注意。
char array[3]; char c; array[0] = ’a’; c = array[0]; 配列は適切な要素で初期化しながら定義することもできる。初期化要素が配列全体の要 素数より少ない場合、残りの要素は0で初期化される。また、配列を初期化して定義する場 合、要素数を省略してコンパイラに自動で決定させることもできる。 char carray[3] = {’a’, ’b’, ’c’};
int iarray[4] ={123, 456, 479, 123456789};
int iarray2[6] ={987}; // 残りの要素は0で初期化
char carray2[] = {’a’, ’b’, ’c’}; // carrayと等価
配列の範囲外をアクセスすると、実行時にバッファオーバーフローという危険で深刻なバ グを引き起こす。なぜこれがヤバいかということは次回以降説明するが、とにかくやっちゃ 駄目だ。
char array[] = {’a’, ’b’, ’c’, ’d’}; array[4] = ’e’; //やめろ! array[-1] = ’Z’; //ヤメロ!
配列をループなどと組み合わせて使うことで、同じ型の大量のデータをスマートに処理す ることができる。
2.2 ポインタ ポインタは変数や関数が、マシンの中のどこにあるかを示す値である。 pointer.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 i n t main (v o i d) 6 { 7 i n t i ; 8 i n t ∗p ; // i n tの ポ イ ン タ 9 10 i = 1 2 3 ; 11 p = &i ; // & iで 「 マ シ ン 内 のiの 場 所 」 と い う 意 味 12 c o u t << ∗p << endl ; // ∗ pで 「pの 中 身 」 、 つ ま りiを 示 す 13 i = 6 5 4 ; 14 c o u t << ∗p << endl ; 15 ∗p = 789; 16 c o u t << i << e n d l ; 17 } ポインタ型の変数は「*」を使い、以下のように定義する。 型名 *変数名; 通常の変数からそのポインタ値を得るには「&」演算子を使う。定数のポインタ値を得る ことはできない。 &変数名 2.2.1 ポインタ渡し 関数がreturnで呼び出し元へ返せる値は1つだけであるため、複数の値で結果を返す関 数を作るには構造体を使う必要がある。しかし構造体のコピーはそれなりのオーバーヘッ
ドを伴う処理であるため、構造体が大きければ大きいほど動作が低速になる。 struct.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 s t r u c t S{ 6 i n t i ; 7 c h a r c ; 8 } ; 9 10 11 s t r u c t S f u n c (i n t i ) { 12 s t r u c t S r e t ; 13 14 r e t . i = i + 2 0 0 ; 15 r e t . c = ’A ’; 16 17 r e t u r n r e t ; 18 } 19 20 i n t main (v o i d) 21 { 22 s t r u c t S s ; 23 24 s = f u n c ( 2 0 ) ; // 構 造 体 の コ ピ ー ( = 遅 い ) 25 c o u t << s . i << e n d l << s . c << e n d l ; 26 }
32bit マシンでは1MB の構造体であってもそのポインタは32bit(=4Byte)であるた
め、構造体のコピーではなくポインタ渡しによってポインタだけをコピーする方が高速にな
る。なお、構造体へのポインタからそのメンバを指す場合、アロー演算子「− >」を使うこ
pointer2.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 s t r u c t S{ 6 i n t i ; 7 c h a r c ; 8 } ; 9 10 11 v o i d f u n c (s t r u c t S ∗p) { 12 p−>i += 200; // (∗p ) . i += 200と 等 価 13 p−>c = ’A ’; // (∗p ) . c = ’A ’と 等 価 14 } 15 16 i n t main (v o i d) 17 { 18 s t r u c t S s ; 19 20 s . i = 2 0 ; 21 f u n c (& s ) ; // ポ イ ン タ 渡 し 22 c o u t << s . i << e n d l << s . c << e n d l ; 23 } なお、ローカル変数の内容は関数のreturn後は破棄されるため、ローカル変数のポイン タを返して呼び出し元で利用してはならない。 2.2.2 参照 C++には参照があり、ポインタ渡しと同じことができる。 reference.cpp 1 #i n c l u d e <i o s t r e a m >
2 3 u s i n g namespace s t d ; 4 5 s t r u c t S{ 6 i n t i ; 7 c h a r c ; 8 } ; 9 10 11 v o i d f u n c (s t r u c t S &r e f ) { 12 r e f . i += 2 0 0 ; 13 r e f . c = ’A ’; 14 } 15 16 i n t main (v o i d) 17 { 18 s t r u c t S s ; 19 20 s . i = 2 0 ; 21 f u n c ( s ) ; // 参 照 渡 し 22 c o u t << s . i << e n d l << s . c << e n d l ; 23 } 使い分けの基準には様々なものが考えられる。例えば C言語出身者の場合、参照だと一 見呼び出し元の構造体が変更されるように見えないため、構造体を関数の引数として渡すと きにコピーのオーバヘッドを避けたいだけなら参照、内容の変更もしたい場合はポインタ渡 しとする、などなど。状況によりよく考えて使い分けるといい。 2.2.3 配列とポインタ ポインタ変数への代入式において、配列の名前はその先頭要素へのポインタとして扱われ る。つまり、
char a[16]; char *p = a; は
と等価である。またこのとき、「a[i]」と「*(p+i)」も等価となる。 pa.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 6 i n t main (v o i d) 7 { 8 c h a r a [ ] = {’ a ’ , ’ b ’ , ’ c ’} ; 9 c h a r ∗p= a ; 10 11 c o u t << p [ 0 ] << p [ 1 ] << p [ 2 ] << e n d l ; 12 c o u t << ∗p << ∗(p+1) << ∗(p+2) << endl ; 13 14 f o r (i n t i =0; i < 3 ; i ++) 15 c o u t << p [ i ] ; 16 c o u t << e n d l ; 17 18 f o r (i n t i =0; i < 3 ; i ++) 19 c o u t << ∗p++; 20 c o u t << e n d l ; 21 } 2.2.4 C言語文字列 C言語の文字列型は「\0」(ヌル文字)で終端されたcharの配列と等価である。C++の string型とは違うものである。 cstring.cpp 1 #i n c l u d e <i o s t r e a m > 2
3 u s i n g namespace s t d ; 4 5 6 i n t main (v o i d) 7 { 8 c h a r ∗ s t r = ” hoge ”; 9 c h a r s t r 2 [ ] = ” hoge ”; 10 c h a r s t r 3 [ ] = {’ h ’, ’ o ’, ’ g ’ , ’ e ’, ’\0 ’} ; 11 12 c o u t << s t r << e n d l ; 13 c o u t << s t r 2 << e n d l ; 14 c o u t << s t r 3 << e n d l ; 15 }