在类与对象(一)中,我们主要讲了类和对象的基本知识点,对类和对象进行了初步认识;本文我们将继续关于类与对象的的总结,主要内容包括C++中的6个默认成员函数------构造函数,析构函数,拷贝构造函数,赋值操作符重载相关的知识点和注意事项;
一.类的六个默认成员函数:
1.什么是类的六个默认成员函数????
构造函数;
析构函数;
拷贝构造函数;
赋值运算符重载;
取址(&)运算符重载 ;
const修饰的取地址运算符重载;
2.如果一个类中什么成员都没有,简称为空类。空类中并不是什么都没有,任何一个类在我们不写的情况下,都会自动生成上面的6个默认成员函数;
二.构造函数:
1.什么是构造函数???
我们先来看一段代码:
#include <iostrema>
using namespace std;
calss Date {
pubilc:
void Init(int year,int month,int day){ //对对象进行初始化;
_year=year;
_month=month;
_day=day;
}
void Printf(){
cout<<_year<<"-"<<_month<<"-"<<day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date d1,d2;
d1.Printf(); //这会产生随机值,就需要用构造函数来解决;
d1.Init(2018,5,3);
d1.Printf();
d2.Init(2018,7,1);
d2.Printf();
return 0;
运行结果输出如下:
为什么直接调用Printf()函数会产生随机值,因为没有对其进行初始化;
对于Date类,可以通过Init()函数公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息就会很麻烦,那能否在对象创建时,就将对象设置进去;这就需要使用构造函数。
构造函数概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
注意:构造函数是特殊的成员函数,虽然构造函数的名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
2.构造函数的特性:
(1)函数名与类名相同;
(2)无返回值;
(3)对象实例化时编译器自动调用对应的构造函数;
(4)构造函数可以重载;
我们来通过代码进行说明:
#include <iostream>
using namespace std;
calss Date{
public:
void Init(int year,int month,int day){
_year=year;
_month=month;
_day=day;
}
void Printf(){
cout<<_year<<"-"<<month<<"-"<<day<<endl;
}
//设置一个构造函数;
Date(int year=1900,int month=1,int day=1){
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date d1;
d1.Printf(); //通过缺省参数,自动调用;
d1.Init(2018,5,3);
d1.Printf();
Date d2;
d2.Printf();
return 0;
}
输出运行结果:
运用构造函数可以将函数进行自动调用,实现打印,不会打印随机值;
(5)如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成;
#include <iostream>
using namespace std;
calss A{
A (){
cout<<"A()"<<endl;
}
//在这里定义一个calss A;则在私有中的A _a,就会被初始化-------调用A 的构造函数;如果调用A的构造函数,就会输出”A()“;
calss Date{
public:
void Init(int year,int month,int day){
_year=year;
_month=month;
_day=day;
}
void Printf(){
cout<<_year<<"-"<<month<<"-"<<day<<endl;
}
//设置一个构造函数;
//去掉定义的构造函数,编译器会自动生成一个默认的构造函数;此时代码可以继续运行,但会生成随机值;
// Date(int year=1900,int month=1,int day=1){
// _year=year;
// _month=month;
// _day=day;
// }
private:
int _year;
int _month;
int _day;
A _a;
};
int main(){
Date d1;
d1.Printf();
// d1.Init(2018,5,3); //这里就会编译不通过;
// d1.Printf();
Date d2;
d2.Printf();
return 0;
}
输出运行结果:
(6) 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个;注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
#include <iostream>
using namespace std;
calss Date{
public:
Date(int year,int month,int day){
_year=year;
_month=month;
_day=day;
}
void Printf(){
cout<<_year<<"-"<<month<<"-"<<day<<endl;
}
private:
int _year;
int _month;
int _day;
}
int main(){
Date d1;
d1.Printf();
return 0;
}
运行结果为:
输出结果:没有默认的构造函数可用;因为上面的构造函数是带参的;当设置为全缺省的就可以编辑成功;
(7) 关于编辑器生成的默认成员函数;C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,编译器生成默认的构造函数会对自定义类型成员调用它的默认成员函数;
class Time {
public:
Time() {
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date {
void Ptrintf(){
cout<<_year<<"-"<<_month<<"-"<<day<<endl;
}
private: // 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main() {
Date d;
return 0;
}
运行结果为:
三.析构函数:
1.什么是析构函数???
与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的;而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
2.析构函数的特征:
(1)析构函数名是在类名前加上字符 ~;
(2)无参数无返回值;
(3)一个类有且只有一个析构函数;若未显式定义,系统会自动生成默认的析构函数;
(4)对象生命周期结束时,C++编译系统系统自动调用析构函数;
#include <iostream>
using namespace std;
class Date{
public:
Date(int year=1900,int month=1,int day=1){
cout<<"Date()"<<endl;
_year=year;
_month=month;
_day=day;
}
void Printf(){
cout<<_year<<"-"<<month<<"-"<<day<<endl;
}
~Date(){ //创建析构函数;
cout<<"Date()"<<endl;
}
private:
int _year=year;
int _month=month;
int _day=day;
};
int main(){
Date d1;
d1.Printf();
return 0;
}
输出结果为:
一般不会写析构函数,只有资源需要清理的才写析构函数;比如构造函数中动态开辟了空间,需要把空间销毁就要写析构函数,比如在构造函数中fopen了某个文件析构函数中就要fclose;
class Seqlist{
public:
Seqlist(size_t N = 10){
_array =(int *) malloc(N*sizeof(int));
_capacity = N;
_size = 0;
}
~Seqlist(){
free(_array);
_array = nullptr;
_size = _capacity = 0;
}
private:
int *_array;
size_t _size;
size_t _capacity;
};
int main (){
Seqlist d;
return 0;
}
(5)关于编译器生成的默认析构函数,对会自定类型成员调用它的析构函数;
class String {
public:
String(const char* str = "jack") {
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String() {
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person {
private:
String _name;
int _age;
};
int main() {
Person p;
return 0;
}
四.拷贝构造函数:
1.什么是拷贝构造函数??
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象 创建新对象时由编译器自动调用;
2.拷贝构造函数的特征:
(1) 拷贝构造函数是构造函数的一个重载形式;
(2)拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用;
#include <iostream>
using namespace std;
calss Date{
public:
Date(int year=2019,int month=5,int day=12){
cout<<"Date()"<<endl;
_year=year;
_month=month;
_day=day;
}
//建立拷贝构造函数;
Date(const Date& d){
_year=d._year;
_month=d._month;
_day=d._day;
}
void Printf(){
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
~Date(){
cout<<"Date()"<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date d1;
d1.Printf();
Date d2(d1);
d2.Printf();
return 0;
}
输出结果如下:
(3) 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
一般情况下系统生成的拷贝构造函数是可以进行拷贝的,这种拷贝是按照字节序进行拷贝叫浅拷贝;这种拷贝只能解决一部分拷贝问题,不能解决所有问题;比如说像顺序表;日期可以进行拷贝,但顺序表会出现问题;析构之后地址空间就会给别人了;第二次析构会把别人的地址空间析构了;
析构和访问都会有问题;可以通过两种方法进行解决:一种是深拷贝—开一样大小的空间一样的值,不会指向原来的空间,而是指向重新开辟的新空间,然后只是把值拷贝过来;第二种:引用计数的写时拷贝;
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
void Printf(){
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1;
// 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
Date d2(d1);
d1.Printf();
d2.Printf();
return 0;
}
输出结果为:
五.赋值运算符重载:
运算符的重载函数名字为:关键字operator后面接需要重载的运算符符号;
函数原型:返回值类型 operator操作符(参数列表);
1.为什么要对赋值运算符进行重载???
某些情况下,当我们编写一个类的时候,,并不需要为该类重载“=”运算符,因为编译系统为每个类提供了默认的赋值运算符“=”,使用这个默认的赋值运算符操作类对象时,该运算符会把这个类的所有数据成员都进行一次赋值操作;
编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了;
代码如下所示:
#include <iostream>
using namespace std;
class Date{
public:
Date(int year = 1900, int month = 1, int day = 1){
cout << "Date()" << endl;
_year = year;
_month = month;
_day = day;
}
void printf(){
cout<<_year<<"-"<<_month<<"-"<<day<<endl;
}
~Date(){
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date d1(2019, 5, 12);
Date d2(2019, 5, 13);
Date d3=d1; // 这里d1调用的编译器生成operator=完成拷贝,d3和d1的值是一样的。
d1.printf();
d2.printf();
d3.printf();
return 0;
}
执行结果如下:
但是在接下来的情况下就会出现错误!!!
代码如下所示:
#include <iostream>
using namespace std;
class String {
public:
String(const char* str = "jack") {
char* _str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String(){
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main() {
String s1("hello");
String s2("world");
s1 = s2;
system("pause");
return 0;
}
由于重复释放了一块内存,导致程序崩溃报错。在这种情况下,就需要我们重载赋值运算符“=”了。
2.赋值运算符主要有以下几点:
(1)参数类型;
(2)返回值;
(3)检测是否自己给自己赋值;
(4)返回*this ;
(5)一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝;
举例如下所示:
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
Date (const Date& d){
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d) { //这里相当于Date& operator=(Date* this,const Date& d)
if(this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
}
private:
int _year ;
int _month ;
int _day ;
};
五.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生:
class Date {
public :
Date* operator&() {
return this ;
}
const Date* operator&()const{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容;
关于const 成员函数的几个问题:
(1)const对象不可以调用非const成员函数;
(2)非const对象可以调用const成员函数;
(3)const成员函数内不可以调用其它的非const成员函数;
(4)非const成员函数内可以调用其它的const成员函数;
这是由const的修饰规则决定的;
总结:本文主要讲了关于C++中类的六个默认成员函数的定义,特性以及在代码中的注意事项,重点需要掌握的是前四个函数----构造函数,析构函数,拷贝构造函数,赋值操作符的重载;注意const成员函数。