Con trỏ trong C: khi nào nên sử dụng ký hiệu và dấu hoa thị?


298

Tôi chỉ mới bắt đầu với con trỏ, và tôi hơi bối rối. Tôi biết &có nghĩa là địa chỉ của một biến và *có thể được sử dụng trước một biến con trỏ để lấy giá trị của đối tượng được trỏ bởi con trỏ. Nhưng mọi thứ hoạt động khác nhau khi bạn làm việc với mảng, chuỗi hoặc khi bạn đang gọi các hàm với một bản sao con trỏ của một biến. Thật khó để thấy một mô hình logic bên trong tất cả những điều này.

Khi nào tôi nên sử dụng &*?


5
Hãy minh họa cách bạn nhìn thấy mọi thứ đôi khi làm việc khác nhau. Mặt khác, chúng tôi phải đoán những gì nó làm bạn bối rối.
Drew Dormann

1
Đồng ý với Neil Butterworth. Có lẽ sẽ nhận được nhiều thông tin hơn khi nhận nó từ cuốn sách và lời giải thích của K & R khá rõ ràng.
Tom

145
Tôi không đồng ý với tất cả các bạn nói rằng không nên hỏi những loại câu hỏi này trên SO. SO đã trở thành tài nguyên số 1 khi tìm kiếm trên Google. Bạn không cung cấp đủ tín dụng cho những câu trả lời này. Đọc phản hồi của Dan Olson. Câu trả lời này thực sự sâu sắc và vô cùng hữu ích cho người mới. RTFMlà vô ích, và khá thẳng thắn rất thô lỗ. Nếu bạn không có thời gian để trả lời, thì hãy tôn trọng những người dành thời gian để trả lời những câu hỏi này. Tôi ước tôi có thể @ này để "anon" nhưng rõ ràng anh ấy / cô ấy không có thời gian để đóng góp theo bất kỳ cách có ý nghĩa nào.
SSH này vào

18
Những gì SSH Điều này nói là hoàn toàn đúng. Một số người hét lên "Chỉ Google thôi", nhưng ngày nay nó lại khác: "Chỉ cần nhìn vào StackOverflow." Câu hỏi này hữu ích cho nhiều người. (Do đó, các upvote và không downvote.)
MC Emperor

Câu trả lời:


610

Bạn có con trỏ và giá trị:

int* p; // variable p is pointer to integer type
int i; // integer value

Bạn biến một con trỏ thành một giá trị với *:

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

Bạn biến một giá trị thành một con trỏ với &:

int* p2 = &i; // pointer p2 will point to the address of integer i

Chỉnh sửa: Trong trường hợp mảng, chúng được đối xử rất giống với con trỏ. Nếu bạn nghĩ về chúng như các con trỏ, bạn sẽ sử dụng *để lấy các giá trị bên trong chúng như được giải thích ở trên, nhưng cũng có một cách khác, phổ biến hơn khi sử dụng []toán tử:

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

Để có được yếu tố thứ hai:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

Vì vậy, []toán tử lập chỉ mục là một dạng đặc biệt của *toán tử và nó hoạt động như thế này:

a[i] == *(a + i);  // these two statements are the same thing

2
Tại sao điều này không hoạt động? int aX[] = {3, 4}; int *bX = &aX;
Pieter

5
Mảng là đặc biệt và có thể được chuyển đổi thành con trỏ trong suốt. Điều này nhấn mạnh một cách khác để đi từ một con trỏ đến một giá trị, tôi sẽ thêm nó vào phần giải thích ở trên.
Dan Olson

4
Nếu tôi hiểu điều này một cách chính xác ... ví dụ int *bX = &aX;này không hoạt động vì aXđã trả về địa chỉ của aX[0](nghĩa là &aX[0]), vì vậy &aXsẽ nhận được địa chỉ của một địa chỉ không có ý nghĩa. Điều này có đúng không?
Pieter

6
Bạn đã đúng, mặc dù có những trường hợp bạn thực sự muốn địa chỉ của địa chỉ. Trong trường hợp đó, bạn sẽ khai báo nó là int ** bX = & aX, nhưng đây là một chủ đề nâng cao hơn.
Dan Olson

3
@Dan, đã cho int aX[] = {3,4};, int **bX = &aX;là một lỗi. &aXthuộc loại "con trỏ tới mảng [2] int", không phải "con trỏ tới con trỏ tới int". Cụ thể, tên của một mảng không được coi là một con trỏ tới phần tử đầu tiên của nó cho unary &. Bạn có thể làm:int (*bX)[2] = &aX;
Alok Singhal

28

Có một mô hình khi xử lý các mảng và hàm; ban đầu chỉ hơi khó nhìn.

Khi xử lý các mảng, cần nhớ những điều sau: khi một biểu thức mảng xuất hiện trong hầu hết các ngữ cảnh, loại biểu thức được chuyển đổi ngầm định từ "mảng phần tử N của T" thành "con trỏ thành T" và giá trị của nó được đặt để trỏ đến phần tử đầu tiên trong mảng. Các ngoại lệ cho quy tắc này là khi biểu thức mảng xuất hiện dưới dạng toán hạng của toán tử &hoặc sizeoftoán tử hoặc khi đó là một chuỗi ký tự được sử dụng làm bộ khởi tạo trong khai báo.

Do đó, khi bạn gọi một hàm có biểu thức mảng là một đối số, hàm sẽ nhận được một con trỏ, không phải là một mảng:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

Đây là lý do tại sao bạn không sử dụng &toán tử cho các đối số tương ứng với "% s" trong scanf():

char str[STRING_LENGTH];
...
scanf("%s", str);

Do chuyển đổi ngầm định, scanf()nhận được một char *giá trị trỏ đến đầu strmảng. Điều này đúng với bất kỳ hàm nào được gọi với một biểu thức mảng làm đối số (chỉ về bất kỳ str*hàm nào *scanf*printfhàm, v.v.).

Trong thực tế, có lẽ bạn sẽ không bao giờ gọi một hàm có biểu thức mảng bằng &toán tử, như trong:

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

Mã như vậy không phải là rất phổ biến; bạn phải biết kích thước của mảng trong khai báo hàm và hàm chỉ hoạt động với các con trỏ tới các mảng có kích thước cụ thể (một con trỏ tới mảng 10 phần tử của T là một kiểu khác với một con trỏ tới mảng 11 phần tử của T).

Khi một biểu thức mảng xuất hiện dưới dạng toán hạng cho &toán tử, loại biểu thức kết quả là "con trỏ tới mảng phần tử N của T", hoặc T (*)[N]khác với một mảng con trỏ ( T *[N]) và con trỏ đến loại cơ sở ( T *).

Khi xử lý các hàm và con trỏ, quy tắc cần nhớ là: nếu bạn muốn thay đổi giá trị của một đối số và nó được phản ánh trong mã gọi, bạn phải chuyển một con trỏ tới điều bạn muốn sửa đổi. Một lần nữa, mảng ném một chút cờ lê vào công trình, nhưng trước tiên chúng ta sẽ xử lý các trường hợp bình thường.

Hãy nhớ rằng C vượt qua tất cả các đối số hàm theo giá trị; tham số chính thức nhận được một bản sao của giá trị trong tham số thực và mọi thay đổi đối với tham số chính thức không được phản ánh trong tham số thực tế. Ví dụ phổ biến là hàm hoán đổi:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

Bạn sẽ nhận được đầu ra sau:

trước khi trao đổi: a = 1, b = 2
sau khi trao đổi: a = 1, b = 2

Các tham số chính thức xylà các đối tượng riêng biệt từ ab, do đó thay đổi xykhông được phản ánh trong ab. Vì chúng tôi muốn sửa đổi các giá trị của ab, chúng tôi phải chuyển các con trỏ tới chúng cho hàm hoán đổi:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

Bây giờ đầu ra của bạn sẽ là

trước khi trao đổi: a = 1, b = 2
sau khi trao đổi: a = 2, b = 1

Lưu ý rằng, trong hàm hoán đổi, chúng ta không thay đổi các giá trị của xy, nhưng các giá trị của cái gì xy trỏ đến . Viết đến *xlà khác với viết đến x; bản thân chúng tôi không cập nhật giá trị x, chúng tôi nhận vị trí xvà cập nhật giá trị ở vị trí đó.

Điều này cũng đúng như vậy nếu chúng ta muốn sửa đổi một giá trị con trỏ; nếu chúng ta viết

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

sau đó chúng tôi sửa đổi giá trị của tham số đầu vào stream, không phải là stream điểm nào , vì vậy việc thay đổi streamkhông ảnh hưởng đến giá trị của in; để làm việc này, chúng ta phải chuyển một con trỏ tới con trỏ:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

Một lần nữa, mảng ném một chút cờ lê khỉ vào công trình. Khi bạn truyền một biểu thức mảng cho một hàm, những gì hàm nhận được là một con trỏ. Do cách xác định đăng ký mảng, bạn có thể sử dụng toán tử đăng ký trên một con trỏ giống như cách bạn có thể sử dụng nó trên một mảng:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

Lưu ý rằng các đối tượng mảng có thể không được chỉ định; tức là bạn không thể làm một cái gì đó như

int a[10], b[10];
...
a = b;

vì vậy bạn muốn cẩn thận khi bạn xử lý các con trỏ tới mảng; cái gì đó như

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

sẽ không làm việc


16

Đơn giản thôi

  • &có nghĩa là địa chỉ của , bạn sẽ thấy rằng trong trình giữ chỗ cho các hàm để sửa đổi biến tham số như trong C, các biến tham số được truyền theo giá trị, sử dụng ký hiệu và nghĩa là chuyển qua tham chiếu.
  • *có nghĩa là dereference của một biến con trỏ, có nghĩa là để có được những giá trị mà biến con trỏ.
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

Ví dụ trên minh họa cách gọi hàm foobằng cách sử dụng tham chiếu qua, so sánh với điều này

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

Dưới đây là một minh chứng của việc sử dụng một dereference

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

Ở trên minh họa cách chúng ta có địa chỉ y và gán nó cho biến con trỏ p. Sau đó, chúng tôi dereference p bằng cách gắn các *vào phía trước của nó để có được những giá trị của p, tức là *p.


10

Vâng, điều đó có thể khá phức tạp kể từ khi * nó được sử dụng cho nhiều mục đích khác nhau trong C / C ++.

Nếu *xuất hiện trước một biến / hàm đã được khai báo, điều đó có nghĩa là:

  • a) *cung cấp quyền truy cập vào giá trị của biến đó (nếu loại của biến đó là loại con trỏ hoặc bị quá tải* toán tử).
  • b) *có nghĩa là toán tử nhân, trong trường hợp đó, phải có một biến khác ở bên trái của*

Nếu *xuất hiện trong một biến hoặc khai báo hàm, điều đó có nghĩa là biến đó là một con trỏ:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

Nếu &xuất hiện trong một khai báo biến hoặc hàm, điều đó thường có nghĩa là biến đó là tham chiếu đến một biến của loại đó.

Nếu &xuất hiện trước một biến đã được khai báo, nó sẽ trả về địa chỉ của biến đó

Ngoài ra, bạn nên biết rằng, khi truyền một mảng cho một hàm, bạn sẽ luôn phải chuyển kích thước mảng của mảng đó, ngoại trừ khi mảng đó là một chuỗi giống như một chuỗi kết thúc 0 (mảng char).


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (không gian không hợp lệ)
PixnBits

4

Khi bạn khai báo một biến con trỏ hoặc tham số hàm, hãy sử dụng dấu *:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

NB: mỗi biến được khai báo cần riêng *.

Khi bạn muốn lấy địa chỉ của một giá trị, hãy sử dụng &. Khi bạn muốn đọc hoặc ghi giá trị trong một con trỏ, hãy sử dụng *.

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

Mảng thường chỉ được xử lý như con trỏ. Khi bạn khai báo một tham số mảng trong một hàm, bạn có thể dễ dàng khai báo nó là một con trỏ (nó có nghĩa tương tự). Khi bạn truyền một mảng cho một hàm, bạn thực sự đang chuyển một con trỏ đến phần tử đầu tiên.

Con trỏ hàm là những thứ duy nhất không tuân theo các quy tắc. Bạn có thể lấy địa chỉ của hàm mà không cần sử dụng & và bạn có thể gọi con trỏ hàm mà không cần sử dụng *.


4

Tôi đã xem qua tất cả những lời giải thích dài dòng thay vì chuyển sang một video từ Đại học New South Wales để giải cứu. Đây là lời giải thích đơn giản: nếu chúng ta có một ô có địa chỉ xvà giá trị 7, cách gián tiếp để hỏi địa chỉ giá trị 7&7và cách gián tiếp để yêu cầu giá trị tại địa chỉ x*x. Vì vậy, một (cell: x , value: 7) == (cell: &7 , value: *x)cách khác để xem xét nó: Johnngồi tại 7th seat. The *7th seatwill trỏ đến John&Johnsẽ cho address/ vị trí của 7th seat. Lời giải thích đơn giản này đã giúp tôi và hy vọng nó cũng sẽ giúp người khác. Đây là liên kết cho video xuất sắc: bấm vào đây.

Đây là một ví dụ khác:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

Bổ trợ: Luôn khởi tạo con trỏ trước khi sử dụng chúng. Nếu không, con trỏ sẽ trỏ đến bất cứ thứ gì, điều này có thể dẫn đến sự cố chương trình vì hệ điều hành sẽ ngăn bạn truy cập vào bộ nhớ mà nó biết bạn không sở hữu. Nhưng chỉ cần đặt p = &x;, chúng tôi đang gán cho con trỏ một vị trí cụ thể.


3

Trên thực tế, bạn có nó vỗ nhẹ, không có gì bạn cần biết thêm :-)

Tôi chỉ cần thêm các bit sau:

  • hai hoạt động là hai đầu đối diện của quang phổ. &lấy một biến và cung cấp cho bạn địa chỉ, *lấy một địa chỉ và cung cấp cho bạn biến (hoặc nội dung).
  • mảng "xuống cấp" cho con trỏ khi bạn chuyển chúng đến các hàm.
  • bạn thực sự có thể có nhiều cấp độ theo hướng ( char **pcó nghĩa plà con trỏ tới con trỏ tới a char.

Đối với những thứ hoạt động khác nhau, không thực sự:

  • các mảng, như đã đề cập, làm suy giảm các con trỏ (đến phần tử đầu tiên trong mảng) khi được truyền cho các hàm; họ không lưu giữ thông tin kích thước.
  • không có chuỗi nào trong C, chỉ là các mảng ký tự mà theo quy ước, đại diện cho một chuỗi các ký tự được chấm dứt bởi một \0ký tự zero ( ).
  • Khi bạn chuyển địa chỉ của một biến cho một hàm, bạn có thể hủy tham chiếu con trỏ để thay đổi chính biến đó (thông thường các biến được truyền theo giá trị (ngoại trừ mảng)).

3

Tôi nghĩ bạn hơi bối rối. Bạn nên đọc một hướng dẫn tốt / cuốn sách về con trỏ.

Đây hướng dẫn là rất tốt cho người mới bắt đầu (giải thích rõ ràng những gì &*đang có). Và vâng đừng quên đọc cuốn sách Con trỏ trong C của Kenneth Reek.

Sự khác biệt giữa &*rất rõ ràng.

Thí dụ:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}

1

Ok, có vẻ như bài viết của bạn đã được chỉnh sửa ...

double foo[4];
double *bar_1 = &foo[0];

Xem làm thế nào bạn có thể sử dụng &để có được địa chỉ của sự bắt đầu của cấu trúc mảng? Sau đây

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

sẽ làm điều tương tự.


Câu hỏi đã được gắn thẻ C không phải C ++.
Prasoon Saurav

1
Và tôi đã loại bỏ cout vi phạm <<
Wheaties
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.