Sử dụng thành ngữ của các khối tùy ý trong C là gì?


15

Một khối là một danh sách các câu lệnh sẽ được thực thi. Ví dụ về nơi các khối xuất hiện trong C là sau câu lệnh while và trong câu lệnh if

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C cũng cho phép một khối được lồng trong một khối. Tôi có thể sử dụng điều này để sử dụng lại tên biến, giả sử tôi thực sự thích 'x'

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

sẽ in số 5 mười lần. Tôi đoán tôi có thể thấy các tình huống mà việc giữ số lượng tên biến thấp là mong muốn. Có lẽ trong việc mở rộng vĩ mô. Tuy nhiên, tôi không thể thấy bất kỳ lý do khó khăn nào để cần tính năng này. Bất cứ ai có thể giúp tôi hiểu việc sử dụng tính năng này bằng cách cung cấp một số thành ngữ nơi nó được sử dụng.


1
Thành thật mà nói, tôi chỉ đang cố gắng hiểu cú pháp C, và tò mò.
Jonathan Gallagher

Tinh thần của C là tin tưởng vào lập trình viên. Lập trình viên có quyền quyết định để làm cho một cái gì đó tuyệt vời ... hoặc làm cho một cái gì đó khủng khiếp. Cá nhân, tôi không thích tên biến quá tải nhưng một lập trình viên khác có thể. Vào cuối ngày, nếu chúng ta hoàn thành những gì chúng ta phải làm với các lỗi tối thiểu ... tại sao chúng ta nên tranh luận?
Fiddling Bits

1
Đó là cảm giác tôi nhận được từ C. Tôi hoàn toàn dành cho cú pháp hỗ trợ các kiểu mã hóa khác nhau (miễn là ngữ nghĩa của ngôn ngữ là ổn). Chỉ là ... tôi đã thấy điều này, và phản ứng ngay lập tức của tôi là, tôi có thể áp dụng chuyển đổi nguồn thành nguồn đổi tên tất cả các biến trong một khối bằng tên mới và loại bỏ hoàn toàn khối. Bất cứ khi nào tôi nghĩ rằng tôi có thể thoát khỏi một cái gì đó, tôi cho rằng có một cái gì đó tôi đã bỏ lỡ.
Jonathan Gallagher

Thời gian buồn cười, với tư cách là một lập trình viên không phải C, tôi tình cờ thấy cú pháp này ngày hôm nay trong mã C và tò mò không biết nó dùng để làm gì. Tôi vui mừng bạn đã hỏi.
Brandon

Câu trả lời:


8

Ý tưởng không phải là giữ cho số lượng tên biến ở mức thấp hay nói cách khác là khuyến khích sử dụng lại tên, mà là để giới hạn phạm vi của các biến. Nếu bạn có:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

sau đó phạm vi yđược giới hạn trong khối, có nghĩa là bạn có thể quên nó trước hoặc sau khối. Bạn thấy điều này được sử dụng thường xuyên nhất liên quan đến các vòng lặp và điều kiện. Bạn cũng thấy nó thường xuyên hơn trong các ngôn ngữ giống như C ++, trong đó một biến đi ra khỏi phạm vi gây ra sự phá hủy.


Tôi nghĩ rằng khối tự nhiên xảy ra với các điều kiện, vòng lặp và các cơ quan chức năng. Điều tôi tò mò là ở C, tôi có thể đặt một khối ở bất cứ đâu. Nhận xét thứ hai của bạn về C ++ rất thú vị - việc để lại một phạm vi gây ra sự hủy diệt. Có phải điều này chỉ đề cập đến bộ sưu tập rác hay là một cách sử dụng khác sau đây: tôi có thể lấy một thân hàm, có "phần" riêng biệt và sử dụng các khối để kiểm soát dấu chân bộ nhớ bằng cách kích hoạt phá hủy không gian biến?
Jonathan Gallagher

1
Theo như tôi biết, C sẽ phân bổ tất cả bộ nhớ cho tất cả các biến trong hàm. Việc họ thoát khỏi phạm vi một phần thông qua một chức năng sẽ không có lợi ích hiệu suất trong C. Tương tự như vậy, việc biến các biến vào phạm vi "chỉ đôi khi" sẽ không thay đổi dấu chân bộ nhớ trong bất kỳ cuộc gọi nào đến chức năng
Gankro 23/12/13

@Gankro Nó có thể có tác động nếu có nhiều phạm vi lồng nhau độc quyền. Một trình biên dịch có thể sử dụng lại một đoạn bộ nhớ preallocated duy nhất cho các biến trong mỗi phạm vi này. Tất nhiên, lý do tại sao nó không xuất hiện trong tâm trí là nếu một phạm vi tùy ý duy nhất đã là một dấu hiệu bạn có thể cần trích xuất thành một hàm, hai hoặc nhiều phạm vi tùy ý chắc chắn là một dấu hiệu tốt cho thấy bạn cần cấu trúc lại. Tuy nhiên, nó bật lên như một giải pháp lành mạnh trong những thứ như khối trường hợp chuyển đổi theo thời gian.
tne

16

Bởi vì trong những ngày xưa của C, các biến mới chỉ có thể được khai báo trong một khối mới.

Bằng cách này, các lập trình viên có thể giới thiệu các biến mới ở giữa một hàm mà không làm rò rỉ nó và giảm thiểu việc sử dụng ngăn xếp.

Với tối ưu hóa ngày nay, nó là vô ích và là một dấu hiệu cho thấy bạn cần xem xét trích xuất khối trong chức năng riêng của nó.

Trong một câu lệnh chuyển đổi, sẽ rất hữu ích khi đặt các trường hợp trong các khối riêng của chúng để tránh khai báo kép.

Trong C ++, ví dụ rất hữu ích cho các bộ bảo vệ khóa RAII và đảm bảo các hàm hủy chạy khóa phát hành khi thực thi vượt quá phạm vi và vẫn thực hiện các công việc khác ngoài phần quan trọng.


2
+1 cho bộ bảo vệ khóa RAII. Điều đó đang được nói, không phải khái niệm tương tự này cũng hữu ích cho việc phân bổ bộ đệm ngăn xếp lớn trong các phần của một thói quen? Tôi chưa bao giờ làm điều đó, nhưng nghe có vẻ như điều gì đó chắc chắn có thể xảy ra trong một số mã nhúng ...
J Trana 23/12/13

2

Tôi sẽ không xem nó là các khối "tùy ý". Đây không phải là một tính năng có ý nghĩa rất lớn đối với nhà phát triển sử dụng, nhưng cách C sử dụng các khối cho phép cùng một cấu trúc khối được sử dụng ở một số nơi có cùng ngữ nghĩa. Một khối (trong C) là một phạm vi mới và các biến còn lại sẽ bị loại bỏ. Đây là thống nhất bất kể khối được sử dụng như thế nào.

Trong các ngôn ngữ khác, đây không phải là trường hợp. Điều này có lợi thế là cho phép ít lạm dụng như bạn thể hiện, nhưng nhược điểm mà các khối hành xử khác nhau tùy thuộc vào bối cảnh chúng đang ở.

Tôi hiếm khi thấy các khối độc lập được sử dụng trong C hoặc C ++ - thường khi có cấu trúc lớn hoặc một đối tượng đại diện cho một kết nối hoặc thứ gì đó mà bạn muốn buộc phá hủy. Thông thường đây là một gợi ý rằng chức năng của bạn đang làm quá nhiều thứ và / hoặc quá dài.


1
Thật không may, tôi thường xuyên nhìn thấy các khối độc lập - trong hầu hết các trường hợp vì chức năng này hoạt động rất nhiều và / hoặc quá dài.
mattnz

Tôi cũng thường xuyên nhìn thấy và viết các khối độc lập trong C ++ nhưng chỉ để buộc phá hủy các hàm bao quanh các khóa và các tài nguyên được chia sẻ khác.
J Trana

2

Bạn phải nhận ra, các nguyên tắc lập trình dường như rõ ràng bây giờ không phải lúc nào cũng như vậy. C thực hành tốt nhất phụ thuộc rất nhiều vào tuổi của bạn. Khi C lần đầu tiên được giới thiệu, việc phá mã của bạn thành các hàm nhỏ được coi là quá kém hiệu quả. Về cơ bản, Dennis Ritchie đã nói dối và cho biết các cuộc gọi chức năng thực sự hiệu quả trong C (lúc đó họ không có), đó là điều khiến mọi người bắt đầu sử dụng chúng nhiều hơn, mặc dù các lập trình viên C bằng cách nào đó chưa bao giờ thực sự vượt qua văn hóa tối ưu hóa sớm.

Đó một thực hành lập trình tốt ngay cả ngày nay để giới hạn phạm vi các biến của bạn càng nhỏ càng tốt. Ngày nay, chúng ta thường làm điều đó bằng cách tạo một hàm mới, nhưng nếu các hàm được coi là đắt tiền, thì việc giới thiệu một khối mới là một cách hợp lý để giới hạn phạm vi của bạn mà không cần phải gọi hàm.

Tuy nhiên, tôi đã bắt đầu lập trình trong C hơn 20 năm trước, khi bạn phải khai báo tất cả các biến của bạn ở đầu phạm vi và tôi không nhớ lại bóng đổ biến như thế từng được coi là phong cách tốt. Khai báo lại thành hai khối lần lượt như trong câu lệnh chuyển đổi, có, nhưng không bị bóng. Có thể nếu biến đã được sử dụng và tên cụ thể rất đặc biệt cho API bạn đang gọi, ví dụ như destsrctrong strcpy.


1

Các khối tùy ý rất hữu ích để giới thiệu các biến trung gian chỉ được sử dụng trong các trường hợp đặc biệt của tính toán.

Đây là một mô hình phổ biến trong điện toán khoa học, trong đó các thủ tục số thường:

  1. dựa vào rất nhiều thông số hoặc số lượng trung gian;
  2. phải đối phó với rất nhiều trường hợp đặc biệt.

Do điểm thứ hai, rất hữu ích khi giới thiệu các biến tạm thời có phạm vi giới hạn, điều này đạt được bằng cách sử dụng một khối tùy ý hoặc bằng cách giới thiệu một hàm phụ trợ.

Mặc dù giới thiệu một chức năng phụ trợ có thể trông giống như không có trí tuệ hoặc thực hành tốt nhất để theo dõi một cách mù quáng, nhưng thực sự có rất ít lợi ích để làm như vậy trong tình huống cụ thể này.

Bởi vì có rất nhiều tham số và số lượng trung gian, chúng tôi muốn giới thiệu một cấu trúc để truyền các tham số này cho hàm phụ trợ.

Nhưng, vì chúng tôi muốn có kết quả với thực tiễn của mình, chúng tôi sẽ không chỉ giới thiệu một chức năng phụ trợ mà là một số chức năng. Vì vậy, chúng tôi giới thiệu các cấu trúc đặc biệt truyền tải các tham số cho từng hàm, trong đó giới thiệu rất nhiều mã trên đầu để di chuyển các tham số qua lại hoặc chúng tôi giới thiệu một cấu trúc sẽ điều khiển tất cả cấu trúc bảng tính, chứa tất cả các biến của chúng tôi nhưng trông giống như một gói bit mà không có tính nhất quán, trong đó bất cứ lúc nào chỉ có một nửa tham số có ý nghĩa thú vị.

Do đó, các cấu trúc phụ trợ này thường cồng kềnh và sử dụng chúng có nghĩa là lựa chọn giữa mã hóa hoặc giới thiệu một sự trừu tượng có phạm vi quá rộng và làm suy yếu ý nghĩa của chương trình, thay vì làm cho nó bị mờ đi .

Giới thiệu các chức năng phụ trợ có thể dễ dàng kiểm tra đơn vị chương trình bằng cách đưa ra mức độ chi tiết kiểm tra tốt hơn nhưng kết hợp kiểm tra đơn vị để không kiểm tra các quy trình cấp thấp và kiểm tra hồi quy dưới dạng so sánh (với chữ số) của các quy trình số cũng làm việc tốt như nhau .


0

Nói chung, việc sử dụng lại tên biến theo cách này gây ra quá nhiều nhầm lẫn cho người đọc mã trong tương lai của bạn. Tốt hơn là chỉ cần đặt tên cho biến bên trong một cái gì đó khác. Trong thực tế, ngôn ngữ C # đặc biệt không cho phép sử dụng các biến theo cách này.

Tôi có thể thấy việc sử dụng các khối lồng nhau trong mở rộng macro sẽ hữu ích như thế nào để ngăn chặn các xung đột đặt tên biến.


0

Đó là cho phạm vi những thứ thường không có phạm vi ở cấp độ đó. Điều này là cực kỳ hiếm, và nói chung tái cấu trúc là một lựa chọn tốt hơn, nhưng tôi đã chạy qua nó một hoặc hai lần trong các câu lệnh chuyển đổi:

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

Khi bạn xem xét bảo trì, tái cấu trúc những thứ này nên được cân bằng với việc có tất cả các triển khai liên tiếp, miễn là mỗi dòng chỉ dài một vài dòng. Có thể dễ dàng có tất cả chúng cùng nhau, hơn là một danh sách các tên hàm.

Trong trường hợp của tôi, nếu tôi nhớ lại một cách chính xác, hầu hết các trường hợp là các biến thể nhỏ của cùng một mã - ngoại trừ một trong số chúng giống hệt với mã khác, chỉ cần thêm tiền xử lý. Một công tắc cuối cùng là hình thức đơn giản nhất để sử dụng mã và phạm vi bổ sung cho phép sử dụng các biến cục bộ bổ sung mà nó và các trường hợp khác không phải lo lắng (chẳng hạn như tên biến vô tình trùng lặp với một biến được xác định trước công tắc nhưng sử dụng sau).

Lưu ý rằng câu lệnh chuyển đổi chỉ đơn giản là việc sử dụng nó mà tôi đã chạy qua. Tôi chắc chắn rằng nó cũng có thể áp dụng ở nơi khác, nhưng tôi không nhớ là đã thấy chúng được sử dụng hiệu quả theo cách đó.

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.