Chuyển tới nội dung chính

Ngôn ngữ C và chương trình đầu tiên

Từ bài này, ta bắt đầu biến những ý tưởng về thuật toán thành mã thật, viết bằng một ngôn ngữ lập trình cụ thể là ngôn ngữ C. Ta sẽ làm quen với công cụ để gõ và chạy chương trình, viết dòng lệnh đầu tiên, rồi mổ xẻ cấu trúc của một chương trình C cho tới từng dấu chấm phẩy. Hãy đi thật chậm, vì những khái niệm ở đây sẽ theo ta suốt cả học phần.

Giới thiệu ngôn ngữ C

Một chút lịch sử

Ngôn ngữ C ra đời đầu thập niên 1970 tại phòng thí nghiệm Bell Labs ở Mỹ, do Dennis Ritchie thiết kế. Ban đầu C được tạo ra để viết lại hệ điều hành Unix, vốn trước đó phải viết bằng hợp ngữ rất khó bảo trì. Nhờ C, mã của Unix trở nên gọn gàng và dễ chuyển sang máy khác hơn nhiều.

Năm 1978, Brian Kernighan và Dennis Ritchie xuất bản cuốn sách The C Programming Language, thường gọi tắt là sách K&R, và đây trở thành mô tả chuẩn không chính thức đầu tiên của ngôn ngữ. Về sau, để tránh mỗi nơi hiểu C một kiểu, người ta chuẩn hóa ngôn ngữ qua các tổ chức tiêu chuẩn: bản ANSI C năm 1989 (còn gọi là C89), rồi C99, C11 và các bản sau. Trong học phần này ta dùng C chuẩn ở mức cơ bản, đủ để chạy trên mọi trình biên dịch phổ biến.

Các mốc ra đời và chuẩn hóa của C
  1. đầu 1970s
    C ra đời
    Dennis Ritchie thiết kế tại Bell Labs để viết lại hệ điều hành Unix.
  2. 1978
    Sách K&R
    Kernighan và Ritchie xuất bản The C Programming Language, mô tả chuẩn không chính thức đầu tiên.
  3. 1989
    ANSI C (C89)
    Chuẩn hóa chính thức để mọi nơi hiểu C một kiểu.
  4. về sau
    C99, C11 và các bản sau
    Ngôn ngữ tiếp tục được bổ sung; học phần này dùng mức cơ bản chạy được trên mọi trình biên dịch.
ghi chú

Ngôn ngữ C là tổ tiên của rất nhiều ngôn ngữ ngày nay. C++, C#, Java, JavaScript hay Go đều vay mượn cú pháp và tư duy từ C. Học vững C, ta sẽ thấy các ngôn ngữ kia trở nên quen thuộc một cách bất ngờ.

Vì sao nên học C

Người mới thường hỏi: đã có nhiều ngôn ngữ hiện đại, vì sao vẫn bắt đầu bằng C? Có mấy lý do chính.

  • C gần với phần cứng. C cho ta làm việc trực tiếp với bộ nhớ, địa chỉ và byte. Khi học C, ta hiểu rõ máy tính thực sự lưu trữ và xử lý dữ liệu ra sao, thay vì coi máy như một hộp đen. Đây là nền tảng để sau này học con trỏ, cấu trúc dữ liệu và hệ điều hành.
  • C dạy tư duy lập trình cấu trúc. Ngôn ngữ này buộc ta khai báo rõ kiểu dữ liệu, tổ chức chương trình thành các hàm, suy nghĩ mạch lạc từng bước. Đó chính là cốt lõi của môn học nền tảng này.
  • C có mặt khắp nơi. Hệ điều hành, trình điều khiển thiết bị, hệ thống nhúng trên ô tô hay lò vi sóng, các phần mềm đòi hỏi tốc độ cao đều có dấu vết của C. Một lượng khổng lồ mã nguồn quan trọng của thế giới viết bằng C.
  • C là bệ phóng. Hiểu C rồi, chuyển sang C++, Java hay Python sẽ rất nhanh, vì phần lớn khái niệm đã có sẵn trong đầu.
Ghi nhớ

C là một ngôn ngữ nhỏ gọn nhưng mạnh mẽ và gần phần cứng. Học C không chỉ để viết được chương trình, mà còn để hiểu máy tính vận hành như thế nào ở bên dưới.

Môi trường lập trình

Để viết một chương trình C, ta cần hai thứ: một trình soạn thảo để gõ mã, và một trình biên dịch để dịch mã đó thành chương trình máy chạy được. Nhiều công cụ gói cả hai (và thêm trình gỡ lỗi) vào một phần mềm duy nhất, gọi là môi trường phát triển tích hợp, viết tắt IDE.

Dev-C++

Theo đề cương học phần, ta dùng Dev-C++, một IDE miễn phí, nhẹ và quen thuộc với người mới học ở Việt Nam. Dev-C++ đã tích hợp sẵn trình biên dịch nên cài một lần là dùng được ngay, không phải cấu hình phức tạp. Các bước làm việc cơ bản như sau.

  1. Cài đặt. Tải Dev-C++ từ trang chính thức, chạy bộ cài và bấm tiếp tục cho tới khi hoàn tất. Lần đầu mở lên, chọn ngôn ngữ giao diện và để các thiết lập mặc định.
  2. Tạo tệp mã nguồn. Vào File rồi New rồi Source File, hoặc bấm tổ hợp Ctrl+N. Một trang trắng hiện ra để ta gõ mã.
  3. Lưu tệp. Bấm Ctrl+S, đặt tên tệp với phần mở rộng .c (ví dụ hello.c). Phần mở rộng .c báo cho công cụ biết đây là mã C.
  4. Biên dịch và chạy. Bấm phím F11 để vừa biên dịch vừa chạy. Nếu mã có lỗi, Dev-C++ liệt kê lỗi ở khung phía dưới; nếu không lỗi, một cửa sổ đen console hiện ra hiển thị kết quả.
Lưu ý

Nhớ lưu tệp với đuôi .c chứ không phải .cpp. Đuôi .cpp sẽ khiến công cụ biên dịch theo ngôn ngữ C++, đôi khi làm một số đoạn mã C hành xử khác đi. Trong môn này ta luôn dùng .c.

Vài công cụ khác

Dev-C++ không phải lựa chọn duy nhất. Hai cái tên đáng biết:

  • gcc. Đây là trình biên dịch chạy bằng dòng lệnh, rất phổ biến trên Linux và cũng có trên Windows. Khi đã quen, ta chỉ cần gõ gcc hello.c -o hello để biên dịch rồi ./hello để chạy. Nhiều IDE, kể cả Dev-C++, thực ra gọi gcc ở bên dưới.
  • VS Code. Một trình soạn thảo hiện đại, đẹp và mạnh, nhưng cần cài thêm trình biên dịch và một vài tiện ích mở rộng. Phù hợp khi ta đã quen việc và muốn một môi trường chuyên nghiệp hơn.

Ở giai đoạn đầu, ta cứ dùng Dev-C++ cho đơn giản. Khi đã tự tin, hãy thử gcc để hiểu rõ hơn quá trình biên dịch diễn ra thế nào.

Chương trình đầu tiên: Hello, World

Theo truyền thống lâu đời trong nghề, chương trình đầu tiên ai cũng viết là chương trình in ra dòng chữ Hello, World. Hãy gõ đoạn mã sau vào Dev-C++, lưu lại rồi bấm F11.

#include <stdio.h> // bring in the standard input/output library

int main(void) {
printf("Hello, World\n"); // print a line of text to the screen
return 0; // tell the operating system: success
}

Nếu mọi thứ đúng, một cửa sổ console hiện ra với dòng chữ Hello, World. Xin chúc mừng, ta vừa chạy chương trình C đầu tiên. Trông thì ngắn nhưng đoạn mã này chứa hầu hết các thành phần cốt lõi của một chương trình C. Phần tiếp theo ta sẽ mổ xẻ từng dòng.

Quy trình soạn, biên dịch và chạy

Điều quan trọng cần hiểu: máy tính không hiểu trực tiếp dòng chữ ta gõ. Mã ta viết, gọi là mã nguồn, chỉ là văn bản dành cho con người đọc. Trình biên dịch dịch mã nguồn thành mã máy mà bộ xử lý hiểu được, đóng gói thành một tệp chương trình chạy được. Sau đó ta mới chạy tệp ấy. Vòng làm việc tóm tắt như sau:

soạn mã (Dev-C++) → hello.c (mã nguồn) → biên dịch (compiler)
→ hello.exe (tệp chạy được) → chạy (console)

Nếu trong lúc biên dịch hoặc chạy phát hiện lỗi, ta quay lại bước soạn mã để sửa rồi biên dịch lại.

Ghi nhớ

Mã nguồn (tệp .c) là văn bản cho người đọc. Trình biên dịch biến nó thành tệp chạy được cho máy. Mỗi lần sửa mã, ta phải biên dịch lại thì thay đổi mới có hiệu lực.

Cấu trúc một chương trình C

Bây giờ ta quay lại đoạn mã Hello, World và giải thích từng phần.

Dòng #include

Dòng #include <stdio.h> là một chỉ thị tiền xử lý. Nó yêu cầu nạp nội dung của thư viện stdio.h vào chương trình trước khi biên dịch. Tên stdio viết tắt của standard input output, nghĩa là vào ra chuẩn. Nhờ thư viện này ta mới dùng được hàm printf để in ra màn hình. Mọi dòng bắt đầu bằng dấu thăng # đều là chỉ thị tiền xử lý và không kết thúc bằng dấu chấm phẩy.

Hàm main

Dòng int main(void) mở đầu hàm main. Đây là hàm đặc biệt: khi chạy chương trình, máy luôn bắt đầu thực thi từ hàm main. Mọi chương trình C đều phải có đúng một hàm main. Từ int ở trước cho biết hàm này trả về một số nguyên cho hệ điều hành; chữ void trong ngoặc nghĩa là hàm không nhận tham số đầu vào. Ta sẽ học kỹ về hàm ở bài sau, hiện giờ chỉ cần coi main là điểm khởi đầu.

Khối lệnh và dấu ngoặc nhọn

Cặp dấu ngoặc nhọn {} bao quanh phần thân của hàm, gọi là một khối lệnh. Mọi câu lệnh nằm giữa hai dấu này thuộc về hàm main và sẽ được chạy lần lượt từ trên xuống. Dấu mở khối luôn phải có dấu đóng khối tương ứng; thiếu một trong hai là lỗi cú pháp.

Câu lệnh và dấu chấm phẩy

Bên trong khối là các câu lệnh. Dòng printf("Hello, World\n"); là một câu lệnh gọi hàm printf để in chuỗi chữ ra màn hình. Phần \n ở cuối chuỗi là ký tự xuống dòng, giúp con trỏ nhảy xuống dòng mới sau khi in. Mỗi câu lệnh trong C phải kết thúc bằng dấu chấm phẩy ;. Đây là dấu chấm câu của ngôn ngữ, báo hết một lệnh.

Lệnh return 0

Dòng return 0; kết thúc hàm main và trả giá trị 0 cho hệ điều hành. Theo quy ước, trả về 0 nghĩa là chương trình chạy xong êm đẹp, không gặp lỗi. Một giá trị khác 0 thường ngầm hiểu là có trục trặc. Vì main được khai báo trả int, ta cần dòng return này.

Lưu ý

Lỗi phổ biến nhất của người mới là quên dấu chấm phẩy ở cuối câu lệnh. Trình biên dịch khi đó báo lỗi ở dòng kế tiếp, khiến ta bối rối vì tưởng dòng đó sai. Khi gặp lỗi cú pháp khó hiểu, hãy kiểm tra dấu chấm phẩy của dòng ngay phía trên.

Ta mở rộng Hello, World để in hai dòng. Để ý mỗi câu lệnh printf đều có dấu chấm phẩy, và mỗi chuỗi kết thúc bằng \n để xuống dòng.

#include <stdio.h>

int main(void) {
printf("Xin chao cac ban\n"); // first line
printf("Mon: Nhap mon lap trinh\n"); // second line
return 0;
}

Kết quả in ra hai dòng riêng biệt. Nếu bỏ \n ở câu đầu, hai dòng sẽ dính liền nhau trên cùng một hàng.

Các thành phần của ngôn ngữ

Giống như tiếng Việt được tạo từ bảng chữ cái, từ vựng và quy tắc ngữ pháp, ngôn ngữ C cũng có các viên gạch cơ bản.

Bảng chữ cái và phân biệt hoa thường

Mã C được viết từ các chữ cái Latinh hoa và thường, các chữ số từ 0 đến 9, các ký tự đặc biệt như dấu cộng, trừ, nhân, chia, ngoặc các loại, dấu chấm phẩy, dấu thăng #, dấu và & cùng khoảng trắng.

Lưu ý

C phân biệt chữ hoa và chữ thường. Vì vậy Tong, tongTONG là ba tên khác nhau hoàn toàn. Cũng vì lẽ đó, hàm tên main phải viết thường, viết Main là sai.

Từ khóa

Từ khóa là những từ đã được ngôn ngữ dành riêng, mang ý nghĩa cố định, ta không được dùng làm tên biến hay tên hàm. Một số từ khóa quen thuộc trong C: int, float, double, char, if, else, for, while, do, switch, case, break, continue, return, void, const, struct, sizeof. Ta sẽ gặp dần các từ khóa này qua từng bài.

Tên và định danh

Định danh, hay gọi nôm na là tên, là chuỗi ký tự do người lập trình đặt ra để gọi biến, hằng, hàm. Quy tắc đặt tên trong C:

  1. Chỉ gồm chữ cái, chữ số và dấu gạch dưới _.
  2. Không được bắt đầu bằng chữ số. Ví dụ 2so là sai, so2 là đúng.
  3. Không được trùng với từ khóa. Không thể đặt biến tên int hay return.
  4. Không chứa khoảng trắng và không dùng dấu tiếng Việt. Thay vì viết có dấu, ta viết tong_diem hoặc tongDiem.
Ghi nhớ

Hãy đặt tên có nghĩa. Tên diem_trung_binh cho người đọc biết ngay biến đó chứa gì, còn tên x hay a1 thì không. Mã dễ đọc là mã dễ sửa và dễ tìm lỗi.

Ghi chú

Ghi chú là phần văn bản dành cho con người đọc, được trình biên dịch bỏ qua hoàn toàn. Ta dùng ghi chú để giải thích mã đang làm gì. C có hai cách viết ghi chú: ghi chú một dòng bắt đầu bằng // kéo dài tới hết dòng; ghi chú nhiều dòng nằm giữa /**/, có thể trải qua nhiều dòng.

#include <stdio.h>

/* This is a multi-line comment.
It can describe the whole program. */
int main(void) {
int age = 20; // a single-line comment after a statement
printf("%d\n", age); // print the value of age
return 0;
}
ghi chú

Trong học phần này, ghi chú trong mã được viết bằng tiếng Anh theo quy ước nghề nghiệp, còn văn xuôi giải thích thì bằng tiếng Việt. Người học nên tập viết ghi chú tiếng Anh ngắn gọn ngay từ đầu.

Bài tập thực hành

Bài tập 1: chạy chương trình đầu tiên

Cài đặt Dev-C++ trên máy của bạn, gõ lại chương trình Hello, World, lưu với tên hello.c, biên dịch và chạy. Ghi lại các bước bạn đã làm.

Gợi ý

Mở Dev-C++, bấm Ctrl+N tạo tệp mới, gõ đúng đoạn mã Hello, World, bấm Ctrl+S lưu với tên hello.c, rồi bấm F11 để biên dịch và chạy. Nếu cửa sổ console hiện dòng Hello, World là thành công. Nếu báo lỗi, kiểm tra lại dấu chấm phẩy, dấu ngoặc và dòng #include.

Bài tập 2: tên hợp lệ hay không

Trong các tên sau, tên nào hợp lệ để đặt cho biến trong C, tên nào không và vì sao: so_luong, 2diem, tongDiem, return, ho ten, _tam.

Gợi ý

so_luong hợp lệ; 2diem sai vì bắt đầu bằng chữ số; tongDiem hợp lệ; return sai vì trùng từ khóa; ho ten sai vì chứa khoảng trắng; _tam hợp lệ vì bắt đầu bằng dấu gạch dưới và chỉ gồm chữ với gạch dưới.

Bài tập 3: in nhiều dòng

Viết chương trình in ra màn hình ba dòng: họ tên của bạn, mã sinh viên và tên lớp, mỗi thông tin trên một dòng riêng.

Gợi ý

Dùng ba câu lệnh printf, mỗi câu một chuỗi kết thúc bằng \n để xuống dòng. Nhớ đặt mỗi câu lệnh trong thân hàm main và kết thúc bằng dấu chấm phẩy.

#include <stdio.h>

int main(void) {
printf("Nguyen Van A\n"); // full name
printf("SV001\n"); // student id
printf("Lop 22IT\n"); // class name
return 0;
}

Bài tập 4: tìm và sửa lỗi

Sửa lại đoạn mã sau cho chạy được; có ít nhất ba lỗi gồm thiếu dấu chấm phẩy, thiếu dòng #include và tên hàm chính viết sai: int Main(void) { printf("Xin chao") return 0; }.

Gợi ý

Ba lỗi: thiếu #include <stdio.h> ở đầu; tên hàm Main viết hoa chữ M, phải sửa thành main viết thường; thiếu dấu chấm phẩy sau lời gọi printf. Sau khi sửa:

#include <stdio.h>

int main(void) {
printf("Xin chao");
return 0;
}
Ghi nhớ

Ba việc cốt lõi của bài này: chương trình C luôn bắt đầu từ hàm main; mỗi câu lệnh kết thúc bằng dấu chấm phẩy; mã nguồn .c phải được biên dịch thành tệp chạy được trước khi máy hiểu. Nắm chắc cấu trúc này là đã vững nền để đi tiếp.