CS50x : Week 1 - C

Published on

เขียนวันที่ : Apr 3, 2022

Discord

เนื้อหา Week 1 - C เริ่มเรียนการเขียนโปรแกรมจริงๆ ด้วยการเขียนภาษาซี (C Programming) สำหรับบทเรียนนี้ มือใหม่ หรือคนที่ไม่เคยหัดเขียนโปรแกรม อาจจะงงๆ กับการใช้งานโปรแกรมได้ และผมมีเพิ่มเนื้อหาบางส่วน ที่คิดว่า หลายๆ คนน่าจะติดปัญหา

จริงๆแล้วถ้าติดปัญหาส่วนไหน พยายามลองแก้ปัญหา ลองค้นหาเพิ่มเติมก่อนครับ ค่อยกลับมาดูใหม่ เช่น VS Code หากยังไม่มี ก็ลองไปดาวน์โหลด และติดตั้งดูก่อน ครับ หรือตอนอาจารย์สอนพวก Command Line เบื้องต้น ก็ลองฝึกทำตาม ฝึกหลายๆแบบดูครับ

และเนื้อหาเอาจริงๆ ก็คือเยอะมากๆ วันแรก ไม่เข้าใจ ไม่แปลกครับ พยายามทบทวน ค่อยๆ อ่าน ค่อยๆเรียนไป เนื้อหาพวกนี้ ถ้ารู้ไว้ เราก็สามารถต่อยอดในหลายๆ ภาษาได้ไม่ยากครับ (ใกล้เคียงกัน)

หลายๆครั้ง ถ้าเรา compile แล้วมี Error หรือ Text Editor / IDEs มีแจ้งเตือน หรือ warning พยายามลองอ่าน Error ที่มันขึ้นบอกก่อนนะครับ ถ้าเราอ่าน error เข้าใจ จะช่วยให้เราเข้าใจการทำงานของโปรแกรมได้ดีขึ้น

References

#include <stdio.h>
int main(void)
{
 printf("hello, world\n");
}

C, IDE และ Compiler

  • เป้าหมายการเรียนคือ ไม่ใช่การเรียนภาษาใดภาษาหนึ่ง แต่คือเรียนเขียนโปรแกรม (How to program) ซึ่งถ้าเรามีพื้นฐานแล้ว เราสามารถไปต่อยอดภาษา Programming อื่นๆได้ครับ
  • Compiler เป็นตัวที่เอาไว้แปลงโค๊ดของเรา (Source Code) ให้เป็นภาษาเครื่อง (Machine Code) เพราะคอมรู้จักแค่ 0 กับ 1 (binary)
  • Visual Studio Code เป็น Text Editor ที่เอาไว้เขียน source code
  • สามารถใช้งาน VS Code เวอร์ชั่น Cloud Based ได้ครับ ใช้ Github ในการ login (อาจจะต้องลง Extension เสริม เช่น Codespace ต้องลองดูครับ ผมไม่ได้ลองส่วนนี้)
  • ตัว VS Code จะมี Terminal อยู่ด้านล่าง หากไม่มีกด View -> Terminal เพื่อเปิด Terminal ขึ้นมา
  • ใน Terminal เราจะสามารถใช้ Command Line Interface (CLI) ได้
  • make hello เพื่อทำการ compile ไฟล์ hello.c ของเรา เป็น machine code (execution)
  • ./hello - ให้คอมพิวเตอร์หาไฟล์ในโฟลเดอร์ปัจจุบัน (.) ที่ชื่อ hello และสั่งรันโปรแกรม
  • hello - เป็นไฟล์ที่ได้หลังจาก compiler แล้ว เป็นไฟล์ execution ที่เอาไว้รัน
  • stdio.h คือ Header file เพื่อบอกให้ compiler ทำการ load library ในโปรแกรมเรา

คำสั่ง Linux เบื้องต้น

  • cd <directory> - เอาไว้เปลี่ยน folder (directory) เช่น cd documents ก็คือเหมือนกด file explorer โฟลเดอร์ documents
  • cp - คือการ copy file
  • rm <file> - เป็นคำสั่งที่เอาไว้ลบไฟล์ rm ย่อมาจาก remove จะได้จำง่ายๆ
  • ls - เป็นคำสั่งที่เอาไว้แสดงไฟล์ทั้งหมดใน folder ของเรา (ls คือ list directory contents)
  • mkdir - สร้างโฟลเดอร์ใหม่

อ่านเพิ่มเติมได้ Command Line พื้นฐานบน Ubuntu

วิธีการใช้ cs50.h ร่วมกับ VS Code

สำหรับมือใหม่ VS Code ผมมีบทความเกี่ยวกับการใช้ VS Code บน Windows ครับ (ใช้ได้ทั้ง C และ C++) - เริ่มต้น C++ บน Windows

ในโค๊ดตัวอย่าง หรือในบทเรียน จะมีการใช้ library cs50.h ถ้าใครใช้งานผ่าน CS50IDE หรือ Codespace ก็อาจจะไม่มีปัญหา

แต่ถ้าใครใช้ VS Code บนเครื่องตัวเอง มือใหม่อาจจะงงๆได้ครับ ก็เลยมาแนะนำ นิดนึงคือ

  1. ทำการดาวน์โหดลไฟล์ cs50.h และ cs50.c ทั้งสองไฟล์และก็อปไปวางไว้ที่เดียวกับไฟล์ที่เราเขียน เช่น hello.c ก็อยู่โฟลเดอร์เดียวกัน
  2. จากนั้นที่ไฟล์ hello.c ทำการเพิ่ม
#include "cs50.h"

ไฟล์ก็จะได้เป็นแบบนี้ (ลองใช้งาน get_string)

#include <stdio.h>
#include "cs50.h"

int main(void)
{
    string answer = get_string("What's your name?");
    printf("hello, %s\n", answer);
}

สังเกตว่า "cs50.h" มี double quote นะครับ ต่างกับ <stdio.h> เพราะ <stdio.h> หมายถึง includes จาก folder ของ compiler ส่วน "cs50.h" คือทำการ include ไฟล์ที่อยู่ในโฟลเดอร์

  1. ตอน compile ให้ใช้คำสั่ง option cs50.c ต่อท้ายด้วย
gcc hello.c -o hello cs50.C
  1. เมื่อ compile เรียบร้อย ก็ทำการกด รันโปรแกรม ตัวโปรแกรมจะรอรับ input จากเรา เมื่อเราพิมพ์คำลงไป กด Enter มันก็จะทำการ hello <สิ่งที่เราพิมพ์> ออกมา
./hello

IDE Online

สำหรับใครไม่ได้ติดตั้ง VS Code สามารถใช้ IDE Online ได้ เช่น

Data Types

  • เวลากำหนดตัวแปร เราต้องระบุ type ให้มันด้วย ซึ่ง type ในภาษาซี มีด้วยกันหลายแบบ
  • bool - คือ boolean expression เป็นได้ true หรือ false
  • char - คือตัวอักษรตัวเดียว (ขนาด 1 byte)
  • int - คือตัวเลขปกติ (จำนวนเต็ม)
  • long - คือเหมือน int ที่เก็บค่าได้มากกว่า (จำนวน bit มากกว่า)
  • float - คือค่าพวกทศนิยม
  • double - เหมือน float แต่มีจุดทศนิยมได้มากกว่า
  • string - กลุ่มคำ char หลายๆตัวรวมกัน
  • ส่วน operators ก็เหมือนคณิตศาสตร์ปกติเลยครับ มี + - * / (คูณ ใช้ *)
  • และมี % (Modulo Operator) เอาไว้หาค่าเศษของการหาร เช่น 5 mod 2 คือ 5 หาร 2 เหลือเศษ 1 ฉะนั้น 5 % 2 เลยเท่ากับ 1

printf

  • printf - เป็น function ที่เอาไว้แสดง display ออกทางหน้าจอ เราสามารถกำหนดรูปแบบให้มันได้ (format code)
  • %c - สำหรับตัวแปรชนิด char
  • %f - สำหรับตัวแปรชนิด float
  • %i - สำหรับตัวแปรชนิด int
  • %li - สำหรับ long (long integer)
  • %s - สำหรับ string

สังเกตวิธีการจำ format codes ง่ายๆ ก็คือ ตัวย่อของ data types ครับ

การประกาศตัวแปร ชื่อ counter ให้มีค่าเท่ากับ 0 ทำได้ดังนี้

int counter = 0;

การเปลี่ยนค่า counter มีค่าเท่ากับค่าตัวมันเอง +1

counter = counter + 1;

สามารถเขียนได้อีกรูปแบบ (syntactic sugar) มีค่าเท่ากันกับด้านบน

counter += 1;

เช่นกันโค๊ดด้านล่าง 2 บรรทัด เหมือนกัน

counter = counter + 5;
// เหมือนกัน
counter += 5;

และตัว counter++ หรือ counter-- ก็มีค่าเท่ากับ counter += 1 และ `counter -= 1 ครับ

Challenge: ลองไปหาข้อมูลดูว่า counter++ และ ++counter ต่างกันยังไงนะ?

ตัวอย่างโปรแกรม Calculator

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int x = get_int("x: ");
    int y = get_int("y: ");
    printf("%i\n", x + y);
}

Condition เราสามารถเขียนแบบนี้ เพื่อเช็คเงื่อนไขว่า ถ้า x มากกว่า y ให้โปรแกรมทำงานในส่วน body {} ด้านล่าง

if (x < y)
{
    printf("x is less than y");
}
  • If Else ชื่อเช็คเงื่อนไขเป็นจริงๆ ทำใน if ถ้าไม่จริง ทำใน else
if (x < y)
{
    printf("x is less than y\n");
}
else
{
    printf("x is not less than y\n");
}
  • Else Ifเราสามารถเช็คหลายๆ เงื่อนไข ด้วย else if
if (x < y)
{
    printf("x is less than y\n");
}
else if (x > y)
{
    printf("x is greater than y\n");
}
else if (x == y)
{
    printf("x is equal to y\n");
}
  • ในภาษา C เวลาใช้เปรียบเทียบค่าจะใช้ == เช่น if (x == y) แต่ถ้าเป็น = จะเป็นการประกาศและกำหนดค่าให้ตัวแปร

  • const เป็นการบอกให้ compiler รู้ว่า ค่าของ variable (ตัวแปร) ของเราจะไม่เปลี่ยนค่า

  • เราสามารถใช้ หรือ (or) (||), และ (and) (&&) ใน condition ได้ แบบด้านล่าง

if (c == 'Y' || c == 'y')

ตัว bar | เป็นปุ่มที่อยู่บนปุ่ม Enter นะครับ

Loops, Function

  • Loop คือการทำงานซ้ำๆ ของโปรแกรม

จากตัวอย่างโค๊ด เราให้มันแสดงคำว่า meow ออกมา 3 ครั้ง จะเป็นแบบนี้

#include <stdio.h>

int main(void)
{
    printf("meow\n");
    printf("meow\n");
    printf("meow\n");
}

แล้วถ้าเกิดอยากให้มันแสดง 10 20 หรือ 100 ครั้ง ต้องมานั่งพิมพ์ซ้ำๆ 100 รอบหรือเปล่านะ?

while

เป็น loop ที่จะทำคำสั่งใน {} จนกว่า condition เงื่อนไขจะเป็น false (เท็จ) โดยมีรูปแบบ แบบนี้

while (true)
{
    printf("meow\n");
}

ด้านบน จะแสดง meow จนกว่า กว่า จะเป็น false แต่ในทีนี้มันเป็น true ตลอด ก็จะรันจนกว่าเราจะปิดโปรแกรม (แบบนี้เป็น infinity loop รันไม่มีวันจบ จนโปรแกรม crash สมัยผมเรียนปี 1 ก็ลืมบ่อยๆครับ 🤣)

ฉะนั้น การพิมพ์ meow 3 ครั้งด้วย while จะเป็นแบบนี้

int counter = 0;
while (counter < 3)
{
    printf("meow\n");
    counter = counter + 1;
}

วิธีการคิดง่ายๆ

  1. เราเช็คก่อนว่า counter มากกว่า 3 มั้ย? คำตอบคือ มากกว่า เพราะ counter = 0 ตอนนี้ ฉะนั้นมันก็ทำงานใน {}
  2. ทำการ printf() บรรทัด 4 และ เพิ่มค่า counter +1 (ตอนนี้ counter = 1) จบรอบ ย้อนกลับไปเช็ค while อีกครั้ง
  3. เช็ค while (counter < 3) หรือไม่? ใช่ มากกว่า เพราะ counter = 1 ตอนนี้
  4. ก็วน loop ทำงานใน function body {} เพิ่มค่า counter วนลูบ while จนกว่า เงื่อนไขจะเป็น false ก็จบการทำงาน

สามารถใช้ i++ แทน i = i + 1 ได้เหมือนกัน

int i = 0;
while (i < 3)
{
    printf("meow\n");
    i++;
}
  • หลายๆครั้งมักใช้ตัวแปรชื่อ i สำหรับการ loop เป็น conventional variable name นะไม่ได้บังคับ

for loop

เป็น loop ที่เหมือนกับ while แต่ต่างกันที่ การกำหนดค่าเริ่มต้น, เงื่อนไข และ ค่าที่อัพเดท ทำใน for เลย ต่างจาก while ค่าเริ่มต้น กำหนดข้างนอก loop และเงื่อนไข อยู่ตรง while และ ค่าที่อัพเดท อยู่ใน body ของ loop

รูปแบบของ for loop เป็นแบบนีิ

for (initialization; condition; update) {
    // body of-loop
}

และถ้าเขียนด้วย for loop จาก while loop ด้านบน ก็จะเป็นบบนี้

for (int i = 0; i < 3; i++)
{
    printf("meow\n");
}
  • คือ กำหนด i=0 เริ่มต้น
  • เช็คเงื่อนไข ว่า i<3 หรือไม่?
  • และ เพิ่มค่า i++ เมื่อจบ loop 1 รอบ (เหมือน while loop ที่เพิ่ม i++ หรือ i = i +1)
  • int i = 0 ที่ตั้งขึ้นมาใน for loop จะใช้ได้แค่ scope ของ loop นี้นะครับ ใช้ข้างนอกไม่ได้

โปรแกรม แสดงข้อความ meow 3 ครั้งด้วย for loop:

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        printf("meow\n");
    }
}

function

เป็นคำสั่ง หรือชุดคำสั่งเพื่อทำงานบางอย่าง ที่เรากำหนด ถ้าเราสังเกต int main(void) {} ก็คือ function main นั่นเอง

function meow ที่เป็น function ที่เอาไว้ printf meow อย่างเดียว โดยไม่ได้รับค่าใดๆ และไม่ return ค่าใดๆ กลับไป

void meow(void)
{
    printf("meow\n");
}

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        meow();
    }
}
  • เวลาเรียก function ก็ใช้ () วงเล็บเปิด / ปิด เช่น meow();

ภาษาซีจะทำงานจากบน ลง ล่าง ถ้าเกิดเราไปเรียก meow() ก่อนที่ compiler มันจะรู้จัก มันก็จะ error แบบตัวอย่างโค๊ดด้านล่าง void meow(void) ถูกประกาศไว้หลัง function main ทำให้มันยังไม่รู้จักขณะเรียก

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        meow();
    }
}

void meow(void)
{
    printf("meow\n");
}
  • เราแก้ไขด้วยการทำ prototype function ครับ คือ function ที่ไม่มี body ข้างใน รู้แค่ function ชื่ออะไร รับค่าอะไร ส่งค่าอะไรกลับไป แบบนี้
void meow(void);

เมื่อรวมกับที่ error แล้ว ก็จะได้เป็นแบบนี้ compile ผ่านแล้ว

#include <stdio.h>

void meow(void);

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        meow();
    }
}

void meow(void)
{
    printf("meow\n");
}

function สามารถรับค่าได้ และตอนเรียก function ก็ส่งค่าไปให้ function แบบนี้

#include <stdio.h>

void meow(int n);

int main(void)
{
    meow(3);
}

void meow(int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("meow\n");
    }
}
  • จะเห็นว่า void meow(int n) เรากำหนดว่า function meow จะรับค่า n มา (เท่าไหร่ก็ได้ แล้วแต่ผู้เรียก function จะส่งมา)
  • ใน meow function ก็ for loop ตามจำนวน n เพื่อโชว์ meow
  • และตอนเรียก meow(3) ก็แค่ส่งจำนวนที่เราต้องการแสดง ในทีนี้คือ 3 ก็พิมพ์ meow 3 รอบ

Mario

ใช้ for loop ในการ print ?

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 4; i++)
    {
        printf("?");
    }
    printf("\n");
}

สามารถรับค่า user input แล้วก็แสดง ? ตามจำนวนที่ user กรอกได้แบบนี้

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int n;
    do
    {
        n = get_int("Width: ");
    }
    while (n < 1);

    for (int i = 0; i < n; i++)
    {
        printf("?");
    }
    printf("\n");
}
  • do while เป็น loop อีกชนิด ที่มันการันตี ว่าจะต้องทำงานอย่างน้อย 1 ครั้ง แล้วก็เช็คเงื่อนไขใน while(condition) ถ้าจริง ก็ทำ loop เดิม ถ้าไม่ก็จบน loop ในตัวอย่าง คือรับค่า input จาก user ถ้า user พิมพ์ค่ามากกว่า 1 ก็จบการทำงาน และค่า n ก็คือค่าที่ user พิมพ์มมา
  • for loop ตามจำนวน n ครั้ง เพื่อแสดง ?

แบบยากขึ้น คือมองเป็น 2 มิติ (มีแนวตั้ง แนวนอน) (row/column)

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int n;
    do
    {
        n = get_int("Size: ");
    }
    while (n < 1);

    // For each row
    for (int i = 0; i < n; i++)
    {
        // For each column
        for (int j = 0; j < n; j++)
        {
            // Print a brick
            printf("#");
        }

        // Move to next row
        printf("\n");
    }
}

ผลลัพธ์ด้านบนได้แบบนี้

Size: 3

###
###
###

เรื่อง loop หากใครยังเพิ่งหัดเขียนโปรแกรม แนะนำลองพิมพ์ ลองหัด ลองประยุกต์ใช้งาน จนกว่าจะคล่องนะครับ เพราะมันมีประโยชน์มากๆ เพราะมันอาจจะดูซับซ้อนในตอนแรก แต่เมื่อเข้าใจการทำงานแล้ว ก็จะไม่ยากเลย วิธีที่ง่ายสุด คือ ลองคิดการทำงานของ loop แต่ละขั้นตอน ลงกระดาษ (หรือถ้าใครรู้จักการ debug และการใช้ breakpoint ของ VS Code หรือ IDE ตัวอื่นๆ ก็จะพอเห็นภาพครับ)

การหยุดการทำงานของ loop จะใช้ break :

while (true)
{
    n = get_int("Size: ");
    if (n > 1)
    {
        break;
    }
}

Imprecision, overflow

เรื่องของการคำนวณ พวก floating number (ค่าที่มีทศนิยม) มักจะไม่ค่อยถูก หรือคลาดเคลื่อนไปบ้าง

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    // Prompt user for x
    float x = get_float("x: ");

    // Prompt user for y
    float y = get_float("y: ");

    // Divide x by y
    float z = x / y;

    printf("%f\n", z);
}

ผลลัพธ์

$ ./calculator
x: 2
y: 3
0.666667
$ ./calculator
x: 1
y: 10
0.100000
$
  • เราสามารถใช้ printf("%.50f\n", z); หรือ %.5f เพื่อกำหนดจุดทศนิยมได้
  • ปัญหาคือ computer ไม่สามารถแสดงค่าจริงๆ ได้ ทำได้แค่ใกล้เคียงที่สุด เพราะด้วยข้อจำกัดของ 32 bit เอาแบบง่ายๆ 2 หาร 3 จริงๆ ได้ค่าเท่าไหร่ ถ้าเราคิดด้วยมือ มันก็คือ 0.6666. ไปเรื่อยๆ ใช่มั้ยครับ แล้วแต่เราจะพอแค่ไหน เช่น 0.66666667
  • ในอนาคตปี 2038 เราอาจจะมีปัญหา ในการเก็บข้อมูลเวลาในระบบ computer เพราะเราใช้แบบ 32-bit
  • แต่ปัจจุบัน hardware มีความสามารถมากขึ้น สามารถจัดการ bit ได้มากกว่าเดิม

ตัวอย่างสุดท้าย

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    float amount = get_float("Dollar Amount: ");
    int pennies = amount * 100;
    printf("Pennies: %i\n", pennies);
}

ผลลัพธ์

$ ./pennies
Dollar Amount: .99
Pennies: 99
$ ./pennies
Dollar Amount: 1.23
Pennies: 123
$ ./pennies
Dollar Amount: 4.20
Pennies: 419

เหมือน ค่าที่รับสุดท้าย 4.20 จะแสดงไม่ตรง ซึ่งจริงๆ มันอาจจะเก็บค่า 4.199999... และเวลา คูณด้วย 100 มันเลยกลายเป็น 419

ใช้ round มาช่วย

#include <cs50.h>
#include <math.h>
#include <stdio.h>

int main(void)
{
    float amount = get_float("Dollar Amount: ");
    int pennies = round(amount * 100);
    printf("Pennies: %i\n", pennies);
}

Quiz ท้ายบทเรียน

จริงๆ ผู้เรียนสามารถทำ quiz ส่งได้ และก็มีรายละเอียดและแบบฝึกเพิ่มเติม ลองดูครับ

สรุป

Week 1 มีอะไรให้เรียนเยอะมาก และนอกจากจะนั่งดู video 2ชั่วโมงแล้วจบ ต้องบอกว่าไม่ใช่ครับ แต่ละขั้นตอน ต้องใช้เวลาศึกษาเพิ่มเติม เช่น loop ต่างๆ หรือ if/else ถ้าเป็นมือใหม่ ยังไงก็ต้องลองหัดเขียน ลองสร้างคำถาม แล้วทำตาม หรือหาโจทย์ฝึกทำเพิ่มเติมครับ

แหล่งศึกษาเพิ่มเติม (ภาษาอังกฤษ)

ขอให้สนุกกับการเรียนนะครับ

Happy Coding ❤️

Buy Me A Coffee
Discord