先写一个日期类
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;
}
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操作符(参数列表)
注意:
- 第一个参数是左操作数 第二个参数是右操作数..顺序不能改。
- 运算符优先级
- .* :: 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;
}
注意:
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
- 赋值运算符重载只能是类的成员函数。
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
前置++和后置++重载
// 前置++:返回+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);//这里复用了 >=的操作符重载
}