Tại sao phải khai báo một biến trong một dòng và gán cho nó trong dòng tiếp theo?


101

Tôi thường thấy trong mã C và C ++ quy ước sau:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

thay vì

some_type val = something;
some_type *ptr = &something_else;

Ban đầu tôi cho rằng đây là thói quen còn sót lại từ những ngày bạn phải khai báo tất cả các biến cục bộ ở đầu phạm vi. Nhưng tôi đã học được cách không từ bỏ quá nhanh thói quen của các nhà phát triển kỳ cựu. Vì vậy, có lý do chính đáng để khai báo trong một dòng và gán sau đó không?


12
+1 cho "Tôi đã học được cách không từ bỏ quá nhanh thói quen của các nhà phát triển kỳ cựu." Đó là một bài học khôn ngoan để học hỏi.
tự đại diện

Câu trả lời:


92

C

Trong C89, tất cả các khai báo phải ở đầu một phạm vi ( { ... }), nhưng yêu cầu này đã được loại bỏ nhanh chóng (đầu tiên là với các phần mở rộng trình biên dịch và sau đó là tiêu chuẩn).

C ++

Những ví dụ này không giống nhau. some_type val = something;gọi hàm tạo sao chép trong khi val = something;gọi hàm tạo mặc định và sau đó là operator=hàm. Sự khác biệt này thường rất quan trọng.

Thói quen

Một số người thích khai báo biến trước và sau đó xác định chúng, trong trường hợp họ định dạng lại mã của họ sau đó với khai báo ở một điểm và định nghĩa ở điểm khác.

Về con trỏ, một số người chỉ có thói quen khởi tạo mọi con trỏ tới NULLhoặc nullptr, bất kể họ làm gì với con trỏ đó.


1
Sự khác biệt lớn cho C ++, cảm ơn. Còn ở đồng bằng C thì sao?
Jonathan Sterling

13
Thực tế là MSVC vẫn không hỗ trợ khai báo trừ khi bắt đầu một khối khi nó biên dịch ở chế độ C là nguồn gây khó chịu vô tận cho tôi.
Michael Burr

5
@Michael Burr: Điều này là do MSVC hoàn toàn không hỗ trợ C99.
orlp

3
"some_type val = cái gì đó; kêu gọi các nhà xây dựng bản sao": nó có thể gọi constructor sao chép, nhưng tiêu chuẩn cho phép trình biên dịch để bõ mẫu âm chót mặc định-xây dựng một tempory, sao chép xây dựng val và phá hủy tạm thời và trực tiếp xây dựng val sử dụng một some_typeconstructor lấy somethinglàm đối số duy nhất. Đây là một trường hợp cạnh rất thú vị và khác thường trong C ++ ... nó có nghĩa là có một giả định về ý nghĩa ngữ nghĩa của các hoạt động này.

2
@Aerovistae: đối với các loại tích hợp, chúng giống nhau, nhưng không phải lúc nào cũng giống nhau đối với các loại do người dùng xác định.
orlp

27

Bạn đã gắn thẻ câu hỏi C và C ++ của mình cùng một lúc, trong khi câu trả lời khác nhau đáng kể trong các ngôn ngữ này.

Thứ nhất, từ ngữ của tiêu đề câu hỏi của bạn là không chính xác (hoặc chính xác hơn là không liên quan đến chính câu hỏi). Trong cả hai ví dụ của bạn, biến được khai báo và định nghĩa đồng thời, trong một dòng. Sự khác biệt giữa các ví dụ của bạn là trong lần đầu tiên, các biến được để lại chưa được khởi tạo hoặc khởi tạo với giá trị giả và sau đó nó được gán một giá trị có ý nghĩa sau đó. Trong ví dụ thứ hai, các biến được khởi tạo ngay lập tức.

Thứ hai, trong ngôn ngữ C ++, như @nightcracker lưu ý trong câu trả lời của ông, hai cấu trúc này khác nhau về mặt ngữ nghĩa. Cái đầu tiên dựa vào khởi tạo trong khi cái thứ hai - trên bài tập. Trong C ++, các hoạt động này là quá tải và do đó có thể dẫn đến các kết quả khác nhau (mặc dù người ta có thể lưu ý rằng việc tạo ra quá tải không tương đương của khởi tạo và gán không phải là một ý tưởng hay).

Trong ngôn ngữ C tiêu chuẩn ban đầu (C89 / 90), việc khai báo các biến ở giữa khối là bất hợp pháp, đó là lý do tại sao bạn có thể thấy các biến được khai báo chưa được khởi tạo (hoặc khởi tạo với các giá trị giả) ở đầu khối và sau đó được gán có ý nghĩa giá trị sau này, khi những giá trị có ý nghĩa trở nên có sẵn.

Trong ngôn ngữ C99, bạn có thể khai báo các biến ở giữa khối (giống như trong C ++), điều đó có nghĩa là cách tiếp cận đầu tiên chỉ cần thiết trong một số trường hợp cụ thể khi không biết trình khởi tạo tại điểm khai báo. (Điều này cũng áp dụng cho C ++).


2
@Jonathan Sterling: Tôi đọc ví dụ của bạn. Có lẽ bạn cần theo dõi thuật ngữ chuẩn của ngôn ngữ C và C ++. Cụ thể, về các điều khoản khai báođịnh nghĩa , có ý nghĩa cụ thể trong các ngôn ngữ này. Tôi sẽ lặp lại một lần nữa: trong cả hai ví dụ của bạn, các biến được khai báo và định nghĩa trong một dòng. Trong C / C ++, dòng some_type val;ngay lập tức khai báoxác định biến val. Đây là những gì tôi có nghĩa trong câu trả lời của tôi.

1
Tôi thấy những gì bạn có ý nghĩa ở đó. Bạn chắc chắn đúng về việc tuyên bốđịnh nghĩa là khá vô nghĩa theo cách tôi đã sử dụng chúng. Tôi hy vọng bạn chấp nhận lời xin lỗi của tôi cho từ ngữ kém, và bình luận thiếu suy nghĩ.
Jonathan Sterling

1
Vì vậy, nếu sự đồng thuận là việc tuyên bố của người Bỉ là từ sai, tôi đề nghị ai đó có kiến ​​thức tốt hơn về tiêu chuẩn hơn tôi sẽ chỉnh sửa trang Wikibooks.
Jonathan Sterling

2
Trong bất kỳ bối cảnh nào khác, khai báo sẽ là từ đúng, nhưng vì khai báo là một khái niệm được xác định rõ ràng , với hậu quả, trong C và C ++, bạn không thể sử dụng nó một cách lỏng lẻo như trong các bối cảnh khác.
orlp

2
@ybungalobill: Bạn sai rồi. Tuyên bốđịnh nghĩa trong C / C ++ không phải là khái niệm loại trừ lẫn nhau. Trên thực tế, định nghĩa chỉ là một hình thức khai báo cụ thể . Mỗi định nghĩa là một tuyên bố cùng một lúc (với một vài ngoại lệ). Có các khai báo xác định (tức là định nghĩa) và khai báo không xác định. Hơn nữa, thông thường khai báo nhiệt được sử dụng mọi lúc (ngay cả khi đó là một định nghĩa), ngoại trừ bối cảnh khi sự phân biệt giữa hai là quan trọng.

13

Tôi nghĩ đó là một thói quen cũ, còn sót lại từ thời "khai báo địa phương". Và do đó, như là câu trả lời cho câu hỏi của bạn: Không tôi không nghĩ có lý do chính đáng. Tôi không bao giờ tự làm điều đó.


4

Tôi đã nói điều gì đó về câu trả lời của tôi cho câu hỏi của Helium3 .

Về cơ bản, tôi nói đó là một trợ giúp trực quan để dễ dàng nhìn thấy những gì được thay đổi.

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}

4

Các câu trả lời khác là khá tốt. Có một số lịch sử xung quanh điều này trong C. Trong C ++ có sự khác biệt giữa một hàm tạo và toán tử gán.

Tôi ngạc nhiên không ai đề cập đến điểm bổ sung: giữ cho các khai báo tách biệt với việc sử dụng một biến đôi khi có thể dễ đọc hơn rất nhiều.

Nói một cách trực quan, khi đọc mã, các tạo tác trần tục hơn, chẳng hạn như các loại và tên của các biến, không phải là thứ nhảy ra khỏi bạn. Đó là những câu mà bạn thường quan tâm nhất, dành nhiều thời gian để nhìn chằm chằm nhất, và vì vậy có xu hướng liếc qua phần còn lại.

Nếu tôi có một số loại, tên và phân công tất cả diễn ra trong cùng một không gian chật hẹp, đó là một chút quá tải thông tin. Hơn nữa, nó có nghĩa là một cái gì đó quan trọng đang diễn ra trong không gian mà tôi thường liếc qua.

Có vẻ hơi phản cảm khi nói, nhưng đây là một trường hợp trong đó làm cho nguồn của bạn chiếm nhiều không gian theo chiều dọc hơn có thể làm cho nó tốt hơn. Tôi thấy điều này giống như lý do tại sao bạn không nên viết các dòng bị kẹt, làm số lượng lớn con số và gán số trong một không gian thẳng đứng chặt chẽ - chỉ vì ngôn ngữ cho phép bạn thoát khỏi những điều như vậy không có nghĩa là bạn nên làm tất cả thời gian :-)


2

Trong C, đây là cách thực hành chuẩn vì các biến phải được khai báo khi bắt đầu hàm, không giống như trong C ++, nơi nó có thể được khai báo ở bất cứ đâu trong thân hàm được sử dụng sau đó. Con trỏ được đặt thành 0 hoặc NULL, vì nó chỉ đảm bảo rằng con trỏ trỏ đến không có rác. Mặt khác, không có lợi thế đáng kể nào mà tôi có thể nghĩ ra, điều này bắt buộc bất cứ ai phải làm như vậy.


2

Ưu điểm cho việc định vị các định nghĩa biến và khởi tạo có ý nghĩa của chúng:

  • nếu các biến thường được gán một giá trị có ý nghĩa khi chúng xuất hiện lần đầu tiên trong mã (một góc nhìn khác về cùng một điều: bạn trì hoãn sự xuất hiện của chúng cho đến khi một giá trị có ý nghĩa là không thể chấp nhận được) thì sẽ không có khả năng chúng vô tình được sử dụng với giá trị vô nghĩa hoặc chưa được xác định ( Điều có thể dễ dàng xảy ra là một số khởi tạo vô tình bị bỏ qua do các câu điều kiện, đánh giá ngắn mạch, ngoại lệ, v.v.)

  • có thể hiệu quả hơn

    • tránh các chi phí thiết lập giá trị ban đầu (xây dựng mặc định hoặc khởi tạo thành một số giá trị sentinel như NULL)
    • operator= đôi khi có thể kém hiệu quả hơn và yêu cầu một đối tượng tạm thời
    • đôi khi (đặc biệt đối với các hàm nội tuyến) trình tối ưu hóa có thể loại bỏ một số / tất cả sự không hiệu quả

  • tối thiểu hóa phạm vi của các biến lần lượt giảm thiểu số lượng biến trung bình đồng thời trong phạm vi : điều này

    • giúp dễ dàng theo dõi tinh thần các biến trong phạm vi, luồng thực thi và câu lệnh có thể ảnh hưởng đến các biến đó và nhập giá trị của chúng
    • ít nhất là đối với một số đối tượng phức tạp và mờ đục, điều này làm giảm việc sử dụng tài nguyên (heap, thread, bộ nhớ chia sẻ, mô tả) của chương trình
  • đôi khi ngắn gọn hơn khi bạn không lặp lại tên biến trong định nghĩa sau đó trong một nhiệm vụ có ý nghĩa ban đầu

  • cần thiết cho một số loại nhất định như tham chiếu và khi bạn muốn đối tượng trở thành const

Các đối số để nhóm các định nghĩa biến:

  • đôi khi nó thuận tiện và / hoặc súc tích để xác định loại của một số biến:

    the_same_type v1, v2, v3;

    (nếu lý do chỉ là tên loại quá dài hoặc phức tạp, typedefđôi khi có thể tốt hơn)

  • đôi khi, mong muốn nhóm các biến độc lập với việc sử dụng chúng để nhấn mạnh tập hợp các biến (và loại) liên quan đến một số thao tác:

    type v1;
    type v2; type v3;

    Điều này nhấn mạnh tính phổ biến của loại và giúp thay đổi chúng dễ dàng hơn một chút, trong khi vẫn bám vào một biến trên mỗi dòng tạo điều kiện sao chép-dán, //nhận xét , v.v.

Như thường thấy trong lập trình, trong khi có thể có một lợi ích thực nghiệm rõ ràng cho một thực hành trong hầu hết các tình huống, thì thực tế khác thực sự có thể tốt hơn rất nhiều trong một vài trường hợp.


Tôi muốn nhiều ngôn ngữ sẽ phân biệt trường hợp mã khai báo và đặt giá trị của biến không bao giờ được viết ở nơi khác, mặc dù các biến mới có thể sử dụng cùng tên [nghĩa là hành vi sẽ giống nhau cho dù các câu lệnh sau sử dụng cùng một biến hay một cái khác], từ những nơi mà mã tạo ra một biến phải ghi được ở nhiều nơi. Mặc dù cả hai trường hợp sử dụng sẽ thực hiện theo cùng một cách, nhưng biết khi nào các biến có thể thay đổi là rất hữu ích khi cố gắng theo dõi các lỗi.
supercat
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.