رفتن به محتوا

کلاس‌ها، ساختارها و کپسوله‌سازی

درک تفاوت بین struct و class، تعیین‌کننده‌های دسترسی، کپسوله‌سازی، توابع عضو const، اعضای استاتیک و مقداردهی اولیه تجمعی مدرن.

کلاس (Class) چیست؟

یک کلاس در ‎C++‎ یک نوع تعریف‌شده توسط کاربر است که داده‌ها (متغیرهای عضو) و عملیات روی آن داده‌ها (توابع عضو) را در یک واحد واحد جمع‌آوری می‌کند. این جمع‌آوری کپسوله‌سازی (Encapsulation) نامیده می‌شود — یکی از چهار رکن OOP.

نکته کلیدی این است که یک کلاس یک ناوردا (invariant) تعریف می‌کند: مجموعه‌ای از قوانین که همیشه باید درباره وضعیت داخلی آن صادق باشند. برای مثال، یک کلاس Date ممکن است نیاز داشته باشد که ماه همیشه بین ۱ و ۱۲ باشد. کپسوله‌سازی به شما اجازه می‌دهد با کنترل دسترسی به وضعیت داخلی، این ناورداها را اعمال کنید.

struct در مقابل class

در ‎C++‎، struct و class تقریباً یکسان هستند. تنها تفاوت در تعیین‌کننده دسترسی پیش‌فرض است: struct به‌صورت پیش‌فرض public است، در حالی که class به‌صورت پیش‌فرض private است. قرارداد: از struct برای تجمیع داده‌های ساده و از class زمانی که ناورداهایی برای حفظ دارید استفاده کنید.

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

تعیین‌کننده‌های دسترسی و اشاره‌گر this

‎C++‎ سه سطح دسترسی ارائه می‌دهد: public (هر کسی می‌تواند دسترسی پیدا کند)، private (فقط خود کلاس و دوستانش)، و protected (کلاس، دوستانش و کلاس‌های مشتق‌شده). اشاره‌گر this یک اشاره‌گر ضمنی به شیء فعلی است که درون هر تابع عضو غیر استاتیک در دسترس است.

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

اعضای استاتیک و friend

اعضای استاتیک (Static Members) به کلاس تعلق دارند، نه به هیچ نمونه‌ای. از آن‌ها برای وضعیت مشترک یا توابع کمکی استفاده کنید. کلمه کلیدی friend به یک تابع غیر عضو یا کلاس دیگر اجازه دسترسی به اعضای خصوصی را می‌دهد — با احتیاط استفاده کنید، زیرا کپسوله‌سازی را می‌شکند.

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

مقداردهی اولیه تجمعی و مقداردهنده‌های تعیین‌شده (‎C++‎20)

یک تجمیع (Aggregate) یک کلاس یا ساختار بدون سازنده‌های اعلام‌شده توسط کاربر، بدون اعضای داده غیر استاتیک خصوصی/محافظت‌شده، بدون توابع مجازی و بدون کلاس‌های پایه مجازی است. ‎C++‎20 مقداردهنده‌های تعیین‌شده (Designated Initializers) را اضافه می‌کند که به شما اجازه می‌دهد اعضایی که مقداردهی می‌کنید را نام‌گذاری کنید.

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;
}
بهترین شیوه

اعضای داده را به‌صورت پیش‌فرض خصوصی (private) کنید و فقط آنچه نیاز است را از طریق یک رابط عمومی حداقلی ارائه دهید. اگر کلاس هیچ ناوردایی برای حفظ ندارد (فقط یک بسته‌ای از داده‌ها است)، به جای آن از struct با اعضای عمومی استفاده کنید. برای هر فیلد getter و setter اضافه نکنید — این هدف کپسوله‌سازی را از بین می‌برد. در عوض، عملیات معنادار مانند deposit() و withdraw() را به جای set_balance() ارائه دهید.

دام

اگر یک تابع عضو وضعیت شیء را تغییر نمی‌دهد، آن را const علامت‌گذاری کنید. بدون const، نمی‌توانید تابع را روی یک شیء const یا از طریق یک ارجاع const فراخوانی کنید — و این هنگام ارسال اشیاء با const& همیشه اتفاق می‌افتد. یک const جا افتاده روی یک تابع دسترسی، منبع رایج خطاهای کامپایل در پایگاه‌های کد بزرگ‌تر است.

نکات کلیدی
  • struct و class فقط در دسترسی پیش‌فرض تفاوت دارند: struct عمومی و class خصوصی است
  • کپسوله‌سازی ناورداهای کلاس را محافظت می‌کند — از داده‌های private با یک رابط عمومی معنادار استفاده کنید
  • توابع عضو غیرتغییردهنده را const علامت‌گذاری کنید تا با ارجاعات const کار کنند
  • از inline static (در ‎C++‎17) برای اعضای استاتیک تعریف‌شده در هدرها استفاده کنید
  • مقداردهنده‌های تعیین‌شده ‎C++‎20 مقداردهی اولیه تجمعی را خوانا و خودمستند می‌کنند
  • friend باید با احتیاط استفاده شود — معمولاً برای سربارگذاری عملگرهایی که به دسترسی خصوصی نیاز دارند

آزمون — دانش خود را بسنجید

(15 امتیاز)

1. تنها تفاوت بین `struct` و `class` در ‎C++‎ چیست؟

2. چرا باید توابع عضو دسترسی را `const` علامت‌گذاری کنید؟

3. `inline static int count_ = 0;` در ‎C++‎17 چه کاری انجام می‌دهد؟