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 sizeof
toá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 str
mả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
và *printf
hà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 x
và y
là các đối tượng riêng biệt từ a
và b
, do đó thay đổi x
và y
không được phản ánh trong a
và b
. Vì chúng tôi muốn sửa đổi các giá trị của a
và b
, 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 x
và y
, nhưng các giá trị của cái gì x
và y
trỏ đến . Viết đến *x
là 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í x
và 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 stream
khô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