构造函数
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
//using namespace std;
class Data
{
public:
void Init(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
d1.Init(2021, 10, 12);
return 0;
}
当上段代码中的d1忘记初始化时,只是构造函数就起了至关重要的作用
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
构造函数的特征:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载(提供多种初始化的方式)。
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
总结:
不写构造函数,编译器默认生成构造函数
编译器默认生成的构造函数:
1.内置类型不会初始化
2.自定义类型它会调用它的无参构造函数进行初始化
上方的代码(c++98)的语法有些繁琐,而在c++11中可以优化
注:这里的成员变量只是定义,不是初始化,就像函数缺省参数一样,给的是缺省值。
注:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
默认构造函数可以理解为不传参就可以调用的函数
注:成员变量的命名风格一般都是加个前缀或者后缀标识区分就行。
析构函数
析构函数是特殊的成员函数。
其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
注:上方的代码中Data类中的析构函数可以不写;但Stack中的析构函数必须写
注:
析构函数跟构造函数类似,编译器默认生成的析构函数:
1.内置类型成员不处理
2.自定义类型成员会去调用自身的析构函数
拷贝构造函数
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用.
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
拷贝函数中的参数必须是引用传参或指针,不然会造成死递归
注:
如果类中没写拷贝构造,编译器会默认生成拷贝构造函数,与构造函数和析构函数不同:
1.内置类型,字节序的浅拷贝(按字节序的拷贝)
2.自定义类型,会去调用它的的拷贝构造函数
注:编译器默认生成拷贝构造函数并不能解决所有问题,像Stack这样的类,编译器默认生成拷贝构造完成的就是浅拷贝。解决方法:实现深拷贝的拷贝构造函数。
若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数:operator 运算符
参数:操作符的操作数有几个就有几个参数,参数类型你要操作的类型对象
返回值:看运算符运算后的返回值是什么
总结:
- 运算符默认都是给内置类型变量用的
- 自定义类型的变量想用这些操作符,就得自己进行运算符重载
- 运算符重载的意思是自己去写一个函数定义实现这里的运算符行为
- 运算符号重载写成全局的可以,但是面临访问私有成员变量的问题
注:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员,函数的操作符有一个默认的形参this,限定为第一个形参
- .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
按正常运算符重载无法区分前置++和后置++,为了区分,这里就做了一个特殊处理,给后置++增加int参数,这样就构成了函数重载
赋值运算符重载
赋值运算符主要有四点:
- 参数类型
- 返回值
- 检测是否自己给自己赋值
- 返回*this
- 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
如果自己不写,编译器会默认生成赋值重载,跟拷贝构造类似,内置类型成员会完成值拷贝,自定义类型成员会调用它的赋值重载
原则上:编译器默认生成的能完成自己要的功能就可以不写,不能完成就需要我们自己写一个.。
实现数据结构类,基本都要自己写这几个成员函数(构造函数、析构函数、拷贝函数、赋值重载都需要自己写,默认生成的都有问题,不能用)
总结:
常用短小函数,直接在类里面定义,他们默认就是inline
C++为了增强程序的可读性,提出运算符重载,并且让我们可以实现运算符重载函数,控制这个运算符行为
一个类到底要重载哪些运算符,并且要考虑重载了这个运算符有没有意义
运算符重载和函数重载,都用了重载这个词,但是他们之前没有关联
取地址及const取地址操作符重载
class Date
{
public :
Date* operator&()//取地址操作符重载
{
return this ;
}
const Date* operator&()const//const取地址操作符重载
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
不想让别人获取到地址信息
class Date
{
public :
Date* operator&()//取地址操作符重载
{
return nullptr ;
}
const Date* operator&()const//const取地址操作符重载
{
return nullptr ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人获取到指定的内容。
练习(类的默认成员函数)
Date.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
public:
int GetMonthDay(int year, int month)
{
static int date[13] = { 0,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 date[month];
}
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
if (_year < 0 || _month < 1 || _month>12 || _day<=0 || _day>GetMonthDay(_year, _month))
{
cout << this->_year << "/" << this->_month << "/" << this->_day;
cout << "非法日期" << endl;
}
}
void print()const
{
cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}
Date& operator += (int day);
Date operator +(int day)const;
Date& operator -=(int day);
Date operator -(int day)const;
Date& operator ++();
Date operator ++(int);
Date& operator --();
Date operator --(int);
int operator-(const Date& d)const;
bool operator >(const Date& d)const
{
if (_year > d._year)
return true;
else if (_year == d._year && _month > d._month)
return true;
else if (_year == d._year && _month == d._month && _day > d._day)
return true;
else
return false;
}
bool operator >=(const Date& d)const
{
return *this > d || *this == d;
}
bool operator <(const Date& d)const
{
return !(*this >= d);
}
bool operator <=(const Date& d)const
{
return *this < d || *this == d;
}
bool operator ==(const Date& d)const
{
return _day == d._day && _month == d._month && _year == d._year;
}
bool operator !=(const Date& d)const
{
return !(*this == d);
}
Date* operator &()
{
return this;
}
const Date* operator &()const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
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 > 12)
{
_month = 1;
_year++;
}
}
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 < 1)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator -(int day)const
{
Date ret(*this);
ret -= day;
return ret;
}
Date& Date::operator ++()
{
return *this += 1;
}
Date Date::operator ++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date& Date::operator --()
{
return *this -= 1;
}
Date Date::operator --(int)
{
Date tmp(*this);
*this -= 1;
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 m = 0;
while (max != min)
{
min++;
m++;
}
return m * flag;
}