Sử dụng toán tử bitwise cho Boolean trong C ++


81

Có lý do gì để không sử dụng các toán tử bitwise &, |, và ^ cho các giá trị "bool" trong C ++?

Đôi khi tôi gặp phải những tình huống mà tôi muốn chính xác một trong hai điều kiện là true (XOR), vì vậy tôi chỉ cần ném toán tử ^ vào một biểu thức điều kiện. Đôi khi tôi cũng muốn tất cả các phần của một điều kiện được đánh giá xem kết quả có đúng hay không (thay vì đoản mạch), vì vậy tôi sử dụng & và |. Đôi khi tôi cũng cần tích lũy các giá trị Boolean, và & = và | = có thể khá hữu ích.

Tôi đã hơi nhướng mày khi làm điều này, nhưng mã vẫn có ý nghĩa và rõ ràng hơn so với cách khác. Có lý do gì KHÔNG sử dụng chúng cho bools? Có bất kỳ trình biên dịch hiện đại nào cho kết quả không tốt cho việc này không?


Nếu bạn đã học đủ nhiều về C ++ kể từ khi đặt câu hỏi này, bạn nên quay lại và bỏ chấp nhận câu trả lời hiện tại và chấp nhận câu trả lời của Patrick Johnmeyer vì đã giải thích chính xác sự khác biệt về chập mạch.
codetaku

Nhận xét chung vì mọi người đặt câu hỏi liệu đây có phải là một ý kiến ​​hay không. Nếu bạn quan tâm đến phạm vi chi nhánh của các bài kiểm tra đơn vị, thì việc giảm chi nhánh là điều có thể thực hiện được. Các toán tử bitwise rất hữu ích cho việc này.
qznc

Câu trả lời:


59

||&&là các toán tử boolean và các toán tử tích hợp được đảm bảo trả về một trong hai truehoặc false. Không có gì khác.

|, &^là các toán tử bitwise. Khi miền các số bạn thao tác chỉ là 1 và 0, thì chúng hoàn toàn giống nhau, nhưng trong trường hợp các boolean của bạn không hoàn toàn là 1 và 0 - như trường hợp của ngôn ngữ C - bạn có thể mắc phải một số hành vi bạn không muốn. Ví dụ:

BOOL two = 2;
BOOL one = 1;
BOOL and = two & one;   //and = 0
BOOL cand = two && one; //cand = 1

Tuy nhiên, trong C ++, boolkiểu được đảm bảo chỉ là a truehoặc a false(chuyển đổi hoàn toàn thành tương ứng 10), vì vậy sẽ ít lo lắng hơn từ lập trường này, nhưng thực tế là mọi người không quen nhìn thấy những thứ như vậy trong mã đưa ra một lập luận xác đáng để không làm điều đó. Chỉ cần nói b = b && xvà làm với nó.


không có cái gọi là toán tử xor logic ^^ trong C ++. Đối với cả hai đối số là bools! = Làm điều tương tự, nhưng có thể nguy hiểm nếu một trong hai đối số không phải là bool (như cách sử dụng ^ cho xor logic). Không chắc chắn cách này đã được chấp nhận ...
Greg Rogers

1
Nhìn lại, điều đó thật là ngớ ngẩn. Dù sao, đã thay đổi ví dụ thành && để khắc phục sự cố. Tôi thề rằng tôi đã sử dụng ^^ đôi khi trong quá khứ, nhưng điều này không phải là trường hợp.
Patrick

C có kiểu bool chỉ có thể là 0 hoặc 1 kể từ 10 năm nay. _Bool
Johannes Schaub - litb

16
Họ vẫn không giống nhau ngay cả đối với bools vì họ không ngắn circut
aaronman

3
Vâng, tôi thực sự tin rằng đây sẽ không phải là câu trả lời được chấp nhận cho đến khi bạn thay đổi "Khi miền các số bạn sử dụng chỉ là 1 và 0, thì chúng hoàn toàn giống nhau" thành "Khi miền các số bạn sử dụng chỉ là 1 và 0, sự khác biệt duy nhất là các toán tử bitwise không bị đoản mạch ". Tuyên bố trước đây là sai hoàn toàn, vì vậy trong khi đó tôi đã từ chối câu trả lời này vì nó chứa câu đó.
codetaku

32

Hai lý do chính. Tóm lại, hãy cân nhắc kỹ lưỡng; có thể có một lý do chính đáng cho điều đó, nhưng nếu có RẤT rõ ràng trong nhận xét của bạn vì nó có thể giòn và như bạn nói, mọi người thường không quen nhìn thấy những đoạn mã như thế này.

Bitwise xor! = Logical xor (ngoại trừ 0 và 1)

Thứ nhất, nếu bạn đang hoạt động trên các giá trị khác falsetrue(hoặc 01, dưới dạng số nguyên), ^toán tử có thể đưa ra hành vi không tương đương với xor logic. Ví dụ:

int one = 1;
int two = 2;

// bitwise xor
if (one ^ two)
{
  // executes because expression = 3 and any non-zero integer evaluates to true
}

// logical xor; more correctly would be coded as
//   if (bool(one) != bool(two))
// but spelled out to be explicit in the context of the problem
if ((one && !two) || (!one && two))
{
  // does not execute b/c expression = ((true && false) || (false && true))
  // which evaluates to false
}

Ghi có cho người dùng @Patrick vì đã thể hiện điều này trước tiên.

Thứ tự hoạt động

Thứ hai, |, &, và ^, như các nhà khai thác Bitwise, không ngắn mạch. Ngoài ra, nhiều toán tử bitwise được xâu chuỗi lại với nhau trong một câu lệnh duy nhất - ngay cả với dấu ngoặc đơn rõ ràng - có thể được sắp xếp lại bằng cách tối ưu hóa trình biên dịch, vì cả 3 hoạt động thường giao hoán. Điều này rất quan trọng nếu thứ tự của các hoạt động quan trọng.

Nói cách khác

bool result = true;
result = result && a() && b();
// will not call a() if result false, will not call b() if result or a() false

không phải lúc nào cũng cho kết quả (hoặc trạng thái kết thúc) giống như

bool result = true;
result &= (a() & b());
// a() and b() both will be called, but not necessarily in that order in an
// optimizing compiler

Điều này đặc biệt quan trọng vì bạn có thể không kiểm soát các phương pháp a()b()hoặc ai đó khác có thể đến và thay đổi chúng sau đó mà không hiểu về sự phụ thuộc và gây ra lỗi khó chịu (và thường chỉ phát hành bản dựng).


Không thực sự là một so sánh công bằng nếu bạn chuyển thành bool trong một trường hợp và không trong trường hợp khác ... Việc chuyển thành bool trong cả hai trường hợp là điều gì sẽ làm cho nó hoạt động, và tại sao nó lại dễ vỡ (vì bạn phải nhớ nó).
Greg Rogers

Giải thích về hiện tượng đoản mạch là tại sao đây hoàn toàn phải là câu trả lời được chấp nhận; Câu trả lời Patrick (err ... các Patrick khác, những người chỉ được đặt tên là Patrick) là hoàn toàn sai khi nói "Khi lĩnh vực số bạn hoạt động trên chỉ là 1 và 0, sau đó họ đều giống hệt nhau"
codetaku

13

tôi nghĩ

a != b

là những gì bạn muốn


1
Điều này là đúng, +1. Nhưng không có phiên bản gán của toán tử này ( !==có thể nói như vậy), vì vậy nếu bạn đang tính toán XOR của một chuỗi các boolgiá trị, bạn sẽ cần phải viết acc = acc!= condition(i);trong một thân vòng lặp. Trình biên dịch có thể có thể xử lý điều này một cách hiệu quả như thể !==đã tồn tại, nhưng một số có thể thấy rằng nó trông không đẹp và thích phương án thay thế là thêm các boolean dưới dạng số nguyên và sau đó kiểm tra xem tổng có phải là số lẻ hay không.
Marc van Leeuwen

10

Lông mày nhướng lên đủ để nói với bạn rằng hãy ngừng làm điều đó. Bạn không viết mã cho trình biên dịch, bạn viết nó cho các lập trình viên đồng nghiệp của mình trước rồi mới viết cho trình biên dịch. Ngay cả khi các trình biên dịch hoạt động, việc khiến người khác ngạc nhiên không phải là điều bạn muốn - các toán tử bitwise dành cho các hoạt động bit không dành cho bools.
Tôi cho rằng bạn cũng ăn táo bằng nĩa? Nó hoạt động nhưng nó làm mọi người ngạc nhiên vì vậy tốt hơn là không nên làm điều đó.


3
Có, đây phải là câu trả lời hàng đầu. Tuân theo nguyên tắc ít bất ngờ nhất. Mã làm những điều bất thường khó đọc và khó hiểu hơn và mã được đọc thường xuyên hơn nhiều so với mã được viết. Đừng sử dụng những thủ thuật đáng yêu như thế này.
Eloff

1
Tôi ở đây vì đồng nghiệp của tôi đã sử dụng toán tử bitwise "và" cho bool. Và bây giờ tôi đang dành thời gian để tìm hiểu xem điều này có đúng hay không. Nếu anh ấy không làm điều này, tôi sẽ làm điều gì đó hữu ích hơn bây giờ -_-. Nếu bạn coi trọng thời gian của đồng nghiệp, VUI LÒNG không sử dụng toán tử bitwise cho bool!
anton_rh

7

Nhược điểm của các toán tử cấp bitle.

Bạn hỏi:

“Có lý do nào đó không sử dụng các toán tử Bitwise &, |^cho '' giá trị bool trong C ++? ”

Có, các toán tử logic , đó là các toán tử boolean cấp cao được tích hợp sẵn !, &&||cung cấp các ưu điểm sau:

  • Đảm bảo chuyển đổi các đối số thành bool, tức là sang 01giá trị thứ tự.

  • Đánh giá ngắn mạch đảm bảo trong đó đánh giá biểu hiện dừng lại ngay khi biết kết quả cuối cùng.
    Điều này có thể được hiểu là một lôgic giá trị cây, với Đúng , Sai và Không xác định .

  • Tương đương có thể đọc được văn bản not, andor, ngay cả khi tôi không sử dụng chúng bản thân mình.
    Như ghi chú đọc Antimon trong một chú thích cũng là nhà khai thác bitlevel có thẻ thay thế, cụ thể là bitand, bitor, xorcompl, nhưng theo ý kiến của tôi đây là ít có thể đọc hơn and, ornot.

Nói một cách đơn giản, mỗi ưu điểm như vậy của các toán tử cấp cao là một nhược điểm của các toán tử cấp bit.

Đặc biệt, vì các toán tử bitwise thiếu chuyển đổi đối số thành 0/1 nên bạn sẽ nhận được ví dụ 1 & 20, while 1 && 2true. Ngoài ra ^, bitwise độc ​​quyền hoặc, có thể hoạt động sai theo cách này. Được coi là giá trị boolean 1 và 2 là giống nhau true, nhưng được coi là bitpatterns thì chúng khác nhau.


Cách diễn đạt logic hoặc / hoặc trong C ++.

Sau đó, bạn cung cấp một chút thông tin cơ bản cho câu hỏi,

“Đôi khi tôi gặp phải những tình huống mà tôi muốn chính xác một trong hai điều kiện là true (XOR), vì vậy tôi chỉ cần ném toán tử ^ vào một biểu thức điều kiện.”

Vâng, các toán tử bitwise có mức độ ưu tiên cao hơn các toán tử logic. Điều này đặc biệt có nghĩa là trong một biểu thức hỗn hợp, chẳng hạn như

a && b ^ c

bạn nhận được kết quả có lẽ bất ngờ a && (b ^ c).

Thay vào đó chỉ viết

(a && b) != c

diễn đạt ngắn gọn hơn ý của bạn.

Đối với nhiều đối số hoặc / hoặc không có toán tử C ++ nào thực hiện công việc. Ví dụ, nếu bạn viết a ^ b ^ chơn mà không phải là một biểu hiện nói rằng “một trong hai a, bhoặc clà đúng”. Thay vào đó nó nói, “Một số lẻ của a, bclà đúng sự thật”, đó có thể là 1 trong số họ hoặc tất cả 3 ...

Để bày tỏ sự chung hoặc / hoặc khi a, bclà kiểu bool, chỉ cần ghi

(a + b + c) == 1

hoặc, với các boolđối số không , hãy chuyển đổi chúng thành bool:

(!!a + !!b + !!c) == 1


Sử dụng &=để tích lũy kết quả boolean.

Bạn nói rõ thêm,

“Đôi khi tôi cũng cần tích lũy các giá trị Boolean &=|=?có thể khá hữu ích.”

Vâng, điều này tương ứng với việc kiểm tra xem tất cả hoặc bất kỳ điều kiện nào tương ứng được thỏa mãn hay không, và định luật de Morgan cho bạn biết cách đi từ điều này đến điều kiện khác. Tức là bạn chỉ cần một trong số chúng. Về nguyên tắc, bạn có thể sử dụng *=như một người điều hành &&=(đối với George Boole đã phát hiện ra, logic AND rất dễ được biểu thị dưới dạng phép nhân), nhưng tôi nghĩ rằng điều đó sẽ gây rắc rối và có thể đánh lừa những người bảo trì mã.

Cũng xem xét:

struct Bool
{
    bool    value;

    void operator&=( bool const v ) { value = value && v; }
    operator bool() const { return value; }
};

#include <iostream>

int main()
{
    using namespace std;

    Bool a  = {true};
    a &= true || false;
    a &= 1234;
    cout << boolalpha << a << endl;

    bool b = {true};
    b &= true || false;
    b &= 1234;
    cout << boolalpha << b << endl;
}

Đầu ra với Visual C ++ 11.0 và g ++ 4.7.1:

thật
sai

Lý do cho sự khác biệt trong kết quả là bitlevel &=không cung cấp chuyển đổi sang đối boolsố bên phải của nó.

Vì vậy, bạn mong muốn kết quả nào trong số những kết quả này &=?

Nếu là trước, truethì tốt hơn nên xác định một toán tử (ví dụ như ở trên) hoặc hàm được đặt tên, hoặc sử dụng một chuyển đổi rõ ràng của biểu thức bên phải hoặc viết cập nhật đầy đủ.


nhà khai thác Bitwise cũng có văn bản tương đương
Antimon

@Antimony: Tôi không hiểu rằng bình luận lúc đầu nhưng có, các hoạt động bitlevel có thẻ thay thế bitand, bitor, xorcompl. Tôi nghĩ đó là lý do tại sao tôi sử dụng chứng chỉ "có thể đọc được". Tất nhiên, khả năng đọc của vd compl 42là chủ quan. ;-)
Chúc mừng và hth. - Alf

Theo như tôi có thể nói các toán tử logic có thể được nạp chồng với các loại đối số khác (mặc dù chúng thường không phải vậy), vì vậy điểm đầu tiên "chuyển đổi đảm bảo đối số thành bool" chỉ là hệ quả của quy ước, không phải là đảm bảo rằng ngôn ngữ C ++ thực sự cung cấp bạn.
Marc van Leeuwen

@MarcvanLeeuwen: Có thể hệ thống con trực quan của bạn không nhận thấy “các toán tử boolean cấp cao được tích hợp sẵn !, && và ||”. Bạn nói đúng rằng các toán tử này có thể bị quá tải, và sau đó một vấn đề chính là ngữ nghĩa thay đổi một cách tinh vi, không còn đánh giá ngắn mạch nữa. Nhưng đó là một vấn đề với quá tải, không phải với các toán tử cài sẵn.
Chúc mừng và hth. - Alf

Đúng vậy (tôi đã bỏ lỡ điều đó). Nhưng sau đó câu lệnh vẫn gây hiểu lầm, bởi vì bạn chỉ đang nói rằng những toán tử đó nhận các đối số boolean; nếu (và chỉ khi) các phiên bản tích hợp của toán tử được chọn, thì trình biên dịch sẽ chèn một chuyển đổi của các đối số. Tương tự, toán tử cộng số nguyên không dấu tích hợp sẵn (cấp cao) +đảm bảo chuyển đổi các đối số của nó thành unsigned, nhưng điều đó không ngăn cản việc unsigned int n=-1; n+=3.14;thực sự sử dụng double(hoặc là float?) Phép toán cộng. (So ​​sánh &=ví dụ của bạn .)
Marc van Leeuwen

3

Trái ngược với câu trả lời của Patrick, C ++ không có ^^toán tử để thực hiện đoản mạch độc quyền hoặc. Nếu bạn nghĩ về nó trong một giây, có một ^^toán tử sẽ không có ý nghĩa gì: với hoặc, kết quả luôn phụ thuộc vào cả hai toán hạng. Tuy nhiên, cảnh báo của Patrick về các kiểu không phải bool"Boolean" cũng tốt như nhau khi so sánh 1 & 2với 1 && 2. Một ví dụ cổ điển về điều này là GetMessage()hàm Windows , trả về một trạng thái ba BOOL: nonzero 0, hoặc -1.

Sử dụng &thay thế &&|thay vì ||không phải là lỗi đánh máy phổ biến, vì vậy nếu bạn đang cố tình làm điều đó, nó xứng đáng được nhận xét tại sao.


6
Một ^^toán tử sẽ vẫn hữu ích bất kể việc xem xét ngắn mạch. Nó sẽ a) đánh giá các toán hạng trong một bối cảnh boolean và b) bảo lãnh trả về một 1 hoặc 0.
Craig McQueen

@CraigMcQueen. Thật đơn giản để xác định inline bool XOR(bool a,bool b) { return a!=b; }và lấy những gì bạn muốn, ngoại trừ nó là một hàm chứ không phải là một toán tử (infix). Hoặc bạn có thể sử dụng trực tiếp !=hoặc nạp chồng một số toán tử khác với ý nghĩa này, nhưng tất nhiên bạn cần phải rất cẩn thận để không vô tình sử dụng quá tải ngoài ý muốn của cùng một tên. Và tình cờ ||&&trở lại truehoặc false, không 1hoặc 0.
Marc van Leeuwen

Và có vẻ như thực tế là !, ||&&đánh giá lập luận của họ trong một bối cảnh Boolean chỉ là bởi vì các nhà khai thác hiếm khi quá tải để chấp nhận các loại khác; Theo như tôi có thể nói thì ngôn ngữ không cho phép quá tải như vậy, và do đó không đảm bảo đánh giá các đối số trong ngữ cảnh Boolean.
Marc van Leeuwen

2

Patrick đã đưa ra những điểm tốt và tôi sẽ không lặp lại chúng. Tuy nhiên, tôi có thể khuyên bạn nên giảm các câu lệnh 'if' thành tiếng Anh có thể đọc được ở bất cứ đâu có thể bằng cách sử dụng boolean vars được đặt tên tốt.

bool onlyAIsTrue = (a && !b); // you could use bitwise XOR here
bool onlyBIsTrue = (b && !a); // and not need this second line
if (onlyAIsTrue || onlyBIsTrue)
{
 .. stuff ..
}

Bạn có thể nghĩ rằng việc sử dụng boolean có vẻ không cần thiết, nhưng nó có ích với hai điều chính:

  • Mã của bạn dễ hiểu hơn vì boolean trung gian cho điều kiện 'nếu' làm cho ý định của điều kiện rõ ràng hơn.
  • Nếu bạn đang sử dụng mã không chuẩn hoặc không mong muốn, chẳng hạn như toán tử bitwise trên các giá trị boolean, mọi người có thể dễ dàng hiểu lý do tại sao bạn làm điều này hơn.

CHỈNH SỬA: Bạn đã không nói rõ ràng rằng bạn muốn điều kiện cho câu lệnh 'if' (mặc dù điều này có vẻ rất có thể xảy ra), đó là giả định của tôi. Nhưng đề xuất của tôi về giá trị boolean trung gian vẫn còn.


1

Việc sử dụng các phép toán bit cho bool giúp bộ xử lý tiết kiệm logic dự đoán nhánh không cần thiết, do một lệnh 'cmp' do các phép toán logic mang lại.

Thay thế logic bằng các phép toán bit (trong đó tất cả các toán hạng là bool) tạo ra mã hiệu quả hơn cung cấp cùng một kết quả. Lý tưởng nhất là hiệu quả phải lớn hơn tất cả các lợi ích ngắn mạch có thể được tận dụng trong việc sắp xếp sử dụng các phép toán logic.

Điều này có thể làm cho mã hơi khó đọc mặc dù lập trình viên nên nhận xét nó với lý do tại sao nó được làm như vậy.


0

IIRC, nhiều trình biên dịch C ++ sẽ cảnh báo khi cố truyền kết quả của một thao tác bitwise dưới dạng bool. Bạn sẽ phải sử dụng kiểu ép kiểu để làm cho trình biên dịch hài lòng.

Sử dụng thao tác bitwise trong biểu thức if sẽ phục vụ cùng một lời chỉ trích, mặc dù có lẽ không phải bởi trình biên dịch. Mọi giá trị khác 0 đều được coi là đúng, vì vậy, một cái gì đó như "if (7 & 3)" sẽ đúng. Hành vi này có thể được chấp nhận trong Perl, nhưng C / C ++ là những ngôn ngữ rất rõ ràng. Tôi nghĩ rằng lông mày của Spock là do siêng năng. :) Tôi sẽ thêm "== 0" hoặc "! = 0" để làm cho nó hoàn toàn rõ ràng mục tiêu của bạn là gì.

Nhưng dù sao, nó nghe có vẻ là một sở thích cá nhân. Tôi sẽ chạy mã thông qua lint hoặc công cụ tương tự và xem liệu nó có cho rằng đó là một chiến lược không khôn ngoan hay không. Cá nhân, nó giống như một lỗi mã hóa.


Bài viết của bạn đã thúc đẩy tôi kiểm tra điều này và gcc đã không cảnh báo! Thật đáng tiếc, bởi vì tôi sẽ sử dụng cảnh báo đó để biện minh cho việc để tôi chạy & = để tích lũy kết quả bool, tin rằng những người khác sẽ nhìn thấy cảnh báo nếu sau này họ thay đổi bài kiểm tra của tôi. Mã của tôi sẽ không thành công đối với số nguyên chẵn, vì chúng đánh giá là 0 / false - không có cảnh báo! Thời gian để cấu trúc lại ...
nhà hiền triết
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.