大纲
1.构造函数(作用是:初始化,初始化,初始化,初始化,只有初始化的时候调用)
1)无参数构造函数( 调用方法: Test t1, t2;)
2)有参数构造函数(调用方法:调用的三种方法,这三种方法都是一样的)
Test5 t1(10,20); //c++编译器默认调用有参构造函数 括号法
Test5 t3 = (20, 10); //c++编译器默认调用有参构造函数 等号法(C++对等号符进行了功能加强)这里用得是逗号表达式。
Test5 t4 = Test5(30); //程序员手工调用构造函数 产生了一个对象 直接调用构造函数法
3)copy构造函数 (用一对象初始化另一对象时,C++编译器会自动调用这个对象的Copy构造函数。)
4)copy构造函数的四种调用场景:
第一种:Test4 t2 = t1; //用t1来初始化 t2
第二种:Test4 t2(t1); //用t1对象 初始化 t2对象
第三种:函数调用时:当形参是一个类的时候,函数调用时,用实参去初始化形参,会调用copy构造函数。
第四种:C++中函数返回值是一个对象时。对应有下面几种情况:
(1). 函数的返回值是一个元素 (复杂类型的), 编译器自己创建一个新的匿名对象,然后返回这个新的匿名对象,所以会调用匿名对象类的copy构造函数。如果只是调用,没有变量去接匿名对象的返回值,调用完就会马上被析构掉。
(2)用这个返回的新匿名对象去初始化一个对象(Location m = g(); ),会直接将返回的匿名对象转换成m, 因为返回值直接变成了m,所以不会调用构造函数和析构函数。这是编译器的规定,无需解释。
(3)用这个返回的新匿名对象赋值给一个对象(m2 = g();),注意不是初始化。生命周期结束后会调用析构函数。
注意:在VS编译器里是用创建新的匿名对象的方式返回。而在Linux平台上,是不会产生了匿名对象,直接返回,提高了执行效率。从而原本仅在fun函数内有效(局部变量生存周期)的t对象,由于被返回,在test1函数中仍然有效。
2.析构函数(清理内存)
生命周期结束(函数结束)的时候就会调用析构函数。
析构函数执行顺序:(在程序顺序角度:先从后面执行的程序开始往上析构–>出栈)
在内存模型中有一块区域叫做栈区,它是由系统维护的(程序员无法操作),用来存储函数的参数、局部变量等,类似于数据结构中的栈,也是先进后出。
当遇到函数调用时,首先将下一条指令的地址压入栈区,然后将函数参数压入栈区,随着函数的执行,再将局部变量(或对象)按顺序压入栈区。
栈区是先进后出的结构,当函数执行结束后,先把最后压入的变量(或对象)弹出,以此类推,最后把第一个压入的变量弹出。接下来,再按照先进后出的规则弹出函数参数,弹出下一条指令地址。有了下一条指令的地址,函数调用结束后才能够继续执行后面的代码。
所谓弹出变量,就是销毁变量,清空变量所占用的资源。如果这个变量是一个对象,那么就会执行析构函数。
3.构造函数调用规则研究
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数:当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制。
总结:构造函数设计规则,只要你写了构造函数,那么你必须调用。多个时候至少用一个。
4.构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数
5.浅拷贝和深拷贝
因为编译器默认创建的是一个默认简单的拷贝构造函数,只进行简单的成员变量的值复制,属于浅拷贝。如果涉及到一些复杂的初始化,指针变量,分配内存,等问题,C++提供的默认构造函数无法完成此拷贝工作,甚至会导致出错。此时我们只能手工编程copy构造函数,来达到我们要实现的目的。这就是深copy。
6.构造函数的初始化列表
构造函数初始化列表作用:
1)构造函数的初始化列表 解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数,规则:设计了构造函数就必须初始化)
2)初始化列表 用来 给const 属性赋值
3) 初始化列表速度比放到构造函数里面初始化变量快
4)第三个原因是定义为引用的属性必须使用初始化列表。
语法:
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3) 例子: B(int _b1, int _b2, int m, int n) : a1(m), a2(n), c(0)
执行顺序:
如果组合对象有多个,按照定义顺序(定义谁先就初始化谁先), 而不是按照初始化列表的顺序。(析构函数顺序与之相反)
7.扩展:逗号表达式
1.构造函数
为什么需要构造函数?
注意:类的数据成员是不能在声明类时初始化的。
而创建一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。
为了解决这个问题,C++编译器提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。
1.构造函数定义
1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
2)构造函数在定义时可以有参数;
3)没有任何返回类型的声明。
2.构造函数的调用
自动调用:一般情况下C++编译器会自动调用构造函数
手动调用:在一些情况下则需要手工调用构造函数
2.析构函数
1.析构函数定义及调用
1)C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法:~ClassName()
2)析构函数没有参数也没有任何返回类型的声明
3)析构函数在对象销毁时自动被调用
4)析构函数调用机制
3.案例
#include <iostream>
#include<string.h>
using namespace std;
class Test
{
public:
Test() //无参数 构造函数
{
a = 10; //作用完成对属性的初始化工作
p = new char[100];
strcpy(p, "aaaaffff");
cout<<"我是构造函数 被执行了"<<endl;
}
void print()
{
cout<<p<<endl;
cout<<a<<endl;
}
~Test() //析构函数
{
if (p != NULL)
{
cout<<"p不为空所以要清除"<<endl;
delete []p;
}
cout<<"我是析构函数,被调用了" <<endl;
}
protected:
private:
int a ;
char *p;
};
//给对象搭建一个舞台,研究对象的行为
void objplay()
{
//先创建的对象 后释放
Test t1; //直接定义,这种情况下,如果有多个构造函数,只会调用无参数构造函数
t1.print();
printf("分隔符\n");
Test t2;
t2.print();
}
int main()
{
objplay();
//t1和t2的作用域消失,所以会自动调用析构函数
cout<<"hello..."<<endl;
return 0;
}
4.构造函数的分类及调用
注意