0


【C++】类和对象(中篇)

文章目录

类的6个默认成员函数

class Date{ };

看起来这只是一个普通的空类!
但是当真里面什么都没有吗??
其实不然,尽管它是一个空类,编译器依旧会为其生成6个默认的成员函数

具体如下:
在这里插入图片描述
默认成员函数的特点?
就是 用户自己没有写,编译器会自动生成。一旦用户显式提供,那么就不再生成
下面我们一 一详细理解每个函数

构造函数

是一个特殊的成员函数

主要特点:

  1. 函数名与类名相同
  2. 没有返回值类型
  3. 创建类类型的对象的时候由编译器自动调用
  4. 在对象整个生命周期中只调用一次
  5. 构造函数可以重载 也可以在定义时给一个全缺省参数(无参 && 全参一次搞定)

代码验证:
情景一:使用编译器提供的默认构造函数,查看实例化一个对象到底做了什么
在这里插入图片描述
情景二:自定义一个构造函数,查看实例化对象做了什么
在这里插入图片描述
实验:
在这里插入图片描述
情景三:在上例的基础上,以

Date d1

的方式初始化会怎样?
在这里插入图片描述
情景四:用户实现一个构造函数,既可以传递参数,也适用于无参构造(全缺省构造)
在这里插入图片描述
此时就不会有报错了
在这里插入图片描述
注:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

调用场景

创建新对象的时候,由编译器自动调用,并且在整个生命周期内只调用1次
在这里插入图片描述

编译器生成的默认构造函数到底有用没?

答案是有用的!
情景模拟:
前提:有两个类,分别是Time和Date类,Time类的对象作为Date类的一个成员变量。Date类没有自定义构造函数,Time类自定义了构造函数,我们实例化一个Date类的对象,观察现象:
在这里插入图片描述
分析构造过程如下:
在这里插入图片描述

析构函数

概念

是一个特殊的成员函数
构造函数功能相反,析构函数不是完整对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作

特点

  1. 函数名是 ~类名
  2. 无参数,无返回值
  3. 一个类有且仅有1个析构函数。若未显式定义,系统会自动生成默认的析构函数
  4. 对象生命周期结束时,C++编译系统自动调用析构函数
  5. 编译器自动生成的析构函数,对会自定义类型成员调用它的析构函数 验证: 在Date类中自定义析构函数,观察析构函数的调用场景在这里插入图片描述在这里插入图片描述在这里插入图片描述 冷静分析一下,我们发现,我们实例化的对象d是在main函数的栈帧上开辟的空间,那我们在函数调用完毕后该函数的栈帧也就被释放,那么在该栈帧上的变量也就随之消亡。因此,在这种情况下,析构函数也就可有可无了。 但是析构函数还是有它的用武之地的~~看下面的例子

调用场景

前提:我们使用C++的方式实现一个栈的结构。它的具体操作如下:

//stack.h#pragma once#include<iostream>usingnamespace std;typedefint SDataType;classStack{public:Stack();~Stack();voidStackPush(SDataType x);voidStackPop();
    SDataType StackTop();intStackSize();intStackEmpty();//1:空 0:非空private:voidStackExpand();private:
    SDataType* _array;int _size;int _capacity;};//测试函数voidTestStack();
//stack.cpp#include"stack.h"

Stack::Stack(){
    _array =(SDataType*)malloc(sizeof(SDataType)*3);if(nullptr== _array){perror("malloc");return;}
    _size =0;
    _capacity =3;}void Stack::StackExpand(){//1、申请新空间(这里按照原空间的2倍申请)int newCapacity = _capacity *2;
    SDataType* tmp =(SDataType*)malloc(sizeof(SDataType)*newCapacity);if(nullptr== tmp){perror("malloc");return;}//2、将旧空间的值copy的新空间for(int i =0; i < _capacity; i++){
        tmp[i]= _array[i];}//3、释放旧空间free(_array);//4、使用新空间
    _array = tmp;
    _capacity = newCapacity;}

Stack::~Stack(){if(_array){free(_array);
        _size =0;
        _capacity =0;}}void Stack::StackPush(SDataType x){if(_size == _capacity){//扩容StackExpand();}
    _array[_size++]= x;}void Stack::StackPop(){if(!StackEmpty()){
        _size--;}}
SDataType Stack::StackTop(){if(StackEmpty()){perror("StackEmpty!");return-1;}return _array[_size -1];}int Stack::StackSize(){return _size;}int Stack::StackEmpty()//1:空 0:非空{return0== _size;}voidTestStack(){
    Stack s;
    s.StackPush(1);
    s.StackPush(2);
    s.StackPush(3);
    s.StackPush(4);
    s.StackPush(5);
    s.StackPush(6);

    cout << s.StackSize()<< endl;
    cout << s.StackTop()<< endl;

    s.StackPop();
    s.StackPop();

    cout << s.StackSize()<< endl;
    cout << s.StackTop()<< endl;}

对于上述实现的代码,我们着重关注一下它的析构函数
在这里插入图片描述
我们可以看到,该析构函数对程序从堆上申请的资源进行了释放。如果没有该析构函数,那就会发生内存泄露。
因此,我们可以得到:
只要程序涉及到资源的申请,那么析构函数必须要程序员手动实现,默认的析构函数是无法达到我们的需求的。

拷贝构造函数

概念

只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰) ,在用 已存在的类类型对象创建新对象的时候,由编译器自动调用。

特性

  1. 拷贝构造函数是构造函数的重载形式
  2. 拷贝构造函数的参数只有一个,并且必须使用引用传参,使用传值传参就会发生无穷递归在这里插入图片描述
  3. 若未显式定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储,按字节序完成拷贝,这种拷贝我们叫做浅拷贝或者值拷贝 浅拷贝在不涉及资源申请的类中可以正常使用,但是在涉及资源申请的类中是无法正常使用的 后者会在释放申请资源的时候,调用两次析构函数,导致程序崩溃
  4. 默认生成的拷贝构造不能满足申请资源的情况,因此需要我们实现深拷贝

典型调用场景

  1. 使用已经存在的对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象

运算符重载

为什么要有运算符重载

一句话,就是为了提高代码的可读性

基本形式

  • 是一个具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
  • 函数名: 关键字operator后面加需要重载的运算符符号
  • 函数原型: 返回值类型 operator操作符(参数列表)

注意事项

  1. 不能通过连接其他符号来创建新的操作符:比如 operator @
  2. 重载操作符必须有一个类类型或者枚举类型的操作数
  3. 用于内置类型的操作符,其含义不能改变。例如内置类型的+,不能改变其含义
  4. 作为类成员的重载函数时,其形参看起来比操作数数目少一,成员函数的操作符有一个默认的形参this限定为第一个形参
  5. 5个运算符不能重载在这里插入图片描述

赋值操作符重载

  1. 形式
 返回值类型  operator=(参数){....}
  1. 参数类型 一般都是类类型对象的引用 相较于传值这样就会少了形参实例化的拷贝构造
  2. 返回值 一般返回 *this 是类类型对象的引用
  3. 检测是否在给自己赋值if(this != &d)

来一个赋值操作符重载的例子
在这里插入图片描述
一个类如果没有显示定义赋值运算符重载函数,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)
和前面一样,对于需要申请资源的类来说,赋值运算符重载依旧需要我们自己编写,否则程序在最后析构的时候就会发生崩溃。

日期类实现

date.h
#pragma once#include<iostream>usingnamespace std;classDate{public:Date(int year =2001,int month =5,int day =7);voidPrint();// 获取某年某月的天数intGetMonthDay(int year,int month);// 拷贝构造函数// d2(d1)Date(const Date& d);// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)
    Date&operator=(const Date& d);// 日期+=天数
    Date&operator+=(int day);// 日期+天数
    Date operator+(int day);// 日期-天数
    Date operator-(int day);// 日期-=天数
    Date&operator-=(int day);// 前置++
    Date&operator++();// 后置++
    Date operator++(int);// 后置--
    Date operator--(int);// 前置--
    Date&operator--();// >运算符重载booloperator>(const Date& d);// ==运算符重载booloperator==(const Date& d);// >=运算符重载inlinebooloperator>=(const Date& d);// <运算符重载booloperator<(const Date& d);// <=运算符重载booloperator<=(const Date& d);// !=运算符重载booloperator!=(const Date& d);// 日期-日期 返回天数intoperator-(const Date& d);private:int _year;int _month;int _day;};
date.cpp
#include"date.h"

Date::Date(int year,int month,int day){//检查日期的合法性if(year >=0&& month >0&& month <=12&& day >0&& day <=GetMonthDay(year, month)){
        _year = year;
        _month = month;
        _day = day;}else{
        cout <<"非法日期"<< endl;}}void Date::Print(){
    cout << _year <<"-"<< _month <<"-"<< _day << endl;}// 拷贝构造函数// d2(d1)
Date::Date(const Date& d){
    _year = d._year;
    _month = d._month;
    _day = d._day;}// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)
Date&  Date::operator=(const Date& d){if(this!=&d){
        _year = d._year;
        _month = d._month;
        _day = d._day;}return*this;}// 获取某年某月的天数inlineint Date::GetMonthDay(int year,int month){staticint dayArray[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};int day = dayArray[month];if(2== month &&((year %4==0&& year %100!=0)|| year %400==0)){
        day =29;}return day;}/// 日期+=天数
Date&  Date::operator+=(int day){if(day <0){*this-=-day;}else{
        _day += day;while(_day >GetMonthDay(_year,_month)){//产生进位,调整为正确的日期格式//减掉当月的天数,然后将月+1
            _day -=GetMonthDay(_year, _month);++_month;//判断月是否越界,月满了的话年+1if(_month >12){++_year;
                _month =1;}}}return*this;}// 日期+天数
Date  Date::operator+(int day){//日期不能发生变化,返回的是日期加上天数的结果(因此无法使用引用返回)//可以复用+=
    Date tmp(*this);
    tmp += day;return tmp;}// 日期-=天数
Date& Date::operator-=(int day){if(day <0){*this+=-day;}else{
        _day -= day;while(_day <=0){//月先--,因为借的是上一个月的天数--_month;if(_month ==0){--_year;
                _month =12;}
            _day +=GetMonthDay(_year, _month);}}return*this;}// 日期-天数
Date Date::operator-(int day){//拷贝构造当前对象
    Date tmp(*this);//复用-=
    tmp -= day;return tmp;}///// 前置++
Date&  Date::operator++(){*this+=1;return*this;}// 后置++(int是一个占位符,不起实际作用)
Date  Date::operator++(int){
    Date tmp(*this);*this+=1;return tmp;}// 后置--
Date Date::operator--(int){
    Date tmp(*this);*this-=1;return tmp;}// 前置--
Date& Date::operator--(){*this-=1;return*this;}/// >运算符重载bool Date::operator>(const Date& d){if(_year > d._year){returntrue;}elseif(_year == d._year){if(_month > d._month){returntrue;}elseif(_month == d._month){if(_day > d._day){returntrue;}}}returnfalse;}// ==运算符重载bool Date::operator==(const Date& d){return _year == d._year &&
        _month == d._month &&
        _day == d._day;}// >=运算符重载inlinebool Date::operator>=(const Date& d){return*this> d ||*this== d;}// <运算符重载bool Date::operator<(const Date& d){return!(*this>= d);}// <=运算符重载bool Date::operator<=(const Date& d){return*this< d ||*this== d;}// !=运算符重载bool Date::operator!=(const Date& d){return!(*this== d);}//// 日期-日期 返回天数int Date::operator-(const Date& d){
    Date max =*this;
    Date min = d;int flag =1;if(max < min){
        max = d;
        min =*this;
        flag =-1;}int count =0;while(min != max){++min;
        count++;}return count* flag;}

具体的测试代码见gitee

const成员函数

  1. const修饰类的成员函数将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。在这里插入图片描述
  2. 几个经典问题 2.1const对象可以调用非const成员函数吗?(不行)在这里插入图片描述 2.2 非const对象可以调用const成员函数吗?(可以)在这里插入图片描述 2.3 const成员函数内可以调用其它的非const成员函数吗?(不行)在这里插入图片描述 2.4非const成员函数内可以调用其它的const成员函数吗?(可以)在这里插入图片描述

取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

classDate{public:
 Date*operator&(){returnthis;}const Date*operator&()const{returnthis;}private:int _year ;int _month ;int _day ;};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

以上就是类和对象中6个默认函数的基本认识~~感觉有收获的小伙伴,多多分享给身边的友友们!!

在这里插入图片描述

标签: c++ 开发语言 后端

本文转载自: https://blog.csdn.net/Suk_god/article/details/123589822
版权归原作者 Suk-god 所有, 如有侵权,请联系我们删除。

“【C++】类和对象(中篇)”的评论:

还没有评论