2008-09-09 26 views
118

Nhà điều hành , hoạt động trong C?Nhà điều hành dấu phẩy làm gì?

+0

bản sao có thể có của [Sử dụng toán tử dấu phẩy là gì?] (Http: // stackoverflow.com/questions/17902992/what-is-the-proper-use-of-the-dấu phẩy-nhà điều hành) –

+1

Như tôi lưu ý trong câu trả lời của tôi, có một điểm chuỗi sau khi đánh giá toán hạng bên trái. Điều này không giống như dấu phẩy trong một cuộc gọi hàm chỉ là ngữ pháp. –

Trả lời

97

Khái niệm:

(expression1, expression2) 

expression1 đầu tiên được đánh giá, sau đó expression2 được đánh giá, và giá trị của expression2 được trả lại cho toàn bộ biểu thức.

+1

sau đó nếu tôi viết i = (5,4,3,2,1,0) thì lý tưởng nó sẽ trả về 0, đúng không? nhưng tôi đang được gán một giá trị là 5? Bạn có thể vui lòng giúp tôi hiểu tôi đang đi sai ở đâu không? – Jayesh

+12

@James: Giá trị của một hoạt động dấu phẩy sẽ luôn là giá trị của biểu thức cuối cùng. Tại thời điểm không có 'i' có các giá trị 5, 4, 3, 2 hoặc 1. Nó chỉ đơn giản là 0. Nó thực sự vô dụng trừ khi các biểu thức có tác dụng phụ. –

+4

Lưu ý rằng có một điểm chuỗi đầy đủ giữa việc đánh giá LHS của biểu thức dấu phẩy và đánh giá của RHS (xem [Shafik Yaghmour] (http://stackoverflow.com/users/1708801/shafik-yaghmour) 's [answer] (http://stackoverflow.com/a/18444099/15168) để có báo giá từ tiêu chuẩn C99). Đây là thuộc tính quan trọng của toán tử dấu phẩy. –

8

Nó gây ra việc đánh giá nhiều câu lệnh, nhưng chỉ sử dụng giá trị cuối cùng làm giá trị kết quả (rvalue, tôi nghĩ).

Vậy ...

int f() { return 7; } 
int g() { return 8; } 

int x = (printf("assigning x"), f(), g()); 

nên kết quả trong x được thiết lập để 8.

3

Như câu trả lời trước đó đã tuyên bố nó đánh giá tất cả các báo cáo nhưng sử dụng người cuối cùng là giá trị của biểu thức. Cá nhân tôi đã chỉ tìm thấy nó hữu ích trong các biểu thức lặp:

for (tmp=0, i = MAX; i > 0; i--) 
2

Nơi duy nhất tôi đã nhìn thấy nó là hữu ích là khi bạn viết một vòng lặp sôi nổi mà bạn muốn làm nhiều thứ theo một trong các biểu thức (có thể là biểu thức init hoặc vòng lặp biểu hiện cái gì đó như:.

bool arraysAreMirrored(int a1[], int a2[], size_t size) 
{ 
    size_t i1, i2; 
    for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--) 
    { 
    if(a1[i1] != a2[i2]) 
    { 
     return false; 
    } 
    } 

    return true; 
} 

Pardon tôi nếu có bất kỳ lỗi cú pháp hoặc nếu tôi trộn lẫn trong bất cứ điều gì đó không phải là C. nghiêm ngặt tôi không tranh cãi rằng, nhà điều hành là hình thức tốt, nhưng đó là những gì bạn có thể sử dụng nó cho. Trong trường hợp trên tôi có thể sử dụng một vòng lặp while thay vì vậy nhiều biểu thức trên init và vòng lặp sẽ b e rõ ràng hơn. (Và tôi muốn khởi i1 và i2 inline thay vì tuyên bố và sau đó khởi tạo .... blah blah blah.)

+0

Tôi đoán bạn có nghĩa là i1 = 0, i2 = size -1 – frankster

+0

Thật vậy! Đã sửa đổi đúng. – Owen

85

tôi đã nhìn thấy sử dụng nhiều nhất trong while vòng:

string s; 
while(read_string(s), s.len() > 5) 
{ 
    //do something 
} 

Nó sẽ làm hoạt động, sau đó thực hiện một thử nghiệm dựa trên tác dụng phụ. Cách khác là làm như sau:

string s; 
read_string(s); 
while(s.len() > 5) 
{ 
    //do something 
    read_string(s); 
} 
+12

Xin chào, rất tiện lợi! Tôi thường phải làm những điều không chính thống trong một vòng lặp để khắc phục vấn đề đó. – staticsan

+4

Mặc dù nó có thể ít bị che khuất và dễ đọc hơn nếu bạn đã làm một cái gì đó như: 'while (read_string (s) && s.len()> 5)'. Rõ ràng điều đó sẽ không hoạt động nếu 'read_string' không có giá trị trả về (hoặc không có giá trị trả về). (Edit: Xin lỗi, không nhận thấy bài viết này cũ bao nhiêu.) – jamesdlin

+8

@staticsan Đừng ngại sử dụng 'while (1)' với câu lệnh 'break; 'trong cơ thể. Cố gắng để buộc phần phá vỡ của mã vào trong khi kiểm tra trong khi hoặc xuống vào kiểm tra do-while, thường là một sự lãng phí năng lượng và làm cho mã khó hiểu hơn. – potrzebie

26

Toán tử dấu phẩy kết hợp hai biểu thức ở hai bên thành một, đánh giá cả hai biểu thức theo thứ tự từ trái sang phải. Giá trị của phía bên phải được trả về dưới dạng giá trị của toàn bộ biểu thức. (expr1, expr2) giống như { expr1; expr2; } nhưng bạn có thể sử dụng kết quả của expr2 trong cuộc gọi hoặc nhiệm vụ chức năng.

Nó thường được nhìn thấy trong for vòng để khởi hoặc duy trì nhiều biến như thế này:

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh) 
{ 
    /* do something with low and high and put new values 
     in newlow and newhigh */ 
} 

Ngoài ra, tôi đã chỉ được sử dụng nó "tức giận" trong một trường hợp khác, khi gói lên hai các hoạt động nên luôn đi cùng nhau trong macro.Chúng tôi đã có mã đó sao chép các giá trị nhị phân khác nhau vào một bộ đệm byte để gửi trên mạng, và một con trỏ duy trì nơi chúng tôi đã đứng dậy để:

unsigned char outbuff[BUFFSIZE]; 
unsigned char *ptr = outbuff; 

*ptr++ = first_byte_value; 
*ptr++ = second_byte_value; 

send_buff(outbuff, (int)(ptr - outbuff)); 

Trường hợp giá trị là short s hoặc int s chúng tôi đã làm điều này:

*((short *)ptr)++ = short_value; 
*((int *)ptr)++ = int_value; 

Sau đó chúng tôi đọc rằng đây không phải là hợp lệ C, vì (short *)ptr không còn là giá trị l và không thể tăng lên, mặc dù trình biên dịch của chúng tôi lúc đó không bận tâm. Để khắc phục điều này, chúng tôi chia biểu thức thành hai:

*(short *)ptr = short_value; 
ptr += sizeof(short); 

Tuy nhiên, phương pháp này dựa vào tất cả các nhà phát triển nhớ đặt cả hai câu lệnh trong mọi lúc. Chúng tôi muốn có một chức năng mà bạn có thể vượt qua trong con trỏ đầu ra, giá trị và kiểu của giá trị. Đây C là, không phải C++ với các mẫu, chúng tôi không thể có một hàm có một kiểu bất kỳ, vì vậy chúng tôi quyết định chọn một macro:

#define ASSIGN_INCR(p, val, type) ((*((type) *)(p) = (val)), (p) += sizeof(type)) 

Bằng cách sử dụng toán tử phẩy chúng tôi có thể sử dụng điều này trong các biểu thức hoặc như tuyên bố như chúng tôi mong muốn:

if (need_to_output_short) 
    ASSIGN_INCR(ptr, short_value, short); 

latest_pos = ASSIGN_INCR(ptr, int_value, int); 

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff)); 

Tôi không đề xuất bất kỳ ví dụ nào trong số này là phong cách tốt! Thật vậy, tôi dường như nhớ số hoàn thành mã số của Steve McConnell để chống lại ngay cả khi sử dụng toán tử dấu phẩy trong vòng lặp for: để dễ đọc và bảo trì, vòng lặp phải được kiểm soát bởi chỉ một biến và các dòng trong dòng for sẽ chỉ chứa vòng lặp - mã kiểm soát, không phải các bit phụ khác của khởi tạo hoặc bảo trì vòng lặp.

+0

Cảm ơn! Đó là câu trả lời đầu tiên của tôi về StackOverflow: kể từ đó tôi có lẽ đã học được rằng sự đồng nhất là có giá trị :-). –

+0

Đôi khi tôi đánh giá cao một chút về độ trong như trường hợp ở đây, nơi bạn mô tả sự tiến hóa của một giải pháp (cách bạn đạt được điều đó). –

20

comma operator sẽ đánh giá toán hạng bên trái, loại bỏ kết quả và sau đó đánh giá toán hạng bên phải và đó sẽ là kết quả. Các ngữ sử dụng như đã nêu trong các liên kết là khi khởi tạo các biến được sử dụng trong một vòng lặp for, và nó mang lại cho các ví dụ sau:

void rev(char *s, size_t len) 
{ 
    char *first; 
    for (first = s, s += len - 1; s >= first; --s) 
     /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
     putchar(*s); 
} 

Nếu không có nhiều lớn sử dụng của các nhà điều hành dấu phẩy , mặc dù nó là easy to abuse để tạo mã khó đọc và duy trì.

Từ draft C99 standard ngữ pháp là như sau:

expression: 
    assignment-expression 
    expression , assignment-expression 

đoạn 2 nói:

Các toán hạng trái của một nhà điều hành dấu phẩy được đánh giá là một biểu thức còn thiếu; có một điểm chuỗi sau khi đánh giá. Sau đó, toán hạng bên phải được đánh giá; kết quả có loại và giá trị của nó.97) Nếu một nỗ lực được thực hiện để sửa đổi kết quả của toán tử dấu phẩy hoặc để truy cập kết quả đó sau điểm chuỗi tiếp theo, hành vi không xác định.

Footnote 97 nói:

Một nhà điều hành dấu phẩy không không mang lại một giá trị trái.

có nghĩa là bạn không thể gán cho kết quả của nhà điều hành dấu phẩy.

Điều quan trọng cần lưu ý rằng các nhà điều hành dấu phẩy có lowest precedence và do đó có những trường hợp sử dụng () có thể tạo sự khác biệt lớn, ví dụ:

#include <stdio.h> 

int main() 
{ 
    int x, y ; 

    x = 1, 2 ; 
    y = (3,4) ; 

    printf("%d %d\n", x, y) ; 
} 

sẽ có kết quả như sau:

1 4 
4

Toán tử dấu phẩy không có ý nghĩa gì, nó là một tính năng không cần thiết 100%. Việc sử dụng chính của nó là "những người cố gắng thông minh" và do đó sử dụng nó để (vô tình) làm xáo trộn mã có thể đọc được. Các khu vực chính của việc sử dụng là để xáo trộn cho vòng, ví dụ:

for(int i=0, count=0; i<x; i++, count++) 

đâu int i=0, count=0 thực sự không phải là các nhà điều hành dấu phẩy, nhưng một danh sách kê khai (chúng tôi đang đã nhầm lẫn ở đây). i++, count++ là toán tử dấu phẩy, đánh giá toán hạng bên trái trước và sau đó là toán hạng bên phải. Kết quả của toán tử dấu phẩy là kết quả của toán hạng bên phải. Kết quả của toán hạng bên trái bị loại bỏ.

Nhưng đoạn code trên có thể được viết bằng một cách rất dễ đọc hơn mà không cần các nhà điều hành dấu phẩy:

int count = 0; 
for(int i=0; i<x; i++) // readable for loop, no nonsense anywhere 
{ 
    ... 
    count++; 
} 

Việc sử dụng thực duy nhất của các nhà điều hành dấu phẩy Tôi đã thấy, là các cuộc thảo luận nhân tạo về điểm chuỗi , vì toán tử dấu phẩy đi kèm với một điểm chuỗi giữa việc đánh giá toán hạng trái và phải.

Vì vậy, nếu bạn có một số mã hành vi undefined như thế này:

printf("%d %d", i++, i++); 

Bạn thực sự có thể biến nó thành hành vi chỉ đơn thuần là không xác định (tự đánh giá các thông số chức năng) bằng cách viết

printf("%d %d", (0,i++), (0,i++)); 

Có bây giờ là một điểm chuỗi giữa mỗi đánh giá của i++, do đó, ít nhất chương trình sẽ không có nguy cơ sụp đổ và ghi nữa, mặc dù thứ tự đánh giá các tham số chức năng vẫn chưa được chỉ định.

Tất nhiên, không ai có thể viết mã như vậy trong các ứng dụng thực, nó chỉ hữu ích cho các cuộc thảo luận luật sư về các điểm trình tự trong ngôn ngữ C.

Nhà điều hành dấu phẩy bị cấm bởi MISRA-C: 2004 và MISRA-C: 2012 với lý do là nó tạo mã ít đọc được hơn.

+7

Phiên bản "không xác định" của bạn vẫn chưa được xác định. Việc đánh giá các đối số có thể trùng lặp, do đó, đầu tiên hai số 0 được đánh giá, sau đó có một điểm chuỗi và cuối cùng là hai giá trị được đánh giá. Điều này thỏa mãn yêu cầu rằng có một điểm chuỗi giữa dấu phẩy LHS và RHS. Đối với vòng lặp 'for' được viết lại của bạn, hãy xem xét 'tiếp tục'. Những gì bạn đề nghị là tương đương thực sự là không. Đối với khả năng đọc, hãy xem xét 'srC++, dst ++', trong đó giữ cả hai gia số cùng nhau trong mã làm cho nó dễ dàng hơn cho người đọc để xác minh rằng cả hai con trỏ được tăng lên chính xác như nhau. – hvd

+1

@hvd Tôi không tin rằng việc triển khai tuân thủ nghiêm ngặt được phép xóa các điểm chuỗi mà nhận xét của bạn cho thấy. Tại sao mọi người lại xem xét tiếp tục? Nó chỉ hữu dụng cho việc viết mã spaghetti (và cũng bị cấm bởi MISRA). Và vòng lặp tương đương _is_ dù sao đi nữa, cả về mặt kết quả và về mặt "máy trừu tượng" của C. Và tôi thực sự không nghĩ rằng 'srC++, dst ++;' dễ đọc hơn 'srC++; (dòng mới) dst ++; '. Tôi nghi ngờ rất ít. – Lundin

+4

Tốt, sau đó đánh giá 0, đánh giá 0, điểm trình tự, điểm chuỗi, đánh giá i + +, đánh giá i + +. Cùng một kết quả, vì vậy tôi không nghĩ rằng sự khác biệt quan trọng. Đối với srC++, dst ++, tôi đã đề cập đến khi xuất hiện trong một for, vì đó là câu trả lời của bạn tập trung vào. – hvd