文章目录
- 1.类的6个默认成员函数(天选之子)
- 2.构造函数
- 3.析构函数
- 3.1特性
- 4.拷贝构造
1.类的6个默认成员函数(天选之子)
C语言中,可能中途return也可能最后return,destroy的地方很多,比较麻烦。
以下为C++进行的改进,俗称”天选之子“。
- 构造函数和析构函数如果不写,编译器则会自动生成一个默认。
- 如果我们实现了任意一个,编译器就不会自动生成了。
- 对象在实例化的时候必须调用构造函数(包括自己写的和编译器自动生成的),应调用自己生成的那个。
C++类型分为:内置类型(int,char,任意类型的指针)不处理,
自定义类型(class,struct,union联合体)调用他的构造
默认生成的构造函数对于内置类型的成员不做处理,对于自定义类型的成员,会去调用它的构造(不用传参的构造)
与析构函数相似
class Date
{
public:Date(){_year = 1;_month= 1;_day= 1;}Date(int year, int month, int day)//带参的初始化{_year = year;_month = month;_day = day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:
//基本类型或内置类型,因为带intint _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2023, 9, 13);d1.Print();d2.Print();return 0;
}
合并两个构造函数,用全缺省更好用
Date(int year=1, int month=1, int day=1)//全缺省{_year = year;_month = month;_day = day;}
两个栈实现一个队列(push,pop,peek,empty)问题。
C
typedef struct {ST pushst;ST popst;
}MyQueue;
bool myQueueEmpty(MyQueue* obj);
MyQueue* myQueueCreate()
{MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));StackInit(&pq->pushst);StackInit(&pq->popst);return pq;
}
void myQueueFree(MyQueue* obj)
{assert(obj);StackDestroy(&obj->pushst);StackDestroy(&obj->popst);return pq;
}
C++
class MyQueue {
public://默认生成的构造函数,对自定义类型,会调用他的默认构造函数。void push(int x){}//...Stack _pushST;Stack _popST;
};
int main()
{MyQueue q;return 0;
}
2.构造函数
实际上是在初始化对象,特殊的成员函数。作用和Init相同。,主要任务不是开空间创造对象,而是初始化对象。
- 函数名和类名相同。名字已经定好了
- 没有返回值,什么也不用写。
- 对象实例化时自动调用对应的构造函数,不需要调用显示的Init
- 可以重载(一个类可以有多个构造函数),有多个构造函数就有多种初始化方式。
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,对于内置类型不做处理,对于自定义类型会去调用它的构造函数。一旦用户显式定义编译器将不再生成。
- 无参和全缺省的构造函数都被称为默认构造函数,且仅有一个,同时调用时存在歧义。不写编译器自动生成的也是默认构造函数。不传参数就可以调用构造函数。一般建议每个类都提供一个默认构造函数。
#include <iostream>
using namespace std;
class Stack
{
public://成员函数void Init(int n=4){_a = (int*)malloc(sizeof(int) * n);if (nullptr == a){perror("malloc申请空间失败");return;}capacity = n;size = 0;}void Push(int x){//...a[size++] = x;}void Destroy(){//...防止内存泄漏,可以把这件事交给编译器,让他自动搞起来。}//...
private://成员变量int* _a;int _size;int _capacity;
};
int main()
{//定义一个栈Stack st;//初始化st.Init(4);st.Push(1);st.Push(1);st.Push(1);st.Push(1);//进行销毁st.Destroy();return 0;
}
class Stack
{
public:Stack(){_a = nullptr;_size = _capacity = 0;}Stack(int n)//有了构造函数的类就不需要Init的类//构成函数重载{_a = (int*)malloc(sizeof(int) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_size = 0;}void Push(int x){//..._a[_size++] = x;}void Destroy(){//...防止内存泄漏,可以把这件事交给编译器,让他自动搞起来。}
private://成员变量int* _a;int _size;int _capacity;
};
int main()
{//构造函数的调用//定义一个栈//Stack st;//无参,不带(),否则会报错//因为是声明函数还是调用对象分不清楚。有点混淆Stack st(4);//带参//Stack st(4,3);st.Push(1);st.Push(1);st.Push(1);st.Push(1);//进行销毁st.Destroy();return 0;
}
Stack st(4)不等同于st.Stack(4)
构造函数不能用对象调用,st定义好了才向下走。
3.析构函数
destroy,和刚才构造函数的方法类似,定义一个函数让他自动调用。功能和构造函数相反,析构函数不是完成对象本身的销毁,析构函数进行清理资源,在对象出了作用域,生命周期销毁后进行调用。
3.1特性
- 函数名是在类名前加==~==。C语言中代表按位取反。
- 无参数无返回值。
- 一个类只有一个析构函数,若未显式定义,系统则**自动生成默认的析构函数。**注意:析构函数不能重载。
- 生命周期结束时,C++编译系统自动调用析构函数。
~Stack()
{free(_a);_a=nullptr;_size=_capacity=0;
}//只定义一个对象就释放一次,两个对象释放2次
4.拷贝构造
- 是构造函数的一个重载,函数名和类名相同,没有返回值。
- 参数只有一个且必须是类类型对象的引用,使用传值编译器直接报错,因为会引发无穷递归调用
Date(Date d){_year = d._year;_month = d._month;_day = d._day;//拷贝构造}//编译器不允许发生这种拷贝构造,会产生无穷递归,规定这个地方必须用引用
//普通整型,内置类型可以直接传递,但是自定义类型不可以。
//传值传参
void Func1(Date d)
{}//Func传参的时候是一个拷贝,把d1拷贝给d
//传引用传参
void Func2(Date& d)
{
}//d是d1的别名
int main()
{
Func1(d1);
return 0;
}
-
自定义类型不能编译器随便拷贝。内置类型编译器可以直接拷贝,自定义类型的拷贝需要调用拷贝构造。
-
按字节拷贝:浅拷贝
-
对于栈来说,浅拷贝两个栈指定同一空间,会析构两次。
-
C++规定自定义类型要调用一个拷贝构造,栈这样的类要调用一个深拷贝(自己去开一块空间)的拷贝构造。
-
要调用func1先传参,传参就是一个拷贝构造。
-
Date d2(d1);//对象实例化调用,先调用对应的构造函数,此时是拷贝构造Date (Date d);,调用拷贝构造之前要先传参d1传给d,传值传参又形成一个拷贝构造;拷贝构造要先传参,传值传参又是一个拷贝构造。。。。。递归下去。套娃
解决方法:引用。Date (Date &d);//d是d1的别名 -
拷贝构造也可以这么写:Date d2=d1;
拷贝构造一般加const。 -
Date (const Date &d);//权限缩小,权限可以缩小不可放大。
-
引用不需要调用函数。调用就是传参
-
指针也是内置类型。自定义类型必须用拷贝构造完成,编译器驾驭不了。
Date(const Date* d)
{cout<<"Date(Date& d)"<<endl;_year = d->_year;_month = d->_month;_day = d->_day;//不是拷贝构造
}//只是一个普通的构造函数Date d4(&d1);Dare d5=&d1;//复杂别扭
实现一个函数,实现多少天以后的日期。
int GetMonthDay(int year, int month){//if,else/switch,case//数组assert(month > 0 && month < 13);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;}else{return monthArray[month];}}Date GetAfterXDay(int x){//先用一个结果,先生成一个中间临时对象。拷贝d1,this是d1的地址//Date tmp;//tmp._year = _year;//传值返回不用tmp,因为tmp出了作用域后被销毁,返回tmp的拷贝。进行拷贝构造//用同类型对象//Date tmp(*this);拷贝构造Date tmp = *this;//拷贝构造//不能用引用,用引用tmp是d1的别名。tmp的改变就是d1的改变。//不动自己的成员//加法进位tmp._day += x;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* this;//返回自己,自己的年月日都在往后加,this是这个对象的指针,*this是这个对象//除了作用于还在return tmp;//局部对象}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}int main(){Date d1(2023, 9, 13);Date d2 = d1.GetAfterXDay(100);d1.Print();d2.Print();//这时改变_year等会改变d1return 0;}
+(不改变)和+=(改变)返回值的区别。