Menu

Toán tử trong C++: số học, so sánh, logic và hơn thế

Tìm hiểu toán tử trong C++: số học, so sánh, logic, gán và bitwise, cùng những cái bẫy quanh phép chia số nguyên, độ ưu tiên và cơ chế short-circuit.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Toán tử làm gì

Toán tử (operator) là ký hiệu thực hiện một thao tác trên một hoặc nhiều giá trị, gọi là toán hạng (operand). Thật ra bạn đã dùng một toán tử rồi: dấu = gắn một giá trị vào biến. C++ có rất nhiều toán tử cho tính toán, so sánh, logic và thao tác bit, cùng vài chỗ hiểm hay làm người mới vấp.

Ở trang trước bạn đã thấy const khóa cứng một giá trị như thế nào. Toán tử là cách bạn tính ra những giá trị cần lưu, dù là hằng hay không. Cùng đi qua các nhóm toán tử bạn sẽ dùng hằng ngày.

Toán tử số học

Có năm toán tử số học: +, -, *, / và % (modulo, tức phần dư của phép chia):

Cái bẫy lớn nằm ở phép chia. 17 / 5 in ra 3, không phải 3.4. Khi cả hai toán hạng đều là số nguyên, / thực hiện phép chia số nguyên và bỏ đi phần thập phân: kết quả bị cắt về phía 0 chứ không làm tròn. Muốn có kết quả thập phân thật, ít nhất một toán hạng phải là số thực:

Toán tử % chỉ dùng được với số nguyên. Dùng % với double sẽ gây lỗi biên dịch; với số thực, hãy dùng std::fmod trong <cmath> để lấy phần dư.

Toán tử gán và toán tử gán kết hợp

Một dấu = là để gán, không phải để so sánh. C++ có các dạng kết hợp, gộp một phép tính với phép gán để bạn không phải viết lại tên biến:

Một lỗi kinh điển và rất khó chịu là viết = trong điều kiện trong khi ý bạn là ==. if (x = 0) gán 0 cho x rồi kiểm tra kết quả (là giá trị falsy), thay vì so sánh. Các trình biên dịch hiện đại sẽ cảnh báo lỗi này khi bạn bật cảnh báo: hãy luôn bật -Wall và đừng bỏ qua cảnh báo đó.

Toán tử so sánh

Toán tử so sánh đặt một câu hỏi có/không và trả về một bool (true hoặc false):

Mặc định cout in bool ra dưới dạng 1 hoặc 0. Chỉ cần đưa boolalpha vào stream một lần, nó sẽ chuyển sang in chữ true/false cho phần còn lại của stream đó.

Một cái bẫy khó thấy: đừng nối các phép so sánh như 1 < x < 10. Biểu thức này được hiểu là (1 < x) < 10: phép so sánh đầu tiên trả về một bool (0 hoặc 1), rồi giá trị đó được so với 10, nên cả biểu thức gần như luôn là true. Hãy viết 1 < x && x < 10.

Toán tử logic và short-circuit

&& (và), || (hoặc) và ! (phủ định) kết hợp các biểu thức boolean. Hai toán tử đầu có cơ chế short-circuit: việc tính toán dừng lại ngay khi đã biết kết quả.

Cả check("A") lẫn check("B") đều không bao giờ chạy: đó chính là short-circuit. Đây không chỉ là một cách tối ưu mà còn là một công cụ. Bạn có thể yên tâm viết if (ptr != nullptr && ptr->ready), vì phần ptr->ready chỉ được tính khi ptr khác null, tránh việc truy cập qua một con trỏ không hợp lệ.

Tăng và giảm

++ cộng thêm một; -- trừ đi một. Mỗi toán tử có dạng tiền tố (prefix) và hậu tố (postfix), và sự khác biệt này quan trọng khi bạn dùng đến kết quả:

Khi bạn chỉ cần tác dụng phụ (chẳng hạn trong vòng lặp for), hãy dùng ++i. Với int thông thường thì hai cách như nhau, nhưng với các kiểu nặng hơn như iterator, tăng sau phải sao chép giá trị cũ trước, tốn công vô ích.

Thêm một lưu ý: đừng sửa cùng một biến hai lần trong một biểu thức, như i = i++ + 1; hay arr[i] = i++;. Thứ tự của các lần cập nhật đó không được quy định, và kết quả là hành vi không xác định. Mỗi câu lệnh chỉ nên sửa mỗi biến một lần.

Toán tử bitwise và độ ưu tiên

Với các tác vụ cấp thấp có toán tử bitwise: & (và), | (hoặc), ^ (xor), ~ (phủ định) và các phép dịch bit <<, >>.

Cẩn thận: << và >> cũng là toán tử stream của cout. Trong một dòng cout, bạn thường phải đặt phép dịch bit trong ngoặc, nếu không trình biên dịch sẽ hiểu đó là thao tác đưa dữ liệu vào stream.

Cuối cùng, độ ưu tiên quyết định phép nào được tính trước khi bạn trộn nhiều toán tử. * và / được ưu tiên hơn + và -, giống như trong toán học, nên 2 + 3 * 4 bằng 14. Phép so sánh có độ ưu tiên thấp hơn phép số học, còn &&/|| lại thấp hơn nữa. Khi không chắc, đừng cố học thuộc cả bảng: hãy thêm dấu ngoặc. (a + b) * c rõ ràng hơn nhiều so với việc bắt người đọc nhớ quy tắc.

Tiếp theo: ép kiểu

Ở trên bạn đã thấy (double)a / b buộc một số nguyên tham gia phép chia số thực. Đó là ép kiểu (cast): chủ động chuyển một giá trị từ kiểu này sang kiểu khác. Ở trang tiếp theo, chúng ta sẽ tìm hiểu các công cụ chuyển kiểu của C++, từ chuyển đổi ngầm định đến static_cast, và khi nào thì mỗi cách là an toàn.

Câu hỏi thường gặp

C++ có những toán tử nào?

C++ chia toán tử thành các nhóm: số học (+ - * / %), so sánh (== != < > <= >=), logic (&& || !), gán (= += -= ...), tăng/giảm (++ --) và bitwise (& | ^ ~ << >>). Ngoài ra còn có toán tử ba ngôi ?: và một số toán tử khác như sizeof.

Tại sao 5 / 2 lại bằng 2 trong C++?

Vì cả hai toán hạng đều là int, nên / thực hiện phép chia số nguyên và phần thập phân bị bỏ đi, không phải làm tròn. Muốn được 2.5, hãy để ít nhất một toán hạng là số thực: 5.0 / 2 hoặc 5 / 2.0.

++i và i++ trong C++ khác nhau thế nào?

Cả hai đều cộng 1 vào i. ++i (tăng trước) tăng giá trị rồi trả về giá trị mới; i++ (tăng sau) trả về giá trị cũ rồi mới tăng. Khi bạn chỉ cần tác dụng phụ, hãy dùng ++i.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu