Giao diện
Chương 1
Công cụ, chia file và đọc lỗi biên dịch
Chương này ăn điểm ở đâu trong đề thi
- Chia file rõ ràng (.h, .cpp, main.cpp, đúng tên)2 điểm, có ở 12/12 đề
- Chương trình chạy đúng, test case hợp lý2 – 3 điểm, có ở 12/12 đề
Đây là chương ít “lý thuyết” nhất và cũng là chương đáng đầu tư nhất. Một nửa số điểm của mọi đề thi nằm ở đây, và đây là phần duy nhất trong môn học mà luyện đi luyện lại chắc chắn có kết quả: tuần đầu bạn mất 20 phút để dựng một lớp chạy được, tới tuần thứ tám chỉ còn 4 phút. 16 phút đó là 16 phút bạn có thêm trong phòng thi để nghĩ.
1.1 Cài đặt môi trường
Bạn không bắt buộc phải cài g++ riêng
Dev-C++, Code::Blocks, Visual Studio và CLion đều kèm sẵn bộ biên dịch. Tải về, bấm Next mấy lần, là dịch được ngay — không phải cài thêm gì, không phải sửa PATH.
Chỉ VS Code và các trình soạn thảo thường (Notepad++, Sublime…) mới cần cài bộ biên dịch riêng, vì chúng không phải IDE.
Hướng dẫn từng bước cho từng công cụ, có hình minh họa, nằm ở Phụ lục E. Chọn một công cụ, làm theo đúng mục của nó, rồi quay lại đây.
| Công cụ | Dung lượng | Kèm compiler? | Hướng dẫn |
|---|---|---|---|
| Dev-C++ | ~60 MB | Có — TDM-GCC | Phụ lục E.1 |
| Code::Blocks | ~150 MB | Có, nếu tải bản mingw-setup | Phụ lục E.2 |
| Visual Studio Community | ~8 GB | Có — MSVC | Phụ lục E.3 |
| CLion | ~1 GB | Có — MinGW kèm sẵn | Phụ lục E.4 |
| VS Code / trình soạn thảo | ~120 MB | KHÔNG — phải cài MinGW-w64 | Phụ lục E.5 |
Nếu chưa biết chọn gì: lấy Dev-C++. Nhẹ nhất, cài nhanh nhất, và làm được đúng mọi thứ môn này cần.
Hai việc công cụ nào cũng phải làm được
Dù chọn gì, bạn phải làm được hai việc sau — nếu không, công cụ đó không dùng được cho môn này:
- Tạo một project chứa nhiều file
.hvà.cpp, rồi dịch tất cả cùng lúc. Đây là chỗ mọi công cụ đều có bẫy riêng; Phụ lục E chỉ rõ từng cái. - Bật chuẩn C++17. Mỗi công cụ đặt tùy chọn này ở một chỗ khác nhau; Phụ lục E ghi đúng đường dẫn menu.
Vì sao sách vẫn dạy lệnh g++
Người chấm mở thư mục bài của bạn và gõ đúng một lệnh: g++ -std=c++17 *.cpp -o main.exe. Nếu lệnh đó không chạy, bạn mất tới 5 điểm bất kể code đẹp thế nào.
Dev-C++, Code::Blocks và CLion đều dùng GCC bên trong, nên nút Build của chúng thực chất là lệnh đó. Visual Studio dùng MSVC — khác, nhưng code của môn này chạy được ở cả hai.
Vì vậy sách viết lệnh g++ ở mọi chỗ: nó là mẫu số chung, và là thứ bạn gặp trong đề thi. Nếu công cụ của bạn có nút Build, hãy đọc “gõ lệnh g++” thành “bấm Build & Run”.
1.2 Chương trình đầu tiên — từng bước từ thư mục trống
Làm theo đúng thứ tự này. Đừng chép cả cụm rồi dán — gõ từng bước và chạy sau mỗi bước, vì mục đích của bài này là quen tay chứ không phải có được file kết quả.
Nếu bạn dùng IDE (Dev-C++, Code::Blocks, Visual Studio, CLion)
Chỉ hai chỗ khác: Bước 1 — thay vì mkdir thì tạo project mới theo Phụ lục E; và Bước 6–7 — thay vì gõ lệnh g++ thì bấm Build & Run.
Mọi bước còn lại giống nhau hoàn toàn — kể cả Bước 8, cố ý làm sai để quen mặt các loại lỗi, vì IDE nào cũng in đúng những thông báo đó trong khung Build log của nó.
Bước 1. Tạo thư mục làm việc
sh
mkdir C:\OOP\thu1
cd C:\OOP\thu1Từ giờ mọi lệnh đều gõ trong cửa sổ đang đứng ở thư mục này. Gõ dir để xem thư mục đang có gì — hiện tại là trống.
Bước 2. Tạo file Document.h và gõ phần khai báo
Mở trình soạn thảo, tạo file mới, lưu vào C:\OOP\thu1\Document.h trước khi gõ (lưu trước để có tô màu). Rồi gõ:
cpp
#ifndef DOCUMENT_H
#define DOCUMENT_H
#include <string>
class Document {
public:
std::string id;
std::string title;
int year;
Document(const std::string& id, const std::string& title, int year);
void display() const;
};
#endifLưu lại. Chú ý dấu ; sau dấu } đóng lớp — thiếu nó là lỗi phổ biến nhất của cả môn học.
Bước 3. Kiểm tra cú pháp ngay, đừng đợi viết xong hết
sh
g++ -std=c++17 -fsyntax-only Document.hNếu đúng, bạn sẽ thấy:
text
(khong in ra gi ca)Không in gì ra nghĩa là không có lỗi — đó là tin tốt. Nếu có lỗi, sửa ngay bây giờ, đừng viết tiếp.
Bước 4. Tạo file Document.cpp và cài đặt hai hàm
cpp
#include "Document.h"
#include <iostream>
using namespace std;
Document::Document(const string& id, const string& title, int year)
: id(id), title(title), year(year) {}
void Document::display() const {
cout << "[TL] " << id << " | " << title << " | " << year << endl;
}Ba điều dễ quên ở bước này: dòng #include "Document.h" ở đầu; tiền tố Document:: trước mỗi tên hàm; và dấu hai chấm mở danh sách khởi tạo.
Bước 5. Tạo file main.cpp
cpp
#include "Document.h"
int main() {
Document d1("TL001", "Lap trinh huong doi tuong", 2019);
Document d2("TL002", "Vat ly dai cuong 1", 2021);
d1.display();
d2.display();
return 0;
}Bước 6. Biên dịch cả ba file bằng một lệnh
sh
g++ -std=c++17 -Wall Document.cpp main.cpp -o main.exeNếu đúng, bạn sẽ thấy:
text
(khong in ra gi ca)Gõ dir để xác nhận đã có file main.exe.
Bước 7. Chạy
text
main.exeBạn sẽ thấy:
text
[TL] TL001 | Lap trinh huong doi tuong | 2019
[TL] TL002 | Vat ly dai cuong 1 | 2021Bước 8. Cố ý làm sai để quen mặt ba loại lỗi
Đây là bước quan trọng nhất của mục này, đừng bỏ. Làm lần lượt ba việc sau, mỗi lần biên dịch lại rồi sửa về như cũ:
- Xoá dấu
;sau}trongDocument.h. - Bỏ
Document.cppkhỏi lệnh biên dịch, chỉ gõg++ -std=c++17 main.cpp -o main.exe. - Xoá dòng
#include "Document.h"trongmain.cpp.
Ghi lại ba thông báo lỗi. Mục 1.6 sẽ giải thích từng cái — nhưng bạn cần gặp chúng trước, rồi đọc giải thích sau, chứ không phải ngược lại.
1.3 Vì sao phải tách .h và .cpp
Chương trình một file main.cpp duy nhất vẫn chạy. Vậy vì sao đề thi bắt tách, và bắt tới 2 điểm?
- Vì một lớp là một đơn vị. Mỗi lớp một cặp file, tìm chỗ nào cũng biết ngay ở đâu.
- Vì biên dịch lại nhanh hơn. Sửa một
.cppthì chỉ file đó phải dịch lại. - Vì người khác chỉ cần đọc
.hlà biết lớp của bạn làm được gì, không phải đọc phần cài đặt. - Và vì đề thi ghi rõ. Đây là lý do thực dụng nhất.
| File | Chứa | Không chứa |
|---|---|---|
Document.h | Khai báo lớp: tên lớp, thuộc tính, nguyên mẫu hàm | Thân hàm |
Document.cpp | Cài đặt từng hàm, viết dạng Document::tenHam | Khai báo lớp |
main.cpp | Hàm main(), nơi tạo đối tượng và chạy thử | Định nghĩa lớp |
Ba quy ước nhỏ, mất điểm nếu quên
1. Trong .h viết std::string, đừng viết using namespace std;. File .h được nhiều file khác nhúng vào, một dòng using trong đó sẽ lan ra cả chương trình. Trong .cpp thì thoải mái.
2. Dấu chấm phẩy sau dấu } đóng lớp.
3. Trong .cpp, mỗi hàm phải có tiền tố TenLop::. Thiếu nó thì bạn vừa tạo một hàm tự do trùng tên, và lớp của bạn vẫn thiếu hàm đó.
1.4 Header guard — ba dòng cứu bạn khỏi một lỗi khó hiểu
Ba dòng #ifndef / #define / #endif nghĩa là: “nếu chưa từng đọc file này thì đọc, còn rồi thì bỏ qua”. Tên ở giữa viết hoa theo tên file, phải khác nhau giữa các file.
Thiếu nó thì chuyện gì xảy ra? Giả sử Book.h có #include "Document.h", và main.cpp nhúng cả hai. Trình biên dịch đọc định nghĩa lớp Document hai lần và báo:
Thông báo lỗi:
text
Document.h:2:7: error: redefinition of 'class Document'
2 | class Document {
| ^~~~~~~~
In file included from main.cpp:1:
Document.h:2:7: note: previous definition of 'class Document'Cách đọc: chữ redefinition cộng với dòng note: previous definition chỉ đúng bệnh — cùng một lớp bị đọc hai lần. Gần như luôn luôn là quên header guard.
1.5 Lệnh biên dịch
sh
g++ -std=c++17 -Wall Document.cpp Reader.cpp main.cpp -o main.exeHoặc gọn hơn, khi tất cả file trong cùng thư mục:
sh
g++ -std=c++17 -Wall *.cpp -o main.exe-std=c++17chọn phiên bản ngôn ngữ. Không có nó, vài cú pháp trong sách này sẽ báo lỗi.-Wallbật cảnh báo. Cảnh báo không chặn chương trình chạy nhưng thường chỉ đúng chỗ sắp hỏng — hãy đọc.-o main.exeđặt tên file kết quả. Thiếu nó thì g++ tạoa.exe.-fsyntax-onlychỉ kiểm tra cú pháp, không tạo file. Dùng khi mới viết xong một.hvà muốn kiểm tra ngay.
Chỉ liệt kê file .cpp, không liệt kê file .h. File .h đã được kéo vào qua #include rồi.
1.6 Bốn loại lỗi và cách phân biệt
Bài giảng tuần 2 đã chia lỗi thành ba loại: lỗi cú pháp (syntax), lỗi lúc chạy (runtime) và lỗi logic. Cách chia đó vẫn đúng nguyên. Từ tuần 4, khi bạn bắt đầu tách nhiều file, xuất hiện thêm loại thứ tư mà tuần 2 chưa gặp: lỗi liên kết.
| Loại | Xảy ra lúc | Trình biên dịch nói gì | Đã học ở |
|---|---|---|---|
| Cú pháp | Đang dịch | error: + số dòng | Bài giảng tuần 2 |
| Liên kết | Sau khi dịch xong | undefined reference | Mục này |
| Lúc chạy | Khi chạy | Không nói gì, chương trình chết | Bài giảng tuần 2 |
| Logic | Khi chạy | Không nói gì, kết quả sai | Bài giảng tuần 2 |
Bài giảng tuần 2 cũng đã liệt kê bốn lỗi cú pháp thường gặp nhất — thiếu ngoặc nhọn, thiếu chấm phẩy, thiếu dấu nháy, gõ sai tên. Ba trong bốn cái đó sẽ xuất hiện ngay dưới đây.
Loại 1 — Lỗi cú pháp: trình biên dịch không hiểu bạn viết gì
Bước 8 việc 1 sinh ra lỗi này:
text
Document.h:8:2: error: expected ';' after class definition
8 | }
| ^
| ;Đọc: file Document.h, dòng 8, cột 2. Chờ đợi một dấu ; sau phần định nghĩa lớp. g++ còn vẽ mũi tên chỉ đúng chỗ và gợi ý ký tự cần thêm.
Bước 8 việc 3 sinh ra lỗi này:
text
main.cpp:3:5: error: 'Document' was not declared in this scope
3 | Document d;
| ^~~~~~~~Đọc: was not declared in this scope = “tôi chưa từng nghe tên này”. Hoặc bạn gõ sai tên lớp, hoặc bạn quên #include "Document.h".
Loại 2 — Lỗi liên kết: hiểu rồi, nhưng không tìm thấy phần thân
Bước 8 việc 2 sinh ra lỗi này:
text
ld.exe: main.o:main.cpp:(.text+0x37): undefined reference to
`Document::display() const'
collect2.exe: error: ld returned 1 exit statusNhận diện lỗi liên kết
Ba dấu hiệu: có chữ undefined reference; có ld.exe hoặc collect2; và không có số dòng trong file của bạn theo kiểu thông thường.
Ba nguyên nhân, theo thứ tự thường gặp: (1) quên file .cpp trong lệnh g++; (2) quên tiền tố TenLop:: khi cài đặt hàm; (3) khai báo trong .h một hàm rồi chưa viết thân nó ở đâu cả.
Nếu thiếu main thì thông báo là undefined reference to `WinMain'.
Loại 3 và 4 — Lỗi lúc chạy và lỗi logic
Hai loại này bài giảng tuần 2 đã nói. Điểm chung: g++ không nói gì cả. Điểm khác nhau: lỗi lúc chạy làm chương trình chết hoặc treo; lỗi logic thì chương trình chạy ngon lành nhưng in ra kết quả sai — và đó là loại nguy hiểm nhất vì bạn có thể nộp bài mà không biết.
Ba nguyên nhân lỗi lúc chạy thường gặp trong môn này:
- Dùng con trỏ
nullptr. Tìm không thấy nhưng vẫn gọi phương thức trên kết quả. - Vòng lặp không dừng. Thường do quên tăng biến đếm. Trong phòng thi thì coi như mất bài.
- Quên
deletehoặcdeletehai lần.
Cách tìm cho cả hai loại: chèn cout << "den day roi" << endl; vào vài chỗ, chạy lại, xem dòng nào in được dòng nào không. Chương trình chết ở khoảng giữa hai dòng in cuối cùng. Với lỗi logic thì in thêm giá trị các biến ra để xem cái nào lệch.
Cách phòng lỗi logic tốt nhất trong phòng thi: đề luôn in sẵn khối “Kết quả kỳ vọng”. So output của bạn với nó từng dòng — đó chính là bộ test mà đề tặng bạn.
1.7 Quy trình sáu bước cho một lớp mới
Học thuộc thứ tự này. Tới tuần thứ tám nó phải thành phản xạ, không phải thứ phải nghĩ:
cpp
1. Tao TenLop.h -> go 3 dong header guard truoc tien
2. #include nhung thu can (<string>, cac .h cua lop cha)
3. Khai bao lop: thuoc tinh, constructor, cac phuong thuc
4. Tao TenLop.cpp -> #include "TenLop.h" o dong dau
5. Cai dat tung ham, nho tien to TenLop::
6. Bien dich NGAY, dung doi viet xong het moi bien dichBước 6 là bước hay bị bỏ nhất
Viết hết 10 file rồi mới biên dịch lần đầu thì bạn nhận về 60 dòng lỗi và không biết bắt đầu từ đâu. Viết xong một lớp là biên dịch — dùng g++ -fsyntax-only TenLop.h nếu chưa có gì để chạy.
Trong 60 phút thi, người biên dịch 15 lần luôn về đích trước người biên dịch 2 lần.
1.8 Bài luyện năm phút, lặp lại mỗi tuần
Đây là bài tập quan trọng nhất trong sách. Nó không dạy khái niệm mới — nó biến thao tác thành phản xạ. Làm mỗi buổi, có bấm giờ, ghi lại thời gian.
Bài drill — từ thư mục trống
1. Tạo một lớp bất kỳ có 2 thuộc tính private.
2. Một constructor đầy đủ, dùng danh sách khởi tạo.
3. Một getter có const.
4. Tách .h / .cpp đúng quy ước, có header guard.
5. Một main.cpp tạo đối tượng và in ra.
6. Biên dịch một lệnh, chạy được.
Mục tiêu: tuần 3 dưới 20 phút → tuần 8 dưới 4 phút.
Ghi lại thời gian từng tuần vào một file text. Con số đó là thước đo trung thực nhất cho biết bạn đã sẵn sàng thi hay chưa — trung thực hơn cảm giác “mình hiểu bài rồi”.
1.9 Đặt tên file và nộp bài
Phần này không có gì để hiểu, chỉ có gì để nhớ. Nhưng nó là 2 điểm, và mỗi năm đều có sinh viên mất trắng vì nộp sai.
- Tên file đúng chính tả và đúng chữ hoa chữ thường như đề yêu cầu:
Document.hchứ không phảidocument.h. - Một lớp một cặp file. Không gộp ba lớp vào một
.h. - Không bao giờ
#include "Document.cpp". - Không nộp file
.exevà thư mục.vscode. - Nén thành
<MSSV>_<Ho ten khong dau>.zip, đặt vào nơi đề chỉ định (thường làC:\OOP).
Một phút cuối giờ đáng giá mấy điểm
Trước khi nộp, mở một cửa sổ dòng lệnh mới, vào đúng thư mục bài làm và gõ lại g++ -std=c++17 *.cpp -o main.exe một lần nữa. Nếu nó không chạy trên máy bạn thì nó cũng sẽ không chạy trên máy người chấm.
Tự kiểm tra
Bấm giờ. Đóng sách lại. Từ thư mục trống:
Bấm giờ00:00Chưa bắt đầu
- Viết lớp
Readergồmid,fullName(đềuprivate), một constructor đầy đủ, mộtgetFullName() constvà mộtdisplay(). - Tách
Reader.h/Reader.cpp/main.cpp. - Biên dịch bằng một lệnh, chạy được và in ra thông tin một bạn đọc.
- Cố ý xoá file
Reader.cppkhỏi lệnh biên dịch. Ghi lại thông báo lỗi và gọi tên loại lỗi đó. - Cố ý xoá ba dòng header guard trong
Reader.hrồi#includenó hai lần trongmain.cpp. Ghi lại thông báo lỗi.
Đạt yêu cầu: dưới 15 phút ở lần đầu. Nếu quá 25 phút, làm lại bài này trước khi sang chương 2 — mọi chương sau đều đứng trên nó.