C++类和对象(上):封装与对象的生命周期
面向对象编程(OOP)是 C++ 区别于 C 语言最核心的设计范式。在 C++ 中,类(class) 是构造对象的蓝图,对象(object) 是类的具体实例。这一章我们从最基础的封装开始,逐步深入到对象从创建到销毁的完整生命周期。
一、封装:类的骨架
1.1 为什么需要封装
写 C 程序时,数据和操作数据的函数是分开的。你定义结构体存储数据,然后编写一组函数来操作它。这种方式有一个隐患:调用者可以绕过你的函数,直接修改结构体内部的数据。
封装(Encapsulation)解决的就是这个问题——把数据和对数据的操作捆绑在一起,并对外隐藏实现细节。
1.2 类的定义与基本使用
C++ 中定义类使用 class 关键字,类中的变量称为 成员变量(属性),函数称为 成员函数(方法)。
#include <iostream>
#include <string>
using namespace std;
class Clock {
public:
int hour;
int minute;
int second;
void setTime(int h, int m, int s) {
hour = h;
minute = m;
second = s;
}
void showTime() {
cout << hour << ":" << minute << ":" << second << endl;
}
};
int main() {
Clock c;
c.setTime(10, 30, 45);
c.showTime();
c.hour = 12;
c.showTime();
return 0;
}
1.3 访问权限:public / protected / private
| 限定符 | 类内访问 | 派生类访问 | 类外访问 |
|---|---|---|---|
public | 允许 | 允许 | 允许 |
protected | 允许 | 允许 | 禁止 |
private | 允许 | 禁止 | 禁止 |
class BankAccount {
public:
void deposit(double amount) {
if (amount > 0) balance += amount;
}
double getBalance() { return balance; }
private:
double balance = 0.0;
};
int main() {
BankAccount acc;
acc.deposit(1000);
cout << acc.getBalance() << endl;
return 0;
}
1.4 struct 与 class 的区别
C++ 中 struct 和 class 唯一的区别是默认访问权限。
struct Point {
int x; // 默认 public
int y;
};
class Rect {
int w; // 默认 private
int h;
};
1.5 成员属性私有化实践
将所有成员变量设为 private,通过 public 的 getter/setter 访问,可以在 setter 中加入校验逻辑。
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
void setName(const string& name) { m_name = name; }
string getName() const { return m_name; }
void setAge(int age) {
if (age >= 3 && age <= 100) m_age = age;
else cout << "年龄不合法" << endl;
}
int getAge() const { return m_age; }
void setScore(double s) {
if (s >= 0 && s <= 100) m_score = s;
else cout << "成绩不合法" << endl;
}
double getScore() const { return m_score; }
void show() const {
cout << m_name << "," << m_age << "岁,成绩" << m_score << endl;
}
private:
string m_name;
int m_age = 0;
double m_score = 0.0;
};
int main() {
Student stu;
stu.setName("小明");
stu.setAge(20);
stu.setScore(95.5);
stu.show();
stu.setAge(200);
return 0;
}
二、对象的初始化和清理
对象从创建到销毁有一个完整的生命周期。C++ 通过构造函数和析构函数让这个生命周期变得可控。
2.1 构造函数与析构函数
- 构造函数(Constructor):对象创建时自动调用,用于初始化。
- 析构函数(Destructor):对象销毁时自动调用,用于清理资源。
#include <iostream>
using namespace std;
class Timer {
public:
Timer() {
startTime = 0;
cout << "Timer 构造" << endl;
}
~Timer() {
cout << "Timer 析构" << endl;
}
private:
int startTime;
};
void test() {
Timer t;
}
int main() {
cout << "--- start ---" << endl;
test();
cout << "--- end ---" << endl;
return 0;
}
如果类中没有定义构造和析构,编译器会隐式生成一个空实现。每个对象都有构造和析构,即使你没写。
2.2 构造函数的分类与调用
构造函数分为三类:
- 无参构造(默认构造):
Person() - 有参构造:
Person(int age) - 拷贝构造:
Person(const Person& p)
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person() : name("未命名"), age(0) {
cout << "无参构造" << endl;
}
Person(const string& n, int a) : name(n), age(a) {
cout << "有参构造" << endl;
}
Person(const Person& p) : name(p.name + "(副本)"), age(p.age) {
cout << "拷贝构造" << endl;
}
~Person() { }
void show() const {
cout << name << "," << age << "岁" << endl;
}
private:
string name;
int age;
};
int main() {
Person p1;
Person p2("张三", 25);
Person p3(p2);
Person p4 = {"王五", 28};
p1.show();
p2.show();
p3.show();
return 0;
}
注意:
Person p1();会被编译器解释为函数声明。无参构造必须写为Person p1;。
2.3 拷贝构造的调用时机
值传递、值返回、用已有对象构造新对象时都会触发拷贝构造。
2.4 深拷贝与浅拷贝
浅拷贝只复制指针值,导致两个对象指向同一块堆内存,销毁时 double free。深拷贝重新开辟内存并复制内容,使每个对象独立管理资源。
#include <iostream>
#include <cstring>
using namespace std;
class StringBox {
public:
StringBox(const char* str) {
len = strlen(str);
data = new char[len + 1];
strcpy(data, str);
cout << "构造" << endl;
}
StringBox(const StringBox& s) {
len = s.len;
data = new char[len + 1];
strcpy(data, s.data);
cout << "深拷贝" << endl;
}
~StringBox() { delete[] data; }
private:
char* data;
int len;
};
int main() {
StringBox s1("Hello");
StringBox s2(s1);
return 0;
}
2.5 初始化列表
初始化列表在构造函数体执行之前初始化成员变量。const 成员、引用成员、无默认构造的组合类成员必须使用。
#include <iostream>
#include <string>
using namespace std;
class Engine {
public:
Engine(int hp) : horsepower(hp) { }
private:
int horsepower;
};
class Car {
public:
Car(const string& brand, int hp)
: m_brand(brand)
, m_engine(hp)
, m_year(2024)
{ }
void info() const {
cout << m_brand << "," << m_year << "年款" << endl;
}
private:
string m_brand;
Engine m_engine;
const int m_year;
};
int main() {
Car car("丰田", 200);
car.info();
return 0;
}
三、对象数组与对象指针
3.1 对象数组
Point arr1[3];
Point arr2[3] = { Point(1,2), Point(3,4), Point(5,6) };
Point* arr3 = new Point[3];
delete[] arr3;
3.2 对象指针
Rect r1(4, 5);
Rect* ptr = &r1;
cout << ptr->area() << endl;
Rect* heap = new Rect(3, 6);
delete heap;
四、综合练习:影片租借系统
运用封装、构造/析构、深拷贝和对象数组知识。
#include <iostream>
#include <cstring>
using namespace std;
class Movie {
public:
Movie() : title(nullptr), duration(0), price(0) {}
Movie(const char* t, int d, double p)
: duration(d), price(p) {
title = new char[strlen(t) + 1];
strcpy(title, t);
}
Movie(const Movie& m) : duration(m.duration), price(m.price) {
title = new char[strlen(m.title) + 1];
strcpy(title, m.title);
}
~Movie() { delete[] title; }
void show() const {
cout << "《" << title << "》 " << duration
<< "分钟 " << price << "元/天" << endl;
}
double getPrice() const { return price; }
private:
char* title;
int duration;
double price;
};
class RentalStore {
public:
RentalStore() : count(0) {}
void addMovie(const Movie& m) {
if (count < 10) movies[count++] = m;
}
double calcTotalRent(int days) const {
double total = 0;
for (int i = 0; i < count; ++i)
total += movies[i].getPrice() * days;
return total;
}
void showAll() const {
cout << "\n=== 影片仓库 ===" << endl;
for (int i = 0; i < count; ++i) {
cout << i + 1 << ". ";
movies[i].show();
}
}
private:
Movie movies[10];
int count;
};
int main() {
Movie m1("流浪地球", 125, 3.5);
Movie m2("让子弹飞", 132, 2.8);
Movie m3("千与千寻", 124, 4.0);
RentalStore store;
store.addMovie(m1);
store.addMovie(m2);
store.addMovie(m3);
store.showAll();
cout << "\n租借3天,总价" << store.calcTotalRent(3) << "元" << endl;
return 0;
}
本章小结
| 要点 | 说明 |
|---|---|
| 封装 | 将数据和行为捆绑,通过访问权限控制对外接口 |
| public/protected/private | 三种访问级别的含义与用法 |
| 构造函数 | 无参、有参、拷贝三种构造及调用方式 |
| 深拷贝 vs 浅拷贝 | 指针成员必须深拷贝,否则 double free |
| 初始化列表 | const/引用/无默认构造的组合类必须使用 |
| 对象数组与指针 | 栈上数组、堆上数组、对象指针的基本操作 |