Lulus dengan referensi dalam C


204

Jika C tidak mendukung melewati variabel dengan referensi, mengapa ini bekerja?

#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;
}

Keluaran:

$ gcc -std=c99 test.c
$ a.exe
i = 21 

22
Di mana dalam kode ini Anda melewati referensi ?
Atul

15
Perlu dicatat bahwa C tidak memiliki referensi, hanya dapat ditiru menggunakan pointer.
Beberapa programmer Bung

6
Pernyataan yang benar adalah "C tidak mendukung secara implisit melewati variabel dengan referensi" - Anda harus secara eksplisit membuat referensi (with &) sebelum memanggil fungsi dan secara eksplisit men-decereference (with *) dalam fungsi.
Chris Dodd

2
Keluaran kode Anda persis sama dengan panggilan f(&i);ini adalah implementasi pass by reference, yang tidak ada murni dalam C. C pass by reference
EsmaeelE

@Someprogrammerdude Melewati pointer adalah melewati-oleh-referensi. Ini tampaknya menjadi salah satu fakta yang membuat programmer "cerdas" C merasa bangga. Seperti mereka mendapatkan tendangan keluar dari sana. "Oh Anda mungkin BERPIKIR C memiliki pass-by-reference tetapi tidak itu sebenarnya hanya nilai dari alamat memori yang dilewati harharhar". Melewati dengan referensi secara harfiah hanya berarti melewati alamat memori tempat variabel disimpan daripada nilai variabel itu sendiri. Itulah yang diizinkan oleh C, dan ini merupakan referensi per detik setiap kali Anda melewati sebuah pointer, karena sebuah pointer adalah referensi ke lokasi memori variabel.
YungGun

Jawaban:


315

Karena Anda melewatkan nilai pointer ke metode dan kemudian mendereferensi untuk mendapatkan integer yang ditunjuk.


4
f (p); -> apakah ini berarti lewat nilai? kemudian dereferencing untuk mendapatkan integer yang ditunjuk. -> bisakah Anda memberikan penjelasan lebih lanjut.
bapi

4
@ Bapi, mendereferensi pointer berarti "mendapatkan nilai yang direferensikan pointer ini".
Rauni Lillemets

Apa yang kita panggil untuk metode memanggil fungsi yang mengambil alamat variabel daripada melewati pointer. Contoh: func1 (int & a). Apakah ini bukan panggilan dengan referensi? Dalam hal ini referensi benar-benar diambil dan dalam hal pointer, kita masih melewati nilai karena kita hanya melewati pointer dengan nilai.
Jon Wheelock

1
Saat menggunakan pointer, fakta kuncinya adalah bahwa salinan pointer dilewatkan ke fungsi. Fungsi kemudian menggunakan pointer itu, bukan yang asli. Ini masih nilai-per-nilai, tetapi berhasil.
Danijel

1
@Danijel Dimungkinkan untuk melewatkan pointer yang bukan salinan apa pun ke panggilan fungsi. Misalnya, memanggil fungsi func: func(&A);Ini akan meneruskan pointer ke A ke fungsi tanpa menyalin apa pun. Ini adalah nilai per nilai, tetapi nilai itu adalah referensi, jadi Anda 'meneruskan variabel' variabel A. Tidak perlu menyalin. Ini valid untuk mengatakan itu hanya sebagai referensi.
YungGun

124

Itu bukan pass-by-reference, yaitu pass-by-value seperti yang dinyatakan orang lain.

Bahasa C adalah pass-by-value tanpa kecuali. Melewati pointer sebagai parameter tidak berarti lulus dengan referensi.

Aturannya adalah sebagai berikut:

Suatu fungsi tidak dapat mengubah nilai parameter aktual.


Mari kita coba melihat perbedaan antara parameter skalar dan pointer dari suatu fungsi.

Variabel skalar

Program singkat ini menunjukkan nilai per nilai menggunakan variabel skalar. paramdisebut parameter formal dan variablepada pemanggilan fungsi disebut parameter aktual. Catatan kenaikan paramfungsi tidak berubah 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;
}

Hasilnya adalah

I've received value 111
variable=111

Ilusi pass-by-reference

Kami mengubah sedikit kode. paramadalah pointer sekarang.

#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;
}

Hasilnya adalah

I've received value 111
variable=112

Itu membuat Anda percaya bahwa parameter dilewatkan oleh referensi. Bukan itu. Itu diteruskan oleh nilai, nilai param menjadi alamat. Nilai tipe int bertambah, dan itulah efek samping yang membuat kita berpikir bahwa itu adalah panggilan fungsi pass-by-reference.

Pointer - melewati nilai

Bagaimana kita bisa menunjukkan / membuktikan fakta itu? Yah, mungkin kita bisa mencoba contoh pertama variabel Scalar, tetapi alih-alih skalar kita menggunakan alamat (pointer). Mari kita lihat apakah itu bisa membantu.

#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;
}

Hasilnya adalah bahwa kedua alamat tersebut sama (jangan khawatir tentang nilai pastinya).

Contoh hasil:

param's address -1846583468
ptr's address -1846583468

Menurut pendapat saya ini membuktikan dengan jelas bahwa pointer dilewatkan oleh nilai. Kalau tidak ptrakan NULLsetelah pemanggilan fungsi.


69

Dalam C, Pass-by-reference disimulasikan dengan mengirimkan alamat variabel (pointer) dan mendereferensi alamat tersebut dalam fungsi untuk membaca atau menulis variabel aktual. Ini akan disebut sebagai "pass-by-reference gaya C".

Sumber: www-cs-students.stanford.edu


50

Karena tidak ada pass-by-reference dalam kode di atas. Menggunakan pointer (seperti void func(int* p)) adalah pass-by-address. Ini adalah referensi per detik dalam C ++ (tidak akan berfungsi dalam C):

void func(int& ref) {ref = 4;}

...
int a;
func(a);
// a is 4 now

1
Saya suka jawaban pass-by-address . Lebih masuk akal.
Afzaal Ahmad Zeeshan

Alamat dan referensi sama dalam konteks ini. Tetapi Anda dapat menggunakan istilah-istilah itu untuk membedakan keduanya, hanya saja tidak jujur ​​dengan makna aslinya.
YungGun

27

Contoh Anda berfungsi karena Anda meneruskan alamat variabel Anda ke fungsi yang memanipulasi nilainya dengan operator dereference .

Sementara C tidak mendukung tipe data referensi , Anda masih bisa mensimulasikan melewati-oleh-referensi dengan secara eksplisit melewati nilai pointer, seperti dalam contoh Anda.

Tipe data referensi C ++ kurang kuat tetapi dianggap lebih aman daripada tipe pointer yang diwarisi dari C. Ini akan menjadi contoh Anda, disesuaikan untuk menggunakan referensi C ++ :

void f(int &j) {
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

3
Artikel Wikipedia itu tentang C ++, bukan C. Referensi ada sebelum C ++ dan tidak bergantung pada sintaksis C ++ khusus.

1
@Roger: Poin bagus ... Saya menghapus referensi eksplisit ke C ++ dari jawaban saya.
Daniel Vassallo

1
Dan artikel baru itu mengatakan "referensi sering disebut pointer" yang tidak sesuai dengan jawaban Anda.

12

Anda melewati sebuah pointer (alamat lokasi) berdasarkan nilai .

Itu seperti mengatakan "inilah tempat dengan data yang saya ingin Anda perbarui."


6

p adalah variabel pointer. Nilainya adalah alamat i. Saat Anda memanggil f, Anda meneruskan nilai p, yang merupakan alamat i.



5

Dalam C semuanya pass-by-value. Penggunaan pointer memberi kita ilusi bahwa kita melewati referensi karena nilai variabel berubah. Namun, jika Anda mencetak alamat variabel pointer, Anda akan melihat bahwa itu tidak terpengaruh. Sebuah salinan dari nilai dari alamat dilewatkan-in ke fungsi. Di bawah ini adalah cuplikan yang menggambarkan hal itu.

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

4

Karena Anda melewatkan pointer (alamat memori) ke variabel p ke dalam fungsi f. Dengan kata lain Anda melewatkan pointer bukan referensi.


4

Jawaban singkat: Ya, C mengimplementasikan parameter passing dengan referensi menggunakan pointer.

Saat mengimplementasikan parameter passing, perancang bahasa pemrograman menggunakan tiga strategi yang berbeda (atau model semantik): mentransfer data ke subprogram, menerima data dari subprogram, atau melakukan keduanya. Model-model ini umumnya dikenal sebagai dalam mode, mode keluar, dan mode keluar, sesuai.

Beberapa model telah dirancang oleh perancang bahasa untuk menerapkan tiga strategi pengoperan parameter dasar ini:

Pass-by-Value (dalam mode semantik) Pass-by-Result (semantik mode out) Pass-by-Value-Result (semantik mode inout) Pass-by-Reference (semantik mode inout) Pass-by-Name (mode inout) semantik)

Pass-by-reference adalah teknik kedua untuk passing parameter mode keluar. Alih-alih menyalin data bolak-balik antara rutin utama dan subprogram, sistem runtime mengirim jalur akses langsung ke data untuk subprogram. Dalam strategi ini, subprogram memiliki akses langsung ke data yang secara efektif berbagi data dengan rutin utama. Keuntungan utama dengan teknik ini adalah sangat efisien dalam waktu dan ruang karena tidak perlu menduplikasi ruang dan tidak ada operasi penyalinan data.

Implementasi passing parameter dalam C: C mengimplementasikan semantik nilai demi nilai dan juga semantik (inout mode) dengan menggunakan pointer sebagai parameter. Pointer dikirim ke subprogram dan tidak ada data aktual yang disalin sama sekali. Namun, karena pointer adalah jalur akses ke data rutin utama, subprogram dapat mengubah data dalam rutinitas utama. C mengadopsi metode ini dari ALGOL68.

Implementasi passing parameter dalam C ++: C ++ juga mengimplementasikan semantik pass-by-reference (mode keluaran) menggunakan pointer dan juga menggunakan pointer jenis khusus, yang disebut tipe referensi. Pointer tipe referensi secara implisit direferensikan di dalam subprogram tetapi semantik mereka juga lulus-oleh-referensi.

Jadi konsep utama di sini adalah pass-by-reference mengimplementasikan jalur akses ke data alih-alih menyalin data ke dalam subprogram. Jalur akses data dapat berupa pointer dereferensi secara eksplisit atau pointer dereferensi otomatis (tipe referensi).

Untuk info lebih lanjut, silakan merujuk ke buku Concepts of Programming Languages ​​oleh Robert Sebesta, 10th Ed., Bab 9.


3

Anda tidak melewatkan int dengan referensi, Anda melewati nilai pointer-to-an-int. Sintaks berbeda, artinya sama.


1
+1 "Sintaks berbeda, artinya sama." .. Jadi hal yang sama, karena maknanya lebih penting daripada sintaksis.

Tidak benar. Dengan menelepon void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }dengan int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, ia mencetak 111 bukannya 500. Jika Anda melewati referensi, ia akan mencetak 500. C tidak mendukung parameter lewat dengan referensi.
Konfle Dolex

@ Confle, jika Anda secara sintaksis memberikan referensi, ptr = &newvalueakan dianulir. Terlepas dari perbedaannya, saya pikir Anda menunjukkan bahwa "makna yang sama" tidak sepenuhnya benar karena Anda juga memiliki fungsi tambahan dalam C (kemampuan untuk menetapkan kembali "referensi" itu sendiri).
xan

Kami tidak pernah menulis sesuatu seperti ptr=&newvaluejika dilewatkan dengan referensi. Sebagai gantinya, kita menulis ptr=newvalueDi sini adalah contoh dalam C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }Nilai parameter yang dilewatkan ke func () akan menjadi 500.
Konfle Dolex

Dalam kasus komentar saya di atas, tidak ada gunanya melewati param dengan referensi. Namun, jika param adalah objek, bukan POD, ini akan membuat perbedaan yang signifikan karena setiap perubahan setelah param = new Class()di dalam fungsi tidak akan berpengaruh bagi pemanggil jika dilewatkan oleh nilai (pointer). Jika paramdilewatkan dengan referensi, perubahan akan terlihat oleh penelepon.
Konfle Dolex

3

Dalam C, untuk lulus dengan referensi Anda menggunakan alamat-operator &yang harus digunakan terhadap variabel, tetapi dalam kasus Anda, karena Anda telah menggunakan variabel pointer p, Anda tidak perlu awalan dengan alamat-operator. Itu akan menjadi benar jika Anda digunakan &isebagai parameter: f(&i).

Anda juga dapat menambahkan ini, ke dereferensi pdan melihat bagaimana nilai itu cocok i:

printf("p=%d \n",*p);

Mengapa Anda merasa perlu mengulang semua kode (termasuk blok komentar itu ..) untuk memberitahunya bahwa ia harus menambahkan printf?

@ Neil: Kesalahan itu diperkenalkan oleh edit @ William, saya akan membalikkannya sekarang. Dan sekarang jelas bahwa tommieb hanya sebagian besar benar: Anda dapat menerapkan & ke objek apa pun, bukan hanya variabel.

2

pointer dan referensi adalah dua hal yang berbeda.

Beberapa hal yang belum saya lihat disebutkan.

Pointer adalah alamat sesuatu. Pointer dapat disimpan dan disalin seperti variabel lainnya. Dengan demikian memiliki ukuran.

Referensi harus dilihat sebagai ALIAS sesuatu. Itu tidak memiliki ukuran dan tidak dapat disimpan. Ini HARUS referensi sesuatu, yaitu. itu tidak bisa nol atau diubah. Yah, kadang-kadang kompiler perlu menyimpan referensi sebagai pointer, tetapi itu adalah detail implementasi.

Dengan referensi Anda tidak memiliki masalah dengan pointer, seperti penanganan kepemilikan, pemeriksaan nol, de-referensi penggunaan.


1

'Lulus dengan referensi' (dengan menggunakan petunjuk) telah ada di C dari awal. Menurut Anda mengapa tidak?


7
Karena secara teknis tidak lewat referensi.
Mehrdad Afshari

7
Melewati nilai pointer tidak sama dengan pass-by-reference. Memperbarui nilai j( tidak *j ) di f()tidak berpengaruh pada idalam main().
John Bode

6
Secara semantik sama dengan melewati referensi, dan itu cukup bagus untuk mengatakan itu lewat referensi. Benar, Standar C tidak menggunakan istilah "referensi", tetapi itu tidak mengejutkan saya juga bukan masalah. Kami juga tidak berbicara bahasa standar di SO, meskipun kami mungkin merujuk ke standar, jika tidak kita tidak akan melihat ada yang berbicara tentang nilai (Standar C tidak menggunakan istilah).

4
@ Jim: Terima kasih telah memberi tahu kami bahwa Anda yang menaikkan komentar John.

1

Saya pikir C sebenarnya mendukung pass by reference.

Sebagian besar bahasa membutuhkan gula sintaksis untuk lulus dengan referensi alih-alih nilai. (C ++ misalnya membutuhkan & dalam deklarasi parameter).

C juga membutuhkan gula sintaksis untuk ini. Itu * dalam deklarasi tipe parameter dan & pada argumen. Jadi * dan & adalah sintaks C untuk pass by reference.

Orang sekarang dapat berpendapat bahwa pass by reference sebenarnya hanya memerlukan sintaks pada deklarasi parameter, bukan pada sisi argumen.

Tapi sekarang datang C # yang melakukan dukungan dengan referensi yang lewat dan membutuhkan gula sintaksis pada kedua parameter dan argumen sisi.

Argumen bahwa C tidak memiliki by-ref menyebabkan elemen sintaksis untuk mengekspresikannya menunjukkan implementasi teknis yang mendasarinya bukanlah argumen sama sekali, karena ini berlaku lebih atau kurang untuk semua implementasi.

Satu-satunya argumen yang tersisa adalah bahwa melewati oleh ref dalam C bukan fitur monolitik tetapi menggabungkan dua fitur yang ada. (Ambil ref argumen oleh &, harapkan ref untuk mengetikkan dengan *.) C # misalnya memang membutuhkan dua elemen sintaksis, tetapi mereka tidak dapat digunakan tanpa satu sama lain.

Ini jelas argumen yang berbahaya, karena banyak fitur lain dalam bahasa yang terdiri dari fitur lain. (seperti dukungan string dalam C ++)


1

Apa yang Anda lakukan adalah nilai demi nilai, bukan referensi. Karena Anda mengirim nilai variabel 'p' ke fungsi 'f' (utama sebagai f (p);)

Program yang sama dalam C dengan pass by reference akan terlihat seperti, (!!! program ini memberikan 2 kesalahan karena pass by reference tidak didukung dalam C)

#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;
}

Keluaran:-

3:12: kesalahan: diharapkan ';', ',' atau ')' sebelum '&' token
             batal f (int & j);
                        ^
9: 3: peringatan: pernyataan implisit fungsi 'f'
               f (a);
               ^
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.