Chuyển đến nội dung

Lớp học, Cấu trúc & Đóng gói

Hiểu sự khác biệt giữa cấu trúc và lớp, bộ xác định truy cập, đóng gói, hàm thành viên const, thành viên tĩnh và khởi tạo tổng hợp hiện đại.

Một lớp học là gì?

class trong C++ là loại do người dùng xác định, gộp dữ liệu (biến thành viên) và các thao tác trên dữ liệu đó (hàm thành viên) vào một đơn vị. Gói này được gọi là đóng gói — một trong bốn trụ cột của OOP.

Thông tin chi tiết quan trọng là một lớp định nghĩa bất biến: một tập hợp các quy tắc phải luôn đúng về trạng thái bên trong của nó. Ví dụ: lớpDatecó thể yêu cầu tháng luôn nằm trong khoảng từ 1 đến 12. Tính năng đóng gói cho phép bạn thực thi các bất biến này bằng cách kiểm soát quyền truy cập vào trạng thái nội bộ.

cấu trúc vs lớp

Trong C++,structvàclassgần như giống hệt nhau. Sự khác biệt duy nhất là công cụ xác định quyền truy cập mặc định:structmặc định làpublic, trong khiclassmặc định làprivate. Quy ước: sử dụngstructđể tổng hợp dữ liệu đơn giản,classkhi bạn cần duy trì các bất biến.

struct_vs_class.cpp
#include <iostream>
#include <string>

// struct: members are public by default
struct Point {
    double x;  // public
    double y;  // public
};

// class: members are private by default
class BankAccount {
    std::string owner;   // private
    double balance;      // private

public:
    BankAccount(const std::string& name, double initial)
        : owner{name}, balance{initial} {}

    double get_balance() const { return balance; }

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
};

int main() {
    Point p{3.0, 4.0};  // aggregate initialization
    std::cout << p.x << ", " << p.y << '\n';

    BankAccount acct{"Alice", 1000.0};
    acct.deposit(500.0);
    std::cout << acct.get_balance() << '\n';  // 1500
    // acct.balance = -999;  // ERROR: private member

    return 0;
}

Bộ chỉ định truy cập & Con trỏ này

C++ cung cấp ba cấp độ truy cập: public (bất kỳ ai cũng có thể truy cập), private (chỉ lớp và bạn bè của nó) và protected (lớp, bạn bè của nó và các lớp dẫn xuất). Con trỏthislà một con trỏ ngầm tới đối tượng hiện tại có sẵn bên trong mọi hàm thành viên không tĩnh.

access_specifiers.cpp
#include <iostream>
#include <string>

class Employee {
private:
    std::string name_;
    int id_;

protected:
    double salary_;  // accessible to derived classes

public:
    Employee(const std::string& name, int id, double salary)
        : name_{name}, id_{id}, salary_{salary} {}

    // const member function: promises not to modify the object
    const std::string& get_name() const { return name_; }
    int get_id() const { return id_; }

    // 'this' pointer usage — return *this for method chaining
    Employee& set_name(const std::string& name) {
        this->name_ = name;  // 'this->' disambiguates
        return *this;
    }

    Employee& set_id(int id) {
        this->id_ = id;
        return *this;
    }

    void print() const {
        std::cout << "Employee{" << name_ << ", #" << id_
                  << ", $" << salary_ << "}\n";
    }
};

int main() {
    Employee e{"Bob", 42, 75000.0};
    e.set_name("Robert").set_id(43);  // method chaining via *this
    e.print();  // Employee{Robert, #43, $75000}
    return 0;
}

Thành viên tĩnh và bạn bè

Thành viên tĩnh thuộc về lớp, không thuộc bất kỳ trường hợp nào. Sử dụng chúng cho các chức năng trạng thái hoặc tiện ích được chia sẻ. Từ khóa friend cấp cho hàm không phải thành viên hoặc quyền truy cập lớp khác cho các thành viên riêng tư — hãy sử dụng một cách tiết kiệm vì nó phá vỡ tính đóng gói.

static_friend.cpp
#include <iostream>

class Widget {
    int value_;

    // C++17: inline static — define in header, no .cpp needed
    inline static int count_ = 0;

public:
    explicit Widget(int v) : value_{v} { ++count_; }
    ~Widget() { --count_; }

    static int get_count() { return count_; }

    // friend function can access private members
    friend std::ostream& operator<<(std::ostream& os, const Widget& w);
};

std::ostream& operator<<(std::ostream& os, const Widget& w) {
    return os << "Widget(" << w.value_ << ')';  // accesses private value_
}

int main() {
    Widget a{10}, b{20};
    std::cout << Widget::get_count() << '\n';  // 2
    std::cout << a << '\n';  // Widget(10)
    {
        Widget c{30};
        std::cout << Widget::get_count() << '\n';  // 3
    }
    std::cout << Widget::get_count() << '\n';  // 2
    return 0;
}

Khởi tạo tổng hợp & Công cụ khởi tạo được chỉ định (C++20)

tổng hợp là một lớp hoặc cấu trúc không có hàm tạo do người dùng khai báo, không có thành viên dữ liệu không tĩnh riêng tư/được bảo vệ, không có hàm ảo và không có lớp cơ sở ảo. C++20 thêm công cụ khởi tạo được chỉ định, cho phép bạn đặt tên cho thành viên nào bạn đang khởi tạo.

designated_init.cpp
#include <iostream>
#include <string>

struct Config {
    std::string host = "localhost";
    int port = 8080;
    bool use_tls = false;
    int max_connections = 100;
};

int main() {
    // Traditional aggregate initialization
    Config c1{"example.com", 443, true, 200};

    // C++20 designated initializers — much more readable
    Config c2{
        .host = "api.example.com",
        .port = 443,
        .use_tls = true
        // .max_connections uses default value 100
    };

    std::cout << c2.host << ':' << c2.port << '\n';
    std::cout << "TLS: " << std::boolalpha << c2.use_tls << '\n';
    std::cout << "Max: " << c2.max_connections << '\n';  // 100
    return 0;
}
Thực hành tốt

Đặt các thành viên dữ liệu riêng tư theo mặc định và chỉ hiển thị những gì cần thiết thông qua giao diện công khai tối thiểu. Nếu một lớp không có bất biến nào để duy trì (nó chỉ là một gói dữ liệu), thay vào đó hãy sử dụng struct với các thành viên công khai. Không thêm getters và setters cho mọi trường - điều đó làm mất đi mục đích đóng gói. Thay vào đó, hãy cung cấp các hoạt động có ý nghĩa nhưdeposit()vàwithdraw()thay vìset_balance().

Lỗi thường gặp

Nếu hàm thành viên không sửa đổi trạng thái của đối tượng, hãy đánh dấu nó làconst. Nếu không cóconst, bạn không thể gọi hàm trên đối tượngconsthoặc thông qua tham chiếuconst— điều này luôn xảy ra khi truyền đối tượng bằngconst&. Việc thiếuconsttrên một trình truy cập là nguyên nhân phổ biến gây ra lỗi biên dịch trong các cơ sở mã lớn hơn.

Điểm chính
  • structvàclasschỉ khác nhau ở quyền truy cập mặc định:structlà công khai,classlà riêng tư
  • Tính năng đóng gói bảo vệ bất biến lớp — sử dụng dữ liệuprivatevới giao diện chung có ý nghĩa
  • Đánh dấu các hàm thành viên không biến đổiconstđể chúng hoạt động với các tham chiếuconst
  • Sử dụnginline static(C++17) cho các thành viên tĩnh được xác định trong tiêu đề
  • Trình khởi tạo được chỉ định C++20 giúp quá trình khởi tạo tổng hợp có thể đọc được và tự ghi lại
  • Nên sử dụngfriendmột cách tiết kiệm - thường dành cho trường hợp quá tải của người vận hành cần quyền truy cập riêng tư

Câu đố — Kiểm tra kiến thức

(15 XP)

1. Sự khác biệt DUY NHẤT giữa`struct`và`class`trong C++ là gì?

2. Tại sao bạn nên đánh dấu các hàm thành viên của trình truy cập là`const`?

3. `inline static int count_ = 0;`đạt được gì trong C++17?