类中的默认成员函数有6个,但重点需要掌握的有构造函数、析构函数、拷贝构造函数、赋值运算符的重载这四个。今天我们就来一一讲解。
一、构造函数
·功能
虽然名称叫“构造”,但主要任务并不是开空间创建对象,而是初始化对象
·特征
我们以Date类为例,分别讲述构造函数的几种情况:
class Date
{
private:
int _year;
int _month;
int _day;
};
(1)没有自己定义构造函数时,编译器会自动生成构造函数,对象也可以创建成功
int main()
{
Dates d1;
return 0;
}
那系统自动生成的构造函数到底做了哪些事呢?
- 对于编译器的内置类型,如int,char,float,double以及指针等,编译器不做处理
- 对于自定义类型,如我们自己创建的类和结构体,编译器会自动找到他们的默认构造函数,进行初始化
但这样显得极不公平,因为对不同类型做了不同处理,有人将它视为C++的一个小缺陷。在C++11以后的版本里,针对此推出了一个新语法:
class Data
{
private:
int _year = 2021;//不是赋值,是给缺省值
int _month = 8;
int _day = 5;
};
这样在调用系统自动生成的构造函数时,会按照所给的缺省值给内置类型的变量进行赋值
举个例子:
class a
{
public:
a(int a = 0)
{
cout << "a(int a=0)构造函数" << endl;
_a = a;
}
void print()
{
cout << _a << endl;
}
private:
int _a;
};
class Date
{
public:
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
_aa.print();
}
private:
int _year;
int _month;
int _day;
a _aa;
};
int main()
{
Date d2;
d2.print();//output:a(int a=0)构造函数
return 0;
}
_year,_month,_day是内置类型的变量,编译器不处理;
_aa是自定义类型的变量,编译器会找到_aa所在类的默认构造函数,并调用
2.绝大多数情况下,需要我们自己写构造函数。我们可以根据需要,利用构造函数可重载的特性,结合缺省参数,编写多个构造函数
例如:
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date()
{
_year = 2021;
_month = 8;
_day = 5;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d1;//调用第一个构造函数
Date d2(2020, 9, 25);//调用第二个构造函数
return 0;
}
自己写构造函数时,最好写一个全缺省的,这样适应性最广
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2021, int month = 8, int day = 5)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d1;
Date d2(2020, 9, 25);
return 0;
}
构造函数还有一个很重要的概念:默认构造函数。
很多人将其等同于编译器自动生成的构造函数,这是严重的误区!
实际上,默认构造函数有以下三类:
这三者都被称为默认构造函数,
即:不用传实际参数的为默认构造函数。
默认构造函数可以跟其他需要传参的构造函数一起构成重载,
在运用时,三者只能选一个来使用。
二、析构函数
·概念
析构函数是特殊的成员函数,不是完成对象的销毁。对象本身的销毁工作由编译器完成。对象销毁时会自动调用析构函数,完成类的一些资源清理的工作。
·特征
同构造函数一样,我们也分几种情况讲解:
(1)对于系统自动生成的构造函数,系统对内置类型/自定义类型区别对待
对于内置类型,编译器生成一个毫无作用的析构函数,不做处理
因为临时对象在存在于栈帧中,函数结束,栈帧销毁,对象随之销毁,不需要析构函数处理对于自定义类型,会调用它自己的析构函数
例如:
class stack
{
private:
int* _a;
int size;
int capacity;
public:
~stack()
{
cout << "stack析构函数" << endl;
free(_a);
_a = nullptr;
size = 0;
capacity = 0;
}
};
class Date
{
private:
int _year;
int _month;
int _day;
stack st;
};
int main()
{
Date d1;//output:stack析构函数
return 0;
}
date类本身没有析构函数,对于自定义类型对象st, 调用了stack的析构函数
(2)自己定义的析构函数
对于date类
class Date
{
private:
int _year;
int _month;
int _day;
public:
~Date();
};
由于全是内置类型,我们可以不写析构函数。
但对于如stack类有指针类的变量,一般都需要自己写。
有一点要明确:对于_a,析构函数不是回收_a本身,而是清理_a所指向空间上的内容。
可以理解成_a有两块内容要清理,一块是本身,一块是它所指向的空间,析构函数清理的是后者。
class stack
{
private:
int* _a;
int _size;
int _capacity;
public:
~stack()
{
//需要在析构函数中 手动回收空间以及置为nullptr
free(_a);
_a = nullptr;
_size = 0;
_capacity = 0;
}
};
·关于调用顺序
一个类对应的构造的多个对象,遵循一个顺序:先构造的后析构,后构造的先析构
三、拷贝构造函数
·概念:
是完成对象的拷贝的构造函数,是一种特殊的构造函数。
还是以日期类为例:
class Date
{
public:
//拷贝构造函数
Date(Date& d)//参数最好加上const修饰,即Date(Date const& d);
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2021, 5, 25);
Date d2(d1);//调用拷贝构造函数,用d1来初始化d2
return 0;
}
Date(Date& d)
这里必须用引用!若传值,将会引发无穷调用拷贝构造(传值时,实参与形参时两块空间。为形参开辟空间的过程又是一次拷贝构造)
拷贝构造函数如果不自己实现,编译器会自动生成拷贝构造函数:
(1)默认拷贝构造函数对内置类型:完成浅拷贝(值拷贝)
上方代码中d2本身是随机值,Date d2(d1)会按照字节序将d1中的内容拷贝到 d2中
注意:默认的构造和析构函数对内置类型是不管的,但默认拷贝构造函数对内置也会进行赋值。(2)默认拷贝构造函数对自定义类型:调用该类中它自己的拷贝构造函数
但有时候,调用系统自动生成的拷贝构造函数会有问题
比如下面的Stack类:
class Stack
{
private:
int* a;
int size;
int capacity;
};
int main()
{
Stack p1;
Stack p2(p1);
return 0;
}
stack类中有指针,意味着p1和p2的a对象指向同一块空间,那么:
1.其中一个对象插入或删除数据,都会导致另外一个对象被执行相同的操作
2.调用析构函数时,会导致同一块空间被free(delete)两次
因此我们得自己写拷贝构造函数:
Stack(Stack& st)
{
a = (int*)malloc(st.size*sizeof(int));
memcpy(a, st.a, st.size * sizeof(int));
}
小结一下:
Date这样的类,需要的就是浅拷贝,那么编译器自动生成的拷贝构造函数就够用了
但是像Stack这样含有指针的类,得自己写拷贝构造函数实现深拷贝
四、重载赋值运算符=
·概念
已经定义出来的对象之间的复制拷贝(拷贝构造函数是定义时实现复制拷贝)
重载赋值运算符的特性跟拷贝构造函数是一致的
编译器自动生成的重载赋值运算符,对内置类型、自定义类型区别处理:
(1)重载赋值运算符对内置类型:完成浅拷贝(值拷贝)
(2)重载赋值运算符对自定义类型:调用该类中它自己的重载赋值运算符函数
因此同样地,对于Stack类,我们要自己写重载赋值运算符函数:
class Stack
{
private:
int* a;
int size;
int capacity;
public:
//构造函数
Stack(int* a = NULL, int size = 4, int capacity = 0)
{
memcpy(this->a, a, sizeof(a));
this->size = size;
this->capacity = capacity;
}
//重载赋值运算符
Stack operator=(Stack st)
{
this->a = (int*)malloc(sizeof(int) * st.size);
memcpy(this->a, st.a,sizeof(int)*st.size);
this->size = st.size;
this->capacity = st.capacity;
return *this;
}
};
int main()
{
int a[] = { 1,2,3,4,5 };
Stack p1(a,4,0);
Stack p2;
p2 = p1;
return 0;
}
到现在为止,我已向大家讲了C++中有关类的基本语法知识点,那在下一篇C++初阶(4)中,我想进行一次实践,用前三篇的知识实现Date日期类