类与对象
类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
创建类可以用class,也可以沿用C中的struct
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用
访问限定符
public:公有,修饰的成员在类外可以直接被访问
protected:保护,在类外不能直接被访问
private:私有,在类外不能直接被访问
访问限定符到下一个访问限定符结束
声明和定义分离:
类的成员变量放在函数声明中,函数声明和定义可以分开,定义时要指定类域,定义时,函数先从函数定义处寻找变量声明,再在类域中寻找
声明与定义在一起时默认为内联函数(编译器决定是否采用内联)
类的实例化
用类类型创建对象的过程,称为类的实例化,可以说类本身并不具有大小,实例化的对象才会开辟空间
一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量类大小的计算
类的大小
只算类成员大小(考虑内存对齐),不计算类函数大小,类函数存在公共空间 (成员函数及其地址不在对象中 ,成员变量存在对象中)
无成员变量的类大小为1,标识该类
this指针
class Date
{
public:
void init(int year,int mouth,int day)
{
_year=year;
_mouth=mouth;
_day=day;
}
void Print()
{
cout<<_year<<" "<<_mouth<<""<<day<<endl;
}
//void Print(Date* this)在同一个类不同的对象调用该函数时this指针指向的对象成员叶不同
//{
// cout<<this->_year<<" "<<this->_mouth<<""<<this->day<<endl;
//}
private:
int _year;
int _mouth;
int _day;
}
int main()
{
Date d1(2023,12,02);
Date d2(2023,12,03);
d1.Print();//d1.Print(&d1)
d2.Print();// d2.Print(&d2)
return 0;
}
不能显示的传this相关实参和形参,可以在类中显示的使用
特性:
- this指针的类型:类 类型* const(如Date* const this),即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递,或者存在栈帧中。
类的默认成员函数:如果不人为创建,类中会自动生成。该函数所传参数为随机值
构造函数:作用初始化对象,不传参则初始化内容为随机值,函数名与类名相同,无返回值,构造函数可以重载,用类对对象实例化时自动调用构造函数
class Date
{
public :
Date()//同默认生成的构造函数,内置类型成员不做处理为随机值,自定义类型会去调用其自身构造
{}
Date(int year=2023,int mouth=12,int day=06)
{
_year=year;
_mouth=mouth;
_day=day;
}
private:
int _year;
int _mouth;
int _day;
}
int main()
{
Date d1;//无参初始化时,不加括号,避免与Date d1()产生歧义,初始化为随机值
Date d2(2023,12);//缺省构造
return 0;
}
#include<stack>
class myqueue()
{
private:
stack _pushstack;
stack _popstack;
}
int mian()
{
using namespace std;
myqueue mq;//在实例化时,myqueue调用自身默认构造,生成stack时调用stack自身的构造函数
retrun 0;
}
指针均为内置类型,无论是什么类型的指针(类指针,结构体指针)本质都是指向一个地址
C11后类对内置类型成员可以在定义出给缺省值,此处的缺省值给默认构造使用,不为赋值,仅为定义
private:
int _year=2023;
int _mouth=12;
int _day=06;
一般情况下,都要写构造函数
当成员都是自定义类型(自定义的构造函数),或者声明时给了缺省值可以考虑默认生成构造函数
这里的默认构造三种情况,但是不能同时存在,否则有调用歧义:
编译器默认生成的构造函数
无参数传递的构造函数
全缺省的构造函数
对于const与int&等(引用)类型,没有默认构造的自定义类对象等成员变量的声明与定义需要初始化列表(因为上述变量的定义声明须同时进行,分离则需要初始化列表)
class Date
{
public :
Date(int year,int mouth,int day)
:_ref(year)
,_n(1)
{
_year=year;
_mouth=mouth;
_day=day;
}
private:
int _year;
int _mouth;
int _day;
int& _ref;
const _n;
}
}
初始化列表和函数体内可以混用,const,引用,没有默认构造的自定义类对象 的只能在初始化列表 。
构造函数不能只要初始化列表,因为有些初始化或者检查的工作,初始化列表也不能全部搞定
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
建议声明顺序和初始化列表顺序保持一致,避免出现声明顺序与传值顺序不一致的问题
析构函数:对象在销毁时自动调用析构函数,完成对对象中的资源自动清理
对于没有在堆上开空间的对象,仅仅需要默认的析构函数,因为对象在栈帧上开辟空间,栈帧的销毁由系统管理,只有在堆上开辟的空间需要主动释放,避免内存泄漏
对于默认生成的析构函数行为上对于内置类型不做处理,自定义类型成员会务调用它的析构函数
class stack()
{
public:
int* stack(int capacity,int top)
{
int* _a=(int*)malloc(sizeof(int)*capacity);
if(nullptr==_a)
{
exit(-1);
}
_capacity=capacity;
_top=0;
}
~stack()//析构函数
{
free(_a);
_capacity=0;
_a=nullptr;
}
private:
int* _a;
int _capacity;
int _top;
}
拷贝构造函数 :是构造函数的重载,参数为同类型对象的引用,对于内置类型完成值拷贝,对于自定义类型成员调用其自身拷贝构造
默认生成的拷贝函数为浅拷贝(值拷贝),对成员变量的拷贝(值拷贝 ),而对于在堆上开辟的空间的对象来说,浅拷贝会对该对象指向开辟空间的指针进行拷贝,原对象与拷贝对象生命周期结束调用析构函数时,会出现重复对同一空间的释放,造成错误。
因此,默认拷贝构造函数有问题,需要自行深拷贝(拷贝指向的资源,有着该资源一样大的空间,且修改相互不影响)
stack(stack& stt)
{
_a=(int*)malloc(sizeof(int)*stt._capacity);
if(nullptr==_a)
{
exit(-1);
}
memcpy(_a,stt_a,sizeof(int)*stt._top);
_top=stt._top;
_capacity=stt._capacity;
}
运算符重载:operator +函数名
class Date
{
public :
bool operator==(const Date& y)//该参数中有隐藏的this->x
{
return _year=y,_year&&_mouth=y,_mouth&&_day=y,_day
}
Date(int year=2023,int mouth=12,int day=6)
{
_year=year;
_mouth=mouth;
_day=day;
}
private:
int _year;
int _mouth;
int _day;
}
int main()
{
Date d1(2022,12,6);
Date d2(2023,12);//缺省构造
std::cout<<(d1==d2)<<ebdl;//d1==d2->d1.operator(d2)
return 0;
}
这里的运算符重载没有意义,具体看不同需求有不同的运算符重载(加减乘除,判等,赋值,大于小于 )
以下是对个日期类的运算符重载例子
日期类
class Date
{
private:
int _year;
int _mouth;
int _day;
}
获取该月份天数:
int Date::GetMonthDay(int year, int month)
{
assert(year >= 1 && month >= 1 && month <= 12);
int monthArray[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 monthArray[month];
}
构造重载:
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (_year < 1 ||
_month < 1 || _month > 12 ||
_day < 1 || _day > GetMonthDay(_year, _month))
{
//assert(false);
Print();
cout << "日期非法" << endl;
}
}
判断日期先后(> >= < <=)的运算符重载,(>=)复用(> ==)
bool Date::operator>(const Date& y)
{
if (_year > y._year)
{
return true;
}
else if (_year == y._year && _month > y._month)
{
return true;
}
else if (_year == y._year && _month == y._month && _day > y._day)
{
return true;
}
return false;
}
bool Date::operator>=(const Date& y)
{
return *this > y || *this == y;
}
bool Date::operator<(const Date& y)
{
return !(*this >= y);
}
bool Date::operator<=(const Date& y)
{
return !(*this > y);
}
计算日期(+= +),这里+复用+=,避免多次临时Date类的创建销毁
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)
{
Date tmp(*this);
tmp += day;
return tmp;
}
计算回退日期(-= -):这里-复用-=,避免多次临时Date类的创建销毁
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)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
常见操作符的重载:这里用传递参数的方法区分前置++/–与后置++/–,避免函数的重定义(复用+=/-=)
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
// ++d1
Date& Date::operator++()
{
*this += 1;
return *this;
}
// d1++
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;
}
计算两个日期之间差值:复用++,避免因为年与日进位不同导致的问题
int Date::operator-(const Date& d)
{
// 假设左大右小
int flag = 1;
Date max = *this;
Date min = d;
// 假设错了,左小右大
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
流插入运算符的重载,内置类型的流插入由iostream的cout重载实现,对于由自定义的类,流插入运算符需要自己定义实现, 多次流插入则返回值为ostream&(cout)
ostream& Date::operator<<(ostream& out)
{
out<<_year<<" "<<_mouth<<" "<<_day<<endl;
return out;
}
而对于上述的流插入运算符的重载,因为含有Date.this指针为隐含参数作为第一个,调用时则会写成d<<cout(双参数中默认左操作数为第一个参数,右操作数作为第二参数,对应调用则会不符合可读性 )
ostream& Date::operator<<(ostream& out,const Date& d)
{
out<<d._year<<" "<<d._mouth<<" "<<d._day<<end l;
return out;
}
这里需要在类外定义,类里面声明友元函数,因为类中定义默认会将this指针作为第一参数且不允许显示传参
friend ostream& Date::operator<<(ostream& out,const Date& d)
同理流提取
//类外定义
istream& operator>>(istream& in,Date& d)
{
in >>d._year>>d._mouth>>._day;
return in;
}
//类中定义
friend istream& operator>>(istream& in,Date& d)
const成员函数
涉及const修饰的对象所调用的函数,注意在const修饰下的权限放大与缩小问题,权限可以放大,但是不能缩小
当对象为const修饰时,不能调用非const的函数
const Date d1(2023, 10, 31);
//void Date::Print() d1不能调用
//{
// cout << _year << "/" << _month << "/" << _day << endl;
//}
void Date::Print() const
{
cout << _year << "/" << _month << "/" << _day << endl;
}
取地址重载运算符 (默认成员函数,不定义的话编译器会自己实现),返回对象的地址
Date* operator&()
{
return this ;
}