[0]zero-index clytie0x/zero-index

1.1 ตัวแปร

โปรแกรม Hello World พิมพ์ข้อความเดิมทุกครั้งที่รัน แต่โจทย์จริงแทบทุกข้อให้ตัวเลขมา แล้วให้เราคำนวณต่อ เช่น รับคะแนนสอบมาแล้วหาผลรวม รับความกว้างกับความยาวมาแล้วหาพื้นที่ โปรแกรมจึงต้องมีที่จำตัวเลขไว้ใช้ ที่จำนั้นเรียกว่า ตัวแปร (variable)

ตัวแปรคือกล่องที่มีชื่อ

ให้นึกถึงกล่องในหน่วยความจำของคอมพิวเตอร์ แต่ละกล่องมีสามอย่าง

  • ชื่อ ใช้เรียกกล่องนั้น เช่น score
  • ค่า ของที่เก็บอยู่ในกล่อง เก็บได้ทีละหนึ่งค่า ใส่ค่าใหม่เมื่อไร ค่าเก่าหายทันที
  • ชนิด (type) บอกว่ากล่องนี้เก็บของแบบไหน เช่น int เก็บจำนวนเต็ม
score 85 int age 16 int

ตัวแปรตัวแรก

#include <stdio.h>

int main()
{
    int score; // (1)!
    score = 85; // (2)!
    printf("score = %d\n", score); // (3)!
    return 0;
}
  1. ประกาศ (declare) ตัวแปร บอก compiler ว่าขอกล่องชนิด int ชื่อ score หนึ่งกล่อง int ย่อมาจาก integer แปลว่าจำนวนเต็ม
  2. กำหนดค่า (assign) เอา 85 ใส่ลงกล่อง score
  3. %d คือช่องว่างที่รอตัวเลข printf จะเอาค่าของ score มาแทนที่ตรงนั้น
score = 85

ข้อความ score = ในเครื่องหมายคำพูดพิมพ์ออกมาตรงตัว ส่วน %d ถูกแทนด้วย 85 ตัว d มาจาก decimal คือเลขฐานสิบ

ไม่แสดงบรรทัด Process returned แล้ว

ตั้งแต่บทนี้ไป กล่องผลลัพธ์จะแสดงเฉพาะสิ่งที่โปรแกรมพิมพ์ ส่วนสองบรรทัดท้ายที่ Code::Blocks เติมให้ น้องจะยังเห็นในเครื่องตัวเองเหมือนเดิม

ประกาศพร้อมกำหนดค่า

ประกาศแล้วกำหนดค่าในบรรทัดเดียวได้ เขียนสั้นกว่า และกันลืมใส่ค่า

#include <stdio.h>

int main()
{
    int math = 78;
    int science = 91;
    printf("math = %d, science = %d\n", math, science);
    printf("total = %d\n", math + science);
    return 0;
}
math = 78, science = 91
total = 169
  • printf หนึ่งครั้งมี %d กี่ตัวก็ได้ ค่าที่อยู่หลังข้อความจะถูกเติมลงไป ตามลำดับ ตัวแรกเติม %d ตัวแรก ตัวที่สองเติมตัวที่สอง
  • ค่าที่เติมไม่จำเป็นต้องเป็นตัวแปรเดี่ยวๆ จะเป็นผลคำนวณอย่าง math + science ก็ได้

ตัวแปรชนิดเดียวกันประกาศในบรรทัดเดียว คั่นด้วยจุลภาคได้ แบบนี้

int math = 78, science = 91;

เปลี่ยนค่าตัวแปร

ชื่อว่าตัวแปรเพราะค่าเปลี่ยนได้ โปรแกรมทำงานจากบนลงล่างทีละบรรทัด ตอนที่ printf ทำงาน ตัวแปรมีค่าอะไร ก็พิมพ์ค่านั้น

#include <stdio.h>

int main()
{
    int score = 85;
    printf("first = %d\n", score);
    score = 92;
    printf("second = %d\n", score);
    score = score + 5;
    printf("with bonus = %d\n", score);
    return 0;
}
first = 85
second = 92
with bonus = 97

บรรทัดที่ 9 score = score + 5; ดูแปลกถ้าคิดแบบคณิตศาสตร์ เพราะไม่มีเลขไหนเท่ากับตัวมันเองบวก 5 แต่ในภาษา C เครื่องหมาย = ไม่ได้แปลว่าเท่ากับ แปลว่า "เอาค่าทางขวาไปใส่ในกล่องทางซ้าย" คอมพิวเตอร์ทำสองจังหวะ

  1. คำนวณด้านขวาก่อน ตอนนี้ score เป็น 92 ได้ 92 + 5 = 97
  2. เอา 97 ใส่ลงกล่อง score ทับค่าเดิม

รูปแบบนี้จะเจออีกบ่อยมาก เช่น บวกคะแนนสะสม นับจำนวนครั้ง ตอนเรียนเรื่องวนซ้ำในบทที่ 4

ตั้งชื่อตัวแปร

กฎที่ compiler บังคับ

  • ใช้ได้แค่ตัวอักษรภาษาอังกฤษ ตัวเลข และขีดล่าง _ ห้ามมีช่องว่าง
  • ห้ามขึ้นต้นด้วยตัวเลข
  • ห้ามซ้ำกับคำสงวนของภาษา C เช่น int return double
  • ตัวพิมพ์เล็กกับพิมพ์ใหญ่ถือว่าต่างกัน score กับ Score เป็นคนละตัว
ชื่อใช้ได้ไหม
scoreได้
total_scoreได้ คั่นคำด้วยขีดล่าง อ่านง่าย
score2ได้ ตัวเลขอยู่หลังได้
2nd_scoreไม่ได้ ขึ้นต้นด้วยตัวเลข
total scoreไม่ได้ มีช่องว่าง
returnไม่ได้ เป็นคำสงวน

นอกจากกฎของ compiler ให้ตั้งชื่อที่บอกว่าเก็บอะไร width กับ height อ่านแล้วรู้เรื่องทันที แต่ a กับ b ต้องย้อนไปดูว่าคืออะไร เอกสารนี้ใช้ภาษาอังกฤษตัวเล็ก คั่นคำด้วยขีดล่าง

รับค่าจากผู้ใช้ด้วย scanf

ถ้าค่าตัวแปรเขียนตายตัวในโค้ด โปรแกรมก็ตอบได้คำตอบเดียว โจทย์ในการแข่งขันจะส่ง ข้อมูลเข้า (input) มาให้ แล้วให้โปรแกรมคำนวณ คำสั่งที่รับตัวเลขเข้ามาใส่ตัวแปรคือ scanf

#include <stdio.h>

int main()
{
    int age;
    scanf("%d", &age);
    printf("next year you will be %d\n", age + 1);
    return 0;
}
15
next year you will be 16

กด F9 แล้วหน้าต่างดำจะว่างอยู่ เพราะโปรแกรมรอให้พิมพ์ พิมพ์ 15 แล้วกด Enter โปรแกรมถึงจะทำงานต่อ ในหน้าต่างจริงจะเห็นเลขที่เราพิมพ์อยู่บรรทัดบน ส่วนในเอกสารนี้แยกข้อมูลเข้ากับผลลัพธ์ไว้คนละกล่อง

  • "%d" บอก scanf ว่าจะอ่านจำนวนเต็มหนึ่งตัว
  • &age คือ ที่อยู่ ของกล่อง age ในหน่วยความจำ scanf ต้องรู้ว่ากล่องอยู่ที่ไหนถึงจะเอาค่าไปใส่ได้ ตอนนี้จำไว้ว่า ตัวแปรใน scanf ต้องมี & นำหน้า ส่วน printf ไม่ต้องมี เรื่องที่อยู่จะอธิบายเต็มๆ ในบทที่ 10

รับหลายค่าพร้อมกันก็ได้ ใส่ %d ให้ครบตามจำนวนตัวแปร

#include <stdio.h>

int main()
{
    int width, height;
    scanf("%d %d", &width, &height);
    printf("area = %d\n", width * height);
    return 0;
}
12 7
area = 84

เครื่องหมาย * คือคูณ หน้าถัดไปจะดูเครื่องหมายคำนวณทั้งหมด ข้อมูลเข้าสองตัวจะคั่นด้วยช่องว่างหรือขึ้นบรรทัดใหม่ก็ได้ scanf อ่านได้ทั้งสองแบบ รายละเอียดอื่นของ scanf อยู่ในบทที่ 2

ลองทำให้พัง

ทำทีละแบบ อ่านข้อความที่ได้ แล้วแก้กลับให้ถูกก่อนลองแบบถัดไป

แบบที่ 1 พิมพ์ชื่อตัวแปรผิด

ในโปรแกรม score แก้บรรทัด printf ให้ใช้ scroe แทน score

score.c:7:28: error: 'scroe' undeclared (first use in this function); did you mean 'score'?
    7 |     printf("score = %d\n", scroe);
      |                            ^~~~~
      |                            score
score.c:7:28: note: each undeclared identifier is reported only once for each function it appears in
score.c:5:9: warning: variable 'score' set but not used [-Wunused-but-set-variable]
    5 |     int score;
      |         ^~~~~

undeclared แปลว่ายังไม่ได้ประกาศ compiler ไม่รู้จักชื่อ scroe เพราะกล่องที่เราขอไว้ชื่อ score และมันเดาให้ด้วยว่าน่าจะหมายถึง score ถ้าลืมประกาศตัวแปรก่อนใช้ ก็ได้ error แบบเดียวกันนี้

บรรทัด note บอกแค่ว่าชื่อที่ไม่รู้จักชื่อหนึ่งจะถูกฟ้องครั้งเดียวต่อฟังก์ชัน ไม่ต้องแก้อะไร ส่วน warning set but not used ขึ้นตามมาเพราะตอนนี้ score ถูกใส่ค่าแต่ไม่มีใครเอาไปใช้ พอแก้ชื่อให้ถูก ทั้งสองบรรทัดก็หายไปพร้อม error

แบบที่ 2 ตั้งชื่อขึ้นต้นด้วยตัวเลข

ในโปรแกรม score เปลี่ยนบรรทัด int score; เป็น

int 2nd_score = 90;
score.c:5:9: error: invalid suffix "nd_score" on integer constant
    5 |     int 2nd_score = 90;
      |         ^~~~~~~~~
score.c:5:9: error: expected identifier or '(' before numeric constant
score.c:6:5: error: 'score' undeclared (first use in this function)
    6 |     score = 85;
      |     ^~~~~
score.c:6:5: note: each undeclared identifier is reported only once for each function it appears in

compiler เห็นเลข 2 แล้วคิดว่าเป็นตัวเลข ตามด้วยตัวอักษรแปลกๆ nd_score ข้อความไม่ได้บอกตรงๆ ว่าตั้งชื่อผิดกฎ แต่ชี้ตำแหน่งให้ ถ้าเจอ error ที่อ่านไม่เข้าใจ ให้ดูตำแหน่งที่ ^ ชี้ก่อนเสมอ

error บรรทัดที่สามเกิดตามมา เพราะประกาศพังไปแล้ว score จึงไม่มีอยู่จริง error ที่ต่อกันเป็นพรวนแบบนี้เจอบ่อย ให้แก้ตัวแรกก่อน แล้วคอมไพล์ใหม่ ตัวที่ตามมามักหายไปเอง

แบบที่ 3 ใช้ตัวแปรที่ยังไม่ได้ใส่ค่า

ลบบรรทัด score = 85; ออก เหลือแค่ประกาศแล้วพิมพ์เลย

score.c:6:5: warning: 'score' is used uninitialized [-Wuninitialized]
    6 |     printf("score = %d\n", score);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
score.c:5:9: note: 'score' was declared here
    5 |     int score;
      |         ^~~~~

คราวนี้เป็น warning (คำเตือน) ไม่ใช่ error โปรแกรมยังรันได้ แต่ค่าที่พิมพ์ออกมาคือของที่ค้างอยู่ในหน่วยความจำตรงนั้น เรียกว่า ค่าขยะ รันคนละเครื่องหรือคนละครั้งอาจได้ไม่เหมือนกัน คำเตือนนี้ขึ้นเพราะเราเปิด -Wall ไว้ตอนติดตั้ง ถ้าเห็น warning ให้แก้เหมือนเป็น error

แบบที่ 4 ลืม & ใน scanf

ในโปรแกรม age เขียน scanf("%d", age); โดยไม่มี &

age.c:6:13: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=]
    6 |     scanf("%d", age);
      |            ~^   ~~~
      |             |   |
      |             |   int
      |             int *
age.c:6:5: warning: 'age' is used uninitialized [-Wuninitialized]

ยังเป็นแค่ warning จึงรันได้ แต่ scanf ไม่รู้ว่ากล่อง age อยู่ไหน เลขที่พิมพ์เข้าไปจึงไม่ถึงตัวแปร บนเครื่องที่เขียนเอกสารนี้ พิมพ์ 20 แล้วได้ next year you will be 1 ซึ่งไม่เกี่ยวกับ 20 เลย ส่วน int * ในข้อความคือชนิดของที่อยู่ จะได้เรียนในบทที่ 10

สรุป

  • ตัวแปรคือกล่องที่มีชื่อ ชนิด และค่า ต้องประกาศก่อนใช้ เช่น int score;
  • = คือเอาค่าทางขวาใส่กล่องทางซ้าย ไม่ใช่เท่ากับ
  • printf("%d", x) พิมพ์ค่าจำนวนเต็ม scanf("%d", &x) รับค่าจำนวนเต็ม อย่าลืม &
  • ชื่อตัวแปรใช้ตัวอักษร ตัวเลข ขีดล่าง ห้ามขึ้นต้นด้วยตัวเลข และควรบอกว่าเก็บอะไร
  • warning ไม่หยุดการคอมไพล์ แต่มักแปลว่าโปรแกรมจะได้ค่าผิด