C++ 类与对象
类(class)是 C++ 面向对象编程(OOP) 的核心。简单说,类是一种自定义类型,把"数据"(成员变量)和"操作数据的方法"(成员函数)打包到一起。这一章我们看类的基础:定义、构造析构、访问控制、this 指针、运算符重载。
一、为什么需要类?
在 C 里你写一个"学生",要这么干:
- 定义一个
struct Student,里面放char name[50]和int age。 - 定义函数
void student_introduce(Student* s) - 数据和操作是分离的,容易写错。
类的封装(encapsulation)把数据和操作绑在一起,且能控制外部访问权限。这让大型程序的复杂度可控。
二、定义第一个类
#include <iostream>
#include <string>
using namespace std;
// class 定义一个"类"(自定义类型)
class Student {
private: // 私有:外部不能直接访问(封装)
string name;
int age;
public: // 公有:外部可以访问
// 成员函数(方法)
void introduce() {
cout << "我是 " << name << ", " << age << " 岁" << endl;
}
// setter(通过公有方法控制对私有数据的访问)
void setAge(int a) {
if (a < 0 || a > 150) {
cout << "年龄无效!" << endl;
return;
}
age = a;
}
void setName(const string& n) {
name = n;
}
};
int main() {
Student s; // 创建对象(实例化)
s.setName("Alice");
s.setAge(20);
s.introduce(); // 我是 Alice, 20 岁
// s.age = 999; // 编译错误:age 是 private
return 0;
}几个关键概念:
- private(私有):只有类自己的成员函数能访问。外部代码不能直接读写。这是封装的核心——数据隐藏。
- public(公有):外部可以访问的"接口"。一般暴露方法和构造函数。
- protected(保护):类自己和子类能访问。继承时用到。
- 成员变量(属性):类的"状态"。
- 成员函数(方法):类的"行为"。
三、构造函数
构造函数(constructor)是对象创建时自动调用的特殊函数,用于初始化。名字和类名相同,无返回类型:
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name;
int age;
public:
// 1. 默认构造函数(无参)
Student() {
name = "未知";
age = 0;
cout << "默认构造" << endl;
}
// 2. 带参构造(可以重载)
Student(const string& n, int a) {
name = n;
age = a;
cout << "带参构造: " << name << endl;
}
// 3. 委托构造(C++11):让一个构造函数调另一个
Student(const string& n) : Student(n, 18) {}
// 4. 成员初始化列表(推荐,比在 {} 里赋值更高效)
Student(int a, const string& n) : name(n), age(a) {
cout << "用初始化列表构造" << endl;
}
void show() {
cout << name << ", " << age << endl;
}
};
int main() {
Student s1; // 调默认构造
Student s2("Bob", 20); // 调带参构造
Student s3("Carol"); // 调单参构造
s1.show(); // 未知, 0
s2.show(); // Bob, 20
return 0;
}要点:
- 可以重载:写多个构造函数,参数列表不同。
- 成员初始化列表
: name(n), age(a):比在 里赋值更高效(直接构造,不先默认构造再赋值)。推荐养成习惯。 - 委托构造(C++11):让一个构造函数调另一个,减少重复代码。
- 不写任何构造函数:编译器自动生成默认构造(无参)。一旦你写了任何构造,默认构造就不再自动生成。
四、= default 与 = delete(C++11)
#include <iostream>
using namespace std;
class A {
public:
A() = default; // 显式要求默认构造(C++11)
A(const A&) = delete; // 禁止拷贝(单例/智能指针常用)
A& operator=(const A&) = delete; // 禁止赋值
};
class B {
public:
B(int x) {} // 自定义构造后,编译器不再生成默认构造
// 想要默认构造,需要显式:
// B() = default;
};
int main() {
A a1; // OK
// A a2 = a1; // 编译错误:拷贝构造被 delete
B b(10); // OK
// B b2; // 编译错误:没有默认构造
return 0;
}= default:显式要求编译器生成默认实现(用于默认构造、拷贝构造、析构等)。= delete:禁用某个函数。常用于禁拷贝(单例模式、智能指针 unique_ptr)。- 这是 C++11 控制"特殊成员函数"的现代方式,比 private + 不实现 更清晰。
五、析构函数:清理资源
析构函数(destructor)是对象销毁时自动调用的函数,名字是 ~类名,无参数无返回:
#include <iostream>
using namespace std;
class Buffer {
private:
int* data;
size_t size;
public:
// 构造函数:分配资源
Buffer(size_t n) : size(n) {
data = new int[n]; // 在堆上分配
cout << "分配 " << n << " 个 int" << endl;
}
// 析构函数:对象销毁时自动调用,清理资源
~Buffer() {
delete[] data; // 必须释放,否则内存泄漏!
cout << "释放 " << size << " 个 int" << endl;
}
void fill(int v) {
for (size_t i = 0; i < size; i++) data[i] = v;
}
};
int main() {
{
Buffer buf(10); // 进入作用域:构造
buf.fill(42);
cout << "使用 buf..." << endl;
} // 出作用域:析构(自动调用 ~Buffer)
cout << "buf 已销毁" << endl;
return 0;
}析构的核心用途:释放构造函数申请的资源(堆内存、文件句柄、锁)。这是 C++ 程序员最重要的责任——忘记析构释放 = 内存泄漏。
析构调用时机:
- 局部对象:出作用域时(花括号结束)。
- 全局/static 对象:程序结束时。
- new 创建的对象:用
delete时。
六、this 指针
this 是指向当前对象的指针,在成员函数里隐式可用:
#include <iostream>
using namespace std;
class Box {
private:
double width, height;
public:
Box(double w, double h) : width(w), height(h) {}
// this 是指向当前对象的指针
double area() {
return this->width * this->height; // this->width 等同于 width
}
bool isLargerThan(const Box& other) {
return this->area() > other.area();
}
// this 常见用途 1:参数名和成员名冲突时消歧
void setWidth(double width) {
this->width = width; // this->width 是成员,width 是参数
}
// this 常见用途 2:链式调用(返回 *this)
Box& addWidth(double dw) {
width += dw;
return *this; // 返回当前对象的引用
}
};
int main() {
Box b(3, 4);
cout << b.area() << endl; // 12
// 链式调用:连续调用返回引用的方法
b.addWidth(1).addWidth(2); // width 从 3 -> 4 -> 6
cout << b.area() << endl; // 24
return 0;
}this 的常见用途:
- 消歧:参数名和成员名相同时,
this->width = width。 - 链式调用:成员函数返回
*this引用,让调用者连续调用。 - 自赋值检查:在
operator=里if (this == &rhs) return *this;。 - 返回自身:从函数里
return *this。
七、struct vs class
在 C++ 里,struct 和 class 几乎一样,唯一区别是默认访问权限:
#include <iostream>
#include <string>
using namespace std;
// struct 和 class 几乎一样,唯一区别:默认访问权限
// struct 默认 public,class 默认 private
struct Point { // 默认 public
int x;
int y;
// 不写构造函数,可以"聚合初始化"
};
class BankAccount { // 默认 private
double balance;
public:
BankAccount(double b) : balance(b) {}
double getBalance() { return balance; }
};
int main() {
Point p{3, 4}; // OK:struct 聚合初始化
cout << p.x << ", " << p.y << endl; // 3, 4
BankAccount acc(100);
// acc.balance; // 编译错误:private
cout << acc.getBalance() << endl; // 100
return 0;
}- struct 默认 public:适合"纯数据聚合"(POD),比如 Point、Coordinate。
- class 默认 private:适合"有不变式(invariant)的类",需要封装控制。
- 约定俗成:数据为主用 struct,行为为主用 class。两者都可以有方法、构造析构、继承。
八、运算符重载
C++ 独有的特性:可以给自定义类型定义运算符的含义。比如让两个 Vector 相加用 +、让 cout 能打印自定义类:
#include <iostream>
using namespace std;
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// 重载 + 运算符:成员函数方式
Vector2D operator+(const Vector2D& rhs) const {
return Vector2D(x + rhs.x, y + rhs.y);
}
// 重载 == 运算符
bool operator==(const Vector2D& rhs) const {
return x == rhs.x && y == rhs.y;
}
// 重载 << 让 cout 能打印(必须是非成员函数,通常是 friend)
friend ostream& operator<<(ostream& os, const Vector2D& v);
};
// friend 函数:可以访问类的私有成员
ostream& operator<<(ostream& os, const Vector2D& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
int main() {
Vector2D a(1, 2), b(3, 4);
Vector2D c = a + b; // 调用 operator+
cout << c << endl; // (4, 6)
cout << (a == b) << endl; // 0
return 0;
}运算符重载的要点:
- 语法:用
operator+、operator==等关键字定义函数。 - 可以重载大部分运算符:+ - * / == != < > [] () = 等。
- 不能重载:
::(作用域解析)、.(成员访问)、.*、?:(三元)、sizeof。 - 不要滥用:重载要让运算符的语义"自然"。给
+定义成减法是混淆视听。 - friend 友元:让外部函数访问私有成员。常用于
operator<<(ostream 必须是左操作数)。
九、static 成员
static 成员属于类本身而不是某个对象,所有对象共享一份:
#include <iostream>
using namespace std;
class Counter {
private:
int id;
static int total; // 静态成员:所有对象共享一份
public:
Counter() {
total++;
id = total;
cout << "创建第 " << id << " 个对象" << endl;
}
static int getTotal() { // 静态方法:不依赖对象,通过类名调用
return total;
}
};
// 静态成员必须在类外定义(C++17 才能用 inline 在类内初始化)
int Counter::total = 0;
int main() {
cout << Counter::getTotal() << endl; // 0(通过类名调静态方法)
Counter a, b, c;
cout << "总数: " << Counter::getTotal() << endl; // 3
return 0;
}- 静态成员变量:所有对象共享一份,程序生命周期内存在。
- 静态成员函数:没有 this 指针,不能访问非 static 成员。通过类名调用。
- 典型用途:计数器、配置项、工厂方法的注册表。
- 初始化:必须在类外定义(C++17 可以用
inline在类内初始化)。
小结
这一章你掌握了 C++ 面向对象的基础:类的定义、public/private/protected、构造函数(含初始化列表、= default/delete)、析构函数、this 指针、struct vs class、运算符重载、static 成员。下一篇我们看继承与多态——怎么复用已有的类。
← 上一篇 C++ 引用
下一篇 C++ 继承 →