Skip to content

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ượngKèm compiler?Hướng dẫn
Dev-C++~60 MBCó — TDM-GCCPhụ lục E.1
Code::Blocks~150 MBCó, nếu tải bản mingw-setupPhụ lục E.2
Visual Studio Community~8 GBCó — MSVCPhụ lục E.3
CLion~1 GBCó — MinGW kèm sẵnPhụ lục E.4
VS Code / trình soạn thảo~120 MBKHÔNG — phải cài MinGW-w64Phụ 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:

  1. Tạo một project chứa nhiều file .h.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.
  2. 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ệ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\thu1

Từ 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;
};

#endif

Lư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.h

Nế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.exe

Nếu đúng, bạn sẽ thấy:

text
(khong in ra gi ca)

dir để xác nhận đã có file main.exe.

Bước 7. Chạy

text
main.exe

Bạn sẽ thấy:

text
[TL] TL001 | Lap trinh huong doi tuong | 2019
[TL] TL002 | Vat ly dai cuong 1 | 2021

Bướ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ũ:

  1. Xoá dấu ; sau } trong Document.h.
  2. Bỏ Document.cpp khỏi lệnh biên dịch, chỉ gõ g++ -std=c++17 main.cpp -o main.exe.
  3. Xoá dòng #include "Document.h" trong main.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 .cpp thì chỉ file đó phải dịch lại.
  • Vì người khác chỉ cần đọc .h là 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.
FileChứaKhông chứa
Document.hKhai báo lớp: tên lớp, thuộc tính, nguyên mẫu hàmThân hàm
Document.cppCài đặt từng hàm, viết dạng Document::tenHamKhai báo lớp
main.cppHà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#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.exe

Hoặ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++17 chọ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.
  • -Wall bậ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ạo a.exe.
  • -fsyntax-only chỉ kiểm tra cú pháp, không tạo file. Dùng khi mới viết xong một .h và 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ạiXảy ra lúcTrình biên dịch nói gìĐã học ở
Cú phápĐang dịcherror: + số dòngBài giảng tuần 2
Liên kếtSau khi dịch xongundefined referenceMục này
Lúc chạyKhi chạyKhông nói gì, chương trình chếtBài giảng tuần 2
LogicKhi chạyKhông nói gì, kết quả saiBà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 status

Nhậ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 delete hoặc delete hai 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 dich

Bướ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.h chứ không phải document.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 .exe và 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
  1. Viết lớp Reader gồm id, fullName (đều private), một constructor đầy đủ, một getFullName() const và một display().
  2. Tách Reader.h / Reader.cpp / main.cpp.
  3. Biên dịch bằng một lệnh, chạy được và in ra thông tin một bạn đọc.
  4. Cố ý xoá file Reader.cpp khỏ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 đó.
  5. Cố ý xoá ba dòng header guard trong Reader.h rồi #include nó hai lần trong main.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ó.

Đọc xong và tự làm hết phần “Tự kiểm tra” rồi hãy đánh dấu.

© 2026 Ngan V.T. Nguyen · Nội dung theo giấy phép CC BY-NC-SA 4.0 · Code ví dụ theo giấy phép MIT · Ảnh của bên thứ ba giữ giấy phép riêng, ghi ở Phụ lục E

Nội dung theo giấy phép CC BY-NC-SA 4.0 · Code ví dụ theo giấy phép MIT · Ảnh của bên thứ ba giữ giấy phép riêng, ghi ở Phụ lục E