1. Các toán tử bitwise

C ++ cung cấp các toán tử thao tác 6 bit, thường được gọi là toán tử bitwise:

Toán tửký hiệuFormHoạt động
dịch trái <<x << ytất cả các bit trong x dịch chuyển sang trái y bit
dịch phải >>x >> ytất cả các bit trong x dịch sang phải y bit
bitwise NOT~~xtất cả các bit trong x được lật
bitwise AND&x & ymỗi bit trong x VÀ mỗi bit trong y
bitwise OR|x | ymỗi bit trong x HOẶC mỗi bit trong y
bitwise XOR^x ^ ymỗi bit trong x XOR mỗi bit trong y

Trong các ví dụ sau, chúng ta sẽ chủ yếu làm việc với các giá trị nhị phân 4 bit. Điều này là để thuận tiện và giữ cho các ví dụ đơn giản. Trong các chương trình thực tế, số lượng bit được sử dụng dựa trên kích thước của đối tượng (ví dụ: một đối tượng 2 byte sẽ lưu trữ 16 bit).

Để dễ đọc, chúng ta cũng sẽ bỏ qua tiền tố 0b bên ngoài các ví dụ code (ví dụ: thay vì 0b0101, chúng ta sẽ chỉ sử dụng 0101).

Toán tử dịch chuyển sang trái theo chiều dọc bit (<<) và dịch chuyển theo chiều ngược chiều sang phải (>>)

Toán tử dịch chuyển trái bit (<<) dịch chuyển các bit sang trái. Toán hạng bên trái là biểu thức để dịch chuyển các bit và toán hạng bên phải là một số nguyên các bit để dịch chuyển sang trái.

Vì vậy, khi chúng ta nói x << 1, chúng ta đang nói “dịch chuyển các bit trong biến x sang trái 1 chỗ”. Các bit mới được chuyển vào từ phía bên phải nhận giá trị 0.

0011 << 1 là 0110

0011 << 2 là 1100

0011 << 3 là 1000

Lưu ý rằng trong trường hợp thứ ba, chúng ta đã dịch chuyển một chút về cuối số! Các bit bị dịch chuyển khỏi phần cuối của số nhị phân sẽ bị mất vĩnh viễn.

Toán tử dịch chuyển sang phải theo chiều bit (>>) dịch chuyển các bit sang phải.

1100 >> 1 là 0110

1100 >> 2 là 0011

1100 >> 3 là 0001

Lưu ý rằng trong trường hợp thứ ba, chúng ta đã dịch chuyển một chút về phía cuối bên phải của số, vì vậy nó bị mất.

Đây là một ví dụ về thực hiện một số dịch chuyển bit:

#include <bitset>
#include <iostream>
 
int main()
{
    std::bitset<4> x { 0b1100 };
 
    std::cout << x << '\n';
    std::cout << (x >> 1) << '\n'; // shift right by 1, yielding 0110
    std::cout << (x << 1) << '\n'; // shift left by 1, yielding 1000
 
    return 0;
}

This prints:

1100
0110
1000

Lưu ý rằng kết quả của việc áp dụng các toán tử dịch chuyển bit cho một số nguyên có dấu phụ thuộc vào trình biên dịch trước C ++ 20.

Lưu ý:

Trước C ++ 20, không thay đổi số nguyên có dấu (và thậm chí sau đó, có lẽ vẫn tốt hơn nếu sử dụng không dấu)

Gì!? Không phải toán tử << và toán tử >> được sử dụng cho đầu vào và đầu ra?

Các chương trình ngày nay thường không sử dụng nhiều toán tử dịch chuyển trái và phải theo chiều bit để dịch chuyển các bit. Thay vào đó, bạn có xu hướng thấy toán tử dịch chuyển trái theo chiều bit được sử dụng với std :: cout để xuất văn bản. Hãy xem xét chương trình sau:

#include <bitset>
#include <iostream>
 
int main()
{
    unsigned int x { 0b0100 };
    x = x << 1; // use operator<< for left shift
    std::cout << std::bitset<4>{ x }; // use operator<< for output
 
    return 0;
}

Kết quả:

1000

Trong chương trình trên, làm thế nào để toán tử << biết để dịch chuyển các bit trong một trường hợp và xuất x trong một trường hợp khác? Câu trả lời là std :: cout đã quá tải (cung cấp định nghĩa thay thế cho) toán tử << thực hiện đầu ra bảng điều khiển chứ không phải dịch chuyển bit.

Khi trình biên dịch thấy rằng toán hạng bên trái của operator << là std :: cout, nó biết rằng nó nên gọi operator << rằng std :: cout đã được nạp chồng để thực hiện đầu ra. Nếu toán hạng bên trái là một kiểu tích phân, thì toán tử << biết nó nên thực hiện hành vi dịch chuyển bit thông thường.

2. Điều tương tự cũng áp dụng cho nhà điều hành >>.

Lưu ý rằng nếu bạn đang sử dụng toán tử << cho cả đầu ra và dịch chuyển sang trái, thì bắt buộc phải có dấu ngoặc đơn:

#include <bitset>
#include <iostream>
 
int main()
{
	std::bitset<4> x{ 0b0110 };
 
	std::cout << x << 1 << '\n'; // print value of x (0110), then 1
	std::cout << (x << 1) << '\n'; // print x left shifted by 1 (1100)
 
	return 0;
}

Kết quả:

01101
1100

Dòng đầu tiên in giá trị của x (0110), sau đó là chữ 1. Dòng thứ hai in giá trị của x dịch sang trái 1 (1100).

Chúng ta sẽ nói nhiều hơn về quá tải toán tử trong một phần trong tương lai, bao gồm thảo luận về cách nạp chồng toán tử cho các mục đích riêng của bạn.

3. Bitwise NOT

Toán tử NOT bitwise (~) có lẽ là toán tử dễ hiểu nhất trong tất cả các toán tử bitwise. Nó chỉ cần lật từng bit từ 0 đến 1 hoặc ngược lại. Lưu ý rằng kết quả của một bitwise NOT phụ thuộc vào kích thước kiểu dữ liệu của bạn.

Lật 4 bit:

~ 0100 là 1011

Lật 8 bit:

~ 0000 0100 là 1111 1011

Trong cả trường hợp 4 bit và 8 bit, chúng ta bắt đầu với cùng một số (nhị phân 0100 giống với 0000 0100 theo cách tương tự với số thập phân 7 giống với 07), nhưng chúng ta kết thúc với một kết quả khác.

Chúng ta có thể thấy điều này hoạt động trong chương trình sau:

#include <bitset>
#include <iostream>
 
int main()
{
	std::cout << std::bitset<4>{ ~0b0100u } << ' ' << std::bitset<8>{ ~0b0100u };
 
	return 0;
}

Bản in này:

1011 11111011

4. Bitwise OR

Bitwise OR (|) hoạt động giống như đối số OR logic của nó. Tuy nhiên, thay vì áp dụng OR cho các toán hạng để tạo ra một kết quả duy nhất, OR theo từng bit sẽ áp dụng cho từng bit! Ví dụ, hãy xem xét biểu thức 0b0101 | 0b0110.

Để thực hiện (bất kỳ) phép toán bitwise, dễ nhất là xếp hai toán hạng lên như sau:

0 1 0 1 OR

0 1 1 0

và sau đó áp dụng phép toán cho từng cột bit.

Nếu bạn nhớ, lôgic OR đánh giá thành true (1) nếu toán hạng bên trái, bên phải hoặc cả hai đều đúng (1) và 0 nếu ngược lại. Bitwise OR cho kết quả là 1 nếu bit trái, phải hoặc cả hai là 1 và 0 nếu ngược lại. Do đó, biểu thức đánh giá như thế này:

0 1 0 1 OR

0 1 1 0

——-

0 1 1 1

Kết quả của chúng tôi là 0111 nhị phân.

#include <bitset>
#include <iostream>
 
int main()
{
	std::cout << (std::bitset<4>{ 0b0101 } | std::bitset<4>{ 0b0110 });
 
	return 0;
}

Kết quả

0111

Chúng ta có thể làm điều tương tự với các biểu thức OR ghép, chẳng hạn như 0b0111 | 0b0011 | 0b0001. Nếu bất kỳ bit nào trong cột là 1, kết quả của cột đó là 1.

0 1 1 1 OR

0 0 1 1 OR

0 0 0 1

——–

0 1 1 1

Đây là code cho phần trên:

#include <bitset>
#include <iostream>
 
int main()
{
	std::cout << (std::bitset<4>{ 0b0111 } | std::bitset<4>{ 0b0011 } | std::bitset<4>{ 0b0001 });
 
	return 0;
}

Kết quả

0111

5. Bitwise AND

Bitwise AND (&) hoạt động tương tự như trên. Hợp lý AND đánh giá là true nếu cả toán hạng bên trái và bên phải đều đánh giá là true. Bitwise AND đánh giá là true (1) nếu cả hai bit trong cột là 1. Xét biểu thức 0b0101 & 0b0110. Xếp từng bit lên và áp dụng phép toán AND cho từng cột bit:

0 1 0 1 AND

0 1 1 0

——–

0 1 0 0

#include <bitset>
#include <iostream>
 
int main()
{
	std::cout << (std::bitset<4>{ 0b0101 } & std::bitset<4>{ 0b0110 });
 
	return 0;
}

Kết quả này:

0100

Tương tự, chúng ta có thể làm điều tương tự với các biểu thức AND, chẳng hạn như 0b0001 & 0b0011 & 0b0111. Nếu tất cả các bit trong một cột là 1, kết quả của cột đó là 1.

0 0 0 1 AND

0 0 1 1 AND

0 1 1 1

——–

0 0 0 1

#include <bitset>
#include <iostream>
 
int main()
{
	std::cout << (std::bitset<4>{ 0b0001 } & std::bitset<4>{ 0b0011 } & std::bitset<4>{ 0b0111 });
 
	return 0;
}

6. Toán tử gán bitwise

Tương tự như các toán tử gán số học, C ++ cung cấp các toán tử gán theo bit để tạo điều kiện dễ dàng sửa đổi các biến.

Toán tửký hiệuFormHoạt động
Phép gán dịch trái<<=x <<= yDịch x sang trái y bit
Gán dịch phải>>=x >>= yDịch x sang phải theo y bit
Gán bitwise|=x |= yGán x | y đến x
Gán bitwise &=x &= yGán x & y cho x
Phép gán XOR theo bit ^=x ^= yGán x ^ y cho x

Ví dụ, thay vì viết x = x >> 1 ;, bạn có thể viết x >> = 1 ;.

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

#include <bitset>
#include <iostream>
 
int main()
{
    std::bitset<4> bits { 0b0100 };
    bits >>= 1;
    std::cout << bits;
 
    return 0;
}

Chương trình này in:

0010

7. Tóm lược

Tóm tắt cách đánh giá các hoạt động bitwise sử dụng phương pháp cột:

Khi đánh giá theo bitwise OR, nếu bất kỳ bit nào trong cột là 1, kết quả cho cột đó là 1.

Khi đánh giá bitwise AND, nếu tất cả các bit trong một cột là 1, kết quả cho cột đó là 1.

Khi đánh giá bitwise XOR, nếu có một số bit lẻ trong một cột, kết quả cho cột đó là 1.

Trong bài học tiếp theo, chúng ta sẽ khám phá cách các toán tử này có thể được sử dụng kết hợp với mặt nạ bit để tạo điều kiện thuận lợi cho thao tác trên bit.

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!