Chuyên đề thực chiến C++

Kỹ năng làm bài thi lập trình C++: File I/O, freopen, Template & Mẹo thi

Bài học giúp học sinh chuyển từ “code chạy trên bàn phím” sang “code sẵn sàng nộp bài thi”: đọc/ghi file, dùng freopen an toàn, tạo template gọn, dùng define đúng cách và tránh các lỗi thường gặp.

Mục tiêu bài học

Hiểu File I/OBiết vì sao đề thi thường dùng .INP.OUT thay vì nhập tay.
Viết được code chuẩnĐọc dữ liệu, xử lý và ghi kết quả ra file bằng C++ và Python.
Dùng freopenBiết mẫu if(fopen(...)) freopen(...) để chạy được cả ở máy cá nhân và OJ.
Có template thiNắm một số define, alias, hằng số và mẹo tránh lỗi khi làm bài thi.

1. Vì sao phải đọc / ghi file?

Trong nhiều kỳ thi lập trình truyền thống, đề bài không yêu cầu nhập từ bàn phím trực tiếp. Thay vào đó:

Input file

BAI1.INP

Chứa dữ liệu đầu vào, ví dụ: hai số, mảng, ma trận, đồ thị.

Output file

BAI1.OUT

Chứa kết quả chương trình cần in ra để hệ thống chấm.

Ví dụ đời sống: Giống như giáo viên đưa đề trong một tệp .INP, học sinh làm bài rồi nộp đáp án vào tệp .OUT. Chương trình chấm chỉ cần so sánh file đáp án.

2. Cách đọc / ghi file cơ bản

Bài toán mẫu: File SUM.INP chứa hai số nguyên a, b. Ghi ra SUM.OUT tổng a + b.

#include 
using namespace std;

int main() {
    ifstream fin("SUM.INP");   // mở file để đọc
    ofstream fout("SUM.OUT");  // mở file để ghi

    long long a, b;
    fin >> a >> b;
    fout << a + b;

    return 0;
}
Lưu ý quan trọng: File .INP.OUT phải nằm cùng thư mục chạy chương trình. Nếu tên file sai một ký tự, chương trình có thể không đọc được dữ liệu.

3. Cách dùng if(fopen(...)) freopen(...) trong C++

Trong OJ hiện đại, chương trình thường đọc từ stdin và in ra stdout. Nhưng khi luyện đề file trên máy, ta lại muốn đọc từ SUM.INP và ghi ra SUM.OUT. Mẫu dưới đây giải quyết cả hai trường hợp.

Cách hiểu: Nếu file SUM.INP tồn tại thì chương trình chuyển cin sang đọc file và chuyển cout sang ghi file. Nếu file không tồn tại, chương trình vẫn dùng nhập/xuất chuẩn như bình thường.
#include 
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // Nếu file SUM.INP tồn tại thì đọc/ghi bằng file.
    // Nếu không tồn tại, chương trình vẫn đọc từ bàn phím / stdin như OJ.
    if (fopen("SUM.INP", "r")) {
        freopen("SUM.INP", "r", stdin);
        freopen("SUM.OUT", "w", stdout);
    }

    long long a, b;
    cin >> a >> b;
    cout << a + b;

    return 0;
}
Khi chạy trên máy cá nhânCó file SUM.INP → tự động đọc file, ghi SUM.OUT.
Khi nộp lên OJKhông có file SUM.INP → vẫn đọc bằng cin, in bằng cout.
Không nên quênPhải đặt đúng tên file theo đề: SUM, BAI1, PRODUCT,...

4. Tương tác nhanh: chọn kiểu đọc/ghi phù hợp

Trình tạo mẫu freopen

Thử dữ liệu

Output sẽ hiện tại đây.

Template tạo ra

5. Một số kỹ thuật define, alias và mẹo thi

Các kỹ thuật này giúp code gọn hơn, nhưng cần dùng có kiểm soát để học sinh không bị rối hoặc tạo lỗi khó tìm.

Kỹ thuậtNên dùng khi nào?Lưu ý tránh nhầm
using ll = long long;Khi bài có số lớn, tổng lớn, tích lớn.Rõ nghĩa hơn #define ll long long.
#define all(x)Khi dùng sort, reverse, lower_bound với vector.Chỉ dùng với container có begin(), end().
const int MODBài yêu cầu lấy dư.Không tự lấy dư nếu đề không yêu cầu.
ios::sync_with_stdio(false); cin.tie(nullptr);Hầu như nên đặt đầu main để nhập/xuất nhanh.Không trộn lung tung cin/cout với scanf/printf khi chưa hiểu rõ.
#define int long longChỉ dành cho người đã có kinh nghiệm.Học sinh mới học không nên dùng, dễ lỗi kiểu dữ liệu và hàm main.
#include 
using namespace std;

using ll = long long;
using pii = pair;
using vi = vector;

#define pb push_back
#define fi first
#define se second
#define all(x) (x).begin(), (x).end()

const int INF = 1e9;
const ll LINF = (ll)4e18;
const int MOD = 1000000007;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    vector a = {3, 1, 5, 2};
    sort(all(a));

    for (int x : a) cout << x << ' ';
    return 0;
}
Quy tắc an toàn: Template càng ngắn càng tốt. Chỉ thêm thứ mình hiểu. Không copy một template quá dài có nhiều macro lạ, vì khi sai sẽ rất khó debug.

6. Các lỗi học sinh hay mắc và cách tránh

  1. Sai tên file: Đề yêu cầu BAI1.INP nhưng code lại mở bai1.inp. Nhiều hệ thống phân biệt chữ hoa/thường.
  2. Quên mở file output: Đọc được input nhưng vẫn in ra màn hình, file .OUT rỗng.
  3. Dùng ifstream nhưng vẫn nhập bằng cin: Nếu đã tạo fin, phải đọc bằng fin >> .... Nếu dùng freopen, có thể đọc bằng cin.
  4. Không kiểm tra dữ liệu: Đọc thiếu số, đọc sai định dạng, nhầm dòng đầu là n hay là dữ liệu.
  5. Lạm dụng macro: Macro quá nhiều làm code ngắn nhưng khó hiểu. Khi học sinh mới học, ưu tiên rõ ràng.

7. Quiz kiểm tra nhanh

Câu 1. Trong freopen("SUM.INP", "r", stdin), chữ r nghĩa là gì?

Câu 2. Muốn cout ghi ra file SUM.OUT, tham số cuối của freopen là gì?

Câu 3. Với học sinh mới học, lựa chọn nào an toàn hơn?

8. Bài tập tự luyện phân loại rõ ràng

Các bài dưới đây giúp học sinh luyện từ đọc/ghi file cơ bản đến viết template thi.

📘 Mức 1 — Đọc/ghi 2 số
  1. SUM.INP: hai số a, b. Ghi SUM.OUT: a+b.
  2. PRODUCT.INP: hai số a, b. Ghi PRODUCT.OUT: a*b.
📗 Mức 2 — Dãy số
  1. MAXNUM.INP: dòng 1 là n, dòng 2 có n số. Ghi số lớn nhất.
  2. EVENCOUNT.INP: đếm số chẵn trong dãy.
📙 Mức 3 — Ma trận
  1. COUNTZERO.INP: ma trận R x C. Ghi số phần tử bằng 0.
  2. ROWSUM.INP: ghi tổng từng hàng của ma trận.
🐉 Mức 4 — Template thi
  1. Viết lại bài SUM bằng mẫu if(fopen(...)) freopen(...).
  2. Tạo template cá nhân có solve(), using ll, fast I/O.
💻 Lời giải mẫu MAXNUM C++ / Python
#include 
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    if (fopen("MAXNUM.INP", "r")) {
        freopen("MAXNUM.INP", "r", stdin);
        freopen("MAXNUM.OUT", "w", stdout);
    }

    int n;
    cin >> n;

    long long x, mx;
    cin >> mx;
    for (int i = 2; i <= n; i++) {
        cin >> x;
        if (x > mx) mx = x;
    }

    cout << mx;
    return 0;
}
💻 Lời giải mẫu COUNTZERO C++ / Python
#include 
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    if (fopen("COUNTZERO.INP", "r")) {
        freopen("COUNTZERO.INP", "r", stdin);
        freopen("COUNTZERO.OUT", "w", stdout);
    }

    int r, c;
    cin >> r >> c;

    int cnt = 0;
    for (int i = 0; i < r; i++) {
        for (int j = 0; j < c; j++) {
            int x;
            cin >> x;
            if (x == 0) cnt++;
        }
    }

    cout << cnt;
    return 0;
}
Ghi nhớ: File I/O giúp chương trình khớp đề thi; freopen giúp linh hoạt khi luyện trên máy và nộp OJ; template tốt là template ngắn, rõ, dễ debug.
➡ Tiếp theo: áp dụng template này vào các bài mảng, chuỗi, hàm và đồ thị.