Câu trả lời:
Họ là tương đương chính xác. Tuy nhiên, trong
int *myVariable, myVariable2;
Rõ ràng là myVariable có kiểu int * , trong khi myVariable2 có kiểu int . Trong
int* myVariable, myVariable2;
Có vẻ như rõ ràng là cả hai đều thuộc kiểu int * , nhưng điều đó không chính xác như myVariable2có kiểu int .
Do đó, phong cách lập trình đầu tiên là trực quan hơn.
int* someVarcác dự án cá nhân. Nó có ý nghĩa hơn.
int[10] x. Đây đơn giản không phải là cú pháp của C. Ngữ pháp phân tích rõ ràng là: int (*x)và không phải là (int *) x, do đó, việc đặt dấu hoa thị bên trái chỉ đơn giản là gây hiểu lầm và dựa trên sự hiểu lầm về cú pháp khai báo C.
int* myVariable; int myVariable2;thay thế.
Nếu bạn nhìn nó theo một cách khác, *myVariablelà loại int, điều này có ý nghĩa.
myVariablecó thể là NULL, trong trường hợp này *myVariablegây ra lỗi phân đoạn, nhưng không có loại NULL.
int x = 5; int *pointer = &x;, bởi vì nó gợi ý chúng ta đặt int *pointerthành một giá trị nào đó, chứ không phải pointerchính nó.
Bởi vì * trong dòng đó liên kết chặt chẽ hơn với biến hơn là kiểu:
int* varA, varB; // This is misleading
Như @Lundin chỉ ra dưới đây, const thêm nhiều sự tinh tế hơn để suy nghĩ. Bạn hoàn toàn có thể bỏ qua điều này bằng cách khai báo một biến trên mỗi dòng, điều này không bao giờ mơ hồ:
int* varA;
int varB;
Sự cân bằng giữa mã rõ ràng và mã ngắn gọn rất khó để thực hiện - hàng tá dòng dự phòng int a;cũng không tốt. Tuy nhiên, tôi mặc định một khai báo trên mỗi dòng và lo lắng về việc kết hợp mã sau này.
int *const a, b;. Trường hợp * "ràng buộc"? Loại alà int* const, vậy làm thế nào bạn có thể nói rằng * thuộc về biến khi nó là một phần của chính loại đó?
Một điều chưa ai đề cập ở đây cho đến nay là dấu sao này thực sự là " toán tử quy ước " trong C.
*a = 10;
Dòng trên không có nghĩa là tôi muốn gán 10cho a, nó có nghĩa là tôi muốn gán 10cho bất kỳ vị trí bộ nhớ nào atrỏ đến. Và tôi chưa từng thấy ai viết
* a = 10;
có bạn không Vì vậy, toán tử dereference được viết khá nhiều mà không có khoảng trắng. Điều này có lẽ là để phân biệt nó với một phép nhân bị phá vỡ trên nhiều dòng:
x = a * b * c * d
* e * f * g;
Ở đây *esẽ gây hiểu lầm, phải không?
Được rồi, bây giờ dòng thực sự có nghĩa là gì:
int *a;
Hầu hết mọi người sẽ nói:
Nó có nghĩa alà một con trỏ đến một intgiá trị.
Điều này đúng về mặt kỹ thuật, hầu hết mọi người đều thích nhìn / đọc nó theo cách đó và đó là cách mà các tiêu chuẩn C hiện đại sẽ định nghĩa nó (lưu ý rằng chính ngôn ngữ C có trước tất cả các tiêu chuẩn ANSI và ISO). Nhưng đó không phải là cách duy nhất để xem xét nó. Bạn cũng có thể đọc dòng này như sau:
Giá trị ađược loại bỏ là loại int.
Vì vậy, trên thực tế, dấu hoa thị trong tuyên bố này cũng có thể được xem như là một toán tử quy ước, điều này cũng giải thích vị trí của nó. Và đó alà một con trỏ hoàn toàn không được tuyên bố, thực tế là nó là thứ duy nhất bạn thực sự có thể thực hiện là một con trỏ.
Tiêu chuẩn C chỉ định nghĩa hai nghĩa cho *toán tử:
Và gián tiếp chỉ là một ý nghĩa duy nhất, có ý nghĩa không phải trả thêm cho tuyên bố một con trỏ, đó chỉ là gián tiếp, đó là những gì các hoạt động dereference không, nó thực hiện một truy cập gián tiếp, vì vậy cũng trong một tuyên bố như int *a;đây là một gián tiếp truy cập ( *phương tiện truy cập gián tiếp) và do đó, tuyên bố thứ hai ở trên gần với tiêu chuẩn hơn nhiều so với tuyên bố đầu tiên.
int a, *b, (*c)();như một cái gì đó như "khai báo các đối tượng sau đây là int: đối tượng a, đối tượng được trỏ đến bvà đối tượng được trả về từ hàm được trỏ bởi c".
*trong int *a;không phải là một nhà điều hành và nó không phải là hội nghị a(mà thậm chí còn chưa được xác định)
*(nó chỉ mang một ý nghĩa cho tổng biểu thức, không phải *trong biểu thức!). Nó nói rằng "int a;" khai báo một con trỏ, mà nó không bao giờ yêu cầu khác, nhưng không có nghĩa *, được đọc là * giá trị được quy định của amột int vẫn hoàn toàn hợp lệ, vì điều đó có cùng ý nghĩa thực tế. Không có gì, thực sự không có gì được viết trong 6.7.6.1 sẽ mâu thuẫn với tuyên bố đó.
int *a;là một tuyên bố, không phải là một biểu thức. akhông được quy định bởi int *a;. athậm chí không tồn tại nhưng tại thời điểm *nó đang được xử lý. Bạn có nghĩ int *a = NULL;là một lỗi bởi vì nó hủy bỏ một con trỏ null?
Tôi sẽ đi ra ngoài một chi ở đây và nói rằng có một câu trả lời thẳng cho câu hỏi này , cả cho khai báo biến và các loại tham số và trả về, đó là dấu hoa thị nên đi bên cạnh tên : int *myVariable;. Để đánh giá cao lý do tại sao, hãy xem cách bạn khai báo các loại biểu tượng khác trong C:
int my_function(int arg); cho một chức năng;
float my_array[3] cho một mảng.
Mẫu chung, được gọi là khai báo sau khi sử dụng , là loại biểu tượng được chia thành phần trước tên và các phần xung quanh tên và các phần xung quanh tên bắt chước cú pháp bạn sẽ sử dụng để lấy giá trị của loại bên trái:
int a_return_value = my_function(729);
float an_element = my_array[2];
và : int copy_of_value = *myVariable;.
C ++ ném cờ lê trong công việc với các tham chiếu, vì cú pháp tại điểm bạn sử dụng tham chiếu giống hệt với các loại giá trị, do đó bạn có thể lập luận rằng C ++ có cách tiếp cận khác với C. Mặt khác, C ++ vẫn giữ nguyên hành vi của C trong trường hợp con trỏ, vì vậy các tham chiếu thực sự đứng như một số lẻ trong khía cạnh này.
Đó chỉ là vấn đề ưu tiên.
Khi bạn đọc mã, việc phân biệt giữa các biến và con trỏ sẽ dễ dàng hơn trong trường hợp thứ hai, nhưng nó có thể dẫn đến sự nhầm lẫn khi bạn đặt cả hai biến và con trỏ của một loại chung vào một dòng (điều này thường không được khuyến khích bởi các hướng dẫn dự án, vì giảm khả năng đọc).
Tôi thích khai báo các con trỏ với dấu tương ứng bên cạnh tên loại, vd
int* pMyPointer;
Một bậc thầy vĩ đại đã từng nói "Đọc nó theo cách của trình biên dịch, bạn phải."
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
Cấp điều này là về chủ đề của vị trí const, nhưng quy tắc tương tự áp dụng ở đây.
Trình biên dịch đọc nó dưới dạng:
int (*a);
không phải là:
(int*) a;
Nếu bạn có thói quen đặt ngôi sao bên cạnh biến, nó sẽ giúp cho việc khai báo của bạn dễ đọc hơn. Nó cũng tránh các mắt như:
int* a[10];
-- Biên tập --
Để giải thích chính xác những gì tôi muốn nói khi tôi nói nó được phân tích cú pháp int (*a), điều đó có nghĩa là *liên kết chặt chẽ ahơn so với nó int, theo cách mà trong biểu thức 4 + 3 * 7 3liên kết chặt chẽ 7hơn so với nó 4do ưu tiên cao hơn *.
Với lời xin lỗi cho nghệ thuật ascii, một bản tóm tắt của AST để phân tích cú pháp int *atrông giống như thế này:
Declaration
/ \
/ \
Declaration- Init-
Secifiers Declarator-
| List
| |
| ...
"int" |
Declarator
/ \
/ ...
Pointer \
| Identifier
| |
"*" |
"a"
Như đã được chỉ ra rõ ràng, *liên kết chặt chẽ hơn với atổ tiên chung của chúng là Declarator, trong khi bạn cần đi lên cây Declarationđể tìm một tổ tiên chung có liên quan đến int.
(int*) a.
inttrong trường hợp này. Bước 2. Đọc một tờ khai, bao gồm bất kỳ loại trang trí. *atrong trường hợp này. Bước 3 Đọc ký tự tiếp theo. Nếu dấu phẩy, tiêu thụ nó và quay lại bước 2. Nếu dấu chấm phẩy dừng lại. Nếu bất cứ điều gì khác ném một lỗi cú pháp. ...
int* a, b;và nhận được một cặp con trỏ. Điểm tôi đang làm là các *liên kết với biến và được phân tích cú pháp với nó, không phải với kiểu để tạo thành "kiểu cơ sở" của khai báo. Đó cũng là một phần lý do mà typedefs được giới thiệu để cho phép typedef int *iptr; iptr a, b;tạo ra một vài gợi ý. Bằng cách sử dụng một typedef, bạn có thể liên kết *với int.
Declaration-Specifiervới "trang trí" trong Declaratorđể đến loại cuối cùng cho mỗi biến. Tuy nhiên, nó không "di chuyển" các trang trí đến công cụ xác định Tuyên bố nếu không int a[10], b;sẽ tạo ra kết quả hoàn toàn vô lý, Nó phân tích int *a, b[10];như int *a , b[10] ;. Không có cách nào khác để mô tả nó có ý nghĩa.
int*acuối cùng được trình biên dịch đọc là: " acó loại int*". Đó là những gì tôi muốn nói với nhận xét ban đầu của tôi.
Bởi vì nó có ý nghĩa hơn khi bạn có các khai báo như:
int *a, *b;
int* a, b;tạo ra bmột con trỏ tới int. Nhưng họ đã không làm thế. Và với lý do chính đáng. Theo hệ thống đề xuất của bạn, loại bkhai báo nào sau đây : int* a[10], b;?
Để khai báo nhiều con trỏ trong một dòng, tôi thích int* a, * b;khai báo trực quan hơn "a" như một con trỏ cho một số nguyên và không trộn lẫn các kiểu khi khai báo tương tự "b." Giống như ai đó đã nói, tôi sẽ không khai báo hai loại khác nhau trong cùng một tuyên bố.
Những người thích int* x; đang cố gắng buộc mã của họ vào một thế giới hư cấu nơi loại nằm ở bên trái và mã định danh (tên) ở bên phải.
Tôi nói "hư cấu" bởi vì:
Trong C và C ++, trong trường hợp chung, định danh khai báo được bao quanh bởi thông tin loại.
Điều đó nghe có vẻ điên rồ, nhưng bạn biết đó là sự thật. Dưới đây là một số ví dụ:
int main(int argc, char *argv[])có nghĩa là " mainlà một hàm lấy một intvà một mảng các con trỏ tới charvà trả về một int." Nói cách khác, hầu hết các thông tin loại ở bên phải. Một số người cho rằng khai báo hàm không được tính bởi vì chúng bằng cách nào đó "đặc biệt". OK, chúng ta hãy thử một biến.
void (*fn)(int)có nghĩa fnlà một con trỏ đến một hàm lấy intvà không trả về gì cả.
int a[10]tuyên bố 'a' là một mảng 10 intgiây.
pixel bitmap[height][width].
Rõ ràng, tôi đã chọn các ví dụ anh đào có nhiều thông tin loại bên phải để đưa ra quan điểm của mình. Có rất nhiều khai báo trong đó hầu hết - nếu không phải tất cả - thuộc loại nằm bên trái, như thế struct { int x; int y; } center.
Cú pháp khai báo này phát triển từ mong muốn của K & R để có các tuyên bố phản ánh việc sử dụng. Đọc các khai báo đơn giản là trực quan và đọc các khai báo phức tạp hơn có thể được làm chủ bằng cách học quy tắc phải-trái-phải (đôi khi gọi quy tắc xoắn ốc hoặc chỉ là quy tắc phải-trái).
C đủ đơn giản để nhiều lập trình viên C nắm lấy phong cách này và viết các khai báo đơn giản như int *p.
Trong C ++, cú pháp phức tạp hơn một chút (với các lớp, tham chiếu, mẫu, lớp enum) và, như một phản ứng với sự phức tạp đó, bạn sẽ thấy nỗ lực hơn trong việc tách loại từ định danh trong nhiều khai báo. Nói cách khác, bạn có thể thấy nhiều int* pkhai báo kiểu hơn nếu bạn kiểm tra một lượng lớn mã C ++.
Trong cả hai ngôn ngữ, bạn luôn có thể có kiểu ở bên trái của khai báo biến bởi (1) không bao giờ khai báo nhiều biến trong cùng một câu lệnh và (2) sử dụng typedefs (hoặc khai báo bí danh, trong đó, trớ trêu thay, đặt bí danh định danh ở bên trái của các loại). Ví dụ:
typedef int array_of_10_ints[10];
array_of_10_ints a;
(*fn)giữ con trỏ được liên kết với fnchứ không phải kiểu trả về.
Tôi đã trả lời cho một câu hỏi tương tự trong CP, và vì không ai đề cập đến điều đó, nên ở đây tôi phải chỉ ra rằng C là ngôn ngữ định dạng miễn phí , bất kể kiểu nào bạn chọn đều ổn trong khi trình phân tích cú pháp có thể phân biệt từng mã thông báo. Tính đặc thù này của C dẫn đến một loại rất đặc biệt của cuộc thi gọi là cuộc thi obfuscation C .
Rất nhiều đối số trong chủ đề này là chủ quan rõ ràng và lập luận về "ngôi sao liên kết với tên biến" là ngây thơ. Đây là một vài lập luận không chỉ là ý kiến:
Vòng loại loại con trỏ bị lãng quên
Chính thức, "ngôi sao" không thuộc loại cũng như tên biến, nó là một phần của mục ngữ pháp riêng có tên con trỏ . Cú pháp C chính thức (ISO 9899: 2018) là:
(6.7) khai báo:
khai báo-specifier init-kê khai-list opt;
Trong đó bộ xác định khai báo chứa loại (và lưu trữ) và danh sách init-kê khai chứa con trỏ và tên biến. Mà chúng ta thấy nếu chúng ta mổ xẻ cú pháp danh sách khai báo này thêm:
(6.7.6) declarator:
con trỏ opt trực declarator
...
(6.7.6) con trỏ:
*type-vòng-list lựa chọn
*kiểu vòng loại danh sách lựa chọn con trỏ
Trong đó một người khai báo là toàn bộ khai báo, một người khai báo trực tiếp là mã định danh (tên biến) và một con trỏ là ngôi sao theo sau là một danh sách vòng loại loại tùy chọn thuộc về chính con trỏ.
Điều làm cho các đối số kiểu khác nhau về "ngôi sao thuộc về biến" không nhất quán, là chúng đã quên về các vòng loại loại con trỏ. int* const x, int *const xHoặc int*const x?
Hãy xem xét int *const a, b;, các loại avà là bgì? Không quá rõ ràng rằng "ngôi sao thuộc về biến" nữa. Thay vào đó, người ta sẽ bắt đầu suy nghĩ về nơi constthuộc về.
Bạn chắc chắn có thể đưa ra một đối số âm thanh rằng ngôi sao thuộc về vòng loại loại con trỏ, nhưng không vượt quá điều đó.
Danh sách vòng loại cho con trỏ có thể gây ra sự cố cho những người sử dụng int *akiểu. Những người sử dụng con trỏ bên trong một typedef(mà chúng ta không nên, thực hành rất tệ!) Và nghĩ rằng "ngôi sao thuộc về tên biến" có xu hướng viết ra lỗi rất tinh vi này:
/*** bad code, don't do this ***/
typedef int *bad_idea_t;
...
void func (const bad_idea_t *foo);
Điều này biên dịch sạch sẽ. Bây giờ bạn có thể nghĩ rằng mã được thực hiện const chính xác. Không phải vậy! Mã này vô tình là một const giả chính xác.
Loại foothực tế int*const*- hầu hết các con trỏ bên ngoài được tạo ở chế độ chỉ đọc, không phải là trỏ vào dữ liệu. Vì vậy, bên trong chức năng này, chúng ta có thể làm**foo = n; và nó sẽ thay đổi giá trị biến trong trình gọi.
Điều này là do trong biểu thức const bad_idea_t *foo, *không thuộc về tên biến ở đây! Trong mã giả, khai báo tham số này sẽ được đọc const (bad_idea_t *) foovà không phải là (const bad_idea_t) *foo. Ngôi sao thuộc loại con trỏ ẩn trong trường hợp này - loại là con trỏ và con trỏ đủ điều kiện const được viết là *const.
Nhưng sau đó, gốc rễ của vấn đề trong ví dụ trên là thực hành ẩn con trỏ đằng sau một typedefvà không phải là *phong cách.
Về khai báo nhiều biến trên một dòng
Khai báo nhiều biến trên một dòng được công nhận rộng rãi là thông lệ xấu 1) . CERT-C tổng hợp nó một cách độc đáo như:
DCL04-C. Không khai báo nhiều hơn một biến trên mỗi khai báo
Chỉ cần đọc tiếng Anh, sau đó thông thường đồng ý rằng một tuyên bố nên là một tuyên bố.
Và nó không quan trọng nếu các biến là con trỏ hay không. Khai báo từng biến trên một dòng làm cho mã rõ ràng hơn trong hầu hết mọi trường hợp.
Vì vậy, tranh luận về việc lập trình viên bị nhầm lẫn int* a, blà xấu. Căn nguyên của vấn đề là việc sử dụng nhiều người khai báo, không phải là vị trí của *. Bất kể phong cách, bạn nên viết điều này thay vào đó:
int* a; // or int *a
int b;
Một lý do khác âm nhưng chủ quan sẽ được rằng cho int* aloại alà không có câu hỏiint* và vì vậy sao thuộc với kiểu vòng loại.
Nhưng về cơ bản kết luận của tôi là nhiều lập luận được đăng ở đây chỉ mang tính chủ quan và ngây thơ. Bạn thực sự không thể đưa ra một lập luận hợp lệ cho một trong hai phong cách - đó thực sự là vấn đề sở thích cá nhân chủ quan.
1) CERT-C DCL04-C .
typedef int *bad_idea_t; void func(const bad_idea_t bar); Như nhà tiên tri vĩ đại Dan Saks dạy "Nếu bạn luôn đặt constcàng xa bên phải càng tốt, mà không thay đổi ý nghĩa ngữ nghĩa" thì điều này hoàn toàn chấm dứt là một vấn đề Nó cũng làm cho consttuyên bố của bạn phù hợp hơn để đọc. "Mọi thứ ở bên phải của const const là const, mọi thứ ở bên trái là kiểu của nó." Điều này sẽ áp dụng cho tất cả các consts trong một tuyên bố. Hãy thử vớiint const * * const x;
Xem xét
int *x = new int();
Điều này không dịch sang
int *x;
*x = new int();
Nó thực sự dịch
int *x;
x = new int();
Điều này làm cho int *xký hiệu có phần không nhất quán.
newlà một toán tử C ++. Câu hỏi của anh ấy được gắn thẻ "C" chứ không phải "C ++".
typedefs, nhưng điều đó sẽ thêm sự phức tạp không cần thiết, IMHO.