(入门自用)C++ 类与对象(中)构造/析构/拷贝构造函数+运算符重载

先写一个日期类

class Date
{
public:
    void Init(int year, int month, int day)
    {
    cout<<this<<endl;
    this->_year = year;
    this->_month = month;
    this->_day = day;
    }
    void Print()
    {
    cout <<_year<< "-" <<_month << "-"<< _day <<endl;
    }
private:
    int _year; // 年
    int _month; // 月
    int _day; // 日
  
};
int main()
{
    Date d1, d2;
    d1.Init(2022,7,17);
    d2.Init(2022,7,18);
    d1.Print();
    d2.Print();
    return 0;
}

 栈类

typedef int DataType;
class Stack
{
public:
    void Init(size_t capacity = 3)
    {
        _array = (DataType*)malloc(sizeof(DataType)* capacity);
        if (NULL == _array)
        {
        perror("malloc申请空间失败!!!");
        return;
        }
        _capacity = capacity;
        _size = 0;
    }
    void Push(DataType data)
    {
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }
private:
    DataType* _array;
    int _capacity;
    int _size;
};

为了避免类似栈类使用时,因为没有调用初始化而导致的随机值和崩溃,采用了构造函数。

构造函数

构造函数是一个特殊的成员函数。主要任务不是开空间创建对象,而是初始化。

特征:

1.函数名与类名相同

2.没有返回值 //void也不写

3.对象实例化时,编译器自动调用对应的构造函数

4.构造函数可以重载

5.可以在类中声明,类外定义。

class Date
{
public:
    //void Init(int year, int month, int day)
    //{
    //cout<<this<<endl;
    //this->_year = year;
    //this->_month = month;
    //this->_day = day;
    //}
    Date(int year=2022,int month=7,int day=17)//可以不缺省
    {
        _year=year;
        _month=month;
        _day=day;
    }


    Date()//与全缺省在调用时冲突
    {
        _year=1;
        _month=1;
        _day=1;
    }

    void Print()
    {
    cout <<_year<< "-" <<_month << "-"<< _day <<endl;
    }
private:
    int _year; // 年    
    int _month; // 月
    int _day;// 日
};
int main()
{
    Date d1(2022,7,17);//三个参数都要写 可以再写重载 以传两个参数
    Date d2;// 1 1 1
    return 0;
}

  Date(int year=2022,int month=7,int day-17)
    { _year=year;  _month=month;  _day=day;  }

Date()
    {_year=1;  _month=1;  _day=1;  }

 这两类构造函数,写一种即可.

默认生成构造函数

即无参的,全缺省的构造函数

C++中如果没有写构造函数,系统会自动生成一个.但C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如: int/char/指针...自定义类型就是我们使用class/struct/union等自己定义的类型.

默认生成构造函数,a:内置类型成员不做处理 b:自定义类型回去调用它的默认构造函数。

对于全部是自定义类型的:

class MyQueue
{
private:
    Stack _st1;
    Stack _st2;
};
int main()
{
    MyQueue q;//使用了默认构造函数 自动初始化
   return 0;
}

 对于不全是自定义类型:

class Date
{
private:
    int _year; // 年   
    int _month; // 月
    int _day; // 日
    int a;
};
int main()
{
    Date d1;//这里全是随机值
    return 0;
}

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在类中声明时
可以给默认值
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 2022;
 int _month = 7;  //当构造函数没写的时候 可以在这里给缺省值(不是初始化!)
 int _day = 17;
};
int main()
{
 Date d;// 20222 7 17
 return 0; 
}

explicit修饰的构造函数

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值,但其余均有默认值的构造函数,还具有类型转换的作用。

class Date
{
public:
    //单参构造函数
    Date(int year) 
        :_year(year)
    {}

    //除第一个没有默认参数,其余均有的构造函数
    Date(int year,int month=1,int day=1) 
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}
    //赋值操作符的重载
    Date& operator=(const Date& d)
    {
        if(this!=&d)//不给自己赋值
        {
            _year=d._year;
            _month=d._month;
            _day=d._day;
        }
        return *this;
    }
    
private:
    int _year;
    int _month,
    int _day;
};

void Test()

{

Date d1(2022); / /实际编译器背后会用2023构造一个无名对象

d1 = 2023;//最后用无名对象给d1进行赋值

}

explicit

explicit修饰构造函数,禁止类型转换

explicit Date(int year) 
        :_year(year)
    {}

explicit Date(int year,int month=1,int day=1) 
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}

在调用 Test()时 编译报错。

析构函数

在对象销毁时自动调用,完成清理工作

先定义的后析构

先构造全局对象,在构造局部静态对象,最后才构造普通对象,然而析构对象的顺序是完全按照构造的相反顺序进行的

特征

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。// void 也不写

3. 一个类只能有一个析构函数。没有自己写,系统会自动生成析构函数。

注意:析构函数不能重载

4. 对象生命周期结束时,编译系统自动调用析构函数。

5.可以类中声明类外定义。

6.析构函数中不能delete this,会造成无限递归。

第一类:

没有必要写析构函数

class Date
{
public:
    Date(int year=2022,int month=7,int day=17)//可以不缺省
    {
        _year=year;
        _month=month;
        _day=day;
    }
    ~Date()
    {
        //~Date()没什么要清理的
        //这里以打印一下以表示析构函数被调用
        cout<<"~Date()"<<endl;    
    }

private:
    int _year; // 年    
    int _month; // 月
    int _day;// 日
};

year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可。

第二类:

必须要写析构函数

typedef int DataType;
class Stack
{
public:
    void Init(size_t capacity = 3)
    {
        _array = (DataType*)malloc(sizeof(DataType)* capacity);
       //
       //..
    }
    ~Stack()
    {
        free(_array);//这里由于是开辟了新空间,所以需要手动释放
        _capacity=_size=0;//可不写
        _array=nullptr;//可不写
    }

private:
    DataType* _array;
    int _capacity;
    int _size;
};

第三类:

默认生成的够用

class MyQueue
{
public:
    void push(int x)
    {}
    //这里不需写析构函数。但是默认生成的析构函数对于Stack自定义成员
    //会调用Stack析构函数
private:
    Stack _st1;
    Stack _st2;
    size_t _size=0;//不对内置类型处理 利用补丁
};

默认生成析构函数,a:内置类型成员不做处理 b:自定义类型回去调用它的默认析构函数。

构造生成与析构清理顺序

构造按前后顺序

aa0 aa4存在静态区 静态数据在第一次调用的时候初始化.

aa1 aa2在mian函数的栈帧里面,析构完了再去进行静态区的.

拷贝构造函数

对象在实例化时都需要调用构造函数

拷贝构造 即用同类型的参数利用构造函数进行初始化。

int main()
{
    Date d1(2022,7,23);
    Date d2(d1);//此时调用拷贝构造函数
    Date d3=d1;
    return 0;
}

拷贝构造函数的参数只有一个且必须是类类型对象的引用。(因为传参也要调用拷贝构造)

class Date
{
public:
    Date(int year=2022,int month=7,int day=23)
    {
        _year=year;
        _month=month;
        _day=day;
    }
    //Date d2(d1)
    Date(const Date& d)//拷贝构造 d1的值不能被修改
    {  //this指针的year        //d1的year
        _year        =         d.year;
        _month=d.month;
        _day=d.day;
    }
private:
    int _year; // 年   
    int _month; // 月
    int _day; // 日
};

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调 用其拷贝构造函数完成拷贝的。

拷贝构造函数--调用场景

拷贝构造函数是一个特殊的构造函数,是单参,且参数类型是类对象的引用。

调用时机:

一、用类对象构造另一个类对象

A a1;A a2(a1);

 二、传递参数

void Func(A a){};

 三、返回值

A Func(A a){ return a};

运算符重载

即自定义类型进行运算,无法直接使用运算符.想运算要运算符重载.

格式 : 返回值类型 operator操作符(参数列表)

注意

  1. 第一个参数是左操作数 第二个参数是右操作数..顺序不能改
  2. 运算符优先级
  3.   .*  ::  sizeof  ?:   . 注意以上5个运算符不能重载。

下面实现"=="的重载:

bool operator==(const Date& x1, const Date& x2)//不改变最好加上const
{    //这里year month day需要在public 否则不能读取
    return x1.year==x2.year && x1.month==x2.month
    && x1.day==x2.day
}
//上述是对 == 这个运算符进行重载
//返回值根据需要更改
int main()
{
    Date d1(2022,7,23);
    Date d2(2022,7,24);
    //d1==d2?
    cout<< (d1==d2) <<endl;
    return 0;
}

为了避免因为无法访问而必须进入public,还可以将运算符重载放在类里面

class Date
{ 
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
    _year = year;
    _month = month;
    _day = day;
 }
 
 // bool operator==(Date* this, const Date& d2)
 // 这里需要注意的是,左操作数是this,指向调用函数的对象
 bool operator==(const Date& d2)
 {
 return _year == d2._year;
     && _month == d2._month
     && _day == d2._day;
 }
private:
 int _year;
 int _month;
 int _day;
};

赋值运算符

Date& operator=(const Date& d)
{
    _year=d.year;
    _month=d.month;
    _day=d.day;
    return this*;
}

    
void operator=(const Date&d)//区别是这样不能连续调用
{                           //d1=d2=d3;
    _year=d.year;
    _month=d.month;
    _day=d.day;
}

注意:

  1. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  2. 检测是否自己给自己赋值
  3. 返回*this :要复合连续赋值的含义
  4. 赋值运算符重载只能是类的成员函数
  5. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

前置++和后置++重载

// 前置++:返回+1之后的结果
 // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
 Date& operator++()
 {
     _day += 1;
     return *this;
 }

为了与前置++区分 规定后置++重载时多增加一个int类型的参数

Date operator++(int)
 {
    Date temp(*this);
    _day += 1;
    return temp;
 }

后置++需要返回+1之前的旧值,故需在实现时需要先将this保存一份, 然后给this+1

流插入重载和流提取重载

class Date
{    //友元函数
    friend void operator<<(ostream& out,const Date& d);
pubilic:
private:
 int _year;
 int _month;
 int _day;
}
//流插入重载
inline ostream& operator<<(ostream& out,const Date& d)
{
    out<<d._year<<"年"<<d._month<<"月"<<d._day<<"日"<<endl;
    return out;
}


void operator<<(ostream& out,const Date& d)//不支持连续使用
{
    out<<d._year<<"年"<<d._month<<"月"<<d._day<<"日"<<endl;
    return out;
}
//流提取重载
inline istream& operator>>(istream& in,Date& d)
{
    in>>d._year>>d._month>>d._day;
    return in;
}

int main()
{
    Date d1(2022,7,25)
    cout<<d1;
    return 0;
}

上述两个运算符重载不能写成成员函数,因为this指针会占用其中第一个参数,因而使用时变成了

d1<<cout;

区分:

运算符重载:让自定义类型对象可以使用运算符。转换成调用这个重载函数。

函数重载:支持函数名相同的函数同时存在

运算符重载和函数重载虽然都使用重载这个词,但他们之间没有必然联系。

const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this 指针。

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


int main()
{
    Date d1(2022,7,25);
    const Date d2(2022,7,25);
    d1.Print();// &d1--Date*
    d2.Print();// &d2--const Date* 
    d1<d2;
    d2<d1://err
    return 0;
}

如果d2调用Print或者作为左操作数会有权限放大。为了解决问题,在不改变的成员函数后面加上const

bool Date::operator<(const Date& d) const
{
    return !(*this>=d);//这里复用了 >=的操作符重载
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值