Vòng lặp 'for' bên trong vòng lặp 'for' có thể sử dụng cùng một tên biến bộ đếm không?


107

Tôi có thể sử dụng cùng một biến bộ đếm cho một forvòng lặp bên trong forvòng lặp không?

Hay các biến sẽ ảnh hưởng lẫn nhau? Đoạn mã sau có nên sử dụng một biến khác cho vòng lặp thứ hai, chẳng hạn như j, hay là itốt?

for(int i = 0; i < 10; i++)
{
  for(int i = 0; i < 10; i++)
  {
  }
}

72
Thật là khó hiểu - nó sẽ không vượt qua được tôi trong một cuộc đánh giá mã. Nhưng nó là hợp pháp. Có hai biến khác nhau được gọi i, với các phạm vi khác nhau. Sử dụng -Wshadowvới GCC để tự động báo cáo các sự cố như vậy.
Jonathan Leffler,

15
Tôi ngạc nhiên rằng điều đó -Wshadowkhông được bao gồm trong -Wall.
bùng binh bên trái

5
@leftaroundabout cũng -Wshadowcảnh báo về việc che khuất các biến toàn cục, điều này có thể dễ gây khó chịu trong các dự án lớn hơn.
Khối

9
@leftaroundabout thậm chí còn đáng ngạc nhiên hơn, thậm chí -Wextrakhông bao gồm -Wshadow. Tôi đoán nó đã đủ phổ biến trong một số dự án hoặc một số nhà phát triển gcc thích đổ bóng như một phong cách mã hóa, để đảm bảo bị bỏ qua như thế này.
hyde

5
@leftaroundabout Nhắc lại những gì Cubic đã nói, -Wshadowcó một tỷ lệ dương tính giả khủng khiếp, khiến nó hoàn toàn vô dụng. Phạm vi tồn tại là có lý do, và việc phủ bóng là một điều tiên nghiệm không có vấn đề. Bây giờ -Wshadow-local(lưu ý: không -Wshadow=local ) rất khác. Nhưng thật không may cho đến nay GCC đã từ chối đưa nó vào trong thân cây (mặc dù dường như có những nhánh của GCC bao gồm nó).
Konrad Rudolph,

Câu trả lời:


140

Bạn có thể sử dụng cùng một tên (mã định danh). Nó sẽ là một đối tượng khác. Chúng sẽ không ảnh hưởng đến nhau. Bên trong vòng lặp bên trong, không có cách nào để tham chiếu đến đối tượng được sử dụng trong vòng lặp bên ngoài (trừ khi bạn đưa ra các quy định đặc biệt cho điều đó, như bằng cách cung cấp một con trỏ đến nó).

Đây là phong cách nói chung là xấu, dễ gây nhầm lẫn và nên tránh.

Các đối tượng chỉ khác nhau nếu đối tượng bên trong được xác định riêng biệt, như với đối tượng int ibạn đã trình bày. Nếu cùng một tên được sử dụng mà không xác định một đối tượng mới, các vòng lặp sẽ sử dụng cùng một đối tượng và sẽ can thiệp vào nhau.


3
sử dụng for (i) và for (j) lồng nhau và bên trong i ++, sẽ làm tăng biến vòng lặp bên ngoài. Tuy nhiên, những gì bạn nói là đúng nếu bạn sử dụng cùng một số nhận dạng trong cả hai vòng lặp, vì chúng là các biến có phạm vi khác nhau.
KYL3R

3
@BloodGain: “Đối tượng” là một thuật ngữ kỹ thuật được sử dụng trong tiêu chuẩn C. Tôi đã cố tình sử dụng nó ở đây.
Eric Postpischil

1
@EricPostpischil: À, tôi hiểu rồi. Tôi không biết về định nghĩa đó trong tiêu chuẩn, và sợ rằng nó sẽ gây hiểu lầm cho các lập trình viên mới (vì đây rõ ràng là một câu hỏi dành cho người mới bắt đầu), vì C không có "đối tượng" theo nghĩa mà chúng ta thường sử dụng thuật ngữ này. Tôi thấy nó trong tiêu chuẩn C11 và bây giờ tôi tò mò liệu nó có được định nghĩa theo cách đó trước C11 hay không.
Bloodgain

1
Nó đã được. Nó là 3,14 trong tiêu chuẩn C99, thay vì 3,15. Vì vậy, không có lý do gì về phía tôi. Điều đó sẽ dạy tôi đặt câu hỏi cho bạn <: - |
Bloodgain

1
Tổng quát hơn: không có gì ngăn cản bạn sử dụng lại tên biến trong bất kỳ phạm vi lồng nhau nào. Tất nhiên, ngoại trừ nỗi sợ hãi về Sự trừng phạt của Chúa vì đã viết những đoạn mã khó hiểu.
Isaac Rabinovitch

56

Đầu tiên, điều này hoàn toàn hợp pháp: mã sẽ biên dịch và chạy, lặp lại phần thân của vòng lặp lồng nhau 10 × 10 = 100 lần. Bộ đếm ivòng bên trong vòng lặp lồng nhau sẽ ẩn bộ đếm của vòng lặp bên ngoài, vì vậy hai bộ đếm sẽ được tăng lên độc lập với nhau.

Vì bên ngoài ibị ẩn, mã bên trong phần thân của vòng lặp lồng nhau sẽ chỉ có quyền truy cập vào giá trị của ivòng lặp lồng nhau, không phải itừ vòng lặp bên ngoài. Trong các tình huống khi vòng lặp lồng nhau không cần quyền truy cập vào imã bên ngoài như vậy có thể hoàn toàn hợp lý. Tuy nhiên, điều này có thể gây ra nhiều nhầm lẫn hơn cho người đọc, vì vậy tốt hơn hết là bạn nên tránh viết mã như vậy để tránh "trách nhiệm bảo trì".

Lưu ý: Mặc dù các biến đếm của cả hai vòng lặp có cùng mã định danh i, chúng vẫn là hai biến độc lập, tức là bạn không sử dụng cùng một biến trong cả hai vòng lặp. Cũng có thể sử dụng cùng một biến trong cả hai vòng lặp, nhưng mã sẽ khó đọc. Đây là một ví dụ:

for (int i = 1 ; i < 100 ; i++) {
    for ( ; i % 10 != 0 ; i++) {
        printf("%02d ", i);
    }
    printf("%d\n", i);
}

Bây giờ cả hai vòng đều sử dụng cùng một biến. Tuy nhiên, phải mất một thời gian để tìm ra mã này làm gì mà không cần biên dịch nó ( demo );


4
Vì câu hỏi được diễn giải là "sử dụng cùng một biến đếm" nên tôi cũng muốn chỉ ra rằng việc phủ bóng chỉ diễn ra khi việc xác định lại xảy ra. Bỏ qua intvòng lặp for bên trong, tức là thực sự sử dụng cùng một biến bộ đếm, sẽ khiến vòng lặp bên ngoài chỉ chạy một lần, vì vòng lặp bên trong sẽ rời đi i == 10. Đây là tầm thường, nhưng nghĩ rằng nó cung cấp làm rõ được cách thức câu hỏi đã được nêu
Easton Bornemeier

@EastonBornemeier Bạn nói đúng, tôi nghĩ rằng tôi nên giải quyết vấn đề "cùng một biến" trong phần nội dung câu trả lời. Cảm ơn bạn!
dasblinkenlight 27/07/18

@EricPostpischil "Biến bóng" là một thuật ngữ chính thức, hoàn chỉnh với trang riêng của nó trên wikipedia . Tuy nhiên, tôi đã cập nhật câu trả lời để phù hợp với từ ngữ của tiêu chuẩn. Cảm ơn bạn!
dasblinkenlight 27/07/18

2
@dasblinkenlight: Thực ra, tôi bị co thắt não về phương hướng, và tên bên trong phủ bóng tên bên ngoài. Nhận xét trước đây của tôi đã sai về mặt đó. Lời xin lỗi của tôi. (Tuy nhiên, đó là theo nghĩa tiếng Anh, không phải theo nghĩa chính thức — Wikipedia không phải là ấn phẩm chính thức cho C hoặc lập trình nói chung và tôi không biết bất kỳ văn phòng hoặc cơ quan có thẩm quyền nào xác định thuật ngữ này.) Tiêu chuẩn C sử dụng "Ẩn", vì vậy điều đó là tốt hơn.
Eric Postpischil 27/07/18

Đẹp, đặc biệt là với ví dụ "cùng một biến". Tuy nhiên, tôi nghĩ rằng " mã sẽ biên dịch và chạy như mong đợi " sẽ tốt hơn vì "mã sẽ biên dịch và chạy như một người đã đọc nó cẩn thận và hiểu tất cả các phân nhánh được mong đợi" ... như bạn nói, mã như thế này " có khả năng tạo ra nhiều nhầm lẫn hơn ở người đọc "và vấn đề là người đọc bối rối có thể mong đợi điều gì đó khác với những gì nó làm.
TripeHound

26

Bạn có thể. Nhưng bạn nên biết về phạm vi của is. nếu chúng ta gọi bên ngoài ivới i_1và bên trong ivới i_2, phạm vi của is như sau:

for(int i = 0; i < 10; i++)
{
     // i means i_1
     for(int i = 0; i < 10; i++)
     {
        // i means i_2
     }
     // i means i_1
}

Bạn nên lưu ý rằng chúng không ảnh hưởng đến nhau và chỉ là phạm vi định nghĩa của chúng là khác nhau.


17

Điều đó hoàn toàn có thể xảy ra nhưng hãy nhớ rằng, bạn sẽ không thể giải quyết được tôi đã khai báo đầu tiên

for(int i = 0; i < 10; i++)//I MEAN THE ONE HERE
{

  for(int i = 0; i < 10; i++)
    {

    }
}

trong vòng lặp thứ hai trong vòng lặp con thứ hai

for(int i = 0; i < 10; i++)
{

  for(int i = 0; i < 10; i++)//the new i
    {
        // i cant see the i thats before this new i here
    }
}

nếu bạn cần điều chỉnh hoặc lấy giá trị của i đầu tiên, hãy sử dụng j trong vòng lặp thứ hai

for(int i = 0; i < 10; i++)
{

  for(int j = 0; j < 10; j++)
    {

    }
}

và nếu bạn đủ sáng tạo, bạn có thể thực hiện cả hai trong một vòng lặp

for(int i ,j= 0; i < 10; (j>9) ? (i++,j=0) : 0 ,j++)
{
    printf("%d %d\n",i,j);
}

6
Nếu tôi bắt gặp biến của tôi bị che khuất trong các vòng lồng nhau trong quá trình xem xét mã, tôi sẽ coi đó là một cơ hội huấn luyện. Nếu tôi bắt gặp ai đó làm xáo trộn vòng lặp bên trong như ví dụ cuối cùng của bạn (đó KHÔNG phải là một vòng lặp), tôi có thể ném họ ra ngoài cửa sổ.
Bloodgain

nó là một vòng lặp, nó chỉ có một cho vòng lặp, nếu nó là 2 nó sẽ có hai cho các từ khóa hoặc hai trong khi từ khóa hoặc một cho và trong khi từ khóa
Dodo

3
Đó là lý do tại sao tôi nói bạn đã làm xáo trộn vòng lặp. Bạn vẫn đang lặp lại, bạn vừa ẩn nó với cú pháp ít rõ ràng hơn. Và nó còn tệ hơn về mọi mặt.
Bloodgain

12

Có, bạn có thể sử dụng cùng một tên biến bộ đếm cho forvòng lặp bên trong như cho forvòng lặp bên ngoài .

Vòng lặp từ for :

for ( init_clause ; cond_expression ; iteration_expression ) loop_statement
Câu lệnh biểu thức được sử dụng như loop_statement thiết lập phạm vi khối riêng của nó , khác với phạm vi của init_clause .

for (int i = 0; ; ) {
    long i = 1;   // valid C, invalid C++
    // ...
}  

Phạm vi của loop_statement được lồng trong phạm vi của init_clause .

Từ Tiêu chuẩn C # 6.8.5p5 Các câu lệnh lặp lại [tôi nhấn mạnh]

Câu lệnh lặp là một khối có phạm vi là một tập hợp con nghiêm ngặt của phạm vi của khối bao quanh nó. Phần thân của vòng lặp cũng là một khối có phạm vi là một tập con nghiêm ngặt của phạm vi của câu lệnh lặp .

Từ Tiêu chuẩn C # 6.2.1p4 Phạm vi của số nhận dạng [tôi nhấn mạnh]

.... Trong phạm vi bên trong, định danh chỉ định thực thể được khai báo trong phạm vi bên trong; các thực thể khai báo trong phạm vi bên ngoài được ẩn (và không nhìn thấy được) trong phạm vi bên trong.


10

Từ góc độ mã / trình biên dịch, đây sẽ là một việc hoàn toàn hợp lệ và hợp pháp để làm. Các int ituyên bố trong khu vực nội for(int i = 0; i < 10; i++)vòng lặp là trong một phạm vi mới và nhỏ hơn, do đó khai bóng tuyên bố int itrong vòng ngoài (hoặc, với các từ khác: Trong phạm vi nội tất cả các truy cập vào biến iđi đến int ikhai báo trong phạm vi bên trong, bỏ rơiint i trong phạm vi bên ngoài).

Điều đó nói rằng, từ góc độ chất lượng mã, điều này hoàn toàn khủng khiếp. Nó khó đọc, khó hiểu và dễ hiểu nhầm. Đừng làm vậy.


8

Có, bạn có thể sử dụng nó nhưng nó khá khó hiểu. Điều quan trọng nhất là phạm vi của biến cục bộ bên trong vòng lặp. Nếu một biến được khai báo bên trong một hàm, phạm vi của biến đó là hàm đó.

int a = 5;
// scope of a that has value 5
int func(){
    int a = 10;
   // scope of a that has value 10
}
// scope of a that has value 5

Tương tự trường hợp với vòng lặp, biến được khai báo bên trong vòng lặp bên trong có phạm vi khác nhau và biến được khai báo vòng lặp bên ngoài có phạm vi khác nhau.

for(int i = 0; i < 10; i++){
    // In first iteration, value of i is 0

    for(int i = 1; i < 10; i++){
        // In first iteration, value of i is 1
    }
    // In first iteration, value of i is 0
}

Cách tiếp cận tốt hơn là sử dụng các biến khác nhau cho các vòng lặp bên trong và bên ngoài.

for(int i = 0; i < 10; i++){

    for(int j = 1; j < 10; j++){

    }

}

8

Có chắc chắn bạn có thể sử dụng biến cùng tên.

Biến lập trình C có thể được khai báo ở ba nơi:
Biến cục bộ: -Bên trong một hàm hoặc một khối.
Biến toàn cục: -Tất cả các hàm.
Các tham số chính thức: -Trong các tham số hàm.

Nhưng trong trường hợp của bạn i scopesẽ phải lưu ý những điều dưới đây

for(int i = 0; i < 10; i++)
{
     // i means 1st for loop variable
     for(int i = 0; i < 10; i++)
     {
        // but here i means 2nd for loop  variable
     }
     //interesting thing here i means 1st for loop variable
}

Lưu ý: Cách tốt nhất là sử dụng các biến khác nhau cho các vòng lặp bên trong và bên ngoài


6

Có - và thú vị hơn nữa là bạn có thể sử dụng lại một tên biến mỗi khi bạn mở một bộ dấu ngoặc nhọn. Điều này thường tiện dụng khi chèn mã chẩn đoán. Nhập một dấu ngoặc nhọn '{' theo sau là khai báo và sử dụng các biến, sau đó đóng dấu ngoặc nhọn và các biến biến mất. Điều này đảm bảo rằng bạn sẽ không can thiệp vào bất kỳ thứ gì trong phần thân chính trong khi vẫn giữ được lợi thế của bất kỳ biến, lớp và phương thức nào được khai báo bên ngoài dấu ngoặc nhọn.


3

Quy tắc phạm vi: Một biến được khai báo trong câu lệnh for chỉ có thể được sử dụng trong câu lệnh đó và phần thân của vòng lặp.

Nếu trong mã của bạn, bạn đã xác định nhiều trường hợp của i trong các vòng lặp bên trong, mỗi trường hợp sẽ chiếm không gian bộ nhớ của riêng nó. Vì vậy, không có gì phải lo lắng về kết quả dù sao nó sẽ giống nhau.

int main(void) {

    int i = 2; //defined with file global scope outside of a function and will remain 2
    if(1)
    {       //new scope, variables created here with same name are different
        int i = 5;//will remain == 5
        for(int i = 0; i < 10; i++)
        {   //new scope for "i"

            printf("i value in first loop: %d \n", i); // Will print 0 in first iteration
            for(int i = 8; i < 15; i++) 
            {   //new scope again for "i", variable with same name is not the same
                printf("i value in nested loop: %d \n", i); // Will print 8 in first iteration
            }
        }

    }

    return 0;
}

Nhưng không nên sử dụng cùng một tên biến vì nó khó hiểu và nó sẽ trở thành mã không thể bảo trì sau này.


1

Phần quan trọng là tham số vòng lặp bên trong chứa int i. Bởi vì iđược định nghĩa lại theo cách này, hai biến không ảnh hưởng đến nhau; phạm vi của chúng khác nhau. Dưới đây là hai ví dụ cho thấy điều này:

for(int i = 0; i < 10; i++) // This code will print "Test" 100 times
{
 for(int i = 0; i < 10; i++)
 {
  puts("Test");
 }
}

Lưu ý rằng đoạn mã trên bao gồm int itham số vòng lặp bên trong và đoạn mã bên dưới chỉ bao gồm i.

for(int i = 0; i < 10; i++) // This code will print "Test" 10 times
{
 for(i = 0; i < 10; i++)
 {
  puts("Test");
 }
}

0

Chà, bạn có thể làm điều này mà không cần các tập lệnh của bạn gặp vấn đề, nhưng bạn nên tránh cấu trúc đó. Nó thường dẫn đến nhầm lẫn

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.