Toán tử dấu phẩy, làm gì?


167

Không những gì ,nhà điều hành làm trong C?



1
Như tôi lưu ý trong câu trả lời của mình, có một điểm thứ tự 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 lệnh gọi hàm chỉ là ngữ pháp.
Shafik Yaghmour

2
@SergeyK. - Cho rằng điều này đã được hỏi và trả lời nhiều năm trước câu hỏi kia, nhiều khả năng câu hỏi kia là một bản sao của câu hỏi này. Tuy nhiên, cái khác cũng được gắn thẻ kép với cả cc ++ , điều này gây phiền toái. Đây là một câu hỏi chỉ có C, với câu trả lời đàng hoàng.
Jonathan Leffler

Câu trả lời:


129

Cách diễn đạt:

(expression1,  expression2)

Biểu thức đầu tiên được ước tính, sau đó biểu thức2 được ước tính và giá trị của biểu thức 2 được trả về cho toàn bộ biểu thức.


3
sau đó nếu tôi viết i = (5,4,3,2,1,0) thì lý tưởng là 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?
Jayesh

19
@James: Giá trị của một thao tác dấu phẩy sẽ luôn là giá trị của biểu thức cuối cùng. Không có điểm nào icó 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ụ.
Jeff Mercado

6
Lưu ý rằng có một điểm trình tự đầy đủ giữa việc đánh giá LHS của biểu thức dấu phẩy và đánh giá RHS (xem câu trả lời của Shafik Yaghmour để biết trích dẫn từ tiêu chuẩn C99). Đây là một tài sản quan trọng của toán tử dấu phẩy.
Jonathan Leffler

5
i = b, c;tương đương với (i = b), cvì gán =có độ ưu tiên cao hơn toán tử dấu phẩy ,. Toán tử dấu phẩy có quyền ưu tiên thấp nhất trong tất cả.
đạp

1
Tôi lo lắng rằng các dấu ngoặc đơn gây hiểu nhầm về hai phép tính: (1) chúng không cần thiết - toán tử dấu phẩy không phải được bao quanh bởi dấu ngoặc đơn; và (2) chúng có thể bị nhầm lẫn với dấu ngoặc đơn xung quanh danh sách đối số của lệnh gọi hàm - nhưng dấu phẩy trong danh sách đối số không phải là toán tử dấu phẩy. Tuy nhiên, sửa nó không hoàn toàn tầm thường. Có thể: Trong tuyên bố: expression1, expression2;đầu tiên expression1được đánh giá, có lẽ là do tác dụng phụ của nó (chẳng hạn như gọi hàm), sau đó có một điểm chuỗi, sau đó expression2được đánh giá và giá trị trả về
giật

119

Tôi đã thấy được sử dụng nhiều nhất trong whilecác vòng lặp:

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

Nó sẽ thực hiện thao tác, sau đó thực hiện kiểm tra dựa trên hiệu ứng phụ. Một cách khác là làm như thế này:

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

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

6
Mặc dù nó có thể ít mơ hồ hơn 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 là sẽ không hoạt động nếu read_stringkhông có giá trị trả về (hoặc không có giá trị có ý nghĩa). (Chỉnh sửa: Xin lỗi, không nhận thấy bài đăng này bao nhiêu tuổi.)
jamesdlin

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

8
@jamesdlin ... và mọi người vẫn đọc nó. Nếu bạn có điều gì hữu ích để nói, thì hãy nói ra. Diễn đàn có vấn đề với các chủ đề được phục hồi bởi vì các chủ đề thường được sắp xếp theo ngày của bài cuối cùng. StackOverflow không có vấn đề như vậy.
Dimitar Slavchev

3
@potrzebie Tôi thích cách tiếp cận dấu phẩy tốt hơn nhiều while(1)break;
Michael

38

Các nhà điều hành dấu phẩy sẽ đánh giá các toán hạng bên trái, loại bỏ kết quả và sau đó đánh giá các toán hạng phải và đó sẽ là kết quả. Việc sử dụng thành ngữ như được lưu ý trong liên kết là khi khởi tạo các biến được sử dụng trong một forvòng lặp và nó đưa ra ví dụ sau:

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

Mặt khác, không có nhiều ứng dụng tuyệt vời của toán tử dấu phẩy , mặc dù rất dễ lạm dụng để tạo mã khó đọc và duy trì.

Từ dự thảo tiêu chuẩn C99 , ngữ pháp 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 trình tự sau khi đánh giá của nó. 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 nó sau điểm chuỗi tiếp theo, hành vi không được xác định.

Chú thích 97 nói:

Một toán tử dấu phẩy không mang lại một giá trị .

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

Điều quan trọng cần lưu ý là toán tử dấu phẩy có mức ưu tiên thấp nhất và do đó, có những trường hợp sử dụng ()có thể tạo ra 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ó đầu ra sau:

1 4

28

Toán tử dấu phẩy kết hợp hai biểu thức hai bên của nó thành một, đánh giá cả hai theo thứ tự từ trái sang phải. Giá trị của phía bên phải được trả về là 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ủaexpr2 một lệnh gọi hoặc gán chức năng.

Nó thường được nhìn thấy trong forcác vòng lặp để khởi tạo 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ỉ sử dụng nó "trong sự tức giận" trong một trường hợp khác, khi kết thúc hai thao tác phải luôn đi cùng nhau trong một macro. Chúng tôi đã có mã sao chép các giá trị nhị phân khác nhau vào bộ đệm byte để gửi trên mạng và một con trỏ được duy trì ở nơi chúng tôi đã có:

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 các giá trị là shorts hoặc ints 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 điều này không thực sự hợp lệ C, vì (short *)ptrkhô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, cách tiếp cận này dựa vào tất cả các nhà phát triển ghi nhớ để đặt cả hai câu lệnh trong mọi thời điểm. Chúng tôi muốn một hàm nơi bạn có thể chuyển qua con trỏ đầu ra, giá trị và loại giá trị. Đây là C, không phải C ++ với các mẫu, chúng tôi không thể có một hàm tùy ý, vì vậy chúng tôi đã giải quyết trê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ử dấu 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ư các câu lệnh như chúng tôi 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ớ Mã hoàn thành của Steve McConnell khuyên không nên sử dụng các toán tử dấu phẩy trong một forvòng lặp: để dễ đọc và duy trì, vòng lặp chỉ nên được điều khiển bởi một biến và các biểu thức trong fordòng chỉ nên chứa mã kiểm soát vòng lặp, không phải các bit bổ sung khác của khởi tạo hoặc bảo trì vòng lặp.


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

Đôi khi tôi đánh giá cao một chút về tính dài dòng như trường hợp ở đây nơi bạn mô tả sự phát triển của một giải pháp (làm thế nào bạn đạt được điều đó).
diod xanh

8

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

Vì thế...

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

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

nên kết quả là x được đặt thành 8.


3

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

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

2

Nơi duy nhất tôi thấy nó hữu ích là khi bạn viết một vòng lặp thú vị, nơi bạn muốn làm nhiều việc trong một trong các biểu thức (có thể là biểu thức init hoặc biểu thức vòng lặp.

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;
}

Xin lỗi nếu có bất kỳ lỗi cú pháp nào hoặc nếu tôi trộn lẫn vào bất cứ điều gì không nghiêm ngặt C. Tôi không cho rằng, toán tử 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 có lẽ tôi sẽ sử dụng một whilevòng lặp để thay vào đó nhiều biểu thức trên init và loop sẽ rõ ràng hơn. (Và tôi sẽ khởi tạo i1 và i2 nội tuyến thay vì khai báo và sau đó khởi tạo .... blah blah blah.)


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

-2

Tôi đang hồi sinh điều này chỉ đơn giản là để giải quyết các câu hỏi từ @Rajesh và @JeffMercado mà tôi nghĩ là rất quan trọng vì đây là một trong những công cụ tìm kiếm hàng đầu.

Lấy đoạn mã sau đây làm ví dụ

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

Nó sẽ in

1 5

Các itrường hợp được xử lý như được giải thích bởi hầu hết các câu trả lời. Tất cả các biểu thức được đánh giá theo thứ tự từ trái sang phải nhưng chỉ biểu thức cuối cùng được gán cho i. Kết quả của ( biểu thức )is 1`.

Các jtrường hợp theo các quy tắc ưu tiên khác nhau kể từ khi ,có ưu tiên toán tử thấp nhất. Do những quy tắc, trình biên dịch thấy nhiệm vụ thể hiện, liên tục, liên tục ... . Các biểu thức một lần nữa được đánh giá theo thứ tự từ trái sang phải và do đó tác dụng phụ của chúng vẫn hiển thị, do đó, j5kết quả của j = 5.

Xen kẽ, int j = 5,4,3,2,1;không được cho phép bởi các đặc tả ngôn ngữ. Một trình khởi tạo mong đợi một biểu thức gán để một ,toán tử trực tiếp không được phép.

Hi vọng điêu nay co ich.

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.