Đi qua tham chiếu trong C


204

Nếu C không hỗ trợ chuyển một biến bằng tham chiếu, tại sao điều này hoạt động?

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

Đầu ra:

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

22
Trường hợp trong mã này bạn đang chuyển tham chiếu ?
Atul

15
Cần lưu ý rằng C không có thông qua tham chiếu, nó chỉ có thể được mô phỏng bằng cách sử dụng con trỏ.
Một số lập trình viên bảnh bao

6
Báo cáo kết quả đúng là "C không hỗ trợ ngầm đi qua một biến bằng cách tham khảo" - bạn cần phải tạo ra một cách rõ ràng tài liệu tham khảo (với &) trước khi gọi hàm và rõ ràng dereference nó (với *) trong hàm.
Chris Dodd

2
Đầu ra mã của bạn chính xác bằng khi gọi f(&i);đây là triển khai truyền bằng tham chiếu, không tồn tại hoàn toàn trong C. C vượt qua tham chiếu
EsmaeelE

@Someprogrammerdude Truyền con trỏ là tham chiếu. Đây dường như là một trong những sự thật mà các lập trình viên C "hiểu biết" tự hào về họ. Giống như họ bị đá ra khỏi nó. "Ồ bạn có thể NGHINK C có tham chiếu qua nhưng thực ra không phải chỉ là giá trị của một địa chỉ bộ nhớ được truyền qua harharhar". Truyền theo tham chiếu theo nghĩa đen chỉ có nghĩa là chuyển địa chỉ bộ nhớ nơi lưu trữ một biến chứ không phải chính giá trị của biến đó. Đó là những gì C cho phép và nó là tham chiếu qua mỗi khi bạn vượt qua một con trỏ, bởi vì một con trỏ là một tham chiếu đến một vị trí bộ nhớ biến.
YungGun

Câu trả lời:


315

Bởi vì bạn đang truyền giá trị của con trỏ cho phương thức và sau đó hủy bỏ nó để lấy số nguyên được trỏ tới.


4
f (p); -> điều này có nghĩa là vượt qua giá trị? sau đó hủy bỏ nó để lấy số nguyên được trỏ đến. -> bạn có thể vui lòng giải thích thêm.
bapi

4
@bapi, hủy bỏ một con trỏ có nghĩa là "lấy giá trị mà con trỏ này đang tham chiếu".
Rauni Lillemets

Những gì chúng ta gọi cho phương thức gọi hàm lấy địa chỉ của biến thay vì truyền con trỏ. Ví dụ: func1 (int & a). Đây không phải là một cuộc gọi bằng cách tham khảo? Trong trường hợp này, tham chiếu thực sự được thực hiện và trong trường hợp con trỏ, chúng ta vẫn truyền theo giá trị vì chúng ta chỉ truyền con trỏ theo giá trị.
Jon Wheelock

1
Khi sử dụng con trỏ, thực tế chính là bản sao của con trỏ được truyền vào hàm. Hàm sau đó sử dụng con trỏ đó, không phải con trỏ ban đầu. Đây vẫn là giá trị truyền qua, nhưng nó hoạt động.
Danijel

1
@Danijel Có thể chuyển một con trỏ không phải là bản sao của bất kỳ lệnh gọi hàm nào. Ví dụ, gọi hàm func: func(&A);Điều này sẽ chuyển một con trỏ đến A cho hàm mà không sao chép bất cứ điều gì. Đó là pass-by-value, nhưng giá trị đó là một tham chiếu, vì vậy bạn là 'pass-by-Reference` biến A. Không cần sao chép. Nó hợp lệ để nói rằng nó là thông qua tham chiếu.
YungGun

124

Đó không phải là thông qua tham chiếu, mà là thông qua giá trị như những người khác đã nêu.

Ngôn ngữ C là giá trị truyền qua không có ngoại lệ. Truyền con trỏ làm tham số không có nghĩa là tham chiếu qua.

Quy tắc như sau:

Một hàm không thể thay đổi giá trị tham số thực tế.


Chúng ta hãy thử xem sự khác biệt giữa các tham số vô hướng và con trỏ của hàm.

Biến vô hướng

Chương trình ngắn này hiển thị giá trị truyền qua sử dụng biến vô hướng. paramđược gọi là tham số chính thức và variabletại chức năng gọi được gọi là tham số thực tế. Lưu ý tăng paramtrong chức năng không thay đổi 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;
}

Kết quả là

I've received value 111
variable=111

Ảo tưởng về sự qua lại

Chúng tôi thay đổi các đoạn mã một chút. parambây giờ là một con trỏ

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

Kết quả là

I've received value 111
variable=112

Điều đó làm cho bạn tin rằng tham số đã được thông qua tham chiếu. Nó không phải là. Nó được thông qua bởi giá trị, giá trị param là một địa chỉ. Giá trị kiểu int được tăng lên và đó là hiệu ứng phụ khiến chúng ta nghĩ rằng đó là một lệnh gọi hàm tham chiếu.

Con trỏ - thông qua giá trị

Làm thế nào chúng ta có thể hiển thị / chứng minh thực tế đó? Chà, có lẽ chúng ta có thể thử ví dụ đầu tiên về các biến vô hướng, nhưng thay vì vô hướng, chúng ta sử dụng địa chỉ (con trỏ). Hãy xem nếu điều đó có thể giúp đỡ.

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

Kết quả sẽ là hai địa chỉ bằng nhau (đừng lo lắng về giá trị chính xác).

Kết quả ví dụ:

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

Theo tôi điều này chứng tỏ rõ ràng rằng con trỏ được truyền qua giá trị. Nếu không ptrsẽ là NULLsau khi gọi chức năng.


69

Trong C, Pass-by-Reference được mô phỏng bằng cách chuyển địa chỉ của một biến (một con trỏ) và hủy bỏ địa chỉ trong hàm để đọc hoặc ghi biến thực. Điều này sẽ được gọi là "tham chiếu kiểu C".

Nguồn: www-cs-students.stanford.edu


50

Bởi vì không có tham chiếu qua trong đoạn mã trên. Sử dụng con trỏ (chẳng hạn như void func(int* p)) là địa chỉ truyền qua. Đây là tham chiếu qua trong C ++ (sẽ không hoạt động trong C):

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

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

1
Tôi thích câu trả lời qua địa chỉ . Có ý nghĩa hơn.
Afzaal Ahmad Zeeshan

Địa chỉ và tài liệu tham khảo là đồng nghĩa trong bối cảnh này. Nhưng bạn có thể sử dụng những thuật ngữ đó để phân biệt hai từ này, nó chỉ không trung thực với nghĩa gốc của chúng.
YungGun

27

Ví dụ của bạn hoạt động vì bạn đang chuyển địa chỉ của biến của mình đến một hàm thao tác giá trị của nó với toán tử dereference .

Mặc dù C không hỗ trợ các kiểu dữ liệu tham chiếu , bạn vẫn có thể mô phỏng chuyển qua tham chiếu bằng cách chuyển rõ ràng các giá trị con trỏ, như trong ví dụ của bạn.

Kiểu dữ liệu tham chiếu C ++ ít mạnh hơn nhưng được coi là an toàn hơn kiểu con trỏ được kế thừa từ C. Đây sẽ là ví dụ của bạn, được điều chỉnh để sử dụng các tham chiếu C ++ :

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

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

  return 0;
}

3
Bài viết Wikipedia đó là về C ++, không phải C. Tài liệu tham khảo tồn tại trước C ++ và không phụ thuộc vào cú pháp C ++ đặc biệt để tồn tại.

1
@Roger: Điểm hay ... Tôi đã xóa tham chiếu rõ ràng đến C ++ khỏi câu trả lời của tôi.
Daniel Vassallo

1
Và bài báo mới đó nói rằng "một tham chiếu thường được gọi là một con trỏ" không hoàn toàn như câu trả lời của bạn.

12

Bạn đang chuyển một con trỏ (vị trí địa chỉ) theo giá trị .

Nó giống như nói "đây là nơi có dữ liệu tôi muốn bạn cập nhật."


6

p là một biến con trỏ. Giá trị của nó là địa chỉ của i. Khi bạn gọi f, bạn chuyển giá trị của p, là địa chỉ của i.


6

Không có tham chiếu qua trong C, nhưng p "tham chiếu" đến i và bạn chuyển p theo giá trị.


5

Trong C mọi thứ đều là giá trị truyền qua. Việc sử dụng các con trỏ cho chúng ta ảo tưởng rằng chúng ta đang chuyển qua tham chiếu vì giá trị của biến thay đổi. Tuy nhiên, nếu bạn in địa chỉ của biến con trỏ, bạn sẽ thấy nó không bị ảnh hưởng. Một bản sao của giá trị của địa chỉ được truyền vào hàm. Dưới đây là một đoạn minh họa rằng.

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

Bởi vì bạn đang truyền một con trỏ (địa chỉ bộ nhớ) cho biến p vào hàm f. Nói cách khác, bạn đang truyền một con trỏ không phải là một tham chiếu.


4

Câu trả lời ngắn: Có, C thực hiện tham số truyền qua tham chiếu bằng cách sử dụng con trỏ.

Trong khi thực hiện truyền tham số, người thiết kế ngôn ngữ lập trình sử dụng ba chiến lược khác nhau (hoặc mô hình ngữ nghĩa): chuyển dữ liệu sang chương trình con, nhận dữ liệu từ chương trình con hoặc thực hiện cả hai. Các mô hình này thường được gọi là trong chế độ, chế độ ra và chế độ đầu vào, tương ứng.

Một số mô hình đã được các nhà thiết kế ngôn ngữ nghĩ ra để thực hiện ba chiến lược truyền tham số cơ bản này:

Pass-by-Value (trong chế độ ngữ nghĩa) Pass-by-result (ngữ nghĩa chế độ out) Pass-by-Value-result (ngữ nghĩa chế độ inout) Pass-by-Reference (ngữ nghĩa chế độ inout) Pass-by-Name (chế độ inout ngữ nghĩa)

Pass-by-Reference là kỹ thuật thứ hai để truyền tham số ở chế độ đầu vào. Thay vì sao chép dữ liệu qua lại giữa thường trình chính và chương trình con, hệ thống thời gian chạy sẽ gửi một đường dẫn truy cập trực tiếp đến dữ liệu cho chương trình con. Trong chiến lược này, chương trình con có quyền truy cập trực tiếp vào dữ liệu chia sẻ dữ liệu một cách hiệu quả với thói quen chính. Ưu điểm chính với kỹ thuật này là nó hoàn toàn hiệu quả về thời gian và không gian vì không cần phải nhân đôi không gian và không có hoạt động sao chép dữ liệu.

Việc thực hiện chuyển tham số trong C: C thực hiện ngữ nghĩa pass-by-value và cả pass-by-Reference (chế độ inout) sử dụng con trỏ làm tham số. Con trỏ được gửi đến chương trình con và không có dữ liệu thực tế nào được sao chép cả. Tuy nhiên, vì một con trỏ là đường dẫn truy cập vào dữ liệu của thường trình chính, chương trình con có thể thay đổi dữ liệu trong thường trình chính. C đã áp dụng phương pháp này từ ALGOL68.

Việc thực hiện chuyển tham số trong C ++: C ++ cũng thực hiện ngữ nghĩa truyền qua tham chiếu (chế độ đầu vào) bằng cách sử dụng các con trỏ và cũng sử dụng một loại con trỏ đặc biệt, được gọi là kiểu tham chiếu. Các con trỏ kiểu tham chiếu được ngầm định bỏ qua bên trong chương trình con nhưng ngữ nghĩa của chúng cũng là tham chiếu qua.

Vì vậy, khái niệm chính ở đây là tham chiếu qua thực hiện một đường dẫn truy cập vào dữ liệu thay vì sao chép dữ liệu vào chương trình con. Đường dẫn truy cập dữ liệu có thể được chỉ định rõ ràng con trỏ hoặc con trỏ được loại bỏ tự động (loại tham chiếu).

Để biết thêm thông tin xin vui lòng tham khảo cuốn sách Khái niệm về ngôn ngữ lập trình của Robert Sebesta, lần thứ 10, Chương 9.


3

Bạn không chuyển một int theo tham chiếu, bạn đang chuyển một con trỏ tới một giá trị. Cú pháp khác nhau, cùng một nghĩa.


1
+1 "Cú pháp khác nhau, cùng một nghĩa." .. Vì vậy, điều tương tự, vì ý nghĩa quan trọng hơn cú pháp.

Không đúng. Bằng cách gọi void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }với int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, nó in 111 thay vì 500. Nếu bạn chuyển qua tham chiếu, nó sẽ in 500. C không hỗ trợ truyền tham số theo tham chiếu.
Konfle Dolex

@Konfle, nếu bạn đang chuyển qua cú pháp theo tham chiếu, ptr = &newvaluesẽ không được phép. Bất kể sự khác biệt, tôi nghĩ rằng bạn đang chỉ ra rằng "cùng một nghĩa" là không chính xác bởi vì bạn cũng có chức năng bổ sung trong C (khả năng tự gán lại "tham chiếu").
xan

Chúng tôi không bao giờ viết một cái gì đó như ptr=&newvaluenếu nó được thông qua tham chiếu. Thay vào đó, chúng tôi viết ptr=newvalueDưới đây là một ví dụ trong C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }Giá trị của tham số được truyền vào func () sẽ trở thành 500.
Konfle Dolex

Trong trường hợp nhận xét của tôi ở trên, việc thông qua tham chiếu là vô nghĩa. Tuy nhiên, nếu param là một đối tượng thay vì POD, điều này sẽ tạo ra sự khác biệt đáng kể bởi vì bất kỳ thay đổi nào sau param = new Class()bên trong hàm sẽ không có tác dụng đối với người gọi nếu nó được truyền bởi giá trị (con trỏ). Nếu paramđược truyền bằng tham chiếu, những thay đổi sẽ hiển thị cho người gọi.
Konfle Dolex

3

Trong C, để chuyển qua tham chiếu, bạn sử dụng toán tử địa chỉ &nên được sử dụng cho một biến, nhưng trong trường hợp của bạn, vì bạn đã sử dụng biến con trỏ p, bạn không cần phải thêm tiền tố vào toán tử địa chỉ. Nó sẽ là đúng nếu bạn sử dụng &ilàm tham số : f(&i).

Bạn cũng có thể thêm phần này, để bổ sung pvà xem giá trị đó khớp như thế nào i:

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

Tại sao bạn cảm thấy cần phải lặp lại tất cả các mã (bao gồm cả khối nhận xét đó ..) để nói với anh ấy rằng anh ấy nên thêm một printf?

@Neil: Lỗi đó được giới thiệu bởi bản chỉnh sửa của @ William, tôi sẽ đảo ngược nó ngay bây giờ. Và bây giờ rõ ràng là tommieb hầu như chỉ đúng: bạn có thể áp dụng & cho bất kỳ đối tượng nào, không chỉ các biến.

2

con trỏ và tài liệu tham khảo là hai thigng khác nhau.

Một vài điều tôi chưa thấy đề cập đến.

Một con trỏ là địa chỉ của một cái gì đó. Một con trỏ có thể được lưu trữ và sao chép như bất kỳ biến nào khác. Do đó có một kích thước.

Một tài liệu tham khảo nên được xem như là một ALIAS của một cái gì đó. Nó không có kích thước và không thể được lưu trữ. Nó PHẢI tham khảo một cái gì đó, tức là. nó không thể là null hoặc thay đổi. Vâng, đôi khi trình biên dịch cần lưu trữ tham chiếu như một con trỏ, nhưng đó là một chi tiết thực hiện.

Với các tài liệu tham khảo, bạn không gặp vấn đề với con trỏ, như xử lý quyền sở hữu, kiểm tra null, hủy tham chiếu khi sử dụng.


1

'Chuyển qua tham chiếu' (bằng cách sử dụng con trỏ) đã có trong C từ đầu. Tại sao bạn nghĩ rằng nó không phải là?


7
Bởi vì về mặt kỹ thuật không đi qua tham chiếu.
Mehrdad Afshari

7
Truyền một giá trị con trỏ không giống như chuyển qua tham chiếu. Cập nhật giá trị của j( không *j ) trong f()không có hiệu lực itrong main().
John Bode

6
Về mặt ngữ nghĩa, nó giống như việc chuyển qua tham chiếu, và điều đó đủ tốt để nói rằng nó đi qua tham chiếu. Đúng, Tiêu chuẩn C không sử dụng thuật ngữ "tham chiếu", nhưng điều đó không làm tôi ngạc nhiên cũng không phải là vấn đề. Chúng tôi cũng không nói tiêu chuẩn về SO, mặc dù chúng tôi có thể đề cập đến tiêu chuẩn, nếu không, chúng tôi sẽ không thấy ai nói về giá trị (Tiêu chuẩn C không sử dụng thuật ngữ này).

4
@Jim: Cảm ơn bạn đã nói với chúng tôi rằng chính bạn đã nâng cao nhận xét của John.

1

Tôi nghĩ rằng C trong thực tế hỗ trợ vượt qua bằng cách tham khảo.

Hầu hết các ngôn ngữ yêu cầu đường cú pháp để vượt qua tham chiếu thay vì giá trị. (Ví dụ C ++ yêu cầu & trong khai báo tham số).

C cũng yêu cầu đường cú pháp cho việc này. Đó là * trong khai báo kiểu tham số và & trên đối số. Vì vậy, * và & cú pháp C để chuyển qua tham chiếu.

Bây giờ người ta có thể lập luận rằng việc truyền thực sự bằng tham chiếu chỉ nên yêu cầu cú pháp trên khai báo tham số, không phải ở phía đối số.

Nhưng bây giờ đến C # mà không hỗ trợ bởi qua tham khảo đòi hỏi đường cú pháp trên cả hai tham số và lập luận bên.

Đối số mà C không có thông qua by-ref khiến cho các phần tử cú pháp biểu thị nó thể hiện việc triển khai kỹ thuật cơ bản hoàn toàn không phải là một đối số, vì điều này áp dụng ít nhiều cho tất cả các triển khai.

Đối số duy nhất còn lại là việc chuyển qua ref trong C không phải là một tính năng nguyên khối mà kết hợp hai tính năng hiện có. (Lấy tham chiếu của đối số theo &, mong đợi ref nhập theo *.) Ví dụ, C # yêu cầu hai yếu tố cú pháp, nhưng chúng không thể được sử dụng mà không có nhau.

Đây rõ ràng là một đối số nguy hiểm, vì nhiều tính năng khác trong các ngôn ngữ được cấu thành từ các tính năng khác. (như hỗ trợ chuỗi trong C ++)


1

Những gì bạn đang làm là vượt qua bởi giá trị không vượt qua tham chiếu. Bởi vì bạn đang gửi giá trị của biến 'p' đến hàm 'f' (chính là f (p);)

Chương trình tương tự trong C với pass by tham chiếu sẽ giống như, (!!! chương trình này đưa ra 2 lỗi là pass do tham chiếu không được hỗ trợ trong 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;
}

Đầu ra: -

3:12: lỗi: dự kiến ​​';', ',' hoặc ')' trước '&' mã thông báo
             khoảng trống f (int & j);
                        ^
9: 3: cảnh báo: khai báo ngầm định hàm 'f'
               f (a);
               ^
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.