跳至内容

类、结构体与封装

理解 struct 与 class 的区别、访问说明符、封装、const 成员函数、静态成员以及现代聚合初始化。

什么是类?

C++ 中的类是一种用户自定义类型,它将数据(成员变量)和对数据的操作(成员函数)捆绑为一个单元。这种捆绑称为封装 ── 面向对象编程四大支柱之一。

关键洞察在于,类定义了一个不变量:一组关于其内部状态必须始终为真的规则。例如,一个 Date 类可能要求月份始终在 1 到 12 之间。封装让你通过控制对内部状态的访问来强制执行这些不变量。

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

静态成员属于类本身,而不属于任何实例。用它们来存储共享状态或实用工具函数。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)

聚合是一个没有用户声明的构造函数、没有私有/受保护的非静态数据成员、没有虚函数、没有虚基类的类或结构体。C++20 添加了指定初始化器,让你可以指定正在初始化的成员名称。

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 是 public,class 是 private
  • 封装保护类的不变量 ── 使用 private 数据配合有意义的公共接口
  • 将不修改状态的成员函数标记为 const,使其可与 const 引用配合使用
  • 使用 inline static(C++17)在头文件中定义静态成员
  • C++20 指定初始化器使聚合初始化具有可读性和自文档化
  • friend 应谨慎使用 ── 通常用于需要访问私有成员的运算符重载

测验 — 检验你的知识

(15 经验值)

1. C++ 中 `struct` 和 `class` 的唯一区别是什么?

2. 为什么应该将访问器成员函数标记为 `const`?

3. C++17 中 `inline static int count_ = 0;` 实现了什么?