C ตัวแปรและชนิดข้อมูลพื้นฐาน
ประกาศตัวแปร
C ต้องประกาศ ชนิด ของตัวแปรเสมอ และประกาศก่อนใช้:
#include <stdio.h>
int main(void)
{
int age = 30;
double height = 172.5;
char initial = 'S';
int count; // ประกาศไว้ก่อน ยังไม่กำหนดค่า
count = 5; // กำหนดค่าทีหลัง
printf("%d %f %c %d\n", age, height, initial, count);
return 0;
}ชนิดข้อมูลพื้นฐาน
| ชนิด | เก็บอะไร | ขนาดทั่วไป (64-bit Linux) |
|---|---|---|
char | ตัวอักษร / จำนวนเต็มเล็ก | 1 ไบต์ |
int | จำนวนเต็ม | 4 ไบต์ (−2.1 พันล้าน ถึง 2.1 พันล้าน) |
long | จำนวนเต็มใหญ่ | 8 ไบต์ (Linux 64-bit) / 4 ไบต์ (Windows) |
long long | จำนวนเต็มใหญ่มาก | 8 ไบต์ |
float | ทศนิยม (ความละเอียดเดี่ยว) | 4 ไบต์ (~7 หลัก) |
double | ทศนิยม (ความละเอียดคู่) | 8 ไบต์ (~15 หลัก) |
_Bool (bool จาก <stdbool.h>) | จริง/เท็จ | 1 ไบต์ |
ขนาดไม่ตายตัว — มาตรฐาน C รับประกันเป็น ช่วงค่าขั้นต่ำ ไม่ใช่จำนวนไบต์: char อย่างน้อย 8 บิต (sizeof(char) = 1 เสมอ แต่ “ไบต์” ไม่จำเป็นต้อง 8 บิต), int อย่างน้อย 16 บิต (±32767), long อย่างน้อย 32 บิต, long long อย่างน้อย 64 บิต เครื่อง/คอมไพเลอร์ต่างกันได้ ถ้าต้องการขนาดแน่นอนใช้ <stdint.h> (บทที่ 37)
sizeof
sizeof บอกขนาดเป็นไบต์ ปกติประเมินตอนคอมไพล์ (ยกเว้น operand เป็น VLA — อาร์เรย์ความยาวแปรผัน — จะประเมินตอนรัน):
#include <stdio.h>
int main(void)
{
printf("char : %zu ไบต์\n", sizeof(char));
printf("int : %zu ไบต์\n", sizeof(int));
printf("long : %zu ไบต์\n", sizeof(long));
printf("float : %zu ไบต์\n", sizeof(float));
printf("double : %zu ไบต์\n", sizeof(double));
printf("int[10] : %zu ไบต์\n", sizeof(int[10]));
return 0;
}char : 1 ไบต์
int : 4 ไบต์
long : 8 ไบต์
float : 4 ไบต์
double : 8 ไบต์
int[10] : 40 ไบต์sizeof คืนชนิด size_t — ใช้ %zu ใน printf (ไม่ใช่ %d)
signed กับ unsigned
จำนวนเต็มเป็นได้ทั้งมีเครื่องหมายและไม่มี:
int a = -5; // signed (ค่าเริ่มต้น)
unsigned int b = 10; // 0 ถึง ~4.2 พันล้าน — ไม่มีค่าลบ
unsigned int c = 0;
c = c - 1; // ไม่ error! วนไปเป็นค่าสูงสุด (4294967295)
printf("%u\n", c); // 4294967295unsigned มีประโยชน์เมื่อค่าเป็นลบไม่ได้จริง ๆ (ขนาด, จำนวน, index) แต่ระวังการลบที่ทำให้ต่ำกว่า 0 — มัน “วน” ไม่ error
char เป็นจำนวนเต็มด้วย
char เก็บเป็นรหัสตัวเลข — ทำเลขคณิตได้ (เครื่องทั่วไปใช้ ASCII ที่ 'A' = 65 แต่มาตรฐาน C ไม่ได้บังคับว่าต้องเป็น ASCII):
char letter = 'A';
printf("%d\n", letter); // 65 บนเครื่องที่ใช้ ASCII
printf("%c\n", letter + 1); // B
printf("%c\n", 'a' + 3); // d
char digit = '7';
int value = digit - '0'; // 7 — เทคนิคแปลง char ตัวเลขเป็น int
printf("%d\n", value);ตัวแปรที่ไม่ init มีค่าขยะ
ต่างจาก Python/Go — ตัวแปร local ที่ไม่กำหนดค่าเริ่มต้น ไม่ใช่ 0 แต่เป็นค่าที่ค้างอยู่ในหน่วยความจำตรงนั้น (ขยะ):
int x;
printf("%d\n", x); // ค่าอะไรก็ได้ — และการอ่านมันคือ undefined behaviorกฎ: กำหนดค่าเริ่มต้นทุกตัวแปรตอนประกาศ int x = 0;
รับค่าจากผู้ใช้ด้วย scanf
#include <stdio.h>
int main(void)
{
int age = 0;
double weight = 0.0;
printf("อายุ: ");
if (scanf("%d", &age) != 1) { // สังเกต & ข้างหน้าตัวแปร; เช็คค่าคืนเสมอ
fprintf(stderr, "อ่านอายุไม่ได้\n");
return 1;
}
printf("น้ำหนัก: ");
if (scanf("%lf", &weight) != 1) { // double ใช้ %lf ใน scanf (ต่างจาก printf ที่ใช้ %f)
fprintf(stderr, "อ่านน้ำหนักไม่ได้\n");
return 1;
}
printf("อายุ %d น้ำหนัก %.1f\n", age, weight);
return 0;
}อายุ: 25
น้ำหนัก: 60.5
อายุ 25 น้ำหนัก 60.5- ต้องมี
&หน้าตัวแปร —scanfต้องรู้ *ที่อยู่* ของตัวแปรเพื่อเขียนค่าลงไป (บทที่ 11 จะเข้าใจว่าทำไม) ลืม&= segfault doubleใช้%lfในscanfแต่%fในprintf— น่าสับสน แต่จำไว้scanfเปราะบางมาก — ถ้าผู้ใช้พิมพ์ตัวอักษรตอนคาด%dมันจะค้าง input ไว้ วน loop ไม่รู้จบ
วิธีที่ปลอดภัยกว่า: fgets + sscanf
char line[100];
int age;
char extra;
printf("อายุ: ");
// " %c" หลัง %d จับอักขระที่ไม่ใช่ช่องว่างตัวถัดไป — ถ้ามี แสดงว่า input เกิน ("25abc")
if (fgets(line, sizeof line, stdin) && sscanf(line, "%d %c", &age, &extra) == 1) {
printf("อายุ %d\n", age);
} else {
printf("อินพุตไม่ถูกต้อง\n");
}อ่านทั้งบรรทัดด้วย fgets ก่อน แล้วค่อยแยกค่าด้วย sscanf — เช็คว่าคืน == 1 พอดี (ได้ age แต่ไม่มีอักขระเกิน); ถ้าได้ 2 แปลว่ามีขยะต่อท้ายเช่น 25abc การตรวจ input ตัวเลขแบบเข้มกว่านี้ใช้ strtol + เช็ค end pointer (บทที่ 35)
ค่าคงที่
const double PI = 3.14159265;
const int MAX_USERS = 100;
// PI = 3.0; // error: assignment of read-only variable
#define BUFFER_SIZE 256 // มาโคร (บทที่ 25) — ไม่มีชนิด, แทนที่ token ก่อนคอมไพล์const ดีกว่า #define สำหรับค่าคงที่ทั่วไป เพราะมีชนิดและ debugger เห็น
เรื่องที่คนมักพลาด
- อ่านตัวแปรที่ยังไม่ init — ได้ค่าขยะ และเป็น UB กำหนดค่าเริ่มต้นเสมอ
scanf("%d", age)ลืม&— เขียนค่าลงที่อยู่มั่ว = segfault (บางทีก็รอด ทำให้ debug ยาก)scanf("%f", &x)เมื่อxเป็นdouble— ต้องเป็น%lfใน scanf- ไม่เช็คค่าคืนของ
scanf— อ่านไม่สำเร็จแล้วตัวแปรยังเป็นค่าขยะ ใช้ต่อ = ผลเพี้ยน/พฤติกรรมไม่แน่นอน เช็ค== จำนวน fieldเสมอ และกำหนดค่าเริ่มต้นให้ตัวแปร unsignedลบจนต่ำกว่า 0 — วนไปเป็นค่ามหาศาล ไม่ error ระวังใน loopfor (unsigned i = n; i >= 0; i--)= infinite- หาร int สองตัวแล้วคาดว่าได้ทศนิยม —
5 / 2เป็น2ไม่ใช่2.5(บทหน้า) %zuกับsizeof— ใช้%dแล้ว-Wallเตือน และผลอาจเพี้ยนบนบางเครื่องcharเป็น signed หรือ unsigned ขึ้นกับเครื่อง — ถ้าเก็บค่า > 127 ใช้unsigned charให้ชัด
สรุป
- ประกาศชนิดเสมอ, กำหนดค่าเริ่มต้นเสมอ (
int x = 0;) — ไม่งั้นได้ค่าขยะ char1B,int4B,double8B (โดยทั่วไป — ไม่รับประกัน) เช็คด้วยsizeof+%zuunsignedไม่มีค่าลบ — การลบต่ำกว่า 0 จะวน ไม่ errorcharทำเลขคณิตได้ ('7' - '0'= 7)scanf("%d", &x)— ต้องมี&,doubleใช้%lf; ปลอดภัยกว่าคือfgets+sscanfconstสำหรับค่าคงที่
บทถัดไปว่าด้วยตัวดำเนินการ — เลขคณิต, integer division, bitwise และเรื่อง overflow