类和对象(中)

个人主页流年如梦

专栏《零基础轻松入门C语言》 《数据结构:从入门到掌握》 《C++编程研习录》

文章目录

前言

类和对象是 C++ 面向对象编程的核心,而默认成员函数则是类的基石。本文将系统讲解构造、析构、拷贝构造、赋值重载四大核心函数,深入剖析浅拷贝与深拷贝、const 成员函数、运算符重载等高频考点,并附上可直接运行的日期类完整代码。内容由浅入深、通俗易懂

一.类的 6 个默认成员函数

如果我们不写任何函数,编译器会自动生成 6 个默认成员函数:

  1. 构造函数 --> 初始化对象
  2. 析构函数 --> 清理资源
  3. 拷贝构造函数 --> 用对象初始化新对象
  4. 赋值运算符重载 --> 对象之间赋值
  5. 普通对象取地址重载
  6. const 对象取地址重载

重点掌握前 4 个,后两个几乎不用自己实现

二.构造函数

2.1作用

对象创建时自动调用 ,完成初始化,替代 Init()

2.2特征

  1. 函数名 = 类名
  2. 无返回值(不用写 void)
  3. 对象实例化时自动调用
  4. 可以重载
  5. 不写则编译器生成无参默认构造
  6. 默认构造 = 无参 + 全缺省 + 编译器生成(只能存在一个)

2.3举例

cpp 复制代码
class Date
{
public:
    // 无参构造(默认构造)
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }

    // 带参构造
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    // 全缺省构造(默认构造)
    // Date(int year=1, int month=1, int day=1)
    // {}

    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;            // 调用无参构造
    Date d2(2025,4,18); // 调用带参构造
    // Date d3();       // 错误写法,会被当成函数声明
    return 0;
}

代码没有报错

2.4编译器默认生成构造的行为

  1. 内置类型int / char / 指针):不处理,随机值
  2. 自定义类型(类对象):调用它的默认构造

三.析构函数

3.1作用

对象生命周期结束时自动调用 ,完成资源释放,替代 Destroy()

3.2特征

  1. 名字:~类名
  2. 无参、无返回值
  3. 一个类只能有一个
  4. 不写则编译器自动生成
  5. 先构造的对象后析构

3.3栈类示例(有资源必须写析构)

cpp 复制代码
class Stack
{
public:
    Stack(int n = 4)
    {
        _a = (int*)malloc(sizeof(int) * n);
        _capacity = n;
        _top = 0;
    }

    // 析构函数
    ~Stack()
    {
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }

private:
    int* _a;
    size_t _capacity;
    size_t _top;
};

3.4什么时候必须写析构

只要类申请了动态资源malloc / new),就必须写析构,否则内存泄漏

四.拷贝构造函数

4.1 作用

一个已存在的对象 去初始化创建一个新对象

写法:

cpp 复制代码
类名(const 类名& other)

4.2必须传引用,不能传值!

传值会引发无穷递归,因为传值需要拷贝,拷贝又要调用拷贝构造......

cpp 复制代码
// 错误写法(递归爆炸)
Date(Date d)
{}

// 正确写法
Date(const Date& d)
{}

4.3浅拷贝与深拷贝

浅拷贝(编译器默认生成)

  1. 逐字节拷贝
  2. 指针会指向同一块内存
  3. 析构两次 --> 崩溃

深拷贝(需要自己写)

  1. 重新开辟空间
  2. 拷贝内容
  3. 两个对象独立

4.4Stack 深拷贝拷贝构造

cpp 复制代码
Stack(const Stack& st)
{
    // 新开空间
    _a = (int*)malloc(sizeof(int) * st._capacity);
    memcpy(_a, st._a, sizeof(int) * st._top);
    _top = st._top;
    _capacity = st._capacity;
}

4.5拷贝构造调用场景

  1. Date d2(d1);
  2. Date d2 = d1;
  3. 函数传值传参
  4. 函数传值返回

五.赋值运算符重载

5.1 作用

让两个已经存在的对象可以用 = 赋值

5.2 特征

  1. 必须是成员函数
  2. 参数:const 类名&
  3. 返回值:类名&(支持连续赋值)
  4. 必须检查自己给自己赋值

5.3 标准写法

cpp 复制代码
Date& operator=(const Date& d)
{
    // 防止自己给自己赋值
    if (this != &d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    return *this;
}

5.4 拷贝构造 vs 赋值重载

cpp 复制代码
Date d1(2024,7,5);
Date d2 = d1;  // 拷贝构造(创建新对象)
Date d3;
d3 = d1;      // 赋值重载(已存在对象赋值)

5.5 Stack 深拷贝赋值重载

cpp 复制代码
Stack& operator=(const Stack& st)
{
    if (this != &st)
    {
        free(_a);
        _a = (int*)malloc(sizeof(int) * st._capacity);
        memcpy(_a, st._a, sizeof(int) * st._top);
        _top = st._top;
        _capacity = st._capacity;
    }
    return *this;
}

六.运算符重载基础

6.1 什么是运算符重载

给类类型对象重新定义运算符的含义

写法:

cpp 复制代码
返回值 operator运算符(参数)

6.2 不能重载的 5 个运算符

.* :: sizeof ? : .

6.3 前置 ++ 与 后置 ++ 区分

  1. 前置++:Date& operator++()
  2. 后置++:Date operator++(int)(int 是占位参数)

七.const 成员函数

7.1作用

const 修饰 this指针 ,表示函数内不能修改成员变量

7.2写法

cpp 复制代码
void Print() const //写在最右边
{
    cout << _year << endl;
}

7.3权限规则

  1. const 对象只能调用 const 成员函数
  2. const 对象可以调用任意函数

八.取地址重载

编译器默认生成即可,几乎不用自己写

cpp 复制代码
Date* operator&()
{
    return this;
}

const Date* operator&() const
{
    return this;
}

九.日期类 Date(实践作业)

9.1Data.h

cpp 复制代码
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;

class Date
{
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);

public:
    Date(int year = 1900, int month = 1, int day = 1);
    bool CheckDate();
    int GetMonthDay(int year, int month);

    bool operator<(const Date& d) const;
    bool operator<=(const Date& d) const;
    bool operator>(const Date& d) const;
    bool operator>=(const Date& d) const;
    bool operator==(const Date& d) const;
    bool operator!=(const Date& d) const;

    Date& operator+=(int day);
    Date operator+(int day) const;
    Date& operator-=(int day);
    Date operator-(int day) const;
    int operator-(const Date& d) const;

    Date& operator++();
    Date operator++(int);
    Date& operator--();
    Date operator--(int);

    void Print() const;

private:
    int _year;
    int _month;
    int _day;
};

9.2Data.cpp

cpp 复制代码
#include "Data.h"

int Date::GetMonthDay(int year, int month)
{
    static int arr[] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
    if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
        return 29;
    return arr[month];
}

bool Date::CheckDate()
{
    if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month))
        return false;
    return true;
}

Date::Date(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;
    if (!CheckDate())
        cout << "日期非法" << endl;
}

bool Date::operator<(const Date& d) const
{
    if (_year < d._year) return true;
    if (_year == d._year && _month < d._month) return true;
    if (_year == d._year && _month == d._month && _day < d._day) return true;
    return false;
}

bool Date::operator<=(const Date& d) const { return *this < d || *this == d; }
bool Date::operator>(const Date& d) const { return !(*this <= d); }
bool Date::operator>=(const Date& d) const { return !(*this < d); }
bool Date::operator==(const Date& d) const
{
    return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator!=(const Date& d) const { return !(*this == d); }

Date& Date::operator+=(int day)
{
    if (day < 0) return *this -= -day;
    _day += day;
    while (_day > GetMonthDay(_year, _month))
    {
        _day -= GetMonthDay(_year, _month);
        _month++;
        if (_month == 13)
        {
            _year++;
            _month = 1;
        }
    }
    return *this;
}

Date Date::operator+(int day) const
{
    Date tmp = *this;
    tmp += day;
    return tmp;
}

Date& Date::operator-=(int day)
{
    if (day < 0) return *this += -day;
    _day -= day;
    while (_day <= 0)
    {
        _month--;
        if (_month == 0)
        {
            _year--;
            _month = 12;
        }
        _day += GetMonthDay(_year, _month);
    }
    return *this;
}

Date Date::operator-(int day) const
{
    Date tmp = *this;
    tmp -= day;
    return tmp;
}

int Date::operator-(const Date& d) const
{
    Date max = *this;
    Date min = d;
    int flag = 1;
    if (*this < d)
    {
        max = d;
        min = *this;
        flag = -1;
    }
    int n = 0;
    while (min != max)
    {
        ++min;
        ++n;
    }
    return n * flag;
}

Date& Date::operator++()
{
    *this += 1;
    return *this;
}

Date Date::operator++(int)
{
    Date tmp = *this;
    *this += 1;
    return tmp;
}

Date& Date::operator--()
{
    *this -= 1;
    return *this;
}

Date Date::operator--(int)
{
    Date tmp = *this;
    *this -= 1;
    return tmp;
}

void Date::Print() const
{
    cout << _year << "-" << _month << "-" << _day << endl;
}

ostream& operator<<(ostream& out, const Date& d)
{
    out << d._year << "年" << d._month << "月" << d._day << "日";
    return out;
}

istream& operator>>(istream& in, Date& d)
{
    cout << "请输入年月日:";
    in >> d._year >> d._month >> d._day;
    if (!d.CheckDate())
        cout << "日期非法" << endl;
    return in;
}

🎯总结

  1. 构造函数:创建对象自动调用,初始化,可重载
  2. 析构函数:对象销毁自动调用,释放资源,只能一个
  3. 拷贝构造:用对象初始化新对象,必须传引用,深浅拷贝是关键
  4. 赋值重载 :已存在对象赋值,必须检查自身赋值,返回* this
  5. 浅拷贝:编译器默认生成,指针类型会崩溃
  6. 深拷贝:自己实现,重新开空间,拷贝数据
  7. const 成员 :修饰this,不能修改成员,const对象只能调用 const 函数
  8. 日期类:运算符重载最经典综合练习

⚠️易错点

  1. 拷贝构造传值导致无穷递归
  2. 有指针资源不写深拷贝,程序崩溃
  3. 赋值重载不判断自己给自己赋值
  4. 分不清拷贝构造赋值重载
  5. const对象调用const成员函数报错
  6. 无参构造写成Date d();被当成函数声明
  7. 内置类型成员,编译器默认构造不初始化

👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞 鼓励原创,让优质内容被更多人看见
⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅
💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步

相关推荐
2301_7736436227 分钟前
ceph镜像
前端·javascript·ceph
To_OC1 小时前
万字解析《JS语言精粹》之第四章:函数15大核心精髓(JS灵魂核心)
前端·javascript·代码规范
宋拾壹1 小时前
同时添加多个类目
android·开发语言·javascript
IT知识分享1 小时前
从零开发在线简繁转换工具:OpenCC 实战、避坑经验与方案选型
javascript·python
川冰ICE1 小时前
JavaScript实战④|天气查询应用,调用API与异步处理
javascript·css·css3
微扬嘴角1 小时前
react篇4--setState、LazyLoad和Hooks
前端·javascript·react.js
凡人叶枫1 小时前
Effective C++ 条款04:确定对象被使用前已先被初始化
java·linux·开发语言·c++·嵌入式开发
杨梦馨1 小时前
万级数据表格卡死?Web Worker 一招搞定
前端·javascript·vue.js
用户484526255821 小时前
JavaScript 数组不是数组,是对象
javascript
用户484526255822 小时前
用栈模拟队列:算法题背后的原型链课
javascript