C++ 面向对象编程(OOP)

C++ 面向对象编程(OOP)

核心:面向对象三大特性------封装、继承、多态,核心思想是"将数据和操作数据的方法绑定在一起",提高代码复用性、可维护性和扩展性。

一、面向对象核心概念

1. 类(Class)与对象(Object)

  • 类:抽象的模板,定义了对象的属性(成员变量)和行为(成员函数),是"对象的蓝图"。
  • 对象:类的实例,是具体的实体,拥有类定义的属性和行为。
  • 关系:一个类可以创建多个对象,每个对象的属性值不同,但行为一致。
示例:类与对象的定义和使用
复制代码
>using namespace std;

// 定义类(Person类)
class Person {
public:  // 访问权限(后续讲解)
    // 1. 成员变量(属性):描述对象的特征
    string name;
    int age;
    char gender;

    // 2. 成员函数(行为):描述对象的操作
    void showInfo() {  // 成员函数可以直接访问类内成员< "姓名< "< age< ",性别:"< endl;
    }

    void setAge(int a) {  // 封装:通过成员函数修改私有/公有成员
        if (a >=<= 120) {  // 增加逻辑校验,保证数据安全
            age = a;
        } else {
            cout << "年龄输入非法!"< endl;
        }
    }
};

// 主函数:创建并使用对象
int main() {
    // 1. 创建对象(实例化类)
    Person p1;  // 栈上创建对象
    Person* p2 = new Person();  // 堆上创建对象(需手动释放)

    // 2. 给对象的属性赋值(公有成员可直接访问)
    p1.name = "张三";
    p1.setAge(20);  // 通过成员函数赋值(推荐,可做校验)
    p1.gender = '男';

    p2->name = "李四";  // 堆对象用 -> 访问成员
    p2->setAge(25);
    p2->gender = '男';

    // 3. 调用对象的成员函数
    p1.showInfo();  // 输出:姓名:张三,年龄:20,性别:男
    p2->showInfo(); // 输出:姓名:李四,年龄:25,性别:男

    // 4. 释放堆对象(避免内存泄漏)
    delete p2;
    p2 = nullptr;  // 避免野指针

    return 0;
}

2. 类的访问权限(封装的核心)

C++ 通过访问权限控制类成员的可见性,实现"封装"------隐藏内部实现,只对外提供安全的访问接口。

三种访问权限(优先级:类内都可访问,类外按权限区分):

访问权限 类内 类外 子类(继承时) 说明
public(公有) 对外暴露的接口(如成员函数、公共属性)
protected(保护) 仅类内和子类可访问(用于继承传递)
private(私有) 仅类内可访问(隐藏核心数据,最常用)

注意:class 关键字默认访问权限是 private;struct 关键字默认访问权限是 public(struct 更适合单纯存储数据,class 适合面向对象封装)。

二、面向对象三大特性(重点)

1. 封装(Encapsulation)

核心思想

属性(成员变量)和行为(成员函数)绑定在一起,隐藏对象的内部实现细节,只通过公共接口(public 成员函数)访问对象,保证数据的安全性和完整性。

封装的实现步骤
  1. 将成员变量设为 private(隐藏数据);
  2. 提供 public 的成员函数(getter/setter),用于访问和修改成员变量;
  3. 在成员函数中添加逻辑校验,避免非法数据。
示例:封装的标准实现
cpp 复制代码
class Student {
private:  // 私有成员:隐藏核心数据
    string id;    // 学号(不可直接修改)
    string name;  // 姓名
    int score;    // 成绩(0-100)

public:  // 公有接口:对外提供访问方式
    // getter 函数:获取私有成员的值
    string getId() { return id; }
    string getName() { return name; }
    int getScore() { return score; }

    // setter 函数:修改私有成员的值(带校验)
    void setId(string i) { id = i; }  // 学号无非法范围,直接赋值
    void setName(string n) { name = n; }
    void setScore(int s) {
        if (s >= 0 &&<= 100) {
            score = s;
        } else< "成绩非法,默认赋值为< endl;
            score = 0;
        }
    }

    // 其他行为:封装的业务逻辑
    void showScore()< name< id< ")的成绩:< endl;
    }
};

// 使用
int main() {
    Student s;
    s.setId("2024001");
    s.setName("王五");
    s.setScore(95);  // 合法,赋值95
    s.showScore();   // 输出:王五(2024001)的成绩:95

    s.setScore(105); // 非法,默认赋值0
    s.showScore();   // 输出:王五(2024001)的成绩:0

    // s.score = 80;  // 错误:private成员,类外不可直接访问
    return 0;
}
封装的优势
  • 数据安全:避免外部直接修改核心数据,通过校验保证数据合法;
  • 代码可维护:修改内部实现时,只要接口不变,外部代码无需修改;
  • 代码复用:封装的类可以在多个场景重复使用。

2. 继承(Inheritance)

核心思想

子类(派生类)继承父类(基类)的属性和行为,同时可以扩展自己的新属性和新行为,实现"代码复用",减少冗余。

继承的语法
cpp 复制代码
// 语法:class 子类名 : 继承方式 父类名
class 子类名 : 继承方式 父类名 {
    // 子类自己的成员(属性+函数)
};
三种继承方式(核心区别:父类成员在子类中的访问权限)
继承方式 父类public成员 父类protected成员 父类private成员 说明
public(公有继承) 子类public 子类protected 子类不可访问 最常用,保留父类访问权限
protected(保护继承) 子类protected 子类protected 子类不可访问 用于子类再继承的场景
private(私有继承) 子类private 子类private 子类不可访问 几乎不用,父类成员被隐藏
示例:公有继承(最常用)
cpp 复制代码
// 父类(基类):Animal
class Animal {
protected:  // 保护成员:子类可访问,类外不可访问
    string name;
    int age;

public:
    // 父类的构造函数(后续讲解)
    Animal(string n, int a) : name(n), age(a) {}

    // 父类的行为
    void eat() {
        cout << "在吃东西!< endl;
    }

    void sleep()< name< "在睡觉!"< endl;
    }
};

// 子类(派生类):Dog,公有继承Animal
class Dog : public Animal {
private:
    string breed;  // 子类扩展的新属性:品种

public:
    // 子类的构造函数:必须先初始化父类(用初始化列表)
    Dog(string n, int a, string b) : Animal(n, a), breed(b) {}

    // 子类扩展的新行为
    void bark() {
       < "(< ")在汪汪叫!" << endl;
    }

    // 子类重写父类的行为(多态的基础,后续讲解)
    void eat() override {
        cout << name << "(" << ")在吃< endl;
    }
};

// 使用
int main() {
    Dog dog("旺财", 3, "中华田园犬");
    dog.eat();    // 调用子类重写的eat():旺财(中华田园犬)在吃骨头!
    dog.sleep();  // 继承父类的sleep():旺财在睡觉!
    dog.bark();   // 子类自己的bark():旺财(中华田园犬)在汪汪叫!

    // dog.name;  // 错误:父类name是protected,子类类外不可访问
    return 0;
}
继承的关键细节
  1. 子类构造函数:必须先初始化父类(通过初始化列表 : 父类构造函数(参数)),再初始化子类自己的成员;
  2. 父类private成员:无论哪种继承方式,子类都不能直接访问(只能通过父类的public/protected成员函数访问);
  3. 继承的传递性:子类可以继承父类的父类(如 Dog 继承 Animal,Animal 继承 Object);
  4. 多重继承(不推荐):一个子类可以继承多个父类,容易出现"菱形继承"(二义性),可通过 virtual 虚继承解决。

3. 多态(Polymorphism)

核心思想

同一接口,不同实现------父类的指针/引用指向子类对象时,调用同名成员函数,会执行子类的实现(而非父类),实现"动态绑定"。

多态的实现条件(缺一不可)
  1. 父类中定义虚函数 (用 virtual 关键字修饰成员函数);
  2. 子类重写 虚函数(函数名、参数列表、返回值完全一致,可加 override 关键字校验);
  3. 父类的指针或引用指向子类对象。
示例:多态的标准实现
cpp 复制代码
#include <string>
using namespace std;

// 父类:Shape(图形)
class Shape {
public:
    // 虚函数:父类的接口
    virtual void draw() {
< "绘制图形< endl;
    }

    // 虚析构函数(重点!避免内存泄漏)
    virtual ~Shape() {
< "Shape析< endl;
    }
};

// 子类1:Circle(圆形)
class Circle : public Shape {
public:
    // 重写虚函数(override 可选,用于校验重写是否正确)
    void draw() override {
       < "绘制圆形(< endl;
    }

    ~Circle() override {
       < "Circle析构< endl;
    }
};

// 子类2:Rectangle(矩形)
class Rectangle : public Shape {
public:
    void draw() override< "绘制矩形(□)"< endl;
    }

    ~Rectangle() override< "Rectangle析构函数" << endl;
    }
};

// 测试函数:接收父类引用(满足多态条件3)
void drawShape(Shape& shape) {
    shape.draw();  // 调用的是子类的draw(),动态绑定
}

int main() {
    Circle circle;
    Rectangle rectangle;

    // 父类引用指向子类对象,调用draw(),执行子类实现
    drawShape(circle);    // 输出:绘制圆形(○)
    drawShape(rectangle); // 输出:绘制矩形(□)

    // 父类指针指向子类对象(另一种方式)
    Shape* shape1 = new Circle();
    Shape* shape2 = new Rectangle();

    shape1->draw();  // 输出:绘制圆形(○)
    shape2->draw();  // 输出:绘制矩形(□)

    // 释放堆对象:虚析构函数会先调用子类析构,再调用父类析构(避免内存泄漏)
    delete shape1;
    delete shape2;

    return 0;
}
多态的关键细节
  1. 虚函数:virtual 关键字只需要在父类中声明,子类重写时可省略,但推荐加 override 校验;

  2. 虚析构函数:如果父类指针指向子类堆对象,父类析构函数必须是虚函数,否则只会调用父类析构,导致子类堆内存泄漏(如上例);

  3. 纯虚函数与抽象类:

    • 纯虚函数:virtual void draw() = 0;(没有函数体,只有声明);

    • 抽象类:包含纯虚函数的类,不能实例化对象,只能作为父类被继承(子类必须重写纯虚函数,否则子类也是抽象类);

    • 示例:

      cpp 复制代码
      class Shape {
      public:
          virtual void draw() = 0;  // 纯虚函数
      };
      // Shape s;  // 错误:抽象类不能实例化
      class Circle : public Shape {
      public:
          void draw() override { ... }  // 必须重写
      };
  4. 静态多态(重载)vs 动态多态(虚函数):

    • 静态多态:编译期绑定,如函数重载(同一函数名,不同参数);
    • 动态多态:运行期绑定,如上述虚函数实现(根据对象实际类型调用对应函数)。

三、类的核心成员(补充)

1. 构造函数(Constructor)

  • 作用:创建对象时自动调用,用于初始化对象的成员变量(避免成员变量为随机值);
  • 特点:
    1. 函数名与类名完全一致,无返回值(连 void 都不能写);
    2. 可重载(多个构造函数,参数列表不同);
    3. 若未手动定义,编译器会自动生成"默认无参构造函数"(空实现);
    4. 有参构造函数会覆盖默认无参构造(若需要无参,需手动定义)。
构造函数的三种形式
cpp 复制代码
class Person {
public:
    string name;
    int age;

    // 1. 无参构造函数(默认构造)
    Person() {
        name = "未知";
        age = 0;
       < "无参构造< endl;
    }

    // 2. 有参构造函数(重载)
    Person(string n, int a) {
        name = n;
        age = a;
        cout< "有参构造函数< endl;
    }

    // 3. 初始化列表构造(推荐,效率更高)
    Person(string n, int a, char g) : name(n), age(a), gender(g) {
< "初始化列表< endl;
    }

private:
    char gender;
};

2. 析构函数(Destructor)

  • 作用:对象销毁时自动调用,用于释放对象占用的资源(如堆内存、文件句柄);
  • 特点:
    1. 函数名:~类名(波浪线+类名),无返回值,无参数(不能重载);
    2. 若未手动定义,编译器会自动生成"默认析构函数"(空实现);
    3. 若对象占用堆内存,必须手动定义析构函数,释放堆内存(避免内存泄漏);
    4. 多态场景中,父类析构函数必须是虚函数(virtual ~类名())。

3. 拷贝构造函数(Copy Constructor)

  • 作用:用一个已存在的对象,创建一个新的对象 (如 Person p2 = p1;);
  • 语法:类名(const 类名& 引用对象)
  • 特点:
    1. 若未手动定义,编译器会自动生成"默认拷贝构造函数"(浅拷贝);
    2. 若对象有堆内存成员(如 char* name),默认浅拷贝会导致"双重释放",需手动实现深拷贝。
示例:深拷贝(解决浅拷贝问题)
cpp 复制代码
class Student {
private:
    char* name;  // 堆内存成员
    int age;

public:
    // 有参构造:分配堆内存
    Student(const char* n, int a) {
        age = a;
        name = new char[strlen(n) + 1];  // 分配内存(+1存'\0')
        strcpy(name, n);  // 复制字符串
    }

    // 拷贝构造函数(深拷贝)
    Student(const Student& s) {
        age = s.age;
        // 重新分配堆内存,避免与原对象共用一块内存
        name = new char[strlen(s.name) + 1];
        strcpy(name, s.name);
    }

    // 析构函数:释放堆内存
    ~Student() {
        delete[] name;  // 释放字符串堆内存
        name = nullptr;
    }
};

4. this 指针

  • 定义:隐含在每个非静态成员函数中,指向当前对象(调用成员函数的对象);
  • 作用:
    1. 区分成员变量和函数参数(当参数名与成员变量名相同时);
    2. 返回当前对象本身(如链式调用)。
示例:this 指针的使用
cpp 复制代码
class Person {
private:
    string name;
    int age;

public:
    // 参数名与成员变量名相同,用this区分
    Person(string name, int age) {
        this->name = name;  // this->name:成员变量;name:参数
        this->age = age;
    }

    // 返回当前对象,实现链式调用
    Person& setAge(int age) {
        this->age = age;
        return *this;  // 返回当前对象的引用
    }

    Person& setName(string name) {
        this->name = name;
        return *this;
    }

    void show()< name< age< endl;
    }
};

// 使用:链式调用
int main() {
    Person p("张三", 20);
    p.setName("李四").setAge(25).show();  // 输出:李四,25岁
    return 0;
}

5. 静态成员(static)

  • 定义:用 static 修饰的类成员(静态成员变量/静态成员函数);

  • 特点:

    1. 静态成员变量:属于整个类,所有对象共用同一份内存(不占用单个对象的内存);
    • 必须在类外初始化(类型 类名::静态成员变量 = 初始值;);
    • 可通过 类名::静态成员变量对象.静态成员变量 访问。
    1. 静态成员函数:属于整个类,没有 this 指针(不能访问非静态成员,只能访问静态成员);
    • 可通过 类名::静态成员函数()对象.静态成员函数() 调用。
示例:静态成员的使用
cpp 复制代码
class Student {
public:
    static int count;  // 静态成员变量:统计学生个数
    string name;

    // 构造函数:每次创建对象,count加1
    Student(string n) : name(n) {
        count++;
    }

    // 静态成员函数:访问静态成员变量
    static int getCount() {
        // return name;  // 错误:静态函数不能访问非静态成员
        return count;
    }
};

// 类外初始化静态成员变量(必须写,否则编译错误)
int Student::count = 0;

int main() {
    Student s1("张三");
    Student s2("李四< "< Student< endl;  // 输出:2
< "学生个数< s1.get< endl;  // 输出:2

    return 0;
}

四、面向对象常见问题(避坑)

  1. 构造函数无返回值,不能写 void
  2. 子类构造函数必须先初始化父类(初始化列表);
  3. 父类指针指向子类堆对象时,父类析构必须是虚函数(避免内存泄漏);
  4. 拷贝构造函数的参数必须是"const 引用"(避免无限递归);
  5. 静态成员函数不能访问非静态成员(无 this 指针);
  6. 虚函数不能是静态成员函数(静态函数属于类,虚函数属于对象,冲突);
  7. 私有成员(private)无论哪种继承,子类都不能直接访问。

五、总结

面向对象编程的核心是封装、继承、多态

  • 封装:隐藏细节,保证数据安全,对外提供接口;
  • 继承:复用父类代码,扩展子类功能;
  • 多态:同一接口,不同实现,实现动态绑定,提高代码灵活性。

C++ 面向对象的核心语法围绕"类和对象"展开,重点掌握构造/析构函数、虚函数、this 指针、静态成员,就能灵活运用面向对象思想开发代码。

相关推荐
白菜欣2 小时前
Linux权限
linux·运维·c++
沐知全栈开发2 小时前
CSS Backgrounds (背景)
开发语言
小草cys2 小时前
树莓派4b + USRP B210 搭建反无人机(反无)系统( HTML + CDN )
开发语言·python·机器学习
君生我老2 小时前
C++ 红黑树
c++
坐吃山猪2 小时前
MFlow03-数据模型解析
开发语言·python·源码·agent·记忆
旖-旎2 小时前
深搜(二叉树剪枝)(3)
数据结构·c++·算法·力扣·剪枝·递归
HABuo2 小时前
【linux网络(一)】初识网络, 理解协议&四层网络模型&网络传输流程
linux·运维·服务器·网络·c++·ubuntu·centos
流年如夢2 小时前
结构体:定义、使用与内存布局
c语言·开发语言·数据结构·c++·算法
thankseveryday2 小时前
Three.js 把 Blender 绘制的曲线(Bezier / 曲线) 导入 Three.js 并作为运动路径 / 动画路径使用
开发语言·javascript·blender