欢迎光临
我们一直在努力

类和对象(中)

在这里插入图片描述

个人主页
流年如梦

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

文章目录

  • 一.类的 6 个默认成员函数
  • 二.构造函数
    • 2.1作用
    • 2.2特征
    • 2.3举例
    • 2.4编译器默认生成构造的行为
  • 三.析构函数
    • 3.1作用
    • 3.2特征
    • 3.3栈类示例(有资源必须写析构)
    • 3.4什么时候必须写析构
  • 四.拷贝构造函数
    • 4.1 作用
    • 4.2必须传引用,不能传值!
    • 4.3浅拷贝与深拷贝
    • 4.4Stack 深拷贝拷贝构造
    • 4.5拷贝构造调用场景
  • 五.赋值运算符重载
    • 5.1 作用
    • 5.2 特征
    • 5.3 标准写法
    • 5.4 拷贝构造 vs 赋值重载
    • 5.5 Stack 深拷贝赋值重载
  • 六.运算符重载基础
    • 6.1 什么是运算符重载
    • 6.2 不能重载的 5 个运算符
    • 6.3 前置 ++ 与 后置 ++ 区分
  • 七.const 成员函数
    • 7.1作用
    • 7.2写法
    • 7.3权限规则
  • 八.取地址重载
  • 九.日期类 Date(实践作业)
    • 9.1Data.h
    • 9.2Data.cpp
  • 🎯总结
  • ⚠️易错点

前言

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

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

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

  • 构造函数 –> 初始化对象
  • 析构函数 –> 清理资源
  • 拷贝构造函数 –> 用对象初始化新对象
  • 赋值运算符重载 –> 对象之间赋值
  • 普通对象取地址重载
  • const 对象取地址重载
  • 重点掌握前 4 个,后两个几乎不用自己实现

    二.构造函数

    2.1作用

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

    2.2特征

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

    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编译器默认生成构造的行为

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

    3.1作用

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

    3.2特征

  • 名字:~类名
  • 无参、无返回值
  • 一个类只能有一个
  • 不写则编译器自动生成
  • 先构造的对象后析构
  • 3.3栈类示例(有资源必须写析构)

    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 作用

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

    写法:

    类名(const 类名& other)

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

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

    // 错误写法(递归爆炸)
    Date(Date d)
    {}

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

    4.3浅拷贝与深拷贝

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

  • 逐字节拷贝
  • 指针会指向同一块内存
  • 析构两次 –> 崩溃
  • 深拷贝(需要自己写)

  • 重新开辟空间
  • 拷贝内容
  • 两个对象独立
  • 4.4Stack 深拷贝拷贝构造

    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拷贝构造调用场景

  • Date d2(d1);
  • Date d2 = d1;
  • 函数传值传参
  • 函数传值返回
  • 五.赋值运算符重载

    5.1 作用

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

    5.2 特征

  • 必须是成员函数
  • 参数:const 类名&
  • 返回值:类名&(支持连续赋值)
  • 必须检查自己给自己赋值
  • 5.3 标准写法

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

    5.4 拷贝构造 vs 赋值重载

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

    5.5 Stack 深拷贝赋值重载

    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 什么是运算符重载

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

    写法:

    返回值 operator运算符(参数)

    6.2 不能重载的 5 个运算符

    .*::sizeof? : .

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

  • 前置++:Date& operator++()
  • 后置++:Date operator++(int)(int 是占位参数)
  • 七.const 成员函数

    7.1作用

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

    7.2写法

    void Print() const //写在最右边
    {
    cout << _year << endl;
    }

    7.3权限规则

  • const 对象只能调用 const 成员函数
  • 非 const 对象可以调用任意函数
  • 八.取地址重载

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

    Date* operator&()
    {
    return this;
    }

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

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

    9.1Data.h

    #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

    #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;
    }

    🎯总结

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

  • 拷贝构造传值导致无穷递归
  • 有指针资源不写深拷贝,程序崩溃
  • 赋值重载不判断自己给自己赋值
  • 分不清拷贝构造和赋值重载
  • const对象调用非const成员函数报错
  • 无参构造写成Date d();被当成函数声明
  • 内置类型成员,编译器默认构造不初始化
  • 👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长 ❤️ 点赞 鼓励原创,让优质内容被更多人看见 ⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅 💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步

    赞(0)
    未经允许不得转载:171主机测评 » 类和对象(中)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址