一、类与对象:从结构体到面向对象的跨越
我记得刚学C语言那会儿,写个学生管理系统,得把姓名、学号、成绩全塞进结构体里。然后呢?操作这些数据的函数散落在各个角落,维护起来那叫一个痛苦。后来接触了C++的类,才恍然大悟——原来代码可以这么组织。
说白了,类就是你的自定义数据类型,而对象是这个类型的具体实例。就像「学生」是一个类,「张三」就是一个对象。
1.1 定义一个类
先看一个最简单的例子。我习惯把类的定义放在头文件里,实现放在cpp里,但初学阶段写在一起也没问题。
class Student {
public: // 先别管这个,后面会讲
// 成员变量
string name;
int age;
float score;
// 成员函数
void showInfo() {
cout << "姓名: " << name << ", 年龄: " << age
<< ", 成绩: " << score << endl;
}
}; // 注意这个分号,我见过有人漏掉它编译报错半天
1.2 创建对象
类定义好了,怎么用?创建对象呗。有两种方式:
int main() {
// 栈上创建(推荐,自动管理生命周期)
Student stu1;
stu1.name = "小明";
stu1.age = 18;
stu1.score = 92.5;
stu1.showInfo();
// 堆上创建(需要手动释放)
Student* stu2 = new Student();
stu2->name = "小红";
stu2->showInfo();
delete stu2; // 别忘了!
return 0;
}
我的习惯:能用栈就别用堆。除非对象生命周期需要跨函数传递,或者对象特别大。堆上new出来的对象,忘记delete就是内存泄漏——我曾经在一个服务器项目里排查了三天,最后发现是new了没delete。
二、访问修饰符:public、private、protected
为什么要有访问修饰符?你想想看,你银行卡的余额能随便让别人看吗?不能。类的设计也是这个道理——把该暴露的暴露,该隐藏的隐藏。
2.1 三种修饰符的作用
| 修饰符 | 类内部 | 派生类 | 外部 |
|---|---|---|---|
| public | 可访问 | 可访问 | 可访问 |
| protected | 可访问 | 可访问 | 不可访问 |
| private | 可访问 | 不可访问 | 不可访问 |
嗯,这里要注意:class的默认访问权限是private,而struct是public。这是C++里class和struct唯一的区别。
2.2 实际应用场景
我建议你这样设计类:
- 成员变量一律private——通过public的getter/setter访问
- 对外接口用public——别人怎么用你的类
- 内部辅助函数用private——你自己调用的,别人不需要知道
- protected留给继承用——子类需要但外部不需要的
class BankAccount {
private:
double balance; // 余额必须私有,不能让人随便改
public:
// 公开的接口
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
double getBalance() {
return balance; // 只读,不能直接改
}
protected:
// 子类可能需要访问的
string accountNumber;
};
我曾经踩过的坑:把成员变量设为public,结果项目里到处都在直接修改它。后来要加个校验逻辑,得把所有调用点找出来改一遍,那叫一个酸爽。所以——永远把成员变量设为private,这是血的教训。
三、成员函数:类的行为
成员函数就是类能做的事情。它和普通函数最大的区别是:它知道自己是哪个对象在调用。
3.1 定义成员函数
可以在类内部直接定义,也可以在类外定义。我个人的习惯是:简单的函数写在类里,复杂的写在类外。
class Rectangle {
private:
double width;
double height;
public:
// 类内定义(隐式inline)
void setSize(double w, double h) {
width = w;
height = h;
}
// 类外声明
double getArea();
};
// 类外定义
double Rectangle::getArea() {
return width * height;
}
3.2 this指针
每个成员函数都有一个隐藏参数——this指针。它指向调用该函数的对象本身。
class Person {
private:
string name;
public:
void setName(string name) {
// 这里参数名和成员变量名冲突了
// this->name 指的是成员变量
// name 指的是参数
this->name = name;
}
};
说白了,this就是「我自己」的指针。当参数名和成员变量名相同时,必须用this->来区分。
3.3 const成员函数
如果一个成员函数不会修改对象的状态,我建议你把它声明为const。这既是承诺,也是保护。
class Circle {
private:
double radius;
public:
double getRadius() const { // 这个函数不会修改radius
return radius;
}
void setRadius(double r) { // 这个函数会修改
radius = r;
}
};
小技巧:const对象只能调用const成员函数。如果你写了一个函数接收const引用参数,它只能调用该对象的const方法。所以——只读的成员函数都加上const,这是好习惯。
四、知识体系总览
下面这张图把本章的核心知识点串起来了。我画图的时候习惯把「类」放在中间,因为它是一切的基础。
五、总结与最佳实践
聊了这么多,我总结几条实战经验:
- 类名用大写开头,对象名用小写开头——这是约定俗成的,别搞反了
- 成员变量一律private,通过public的getter/setter访问——封装性的核心
- 只读成员函数加const——既是文档也是约束
- 能用栈对象就别用堆对象——省心省力,避免内存泄漏
- 类定义末尾别忘了分号——我见过太多人栽在这个小细节上
面向对象的基础就这些。说白了,类就是把数据和操作数据的方法打包在一起。你把这个思想理解了,后面的继承、多态学起来就顺了。