Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 5.811 phút đọc

Toán tử ba ngôi

Sau bài này bạn sẽ làm được

  • Viết biểu thức điều kiện đúng cú pháp
  • Biết khi nào toán tử ba ngôi làm mã dễ đọc hơn if
  • Nhận ra bẫy hai nhánh khác kiểu bị ép ngầm về một kiểu

Đây là toán tử duy nhất trong C có ba toán hạng. Nó rút gọn được một khối if else chỉ để gán giá trị thành một dòng. Dùng đúng chỗ thì mã gọn và rõ, dùng sai chỗ thì mã trở nên khó đọc hơn hẳn.

#Cú pháp

dieu_kien ? gia_tri_neu_dung : gia_tri_neu_sai

Toàn bộ cấu trúc là một biểu thức có giá trị, không phải câu lệnh. Đây là khác biệt cốt lõi so với if else.

int max = (a > b) ? a : b;

printf("%s\n", n % 2 == 0 ? "Chẵn" : "Lẻ");

const char *trang_thai = ok ? "thành công" : "thất bại";

int abs_x = x < 0 ? -x : x;

#Khi nào nên dùng

Gán một giá trị đơn giản

int max   = a > b ? a : b;
int dau   = x < 0 ? -1 : 1;
int hop_le = (tuoi >= 0 && tuoi <= 150);

Chèn vào giữa một biểu thức khác

printf("Bạn có %d quả táo\n", n);
printf("Trạng thái: %s\n", dang_chay ? "đang chạy" : "đã dừng");

/* Chọn đơn vị hiển thị */
printf("%.1f %s\n",
       kich_thuoc >= 1024 ? kich_thuoc / 1024.0 : kich_thuoc,
       kich_thuoc >= 1024 ? "MB" : "KB");

Kẹp giá trị vào một khoảng

int kep(int x, int min, int max)
{
    return x < min ? min : (x > max ? max : x);
}

/* Hoặc dùng macro, nhớ bọc ngoặc đầy đủ */
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

#Khi nào không nên

Lồng nhiều tầng

Không nên
x = a > b ? (c > d ? 1 : 2) : (e > f ? 3 : 4);

/* Phải đọc đi đọc lại mới hiểu. */
Nên
if (a > b) {
    x = (c > d) ? 1 : 2;
} else {
    x = (e > f) ? 3 : 4;
}

Một tầng thì rõ, hai tầng thì bắt đầu khó, ba tầng thì gần như không ai đọc được. Ngoại lệ duy nhất được chấp nhận rộng rãi là chuỗi phân loại có cấu trúc lặp lại đều đặn, và ngay cả khi đó thì nhiều dự án vẫn cấm.

Dùng thay cho câu lệnh

Không nên
a > b ? printf("a lớn hơn") : printf("b lớn hơn");

/* Hợp lệ nhưng sai tinh thần.
   Toán tử ba ngôi sinh ra để TÍNH GIÁ TRỊ,
   không phải để chọn hành động. */
Nên
if (a > b) {
    printf("a lớn hơn");
} else {
    printf("b lớn hơn");
}

Khi hai nhánh có tác dụng phụ

/* Khó đọc và dễ gây hiểu nhầm */
ok ? ghi_log_thanh_cong() : ghi_log_that_bai();

/* Rõ ràng hơn */
if (ok) {
    ghi_log_thanh_cong();
} else {
    ghi_log_that_bai();
}

#Bẫy hai nhánh khác kiểu

Kiểu của cả biểu thức được quyết định bằng cách áp dụng quy tắc chuyển đổi số học thông thường lên hai nhánh, giống như với toán tử hai ngôi. Điều này xảy ra ngay cả khi nhánh đó không được chọn.

printf("%d\n", 1 ? 1 : 2.0);

/* Nhánh thứ hai là double, nên CẢ BIỂU THỨC có kiểu double.
   Giá trị là 1.0 chứ không phải 1.
   In bằng %d là hành vi không xác định. */

#Tổng kết Phần 1

Bạn vừa hoàn thành Phần 1. Đây là những gì đã tích lũy được qua bốn chương.

  • Chương 2: cấu trúc một file nguồn, biến là vùng nhớ có tên, kích thước các kiểu không cố định theo chuẩn, sizeof tính lúc biên dịch, và ba cách tạo hằng số.
  • Chương 3: cơ chế đệm quyết định khi nào chữ hiện ra, năm thành phần của một đặc tả định dạng, và cách nhập liệu an toàn bằng fgets kèm strtol.
  • Chương 4: chia số nguyên cắt phần dư, năm lỗi so sánh kinh điển, đánh giá ngắn mạch, và bảng độ ưu tiên với năm cái bẫy.
  • Chương 5: luôn dùng ngoặc nhọn, thứ tự điều kiện quyết định kết quả, guard clause làm phẳng mã, và fallthrough của switch.

Phần 2 sẽ đi vào vòng lặp và tư duy thuật toán. Đó là nơi bạn bắt đầu giải được những bài toán thật sự, và cũng là nơi bài học về độ phức tạp bắt đầu có ý nghĩa thực tế.

Tự làm thử

  1. Viết hàm trả về giá trị lớn nhất trong ba số, chỉ dùng toán tử ba ngôi.
  2. Viết chương trình in ra một số là chẵn hay lẻ, âm hay dương, dùng toán tử ba ngôi bên trong printf.
  3. Viết hàm kep giới hạn giá trị vào một khoảng, rồi viết lại bằng if else và so sánh.
  4. Chạy printf("%d\n", 1 ? 1 : 2.0) và giải thích kết quả. Sau đó sửa cho đúng.
  5. Viết biểu thức int x = a > b ? a : b + 1; rồi kiểm chứng độ ưu tiên bằng các giá trị cụ thể.
  6. Viết một chương trình tổng hợp dùng lại kiến thức cả Phần 1: menu máy tính có nhập liệu an toàn, xử lý lỗi đầy đủ, dùng switch cho lựa chọn, và in kết quả có định dạng đẹp.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Toán tử ba ngôi là toán tử duy nhất của C có ba toán hạng, và nó là biểu thức chứ không phải câu lệnh.
  • Chỉ một trong hai nhánh được đánh giá, nên nó an toàn khi một nhánh có thể gây lỗi.
  • Nó cho phép khởi tạo biến ngay lúc khai báo, kể cả biến const.
  • Dùng khi chọn một giá trị, dùng if else khi chọn một hành động.
  • Kiểu của cả biểu thức lấy từ cả hai nhánh, nên trộn kiểu khác nhau sinh ra bẫy ép kiểu ngầm.
  • Độ ưu tiên rất thấp nên hầu như luôn cần ngoặc khi nằm trong biểu thức lớn hơn.