Bài 35.2: Đối tượng, lớp, thuộc tính, phương thức trong C++

Chào mừng trở lại với hành trình chinh phục C++ cùng FullhouseDev! Nếu bạn đã theo dõi đến đây, chắc hẳn bạn đã sẵn sàng để khám phá một trong những mô hình lập trình mạnh mẽ và phổ biến nhất: Lập trình hướng đối tượng (Object-Oriented Programming - OOP). OOP không chỉ là một kỹ thuật, nó là một cách tư duy mới về cách chúng ta cấu trúc và tổ chức code, giúp giải quyết các vấn đề phức tạp một cách hiệu quả và có tổ chức.

Trọng tâm của OOP xoay quanh việc xem xét thế giới lập trình như một tập hợp các đối tượng tương tác với nhau. Để có thể làm việc hiệu quả với OOP trong C++, việc đầu tiên và quan trọng nhất là phải nắm vững bốn khái niệm cốt lõi: Đối tượng, Lớp, Thuộc tínhPhương thức.

Hãy cùng nhau "mổ xẻ" từng khái niệm này để thấy chúng hoạt động như thế nào trong C++ nhé!

1. Lớp (Class) - Bản Thiết Kế Của Vạn Vật

Hãy tưởng tượng bạn là một kiến trúc sư và bạn được yêu cầu thiết kế một khu đô thị với hàng trăm ngôi nhà có cấu trúc tương tự nhau. Bạn sẽ không vẽ từng ngôi nhà một từ đầu. Thay vào đó, bạn sẽ tạo ra một bản thiết kế chi tiết cho một loại nhà mẫu, mô tả rõ ràng: ngôi nhà có bao nhiêu tầng, bao nhiêu phòng, vị trí cửa sổ, loại vật liệu chính, v.v. Bản thiết kế này không phải là ngôi nhà thực, nhưng nó chứa tất cả thông tin cần thiết để xây dựng nên những ngôi nhà thực tế.

Trong lập trình hướng đối tượng, Lớp (Class) đóng vai trò như bản thiết kế, khuôn mẫu hoặc nguyên mẫu cho một kiểu dữ liệu do người dùng định nghĩa. Một lớp định nghĩa cấu trúc dữ liệu (những thông tin mà một "thực thể" sẽ lưu trữ) và hành vi (những hành động mà thực thể đó có thể thực hiện).

Nói một cách khác, lớp là một khái niệm trừu tượng mô tả những đặc điểm chung và những hành động chung của một nhóm các đối tượng có cùng bản chất.

Ví dụ: Lớp XeHoi (Car) sẽ định nghĩa rằng một chiếc xe hơi nói chung có các đặc điểm như màu sắc, hãng sản xuất, số chỗ ngồi, và các hành động như khởi động, chạy, phanh.

Trong C++, bạn định nghĩa một lớp bằng từ khóa class:

class TenCuaLop {
    // Khu vực này chứa định nghĩa các thành viên của lớp
    // Bao gồm thuộc tính (data members) và phương thức (member functions)
}; // Đừng quên dấu chấm phẩy kết thúc định nghĩa lớp!

Giải thích code:

  • class là từ khóa bắt buộc để khai báo một lớp.
  • TenCuaLop là tên mà bạn đặt cho lớp của mình (nên bắt đầu bằng chữ cái in hoa theo quy ước phổ biến).
  • Cặp dấu ngoặc nhọn {} chứa thân lớp, nơi bạn định nghĩa các thành viên.
  • Dấu chấm phẩy ; sau dấu ngoặc nhọn cuối cùng là bắt buộc khi định nghĩa lớp.

2. Đối tượng (Object) - Thực Thể Cụ Thể Từ Bản Thiết Kế

Tiếp tục với ví dụ kiến trúc sư: sau khi có bản thiết kế nhà mẫu, người ta sẽ dựa vào đó để thực sự xây dựng những ngôi nhà trên thực địa. Mỗi ngôi nhà được xây dựng xong là một thực thể độc lập, có vị trí cụ thể trên mặt đất, có thể được sơn màu khác với ngôi nhà bên cạnh, mặc dù chúng đều tuân theo cùng một bản thiết kế.

Trong OOP, Đối tượng (Object) là một thực thể cụ thể, một thể hiện (instance) của một lớp. Khi bạn tạo một đối tượng từ một lớp, bạn đang cấp phát bộ nhớ cho thực thể đó và nó sẽ mang tất cả các thuộc tính và phương thức đã được định nghĩa trong lớp đó.

Nếu lớp XeHoi là bản thiết kế chung, thì "chiếc xe VinFast Fadil màu trắng, đời 2021" là một đối tượng cụ thể của lớp XeHoi. "Chiếc xe Toyota Camry màu đen, đời 2023" cũng là một đối tượng khác của lớp XeHoi. Mỗi chiếc xe này là độc lập, có màu sắc, đời xe riêng và bạn có thể "khởi động" hoặc "chạy" chúng riêng biệt.

Để tạo một đối tượng trong C++, bạn khai báo nó giống như khai báo một biến thông thường, với tên lớp đóng vai trò là kiểu dữ liệu:

TenCuaLop tenDoiTuong; // Tạo một đối tượng có tên tenDoiTuong từ lớp TenCuaLop

// Ví dụ:
XeHoi xeCuaToi; // Tạo một đối tượng XeHoi tên là xeCuaToi
XeHoi xeCuaBan; // Tạo một đối tượng XeHoi khác tên là xeCuaBan

Giải thích code:

  • XeHoi là tên lớp (kiểu dữ liệu).
  • xeCuaToixeCuaBan là tên của hai đối tượng khác nhau thuộc lớp XeHoi. Mỗi đối tượng này tồn tại độc lập trong bộ nhớ.

3. Thuộc tính (Attributes) - Đặc Điểm Riêng Của Mỗi Đối tượng

Một ngôi nhà thực tế được xây từ bản thiết kế sẽ có những đặc điểm riêng của nó: địa chỉ, màu sơn cụ thể, số lượng người ở... dù cấu trúc chung giống bản thiết kế.

Trong OOP, Thuộc tính (Attributes), còn được gọi là biến thành viên (data members), là những biến được khai báo bên trong một lớp. Chúng dùng để lưu trữ dữ liệu trạng thái (state) hoặc đặc điểm riêng của một đối tượng cụ thể. Mỗi đối tượng được tạo ra từ cùng một lớp sẽ có một bản sao riêng của tất cả các thuộc tính đã được định nghĩa trong lớp đó.

Ví dụ với lớp XeHoi, các thuộc tính có thể là mauSac (kiểu string), hangSanXuat (kiểu string), soChoNgoi (kiểu int). Đối tượng xeCuaToi có thể có mauSac = "Trang", trong khi đối tượng xeCuaBanmauSac = "Den".

Chúng ta khai báo thuộc tính bên trong phần định nghĩa lớp. Theo quy tắc đóng gói (encapsulation - sẽ nói sâu hơn sau), thuộc tính thường được đặt ở chế độ riêng tư (private), và được truy cập hoặc thay đổi thông qua các phương thức công khai (public). Tuy nhiên, trong ví dụ đơn giản này để minh họa, chúng ta tạm thời đặt chúng ở chế độ public để dễ truy cập:

#include <string> // Can them thu vien string cho string
#include <iostream> // Can them thu vien iostream cho cout

class XeHoi {
public: // Cac thanh vien duoi day co the truy cap tu ben ngoai lop
    // Thuoc tinh (Attributes)
    string mauSac;
    string hangSanXuat;
    int soChoNgoi;
};

int main() {
    XeHoi xeA; // Tao doi tuong xeA
    xeA.mauSac = "Do"; // Gan gia tri cho thuoc tinh 'mauSac' cua xeA
    xeA.hangSanXuat = "Ferrari";
    xeA.soChoNgoi = 2;

    XeHoi xeB; // Tao doi tuong xeB
    xeB.mauSac = "Xanh"; // Gan gia tri cho thuoc tinh 'mauSac' cua xeB
    xeB.hangSanXuat = "Porsche";
    xeB.soChoNgoi = 4;

    // In ra thong tin cac thuoc tinh cua tung doi tuong
    cout << "Xe A: Mau " << xeA.mauSac << ", Hang " << xeA.hangSanXuat << ", So cho " << xeA.soChoNgoi << endl;
    cout << "Xe B: Mau " << xeB.mauSac << ", Hang " << xeB.hangSanXuat << ", So cho " << xeB.soChoNgoi << endl;

    return 0;
}

Giải thích code:

  • Chúng ta định nghĩa lớp XeHoi với ba thuộc tính: mauSac, hangSanXuat (kiểu string) và soChoNgoi (kiểu int).
  • Trong main, chúng ta tạo hai đối tượng xeAxeB từ lớp XeHoi.
  • Mỗi đối tượng có bộ thuộc tính riêng của nó. Chúng ta dùng toán tử . (chấm) để truy cập và gán/đọc giá trị cho các thuộc tính của từng đối tượng (xeA.mauSac, xeB.mauSac, v.v.).
  • Kết quả in ra cho thấy xeAxeB có các giá trị thuộc tính khác nhau, mặc dù chúng cùng thuộc một lớp.

4. Phương thức (Methods) - Hành Động Mà Đối Tượng Có Thể Thực Hiện

Một ngôi nhà không chỉ có cấu trúc và đặc điểm (số phòng, màu sơn) mà còn có những chức năng: bạn có thể mở cửa, bật đèn, mở vòi nước.

Trong OOP, Phương thức (Methods), hay còn gọi là hàm thành viên (member functions), là những hàm được khai báo bên trong một lớp. Chúng định nghĩa hành vi hoặc các thao tác mà các đối tượng của lớp đó có thể thực hiện. Phương thức thường làm việc với các thuộc tính của đối tượng đó, có thể là đọc giá trị thuộc tính, thay đổi chúng, hoặc thực hiện một hành động dựa trên trạng thái của đối tượng.

Với lớp XeHoi, các phương thức có thể là khoiDong(), chay(tocDo), phanh(). Khi bạn gọi phương thức khoiDong() trên đối tượng xeA, chỉ có xe A khởi động, không ảnh hưởng đến xe B. Phương thức chay(tocDo) có thể làm thay đổi trạng thái (ví dụ: tốc độ hiện tại) của chính đối tượng đó.

Chúng ta khai báo phương thức bên trong phần định nghĩa lớp, thường ở chế độ public để có thể gọi từ bên ngoài:

#include <iostream>
#include <string>

class XeHoi {
public:
    string mauSac;
    string hangSanXuat;
    int soChoNgoi;
    bool dangChay = false; // Them mot thuoc tinh de theo doi trang thai

    // Phuong thuc khoi dong xe
    void khoiDong() {
        if (!dangChay) {
            dangChay = true; // Thay doi trang thai cua doi tuong
            cout << hangSanXuat << " mau " << mauSac << " da khoi dong." << endl;
        } else {
            cout << hangSanXuat << " mau " << mauSac << " da dang chay roi." << endl;
        }
    }

    // Phuong thuc tat may xe
    void tatMay() {
        if (dangChay) {
            dangChay = false; // Thay doi trang thai cua doi tuong
            cout << hangSanXuat << " mau " << mauSac << " da tat may." << endl;
        } else {
            cout << hangSanXuat << " mau " << mauSac << " da tat may san roi." << endl;
        }
    }
};

int main() {
    XeHoi xeC;
    xeC.mauSac = "Den";
    xeC.hangSanXuat = "Audi";
    xeC.soChoNgoi = 5;

    // Su dung cac phuong thuc tren doi tuong xeC
    xeC.khoiDong(); // Goi phuong thuc khoiDong() cua xeC
    xeC.khoiDong(); // Goi lai de xem ket qua
    xeC.tatMay();   // Goi phuong thuc tatMay() cua xeC
    xeC.tatMay();   // Goi lai
    xeC.khoiDong(); // Goi lai
    xeC.tatMay();   // Goi lai lan cuoi

    return 0;
}

Giải thích code:

  • Chúng ta thêm một thuộc tính dangChay (kiểu bool) để theo dõi trạng thái của xe.
  • Định nghĩa hai phương thức khoiDong()tatMay() bên trong lớp XeHoi.
  • Các phương thức này truy cập và thay đổi thuộc tính dangChay của chính đối tượng gọi phương thức.
  • Trong main, chúng ta tạo đối tượng xeC và lần lượt gọi các phương thức khoiDong()tatMay() trên nó bằng toán tử . (chấm).
  • Mỗi lần gọi phương thức sẽ thực hiện hành động được định nghĩa và có thể in ra thông báo hoặc thay đổi trạng thái của xeC.

5. Mối Quan Hệ Giữa Bốn Khái Niệm (Ví Dụ Tổng Hợp)

Để thấy rõ sự kết hợp của cả bốn khái niệm, hãy xem xét một ví dụ đơn giản về lớp SinhVien:

  • Lớp (Class): SinhVien - Bản thiết kế chung cho tất cả sinh viên.
  • Thuộc tính (Attributes): maSinhVien (string), ten (string), diemTrungBinh (double) - Những đặc điểm riêng của mỗi sinh viên.
  • Phương thức (Methods): hienThiThongTin() (in ra thông tin của sinh viên đó), capNhatDiem(diemMoi) (thay đổi điểm trung bình) - Những hành động/thao tác có thể thực hiện trên một sinh viên.
  • Đối tượng (Objects): sinhVienA ("SV001", "Nguyen Van A", 8.5), sinhVienB ("SV002", "Tran Thi B", 9.0) - Hai thực thể sinh viên cụ thể, mỗi thực thể có bộ thuộc tính riêng và có thể thực hiện các phương thức.

Đây là code minh họa:

#include <iostream>
#include <string>
#include <iomanip> // Can cho fixed va setprecision

// Dinh nghia lop SinhVien
class SinhVien {
public:
    // Thuoc tinh
    string maSinhVien;
    string ten;
    double diemTrungBinh;

    // Phuong thuc hien thi thong tin
    void hienThiThongTin() {
        cout << "Ma SV: " << maSinhVien << endl;
        cout << "Ten: " << ten << endl;
        cout << fixed << setprecision(2); // Dinh dang diem 2 chu so thap phan
        cout << "Diem TB: " << diemTrungBinh << endl;
        cout << "---" << endl;
    }

    // Phuong thuc cap nhat diem
    void capNhatDiem(double diemMoi) {
        if (diemMoi >= 0 && diemMoi <= 10) {
            diemTrungBinh = diemMoi;
            cout << "Da cap nhat diem cho sinh vien " << ten << "." << endl;
        } else {
            cout << "Diem khong hop le." << endl;
        }
    }
};

int main() {
    // Tao doi tuong sinhVienA
    SinhVien sinhVienA;
    sinhVienA.maSinhVien = "SV001";
    sinhVienA.ten = "Nguyen Van A";
    sinhVienA.diemTrungBinh = 8.5;

    // Tao doi tuong sinhVienB
    SinhVien sinhVienB;
    sinhVienB.maSinhVien = "SV002";
    sinhVienB.ten = "Tran Thi B";
    sinhVienB.diemTrungBinh = 9.0;

    // Hien thi thong tin ban dau
    cout << "Thong tin sinh vien ban dau:" << endl;
    sinhVienA.hienThiThongTin(); // Goi phuong thuc cua sinhVienA
    sinhVienB.hienThiThongTin(); // Goi phuong thuc cua sinhVienB

    // Cap nhat diem cho sinhVienA
    sinhVienA.capNhatDiem(9.2);

    // Hien thi lai thong tin de kiem tra
    cout << "Thong tin sau khi cap nhat:" << endl;
    sinhVienA.hienThiThongTin();
    sinhVienB.hienThiThongTin(); // Diem cua sinhVienB khong thay doi

    return 0;
}

Giải thích code:

  • Lớp SinhVien được định nghĩa với các thuộc tính maSinhVien, ten, diemTrungBinh.
  • Hai phương thức hienThiThongTin()capNhatDiem() được thêm vào để xử lý dữ liệu của sinh viên.
  • Trong main, chúng ta tạo hai đối tượng sinhVienAsinhVienB.
  • Mỗi đối tượng được gán giá trị thuộc tính riêng.
  • Khi gọi sinhVienA.hienThiThongTin(), phương thức này chỉ sử dụng dữ liệu (maSinhVien, ten, diemTrungBinh) của đối tượng sinhVienA. Tương tự với sinhVienB.
  • Khi gọi sinhVienA.capNhatDiem(9.2), chỉ có thuộc tính diemTrungBinh của đối tượng sinhVienA bị thay đổi. Thuộc tính của sinhVienB vẫn giữ nguyên.

Comments

There are no comments at the moment.