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ư myVariable2
có kiểu int .
Do đó, phong cách lập trình đầu tiên là trực quan hơn.
int* someVar
cá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, *myVariable
là loại int
, điều này có ý nghĩa.
myVariable
có thể là NULL, trong trường hợp này *myVariable
gâ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 *pointer
thành một giá trị nào đó, chứ không phải pointer
chí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 a
là 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 10
cho a
, nó có nghĩa là tôi muốn gán 10
cho bất kỳ vị trí bộ nhớ nào a
trỏ đế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 *e
sẽ 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 a
là một con trỏ đến một int
giá 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à đó a
là 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 b
và đố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 a
mộ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. a
không được quy định bởi int *a;
. a
thậ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ẽ a
hơn so với nó int
, theo cách mà trong biểu thức 4 + 3 * 7
3
liên kết chặt chẽ 7
hơn so với nó 4
do ư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 *a
trô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 a
tổ 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
.
int
trong trường hợp này. Bước 2. Đọc một tờ khai, bao gồm bất kỳ loại trang trí. *a
trong 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-Specifier
vớ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*a
cuối cùng được trình biên dịch đọc là: " a
có 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 b
mộ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 b
khai 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à " main
là một hàm lấy một int
và một mảng các con trỏ tới char
và 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 fn
là một con trỏ đến một hàm lấy int
và không trả về gì cả.
int a[10]
tuyên bố 'a' là một mảng 10 int
giâ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* p
khai 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 typedef
s (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 fn
chứ 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 x
Hoặc int*const x
?
Hãy xem xét int *const a, b;
, các loại a
và là b
gì? 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 const
thuộ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 *a
kiể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 foo
thự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 *) foo
và 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 typedef
và 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, b
là 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* a
loại a
là 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 const
cà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 const
tuyê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 *x
ký hiệu có phần không nhất quán.
new
là 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.