Vì bạn chỉ đang học C, tôi khuyên bạn nên thực sự cố gắng hiểu sự khác biệt giữa mảng và con trỏ trước thay vì những điều phổ biến .
Trong khu vực của các tham số và mảng, có một vài quy tắc khó hiểu cần phải rõ ràng trước khi tiếp tục. Đầu tiên, những gì bạn khai báo trong danh sách tham số được xử lý đặc biệt. Có những tình huống như vậy mà mọi thứ không có ý nghĩa như là một tham số hàm trong C. Đây là
- Chức năng như tham số
- Mảng như tham số
Mảng như tham số
Thứ hai có thể không rõ ràng ngay lập tức. Nhưng nó trở nên rõ ràng khi bạn xem xét rằng kích thước của kích thước mảng là một phần của loại trong C (và một mảng có kích thước kích thước không được cung cấp có loại không hoàn chỉnh). Vì vậy, nếu bạn sẽ tạo một hàm lấy một mảng theo giá trị (nhận một bản sao), thì nó chỉ có thể làm như vậy cho một kích thước! Ngoài ra, các mảng có thể trở nên lớn và C cố gắng càng nhanh càng tốt.
Trong C, vì những lý do này, giá trị mảng không tồn tại. Nếu bạn muốn nhận giá trị của một mảng, thay vào đó, con trỏ đến phần tử đầu tiên của mảng đó. Và đây thực sự đã là giải pháp. Thay vì vẽ một tham số mảng không hợp lệ lên phía trước, trình biên dịch C sẽ biến đổi loại tham số tương ứng thành một con trỏ. Hãy nhớ điều này, nó rất quan trọng. Tham số sẽ không phải là một mảng, nhưng thay vào đó, nó sẽ là một con trỏ tới loại phần tử tương ứng.
Bây giờ, nếu bạn cố gắng truyền một mảng, thay vào đó là một con trỏ tới phần tử đầu tiên của mảng.
Tham quan: Chức năng như tham số
Để hoàn thành và vì tôi nghĩ rằng điều này sẽ giúp bạn hiểu rõ hơn về vấn đề này, chúng ta hãy xem tình trạng của vấn đề là gì khi bạn cố gắng có một chức năng như một tham số. Thật vậy, đầu tiên nó sẽ không có ý nghĩa gì. Làm thế nào một tham số có thể là một chức năng? Huh, chúng tôi muốn một biến ở nơi đó, tất nhiên! Vì vậy, những gì trình biên dịch làm khi điều đó xảy ra, một lần nữa, để chuyển đổi hàm thành một con trỏ hàm . Thay vào đó, cố gắng truyền một hàm sẽ chuyển một con trỏ tới hàm tương ứng đó. Vì vậy, sau đây là giống nhau (tương tự như ví dụ mảng):
void f(void g(void));
void f(void (*g)(void));
Lưu ý rằng dấu ngoặc đơn xung quanh *g
là cần thiết. Mặt khác, nó sẽ chỉ định một hàm trả về void*
, thay vì một con trỏ tới một hàm trả về void
.
Quay lại mảng
Bây giờ, tôi đã nói lúc đầu rằng các mảng có thể có một kiểu không hoàn chỉnh - điều này xảy ra nếu bạn chưa đưa ra kích thước. Vì chúng ta đã hình dung rằng một tham số mảng không tồn tại mà thay vào đó, bất kỳ tham số mảng nào cũng là một con trỏ, kích thước của mảng không thành vấn đề. Điều đó có nghĩa là, trình biên dịch sẽ dịch tất cả những điều sau đây, và tất cả đều giống nhau:
int main(int c, char **argv);
int main(int c, char *argv[]);
int main(int c, char *argv[1]);
int main(int c, char *argv[42]);
Tất nhiên, nó không có ý nghĩa gì khi có thể đặt bất kỳ kích thước nào trong đó, và nó chỉ bị vứt đi. Vì lý do đó, C99 đã đưa ra một ý nghĩa mới cho những con số đó và cho phép những thứ khác xuất hiện giữa các dấu ngoặc:
// says: argv is a non-null pointer pointing to at least 5 char*'s
// allows CPU to pre-load some memory.
int main(int c, char *argv[static 5]);
// says: argv is a constant pointer pointing to a char*
int main(int c, char *argv[const]);
// says the same as the previous one
int main(int c, char ** const argv);
Hai dòng cuối cùng nói rằng bạn sẽ không thể thay đổi "argv" trong hàm - nó đã trở thành một con trỏ const. Chỉ có vài trình biên dịch C hỗ trợ các tính năng C99 đó. Nhưng các tính năng này cho thấy rõ rằng "mảng" không thực sự là một. Đó là một con trỏ.
Một lời cảnh báo
Lưu ý rằng tất cả những gì tôi nói ở trên chỉ đúng khi bạn có một mảng làm tham số của hàm. Nếu bạn làm việc với các mảng cục bộ, một mảng sẽ không phải là một con trỏ. Nó sẽ hoạt động như một con trỏ, vì như đã giải thích trước đó, một mảng sẽ được chuyển đổi thành một con trỏ khi giá trị của nó được đọc. Nhưng nó không nên bị nhầm lẫn với con trỏ.
Một ví dụ kinh điển là như sau:
char c[10];
char **c = &c; // does not work.
typedef char array[10];
array *pc = &c; // *does* work.
// same without typedef. Parens needed, because [...] has
// higher precedence than '*'. Analogous to the function example above.
char (*array)[10] = &c;