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

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

📚 基本概念速读

名称 定义 省流
描述一类对象的数据和行为 对象模板
对象 按类创建出来的具体实例 具体东西
成员变量 类内部保存状态的变量 对象的数据
成员函数 类内部操作对象的函数 对象的行为
封装 把数据和操作收进类里,并控制外部访问 管好内部状态
访问控制 publicprivateprotected 限制访问范围 谁能访问
构造函数 对象创建时自动调用的函数 初始化对象
析构函数 对象销毁时自动调用的函数 清理对象
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 对象都有自己的 nameagescore

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;

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

成员函数

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

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() 里能直接使用 nameagescore

对象职责

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

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

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++ 更推荐用 vectorstring、智能指针等标准库类型管理资源。这里用 newdelete[] 是为了看清析构函数的作用。

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

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,成员变量也叫 namethis->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.datab.data 指向同一块堆内存。两个对象销毁时都会 delete[] 同一个地址,这就是资源类常见风险。

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

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

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

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

⚠️ 常见误区

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

✅ 总结

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

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

Happy Hacking! 🎉