• 締切済み

関数のパラメータに配列を渡すときは、非参照型が普通なんですか?

 LippmanのC++プライマー(第4版)を勉強中です。 p.274以降に、配列に作用する関数の定義法と使用法に関する解説があり、p.275に以下の記述があります。 ■配列アーギュメント  配列パラメータも参照型と非参照型がある。  普通、配列は非参照型にする  非参照型パラメータは対応するアーギュメントのコピーで初期化される。配列アーギュメントはその配列の先頭要素へのポインタであり、そのポインタがパラメータにコピーされる。関数はアーギュメントのポインタを変更することはないが、パラメータのポインタを使って配列要素を変更することはできる。 ■配列を参照で渡すこと  配列パラメータを配列への参照にすることもできる。パラメータが配列への参照である場合、コンパイラは配列アーギュメントをポインタに変換しない。配列への参照そのものを渡す。この場合、配列の大きさはパラメータの型の一部である。コンパイラは配列アーギュメントの大きさがパラメータの大きさに一致するかどうかチェックする。 しかしp.268の「ヒント」には以下のように記されています。 ヒント:  Cの素養があるC++プログラマはアーギュメントにアクセスするためにポインタを渡すことに慣れている。 C++では、参照パラメータを使うのが安全かつ自然である。  配列パラメータも参照で渡したほうが、ポインタをコピーしないですむし、配列の大きさを越えてアクセスすることによる実行時エラーも抑止できるので、いいように思いますが、なんで「普通、配列は非参照型にする」んでしょうか?

  • mha01
  • お礼率50% (6/12)

みんなの回答

  • Tasuke22
  • ベストアンサー率33% (1799/5383)
回答No.6

ANo.1です。 私は全然分かっていませんでしたね。 失礼しました。 勉強になります。

mha01
質問者

補足

いえ、とんでもないです。 私もC++が分からないから質問しているわけでして(汗

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

完全に要素数が分からないときには参照にする意味はないです. あと, 要素数がわかっている場合であっても, 今時の普通の実装だと template <std::size_t N> void foo(int (&array)[N}); に対して int a1[5]; int a2[100]; を foo(a1); foo(a2); のように渡すと foo<5> と foo<100> の 2つの実体を作るわけだけど, それってどのくらいうれしいんでしょうか?

mha01
質問者

補足

回答ありがとうございます。 私は今時の普通の実装というものを全然知らないんですが、 int a1[5]; int a2[100]; を foo(a1); foo(a2); のように渡しても、全然うれしくないです。 ただ、 int a1[5]; double a2[100]; ................... ................... .... 中略.......... ................... ................... foo(a1); // foo(int(&)[5])と具現化 foo(a2); // foo(doubleI(&)[100])と具現化 となると、参照を使った方がポインタを使うよりも、多少はうれしいのかなと。 もっとも論点は、Lippmanが、 ポインタと配列はエラーの宝庫だから使うな、 配列を使いたくなったら、ベクトルを使え ポインタを使いたくなったら、参照を使え(もっともベクトルの時はイテレータを使うわけですけど) と強調していたりするのに、配列パラメータの話でいきなり「普通、配列は非参照型にする」とか言うものだから、「あれ?」みたいな感じになっているわけでして。

  • chie65535
  • ベストアンサー率43% (8522/19371)
回答No.4

>なんで「普通、配列は非参照型にする」んでしょうか? 「過去のシガラミ」の為。 下手に仕様変更したら「CからC++に移植する時に、配列を(ポインタのつもりで)引数に渡している部分を、すべて書き替え」しなきゃならない。 >配列の大きさを越えてアクセスすることによる実行時エラーも抑止できるので、いいように思いますが それやられると「コンパイルできないソースコードが山のよう」に出て来るから駄目。 Windowsの某API関数では、ある構造体を typedef struct tagLOGPALETTE { WORD palVersion; WORD palNumEntries; PALETTEENTRY palPalEntry[1]; } LOGPALETTE; とかって定義してて「パレットデータは1個しかないよ」と言いつつ、実際には「パレットデータpalPalEntry[]の要素数はpalNumEntries個ある」などと言う事になってる。 こういう「実際には、定義よりも、配列の実際の要素数が多い」って使い方が良くあるので「参照渡しで要素数のチェックをされたら、移植が大変」になってしまう。 上記の例では「palNumEntriesがもし256なら、palPalEntry[0]~palPalEntry[255]までアクセス可」だったりする。たとえ「palPalEntry[1];」のように「配列要素は1個しかない」と定義されていたとしても。 あと、今は滅多に無いけど、昔のCのソースには「配列の添え字に、負の定数」などと言う、無茶な書き方をしている物もあったりするので、場合によっては「添え字チェックは大きなお世話」だったりします。

mha01
質問者

補足

結局、 「普通は、...」 って書いてあるのは、訳者の舌足らずで、 「通常見かける(昔の)コードでは、」 って解釈すればいいということでしょうか?

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.3

目的に応じた使い分けが大切です。 配列の要素数が事前に分からない場合には、引数を参照型として関数に渡すことができませんので、ポインタで渡す以外にはありません。 一方で、(例えば3次元座標を配列で表す場合のように)確実に要素数が3でなければならない場合には、参照渡しにした方が得策です。 要素数が可変で、かつ要素数の情報も一緒に渡したい場合には、要素数を表す引数を別途渡すか、下記のようにテンプレートを使うしかありません。 template <std::size_t N> void func(int (&array)[N]);

mha01
質問者

補足

>template <std::size_t N> >void func(int (&array)[N]); 結局、要素数が分からない場合は、値テンプレートパラメータを参照で使えば済むわけで、参照で統一したほうがいいのに、何で「普通は...」なんて書いてあるんだという辺りが何でかなぁと思っています。

noname#208124
noname#208124
回答No.2

膨大な配列を非参照型で渡しても配列のコピーなんて作られません どちらにせよあっというまのスタックオーバーフローなんて起きません 普通なんて知りませんが、自分は使い回しづらいと答えておきます

mha01
質問者

補足

 非参照の場合でも、配列のコピーは作られませんが、配列の先頭要素へのポインタのコピーが作られるので、無駄ではないかと思うんです。  また、参照で渡したほうが、配列の大きさ等の付加情報も関数側に知らされるので、配列の大きさに絡んだエラーも起きませんから安全だし、ポインタ絡みの事で注意を払う必要も無いので、良いんじゃないかと。  Stroustrupの下でC++の開発を行ってC++を知り尽くしている著者のLippmanが、C++ではポインタではなく参照を使うようにと推奨しているのに、配列パラメータの場合だけ「普通は非参照にする」と言っているので、何か明確な理由があるのか?、あったら知りたいと思ってます。

  • Tasuke22
  • ベストアンサー率33% (1799/5383)
回答No.1

参照型、非参照型、ですか。私は、値渡し、ポインタ 渡し、と習いました。 引数はスタックを使いますよね。 参照型も非参照型も配列でなければ、まあ同じような サイズを使います。ところが、配列とか文字列とか は膨大なスタックを消費することになります。 これで、再帰関数などにしたら、あっというまにスタッ クオーバになるでしょう。 そんなところに理由があるんじゃないでしょうか。

mha01
質問者

補足

えーと、値渡しとポインタ渡しというのは、C言語の世界の話でして、C++になると『参照』という、C言語にはない概念が出てきます。 なんでこんな概念を導入したかというと、ポインタ絡みのエラーを避けようという目的です。 >引数はスタックを使いますよね。 実引数(アーギュメント)で仮引数(パラメータ)を初期化するときに、実引数のコピーが生成されるのですが、そのコピーオブジェクトがスタックを使うので、膨大な配列とかを値渡ししたら、スタックオーバーになるのです。 参照はポインタの改良版みたいなもので、スタックオーバーにはならないと思うんですけど?

関連するQ&A

  • ポインタの参照

    ある関数でポインタをパラメータとして渡し、呼ばれた関数の中でさらにポインタ参照されたもののメンバを参照するにはどうしたらいいのでしょうか? サンプル   intRet = AAA(&p); ←関数を呼びます。   int AAA(TBL **p) { ↑   このポインタの中身を参照したい } どなたかわかる方いらっしゃいましたらお願いします。

  • android-JNIでクラス配列参照方法について

    androidのJNIで関数引数をクラス配列にした場合、C側ではこのポインタをどのようにすれば取得出来るのでしょうか? 一応GetByteArrayElements()を使用してクラス配列の要素数までは取得できましたが、このクラスのポインタの取得方法が出来ません。 ※クラス配列にある変数をC言語側で参照したいと考えております。 (このクラスはC言語の構造体として扱うようにしています)

  • 関数へのポインタ渡しでの配列の初期化について

    はじめまして、C言語の基本的な質問をさせてください。 C言語で、外部関数へポインタで引数を渡す場合に、 関数に渡されるのはアドレスですよね? で、渡された関数側でそのポインタの配列の初期化を するときにはアドレスだけの情報だと、要素数がいくつ あるか分からず、領域の破壊をしてしまいそうな気が するのですが?いかがでしょうか? また、関数かなんかで、配列の要素数が分かる関数が あったような気がするのですが、それもアドレスだけ でわかるのでしょうか?

  • 配列のポインタ配列のポインタから元の配列を参照する方法について

    C初心者です。下記の様に配列のポインタ配列を作って、そのポインタ配列のポインタを返すコードを書いて、main関数で元の配列の値を参照したいのですが、上手く参照できずに困っています。下記のコードの問題点も含めて、配列のポインタ配列のポインタから、元の配列の値を参照する方法を教えてください。お願い致します。 short int *motion_data(void) { short int data1[5][7] = { {2377,2174,0,0,0,0,0}, {2377,2377,2784,2648,2648,2648,2377}, {2377,2377,2784,2648,2648,2648,2377}, {2377,2377,2377,2377,2377,0,0}, {2377,2377,2377,2377,2377,0,0}, }; short int data2[5][7] = { {2377,2174,0,0,0,0,0}, {2377,2377,2919,2784,2784,2784,2377}, {2377,2377,2919,2784,2784,2784,2377}, {2377,2377,2377,2377,2377,0,0}, {2377,2377,2377,2377,2377,0,0}, }; short int *po_data[2]; po_data[0] = data1[0]; po_data[1] = data2[0]; return *po_data; }

  • ポインタと多次元配列についての質問です

    私の使っている本に、 『ポインタを使って多次元配列にアクセスするには、コンパイラが自動で行っていることを手作業で 行わなければなりません。たとえば、次の配列には各列に5つの要素があります。 float balance[10][5]; したがって、ポインタを使ってbalance[3][1]にアクセスするには、次のようなコードを使用 しなければなりません。 float *p; p = (float *) balance; *(p + (3*5) +1) 目的の要素に到達するには、行番号に行の要素数を掛けてから、その要素の行内での番号を 加えなければなりません。上記の例では、balanceをfloat* にキャストする必要がありました。 配列要素を手作業で指定する都合上、ポインタ演算をfloatポインタに基づいて行わなければなりません。 しかし、balanceによって生成されるポインタの型はfloatの2次元配列です。そこでキャストが 必要になるわけです。』 とあります。 【質問1】なぜ、p = (float *) balance; なのか。p = (float) *balance; ではないのか? 【質問2】本文「上記の例では………必要になるわけです。」の意味がわかりません。 未熟者の私ですがどうか教えてください。

  • 配列ポインタの関数中のメモリ領域

    C初心者です。 関数中で配列ポインタを宣言する場合についての質問です。 たとえばDouble型の2次元のローカルな配列ポインタを用いる場合、 その配列要素数が100である場合は void 関数名(引数1,引数2,...){ int i; double *a[2]; for(i=0;i<2;i++){ a[i] = (double*)malloc(100*sizeof(double)); } for(i=0;i<2;i++){ free(a[i]); } } またこの値を引数1とする場合、引数1をoutとすると void 関数名(double *out,....) とし、 for(i=0;i<2;i++){ out[i] = a[i]; } とすればよいのでしょうか? もしこれがあっているとすると、つぎのような現象で困っています。 配列要素数を50000個ぐらいとし、複数の関数で、同様に mallocを用いて、配列ポインタのローカルでメモリ領域を確保しようとした場合、コンパイルは成功するのですが、その後実行すると、エラーが発生したというメッセージとともにコマンドウィンドが強制終了します。 コンパイラはVisual C++ EXpress Edition 2008です。 データサイズを小さくすると、エラーは起きません。 malloc関数で確保するメモリサイズは、関数の入力引数で定義された変数を用いて計算しており、データサイズに応じて変更されます。 よろしくお願いいたします。

  • C#で、引数にクラスの配列の参照パラメータを渡したい

    C#始めて3日目ですが、そろそろ挫折しそうです。 以下のクラス class Test{ int num; public Test(){ num = 0 }; ~Test(){}; public void set(int n){ num = n; } public int get(){ return num; } } があり、 void kakikae(Te [] t){ for(int i =0; i < 10; i++){ t[i].set(i); } } Test [] test = new Test[10] = { new Test, new Test, new Test, new Test, new Test, new Test, new Test, new Test, new Test, new Test }; kakikae(test); とした時に、test[0]からtest[9]まで 1,2,3,4,5,6,7,8,9 の値が入っている事がしたいのです。 要は、クラスの配列のポインタを渡して、相手側の関数で書き方内容が、関数から戻った時も値渡しでなく 参照渡しのように値が変わって戻ってきて欲しいです。 配列の仕組み、クラスの仕組み、パラメータの仕組みが 分からず、ここから進みません。 1から勉強できるC#の参考書も探しています。

  • C言語で、他の関数で配列を書き換えられないようにしたい

    下のCのプログラムでは、func関数は配列aの先頭要素へのポインタを返します。 main関数の側では配列aの中身を表示します。 しかし、main関数のfor文の中の★の部分をコメントアウトせずに入れると、この配列の中身が書き換わってしまいます。  私はfunc関数以外では、この配列の中身をいじられたくないのです。  なんとかfunc関数を工夫して作成して、func関数以外では、配列の中身が変わらないようにしたいのですが、どうすればよいでしょうか。    とは言ったものの、多分できないだろうなあ、という気がします。  できないならばできないでも仕方ないのですが、確信が持てないのです。 条件があります。 funcでは表示は行なわない。 配列aの中身を表示できるように、funcから呼び出し元へ、aのアドレスまたはaの先頭要素のアドレスがわかるような情報を返す。 #include <stdio.h> char *func(int i) { static char a[]="AAAA"; a[i]='z'; return a; } int main(void) { int i; for(i=0; i<4; i++) { char *p=func(i); /* p[i]='X'; ★配列の中身を書き換えてしまう。 */ puts(p); } return 0; }

  • 配列の要素数を超えた参照のコンパイル

    C言語においては”配列の要素数を超えての参照もコンパイルエラーにはならない”という事がいえます。 例えば int a[10]={1}; とした時、a[-1] a[11]を参照しても、コンパイルエラーにはなりません。不定値が表示されるか、Red Hat Linuxに関しては”セグメンテーション違反です”とでるだけです。 しかし、わたしはCしか学んではいませんので解りませんが、配列の要素数を超えての参照はコンパイルエラーになる言語もあるのではないかと思います。 C言語はよく”暴走する言語”と言われます。規制をできる限り排除して軽くし、ミスはプログラマが取るという意味に考えています。 ”C言語においては配列の要素数を超えての参照もコンパイルエラーにはならないという事”はC言語のその様な設計思想に基ずいた仕様なのでしょうか。 それとも、違う考えに基ずいて、”配列の要素数を超えての参照もコンパイルエラーにはならない”という事に成っているのでしょうか。 宜しく願います。

  • C# 「配列のコピー」と「配列の参照のコピー」の違い。

    C#についての質問です。 今クラスについての勉強をしており、その中で「配列dataのコピーを取得する」メソッドを作成することになり、以下のように記述いたしました。 /// 配列dataのコピーを取得する /// </summary> /// <returns>クラスIntArrayが保持している配列のコピー</returns> public int[] GetArray() { return this.data; } ところが、『「配列のコピー」が行なえていません。これでは、「配列の参照のコピー」です。』という指摘をいただきました。 そこで質問なのですが、「配列のコピー」と「配列の参照のコピー」の違いはどのようなものでしょうか? 独自に調べてもみたのですが、やはりこの2つの違いがいまいちわからず、困っています。 お分かりになる方がいらっしゃいましたら、ご助言をお願いいたします。

専門家に質問してみよう