构造函数
构造函数:对象初始化
析构函数:对象清理
1,都是编译器是默认添加的。
2,只要创建对象和释放对象,那么系统就会默认的调用这两个函数。(不需要手动调用)
3,构造和析构都必须要声明在全局作用域下。也就是前面得有public:
4,构造函数没有返回值,也不用写void
构造函数介绍
1,函数名与类名相同。
2,可以有参数,可以有重载。
3,代码中的p是开辟到栈上的,函数执行完就会释放。构造函数是创建的时候调用的。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数已经被调用" << endl;
}
};
int main()
{
Person p;
return 0;
}
//注意写类的时候最后的‘;’,不要忘了。
析构函数介绍
1,没有返回值,不用写void
2,函数名和类名相同,函数名前加~
3,不可以有参数,不可以发生重载
4,代码中的p是开辟到栈上的,函数执行完就会释放。构造函数是释放的时候调用的。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数已经被调用" << endl;
}
~Person()
{
cout << "析构函数已经调用" << endl;
}
};
int main()
{
Person p;
return 0;
}
构造函数的分类和调用
两种分类分类方式:
按照参数分类:分为无参构造(默认构造函数)和有参构造。
class Person
{
public:
Person()
{
cout << "无参构造函数已经被调用" << endl;
}
Person(int a)
{
cout << "有参构造函数已经被调用" << endl;
}
};
int main()
{
Person p;//调用无参
Person p(1);//调用有参
return 0;
}
按照类型分类:分为普通构造函数 和 拷贝构造函数(除了拷贝构造函数以外的都叫普通构造函数)
拷贝构造函数定义
如果要拷贝复制一个张三出来(张三复制出来以后名字叫做李四),那么就得传进去一个张三,但是不能用值传递,需要使用引用传递,因为如果使用值传递,那么就是将张三放进去,然后经过改变之后出来就成李四了。而目的是复制一个张三,所以肯定不对,应该传进来去张三的引用,然后复制一个与张三具有相同特征的李四。还需要加个const
防止对p进行修改。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:
Person(int a)
{
age = a;
cout << "有参构造函数已经被调用" << endl;
}
Person()
{
cout << "无参构造函数已经被调用" << endl;
}
Person(const Person& p)//拷贝构造函数
{
age = p.age;
cout << "拷贝构造函数已经被调用" << endl;
}
int age;
};
int main()
{
Person p(18);//有参构造
//Person p;//无参构造,不需要加括号,加括号就不会看作是创建实例对象了,而是一个名叫p的函数。
//Person(10);//匿名对象,没有名字,创建这个对象以后就会立即将这个对象回收。(执行完有参构造函数以后立即进行析构函数)
Person p2(p);
cout << "p2的年龄是:" << p2.age << endl;
return 0;
}
根据这个例子可以看出,也就是将构造的对性p 的 年龄等特征克隆到新个体中。
1,注意:不要用拷贝构造函数来初始化匿名对象。
Person(p);//匿名构造函数。
//编译器认为这个是 Person p对象的实例化操作,如果在之前已经实例化过p,那么就会报错:p3重定义。
2,隐式法:
Person p5 = 10;//Person p5 = Person(10);
Person p6 = p5;
//用的比较少。
拷贝构造函数的调用时机
1,用已经创建好的对象来初始化新的对象。
Person p2 = Person(p1);
Person p2 = p1;
//这两种是一样的。
2,用值传递的方式来给函数的参数传值。
class Person
{
public:
Person(int a)
{
age = a;
cout << "有参构造函数已经被调用" << endl;
}
Person()
{
cout << "无参勾走函数已经被调用" << endl;
}
Person(const Person& p)//拷贝构造函数
{
age = p.age;
cout << "拷贝构造函数已经被调用" << endl;
}
~Person()
{
cout << "析构函数已经被调用" << endl;
}
int age;
};
void doWork(Person p)
{
//cout << p.age << endl;
}
int main()
{
Person p1(18);//
doWork(p1);
return 0;
}
输出的结果是:
有参构造函数已经被调用
拷贝构造函数已经被调用
析构函数已经被调用
析构函数已经被调用
调用doWork函数的时候,就已经是在调用拷贝构造函数了,将p作为值传去当参数。值传递的本质就是调用它的拷贝构造函数。所以说拷贝的这个p是新的东西,和原来的p1不是相同的东西。
3,以值的方式返回局部对象
Person doWork2()
{
Person p;
return p;
}
void test2()
{
Person p = doWork2();
}
输出的结果是:
无参勾走函数已经被调用
拷贝构造函数已经被调用
析构函数已经被调用
析构函数已经被调用
doWork2()这里的返回值是按照值的方式返回的,之前说不要返回一个局部对象的引用Person&(p这个局部对象马上就会被释放了)。但是如果返回的是一个值,返回的就不是p了,而是以据p创建出来的新的p2数据(本身是没有名字的,相当于构造了一个匿名对象),然后在执行test2()的时候给这个没有名字的匿名对象赋上名字。(这个匿名对象是return的时候创建出来的,原先的dowork中的p就会释放,而传出来的数据的寿命会延续到下个函数周期。)
注意:
以下是代码和运行之后的结果:代码区别是调用的函数有没有’&‘符号。结果的区别是有无构造拷贝函数的调用。而且在有&符号的函数中使得p的age改成100的话,那么main中的p的age会改变。而没有&符号的函数中p的age发生改变不会影响到main中age的改变。
调用doWork函数的时候,就已经是在调用拷贝构造函数了,将p作为值传去当参数。值传递的本质就是调用它的拷贝构造函数。
发现如果有&,表示上传的是p这个对象,只是另一个名字。而没有&,就表示值传递,传的仅仅只是p的数据,不会使得main中的p进行改变。
void doWork3(Person& p)
{
}
int main()
{
Person p;
doWork3(p);
return 0;
}
无参构造函数已经被调用
析构函数已经被调用
void doWork3(Person p)
{
}
int main()
{
Person
doWork3(p);
return 0;
}
无参构造函数已经被调用
拷贝构造函数已经被调用
析构函数已经被调用
析构函数已经被调用
注意:如果自己写了一个有参的构造函数,那么系统就不会给你生成无参的(默认的)构造函数。如果需要就得自己写无参的构造函数。
深浅拷贝
构造函数的作用就是对对象的属性进行赋值
class Person
{
public:
Person(char* name, int age)
{
m_name = (char*)malloc(strlen(name) + 1);//给名字在堆上申请空间,m_name指向这个空间
strcpy(m_name, name);
m_age = age;
}
int m_age;
char* m_name;
};
根据上面的代码:创建了p1这个对象,m_name保留着名字的地址,指向堆中的“德玛西亚”,年龄是20,然后使用默认拷贝构造函数通过p1创建了p2,指针相同,年龄也一样。然后改变了系统默认的析构函数,如果发现指针不是空,那么就释放掉。这样导致了p2的指针释放了以后再次检验p1 的指针发现不是空,但是如果释放的话就构成了释放重复。
错误的原因:它拷贝的时候用的是编译器的拷贝构造函数,自己写一个就行了。(利用深拷贝解决原来的简单的浅拷贝问题)
Person(const Person&p)//在这里加const是为了防止不小心将传进来的引用修改
{
m_name = (char*)malloc(strlen(p.m_name)+1);
strcpy(m_name,p.m_name);
m_age = p.m_age;
}
//这就是自己创建的拷贝构造函数,然后自己又在堆上创建了空间,然后进行赋值。这样就有两个空间,释放的时候就不会重复了。
完整代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:
Person(const char * name, int age)
{
m_name = (char*)malloc(strlen(name) + 1);//给名字在堆上申请空间,m_name指向这个空间
strcpy(m_name, name);
m_age = age;
}
~Person()
{
if (m_name != NULL)
{
cout << "Person析构调用" << endl;
free(m_name);
m_name = NULL;
}
}
Person(const Person& p)
{
m_name = (char*)malloc(strlen(p.m_name) + 1);
strcpy(m_name, p.m_name);
m_age = p.m_age;
}
int m_age;
char* m_name;
};
int main()
{
const char* name = "德玛西亚";
Person p(name, 20);
Person p2(p);
return 0;
}
//这样的结果是两个析构函数的调用,解决了重复析构的问题。