1.类的默认的六个成员函数
如果一个类中什么都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我实现不的情况下,都会自动生成下面6个默认成员函数
默认成员函数:他们是特殊的成员函数,如果我们不实现,编译器会自己生成一份。
构造函数是一个特殊的成员函数,名字与类名相同,创建类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并在对象的声明周期内只调用一次
2.构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名字叫构造,这里也经常给我们一个误解,但是他并不是开辟空间创建对象,而是初始化对象
【构造函数特征】:
- 函数名与类名相同
- 无返回值
- 对象实例化时编译器自动调用对应构造函数
- 构造函数可以重载
- 如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦自己定义,编译器将不再生成
带参数构造函数,和上面的无参构造函数构成重载
class Date
{
public:
//无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//带参数构造函数,和上面的无参构造函数构成重载
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2022, 1, 17);
return 0;
}
打一个断点调试发现可以运行,显示结果。
还要注意的是,在语法上无参和全缺省参数可以同时存在,因为够成函数重载,但是调用无参的时候编译器无法分辨到底调用了哪个,从在二义性。
class Date
{
public:
//无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//全缺省构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
推荐使用全缺省或半缺省,比较好用
当自己没有写构造函数的时候,编译器会自动帮我们实现构造函数,d对象调用了编译器生成的默认构造函数,但是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么卵用??
这里C++把类分成两类:内置类型(基本类型)和自定义类型
内置类型:像int/char/double/指针/内置类型数组 等等
自定义类型:struct/class定义的类型
我们不编写构造函数的时候,编译器默认生成,对于内置类型的成员变量不做初始化处理。
对于自定义类型成员变量会去调用他的默认构造函数初始化,如果没有默认构造函数就会报错。
任何一个类的默认构造函数就是--不用参数就可以调用,任何一个类的默认构造函数有三个,全缺省、无参、编译器默认生成。
3.析构函数
3.1析构函数概念
前面通过学习了解构造函数是初始化对象,那么析构函数又是干什么的?
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
3.2特性
- 析构函数名是在类前面加上字符~
- 无参数返回值
- 一个类有且只有一个析构函数。若未显示定义,系统会自动生成默认的析构函数
- 对象生命周期结束时,C++编译系统自动调用析构函数
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
Date类没有资源需要清理,所以Date不实现析构函数是可以的,那什么需要清理呢?
在堆上开辟的空间需要清理,堆上的资源不主动释放他就不会释放。就需要自己写一个析构函数,如下:
实现一个栈:
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int)*capacity);
if (_a == nullptr)
{
cout << "malloc fail\n" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
void Push(int x)
{}
~Stack()
{
free(_a);
//虽然自动销毁,但是写一下更好
_top = _capacity = 0;
}
private:
int* _a;
size_t _top;
size_t _capacity;
};
class MyQueue
{
public:
void push(int x)
{
}
private:
Stack PushST;
Stack PopST;
};
int main()
{
Stack st;
Stack st2(20);
MyQueue mq;
return 0;
}
当我们写两个栈实现一个队列这种代码,需要实例化两个栈,开辟的空间在堆上,当我们销毁堆上malloc开辟的空间之后,实例化的对象自动销毁
【注意点】:
还有另外一点,析构函数的销毁顺序跟构造函数翻过来,上述例子先构造st再构造st2,析构函数销毁顺序就是st2,st.
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;
}
4.拷贝构造函数
4.1概念
构造函数:只有单个形参,该形参是对本类型对象的引用(一般用const修饰),在用已存在的类型对象创建新对象时由编译器自动调用。
4.2特征
拷贝构造函数也是特殊的成员函数,特征如下:
- 拷贝构造函数是构造函数的一个重载形式
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//Date(d1);
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
如果不加引用传参,那么变成这种形式Date(const Date d),那么调用拷贝构造需要传参,传参又调用拷贝构造,拷贝构造又调用传参
3.若未显示定义,系统生成默认的拷贝构造函数
- 内置类型成员,会完成按字节序的拷贝。(浅拷贝)
- 自定义类型成员,会调用他的拷贝构造
在有些地方我们可以用系统默认生成的拷贝构造,如浅拷贝这种直接将栈区的值从d1拷贝到d2,而有些地方如深拷贝我们需要自己实现拷贝构造函数,例如栈的实现,拷贝时将st1的地址也拷贝给st2,在销毁函数时,先销毁st2,将地址销毁后,st1的地址就不复存在,出现错误,导致程序崩溃。
【总结】--构造函数、析构函数、拷贝构造
1.构造函数--初始化,在对象实例化时候自动调用,保证实例化对象一定被初始化
构造是默认成员函数,自己不写编译器会生成一份,自己写了编译器就不会生成。
对于编译器默认生成的构造函数
- 对内置类型成员变量不处理
- 对自定义类型成员变量调用他的默认构造函数
2、析构函数,完成对象中资源的清理。如果对象需要资源清理,才需要自己实现析构函数。析构函数对象声明周期到了,以后自动调用,如果正确实现了析构函数,保证了对象中资源被清理。
我们不实现,编译器会生成默认的析构函数,我们实现了,编译器就不会实现了
对于默认生成析构函数
- 对内置类型成员变量不处理
- 对自定义类型成员变量调用它的析构函数
3.拷贝构造:使用同类型的对象去初始化实例对象
如果我们不实现,编译器会生成一份默认的拷贝构造函数
默认生成拷贝构造:
- 内置类型完成按字节序的值拷贝--浅拷贝
- 自定义类型成员变量,会去调用他的拷贝构造