Tôi nên sử dụng char ** argv hoặc char * argv []?


125

Tôi chỉ đang học C và đang tự hỏi tôi nên sử dụng phương pháp nào trong số này trong phương pháp chính của mình. Có sự khác biệt nào không? Cái nào phổ biến hơn?


2
Tôi thích 'char ** argv', và vì vậy tôi thấy nó thường xuyên hơn, nhưng cả hai đều đúng và tôi sẽ không thay đổi một tuyên bố đơn giản vì nó được viết 'char * argv []'.
Jonathan Leffler

+1 vì các vấn đề sâu hơn của mảng so với con trỏ rất quan trọng để hiểu rõ.
RBerteig

2
Thực sự, câu hỏi thực sự tốt. Cảm ơn bạn.
Frank V

5
Đọc phần 6 của câu hỏi thường gặp comp.lang.c ; đó là lời giải thích tốt nhất về mối quan hệ giữa mảng C và con trỏ tôi đã thấy. Hai điểm liên quan: 1. char **argvhoàn toàn tương đương với char *argv[]khai báo tham số (và chỉ dưới dạng khai báo tham số). 2. mảng không phải là con trỏ.
Keith Thompson

Câu trả lời:


160

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 *glà 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;

2
Không có ý tưởng này tồn tại: * argv [tĩnh 1]
superlukas

12

Bạn có thể sử dụng một trong hai. Chúng hoàn toàn tương đương. Xem bình luận của litbcâu trả lời của anh ấy .

Nó thực sự phụ thuộc vào cách bạn muốn sử dụng nó (và bạn có thể sử dụng trong mọi trường hợp):

// echo-with-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char **argv)
{
  while (--argc > 0)
  {
    printf("%s ", *++argv);
  }
  printf("\n");
  return 0;
}

// echo-without-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  int i;
  for (i=1; i<argc; i++)
  {
    printf("%s ", argv[i]);
  }
  printf("\n");
  return 0;
}

Đối với đó là phổ biến hơn - nó không thành vấn đề. Bất kỳ lập trình viên C có kinh nghiệm nào đọc mã của bạn sẽ thấy cả hai đều có thể hoán đổi cho nhau (trong điều kiện phù hợp). Giống như một người nói tiếng Anh có kinh nghiệm đọc "họ" và "họ" dễ dàng như nhau.

Quan trọng hơn là bạn học cách đọc chúng và nhận ra chúng giống nhau như thế nào. Bạn sẽ đọc nhiều mã hơn bạn viết và bạn sẽ cần phải thoải mái như nhau với cả hai.


7
char * argv [] tương đương 100% với char ** argv khi được sử dụng làm kiểu tham số của hàm. không có "const" liên quan, cũng không ngầm. Cả hai đều là con trỏ đến con trỏ đến nhân vật. Nó khác nhau về những gì bạn tuyên bố. Nhưng trình biên dịch điều chỉnh loại tham số thành một con trỏ thành một con trỏ, mặc dù bạn nói đó là một mảng. Do đó, các cách sau đều giống nhau: void f (char * p [100]); void f (char * p []); void f (char ** p);
Julian Schaub - litb

4
Trong C89 (mà hầu hết mọi người sử dụng) cũng không có cách nào để tận dụng lợi ích của việc bạn khai báo nó là một mảng (vì vậy về mặt ngữ nghĩa, không quan trọng bạn khai báo một con trỏ hay một mảng ở đó - cả hai sẽ được coi là một con trỏ). Bắt đầu với C99, bạn có thể hưởng lợi từ việc khai báo nó dưới dạng một mảng. Phần sau đây nói: "p luôn luôn là null và trỏ đến một vùng có ít nhất 100byte": void f (char p [static 100]); Lưu ý rằng loại khôn ngoan, tuy nhiên, p vẫn là một con trỏ.
Julian Schaub - litb

5
(cụ thể, & p sẽ cung cấp cho bạn một char **, nhưng không phải là char ( ) [100] sẽ là trường hợp nếu p * sẽ là một mảng). Tôi ngạc nhiên không ai nhắc đến trong một câu trả lời. Tôi coi nó là rất quan trọng để hiểu.
Johannes Schaub - litb

Cá nhân tôi thích char**bởi vì nó nhắc nhở tôi rằng nó không nên được coi là một mảng thực sự, như làm sizeof[arr] / sizeof[*arr].
raymai97

9

Nó không tạo ra sự khác biệt, nhưng tôi sử dụng char *argv[]vì nó cho thấy đó là một mảng có kích thước cố định của các chuỗi có độ dài thay đổi (thường là char *).



4

Nó không thực sự tạo ra sự khác biệt, nhưng cái sau dễ đọc hơn. Những gì bạn được đưa ra là một loạt các con trỏ char, giống như phiên bản thứ hai nói. Nó có thể được chuyển đổi hoàn toàn thành một con trỏ char kép như trong phiên bản đầu tiên.


2

bạn nên khai báo nó char *argv[], bởi vì tất cả nhiều cách khai báo tương đương, gần với nghĩa trực quan của nó: một chuỗi các chuỗi.


1

char ** → con trỏ tới con trỏ ký tự và char * argv [] có nghĩa là mảng các con trỏ ký tự. Vì chúng ta có thể sử dụng con trỏ thay vì một mảng, cả hai đều có thể được sử dụng.


0

Tôi thấy không có giá trị đặc biệt nào khi sử dụng một trong hai cách tiếp cận thay vì cách tiếp cận khác - sử dụng quy ước phù hợp nhất với phần còn lại của mã của bạn.


-2

Nếu bạn cần số chuỗi thay đổi hoặc động, char ** có thể dễ dàng hoạt động hơn. Nếu số chuỗi của bạn được cố định mặc dù, char * var [] sẽ được ưu tiên.


-2

Tôi biết điều này đã lỗi thời, nhưng nếu bạn chỉ học ngôn ngữ lập trình C và không làm gì lớn với nó, đừng sử dụng các tùy chọn dòng lệnh.

Nếu bạn không sử dụng đối số dòng lệnh, đừng sử dụng một trong hai. Chỉ cần khai báo hàm chính là int main() Nếu bạn

  • Muốn người dùng chương trình của bạn có thể kéo tệp vào chương trình của bạn để bạn có thể thay đổi kết quả của chương trình với nó hoặc
  • Muốn xử lý tùy chọn dòng lệnh ( -help, /?hoặc bất cứ điều gì khác mà đi sau program nametrong terminal hoặc command prompt)

sử dụng bất cứ điều gì có ý nghĩa hơn với bạn. Mặt khác, chỉ cần sử dụng int main() Sau tất cả, nếu cuối cùng bạn muốn thêm tùy chọn dòng lệnh, bạn có thể dễ dàng chỉnh sửa chúng sau này.


Điều này không trả lời câu hỏi.
Lundin
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.