C++:类与对象

大家好,这里是彩妙呀~

从c语言进阶到c++语言,逃不了一个重要的概念:面向对象(OOP)。本篇,彩妙带着大家学习OPP中重要的一部分:封装。

废话不多说,下面进入正文吧!

目录

什么是面向对象?

什么是类与对象?

什么是类?

类的核心构成

来个例子

访问权限三剑客

类的定义

什么是对象?

类的核心特性:从初始化到访问控制的深度解析

类域

默认成员函数

构造函数与析构函数

[成员函数隐藏传参 : this指针](#成员函数隐藏传参 : this指针)

赋值运算符重载

基本语法

成员函数形式

非成员函数形式

详细解说博客占位


什么是面向对象?

面向对象(Object-Oriented Programming ,简称OOP)是一种将程序组织为对象的编程思想。对象就像是现实世界中的事物,每个对象都有自己的属性(数据)和行为(方法)。是一种抽象的方法。

举个例子:学生管理系统

面向过程思维(c语言,关注"怎么做"------按步骤执行的流程):你会思考"先输入数据,然后排序,最后输出"。

按照这种逐步分解问题的方法,将任务划分为多个步骤,按照顺序依次执行,适合解决简单的问题(贪吃蛇,扫雷等)。

面向对象思维(c++,关注"谁来做"------对象之间的交互协作):你会创建"学生"这个对象,它有自己的学号、姓名等属性,以及选课、计算成绩等方法

按照这种将问题分解为对象的方法,将对象的属性行为****封装起来,实现模块化设计,适合解决复杂的、需要协作的问题(在中,大型项目中非常有效,例如美团app等)。

当然,面向对象思维也有自己的四大特性:

  • 封装:把数据和方法打包在一起,隐藏内部细节
  • 继承:子类可以继承父类的特性,实现代码复用
  • 多态:同一操作对不同对象有不同的表现
  • 抽象:提取关键特征,忽略不必要细节

在后续的博客中,彩妙会带着大家一一学习这些重难点

而本篇博客,着重会讲解OOP的基础和核心构建块:类与对象

什么是类与对象?

什么是类?

类(class)是对同一类事物的抽象描述------ 它定义了这类事物的共同属性(特征)和共同行为(功能),但它不是具体存在的实体,只是一套 "规则 / 蓝图 / 模版"。

在c语言中,相信大家都学过结构体(struct),而类(class)可以看做结构体的升级plus版本(结构体能做到类能做,结构体不能做的类也能做)

类的核心构成

任何类都由成员变量成员函数组成,对应事物的 "属性" 和 "行为":

|----------|-----------|
| 构成部分 | 作用 |
| 成员变量(属性) | 描述事物的特征 |
| 成员函数(方法) | 描述事物能做的功能 |

类的 3 个核心作用

抽象归纳:把同一类事物的共性提炼出来,避免重复描述(比如不用分别描述 "小明、小红、小刚" 的属性,用 "学生类" 统一定义);​

封装保护:通过访问权限(public/private)隐藏核心细节(比如学生的密码),只暴露必要功能(比如学习、查成绩);​

批量创建对象:一个类可以创建无数个对象(比如用 "学生类" 创建小明、小红、小刚等 N 个学生对象),复用代码。

来个例子

举 3 个常见例子:​

类:房子设计图​

  • 成员变量(属性):户型(两室一厅)、面积(100㎡)、朝向(朝南)
  • 成员函数(行为):遮风挡雨、供人居住
  • 对象:根据这个设计图盖好的 "你家的房子"(具体实体)

类:汽车蓝图​

  • 成员变量:品牌、排量、颜色
  • 成员函数:行驶、刹车、鸣笛
  • 对象:你开的特斯拉 Model 3(具体实体)

类:学生模板​

  • 成员变量:姓名、年龄、成绩
  • 成员函数:上课、考试、交作业
  • 对象:小明(姓名:小明,年龄 18,成绩 95)

访问权限三剑客

C++类中有三种访问权限,用于控制类成员的可见性 ,实现封装的核心思想:

  1. public(公有) - 完全开放

  2. private(私有) - 内部专用

  3. protected(受保护) - 继承可见

这三个权限在此处没有展现他的性质,后续再次讲解OOP中继承与多态详细说明。

类的定义

cpp 复制代码
class 类名 {
    // 访问权限(控制外部能否访问,之前讲过的public/private/protected)
public:
    // 1. 成员变量(属性):数据类型 + 变量名
    数据类型 变量名1;
    数据类型 变量名2;
    
    // 2. 成员函数(行为):返回值类型 + 函数名(参数) + 函数体
    返回值类型 函数名1(参数列表) {
        函数体; // 实现功能
    }
private:
    // 私有成员(外部无法直接访问,封装的核心)
    数据类型 私有变量;
}; // 注意:类定义结束必须加分号!

例如,定义一个简单的Point类,用于表示二维平面上的点:

cpp 复制代码
class Point {
public:
    // 设置点的坐标
    void setPoint(int x, int y) {
        this->x = x;
        this->y = y;
    }
    // 获取x坐标
    int getX() {
        return x;
    }
    // 获取y坐标
    int getY() {
        return y;
    }
private:
    int x;
    int y;
};

什么是对象?

对象(object)是类的实例化(创建以类为规则 / 蓝图 / 模版的一个对象)。当我们去定义一个类之后,就可以按照这个类来创建对应的对象(也被称作类的实例化):

cpp 复制代码
Point p1;  // 创建一个Point类的对象p1
Point p2;  // 创建另一个Point类的对象p2

//构造函数创建,后续会详解
Point p3(3,4);

//也可以使用new来动态创建对象(内存管理部分会详细说明)
Point* p4 = new Point(2,3);
delete p4;

对于面向对象编程思想(OOP),建议大家找点资料了解一下,如果有时间,彩妙也会写文章来记录自己的学习感想


前置知识了解完,接下来咱们就要开始进入重点了!

类的核心特性:从初始化到访问控制的深度解析

类域

类域(Class Field)通常指类的静态成员变量,它属于类本身而非某个对象实例,在类的所有实例间共享。类域在类加载时被初始化,生命周期伴随整个程序运行,常用于存储全局配置、常量或共享状态。在Java/C++等语言中,通常用static关键字声明。

大部分原理类似命名空间详解,大加可以自行阅读一下。

默认成员函数

在C++中,当定义一个类时,编译器会自动为它生成一些特殊的成员函数,这些被称为默认成员函数(Default Member Functions)。它们是类的基本操作,即使你没有显式定义,编译器也会生成默认版本。
一般情况下编译器会自动生成六个默认成员函数。其中前四个最为关键,而最后两个(取地址重载)相对次要,仅作了解即可。需要注意的是,自C++11标准起,还新增了两个默认成员函数:移动构造函数和移动赋值运算符,这些内容将在后续讨论。

默认成员函数在C++中占据重要地位,其实现机制也较为复杂。学习时需重点关注以下两方面:

  1. 当不显式定义这些函数时,编译器生成的默认实现具有何种行为?这些行为是否能满足实际需求?
  2. 若默认实现无法满足需求,应如何正确实现自定义版本?

本篇博客作为初始了解一下这些函数都有什么作用,更深一步的了解这些函数的使用场景以及注意事项,期待彩妙后续博客为您带来。

构造函数与析构函数

构造函数与析构函数相当于对类的初始化以及销毁,是非常重要且特殊的成员函数。

cpp 复制代码
class Date
{
public:
    int year;
    int month;
    int day;

    // 默认构造函数
    Date()
    {
        year = 0;
        month = 0;
        day = 0;
        std::cout << "Default constructor called." << std::endl;
    }
    
    // 带参数的构造函数
    Date(int y, int m, int d)
    {
        year = y;
        month = m;
        day = d;
        std::cout << "Parameterized constructor called." << std::endl;
    }
    
    // 拷贝构造函数
    Date(const Date& other)
    {
        year = other.year;
        month = other.month;
        day = other.day;
        std::cout << "Copy constructor called." << std::endl;
    }

    // 析构函数
    ~Date()
    {
        std::cout << "Date object is destroyed." << std::endl;
    }
};

上面代码就讲述了两个函数的基本用法:

构造函数相当于初始化(可以使用缺省函数构造)。而析构函数则需要在同名构造函数钱加一个~就可以了。

当然,对于构造函数,也存在多种场景下不同构造方法(例如以类为基础的拷贝构造,按照另一个类为成员的构造,这些留在下一篇博客讲),也意义对应着析构函数。

成员函数隐藏传参 : this指针

在 C++ 中,每个非静态成员函数都隐含着一个this指针参数 ,它指向当前调用该成员函数的对象 。this指针就像是一座隐形的桥梁,使得成员函数能够准确地访问和操作所属对象的成员变量和其他成员函数 。例如,在Date类的setYear函数中:

cpp 复制代码
class Date {
private:
    int year;
    int month;
    int day;

public:
    // 构造函数略

    // 设置年份
    void setYear(int year) {
        this->year = year; // 使用this指针区分成员变量year和参数year
    }
};

代码中,详细展示了使用this指正来区分成员变量以及形参。通过this->year明确表示访问的是对象的成员变量year,而year则表示函数的参数 。

赋值运算符重载

对于类来说,编译器不知道我们定义类基本的几何运算(加减乘除等),所以c++提供了运算符重载来满足我们类的需求:

运算符重载 允许我们为用户自定义类型(类、结构体)定义运算符的行为,使它们像内置类型一样工作。例如,让两个Date对象可以直接相加,或者让Complex对象可以直接用+运算符。

基本语法
成员函数形式
cpp 复制代码
返回类型 operator运算符(参数列表) {
    // 实现
}
非成员函数形式
cpp 复制代码
返回类型 operator运算符(参数1, 参数2) {
    // 实现
}

例如:

cpp 复制代码
  // ========== 1. 比较运算符重载(成员函数形式) ==========
    // == 运算符:判断两个日期是否相等
    bool operator==(const Date& other) const {
        return year == other.year && month == other.month && day == other.day;
    }

    // != 运算符:判断两个日期是否不相等(复用==)
    bool operator!=(const Date& other) const {
        return !(*this == other);
    }

    // < 运算符:判断当前日期是否早于other
    bool operator<(const Date& other) const {
        if (year != other.year) return year < other.year;
        if (month != other.month) return month < other.month;
        return day < other.day;
    }

    // > 运算符:判断当前日期是否晚于other(复用<)
    bool operator>(const Date& other) const {
        return other < *this;
    }

    // <= 运算符:复用<和==
    bool operator<=(const Date& other) const {
        return *this < other || *this == other;
    }

    // >= 运算符:复用>和==
    bool operator>=(const Date& other) const {
        return *this > other || *this == other;
    }

    // ========== 2. 日期+天数 运算符重载(成员函数形式) ==========
    // 日期 + 天数 = 新日期
    Date operator+(int days) const {
        Date temp = *this;  // 复制当前日期,避免修改原对象
        if (days < 0) return temp - (-days);  // 处理负数天数(复用-运算符)

        // 逐天累加,直到days为0
        while (days > 0) {
            int daysOfCurrentMonth = getDaysOfMonth(temp.year, temp.month);
            if (temp.day + days <= daysOfCurrentMonth) {
                temp.day += days;
                days = 0;
            } else {
                // 剩余天数 = 天数 - (当月剩余天数)
                days -= (daysOfCurrentMonth - temp.day + 1);
                temp.day = 1;
                temp.month++;
                // 月份超过12,年份+1,月份重置为1
                if (temp.month > 12) {
                    temp.month = 1;
                    temp.year++;
                }
            }
        }
        return temp;
    }

    // ========== 3. 天数+日期 运算符重载(友元函数形式) ==========
    // 因为左操作数不是类对象,必须用友元/全局函数
    friend Date operator+(int days, const Date& date) {
        return date + days;  // 复用日期+天数,避免重复代码
    }

    // ========== 4. 日期-天数 运算符重载(成员函数形式) ==========
    Date operator-(int days) const {
        Date temp = *this;
        if (days < 0) return temp + (-days);  // 处理负数天数

        // 逐天递减,直到days为0
        while (days > 0) {
            if (temp.day - days >= 1) {
                temp.day -= days;
                days = 0;
            } else {
                // 剩余天数 = 天数 - 当前日期的天数
                days -= temp.day;
                temp.month--;
                // 月份小于1,年份-1,月份重置为12
                if (temp.month < 1) {
                    temp.month = 12;
                    temp.year--;
                }
                // 重置为当月最后一天
                temp.day = getDaysOfMonth(temp.year, temp.month);
            }
        }
        return temp;
    }

    // ========== 5. 日期-日期 运算符重载(成员函数形式) ==========
    // 计算两个日期的天数差(当前日期 - other = 天数)
    int operator-(const Date& other) const {
        Date temp = other;
        int days = 0;
        bool isPositive = *this > other;  // 判断差值正负

        // 以较小的日期为起点,累加直到等于较大的日期
        Date start = isPositive ? other : *this;
        Date end = isPositive ? *this : other;

        while (start != end) {
            start = start + 1;
            days++;
        }

        return isPositive ? days : -days;
    }

    // ========== 6. 自增运算符重载 ==========
    // 前置++:++date(返回引用,支持连续++)
    Date& operator++() {
        *this = *this + 1;
        return *this;
    }

    // 后置++:date++(int是标记,区分前置/后置,返回值而非引用)
    Date operator++(int) {
        Date temp = *this;  // 保存自增前的状态
        *this = *this + 1;
        return temp;        // 返回旧值
    }

    // ========== 7. 自减运算符重载 ==========
    // 前置--:--date
    Date& operator--() {
        *this = *this - 1;
        return *this;
    }

    // 后置--:date--
    Date operator--(int) {
        Date temp = *this;
        *this = *this - 1;
        return temp;
    }

    // ========== 8. 输入输出运算符重载(友元函数形式) ==========
    // 输出运算符 <<
    friend ostream& operator<<(ostream& os, const Date& date) {
        os << date.year << "年" << date.month << "月" << date.day << "日";
        return os;
    }

    // 输入运算符 >>
    friend istream& operator>>(istream& is, Date& date) {
        int y, m, d;
        is >> y >> m >> d;
        // 校验输入合法性
        if (!date.isValidDate(y, m, d)) {
            throw invalid_argument("输入的日期不合法!");
        }
        date.year = y;
        date.month = m;
        date.day = d;
        return is;
    }

如果看不懂,先别着急,后续博客会带着大家来仔细深究这些知识,关注彩妙,给你带来精彩内容

详细解说博客占位符

相关推荐
董世昌419 小时前
添加、删除、替换、插入元素的全方法指南
java·开发语言·前端
Yu_Lijing9 小时前
基于C++的《Head First设计模式》笔记——抽象工厂模式
c++·笔记·设计模式
源代码•宸9 小时前
Leetcode—712. 两个字符串的最小ASCII删除和【中等】
开发语言·后端·算法·leetcode·职场和发展·golang·dp
无风听海9 小时前
C# 中对象相等性判断的全面解析
开发语言·c#
寻星探路9 小时前
【Python 全栈测开之路】Python 基础语法精讲(三):函数、容器类型与文件处理
java·开发语言·c++·人工智能·python·ai·c#
txinyu的博客9 小时前
函数的可重入性决定了函数的线程安全?volatile被认为是可重入关键字?
c++
逑之9 小时前
C语言笔记8:操作符
c语言·开发语言·笔记
无限进步_9 小时前
【C语言&数据结构】相同的树:深入理解二叉树的结构与值比较
c语言·开发语言·数据结构·c++·算法·github·visual studio
枫叶丹49 小时前
【Qt开发】Qt系统(五)-> Qt 多线程
c语言·开发语言·c++·qt