思维导图:
一,构造函数
1.定义
对于构造函数首先就要知道构造函数怎么写,构造函数怎么写呢?你要知道如下两点:
1.构造函数的函数名要与类名相同
2.构造函数是没有返回值的,但可以有参数。
因为这第二点,所以构造函数便可以被重载变成许多的具有不同种类的构造函数。
2.构造函数的分类
1.系统生成的默认构造参数
首先写一个日期类:
class Date { public: private: int _year; int _month; int _day; };
这里可以看到的是日期类里面的每一个变量都没有被赋值,但是在调用实例化一个类以后这些变量会被赋值吗?看看便知道了:
可以看到这些变量都被附上了一个相同的随机值。这是因为调用了系统生成的默认构造函数的原因。如果没有调用默认的构造参数的话就会是这个样子:
2.不含参的构造函数
其实我们也可以自己定义一个不含参的构造函数来达到和系统调用的默认参数一样的效果:
这里需要注意一下,要调用构造无参的构造函数时不能在实例化对象的后面加一个()这样会和声明一个函数的意思混淆。
3.含参的构造参数
可以看到上面两种构造函数都没有将类里面的变量初始化为我们想要的值的作用,但是含参的构造函数却有这个作用:
需要注意的是,当我们自己写了一个自定义的构造函数的时候系统便不会再生成一个无参的构造函数。所以当我们再次想要Date d2,就会报错。在这里,又因为我们学习过缺省参数的概念,所以我们可以在定义含参的构造参数时决定定义的构造参数是全缺省的还是半缺省的。
全缺省:
在这里又可以看到,在我们定义了一个全缺省的构造函数以后便可以不传参的实例化一个对象。
半缺省:
在这里可以看到在我们定义一个半缺省的构造函数以后是不能够不传参实例化一个对象的。
4,默认构造函数
你是不是以为默认构造函数只有一种,那就是系统自动生成的默认构造函数?但是,其实默认构造函数在Cpp中的意思是不用传参便可以调用的函数。所以,其实在Cpp中默认构造函数有三种:
1.系统自动生成的默认构造函数。
2.不含参的自定义构造函数。
3.全缺省的构造函数。
这三种构造函数在Cpp中都被叫做默认构造函数。这样搞确实是挺坑的。所以为了填坑,我们的Cpp祖师爷便规定可以给成员变量赋值从而达到即使不定义构造函数我们也能初始化的效果:
这样便可以给当初定义默认构造函数时的缺陷打上一点补丁了。
二,析构函数
1.定义
析构函数可以说是构造函数的一个重载形式了。要定义析构函数要知道以下几点:
1.析构函数的前面有~,后面接的名字和类名相同。
2.析构函数没有返回值。
3.析构函数不含参数,所以不会构成重载,因而每一个类都有自己唯一的一个构造函数。
2.析构函数的作用
在上面的日期类里面写一个构造函数:
class Date
{
public:
Date(int year = 1999,int month = 9 ,int day = 6 )
{
_year = year;
_month = month;
_day = day;
}
//构造函数
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day ;
};
int main()
{
Date d1;
return 0;
}
其实你会发现这并没有什么用,因为即使你不调用构造函数这些临时的变量也会自动的销毁。所以这也体现了析构函数只会在清理资源时发挥它的作用。比如在我们调用一个堆上生成的变量时,便可以调用析构函数。如:
class Stack { public: Stack(int n = 4)//构造函数 { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc fail\n"); return; } a = tmp; top = n; size = n; } ~Stack()//析构函数释放资源 { free(a); top = 0; size = 0; } private: int* a; int top; int size; }; int main() { Stack ST(1000); return 0; }
这样便可以在定义了一个栈以后避免忘记手动销毁而造成内存泄漏的问题。因为析构函数是会被自动调用的。
三,拷贝构造
1.拷贝构造的定义
拷贝钩爪可以说是构造函数的另外一种重载形式了。拷贝构造的特点有下面几点:
1.函数名和类名相同
2.拷贝构造的参数中必须有一个自定义的类类型的参数
3.拷贝构造不能传值,否则会发生无穷递归的问题。同时也是为了提高程序的效率。
2. 为什么要有拷贝构造
拷贝构造其实是一种深拷贝,而我们平时的拷贝方式就是一种浅拷贝方式。因为是浅拷贝所以在我们拷贝一些指针时便会将指针的地址一摸一样的拷贝过来。此时因为我们有一个析构函数并且析构函数是自动调用的所以对同一个指针所指向的内存空间便会被置空两次因而导致报错,如:
class Stack { public: Stack(int n = 4) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc fail\n"); return; } a = tmp; top = n; size = n; } ~Stack() { cout << "~Stack()" << endl;//调用一次析构函数便打印一次~Stack free(a); a = NULL; top = 0; size = 0; } private: int* a; int top; int size; }; int main() { Stack ST1; Stack ST2 = ST1; return 0; }
执行结果如下:
于是这里便调用了两次的析构函数。并且:
两个不同的栈里面的指针变量a指向的都是同一块地址,所以调用两次析构函数便会对同一块地址释放两次导致错误。 因为这个原因我们便要有深拷贝让两者的指针变量的地址指向不同的两块空间。
2.拷贝构造函数的使用
拷贝构造函数的使用首先就要弄清楚应该传什么,这里要明确的是传参数的时候我们应该传的是同类类型的引用参数。传引用参数才不会导致无限递归调用拷贝构造。所以拷贝构造的代码如下:
class Stack { public: Stack(int n = 4) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc fail\n"); return; } a = tmp; top = n; size = n; } Stack(const Stack& st)//拷贝构造 { a = (int*)malloc(sizeof(int) * st.size); size = st.size; top = st.top; } ~Stack() { cout << "~Stack()" << endl; free(a); a = NULL; top = 0; size = 0; } private: int* a; int top; int size; }; int main() { Stack ST1; Stack ST2 = ST1; return 0; }
使用拷贝构造以后便可以避免两次析构同一块空间的问题:
两个类的指针也是指向不同的地址:
完美解决!!!