Biến khai báo trong for-loop là biến cục bộ?


133

Tôi đã sử dụng C # khá lâu nhưng chưa bao giờ nhận ra những điều sau:

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

Vậy tại sao tôi không thể sử dụng giá trị của 'i' bên ngoài khối for nếu nó không cho phép tôi khai báo một biến có tên này?

Tôi nghĩ rằng biến lặp được sử dụng bởi vòng lặp for chỉ hợp lệ trong phạm vi của nó.


8
Bởi vì phạm vi khối bên ngoài bao gồm phạm vi của vòng lặp for
V4Vendetta

3
Một cách tôi nghĩ về nó (và một số hướng dẫn kiểu mã yêu cầu điều này, đặc biệt đối với các ngôn ngữ được nhập động) là tất cả các biến được khai báo trong một phạm vi có thể được khai báo khi bắt đầu phạm vi đó, có nghĩa là hàm của bạn có thể được viết lạiint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith

2
@ V4Vendetta: Đó là cách khác. Khối bên trong là một hộp đen cho khối cha.
Sebastian Mach

4
Ngoài các lý do kỹ thuật tại sao điều này là không thể, tại sao điều này (hoặc một biến thể của điều này) sẽ trở thành một điều hợp lý để làm?! Nó rõ ràng phục vụ một mục đích khác, vì vậy hãy đặt cho nó một cái tên khác.
Dave

Tôi hoàn toàn không nhận ra điều này nhưng có vẻ như đó là một hạn chế hoàn toàn.
alan2here

Câu trả lời:


119

Lý do bạn không được phép xác định một biến có cùng tên trong cả vòng lặp for cũng như bên ngoài vòng lặp for là vì các biến trong phạm vi bên ngoài là hợp lệ trong phạm vi bên trong. Có nghĩa là sẽ có hai biến 'i' trong vòng lặp for nếu điều này được cho phép.

Xem: Phạm vi MSDN

Đặc biệt:

Phạm vi của một biến cục bộ được khai báo trong khai báo biến cục bộ (Mục 8.5.1) là khối trong đó khai báo xảy ra.

Phạm vi của một biến cục bộ được khai báo trong bộ khởi tạo của câu lệnh for (Mục 8.8.3) là bộ khởi tạo, điều kiện for, bộ lặp for và câu lệnh chứa của câu lệnh for.

Và cũng: Khai báo biến cục bộ (Mục 8.5.1 của đặc tả C #)

Đặc biệt:

Phạm vi của một biến cục bộ được khai báo trong một khai báo biến cục bộ là khối trong đó khai báo xảy ra. Đó là một lỗi khi đề cập đến một biến cục bộ ở vị trí văn bản có trước khai báo biến cục bộ của biến cục bộ. Trong phạm vi của một biến cục bộ, đó là lỗi thời gian biên dịch để khai báo một biến cục bộ hoặc hằng số khác có cùng tên.

(Nhấn mạnh của tôi.)

Điều đó có nghĩa là phạm vi của ivòng lặp for của bạn, là vòng lặp for. Trong khi đó phạm vi ibên ngoài vòng lặp for của bạn là toàn bộ phương thức chính cộng với vòng lặp for. Có nghĩa là bạn có hai lần xuất hiện ibên trong vòng lặp không hợp lệ theo quy định trên.

Lý do tại sao bạn không được phép làm int A = i;là vì int ichỉ có phạm vi sử dụng trong forvòng lặp. Do đó, nó không còn có thể truy cập bên ngoài forvòng lặp.

Như bạn có thể thấy cả hai vấn đề này là kết quả của phạm vi; vấn đề đầu tiên ( int i = 4;) sẽ dẫn đến hai ibiến trong forphạm vi vòng lặp. Trong khi đó int A = i;sẽ dẫn đến quyền truy cập vào một biến nằm ngoài phạm vi.

Thay vào đó, những gì bạn có thể làm là tuyên bố isẽ nằm trong phạm vi của toàn bộ phương thức và sau đó sử dụng nó trong cả phương thức cũng như phạm vi vòng lặp for. Điều này sẽ tránh phá vỡ một trong hai quy tắc.

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

CHỈNH SỬA :

Trình biên dịch C # tất nhiên có thể được thay đổi để cho phép mã này biên dịch khá hợp lệ. Sau khi tất cả điều này là hợp lệ:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

Nhưng nó có thực sự có lợi cho khả năng đọc và bảo trì mã của bạn để có thể viết mã như:

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

Hãy suy nghĩ về khả năng sai lầm ở đây, ibản in cuối cùng ra 0 hay 4? Bây giờ đây là một ví dụ rất nhỏ, một ví dụ khá dễ theo dõi và theo dõi nhưng nó chắc chắn ít được bảo trì và dễ đọc hơn nhiều so với việc khai báo bên ngoài ibằng một tên khác.

Lưu ý:

Xin lưu ý, quy tắc phạm vi của C # khác với quy tắc phạm vi của C ++ . Trong C ++, các biến chỉ trong phạm vi từ nơi chúng được khai báo cho đến khi kết thúc khối. Điều này sẽ làm cho mã của bạn trở thành một cấu trúc hợp lệ trong C ++.


Làm cho ý nghĩa, tôi nghĩ rằng nó nên lỗi trên ibiến bên trong mặc dù; điều đó dường như rõ ràng hơn với tôi
George Duckett

Nhưng phạm vi là cho lệnh và {} của nó? Hay nó có nghĩa là cha mẹ của nó {}?
John V

2
Chà, nếu tôi khai báo 'A' SAU câu lệnh for, nó không hợp lệ trong vòng lặp for vì nó được khai báo sau. Đó là lý do tại sao tôi không hiểu tại sao cùng tên không thể được sử dụng.
John V

9
Có lẽ câu trả lời này, về tính đầy đủ, chỉ ra rằng các quy tắc phạm vi C # khác với các quy tắc cho C ++ trong trường hợp này. Trong C ++, các biến chỉ nằm trong phạm vi từ nơi chúng được khai báo cho đến khi kết thúc khối (xem msdn.microsoft.com/en-us/l Library / b7kfh662 ( v = vs.80 ) .aspx ).
AAT

2
Lưu ý rằng Java có một cách tiếp cận trung gian giữa C ++ và C #: vì đây là ví dụ của OP sẽ hợp lệ trong Java, nhưng nếu iđịnh nghĩa bên ngoài được di chuyển trước vòng lặp for, iđịnh nghĩa bên trong sẽ được đánh dấu là không hợp lệ.
Nicola Musatti

29

Câu trả lời J.Kommer là đúng: một thời gian ngắn, nó là bất hợp pháp cho một biến cục bộ được khai báo trong một không gian khai báo biến cục bộchồng lên nhau không gian khai báo biến cục bộ mà có một địa phương cùng tên.

Có một quy tắc bổ sung của C # cũng bị vi phạm ở đây. Quy tắc bổ sung là việc sử dụng một tên đơn giản để chỉ hai thực thể khác nhau bên trong hai không gian khai báo biến cục bộ khác nhau là bất hợp pháp. Vì vậy, không chỉ là ví dụ của bạn bất hợp pháp, điều này cũng bất hợp pháp:

class C
{
    int x;
    void M()
    {
        int y = x;
        if(whatever)
        {
            int x = 123;

Bởi vì bây giờ tên đơn giản "x" đã được sử dụng bên trong không gian khai báo biến cục bộ của "y" có nghĩa là hai thứ khác nhau - "this.x" và "x" cục bộ.

Xem http://bloss.msdn.com/b/ericlippert/archive/tags/simple+names/ để biết thêm phân tích về các vấn đề này.


2
Ngoài ra, thật thú vị khi lưu ý rằng mã ví dụ của bạn sẽ biên dịch khi bạn thực hiện thay đổiint y = this.x;
Phil

4
@Phil: Đúng. this.xkhông phải là một tên đơn giản .
Eric Lippert

13

Có một cách khai báo và sử dụng ibên trong phương thức sau vòng lặp:

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

Bạn có thể làm điều này trong Java (nó có thể bắt nguồn từ C Tôi không chắc chắn). Tất nhiên là hơi lộn xộn vì lợi ích của một tên biến.


Có, điều này bắt nguồn từ C / C ++.
Branko Dimitrijevic

1
Tôi đã không biết điều này trước đây. Tính năng ngôn ngữ gọn gàng!

@MathiasLykkegaardLorenzen có
Chris S

7

Nếu bạn đã khai báo i trướcfor vòng lặp của mình , bạn có nghĩ rằng nó vẫn hợp lệ để khai báo nó trong vòng lặp không?

Không, bởi vì sau đó phạm vi của hai sẽ chồng chéo lên nhau.

Đối với việc không thể làm được int A=i;, điều đó đơn giản chỉ vì itồn tại trong forvòng lặp, giống như nó nên làm.


7

Ngoài câu trả lời của J.Kommer (+1 btw). Có điều này trong tiêu chuẩn cho phạm vi NET:

khối Nếu bạn khai báo một biến trong cấu trúc khối như câu lệnh If, phạm vi của biến đó chỉ cho đến khi kết thúc khối. Cuộc đời là cho đến khi thủ tục kết thúc.

Quy trình Nếu bạn khai báo một biến trong một thủ tục, nhưng bên ngoài bất kỳ câu lệnh If nào, phạm vi sẽ cho đến Hàm kết thúc hoặc Hàm kết thúc. Thời gian tồn tại của biến là cho đến khi các thủ tục kết thúc.

Do đó, int i được dán trong tiêu đề vòng lặp for sẽ chỉ nằm trong phạm vi trong khối vòng lặp for, NHƯNG thời gian tồn tại của nó cho đến khi Main()mã hoàn thành.


5

Cách dễ nhất để nghĩ về điều này là di chuyển khai báo bên ngoài của I lên phía trên vòng lặp. Nó sẽ trở nên rõ ràng sau đó.

Đó là cùng một phạm vi, do đó không thể được thực hiện.


4

Ngoài ra các quy tắc của C # nhiều lúc không cần thiết về mặt lập trình một cách nghiêm ngặt, nhưng có để giữ cho mã của bạn sạch sẽ và dễ đọc.

ví dụ, họ có thể đã làm điều đó để nếu bạn xác định nó sau vòng lặp thì không sao, tuy nhiên ai đó đọc mã của bạn và bỏ lỡ dòng định nghĩa có thể nghĩ rằng nó phải làm với biến của vòng lặp.


2

Câu trả lời của Kommer là đúng về mặt kỹ thuật. Hãy để tôi diễn giải nó bằng một phép ẩn dụ trên màn hình mù sống động.

Có một màn hình mù một chiều giữa khối for và khối bên ngoài kèm theo sao cho mã từ bên trong khối for có thể nhìn thấy mã bên ngoài nhưng mã ở khối bên ngoài không thể nhìn thấy mã bên trong.

Vì mã bên ngoài không thể nhìn thấy bên trong, nên nó không thể sử dụng bất cứ thứ gì được khai báo bên trong. Nhưng vì mã trong khối for có thể nhìn thấy cả bên trong và bên ngoài, một biến được khai báo ở cả hai vị trí không thể được sử dụng rõ ràng theo tên.

Vì vậy, hoặc bạn không nhìn thấy nó, hoặc bạn C #!


0

Nhìn vào nó theo cùng một cách như thể bạn có thể khai báo intmột usingkhối:

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

Tuy nhiên, vì intkhông thực hiện IDisposable, điều này không thể được thực hiện. Tuy nhiên, nó có thể giúp ai đó hình dung cách một intbiến được đặt trong phạm vi riêng tư.

Một cách khác để nói,

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

Hy vọng điều này sẽ giúp hình dung những gì đang xảy ra.

Tôi thực sự thích tính năng này, vì khai báo inttừ bên trong forvòng lặp giữ cho mã đẹp và chặt chẽ.


WTF? Nếu bạn định gắn thẻ NEG, hãy để các quả bóng nói tại sao.
jp2code

Không phải downvoter và tôi nghĩ rằng so sánh với usingkhá ok, mặc dù ngữ nghĩa là khá khác nhau (có lẽ đó là lý do tại sao nó bị hạ cấp). Lưu ý đó if (true)là dư thừa. Hủy bỏ nó và bạn có một khối phạm vi.
Abel
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.