类、结构体与封装
理解 struct 与 class 的区别、访问说明符、封装、const 成员函数、静态成员以及现代聚合初始化。
什么是类?
C++ 中的类是一种用户自定义类型,它将数据(成员变量)和对数据的操作(成员函数)捆绑为一个单元。这种捆绑称为封装 ── 面向对象编程四大支柱之一。
关键洞察在于,类定义了一个不变量:一组关于其内部状态必须始终为真的规则。例如,一个 Date 类可能要求月份始终在 1 到 12 之间。封装让你通过控制对内部状态的访问来强制执行这些不变量。
struct 与 class
在 C++ 中,struct 和 class 几乎完全相同。唯一的区别是默认访问说明符:struct 默认为 public,而 class 默认为 private。惯例:对纯数据聚合使用 struct,当你有需要维护的不变量时使用 class。
#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 指针是一个隐式指针,指向当前对象,在每个非静态成员函数内部可用。
#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 关键字授予非成员函数或另一个类对私有成员的访问权限 ── 应谨慎使用,因为它会破坏封装。
#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 添加了指定初始化器,让你可以指定正在初始化的成员名称。
#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;` 实现了什么?