C/C++ 入门:类与对象

结构体把数据放在一起,类进一步把“数据”和“操作这些数据的函数”放在一起。

📚 基本概念速读

名称 定义 省流
类 描述一类对象的数据和行为 对象模板
对象 按类创建出来的具体实例 具体东西
成员变量 类内部保存状态的变量 对象的数据
成员函数 类内部操作对象的函数 对象的行为
封装 把数据和操作收进类里,并控制外部访问 管好内部状态
访问控制 用 public、private、protected 限制访问范围 谁能访问
构造函数 对象创建时自动调用的函数 初始化对象
析构函数 对象销毁时自动调用的函数 清理对象
this 指针 成员函数里指向当前对象的指针 当前对象自己
拷贝构造 用一个已有对象创建新对象 复制对象

🧩 类与对象的基本模型

类可以理解为一种“自定义类型说明书”:它规定对象里有什么数据,以及能做什么操作。

1
2
3
4
5
6
7
8
9
10
class Student {
public:
string name;
int age;

void sayHello()
{
cout << "hello, I am " << name << endl;
}
};

按这个类创建对象:

1
2
3
4
Student s;
s.name = "Alice";
s.age = 18;
s.sayHello();

可以这样理解:

1
2
3
4
5
6
7
8
9
class Student
|
v
对象 s
+----------------------+
| name = Alice |
| age = 18 |
| sayHello() |
+----------------------+

类和对象的关系:



flowchart LR
    A[class 类定义] --> B[对象 s1]
    A --> C[对象 s2]
    A --> D[对象 s3]
    B --> E[各自保存自己的成员变量]
    C --> E
    D --> E

📦 封装

成员变量

成员变量保存对象自己的状态。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <string>
using namespace std;

class Student {
public:
string name;
int age;
double score;
};

每个 Student 对象都有自己的 name、age 和 score。

1
2
3
4
5
6
7
8
9
Student a;
a.name = "Alice";
a.age = 18;
a.score = 92.5;

Student b;
b.name = "Bob";
b.age = 19;
b.score = 88.0;

a 和 b 是两个不同对象,它们的成员变量互不影响。

成员函数

成员函数是定义在类里的函数,用来操作对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student {
public:
string name;
int age;
double score;

void print()
{
cout << name << " "
<< age << " "
<< score << endl;
}
};

调用成员函数时,要通过对象调用:

1
2
3
4
5
6
Student s;
s.name = "Alice";
s.age = 18;
s.score = 92.5;

s.print();

成员函数里可以直接访问同一个对象的成员变量,所以 print() 里能直接使用 name、age、score。

对象职责

封装的重点不是“把代码塞进类里”,而是让对象对自己的状态负责。

比如分数不应该随便被设置成负数,可以把修改逻辑收进成员函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Student {
private:
double score;

public:
void setScore(double value)
{
if (value >= 0 && value <= 100) {
score = value;
}
}

double getScore()
{
return score;
}
};

这样外部不能随意破坏 score,只能通过 setScore() 修改。

🔐 访问控制

public

public 成员可以在类外部直接访问。

1
2
3
4
5
6
7
8
9
10
11
class Student {
public:
string name;
};

int main()
{
Student s;
s.name = "Alice";
return 0;
}

如果一个成员是对象对外提供的接口,通常放在 public。

private

private 成员只能在类内部访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BankAccount {
private:
double balance;

public:
void deposit(double amount)
{
if (amount > 0) {
balance += amount;
}
}

double getBalance()
{
return balance;
}
};

外部不能直接写:

1
2
BankAccount account;
// account.balance = -100; // 错误:balance 是 private

这就是封装带来的保护:外部只能通过 deposit() 和 getBalance() 使用账户。

protected

protected 先粗略理解为:类外部不能访问,但子类可以访问。

1
2
3
4
class Person {
protected:
string name;
};

初学阶段如果还没学继承,不需要大量使用 protected。先记住三者区别:

访问控制 类内部 类外部 子类
public 可以 可以 可以
private 可以 不可以 不可以直接访问
protected 可以 不可以 可以

大多数入门类会把成员变量设为 private,把稳定的操作函数设为 public。

🏗️ 构造函数

默认构造

构造函数是在对象创建时自动调用的特殊成员函数。它的名字和类名一样,没有返回类型。

1
2
3
4
5
6
7
8
9
10
11
class Student {
public:
string name;
int age;

Student()
{
name = "unknown";
age = 0;
}
};

创建对象时会自动调用:

1
2
3
4
Student s;

cout << s.name << endl; // unknown
cout << s.age << endl; // 0

这种不需要参数的构造函数叫默认构造函数。

带参构造

构造函数也可以带参数,让对象创建时直接拿到初始值。

1
2
3
4
5
6
7
8
9
10
11
class Student {
public:
string name;
int age;

Student(string studentName, int studentAge)
{
name = studentName;
age = studentAge;
}
};

使用方式:

1
2
3
4
Student s("Alice", 18);

cout << s.name << endl; // Alice
cout << s.age << endl; // 18

这比先创建对象再逐个赋值更稳,因为对象一出生就是完整状态。

初始化列表

构造函数更推荐使用初始化列表初始化成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student {
private:
string name;
int age;

public:
Student(string studentName, int studentAge)
: name(studentName), age(studentAge)
{
}

void print()
{
cout << name << " " << age << endl;
}
};

初始化列表写在参数列表和函数体之间:

1
2
3
Student(...) : name(...), age(...)
{
}

它的含义是:在构造函数体执行前,就把成员变量初始化好。

尤其遇到 const 成员或引用成员时,必须用初始化列表:

1
2
3
4
5
6
7
8
9
10
class Config {
private:
const int port;

public:
Config(int value)
: port(value)
{
}
};

初学阶段可以先养成习惯:构造函数优先用初始化列表设置成员变量。

🧹 析构函数

对象生命周期结束

析构函数在对象销毁时自动调用。它的名字是 ~类名(),没有返回类型,也没有参数。

1
2
3
4
5
6
7
8
9
10
11
12
class Logger {
public:
Logger()
{
cout << "create" << endl;
}

~Logger()
{
cout << "destroy" << endl;
}
};

局部对象离开作用域时会被销毁:

1
2
3
4
5
6
int main()
{
Logger log;
cout << "running" << endl;
return 0;
}

输出顺序大致是:

1
2
3
create
running
destroy

释放资源

析构函数常用于释放对象持有的资源,比如堆内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Buffer {
private:
int* data;

public:
Buffer()
{
data = new int[10];
}

~Buffer()
{
delete[] data;
}
};

Buffer 对象创建时申请内存,对象销毁时释放内存。

现代 C++ 更推荐用 vector、string、智能指针等标准库类型管理资源。这里用 new 和 delete[] 是为了看清析构函数的作用。

对象生命周期可以粗略看成:

1
2
3
4
5
6
7
8
9
10
11
12
13
创建对象
|
v
调用构造函数
|
v
对象正常使用
|
v
离开作用域或被 delete
|
v
调用析构函数

👉 this 指针

成员访问

在成员函数内部,this 指向当前对象。

1
2
3
4
5
6
7
8
9
10
class Student {
private:
string name;

public:
void setName(string name)
{
this->name = name;
}
};

这里参数也叫 name,成员变量也叫 name。this->name 明确表示当前对象的成员变量。

如果没有重名,下面两种写法通常等价:

1
2
score = 90.0;
this->score = 90.0;

this 的类型可以先理解成“指向当前对象的指针”。在 Student 的普通成员函数里,它大致像 Student* const this。

链式调用基础

如果成员函数返回当前对象的引用,就可以连续调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Student {
private:
string name;
int age;

public:
Student& setName(string value)
{
name = value;
return *this;
}

Student& setAge(int value)
{
age = value;
return *this;
}

void print()
{
cout << name << " " << age << endl;
}
};

使用方式:

1
2
Student s;
s.setName("Alice").setAge(18).print();

关键是 return *this;:this 是指针,*this 就是当前对象本身。

📋 拷贝构造

对象复制

拷贝构造函数用于“用一个已有对象创建新对象”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student {
public:
string name;
int age;

Student(string studentName, int studentAge)
: name(studentName), age(studentAge)
{
}

Student(const Student& other)
: name(other.name), age(other.age)
{
cout << "copy" << endl;
}
};

触发拷贝构造:

1
2
Student a("Alice", 18);
Student b = a;

b 是用 a 创建出来的新对象。

默认行为

如果你不写拷贝构造函数,编译器通常会生成一个默认版本,把每个成员逐个复制。

1
2
3
4
5
6
7
8
class Point {
public:
int x;
int y;
};

Point a{1, 2};
Point b = a;

这类只有普通值成员的对象,默认拷贝通常没问题。

可以理解为:

1
2
a.x 复制给 b.x
a.y 复制给 b.y

资源类风险

如果对象里保存裸指针,并且析构函数会释放它,默认拷贝就可能出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Buffer {
private:
int* data;

public:
Buffer()
{
data = new int[10];
}

~Buffer()
{
delete[] data;
}
};

如果发生默认拷贝:

1
2
Buffer a;
Buffer b = a; // 默认拷贝只复制指针地址

结果是 a.data 和 b.data 指向同一块堆内存。两个对象销毁时都会 delete[] 同一个地址,这就是资源类常见风险。

1
2
3
a.data ----+
+---- 堆内存
b.data ----+

解决方向有三类,后面学到资源管理时会展开:

做法 说明
自己写正确的拷贝构造 复制资源本身,而不是只复制地址
禁止拷贝 不允许资源对象被复制
使用标准库资源类型 用 vector、string、智能指针减少手动管理

入门阶段先记住:类里有裸指针和析构函数时,要警惕默认拷贝。

⚠️ 常见误区

误区 正解
类只是带函数的结构体 类更强调封装、接口和对象职责
成员变量都应该 public 需要保护内部状态时应放到 private
构造函数有返回类型 构造函数没有返回类型,连 void 也不写
析构函数要手动调用 大多数情况下由对象生命周期自动触发
this 是当前对象本身 this 是指向当前对象的指针,*this 才是对象本身
默认拷贝总是安全 含裸指针资源的类可能出现重复释放等问题

✅ 总结

类把状态和行为封装到一起:构造函数负责初始化,析构函数负责清理,访问控制保护内部状态,this 表示当前对象,拷贝构造决定对象如何被复制。

学完类与对象后,再看 C++ 就不只是“函数处理数据”,而是“对象带着自己的状态和职责参与程序运行”。

Happy Hacking! 🎉