第一次接触C++类概念的开发者,往往会产生一个疑问:这和C语言里的结构体有什么区别?我当年学习时也纠结过这个问题。实际上,C++的类可以看作是结构体的超集,它们在内存布局上完全一致,但类提供了更强大的封装和控制能力。
让我们看一个典型的结构体定义:
cpp复制struct Student {
char name[20];
int age;
float score;
void printInfo() {
cout << name << ", " << age << ", " << score << endl;
}
};
这个结构体已经很像类了——它甚至包含了成员函数。在C++中,struct和class的唯一区别就是默认访问权限:struct默认public,class默认private。这种设计体现了C++对C的兼容性考虑。
关键理解:当你的结构体需要开始管理自己的数据(而不仅仅是打包数据),就是考虑升级为类的时候。比如需要控制对某些字段的访问,或者需要确保对象被正确初始化。
构造函数是类最重要的特性之一,它解决了"对象诞生时该做什么"的问题。新手常犯的错误是混淆初始化和赋值:
cpp复制class Point {
int x, y;
public:
// 正确的初始化方式(使用成员初始化列表)
Point(int a, int b) : x(a), y(b) {}
// 错误的"初始化"方式(实际上是先默认构造再赋值)
Point(int a, int b) { x = a; y = b; }
};
对于包含const成员或引用成员的类,初始化列表是唯一的选择。比如:
cpp复制class ConstDemo {
const int value;
int& ref;
public:
ConstDemo(int v, int& r) : value(v), ref(r) {}
// 错误:不能在构造函数体内初始化const/引用成员
};
现代C++还支持委托构造函数(C++11)和聚合初始化(C++17)等新特性:
cpp复制class Employee {
string name;
int id;
public:
Employee() : Employee("", 0) {} // 委托构造
Employee(string n, int i) : name(n), id(i) {}
};
// C++17聚合初始化
struct POD { int x; string s; };
POD p{1, "test"}; // 即使没有构造函数也能初始化
理解拷贝控制是掌握C++类的关键里程碑。完整的拷贝控制包括五个特殊成员函数:
T(const T&)T& operator=(const T&)T(T&&) (C++11)T& operator=(T&&) (C++11)~T()一个管理字符串资源的类典型实现:
cpp复制class String {
char* data;
size_t length;
public:
// 构造函数
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 拷贝构造函数
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// 移动构造函数 (C++11)
String(String&& other) noexcept
: data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
}
// 析构函数
~String() {
delete[] data;
}
// 拷贝赋值运算符
String& operator=(const String& rhs) {
if (this != &rhs) {
delete[] data;
length = rhs.length;
data = new char[length + 1];
strcpy(data, rhs.data);
}
return *this;
}
// 移动赋值运算符 (C++11)
String& operator=(String&& rhs) noexcept {
if (this != &rhs) {
delete[] data;
data = rhs.data;
length = rhs.length;
rhs.data = nullptr;
rhs.length = 0;
}
return *this;
}
};
经验法则:如果你需要自定义析构函数,那么大概率也需要自定义拷贝构造函数和拷贝赋值运算符(这就是著名的"三法则",C++11后发展为"五法则")。
const成员函数是保证对象状态不被修改的关键机制:
cpp复制class BankAccount {
double balance;
public:
double getBalance() const { // 承诺不修改对象状态
return balance;
}
void deposit(double amount) { // 非const函数可以修改对象
balance += amount;
}
};
void printBalance(const BankAccount& acc) {
cout << acc.getBalance(); // 只能调用const成员函数
// acc.deposit(100); // 错误:不能在const对象上调用非const函数
}
静态成员属于类本身而非对象实例。静态成员变量需要在类外定义:
cpp复制class Counter {
static int count; // 声明
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; }
};
int Counter::count = 0; // 定义
// 使用
cout << Counter::getCount(); // 无需对象实例
友元打破了封装,应谨慎使用。典型场景是运算符重载:
cpp复制class Complex {
double real, imag;
public:
friend Complex operator+(const Complex&, const Complex&);
};
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.real + b.real, a.imag + b.imag);
}
嵌套类可以表示专属某个类的类型:
cpp复制class LinkedList {
public:
class Node { // 嵌套类
int data;
Node* next;
friend class LinkedList;
};
Node* head;
public:
// LinkedList方法可以访问Node的私有成员
};
定义在函数内部的类,非常少见但有时有用:
cpp复制void foo() {
class Local {
int x;
public:
Local(int val) : x(val) {}
void show() { cout << x; }
};
Local l(42);
l.show();
}
对内存极度敏感的场景可能需要位域:
cpp复制class CompactDate {
unsigned day : 5; // 5位存储日
unsigned month : 4; // 4位存储月
unsigned year : 12; // 12位存储年
};
cpp复制class ShallowCopy {
int* data;
public:
ShallowCopy(int val) { data = new int(val); }
~ShallowCopy() { delete data; }
// 没有自定义拷贝构造函数和拷贝赋值运算符
};
void problem() {
ShallowCopy a(10);
ShallowCopy b = a; // 浅拷贝!
} // 析构时同一内存被delete两次
cpp复制class Base {
public:
Base() { init(); } // 错误做法
virtual void init() = 0;
};
class Derived : public Base {
public:
void init() override {}
};
// 构造Base时虚表尚未建立,无法正确调用Derived::init()
cpp复制// file1.cpp
int global = getSomething();
// file2.cpp
int something = 42;
int getSomething() { return something; }
// 初始化顺序不确定,global可能得到错误值
显式控制特殊成员函数的生成:
cpp复制class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
更安全的继承控制:
cpp复制class Base {
public:
virtual void foo() const;
virtual void bar() final; // 禁止派生类覆盖
};
class Derived : public Base {
public:
void foo() const override; // 显式标记覆盖
// void bar(); // 错误:不能覆盖final函数
};
方便解构类对象:
cpp复制struct Point { int x; int y; };
Point p{1, 2};
auto [x, y] = p; // x=1, y=2
单一职责原则:一个类应该只有一个引起它变化的原因。
优先使用组合而非继承:
cpp复制// 不好:通过继承复用实现
class Stack : public Vector { ... };
// 更好:通过组合复用功能
class Stack {
Vector elements;
...
};
cpp复制class BadDesign {
vector<int> data;
public:
vector<int>& getData() { return data; } // 危险!
};
class Better {
vector<int> data;
public:
const vector<int>& getData() const { return data; } // 稍好
vector<int> copyData() const { return data; } // 最安全
};
cpp复制class FileHandle {
FILE* file;
public:
explicit FileHandle(const char* name) : file(fopen(name, "r")) {}
~FileHandle() { if(file) fclose(file); }
// 禁用拷贝,可能实现移动语义
};
cpp复制Vector createVector() {
Vector v(1000); // 可能直接在调用处构造,避免拷贝
return v; // NRVO
}
Vector v = createVector(); // 可能没有拷贝操作
cpp复制class SmallString {
union {
char local[16]; // 短字符串直接存储
char* heap; // 长字符串用堆
};
size_t length;
bool isLocal() const { return length < sizeof(local); }
};
cpp复制class Debuggable {
int value;
public:
friend ostream& operator<<(ostream& os, const Debuggable& d) {
return os << "Debuggable(" << d.value << ")";
}
};
cpp复制Base* ptr = new Derived;
cout << typeid(*ptr).name(); // 输出Derived的类型名
cpp复制void* operator new(size_t size) {
cout << "Allocating " << size << " bytes\n";
return malloc(size);
}
cpp复制class alignas(16) AlignedData { // C++11对齐指定
double x, y;
};
类模板允许编写类型无关的代码:
cpp复制template <typename T>
class Box {
T content;
public:
Box(const T& t) : content(t) {}
T get() const { return content; }
};
Box<int> intBox(42);
Box<string> strBox("hello");
模板特化允许为特定类型定制实现:
cpp复制template <>
class Box<const char*> {
string content;
public:
Box(const char* s) : content(s) {}
string get() const { return content; }
};
理解类的最好方式就是实现一个简单的智能指针:
cpp复制template <typename T>
class SimplePtr {
T* ptr;
public:
explicit SimplePtr(T* p = nullptr) : ptr(p) {}
~SimplePtr() { delete ptr; }
// 禁用拷贝
SimplePtr(const SimplePtr&) = delete;
SimplePtr& operator=(const SimplePtr&) = delete;
// 允许移动
SimplePtr(SimplePtr&& other) : ptr(other.ptr) {
other.ptr = nullptr;
}
SimplePtr& operator=(SimplePtr&& rhs) {
if (this != &rhs) {
delete ptr;
ptr = rhs.ptr;
rhs.ptr = nullptr;
}
return *this;
}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
explicit operator bool() const { return ptr != nullptr; }
};
这个简单的智能指针展示了C++类的核心概念:资源管理、拷贝控制、运算符重载和模板。