+2

[C++ OOP Thực Chiến] Bài 1: "Khởi động" lại tư duy C++ & Giải mã vũ khí std::vector

Chào các bạn, lại là mình đây!

Nếu các bạn đang đọc bài viết này, có lẽ các bạn đã từng code C/C++, từng "trầy trật" với con trỏ, hoặc đang muốn xây dựng một nền tảng tư duy Lập trình Hướng đối tượng (OOP) thật vững chắc để sau này dễ dàng scale lên các ngôn ngữ, hệ thống phức tạp hơn.

Nhiều trường đại học dạy OOP C++ khá hàn lâm. Trong series "OOP C++ Thực Chiến từ Cơ Bản đến Nâng Cao" này, mình sẽ chia sẻ lại dưới góc nhìn của một người đi làm: code sao cho clean, tối ưu memory, và tại sao các tính năng đó lại tồn tại.

Trước khi ném mình vào thế giới của Object và Class, chúng ta cần "làm nóng" lại động cơ với một vài key-point của C++ hiện đại, và đặc biệt là ôn lại một cấu trúc dữ liệu mà bạn sẽ dùng nhiều nhất trong suốt series này: std::vector.

1. Khởi động nhanh: C++ có gì khác biệt?

Nói ngắn gọn, C++ cho bạn sức mạnh của C (can thiệp sâu vào memory, hiệu năng cực cao) nhưng lại cung cấp thêm các công cụ để tổ chức code lớn (OOP, STL).

Khi ôn tập C++ để chuẩn bị học OOP, bạn chỉ cần nhớ kỹ 2 "vũ khí" cơ bản nhất:

  • Tham chiếu (Reference &): Truyền dữ liệu vào hàm mà không cần copy (tiết kiệm RAM) và cú pháp lại sạch sẽ hơn con trỏ (Pointer *).
  • Standard Template Library (STL): Đừng tự build lại bánh xe. C++ đã cung cấp sẵn các cấu trúc dữ liệu cực ngon, và ngôi sao sáng nhất chính là Vector.

2. std::vector - Hơn cả một mảng động

Hồi mới học C, chắc hẳn bạn rất ám ảnh với việc cấp phát mảng động malloc hay new[], rồi phải nhớ free hay delete[] để tránh memory leak.

Với C++ hiện đại, chúng ta có std::vector. Nó là một mảng động thông minh, tự động quản lý bộ nhớ, tự động phình to ra khi thiếu chỗ. Nhưng dưới góc nhìn của một kỹ sư, nó hoạt động thế nào?

Bí mật nằm ở Size (số phần tử đang có) và Capacity (sức chứa tối đa hiện tại của vùng nhớ được cấp phát). Khi bạn push_back một phần tử mà Size vượt quá Capacity, Vector sẽ làm một việc khá "tốn sức":

1.Tìm một vùng nhớ mới to gấp đôi (hoặc gấp rưỡi) vùng nhớ cũ. 2. Copy toàn bộ data từ nhà cũ sang nhà mới. 3. Giải phóng vùng nhớ cũ.

Hiểu được điều này giúp bạn code C++ tối ưu hơn rất nhiều (ví dụ: dùng reserve() nếu biết trước số lượng phần tử để tránh việc vector phải chuyển nhà liên tục).

3. Code Demo: Vector trong thực chiến

Hãy xem thử một đoạn code nho nhỏ để thấy sự bá đạo và cách vận hành của Vector:

#include <iostream>
#include <vector>

using namespace std;

// Dùng tham chiếu (&) để không bị copy toàn bộ vector khi truyền vào hàm
void printVectorStatus(const vector<int>& v, const string& name) {
    cout << "[" << name << "] Size: " << v.size() 
         << " | Capacity: " << v.capacity() << "\n";
}

int main() {
    // Khởi tạo một vector rỗng
    vector<int> numbers;
    printVectorStatus(numbers, "Initial");

    // Thêm phần tử và quan sát sự bùng nổ của Capacity
    for (int i = 1; i <= 5; i++) {
        numbers.push_back(i * 10);
        cout << "Added " << i * 10 << " -> ";
        printVectorStatus(numbers, "Adding");
    }

    // Duyệt qua vector bằng range-based for loop (C++11) - Cực kỳ clean!
    cout << "Cac phan tu trong vector: ";
    for (int num : numbers) {
        cout << num << " ";
    }
    cout << "\n";

    // Xóa phần tử cuối cùng
    numbers.pop_back();
    printVectorStatus(numbers, "After pop");

    return 0;
}

Kết quả khi chạy đoạn code trên: Bạn sẽ thấy Capacity tăng theo cấp số (0 -> 1 -> 2 -> 4 -> 8...) thay vì tăng từng đơn vị một. Đây chính là nghệ thuật trade-off giữa Memory và Performance trong thiết kế hệ thống!

Tạm kết & Gợi mở

Chúng ta vừa khởi động lại với cú pháp cơ bản và nắm được "linh hồn" của mảng động vector trong C++. Đến đây, bạn đã có đủ công cụ để lưu trữ hàng ngàn biến số khác nhau.

Nhưng hãy tưởng tượng, nếu bạn đang viết một hệ thống quản lý Sinh viên, hoặc một game có hàng trăm Quái vật. Việc lưu trữ bằng hàng tá vector rời rạc (một vector lưu tên, một vector lưu tuổi, một vector lưu điểm...) sẽ biến mã nguồn của bạn thành một "nồi lẩu thập cẩm" không thể bảo trì nổi.

Chúng ta cần một cách để "gói" các dữ liệu và hành vi liên quan chặt chẽ lại với nhau thành một thực thể duy nhất.

Đó chính là khởi nguồn của OOP. Hẹn gặp lại các bạn ở Bài 2: Đối tượng (Object) và Lớp (Class) là gì? Tại sao code đang chạy ngon lại đẻ ra Class làm gì cho phức tạp? Đừng quên Upvote và Follow series để nhận thông báo bài mới nhất nhé! Chúc các bạn code vui!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí