Đây là thực hành tuyệt vời .
Bằng cách tạo các biến bên trong các vòng lặp, bạn đảm bảo phạm vi của chúng được giới hạn bên trong vòng lặp. Nó không thể được tham chiếu hay gọi là bên ngoài vòng lặp.
Cách này:
Nếu tên của biến là một chút "chung chung" (như "i"), không có rủi ro để trộn nó với một biến khác có cùng tên ở đâu đó trong mã của bạn (cũng có thể được giảm thiểu bằng cách sử dụng -Wshadow
hướng dẫn cảnh báo trên GCC)
Trình biên dịch biết rằng phạm vi biến được giới hạn bên trong vòng lặp, và do đó sẽ đưa ra một thông báo lỗi thích hợp nếu biến bị lỗi do tham chiếu ở nơi khác.
Cuối cùng nhưng không kém phần quan trọng, một số tối ưu hóa chuyên dụng có thể được trình biên dịch thực hiện hiệu quả hơn (quan trọng nhất là đăng ký phân bổ), vì nó biết rằng biến không thể được sử dụng bên ngoài vòng lặp. Ví dụ, không cần lưu trữ kết quả để sử dụng lại sau này.
Tóm lại, bạn có quyền làm điều đó.
Tuy nhiên, lưu ý rằng biến không được phép giữ lại giá trị của nó giữa mỗi vòng lặp. Trong trường hợp như vậy, bạn có thể cần phải khởi tạo nó mỗi lần. Bạn cũng có thể tạo một khối lớn hơn, bao gồm vòng lặp, với mục đích duy nhất là khai báo các biến phải giữ giá trị của chúng từ vòng này sang vòng khác. Điều này thường bao gồm chính bộ đếm vòng lặp.
{
int i, retainValue;
for (i=0; i<N; i++)
{
int tmpValue;
/* tmpValue is uninitialized */
/* retainValue still has its previous value from previous loop */
/* Do some stuff here */
}
/* Here, retainValue is still valid; tmpValue no longer */
}
Đối với câu hỏi số 2: Biến được phân bổ một lần, khi hàm được gọi. Trong thực tế, từ góc độ phân bổ, nó (gần như) giống như khai báo biến ở đầu hàm. Sự khác biệt duy nhất là phạm vi: biến không thể được sử dụng bên ngoài vòng lặp. Thậm chí có thể là biến không được phân bổ, chỉ cần sử dụng lại một số vị trí miễn phí (từ biến khác có phạm vi đã kết thúc).
Với phạm vi hạn chế và chính xác hơn đến tối ưu hóa chính xác hơn. Nhưng quan trọng hơn, nó làm cho mã của bạn an toàn hơn, với ít trạng thái hơn (tức là các biến) để lo lắng khi đọc các phần khác của mã.
Điều này đúng ngay cả bên ngoài một if(){...}
khối. Thông thường, thay vì:
int result;
(...)
result = f1();
if (result) then { (...) }
(...)
result = f2();
if (result) then { (...) }
viết an toàn hơn:
(...)
{
int const result = f1();
if (result) then { (...) }
}
(...)
{
int const result = f2();
if (result) then { (...) }
}
Sự khác biệt có vẻ nhỏ, đặc biệt là trên một ví dụ nhỏ như vậy. Nhưng trên cơ sở mã lớn hơn, nó sẽ giúp: bây giờ không có rủi ro để vận chuyển một số result
giá trị từ f1()
để f2()
chặn. Mỗi cái result
được giới hạn nghiêm ngặt trong phạm vi riêng của nó, làm cho vai trò của nó chính xác hơn. Từ góc độ người đánh giá, nó đẹp hơn nhiều, vì anh ta có ít biến số trạng thái dài hơn để lo lắng và theo dõi.
Ngay cả trình biên dịch sẽ giúp tốt hơn: giả sử rằng, trong tương lai, sau một số thay đổi mã sai, result
không được khởi tạo đúng cách với f2()
. Phiên bản thứ hai đơn giản sẽ từ chối hoạt động, nêu rõ thông báo lỗi rõ ràng tại thời gian biên dịch (cách tốt hơn thời gian chạy). Phiên bản đầu tiên sẽ không phát hiện ra bất cứ điều gì, kết quả của việc f1()
đơn giản sẽ được kiểm tra lần thứ hai, bị nhầm lẫn cho kết quả của f2()
.
Thông tin bổ sung
Công cụ nguồn mở CppCheck (một công cụ phân tích tĩnh cho mã C / C ++) cung cấp một số gợi ý tuyệt vời về phạm vi tối ưu của các biến.
Đáp lại nhận xét về phân bổ: Quy tắc trên là đúng trong C, nhưng có thể không dành cho một số lớp C ++.
Đối với các kiểu và cấu trúc tiêu chuẩn, kích thước của biến được biết tại thời điểm biên dịch. Không có thứ gọi là "xây dựng" trong C, vì vậy không gian cho biến sẽ được phân bổ đơn giản vào ngăn xếp (không có bất kỳ khởi tạo nào), khi hàm được gọi. Đó là lý do tại sao có chi phí "không" khi khai báo biến trong vòng lặp.
Tuy nhiên, đối với các lớp C ++, có điều xây dựng này mà tôi biết ít hơn nhiều. Tôi đoán phân bổ có lẽ sẽ không phải là vấn đề, vì trình biên dịch sẽ đủ thông minh để sử dụng lại cùng một không gian, nhưng việc khởi tạo có thể sẽ diễn ra ở mỗi lần lặp.