Làm cách nào để đặt hai câu lệnh tăng trong vòng lặp C ++ 'for'?


93

Tôi muốn tăng hai biến trong forđiều kiện -loop thay vì một.

Vì vậy, một cái gì đó như:

for (int i = 0; i != 5; ++i and ++j) 
    do_something(i, j);

Cú pháp cho điều này là gì?

Câu trả lời:


154

Một thành ngữ phổ biến là sử dụng toán tử dấu phẩy để đánh giá cả hai toán hạng và trả về toán hạng thứ hai. Như vậy:

for(int i = 0; i != 5; ++i,++j) 
    do_something(i,j);

Nhưng nó có thực sự là một toán tử dấu phẩy?

Bây giờ sau khi viết điều đó, một người bình luận cho rằng nó thực sự là một số đường cú pháp đặc biệt trong câu lệnh for, và hoàn toàn không phải là một toán tử dấu phẩy. Tôi đã kiểm tra điều đó trong GCC như sau:

int i=0;
int a=5;
int x=0;

for(i; i<5; x=i++,a++){
    printf("i=%d a=%d x=%d\n",i,a,x);
}

Tôi đã mong đợi x nhận giá trị ban đầu của a, vì vậy nó đáng lẽ phải hiển thị 5,6,7 .. cho x. Những gì tôi nhận được là cái này

i=0 a=5 x=0
i=1 a=6 x=0
i=2 a=7 x=1
i=3 a=8 x=2
i=4 a=9 x=3

Tuy nhiên, nếu tôi đặt dấu ngoặc nhọn cho biểu thức để buộc trình phân tích cú pháp thực sự nhìn thấy một toán tử dấu phẩy, tôi nhận được điều này

int main(){
    int i=0;
    int a=5;
    int x=0;

    for(i=0; i<5; x=(i++,a++)){
        printf("i=%d a=%d x=%d\n",i,a,x);
    }
}

i=0 a=5 x=0
i=1 a=6 x=5
i=2 a=7 x=6
i=3 a=8 x=7
i=4 a=9 x=8

Ban đầu, tôi nghĩ rằng điều này cho thấy nó không hoạt động như một toán tử dấu phẩy, nhưng hóa ra, đây chỉ đơn giản là một vấn đề ưu tiên - toán tử dấu phẩy có mức độ ưu tiên thấp nhất có thể , vì vậy biểu thức x = i ++, a ++ có hiệu quả được phân tích cú pháp là (x = i ++), a ++

Cảm ơn tất cả các ý kiến, đó là một trải nghiệm học tập thú vị và tôi đã sử dụng C trong nhiều năm!


1
Tôi đã đọc vài lần rằng dấu phẩy trong phần đầu tiên hoặc phần ba của vòng lặp for không phải là toán tử dấu phẩy, mà chỉ là dấu phân cách bằng dấu phẩy. (Tuy nhiên, tôi không tìm thấy một nguồn chính thức cho việc này, kể từ khi tôi là đặc biệt xấu ở phân tích chuẩn C ++ ngôn ngữ.)
Daniel Daranas

Đầu tiên tôi nghĩ rằng bạn đã sai, nhưng tôi đã viết một số mã kiểm tra và bạn đã đúng - nó không hoạt động giống như một toán tử dấu phẩy. Sẽ sửa đổi câu trả lời của tôi!
Paul Dixon

19
một toán tử dấu phẩy trong ngữ cảnh đó. Lý do bạn không nhận được những gì bạn mong đợi là toán tử lệnh có quyền ưu tiên thấp hơn toán tử gán, do đó, không có tham số, nó sẽ phân tích cú pháp như (x = i ++), j ++.
caf

6
Nó là một toán tử dấu phẩy. Phép gán quan hệ chặt chẽ hơn toán tử dấu phẩy, vì vậy x = i ++, a ++ được phân tích cú pháp (x = i ++), a ++ chứ không phải x = (i ++, a ++). Đặc tính đó được một số thư viện sử dụng sai để v = 1,2,3; thực hiện những điều trực quan, nhưng chỉ vì v = 1 trả về một đối tượng proxy mà toán tử dấu phẩy quá tải thực hiện một phần thêm.
AProgrammer

3
Đồng ý. Từ open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2857.pdf phần 6.5.3, phần cuối cùng là "biểu thức". (Mặc dù 1.6 # 2 định nghĩa "danh sách biểu thức" là "danh sách các biểu thức được phân tách bằng dấu phẩy", cấu trúc này không xuất hiện trong 6.5.3.). Điều này có nghĩa là khi chúng ta viết "++ i, ++ j" thì nó phải là một biểu thức trong chính nó và do đó "," phải là toán tử dấu phẩy (5.18). (Đây không phải là "danh sách các trình khởi tạo" hoặc "danh sách các đối số của các hàm", đó là các ví dụ trong đó "dấu phẩy có một ý nghĩa đặc biệt", như 5.18 # 2 đã nói.). Tôi thấy nó hơi khó hiểu.
Daniel Daranas

55

Thử cái này

for(int i = 0; i != 5; ++i, ++j)
    do_something(i,j);

17
+1 Bạn cũng có thể khai báo j trong phần đầu tiên. for (int i = 0, j = 0; i! = 5; ++ i, ++ j) {...}
Daniel Daranas

1
+1 Như một lưu ý phụ, cú pháp tương tự này hoạt động trong C # (Tôi nhận được ở đây từ tìm kiếm trên Google cho "C # cho bộ đếm vòng lặp 2" nên tôi nghĩ tôi sẽ đề cập đến điều này).
CodingWithSpike

@CodingWithSpike: Chà, trong C #, dấu phẩy đặc biệt, nó không thực sự hợp pháp khi biểu thức toán tử dấu phẩy xuất hiện ở đó. Ví dụ đó là sử dụng hợp pháp của nhà điều hành dấu phẩy trong C ++, nhưng bị từ chối bởi C #:for( ; ; ((++i), (++j)) )
Ben Voigt

@BenVoigt không liên quan gì đến dấu phẩy. Đây cũng không phải là C # hợp pháp: for(int i = 0; i != 5; (++i)) {Dấu ngoặc đơn bổ sung đánh lừa trình biên dịch nghĩ rằng nó không phải là một hoạt động "gia tăng" nữa.
CodingWithSpike

@CodingWithSpike: Đúng vậy, nhưng dấu ngoặc đơn cũng thay đổi cách C # nhìn thấy dấu phẩy và ngăn cản ý nghĩa đặc biệt bên trong hành động for.
Ben Voigt

6

Cố gắng đừng làm điều đó!

Từ http://www.research.att.com/~bs/JSF-AV-rules.pdf :

Quy tắc AV 199
Biểu thức gia tăng trong vòng lặp for sẽ không thực hiện hành động nào khác ngoài việc thay đổi một tham số vòng lặp đơn thành giá trị tiếp theo cho vòng lặp.

Cơ sở lý luận: Tính dễ đọc.


4
Điều đó đúng, nhưng công bằng mà nói, tôi khá chắc chắn rằng tiêu chuẩn quy tắc được viết cho phần mềm nhúng trong máy bay chiến đấu, chứ không phải chương trình C (++). Nói như vậy, có lẽ đó là một thói quen đọc tốt cần có, và ai biết được, có thể bạn sẽ thiết kế phần mềm cho F-35, và đó sẽ là một thói quen ít bị phá vỡ.
galois

3
for (int i = 0; i != 5; ++i, ++j) 
    do_something(i, j);

2

Tôi đến đây để tự nhắc mình cách viết mã chỉ mục thứ hai vào mệnh đề gia tăng của vòng lặp FOR, điều mà tôi biết có thể được thực hiện chủ yếu bằng cách quan sát nó trong một mẫu mà tôi đã kết hợp vào một dự án khác, được viết bằng C ++.

Hôm nay, tôi đang làm việc trong C #, nhưng tôi cảm thấy chắc chắn rằng nó sẽ tuân theo các quy tắc tương tự về mặt này, vì câu lệnh FOR là một trong những cấu trúc điều khiển lâu đời nhất trong tất cả các lập trình. Rất may, gần đây tôi đã dành vài ngày để ghi lại chính xác hành vi của vòng lặp FOR trong một trong những chương trình C cũ của tôi và tôi nhanh chóng nhận ra rằng những nghiên cứu đó đã tổ chức các bài học áp dụng cho vấn đề C # ngày nay, đặc biệt là hành vi của biến chỉ mục thứ hai .

Đối với những người không cẩn thận, sau đây là tóm tắt những quan sát của tôi. Tất cả những gì tôi thấy đang xảy ra ngày hôm nay, bằng cách quan sát cẩn thận các biến trong cửa sổ Locals, đã xác nhận kỳ vọng của tôi rằng câu lệnh C # FOR hoạt động chính xác giống như câu lệnh C hoặc C ++ FOR.

  1. Lần đầu tiên một vòng lặp FOR thực thi, mệnh đề gia tăng (mệnh đề thứ 3 trong số ba của nó) bị bỏ qua. Trong Visual C và C ++, khối gia tăng được tạo ra dưới dạng ba lệnh máy ở giữa khối thực hiện vòng lặp, để lần vượt qua ban đầu chỉ chạy mã khởi tạo một lần, sau đó nhảy qua khối gia tăng để thực hiện kiểm tra kết thúc. Điều này thực hiện tính năng mà một vòng lặp FOR thực hiện không hoặc nhiều lần, tùy thuộc vào trạng thái của chỉ số và biến giới hạn của nó.
  2. Nếu phần thân của vòng lặp thực thi, câu lệnh cuối cùng của nó là một bước nhảy đến lệnh đầu tiên trong ba lệnh tăng đã bị lần lặp đầu tiên bỏ qua. Sau khi các lệnh này thực thi, điều khiển tự nhiên rơi vào mã kiểm tra giới hạn triển khai mệnh đề giữa. Kết quả của thử nghiệm đó xác định xem phần thân của vòng lặp FOR có thực thi hay không hoặc liệu điều khiển có chuyển đến lệnh tiếp theo sau bước nhảy ở cuối phạm vi của nó hay không.
  3. Vì điều khiển chuyển từ dưới cùng của khối vòng lặp FOR sang khối tăng, biến chỉ mục được tăng trước khi thực hiện kiểm tra. Hành vi này không chỉ giải thích lý do tại sao bạn phải viết mã các mệnh đề giới hạn theo cách bạn đã học, mà còn ảnh hưởng đến bất kỳ số gia thứ cấp nào mà bạn thêm vào, thông qua toán tử dấu phẩy, vì nó trở thành một phần của mệnh đề thứ ba. Do đó, nó không bị thay đổi trong lần lặp đầu tiên, nhưng nó ở lần lặp cuối cùng, không bao giờ thực thi phần thân.

Nếu một trong các biến chỉ mục của bạn vẫn còn trong phạm vi khi vòng lặp kết thúc, giá trị của chúng sẽ cao hơn một ngưỡng so với ngưỡng dừng vòng lặp, trong trường hợp là biến chỉ mục true. Tương tự như vậy, nếu, ví dụ: nếu biến thứ hai được khởi tạo bằng 0 trước khi vòng lặp được nhập, giá trị của nó ở cuối sẽ là số lần lặp, giả sử rằng đó là số tăng (++), không phải là giảm và không có gì trong phần thân của vòng lặp thay đổi giá trị của nó.


1

Tôi đồng ý với squelart. Việc tăng hai biến số rất dễ xảy ra lỗi, đặc biệt nếu bạn chỉ kiểm tra một trong số chúng.

Đây là cách dễ đọc để làm điều này:

int j = 0;
for(int i = 0; i < 5; ++i) {
    do_something(i, j);
    ++j;
}

Forvòng lặp có nghĩa là cho các trường hợp vòng lặp của bạn chạy trên một biến tăng / giảm. Đối với bất kỳ biến nào khác, hãy thay đổi nó trong vòng lặp.

Nếu bạn cần jđược gắn với i, tại sao không để nguyên biến ban đầu và thêm vào i?

for(int i = 0; i < 5; ++i) {
    do_something(i,a+i);
}

Nếu logic của bạn phức tạp hơn (ví dụ: bạn cần thực sự giám sát nhiều hơn một biến), tôi sẽ sử dụng một whilevòng lặp.


2
Trong ví dụ đầu tiên, j được tăng nhiều hơn i! Điều gì về một trình lặp, nơi cần phải thực hiện một số hành động cho x bước đầu tiên? (Và bộ sưu tập luôn đủ dài) Bạn có thể đi lên trình lặp mỗi lần lặp lại, nhưng imho gọn gàng hơn nhiều.
Peter Smit

0
int main(){
    int i=0;
    int a=0;
    for(i;i<5;i++,a++){
        printf("%d %d\n",a,i);
    } 
}

1
Điểm của việc không tạo iacục bộ với vòng lặp là gì?
sbi

2
Không, chỉ hiển thị như thế nào để làm cả hai gia trong, chỉ cần mình một ví dụ về sytnax
Arkaitz Jimenez

0

Sử dụng Toán học. Nếu hai phép toán phụ thuộc toán học vào sự lặp lại của vòng lặp, tại sao không làm phép toán?

int i, j;//That have some meaningful values in them?
for( int counter = 0; counter < count_max; ++counter )
    do_something (counter+i, counter+j);

Hoặc, cụ thể hơn là tham khảo ví dụ của OP:

for(int i = 0; i != 5; ++i)
    do_something(i, j+i);

Đặc biệt nếu bạn đang chuyển vào một hàm theo giá trị, thì bạn sẽ nhận được thứ gì đó thực hiện chính xác những gì bạn muố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.