③ typedef unsigned long int dword ; ④ int unsigned typedef long dword ; 幸いにも , typedef はたいてい宣言の先頭 に置かれるように慣習化されているのて、 , この④のような奇妙な宣言に出くわすこと は少ない。しかし , const などは新しいため にまだ標準的なスタイルが確立していない といえる。 ⑤ const char * p ; ⑥ char const * p ; これらに関しては , 実際に⑤ , ⑥ともに 使われている。筆者の周りて、は⑤のほうが 優勢のようだが , ⑥もしばしば見かける。 繰り返すが , ⑤ , ⑥は同じ意味て、ある。 れらはいずれも「 const な char へのポインタ p 」を意味している。すなわち , p の指し示し ている先の char オプジェクトが const て、ある ( その値を変更してはならない , 変更しない ) ⑦ char * const p ; これに対して次の⑦は意味が違う。 ことを意味する。 104 C MAGAZINE 1991 10 gned< あると定められているため , この例 われるのて、ある。また int はデフォルトて、 si つまり , 省略されているときには int が補 ⑩ static signed int a3 ; ⑩ static signed a2 ; ⑨ static int al ; ⑧ static a0 ; ェクトを宣言している。 たとえば次の宣言はすべて同じ型のオプジ さらに , 省略時解釈というルールがある。 のだ。 こは「宣言子 ( declarat 。 r ) 」の構文範囲な に「宣言指定子」の構文範囲の外なのて、ある。 に注目していただきたい。この位置はすて、 する c 。 nst は「 * 」の右側に登場していること いというわけて、はないのて、ある。⑦に登場 const はどこに出てきても意味が変わらな の手段て、値を設定することはて、きない ) 。 st なのて、ある ( したがって , p には初期化以外 味している。すなわちポインタ p 自身が con これは「 char への const なポインタ p 」を意 て、は signed と記しても記さなくても同じなの て、ある。ただし , 宣言指定子のすべてを省 略した次の形はさすがに許されない ⑩ a4 ; / * これはエラー * / 一方 , 型指定子などは , 任意の組み合わ せが許容されているわけて、はない。実は許 される型指定子の組み合わせは次のとおり て、ある (ANSI 3.5.2 ) 。 VOid signed char Char unsigned Char short, signed short, short int, short int int, signed, signed int unsigned, unsigned int signed long, signed long, long int, signed IO unsigned long, unsigned long int ng int long double double float struct—or—union¯specifier はない。たとえば , コて、くくられた最後の三つはキーワードて はすべて同じ型として取り扱われる。カッ カンマて、区切られて 1 行に記されているの (typedef-name) (enum-specifier) (struct—or—union—specifier) 数型てある。 こともあり得るが ) , もっとも精度の高い実 て、あり ( 実装によっては double と同じという れている。 long double は double とは違う型 され , 新しく longdouble というのが新設さ le と同一の意味を持っていたが , それは廃止 K & R て、は long float という型があって doub れらは厳密には ANSI 違反てある。また , 旧 ngchar などを通すものがあるようだが , そ 一部のコンパイラて、は signedfloat とか 10 とを意味している。 とは構造体あるいは共用体の指定を行うこ これらの点を考えると , C の宣言指定子の 構文は何となく整理されていないという印 象を受けることは否めず , 宣言の自由度が 高いというのは混乱を招く原因になりこそ すれ , 必ずしもメリットにはなっていない ような気がする。これもひとえに歴史を引 きずり , 過去との互換性を重視したからだ ろうか ( それにしてはあっさりと long float を廃止したのはなぜだろう ? ) 。 なせ C の宣言は難解なのか ? 前節て、述べたように , C の型宣言は , その 宣言指定子の記述順序が任意てあるという 特徴がある。しかし最大の特徴は使用する ときと同じ形式で型を宣言するという特徴 て、あろう。これは先の構文定義の init ー decla rator ー list の部分の定義による。 たとえば Modula ー 2 て、は「 INTEGER への ポインタ p 」という宣言を , VAR p POINTER TO INTEGER ; と表現する ( ただし , 普通はポインタ型は T YPE て、型名を定義してそれを用いる ) 。これ を C て、は「 int * p 」と表現しているわけて、あ る。この例だけからもわかるように C の表現 はコンパクトてある。 一方 Modula ー 2 の形式は C に慣れていると 冗長に感じてしまう。しかし , 構文をうろ 覚えのプログラマにとってみれば Modula- 2 方式の表現のほうがわかりやすいことはい うまて、もないだろう。 int ()x [ 256 ] ) (void) ; のような宣言を考えると違いは際だってく VAR x : ARRAY [ 0.255 ] OF POINTE RTO PROCEDURE INTEGER ; C 形式は慣れないとまったく意味不明て、あ るが , ModuIa ー 2 の形式て、あれば , おそら、く 少し心得のあるプログラマて、あれば誰て、も 理解てきるのて、はないだろうか。 C の「式の中て、使われる場合と同じ形式て、 宣言する」という方針が宣言を難解なものに
明解 五ロ = = ロ 言座 と定義されています。ここて typdef は型の「同 義語」を作りだす命令て、す。すなわち size t は unsigned ( int ) の同義語となります。 siz e 。 f 演算子が負の値を返すことはありませ ん。 size t はこのように符号なしの整数型と 定義されます。 なぜこのような同義語をわざわざ定義す るのて、しようか ? sizeof 演算子の型は , ほ かの処理系て、は unsigned short int や unsig ned long int かもしれません。しかし , この ように型を定義することによって『すべて がて、きるのは宣言と同時に行う初期化のと の』処理系て、「 size 。 f 演算子の生成する型は きだけて、す。したがって , 配列 s が、、 12345 ク size t 型て、ある」といえます。 を表すように変更したいのて、あれば , strlen 関数の返す型も非負て、す。したがっ s [ 1 ] = sC0]= てその型は ( 符号つきの int 型などて、はなく ) s [ 3 ] = ' 4 ' sC2]= size t 型を返すと決められています。 sC4] = >const のように , 配列の要素にひとつひとつ値を 代入しなければなりません。 c 。 nst は型を修飾するものて、あり , 型修飾 しかし毎回このように代入をするのはと 子 (typequalifier) と呼ばれるもののひとつ てもめんどうて、す。そこて、文字列の複写を て、す。 const は値が変更不可能て、あることを 行う機能を持っ関数を作成しましよう。 指定するものて、す。たとえば , List 7 の strc 叩 y 関数が文字列の複写を行 const int a=5 う関数て、す。ふたつ目の引数に const が指定 次のように宣言・初期化された x と宣言・定義すると a の値は変更不能とな されている理由はもうわかりますね。 int x=3 , り , ほかの値を代入することがて、きなくな を考えましよう。当然なことて、すが , 初期 文字列の複写の様子は Fig. 8 のようになり ります。 化後に ポインタの宣言て、 , 型の前に const 修飾子 ます。 7 行目の for 文の判定は , を指定すると , そのポインタを介して書き 換え不能て、あるということを意味します。 となっています。 たとえば , まず t Ci] を s Ci] に代入します。このよ void f(const char *s) うな代入式はく代入後の左辺の型と値を持 っ > のて、したね。代入後の文字と ' \ 0 ' を比 較することになります。文字列 " 12345 " の て、ヾ ABCD クて、初期化した配列 s に , 7 行目て、 最後の ' \ 0 ' が代入された後の比較て繰り返 しの処理を終了します。したがって , ' \ 0 ' まて、が正しく複写されます。 文字列の複写を行う関数は , 標準ライプ ラリ strcpy として提供されています ( Table こて、関数の戻り型が char * という見慣 x={l, 2 , 3 , 4 , 5 } , れない形式をしています。 char * というの などとはてきませんよね。 このようなこと 明解 AN C 言語入門講座 133 文字列と文字列リテラル 文字列は『ヌル文字 ' 判 ' が出現するまでの文字の並び』といえます。ですから , 以下の 文字列リテラル ” ABC ” ” AB*ODEF ” は , ( 1 ) は文字列ですが , ②は文字列ではありません。なぜならば ' \ 0 ' の後ろにもまだ文 字が続いているからです。 ( 結論 ) 文字列リテラルは文字列とは限らない ! 1 【問題 1 】 strlen 関数を使って次の文 字列リテラル 、、 ABYtC" 、 \ABCYOXYZ" の長さを表示するプログラムを書け。 さらに実行結果を考察せよ。 字列を操作する 文字列の複写 とほかの値を代入てきます。それて、は文字 列に対して同じような操作をするにはどう すればよいて、しようか 0List 6 を見てくださ い。 5 行目 static char sC8]= ABCD 新たに当 2345 〃を代入しようと ” 12345 ” のように s を介して代入などを行おうとする とエラーとなります。このようにプロトタ していますが , これはコンパイル時にエラ ーとなり , 正しくコンパイルて、きません。 イプに const と書かれていると , strlen 関数 どうしてて、しようか ? int 型の配列 x がある を呼びだすプログラマは「ポインタを渡すの だから , これを介して値を書き換えられな として , いだろうか」という余分な心配をしなくて済 みます。
スタートアップ C 十十 実力養成講座 0 List Date クラス内部のデータをポイントとしたときの例 st 10 にあげておきます。 , おまけ 今回の話はこれて、終わりて、す。 先は , ある意味て、はいわば蛇足なのて、 , コ ンストラクタはもうたくさん , という人は 読まなくてもかまいません。 メンバ変数のコンストラクタ あのクラスのインスタンスが , ほかのク ラスのメンバ変数として含まれる場合があ ります。たとえば List 11 ー①のクラスて、す。 こて、は , 環境変数のように < 名前 > と く値 > が一対一対応するデータを String のカ ップルとして保持しようということをもく ろんて、います。この < 名前 > と < 値 > は不 地味な存在て、すが , 「縁の下のカ持ち」と 可分のものて、あり , どちらか片方が初期化 純にビットコピーが行われて , 元の木阿弥 になってしまいます。普通の代入にも , されているのにもう片方は不定 , などとい て、もいうべき大切な役どころて、す。ポイン 則 述のポインタの問題がっきまといます。 うことはありえません。したがって , List 1 タという魅力的な武器を放棄することなく , 1 ー②のように , ふたつをいつべんに初期化 今度は = ( イコール ) 演算子をオーバーロ 安全な初期化を黙って実行してくれるニク するようなコンストラクタが備わってしか ードしなくてはなりません。すなわち , イ奴。く男 > を感じさせます。ただ , その 今月の標語 2 生来の地味さから , なかなか衆目を集める るべきて、す。 「ポインタを持つクラスは , コピーコン というわけにはいかないようて、す。 さて , name, value は String のインスタン ストラクタと代入演算子を定義すべし」 スて、あるのて、 , 初期化するには適切なコン 周囲の C 十十入門者の反応を見ていると , というところて、すが , 演算子オーバーロー ストラクタを呼び出さなくてはなりません。 こて、引っかかる人が一定の割り合いて、い ドの間題もからんて、くるのて、 , 先送りにし これを行うためには , 先ほどの書式を使っ るようて、す。 C 言語て、は , この問題に対する て , List 11 ー③のようにします。 言語仕様レベルの支援が何もなかっただけ ます。 この書式は本来 , メンバのコンストラク 参考まて、に , Date クラス内部のデータを に , かえってトレードオフになっているの タに引数を与える用途に供するためにある ポインタとしたときのコーディング例を Li かもしれません。 とりあえず , 前出の標語をやみくもに守 コピーコンストラクタの例 Li st るだけてもかまいません。いったん実装し てしまえば , あとは意識する必要もないの て、すから。 代入による初期化の問題はクリアされま した。しかし , これて、代入問題が終わった 00 ・て 0 = あ 0 ま・ ! he1102 hello と書かれた場合。これは , 初期化てはなく 普通の代入て、すからコピーコンストラクタ うんぬんとは関係ありません。やつばり単 1 : / / Date class 2 : #include く time. れ〉 3 : 4 : class Date ( tm* date; 5 : 6 : public: Date ( ) 7 : 8 : time_t t = time(O); date ニ new tm(*localtime(&t)) : 10 : 11 : Date() { delete date; } 12 : Date(const Date& s) { date = new tm(*s . date) : } 13 : Date& operator=(const Date& s) { *date 14 : *S. date ; return *th i s return date->tm_year; ) int year() ( int month() ( return date—>tm_mon { return date—>tm_mday int day() 17 : int wday() { return date—>tm_wday int hour() { return date—>tm_hour ・ { return date—>tm_min; int min() 20 : { return date- 〉 tm_sec; int sec() 21 : ① class Env { String name; String value; ② Env: : Env(const char* n, const char* v) ③ class Env { Env(const char* n, const char* v) :name(n), value(v) ( } スタートアップ C 十十 145
mo typedef-name 型修飾子 type-qualifier : const volatile なお , typedef は構文上は記憶クラス指定 子の一員として扱われているが , もちろん これは形式的な文法の取り扱いの便宜上の ものて、しかない。ただし , ひとつの宣言て、 はひとつの記憶クラス指定子しか指定て、き ない。このため , たとえば , typedef extern int extint ; などという宣言はエラーてある。同一宣言 内て、 extern と typedef を両方用いることはて きない , こて、 Fig. 1 の記述から , これらは 3 種類 は任意の順序て組み合わせてよいというこ とに注目しよう。だからたとえば次のふた つは同じ意味てある。 ① extern unsigned long int x ; ② intlong unsigned extern x ; また , 通常 typedef などは次の③のように 書くのが普通てあるが , これまた④のよう な順序てもよいわけてある。 「型指定子」「型修飾子」とはそれぞれ以下の y は int へのポインタの 256 要素の配列を ように規定されている ( ANSI 3.5.1 , 3.5. 返す関数 2 , 3.5.3 ) 。 しかし , 「関数の配列」とか「配列を返す関 数」というものは許されていない。このよう 己憶クラス指定子 に許される宣言と許されない宣言がカッコ storage-class—specifier ひとって簡単に入れ替わってしまうという ty pedef のも C の宣言の怖いところてある。 extern 言の構文は自由度が高い static autO C て、は宣言の構文そのものに比較的高い自 register 由度を持たせていることと , 同一の構文を 多様な用途に用いていることからも混乱は 発生する。 ANSI になって正式に unsigned, signed, const, volatile というキーワードが導入され たことていっそう事態が複雑になった , たとえば , 次の宣言のう は否定てきない ち構文的に正しいものと正しくないものを 分類してみよう。てきるだろうか。 (a) int long signed extern x ; (b) struct point { int x ; int y ; } t ypedef point ; (c) volatile char const * const volat (d) enum { BLUE, GREEN, READ } const static * volatile primary , タネを明かしてしまうとすべて構文的に は正しい。 前回にも少し触れたが , C の宣言 (declar ation ) とはどのような構文になっているかを ANSI< 調べると ,Fig. 1 ( 訳は筆者による ) のように規定されている ( ANSI 3.5 ) 。 前言指定子は , もうすこしわかりやすく 表現すれば以下のようになる。 宣言指定子とは . ・記憶クラス指定子か ・型指定子か ・型修飾子か を少なくともひとつ以ト並べたものであ る。 そしてここて登場する「記憶クラス指定子」 型指定子 type-specifier ・ VOid Char short int long float double signed unsigned struct—or—union—specifier enum-specifier Fig. 1 ANS 槻格における宣言の定義 ( 1 ) decla 「 ation ・ declaration-spec ifie 「 s init-decla ratO 「 -list opt declaration-specifiers . storage—class-specifier decla 「 ation-specifie 「 s type-specifier decla ration-specifie 「 s opt type-qualifier decla ration-specifiers opt 宣言とは : ・宣言指定子の後に省略可能な初期宣言子リストを並べたものである。 宣言指定子とは . ・記憶クラス指定子の後に省略可能な宣言指定子を並べたものか , または ・型指定子の後に省略可能な宣言指定子を並べたものか , または ・型修飾子の後に省略可能な宣言指定子を並べたものである。 opt ANSI C ー more 103
さを求める関数を List 5 に示します。関数頭 部は , int strlength (char * s) て、す。 char *s は chars [ ] と同じ意味て、し た。呼び出し側は , strlength (str) ・ となっています。 [ ] などのつかない , 単な る配列名はその配列の先頭要素へのポイン タとみなされます。配列 str の先頭要素への ポインタすなわち &str [ 0 ] を引数として渡 します。 ポインタを受け取った関数は [ ] 演算子 を適用して , 配列て、あるかのようにアクセ スて、きましたね (Fig. 6 ) 。 i は配列をなぞっていくために使用しま す。 i を 0 に初期化して文字 ' \ 0 ' が出現する まて、 i をインクリメントしていきます。この 様子は Fig. 7 のような具合て、す。 この関数は ( もう何度も説明してきた ) 逐 次探索の関数て、す。最初に ' \ 0 ' が見つかる まて、探索を行い , そのときの添え字をその 文字列の長さとして返します。 さて文字列の長さを求める関数は , 自分 て、作成するまて、もなく標準ライプラリとし て提供されています。 strlen というライプラ リがそれて、す (TabIe 1 ) 。 ここて、 size t と const が本講座て、初めて出 てきました。このふたつのキーワードを解 説しましよう。 >size-t si zeof 演算子が返す型のことて、 , < stddef. h> などて、定義されています。たとえば LSI C ー 86 て、は , typedef unsigned size t ; Fig. 7 文字列の長さを求める 0 1 2 3 4 List 文字列の長さを求める 「」いい一い一り叮叮い ) いい 0 い一い ~ - に ) 叮リリリりいに ~ い ~ いいに。」叮いに一」に ( いに ~ にに = い - リ」に 1 : #include く stdio. h> 2 : 3 : int strlength(char *s) 5 : 6 : 7 : 8 : 9 : 10 : } 11 : 12 : int main(void) 13 : { static char str[8] ニ” ABCD ”・ 14 : printf("str の長さは Xd*n", strlength(str)) : return( の ; 18 : } int i; i 十十 ) return(i) ; Fig. 6 関数呼び出しと引数の授受 s[O] s [ 1 ] s[2] s[3] sC4] s[5] s[6] sC7] “ main 関数 strlength (str); 配列 Str char *S strlength 関数 標準ライプラリ機能概路 (strlen, strcpy, strcat) TabIe 1 strlen #include く string. h> size t strlen(const char *S) s の指す文字列の長さ ( ' ¥ 0 ' は含めない ) を求め , その値 ( 非負の整数 ) を返す strcpy #include く string. h> char * strcpy(char *sl , const char * s2) sl の指す領域に s2 の指す文字列を \ 0 ' を含めて複写する sl の値を返す strcat #include く string. h> char *st 「 cat(char * sl , const char *s2) sl の指す文字列の末尾に , s2 の指す文字列を連結する sl の値を返す 形 式 解 説 形 式 解 説 形 式 解 説 A B C D*O 長さ = 4 132 C MAGAZINE 1991 10
実力養成講座 6 スタートアップ C 十十 設定したい場合には , List 4 のようなコンス トラクタを用意すればいいて、しよう。この とき , String も Date も 2 種類のコンストラク タを持っことになります。どちらが呼び出 されるかは , 宣言の書き方によって決定さ String s , れます。 Date : : Date(int,int,int,int,int,int) もしも Date クラスに らないんだよ , という人もいるて、しようし , インスタンスが生成された時刻なんてい ンスを宣言て、きないのて、す。 引数に完全にマッチする形て、しかインスタ いうと , 用意されているコンストラクタの ラクタが要求されるということて、す。逆に スタンスの宣言にみあった適切なコンスト せんが , ひとって、も定義したら最後 , イン うものは定義しなければしないて、かまいま 注意が必要なのは , コンストラクタとい が呼び出されます。 とすれば , String : : string (const char * ) String s(" HellO world!" ) ; が呼び出されるし , あるいは という宣言て、あれば , String : : String( ) ドしておくことになります。 うに , 空のコンストラクタをオーバー こういう宣言をしたい場合には , List 5 のよ どうしても ( たとえ初期化されなくても ) という宣言は許されなくなります。 Date d ; が実装されていなかった場合 , Date : : Date( ) だけがあって , ロ らずなことをいってはバチが当たります。 して機能するのてすから , ムダなどと恩知 不可欠なクラスの場合にフェイルセーフと ムダに思えるかもしれませんが , 引数が List 2 : 4 : 5 : 6 : List 引数のあるコンストラクタのオーバーロード 1 : class String ( char* str; 3 : public: String() ( str = NULL; } String(const char* s) { str = strcpy(new charCstrlen(s) + 1], String() { delete str; } Date に外部から時刻を設定するとき class Date { Date: :Date( int year, int int daY' int 0 Ⅱ r , int int sec) Date(int year, int mon, int day' int hour• int min' int sec = 0 ) : date. date. date. date. tm sec ま sec; tm_min = min; date. tm—hour = hour; date. tm_mday = day; tm_mon = mon; tm—year = year; Date(int, int, int, int, int, int Date() 0 class Date { List 空のコンストラクタのオーバーロード とかくコンストラクタには , 『訒期値の代入 メンバ変数 に初期値を代入するだけの処理が多くなり ます。このとき , Fig. 1 の書式て , { } の外 にメンバ変数の初期化を置くことがて、きま す。たとえば , List 6 ー①といった調子て す。 コンストラクタて何やら込み入った処理 を行わなくてはならないような場合には , なとえば .- す。 トラクタ自身の引数を与えることもてきま 期値 > には , ・式を置くことがてき , 可読性を高めることがて、きます。 単純な代入処理と分離することによって , コンス この < 初 : String(const char * ) String . の場合は List 6 ー②のようにすることもて、き ます。しかし , このように代入処理そのも のが多少なりとも入り組んて、いる場合は , はたしてこれて可読性が高まることになる のかどうか疑問てす。また , 副作用のある 引数を初期値として与えることも慎んだほ うがよいて、しよう。 ( カンマ ) て区切って書き並べれば , 複 数のメンバを初期化することもてきますが , List 2 の Date クラスのコンストラクタを Li st 7 のようにしてしまうのも考えものだとい う気がします。どちらの書式を採択するか スタートアップ C 十十 143
Fig. 1 コンストラクタの書式 コンストラクタ名 ( 引数 ... ) : メンバ変数名 ( 初期値 ) 実装 ② string: :String(const char* s) str(strcpy(new char[strlen(s) + 1], ① String: :String() str(NULL) List コンストラクタにおける初期化 List Date クラスのコンストラクタ例 1 : Date: :Date(int year, int mon, int day, とえば , i nt わ ou ら int int sec = の 3 : 4 : 6 : 7 : date. tm_year(year) , date. tm_mon(mon)i date. tm_mday (day) , date. tm_hour(hour), date. tm_min(min), date. tm_sec(sec) は < お好み > に任されているのて、 , プログ ラマのセンスがモノをいうて、しよう。 「ゴ目コンストラクタ List 3 の String クラスの例をもう一度見ま しよう。このクラスは , 実は火薬庫て、す。 思わぬバグを誘発する危険性があります。 いったい何が問題なのかというと , インス タンス同士の代入が起こった場合て、す。た List メンバを不定ポインタにされてしまう例 1 : main() String h 02 hello ; という初期化文。ここて、は , he1102 に対して コンストラクタが呼び出されることなく , he110 の中身が丸ごとコヒ。ー ( ビットコヒ。ー ) されます。要するに , 普通の構造体と同じ ことが起こります。て、は , この初期化文が List 8 のような文脈て、出てきた場合には , 何 が起こるて、しようか。なんだか投げやりな 例て、すが , 意図はくみ取ってください ifl ロックの中て e110 の中身カ市 e1102 にビ ットコヒ。ーされます。次に if プロックを抜け る直前に , he1102 のデストラクタが働いて , : str が delete されます。ここて、 , h he1102 : e110 と he1102 の str は同じ値を共有しているの て、すから , he1102 : : str が delete されるとい うことは , he110 : : str が delete されるのと 同じことて、す。 つまり , hello には何の責任もないのに 勝手にメンバを不定ポインタにされてしま うのて、す。軒先を貸して母屋を取られてし まうような哀れな話て、すが , 予期しない悲 惨なバグの原因になるのはおわかりだと思 います。 この問題を回避するためには , List 9 のよ うなコンストラクタをオーバーロードして おかなくてはなりません。すなわち , 同じ クラスのほかのインスタンスを引数にとっ て , 新しいインスタンスを作るためのコン ストラクタて、す。こういう形式のコンスト ラクタが用意されていれば , String he1102 (hello) ; あるいは , String he1102 h 0 のようにして , 完全に別個のインスタンス とすることがて、きますにのふたつの書式に は機能的な違いはありません ) 。 この役割を担うコンストラクタのことを , 一般にコピーコンストラクタと呼びならわ します。 今月の標語 1 「ポインタを持つクラスは , コピー ストラクタを定義すべし」 コン List メンバを不定ポインタにしないためのコンストラクタ 1 : String: :String(const String& s) 3 : 4 : 5 : 6 : 7 : String he110 ( ” He110 world!"); if ( 1 ) { String e1102 = he110 : return 0 : str = strcpy(new charcstrlen(). str) + 1], 3 : S. str); 144 C MAGAZINE 1991 10
strcpy の使用例 文字列を代入する ( ? ) List List 1 : #include く stdio. h> 2 : #include く string. h> 3 : 4 : int main(void) char sl [ 10 ] , s2 [ 10 ] ; 6 : 7 : strcpy(sl, strcpy(s2, ” ABC ” )) ; 8 : printf( ” sl: Xs s2:Xs*n ” , sl, s2); 9 : 10 : strIen(strcpy(s2, ” 12345 ” ) ) ) ; printf( ” Xd*n ” 11 : 12 : return( の : 13 : 14 : } 1 : #include く stdio. h> 3 : int main(void) static char s[8] ー” ABCD ”・ 5 : 6 : ” 12345 ”・ 7 : S 8 : printf(s); return( の ; 9 : 10 : } / * コンパイルエラー * / 文字列を複写する LiSt Fig. 8 文字列の複写 1 : #include く stdio. h 〉 2 : 3 : void strcopy(char *s, const char *t) 5 : int i; 6 : for (i ニ 0 ; (sCi] = tCi]) ! = ' \ 0 ' ・ 7 : 8 : 10 : 11 : int main(void) 12 : { 13 : static char strC8] 14 : strcopy(str, ” 12345 " ) ; 15 : printf(str) ; 16 : return( の ; 0 1 2 3 4 5 i 十十 ) 1 2 3 4 5 \ 0 ↓ ↓ ↓ ↓↓↓ t ” ABCD ”・ s A B C D \ 0 \ 0 \ 0 \ 0 は char へのポインタて、す。つまり , この関 strcpy が char へのポインタを返すことを利 static char sC20]= 'ABCD" 数は char へのポインタを返します。く sl の 用してこのように簡潔に記述て、きるわけて、 と配列 s を 20 文字分の大きさを確保している 値を返します > と説明しているように , str す。く便利 > て、しよう ? 点に注意しましよう。このように文字列の cpy は最初の引数 sl の値をそのまま返しま ための配列を宣言・定義するときは , あら す。引数とまったく同じものを返すメリッ かじめ格納される文字列の最大の大きさを トは何て、しよう ? 結論をいうとく便利 > 予測しておくことが必要となりますにこて それて、は次に文字列を連結する方法につ だからて、す。 はかなり余裕をとっています ) 。 List 8 は strcpy を使用したプログラム例て、 いて考えましよう。文字列の連結とは , す て、に格納されている文字列の後ろに文字列 す。 8 行目は , strcpy (sl, strcpy (s2, "ABC") ) ; を付け加えるということて、す。 となっています。ま istrcpy(s2, "ABC") て、 文字の配列に単純に文字列を代入 ( 複写 ) 文字列のための配列は , 格納される文 て、きなかったのと同様に文字列の連結も単 配列 s2 に、、 ABC" がコピーされます。この 字列が十分に入る大きさを持つように 戻り値は s2 の値を持つ char へのポインタて、 純に行えません。関数を作成しましよう。 あらかじめ定義しなければならない す。それを再び strcpy の引数として渡しま List 9 の strconcat 関数が , 文字列を連結 す。したがって , sl と s2 の両方に、 SABC" する関数て、す。 Fig. 9 に示しているように s それて、は連結の部分を見ていきましよう。 が複写されます。また 11 行目の の指す文字列の後ろに t の指す文字列を連結 文字列を連結するためには , まず現在の文 " 12345 ” ) ) strlen (strcpy (s2, します。、、 ABCD" の後ろに、、 EFGH" を連 字列の終端すなわち ' \ 0 ' を探さなければな て、は配列 s2 に、、 12345 〃を複写して , その文 結します。連結後は ' \ 0 ' も含めて 9 文字の大 りません。その部分が 7 行目の for 文て、す。 きさとなります。 16 行目て、 字列の長さを求めます。 の部分は strlength と処理が似ています。プ 134 C MAGAZINE 1991 10 , 列の連結 重要
明解 IC 言ロロ 入門講座 Fig. 9 文字列の連結 文字列を連結する く stdio. h 〉 1 : #include 2 : const char *t) 3 : char *strconcat(char *S' int i, J; 5 : 6 : for (i 7 : 8 : for (j 9 : 10 : return(s) ; 11 : 12 : } 13 : 14 : int main(void) 15 : { ” ABCD ”・ static char s [ 20 ] 17 : strconcat(s, printf(" 連結後 :Xs" return( の ; 19 : LiSt 0 1 2 3 4 E F G H \ 0 十十 ) A B C D \ 0 S 0 1 2 3 4 5 6 7 8 9 List AN 準拠をテストする 1 : #include く stdio. h 〉 2 : 3 : int main(void) char str1C4] ” ASDF ”・ char str2C6] = ” ASDF ”・ 7 : if (str2C5] ! ニ ' \ 0 ' ) 8 : puts ( " 配列が正しく初期化されていません " ) : 9 : else 10 : puts( ” OK ! ” ) ; 11 : return( の ; 12 : 13 : } ログラムも大体同じようなものて、す。さて ( 2 ) 文字列配列を文 ' \ 0 ' を見つけたら , そこから文字列を複写 字列リテラルで初 していけばよいことになります。これは str 期化するときの長 copy と似た動作を行います。 9 行目のような さの制限 5 行目を見てくださ for 文となります。 文字列の連結を行う関数は , 標準ライプ い。大きさ 4 の配列 st rl を文字列リテラル ラリて、は strcat として提供されています ( T able 1 ) 。この関数も char へのポインタを返 "ASDF" て、初期化 します。 strcpy と同様にく便利 > な使い方が しています。、、 ASDF 〃に ' \ 0 ' を付加すると 化されていますが , 6 文字目すなわち str2 [ 5 ] 5 文字分の大きさを持ちます。この初期化は て、きるのは説明するまて、もないて、しよう。 にも初期値として 0 ( ' \ 0 ' ) が格納されている 一見正しくないように見えます。しかし AN 必要があります。 8 行目以降ては , 正しく初 【問題 2 】文字の配列に格納された文 SI て、は , このような場合 ' \ 0 ' を付加せずに 期値が代入されているかどうかの判断をし 初期化を行うことになっています。すなわ 字列を空 ( 一文字目がいきなり ' \ 0 ' とな ます。 ち strIC0]—strIC3] に 'A', 'S', 'D', 'F る ) にするにはどうすればよいか さて , あなたの処理系て、は正しくコンパ ' が代入され , ' \ 0 ' はどこにも代入されませ イル・動作しましたか ? ん。規格を満たしていなければ , 正常にコ ょたの処理系チェッ とめ ンパイルすることはて、きません。 ( 3 ) 配列の値の指定されていない部分の初期 最後に , 処理系が ANSI 規格に準拠してい 化 6 行目て、配列 str2 の宣言・初期化を行って るかどうかをチェックするプログラムを示 なく います。ここて、初期化する文字列リテラル します 0List 10 のプログラムをコンパイル・ char *s= 'ABCD は ' \ 0 ' を含めて 5 文字てす。しかし前回説明 実行してみてください のようにポインタを利用する方法もありま したように , 配列に指定された初期値の個 ( 1 ) 自動記憶寿命を持つ配列の初期化 す。配列版文字列とポインタ版文字列は似 数が配列の大きさに満たないとき , 初期値 ANSI て、は , 自動記憶寿命を持つ配列を初 て非なるものてす。次回は文字列とポイン の指定されていない部分は 0 て初期化されま 期化することがてきます。この仕様を満た タの関係を解説します。 す。すなわち 6 行目て宣言している str2 は , さない処理系ては , 正常にコンパイルする ' \ 0 ' を含めて 5 文字の文字列リテラルて、初期 ことがて、きません。 文字列を使うには , 今回説明した配列て 明解 ANSI C 言語入門講座 135