Từ "biên dịch được" đến "đọc hiểu được"
Ở trang trước, bạn đã biến một tệp mã nguồn thành chương trình chạy được nhờ trình biên dịch. Giờ ta chậm lại và xem trong tệp đó thực sự có gì. Mọi chương trình C++ đều được ghép từ cùng vài thành phần cấu trúc, và khi bạn gọi được tên chúng, một tệp .cpp lạ sẽ không còn trông như một mớ hỗn độn.
Đây là một chương trình hoàn chỉnh, chạy được. Đọc qua một lần, rồi ta sẽ mổ xẻ từng dòng.
Năm dòng có ý nghĩa, và dòng nào cũng làm một việc cụ thể. Cùng đi qua từng dòng nhé.
Dòng #include
#include <iostream>
Dòng này nạp header <iostream> để bạn dùng được các tính năng nhập/xuất như cout. #include là một chỉ thị tiền xử lý (preprocessor directive): nó chạy trước khi biên dịch thật sự và về cơ bản là dán nội dung của header đó vào tệp của bạn.
Hai điều làm dòng #include đặc biệt:
- Chúng bắt đầu bằng
#, dấu hiệu của chỉ thị tiền xử lý. - Chúng không kết thúc bằng dấu chấm phẩy. Đây là ngoại lệ phổ biến duy nhất với thói quen "dòng nào cũng kết thúc bằng
;" mà bạn sắp hình thành.
Nếu bạn dùng cout mà quên #include <iostream>, trình biên dịch sẽ không biết cout là gì và bạn nhận lỗi kiểu 'cout' was not declared in this scope. Cách sửa gần như luôn là thêm include còn thiếu.
Hàm main
int main() {
// ...
return 0;
}
main là nơi chương trình bắt đầu. Khi bạn chạy chương trình đã biên dịch, việc thực thi bắt đầu ở dòng đầu tiên bên trong main và dừng khi main trả về. Mọi chương trình C++ có đúng một hàm main, không hơn không kém.
Các thành phần:
int:maintrả một số nguyên về cho hệ điều hành. Theo quy ước,0nghĩa là "đã chạy xong thành công".main(): tên hàm, theo sau là cặp ngoặc tròn chứa tham số (ở đây để trống).{ ... }: cặp ngoặc nhọn bao lấy thân hàm, tức các câu lệnh sẽ chạy.return 0;: kết thúcmainvà báo thành công. Nếu bạn bỏ dòng này, C++ coi nhưmainđã trả về0giúp bạn, nhưng viết ra thì ý định của bạn rõ ràng hơn.
Câu lệnh và dấu chấm phẩy
Câu lệnh (statement) là một chỉ dẫn hoàn chỉnh. Trong C++, mọi câu lệnh đều kết thúc bằng dấu chấm phẩy:
C++ hoàn toàn bỏ qua dấu xuống dòng và khoảng trắng thừa. Trình biên dịch không quan tâm bạn viết các câu lệnh trên một dòng hay mười dòng: chính dấu ; cho nó biết mỗi câu lệnh kết thúc ở đâu. Với trình biên dịch, cả ba cách viết sau đều giống hệt nhau:
int a = 1; int b = 2;
int a = 1;
int b = 2;
int
a
= 1;
Sự tự do đó đi kèm cái bẫy phổ biến nhất của người mới: quên dấu chấm phẩy. Hãy xem chuyện gì xảy ra ở đây.
int x = 5 // <-- no semicolon
int y = 10;
Bạn sẽ nhận lỗi kiểu expected ';' before 'int'. Để ý điểm dễ nhầm: trình biên dịch chỉ nhận ra có gì đó sai khi nó đọc tới dòng tiếp theo, nên số dòng được báo thường nằm dưới lỗi thật một dòng. Khi thông báo lỗi nhắc đến ; bị thiếu, hãy kiểm tra dòng ngay phía trên dòng được báo.
Ngoặc nhọn tạo khối lệnh
Trong khi một số ngôn ngữ dùng thụt lề để nhóm code, C++ dùng ngoặc nhọn { }. Mọi thứ nằm giữa một cặp ngoặc khớp nhau là một khối lệnh (block). Thân hàm, vòng lặp và câu lệnh if đều dùng chúng:
Hai dòng bên trong ngoặc nhọn của if chỉ chạy khi điều kiện đúng. Lệnh cout cuối cùng nằm ngoài ngoặc nhọn, nên nó luôn chạy.
Vì cấu trúc được quyết định bởi ngoặc nhọn chứ không phải thụt lề, C++ không bắt bạn thụt lề. Nhưng bạn vẫn nên làm: thụt lề là cách con người đọc ra cấu trúc mà ngoặc nhọn tạo nên. Một bug hay gặp là ngoặc không khớp: mỗi { cần một } đóng lại. Nếu quên một cái, trình biên dịch thường báo lỗi gần cuối tệp (expected '}' at end of input), cách xa chỗ bạn thật sự viết sai. Đếm ngoặc, hoặc để trình soạn thảo tô sáng cặp ngoặc giúp bạn, sẽ đỡ rất nhiều bối rối.
In ra màn hình với cout và <<
Bạn đã thấy cout vài lần rồi. Đó là luồng đầu ra chuẩn, và bạn gửi giá trị vào nó bằng toán tử << (đọc là "đưa thứ này vào đầu ra"):
Bạn có thể nối nhiều << để in liên tiếp nhiều thứ. endl kết thúc dòng (đồng thời xả bộ đệm); chuỗi thoát "\n" là cách nhẹ hơn để xuống dòng. Với chương trình đơn giản, dùng cách nào cũng được.
Về dòng using namespace std; ở đầu tệp: các tên trong thư viện chuẩn nằm trong namespace std, nên tên đầy đủ là std::cout. Viết using namespace std; một lần giúp bạn bỏ được tiền tố std:: và chỉ cần viết cout. Cách này tiện trong ví dụ ngắn, nhưng ở dự án lớn nhiều nhóm thích viết rõ std::cout để tránh trùng tên, nên đừng ngạc nhiên khi thấy cả hai cách.
Giờ bạn đã đọc được bộ khung của mọi chương trình
Include ở đầu tệp, hàm main làm điểm bắt đầu, câu lệnh kết thúc bằng dấu chấm phẩy, ngoặc nhọn nhóm code thành khối, và cout << để in ra: bộ khung này có mặt trong gần như mọi tệp C++ bạn từng mở. Phần còn lại của ngôn ngữ là các chi tiết xếp chồng lên vài hình dạng này. Khi một chương trình trông đáng sợ, hãy tìm main trước, rồi đọc các câu lệnh theo thứ tự; cấu trúc sẽ dẫn đường cho bạn.
Tiếp theo: chú thích
Hiện tại mọi dòng trong chương trình của bạn đều là code mà trình biên dịch đọc. Tiếp theo ta sẽ thêm những dòng nó cố tình bỏ qua, gọi là chú thích (comment), để bạn để lại ghi chú cho chính mình và người đọc khác. Bạn sẽ thấy dạng một dòng // và dạng khối /* ... */, cùng lúc nào nên dùng dạng nào.
Câu hỏi thường gặp
Vì sao mọi câu lệnh C++ đều cần dấu chấm phẩy?
C++ bỏ qua dấu xuống dòng và khoảng trắng, nên nó cần một dấu hiệu rõ ràng để biết câu lệnh này kết thúc và câu lệnh tiếp theo bắt đầu ở đâu. Dấu hiệu đó là dấu chấm phẩy ;. Thiếu dấu chấm phẩy là lỗi biên dịch phổ biến nhất của người mới, và số dòng được báo thường là dòng ngay sau dòng bạn quên.
int main() trong C++ dùng để làm gì?
main là hàm nơi chương trình bắt đầu chạy: việc thực thi bắt đầu từ dòng đầu tiên của nó và kết thúc khi hàm trả về. Mọi chương trình C++ cần đúng một hàm main. int nghĩa là hàm trả một mã trạng thái kiểu số nguyên về cho hệ điều hành, theo quy ước 0 báo hiệu thành công.
Có bắt buộc phải viết std:: trước cout không?
Bạn có thể viết std::cout (tên đầy đủ) hoặc thêm using namespace std; một lần ở đầu tệp rồi chỉ cần viết cout. Dạng đầy đủ an toàn hơn trong các tệp lớn; using namespace std; là lối tắt phổ biến trong ví dụ nhỏ và bài hướng dẫn.