从C到C++:从结构体到类,面向对象初体验

📅 2026/6/25 13:47:16
从C到C++:从结构体到类,面向对象初体验
学C的时候结构体struct就是把一堆变量打包在一起方便管理。到了Cclass直接把变量和函数都包进去了还加了封装、继承、多态…整个编程的思路都变了。这篇就聊聊从C的结构体过渡到C的类时那些让我哦原来如此的瞬间。一、从 struct 到 class不只是加了函数C的结构体数据的打包C里的struct只能放变量不能放函数函数指针不算// C风格的学生结构体struct Student {char name[20];int age;float score;};// 操作结构体的函数是分开的void student_init(struct Student *s, const char *name, int age, float score) {strcpy(s-name, name);s-age age;s-score score;}void student_print(struct Student *s) {printf(“%s, %d岁, 分数: %.1f\n”, s-name, s-age, s-score);}数据和操作数据的函数是分开的调用的时候要把结构体指针传进去。C的类数据 操作打包在一起C里struct和class都能定义类区别只是默认访问权限可以把数据和函数都放进去// C风格的学生类class Student {public:// 成员函数void init(const string name, int age, float score) {name_ name;age_ age;score_ score;}void print() {cout name_ “, age_ “岁, 分数: score_ endl;}private:// 成员变量string name_;int age_;float score_;};用的时候Student s;s.init(“张三”, 18, 95.5f);s.print();最大的区别C里是数据 操作数据的函数”C里是对象自己带着数据和方法”。就像C里你是命令机器去操作数据C里你是让对象自己干活。二、三大访问限定符封装的艺术C的结构体成员默认都是公开的想怎么改就怎么改很自由也很危险。C有三个访问限定符控制成员的可见性表格限定符 作用public 公开的谁都能访问private 私有的只有类自己的成员函数能访问protected 受保护的自己和子类能访问继承的时候再讲默认访问权限class 默认是 privatestruct 默认是 public为了兼容Cclass A {int x; // 默认private};struct B {int x; // 默认public};为什么要有封装刚开始学的时候觉得好麻烦设成public直接改不行吗后来才明白封装的好处保护数据不让外部随便改防止非法值解耦外部只需要知道怎么用不需要知道内部怎么实现可维护内部实现改了只要接口不变外部代码不用改举个例子// 不好的设计成员变量全公开class Rectangle {public:int width;int height;};// 外部可以随便改甚至改成负数Rectangle r;r.width -10; // 合法但不合理宽不能是负数啊// 好的设计封装起来只暴露接口class Rectangle {public:bool set_width(int w) {if (w 0) return false; // 检查合法性width_ w;return true;}int get_width() const { return width_; } int area() const { return width_ * height_; }private:int width_;int height_;};心得封装不是为了限制而是为了安全。把该藏的藏起来把该露的露出去代码才好维护。三、构造函数与析构函数对象的生与死C里结构体初始化得自己写个init函数还容易忘调用。C有构造函数和析构函数对象创建和销毁的时候自动调用。构造函数对象创建的时候自动调用用来初始化class Student {public:// 构造函数和类同名没有返回值Student(const string name, int age, float score) {name_ name;age_ age;score_ score;}void print() const { cout name_ , age_ 岁 endl; }private:string name_;int age_;float score_;};// 使用Student s(“张三”, 18, 95.5f); // 创建对象的时候自动调用构造函数s.print();构造函数的特点函数名和类名一样没有返回值连void都不写对象创建时自动调用不用手动调用可以重载多个构造函数参数不同默认构造函数没有参数的构造函数叫默认构造函数class Student {public:Student() { // 默认构造函数name_ “未命名”;age_ 0;score_ 0;}Student(const string name, int age, float score) { // ... }};Student s1; // 调用默认构造函数Student s2(“张三”, 18, 95.5f); // 调用有参构造注意如果你自己没写任何构造函数编译器会自动生成一个默认构造函数什么也不做。但只要你写了一个构造函数编译器就不会自动生成默认的了。析构函数对象销毁的时候自动调用用来清理资源class Array {public:Array(int size) {data_ new int[size]; // 构造的时候分配内存size_ size;}~Array() { // 析构函数~ 类名没有参数没有返回值 delete[] data_; // 析构的时候释放内存 }private:int *data_;int size_;};析构函数的特点名字是 ~类名没有参数没有返回值对象销毁时自动调用不能重载一个类只有一个析构函数重要的思想RAII构造函数获取资源析构函数释放资源。对象的生命周期绑定资源的生命周期。这是C里非常重要的编程思想智能指针、锁、文件句柄…都是这么设计的。四、this 指针我是谁成员函数里怎么知道自己在操作哪个对象的数据答案是this 指针。this 是一个隐含的指针指向调用这个成员函数的对象class Student {public:void set_age(int age) {// age_ age; 其实等价于下面这句this-age_ age; // this指向调用这个函数的对象}Student set_name(const string name) { name_ name; return *this; // 返回对象自身的引用可以链式调用 }private:string name_;int age_;};链式调用的例子Student s;s.set_name(“张三”).set_age(18); // 连续调用因为set_name返回了*this小知识静态成员函数没有this指针因为它不属于某个具体的对象。五、从C到C的思维转变从过程式到面向对象C过程式 我要做什么 → 第一步干什么 → 第二步干什么 → 数据在步骤间传来传去C面向对象 有哪些对象 → 每个对象有什么属性和能力 → 对象之间怎么交互举个例子做一个学生管理系统过程式思路先想有哪些功能增删改查数据用数组存函数操作数组面向对象思路先想有哪些类学生类、管理类每个类有什么方法从直接操作数据到通过接口操作C里数据是裸露的想怎么改怎么改。C里数据藏起来通过接口访问更安全也更可控。从手动管理资源到对象管理资源C里malloc/free全靠人记得。C里对象构造的时候拿资源析构的时候放资源只要对象销毁了资源就自动释放了RAII。六、我的踩坑记录坑1构造函数忘了写结果创建对象报错class Student {public:Student(const string name) { // 自己写了有参构造编译器就不生成默认的了name_ name;}private:string name_;};Student s; // 错误没有默认构造函数解决要么加一个默认构造函数要么创建对象的时候传参数。坑2对象里有指针忘了写析构函数class Array {public:Array(int size) : data(new int[size]) {}// 没写析构函数内存泄漏private:int *data;};类里有动态分配的资源一定要写析构函数释放。还要考虑拷贝构造和赋值运算符深拷贝浅拷贝的坑后面讲。坑3const对象不能调用非const成员函数class Student {public:void print() { // 没加constcout name_ endl;}private:string name_;};const Student s;s.print(); // 错误const对象不能调用非const成员函数正确写法不修改成员变量的函数都要加constvoid print() const { // 加const表示这个函数不会修改成员变量cout name_ endl;}建议成员函数只要不修改成员变量就加上const。既是约束自己也是告诉调用者这个函数不会改东西。坑4头文件里写类成员函数的实现忘了加类名// 在cpp文件里void print() { // 错误这是全局函数不是类的成员函数cout name_ endl;}// 正确写法void Student::print() const { // 要加 Student::cout name_ endl;}七、一些实用建议成员变量私有化提供接口访问不要把成员变量设成public用get/set方法访问。既安全又灵活。不修改成员的函数加constconst成员函数可以被const对象调用也能防止你不小心写错。构造函数用初始化列表// 写法1函数体内赋值不好Student::Student(const string name, int age) {name_ name;age_ age;}// 写法2初始化列表更好Student::Student(const string name, int age): name_(name), age_(age) {// 函数体}初始化列表效率更高直接构造不是先构造再赋值而且有些情况必须用初始化列表比如const成员、引用成员、没有默认构造的成员对象。4. 多思考这个类应该提供什么接口写类的时候先想外部要怎么用这个类再想内部怎么实现。接口设计好了内部怎么改都不怕。写在最后从结构体到类不只是加了成员函数那么简单而是整个编程思维的转变——从我要一步步怎么做变成有哪些对象它们各自能做什么。刚开始学面向对象的时候可能觉得这有啥用我用C也能写出来。但等你写过比较大的项目之后就会发现面向对象的优势代码更清晰、更容易维护、更容易扩展。当然面向对象也不是银弹不是什么都要塞到类里。C是多范式语言——过程式、面向对象、泛型编程…选最合适的就好。