C ตัวดำเนินการ
เลขคณิต
#include <stdio.h>
int main(void)
{
printf("%d\n", 7 + 3); // 10
printf("%d\n", 7 - 3); // 4
printf("%d\n", 7 * 3); // 21
printf("%d\n", 7 / 3); // 2 ← integer division! เศษถูกตัดทิ้ง
printf("%d\n", 7 % 3); // 1 ← เศษจากการหาร (modulo)
return 0;
}integer division — กับดักอันดับหนึ่ง
7 / 3 ได้ 2 ไม่ใช่ 2.333 เพราะ ตัวถูกดำเนินการทั้งคู่เป็น int ผลลัพธ์จึงเป็น int เศษหายไป
ถ้าต้องการทศนิยม ต้องมีตัวใดตัวหนึ่งเป็น floating point:
printf("%d\n", 7 / 3); // 2 ← หาร int ได้ int, เศษหายตั้งแต่ตรงนี้
printf("%f\n", 7.0 / 3); // 2.333333 ← 7.0 เป็น double, ผลเป็น double
printf("%f\n", (double)7 / 3); // 2.333333 ← cast ตัวตั้งก่อนหาร
printf("%f\n", 7 / (double)3); // 2.333333 ← cast ตัวหารก็ได้
int a = 7, b = 3;
printf("%f\n", (double)a / b); // 2.333333อย่าเขียน
printf("%f\n", 7 / 3);—7 / 3เป็นint(ค่า2) แต่%fอ่าน argument เป็นdoubleชนิดไม่ตรงกับ format เป็น undefined behavior ไม่ใช่ “ได้2.000000” อย่างที่หลายคนคิด และ(double)(7 / 3)ก็ไม่ช่วย เพราะหาร int จบก่อนแล้วค่อย cast (ได้2.0) — ต้อง cast ตัวถูกดำเนินการ ตัวใดตัวหนึ่งก่อนหาร เช่น(double)7 / 3
% ใช้กับ ชนิดจำนวนเต็ม เท่านั้น (int, long, long long, unsigned ฯลฯ — ไม่ใช่ float/double) เครื่องหมายของผลตามตัวตั้ง: -7 % 3 = -1
กำหนดค่าและเพิ่ม/ลด
int n = 10;
n += 5; // 15
n -= 3; // 12
n *= 2; // 24
n /= 4; // 6
n %= 4; // 2
int i = 5;
printf("%d\n", i++); // 5 (ใช้ค่าเดิม แล้วเพิ่มเป็น 6)
printf("%d\n", ++i); // 7 (เพิ่มเป็น 7 ก่อน แล้วใช้)อย่าเขียน
i++หลายครั้งในนิพจน์เดียว เช่นa[i++] = i++;— เป็น undefined behavior (ลำดับการประเมินไม่แน่นอน)
เปรียบเทียบ — คืน int (0 หรือ 1)
C11/C17 มีชนิด _Bool (และ bool จาก <stdbool.h>) — แต่ตัวดำเนินการ เปรียบเทียบ คืนผลเป็น int: 1 (จริง) หรือ 0 (เท็จ) ไม่ใช่ _Bool
printf("%d\n", 5 > 3); // 1
printf("%d\n", 5 == 3); // 0
printf("%d\n", 5 != 3); // 1
printf("%d\n", 5 >= 5); // 1ในเงื่อนไข: 0 = เท็จ, ทุกค่าที่ไม่ใช่ 0 = จริง
if (3) printf("จริง\n"); // พิมพ์ — 3 ไม่ใช่ 0
if (-1) printf("จริงด้วย\n"); // พิมพ์ — -1 ไม่ใช่ 0
if (0) printf("ไม่พิมพ์\n");ตรรกะ
int age = 25;
int hasLicense = 1;
if (age >= 18 && hasLicense) { /* ทั้งคู่จริง */ }
if (age < 13 || age > 60) { /* อย่างน้อยหนึ่งจริง */ }
if (!hasLicense) { /* ไม่มีใบขับขี่ */ }&& และ || เป็น short-circuit — ถ้า && เจอตัวแรกเท็จ ไม่ประเมินตัวหลัง (สำคัญเวลาเช็ค p != NULL && p->x)
bitwise — ทำงานระดับบิต
unsigned int a = 0b1100; // 12 (0b คือ binary literal, ส่วนขยาย GCC)
unsigned int b = 0b1010; // 10
printf("%u\n", a & b); // 8 (1000) AND
printf("%u\n", a | b); // 14 (1110) OR
printf("%u\n", a ^ b); // 6 (0110) XOR
printf("%u\n", ~a & 0xF); // 3 (0011) NOT
printf("%u\n", a << 2); // 48 เลื่อนซ้าย 2 บิต = คูณ 4
printf("%u\n", a >> 1); // 6 เลื่อนขวา 1 บิต = หาร 2ใช้บ่อยกับ flag:
#define FLAG_READ (1 << 0) // 0b001
#define FLAG_WRITE (1 << 1) // 0b010
#define FLAG_EXEC (1 << 2) // 0b100
int perms = FLAG_READ | FLAG_WRITE; // เปิด read + write
if (perms & FLAG_WRITE) { /* มีสิทธิ์ write */ }
perms &= ~FLAG_WRITE; // ปิด writeoperator precedence — ลำดับความสำคัญ
C มีตารางลำดับที่ยาวและจำยาก — เมื่อสงสัย ใส่วงเล็บ:
int x = 2 + 3 * 4; // 14 (คูณก่อน)
int y = (2 + 3) * 4; // 20
// จุดที่พลาดบ่อย:
if (a & 1 == 0) // ผิด! == มาก่อน & กลายเป็น a & (1 == 0) = a & 0
if ((a & 1) == 0) // ถูก
int z = a << 1 + 2; // a << 3 (+ มาก่อน <<) — มักไม่ใช่ที่ตั้งใจ
int w = (a << 1) + 2; // ที่ตั้งใจลำดับในกลุ่มนี้ จากสูงไปต่ำ: + - → << >> → < <= > >= → == != → & → ^ → |
<<>>สูงกว่า relational/equality —1 << 2 == 4คือ(1 << 2) == 4- แต่
<<>>ต่ำกว่า+-—a << 1 + 2คือa << 3 &^|ต่ำกว่า==!=—a & 1 == 0คือa & (1 == 0)(อย่างในตัวอย่างข้างบน)
ผสม bitwise กับตัวดำเนินการอื่นเมื่อไร ใส่วงเล็บให้ชัดทุกครั้ง
signed integer overflow = undefined behavior
int max = 2147483647; // INT_MAX
int oops = max + 1; // UB! ไม่ใช่ "วนไปเป็น -2147483648" ตามที่หลายคนคิดในทางปฏิบัติมักได้ค่าลบ แต่มาตรฐานบอกว่าเป็น undefined behavior — คอมไพเลอร์ปรับแต่งโดยสมมติว่าไม่มีทางเกิด อาจทำให้โปรแกรมทำอะไรก็ได้ unsigned overflow ต่างกัน — มันวนแบบกำหนดไว้ (modulo 2ⁿ) ไม่ใช่ UB
จับด้วย -fsanitize=undefined (บทที่ 29) ป้องกันด้วยการเช็คก่อนบวก — ต้องดูทั้งฝั่งบวกล้น (overflow) และฝั่งลบล้น (underflow):
#include <limits.h>
// a + b จะเกินช่วง int หรือไม่
if ((b > 0 && a > INT_MAX - b) || // ล้นด้านบวก
(b < 0 && a < INT_MIN - b)) { // ล้นด้านลบ
/* overflow — จัดการ */
} else {
int sum = a + b;
}เรื่องที่คนมักพลาด
7 / 2คาดว่าได้ 3.5 — ได้3(integer division) ต้อง cast อย่างน้อยตัวหนึ่งเป็นdoubleprintf("%f", นิพจน์ที่เป็น int)— ชนิดไม่ตรงกับ%= UB ไม่ใช่แค่ค่าเพี้ยน cast argument ให้ตรง format เสมอif (a & 1 == 0)—==ผูกก่อน&ใส่วงเล็บ(a & 1) == 0i = i++ + ++i;— UB, ผลลัพธ์ไม่แน่นอน อย่าแก้ตัวแปรเดียวหลายครั้งในนิพจน์เดียว- signed overflow “จะวน” — เป็น UB ไม่ใช่พฤติกรรมที่รับประกัน ต่างจาก unsigned
>>กับ signed ค่าลบ — implementation-defined (มัก arithmetic shift) ใช้unsignedเมื่อทำ bit manipulation=แทน==ในเงื่อนไข —if (x = 5)กำหนดค่าแล้วเป็นจริงเสมอ คอมไพเลอร์เตือนด้วย-Wall- หาร/mod ด้วย 0 — UB (มัก crash ด้วย SIGFPE)
สรุป
/ระหว่าง int สองตัว = ตัดเศษ ต้อง cast เป็นdoubleถ้าต้องการทศนิยม,%ใช้กับชนิดจำนวนเต็มเท่านั้น- เปรียบเทียบคืน
int(0/1) ไม่ใช่_Bool; ในเงื่อนไข 0 = เท็จ, ที่เหลือ = จริง && ||short-circuit; bitwise& | ^ ~ << >>สำหรับงานระดับบิต/flag- ลำดับ:
<<>>สูงกว่าการเปรียบเทียบ แต่&^|ต่ำกว่า==!=— ใส่วงเล็บเมื่อผสม bitwise - signed overflow = UB (จับด้วย
-fsanitize=undefined), unsigned วนแบบกำหนดไว้
บทถัดไปว่าด้วยเงื่อนไข — if/else/switch และเรื่อง fallthrough ของ switch