Cが参照による変数の受け渡しをサポートしていない場合、なぜこれが機能するのでしょうか。
#include <stdio.h>
void f(int *j) {
(*j)++;
}
int main() {
int i = 20;
int *p = &i;
f(p);
printf("i = %d\n", i);
return 0;
}
$ gcc -std = c99 test.c $ a.exe i = 21
なぜなら、メソッドへのポインタの値を渡してから、それを参照している整数を取得するためです。
これは参照渡しではなく、他の人が述べたように値渡しです。
C言語は例外なく値渡しです。パラメータとしてポインタを渡すことは、参照渡しを意味しません。
規則は次のとおりです。
関数は実際のパラメータ値を変更することはできません。
関数のスカラパラメータとポインタパラメータの違いを見てみましょう。
この短いプログラムは、スカラー変数を使用して値渡しを示します。 param
は仮パラメータと呼ばれ、関数呼び出し時のvariable
は実パラメータと呼ばれます。関数内でparam
をインクリメントしてもvariable
は変更されません。
#include <stdio.h>
void function(int param) {
printf("I've received value %d\n", param);
param++;
}
int main(void) {
int variable = 111;
function(variable);
printf("variable %d\m", variable);
return 0;
}
結果は
I've received value 111
variable=111
コードを少し変更します。 param
は現在ポインタです。
#include <stdio.h>
void function2(int *param) {
printf("I've received value %d\n", *param);
(*param)++;
}
int main(void) {
int variable = 111;
function2(&variable);
printf("variable %d\n", variable);
return 0;
}
結果は
I've received value 111
variable=112
それはあなたがパラメータが参照によって渡されたと信じさせます。そうではありませんでした。それは値によって渡されました、param値はアドレスです。 int型の値が増やされました。これが、参照渡し関数呼び出しであると思わせる副作用です。
その事実をどのように見せたり証明したりできるでしょうか。おそらく、最初のScalar変数の例を試すことができますが、スカラーの代わりにアドレス(ポインタ)を使用します。それが役立つかどうか見てみましょう。
#include <stdio.h>
void function2(int *param) {
printf("param's address %d\n", param);
param = NULL;
}
int main(void) {
int variable = 111;
int *ptr = &variable;
function2(ptr);
printf("ptr's address %d\n", ptr);
return 0;
}
結果は2つのアドレスが等しいということです(正確な値を心配しないでください)。
結果の例:
param's address -1846583468
ptr's address -1846583468
私の意見では、これはポインタが値渡しであることを明確に証明しています。そうでなければ、ptr
は、関数呼び出し後にNULL
になります。
Cでは、参照渡しは、変数のアドレス(ポインタ)を渡し、関数内でそのアドレスを間接参照して実際の変数を読み書きすることによってシミュレートされます。これは「Cスタイル参照渡し」と呼ばれます。
上記のコードには参照渡しがないためです。 (void func(int* p)
のような)ポインタを使うことはアドレス渡しです。これはC++での参照渡しです(Cでは動作しません)。
void func(int& ref) {ref = 4;}
...
int a;
func(a);
// a is 4 now
あなたはポインタ(アドレス位置)値によるを渡しています。
「ここに私があなたに更新して欲しいデータがある場所がある」と言っているようなものです。
pはポインタ変数です。その値はiのアドレスです。あなたがfを呼び出すとき、あなたは値を渡す of p、それはiのアドレスです。
Cではすべてが値渡しです。ポインターを使用すると、変数のvalueが変更されるため、参照渡ししているように見えます。しかし、ポインタ変数のアドレスを表示するのであれば、影響を受けないことがわかります。アドレスの値のコピーが関数に渡されます。以下はそれを説明する抜粋です。
void add_number(int *a) {
*a = *a + 2;
}
int main(int argc, char *argv[]) {
int a = 2;
printf("before pass by reference, a == %i\n", a);
add_number(&a);
printf("after pass by reference, a == %i\n", a);
printf("before pass by reference, a == %p\n", &a);
add_number(&a);
printf("after pass by reference, a == %p\n", &a);
}
before pass by reference, a == 2
after pass by reference, a == 4
before pass by reference, a == 0x7fff5cf417ec
after pass by reference, a == 0x7fff5cf417ec
Cでは参照渡しはありませんが、pはiを "参照"し、pを値渡しします。
変数pへのポインタ(メモリアドレス)を関数fに渡しているからです。言い換えれば、あなたは参照ではなくポインタを渡しています。
短い答え:はい、Cはポインタを使用して参照によるパラメータの受け渡しを実装しています。
パラメータ受け渡しを実装する間、プログラミング言語の設計者は3つの異なる戦略(またはセマンティックモデル)を使用します。つまり、サブプログラムへのデータ転送、サブプログラムからのデータ受信、またはその両方です。これらのモデルは、それに対応して、インモード、アウトモード、およびインアウトモードとして一般に知られています。
これら3つの基本的なパラメータ引き渡し戦略を実装するために、言語設計者によっていくつかのモデルが考案されました。
値渡し(inモードセマンティクス)結果渡し(outモードセマンティクス)結果渡し(inoutモードセマンティクス)参照渡し(inoutモードセマンティクス)名前渡し(inoutモード)セマンティクス)
参照渡しは、inoutモードのパラメータ渡しの2番目の手法です。メインルーチンとサブプログラム間でデータをやり取りする代わりに、ランタイムシステムはサブプログラムのデータへの直接アクセスパスを送信します。この戦略では、サブプログラムは、データをメインルーチンと効果的に共有しているデータに直接アクセスします。この技法の主な利点は、スペースを複製する必要がなく、データのコピー操作がないため、時間とスペースが非常に効率的なことです。
Cでのパラメータ渡し実装:Cは、ポインタをパラメータとして使用して、値渡しおよび参照渡し(inoutモード)のセマンティクスを実装しています。ポインタはサブプログラムに送信され、実際のデータはまったくコピーされません。ただし、ポインタはメインルーチンのデータへのアクセスパスであるため、サブプログラムはメインルーチンのデータを変更することがあります。 CはALGOL68からこの方法を採用しました。
C++でのパラメータ渡しの実装:C++では、ポインタを使用し、参照型と呼ばれる特別な種類のポインタを使用して、参照渡し(inoutモード)のセマンティクスも実装しています。参照型ポインタは、サブプログラム内で暗黙的に間接参照されますが、それらのセマンティクスも参照渡しです。
したがって、ここでの重要な概念は、参照渡しではデータをサブプログラムにコピーするのではなく、データへのアクセスパスを実装することです。データアクセスパスは、明示的に間接参照されたポインタまたは自動間接参照されたポインタ(参照型)にすることができます。
詳細については、Robert Sebesta著 『プログラミング言語の概念』第10版、第9章を参照してください。
Intを参照渡しではなく、intへのポインタを値渡しです。異なる構文、同じ意味.
Cでは、参照渡しするには、変数に対して使用するaddress-of演算子&
を使用しますが、この場合はポインタ変数p
を使用しているので、アドレスの前に接頭辞を付ける必要はありません。 。 &i
をパラメータとして使用した場合は、それは正しいでしょう:f(&i)
。
これを追加してp
を参照解除し、その値がi
とどのように一致するかを確認することもできます。
printf("p=%d \n",*p);
ポインターと参照は2つの異なる記号です。
私が見たことがないことのいくつかは言及した。
ポインタは何かのアドレスです。ポインタは、他の変数と同じように格納およびコピーできます。従ってそれはサイズを有する。
参照は何かの別名として見られるべきです。サイズがなく、保存できません。それは何かを参照しなければなりません、すなわち。 nullや変更はできません。まあ、時々コンパイラは参照をポインタとして格納する必要がありますが、それは実装の詳細です。
参照を使用すると、所有権処理、nullチェック、使用中の参照解除など、ポインタに関する問題は発生しません。
「参照による受け渡し」(ポインターの使用による)は、最初からC言語で行われています。なぜあなたはそうではないと思いますか?
実際、Cは参照渡しをサポートしていると思います。
ほとんどの言語では、値の代わりに参照で渡す構文糖衣が必要です。 (たとえば、C++ではパラメーター宣言に&が必要です)。
Cでは、このために構文糖衣も必要です。パラメーターの型宣言では*、引数では&です。したがって、*および&isは、参照渡しのC構文です。
参照による実際の受け渡しは、引数側ではなく、パラメータ宣言の構文のみを必要とすると主張することができます。
しかし、C#が登場しました。これはdoesを渡すことでサポートされます-andbothパラメーターと引数側で構文シュガーが必要です。
Cがby-refを渡さないという議論は、構文要素が基礎となる技術的実装を表すことを表現するために、すべての実装に多かれ少なかれ適用されるため、まったく引数ではありません。
残っている唯一の引数は、Cでrefを渡すことはモノリシック機能ではなく、2つの既存の機能を組み合わせているということです。 (引数のrefを&で取得し、refを*で入力する必要があります。)たとえば、C#には2つの構文要素が必要ですが、それらを互いに使用することはできません。
これは明らかに危険な議論です。言語の他の多くの機能は他の機能で構成されているためです。 (C++での文字列サポートのような)
あなたがしているのは、参照渡しではなく値渡しです。変数 'p'の値を関数 'f'に送信しているからです(主にf(p);として)。
参照渡しによるCの同じプログラムは、次のようになります(!!!参照渡しによるCはサポートされていないため、このプログラムでは2つのエラーが発生します)
#include <stdio.h>
void f(int &j) { //j is reference variable to i same as int &j = i
j++;
}
int main() {
int i = 20;
f(i);
printf("i = %d\n", i);
return 0;
}
出力:-
3:12:エラー: '&'トークンの前に ';'、 '、'、または ')'が必要[void] f(int&j); ] 9:3:警告:関数 'f'の暗黙の宣言 f(a); ^