默认函数:
我们不写,编译器默认生成的函数。 无参构造函数 全缺省构造函数
初始与清理:构造函数与析构函数。
一般情况下,显示申请了资源才需要自己写析构,其他情况基本都不需要写。
关于5,6点的解释:
class Date
{
public:
Date(int year=1,int date=1,int day=1)
{
_year = year;
_date = date;
_day = day;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _date;
int _day;
};
typedef int STDataType;
class Stack
{
public:
Stack(int capacity = 4)
{
_Data = (STDataType*)malloc(sizeof(STDataType)*capacity);
if (_Data==nullptr)
{
perror("申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
if (_Data)
{
free(_Data);
}
_capacity = 0;
_top = 0;
}
private:
STDataType* _Data;
int _capacity;
int _top;
};
class Myqueue
{
public:
/*~Myqueue()
{
cout << "~Myqueue()" << endl;
}*/
private:
Stack _push;
Stack _pop;
};
int main()
{
Date d1;
Stack s1;
Myqueue q1;
return 0;
}
//Myqueue类的析构函数被注释掉,没有显示的析构函数,所以编译器自动生成一个默认的析构函数,因为Myqueue成员变量是自定义类型Stack,所以会调用~Stack(),因为有两个Stack类型的成员变量,所以调用两次;
//取消Myqueue的注释,现在有显示析构函数,那么显示析构函数会被调用,自定义类型变量的析构函数也会被调用
//在main函数中,有三个对象,那么后定义的先析构,先析构Myqueue q1,在析构Stack s1,最后Date d1
第6点的解释:
typedef int STDataType;
class Stack
{
public:
Stack(int capacity = 4)//构造
{
_Data = (STDataType*)malloc(sizeof(STDataType) * capacity);
if (_Data == nullptr)
{
perror("申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
Stack(Stack& st)//拷贝构造,深拷贝构造,因为这里存在资源的申请
{//如果没有这个深拷贝,用系统自动生成的默认拷贝构造,会报错,因为代码中存在资源的申请
//在代码结束时,Stack对象会调用析构函数,但是st1与st2的成员变量_a指向的是同一块空间,所以在析构时会造成对同一块空间进行两次释放的问题。
_Data = (STDataType*)malloc(sizeof(STDataType)*st._capacity);
if (_Data==nullptr)
{
perror("申请空间失败");
return;
}
memcpy(_Data,st._Data,sizeof(STDataType)*st._top);
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
if (_Data)
{
free(_Data);
}
_capacity = 0;
_top = 0;
}
private:
STDataType* _Data;
int _capacity;
int _top;
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
typedef int STDataType;
class Stack
{
public:
Stack(int capacity = 4)//构造
{
_Data = (STDataType*)malloc(sizeof(STDataType) * capacity);
if (_Data == nullptr)
{
perror("申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
Stack(Stack& st)//拷贝构造
{
_Data = (STDataType*)malloc(sizeof(STDataType)*st._capacity);
if (_Data==nullptr)
{
perror("申请空间失败");
return;
}
memcpy(_Data,st._Data,sizeof(STDataType)*st._top);
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
if (_Data)
{
free(_Data);
}
_capacity = 0;
_top = 0;
}
void push(int x)
{
_Data[_top++] = x;
}
private:
STDataType* _Data;
int _capacity;
int _top;
};
Stack& func()
{
Stack st;
st.push(1);
st.push(2);
st.push(3);
//.....
return st;//这里返回了局部域的对象,函数结束时,st就销毁了,
}
//返回st,把st的值拷贝到一块临时空间中去,此时这块临时空间就是临时对象,这一过程要调用拷贝构造,再把临时对象的值拷贝给ret,这一过程也要调用拷贝构造,但是当func函数结束时,st对象中指向的资源就已经被释放,所以ret对象存在野引用,当main函数结束时调用析构函数,对ret对象进行析构就存在对已经释放的空间进行析构的问题。
int main()
{
Stack ret=Func();
return 0;
}
运算符重载:(运算符重载是由operator加要重载的符号构成 )
运算符重载是由operator加要重载的运算符构成
运算符的参数有个数与重载的运算符操作对象保持一致
class A
{
public:
void func()
{
cout << "yes" << endl;
}
};
typedef void(A::*PF)();//A::*表示这是一个指向类A的成员的函数指针,是一个指向类A的成员函数的指针的类型,所以下文定义的pf是一个成员函数指针。
int main()
{
PF pf = &A::func;//c++规定,调用函数的指针时需要取地址符号,A::可以理解为取的是A类中的成员函数的地址,但是取的不是该成员函数的实际内存地址,因为成员函数不是像普通函数那样独立存储的,它取的是成员函数的“函数表示符”的地址,这个地址在类的上下文中是唯一的。
A obj;
(obj.*pf)();//对pf解引用得到成员函数的地址,但是仅仅解引用pf是不足以调用成员函数的,因为成员函数需要知道它是在哪个对象上被调用的,此时.就起到了作用,这个操作符将*pf与obj绑定在了一起,告诉编译器是被obj这个对象调用的。
return 0;
}
关于".*"运算符:
class A
{
public:
void func()
{
cout << "A::func()" << endl;
}
};
typedef void(A::*Pf)();//定义一个函数指针,指向的函数返回类型为void,是在A类中的并且参数个数为0,所以函数指针类型为void(A::*)();
int main()
{
Pf pf;
pf = &A::func;
A obj;
(obj.*pf)();
return 0;
}
重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x,int y)
关于调用运算符重载函数的形式:
Date operator+(int day);
Date Date::operator+(int day)
{
Date tmp = *this;
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year,tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
int main()
{
Date d1;
d1+1;//可以这么写
d1.operator(1);//这样写也对
//写成第一种形式编译器会自动转换成第二种形式。
//从底层的角度来讲,编译器调用函数会转换成一串指令call,这两种形式的底层汇编代码是一样的
return 0;
}
取地址运算符重载
const成员函数:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year=2000,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
}
void Print()const//const修饰Date类的Print成员函数,Print的this指针变成:
//(const Date*const this)
{
//(*this)._day = 30;
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,1,1);
d1.Print();
const Date d2(2024,10,10);
d2.Print();
return 0;
}
取地址运算符重载:
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。
除非一些很特殊的场景,比如我们不想让别人取到当我们定义的类前类对象的地址,就可以自己实现一份,胡乱返回一个地址。
class Date
{
public :
Date* operator&()
{
return this;
// return nullptr;
}
const Date* operator&()const
{
return this;
// return nullptr;
}
private :
int _year ; // 年
int _month ; // ⽉
int _day ; // ⽇
};