本文系统讲解 C++ 类(Class)的语法、特性与最佳实践,并提供可运行示例与构建指令。面向 C++17/20 读者,重点涵盖封装、构造/析构、拷贝/移动、继承与多态、静态与友元、运算符重载、模板与现代特性,以及内存管理与异常安全。
目录
- 类基础与语法
- 构造与析构;拷贝控制与移动语义
- 继承与多态
- 静态成员与友元
- 运算符重载
- 模板类与 CRTP(概览)
- 现代 C++ 特性要点
- 内存管理与异常安全(RAII、智能指针、规则)
- 可运行示例与构建
- 最佳实践与常见陷阱
类基础与语法
- 概念:类是用户自定义类型,聚合数据与行为,实现封装与抽象;对象是类的实例。
- 访问控制:
public(对外接口)、protected(对子类)、private(内部实现)。struct默认public,class默认private。 - 成员:数据成员、成员函数、静态成员、类型别名、嵌套类型。
- 作用域解析:用
ClassName::member指定成员或静态成员作用域。 this指针:非静态成员函数内指向当前对象。
示例:
class Point {
public:
Point(int x, int y) : x_(x), y_(y) {}
int x() const { return x_; }
int y() const { return y_; }
void move(int dx, int dy) { x_ += dx; y_ += dy; }
private:
int x_;
int y_;
};
构造与析构;拷贝控制与移动语义
- 构造函数:默认、有参、委托、聚合初始化(POD)。优先使用成员初始化列表,尤其是
const/引用成员。 - 析构函数:负责资源清理;多态基类应有虚析构。
=default/=delete:显式请求或禁止编译器合成特殊成员函数。- 拷贝构造/赋值:深拷贝 vs 浅拷贝;明确所有权策略。
- 移动构造/赋值:从临时或源对象转移资源,提升性能;常标注
noexcept。 - 规则:
- Rule of Three:自定义析构通常也要自定义拷贝构造与拷贝赋值。
- Rule of Five:再加移动构造与移动赋值。
- Rule of Zero:组合 + RAII(
std::vector/std::unique_ptr等)避免自定义上述函数。
资源句柄示例:
class File {
public:
explicit File(const std::string& path) : path_(path), fd_(open(path.c_str(), O_RDONLY)) {}
~File() { if (fd_ >= 0) close(fd_); }
File() = delete; // 禁止无参构造
File(const File&) = delete; // 禁止拷贝
File& operator=(const File&) = delete;
File(File&& other) noexcept : path_(std::move(other.path_)), fd_(other.fd_) { other.fd_ = -1; }
File& operator=(File&& other) noexcept {
if (this != &other) {
if (fd_ >= 0) close(fd_);
path_ = std::move(other.path_);
fd_ = other.fd_;
other.fd_ = -1;
}
return *this;
}
private:
std::string path_;
int fd_{-1};
};
继承与多态
- 继承:
class Derived : public Base {};public继承保持对外接口语义。 - 虚函数:启用动态绑定,通过基类指针/引用调用派生实现。
- 纯虚函数:接口类(抽象类),
virtual void f() = 0;。 override/final:防止签名不匹配或继续重写。- 虚析构:抽象或多态基类必须
virtual ~Base() = default;。
示例:
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0;
};
struct Circle : Shape {
explicit Circle(double r) : r(r) {}
double area() const override { return 3.14159265358979323846 * r * r; }
double r;
};
静态成员与友元
- 静态成员变量/函数:属于类本身而非对象;用于计数、工厂、共享配置。
- 类内常量:
static constexpr提供编译期常量。 - 友元(
friend):授予访问私有成员的权限;谨慎使用以免破坏封装。
示例:
class Counter {
public:
static int next() { return ++count_; }
private:
static int count_;
};
int Counter::count_ = 0;
运算符重载
- 成员 vs 非成员:一元或修改左操作数的通常成员;对称二元(如
operator+)常用非成员以支持隐式转换与对称性。 - 一致性:语义应与内置类型一致;遵循无惊讶原则。
- 比较与三路:C++20
operator<=>简化比较,配合= default自动生成。
示例:
class Vec2 {
public:
double x{}, y{};
Vec2(double x, double y) : x(x), y(y) {}
Vec2& operator+=(const Vec2& rhs) { x += rhs.x; y += rhs.y; return *this; }
friend Vec2 operator+(Vec2 lhs, const Vec2& rhs) { lhs += rhs; return lhs; }
auto operator<=>(const Vec2&) const = default;
};
模板类与 CRTP(概览)
- 类模板:通过类型参数化构建通用类。
- 特化:偏特化/全特化为特定类型提供实现。
- CRTP:编译期“多态”,以模板实现静态分发。
- 概念(C++20):为模板参数添加契约,提升错误信息与可读性。
示例:
template <typename T>
class Box {
public:
explicit Box(T value) : value_(std::move(value)) {}
const T& get() const { return value_; }
private:
T value_;
};
现代 C++ 特性要点
explicit:避免意外隐式转换(单参构造尤需)。override/final:明确重写关系,防止静默失败。noexcept:声明不抛异常,影响优化与异常安全。=default/=delete:控制特殊成员函数的合成与禁用。[[nodiscard]]:防止忽略关键返回值。
内存管理与异常安全
- RAII:以对象生命周期管理资源;构造获取、析构释放。
- 智能指针:
std::unique_ptr(独占)、std::shared_ptr(共享)、std::weak_ptr(观测)。 - 异常安全:提供强保证/基本保证;优先使用不抛异常的移动;拷贝-交换习惯用法。
- 拷贝消除与 RVO:鼓励以值返回,减少不必要拷贝。
可运行示例与构建
文件结构建议:
include/geometry/vec2.hpp
src/vec2.cpp
main.cpp
include/geometry/vec2.hpp
#pragma once
#include
<compare>
namespace geometry {
class Vec2 {
public:
Vec2() = default;
Vec2(double x, double y);
double x() const;
double y() const;
void move(double dx, double dy);
Vec2& operator+=(const Vec2& rhs);
friend Vec2 operator+(Vec2 lhs, const Vec2& rhs);
auto operator<=>(const Vec2&) const = default;
private:
double x_{}, y_{};
};
} // namespace geometry
src/vec2.cpp
#include "geometry/vec2.hpp"
namespace geometry {
Vec2::Vec2(double x, double y) : x_(x), y_(y) {}
double Vec2::x() const { return x_; }
double Vec2::y() const { return y_; }
void Vec2::move(double dx, double dy) { x_ += dx; y_ += dy; }
Vec2& Vec2::operator+=(const Vec2& rhs) { x_ += rhs.x_; y_ += rhs.y_; return *this; }
Vec2 operator+(Vec2 lhs, const Vec2& rhs) { lhs += rhs; return lhs; }
} // namespace geometry
main.cpp
#include
<iostream>
#include "geometry/vec2.hpp"
int main() {
geometry::Vec2 a(1.0, 2.0), b(3.0, 4.0);
auto c = a + b;
c.move(-1.0, 0.5);
std::cout << c.x() << "," << c.y() << "\n";
return 0;
}
构建与运行(macOS zsh,使用 clang++ 或 g++):
mkdir -p build
clang++ -std=gnu++20 -Iinclude src/vec2.cpp main.cpp -o build/app
./build/app
最佳实践与常见陷阱
- 封装边界:数据成员尽量
private,最小化对外接口。 - 显式语义:使用
explicit、override、final、noexcept、[[nodiscard]]。 - 组合优于继承:继承仅用于明确的 is-a 关系抽象。
- 倾向 Rule of Zero:资源交由标准容器/智能指针管理。
- 头/源分离:减少编译耦合与 ODR 问题。
- 避免裸资源:不直接管理裸指针/文件句柄,使用 RAII 包装。
常见陷阱:
- 对象切片:以值方式将派生赋给基类对象丢失派生部分;使用引用/指针。
- 隐式生成:编译器合成默认/拷贝/赋值/移动/析构;用
=delete/=default明确意图。 - 虚析构缺失:通过基类指针删除派生对象导致未定义行为;务必
virtual ~Base()。 - 初始化顺序:成员初始化按声明顺序而非列表顺序;依赖成员调整声明顺序。
- 悬挂引用:返回局部对象引用/指针会悬空;返回值或智能指针。
- 异常安全漏洞:赋值中先释放后分配失败导致资源丢失;使用拷贝-交换或强保证策略。
正文完


