- ベストアンサー
配列とその要素数をメンバにもつ構造体
C言語では,配列を引数とするときに,合わせて要素数を渡さなければいけないといわれます. そこで,配列とその要素数をメンバにもつ構造体を定義して,これを要素数つき配列のように扱えば,引数として渡す場合には,この構造体を渡すだけでよいと思うのですが,このような用法は一般的に用いられているでしょうか. 単純なアイデアだと思うんですが,あまり紹介されているのを見たことがありません. もしあまり用られない用法であれば,その理由を教えてください. 現在,プログラムを書いているのですが,引数の多さに閉口しており,上の考えで,引数を減らしたいと考えています. よろしくお願いいたします.
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
>>C言語では,配列を引数とするときに,合わせて要素数を渡さなければいけないといわれます. 「要素数を渡さなければいけない」という表現は誤解を生むと思います。渡したほうが楽なときもあるけど、渡さないでもいいです。(そういう言い方の文献って読んだことありません)そして、演習等で作るような小さなソースコードでは、そういう用法も使いますが、業務で使うようなソースでは、構造体で渡すほうが一般的です。これは、「引数が減る」という面もありますが、「引数が増えた、引数の属性が変更になった」等の変更による影響が少なくなるからです。 >>単純なアイデアだと思うんですが,あまり紹介されているのを見たことがありません. それは、一般的に「C言語学習用」として現在書店で売られている書籍のレベルが低いからです。ソースで「引数の多さに閉口」っていうようになるのは、(作り方の問題があるかもしれないけど)それなりに複雑な処理を行わせようとしているのだと思います。が、書籍では、そういった例題を出すと、ソースコードのページが増えるし、説明もやっかいになります。 つまり、「教科書」に実務の複雑さを持ち込む必要はないし、かえって有害であると考えているんだと思います。もしかすると、そうした「複雑なソース」を著者たちが書いた事が無いという可能性もあります。 C言語で書かれた業務用ソースを目にする機会は、著作権や機密保護の観点から一般ではほとんど無いと思いますが、一番いいのは、そういったもので学習することかもしれません。 お金に余裕があれば、海外からアプリをソースコードで購入する方法もあります。でも、金銭的な余裕が無い場合、オープンソースのデータベースやCコンパイラのソースコードを参考にされればいいと思います。 ちなみに私の場合は、アメリカからデータベースのソースを購入して日本語化を施したり、その作業中に日本語がうまく通らないため、GNUのCコンパイラに修正を施す等の作業を通じてC言語を学習しました。(お、いずれもローカライズの作業だ) P.S. 以前は、中上級クラスのC言語の本がいくらかあった気もするんですが、どうも最近は初級・中級クラスばかりのような気が・・・。まあ、「専門書は売れない」というのはどの業界でも同じかなあ。
その他の回答 (4)
- sakusaker7
- ベストアンサー率62% (800/1280)
どなたもこの点に関しては触れていないようなので。 struct Array {int max; int body[ARRAYMAX];} のように「配列」を 構造体要素に持たせると、この型を引数や戻り値にしたときに操作する メモリ領域(≒スタック領域)がそれだけ大きくなります。 古のCでは構造体を戻り値や引数に使うことができなかったので (構造体へのポインタを使うのは問題ない)伝統的にこのような手法は 使われてこなかったのでしょう。 gawk という GNU によるawk処理系では、文字列を {int length; char *stptr;} のような構造体で管理しています(本当はもっと複雑ですが説明のため 簡単にしました)。 引数の多さに関してはまあプログラムを見てみないとなんとも。 たとえばWindows のAPIで描画のためにフォントを用意する関数は引数の数が14個あります(構造体を使うようにした類似バージョンもありますが)。 X Window system の描画関連も結構引数は多いものはあります。
お礼
>・・「配列」を構造体要素に持たせると、・・メモリ領域(≒スタック領域)がそれだけ大きくなります。 これも有益なご指摘です。 これから構造体を使うことが増えそうですが、サイズが大きいときには、構造体へのポインタを使うようにしたいと思います。 >gawk という ・・・ gawk も浅薄な使用をしていますが、内部の処理まで考えたことがなかったので、おもしろい話です。 また、要素数+配列の構造体が普通に使われていることを、改めて確認できました。 >引数の多さに関しては・・・ 残念ながらというか、作っているプログラムは xterm などでコマンドライン上で用いるものです。Windows のAPI や X Window System を扱うような複雑なものでないです。 (途中、計算結果を gnuplot に投げてプロットする部分があり、これも引数が多い関数の一つだったりしますが) やはり、設計のやり直しをしたほうが良さそうです。 URL で示していただいた例は、おもしろい例でした。 どうもありがとうございました。
- jacta
- ベストアンサー率26% (845/3158)
一応参考までに回答します。 > C言語では,配列を引数とするときに,合わせて要素数を渡さなければいけないといわれます. 状況によります。 例えば、要素数が常に固定の場合、敢えて要素数を引数で渡す必要はありません。間違いを最小限に抑えるため、 void func(int a[10]); といった書き方をするのもよいでしょう。しかし、これではコンパイラのチェックは全く期待できないので、代わりに、 void func(int (*a)[10]); にした方がやや安全かもしれません。 もし、関数の利用者がaが配列であることを意識しなくてもよいのであれば、 typedef int something[10]; void func(something a); とすることで、ほぼ間違いがなくなります。 次に配列の要素が可変の場合ですが、要素数ではなく、配列の先頭へのポインタと、終端より一つ先へのポインタを渡す方法もよく使われます。具体的には、 int a[10]; func(a, a+10); といった感じです。 場合によっては、この方が融通が利くことがあります。 構造体でポインタと要素数を管理する方法もよく使われますが、それは引数に渡すためというよりは、配列と要素数を常にセットで扱いたいためだと思います。引数のためだけに専用の構造体を組み立てるのは面倒なだけです。 できれば、引数にしようがしまいが、その構造体を使って管理するようにすべきです。そうすると、その構造体を操作するための関数群が生まれ、次第にカプセル化が進んできます。 もう一歩進めて、ヘッダファイルでのその構造体の宣言を不完全宣言だけにすれば、情報隠蔽も実現することができます。
お礼
>コンパイラのチェックは全く期待できないので、代わりに、 >void func(int (*a)[10]); >にした方がやや安全かもしれません。 コンパイラの仕組みにまで配慮したプログラミングをあまりしたことがないので、こういう視点からのご指摘は、新鮮で勉強になります。 >typedef int something[10]; こういう typedef の使い方は知りませんでした。活用したいと思います。 >int a[10]; >func(a, a+10); これも場合によって、便利に使えそうですね。 ターミネータを使う方法と共に、あまり使ったことのない方法です。 最後のお話は ANo.3の方とともに、「オブジェクト実装」の一端だと思いますが(いや、よく分かっていないのですが)、とても惹かれる考えです。 保守性や拡張性の向上という期待から、飛びつきそうですが、私の場合、それ以前に改善すべき点がたくさんあります. とても勉強になりました。 どうもありがとうございました。
- galluda
- ベストアンサー率35% (440/1242)
がると申します。ちぃと色々な観点から。 まず「配列を引数とするときに,合わせて要素数を渡さなければいけない」ですが、概ねYesです。配列そのものは「要素数を含まないデータ」なので、何らかの形で要素数を把握する必要があるので。 ただ、もしこれが「ポインタの配列」ですと、最後にNULLを入れておいて、これをターミネータとして用いるって事はよくありますが。 「配列とその要素数をメンバにもつ構造体を定義して,これを要素数つき配列のように扱えば」については…知っているかぎりだと「割合よく用いられる手法」です。このあたりは業務系の系列にもよるのでしょうが。 ただ、構造体の内容を「動的にする」手法は、なれないとちょっとだけ難しいので。もしかするとそのあたりを「難しい」と感じるような現場だと、あまり用いられないのかもしれません。 あと。構造体の中にある構造体(俗に言う「構造体のネスト」)は、便利ですが同時に要注意なポイントでもあります。 大体…3~4ネスト以上だと、可視性が一気に落ちますので。知っているかぎりで7ネストというものがありましたが…追いかけるのに辟易とした記憶があります(苦笑 引数の多さについては………構造化でCで書いているかぎり、どうしてもある程度避けられない部分があると思います。 ただ、意識してできるだけ「減らそうとする」ことは大切なのではないか、と思います。そうすると「よりきれいな設計」が意識できるようになるので(このあたりは#2さんもおっしゃっている部分ですね)。 で。このあたりが見えてくると、ゆっくりと「オブジェクト指向」が見えてくるのかなぁと思うのが個人的雑感です。 Cを学ばれているのであれば、大変ではあるのですが「C言語でオブジェクト実装」をかけると、かなりいろいろと勉強になるかと思います。
お礼
>「ポインタの配列」ですと、最後にNULLを入れておいて、これをターミネータとして用いる なるほど、こういうやり方もあるんですね。 今つくっているプログラムの中にも、要素数を明示的に扱う必要がない/扱はない方が便利、というものもあると思います。 例えば、一時的に(添え字の最大値が決まった)行列を定義して、処理過程で(添え字の最大値を超えない範囲で)サイズが伸び縮みする。最後に全て捨てる。というものがあります。 その時々の要素数を管理してやりくりしていましたが、ターミネータをうまく使うとスマートかもしれないと思いました。 (一度、realloc を多用するソースを書きましたが、処理に時間がかかり、かつメモリ管理が複雑になるので破棄したりしました) >構造体の内容を「動的にする」・・・ >「構造体のネスト」は、・・・大体…3~4ネスト以上だと、可視性が一気に落ちます データの項目名(複数)をまとめた構造体を、動的に確保して使っておりますが、これもデータを格納した2次元配列、要素数と一緒にまとめてしまおうと考えています。 そこで、構造体の多すぎるネストが可視性を下げる、というご指摘はたいへん有益です。 > 引数の多さについては………構造化でCで書いているかぎり、どうしてもある程度避けられない部分があると思います。 仕様の拡大に伴って引数に増やす、あるいは、ほぼ同じだが少し異なる複数のサブルーチンを一つにまとめる際、異なる部分を制御するためのパラメータを無計画に入れるなど、私の場合はむしろ設計が悪いです。 >・・「C言語でオブジェクト実装」・・ 関数のポインタなどを使うことになるでしょうか。(そういうものがあると知ってるだけで、使ったことありません) 今は、後日のお楽しみということにしたいと思います。 どうもありがとうございました。
- charmer29-2
- ベストアンサー率25% (41/159)
・引き数が多い *関数分割に失敗している 関数は依存関係(引き数や戻り値)が少なくなるように分割するべき。 *データ設計が巧くない 一まとめにできる(するべき)データは初めから構造体にするべき。 #ちょっとしたプログラムでは構造体の中に構造体があるなんてことはざらです。 ・配列を渡すには要素数は必要? char配列においてナル文字を終端とするように、適当な値を終端値として使えば要素数を渡す必要はありません。 #お勧めはしませんが。 ・ノウハウが紹介されていない 本を探しきれていないか、資料を探せていないのでしょう。 ソースを拾えるサイトは検索すればすぐ見つかりますし、 Unix系OSではフリーのソフトはソース公開が原則です。 各種ライブラリもソースが公開されていますね。
お礼
ご回答ありがとうございます。 ご指摘のとおり、引数が多いことに対して、対症療法的に解決しようとした面がありました。 例えば最も引数の多い関数のひとつは、引数の数が合計8個もあります。 設計が悪いというご指摘は、ごもっともです。 >構造体の中に構造体があるなんてことはざらです。 特に質問に即して、配列とその要素数をメンバにもつ構造体も、めずらしくないと理解しました。 ”配列を渡すには要素数は必要”は誤った文章でした。 例として挙げられた char配列は書いていただいたように使っています。 >本を探しきれていないか、資料を探せていないのでしょう。 質問に書いた用法が紹介されている記事を、探すことができませんでした。 また公開されているソースから、用法が広く用いられていることを直接確認することはしませんでした。 公開されているソースを読んでいないことが悪いとうことを痛感しました。 ある現象を調べるのための数値計算のプログラムを作っているのですが、目的に対して、プログラムの作成が副次的なものになりがちで、どうしても勉強がゆきとどきません。 今回は、プログラマーの方のご意見が聞けて、たいへん勉強になりました。 どうもありがとうございました。
お礼
わぁ!詳細なご説明ありがとうございます。 ”渡さなければいけない”は誤解を招く表現でした。申し訳ありません。 構造体で渡すほうが一般的ということをお聞きして、積極的に活用したいと思います。 教科書についてですが、私はようやく今、カーニハンの本を必要に応じて読むというレベルです。これから中上級の本も探してみようと思います。 コンパイラのソースを読むという発想自体、私にはたいへんなことですが、さらにその修正を通じてCを学ぶというのはすごい学習方法ですね。お勧めのとおり、暇をみて GNU C のソースを読んでみたいと思います。 どうもありがとうございました。
補足
ご回答に対する補足でなく、お礼の補足ですが、構造体を使うことで保守性が向上するということは、有用なお言葉でした。 どうもありがとうございました。