类和对象中,包括构造函数和析构函数,比较重要,通过学习总结一下,以便以后可以回顾!
目录
构造函数
1.默认构造函数
2.有参构造函数
3.委托构造函数
4.复制(拷贝)构造函数
5.移动构造函数
左值引用与右值引用
析构函数
析构函数
构造函数,就是在对象创建的时候,把类中的属性进行一个初始化,而且创建对象时候会自动调用并且只调用一次。(就比如我们买一个手机,最开始它会有相应的系统设置(这个就有点类似构造函数),后面可以修改其设置,也就相当于类中函数对属性修改。)
下面介绍几个常用的构造函数...
不含参数的构造函数,举个例子,创建一个Person类,包括年龄和身高两个属性,年龄(整形),身高(整形指针),默认构造初始画年龄是0,身高0。如果我们不写的话系统会默认空实现
#include
using namespace std;
class Person
{
public:
//默认构造函数
Person();
int m_Age;
int* m_Height;
};
//初始化年龄和身高
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
int main()
{
Person p;
cout << "此人的年龄是: " << p.m_Age << endl;
cout << "此人的身高是: " << *(p.m_Height) << endl;
return 0;
}
显然我们在创建函数的同时,把属性进行了初始化,测试把属性输出,另外注意一点的是创建对象时是Person p; 如果我们这样写Person p(); 编译器会认为是创建了一个函数p 返回值类型Person,形参为空 {}空实现 这个一定注意。
下面介绍一下有参构造,还是接着用上面的例子进行添加,继续,如果我们想创建一个指定年龄和身高的对象,这时候就要用到有参构造了,例子
#include
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
int m_Age;
int* m_Height;
};
//默认构造
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age,int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
int main()
{
Person p(18,175);
cout << "此人的年龄是: " << p.m_Age << endl;
cout << "此人的身高是: " << *(p.m_Height) << endl;
return 0;
}
现在创建一个对象p(18,175),创建时可以直接赋值,实现对对象的初始化。
顾名思义,委托构造函数就是把自己构造的事情,交给其他的构造函数顺带完成,例如我把默认构造函数修改一下,
#include
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
//拷贝构造
Person(const Person& p);
int m_Age;
int* m_Height;
};
默认构造
Person::Person() :Person(0, 0)
{
cout << "委托构造函数的调用!" << endl;
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p)
{
cout << "拷贝构造函数的调用!" << endl;
this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);
}
int main()
{
Person p;
cout << "p的年龄是: " << p.m_Age << endl;
cout << "p的身高是: " << *(p.m_Height) << endl;
return 0;
}
这里默认构造用: 把构造交给有参构造顺带完成!所以先会调用有参构造,其次委托构造,输出年龄和身高验证一下:
上面都是开始是给出属性的值进行初始化,而拷贝构造函数在开始是传递一个对象,把对象的个属性拷贝到此对象中,进行对象的初始化(可以把传去的参数,看成一只羊,拷贝构造后克隆了一只新的羊,两只羊的属性一样)根据这样可以实现对象的创建。
这里为了方便把委托构造函数还原成默认构造函数了,如下
#include
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
//拷贝构造
Person(const Person& p);
int m_Age;
int* m_Height;
};
//默认构造
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p)
{
cout << "拷贝构造函数的调用!" << endl;
this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);
}
int main()
{
Person p(18, 175);
cout << "p的年龄是: " << p.m_Age << endl;
cout << "p的身高是: " << *(p.m_Height) << endl;
Person p2(p);
cout << "p2的年龄是: " << p2.m_Age << endl;
cout << "p2的身高是: " << *(p2.m_Height) << endl;
return 0;
}
这里我用了深拷贝构造,这样可以防止堆区的内存重复被析构函数释放,保证代码的安全。深浅拷贝以后有时间我再整理一下,拷贝前面加入const限定可以防止修改成员属性保证完全一致。这个用了引用左值,进行一个赋值,进行理解,上面为左值参数的引用拷贝。
首先要了解左值和右值的引用,左值是位于赋值左侧的对象变量(依附于对象);右值是赋值语句在右侧的值(不依附于对象)。简单可以理解为能用&取地址的就是左值,否则是右值。
int age=20;//左值
int Func()
{
return 100;
}
int ret=Func();//ret是左值,Func()右值
int x=2*2;//x左值,2*2右值
接下来进行右值引用的移动构造函数,即要创建一个右值,然后调用,为了方便输出展示信息,我添加了一个成员函数ShowPerson()展示信息
#include
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
//拷贝构造
Person(const Person& p);
//移动构造函数
Person(Person&& p);
//展示信息
void ShowPerson();
int m_Age;
int* m_Height;
};
//默认构造
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p):m_Age(p.m_Age),m_Height(new int (*p.m_Height))
{
cout << "拷贝构造函数的调用!" << endl;
/*this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);*/
}
//移动构造函数
Person::Person(Person&& p)
{
cout << "移动构造函数的调用!" << endl;
this->m_Age = p.m_Age;
this->m_Height = p.m_Height;
p.m_Height = NULL;
}
//展示信息
void Person::ShowPerson()
{
cout << "年龄是: " << this->m_Age << endl;
cout << "身高是: " << *(this->m_Height) << endl;
}
//返回一个右值的对象
Person GetPerson()
{
Person p;
return p;
}
int main()
{
GetPerson().ShowPerson();
return 0;
}
分析:在主函数中先调用GetPerson()函数创建一个右值对象,调用时创建了一个Person p,所以会先输出调用默认构造,其次调用了右值移动构造,这里进行了深拷贝,防止堆区数据重复释放!右值对象属于无法取地址的对象所以很快就会被清除,用移动构造处理
这里借用:一枚小码农介绍中的一张图,对于普通数据可以直接赋值,但对于指针类型,要把原本的指向赋值为空,然后进行销毁内存!
接下来整理析构函数的时候简单介绍下浅拷贝对指针信息的影响。
析构函数是用于在对象被删除之前的清理工作,在对象生命周期即将结束时被自动调用。(有始有终,什么都会生死,析构函数可以清理对象并且释放内存)
在上面的介绍没有介绍到析构函数,可能不会没有问题。析构函数~加上类名()进行操作,目的时清空数据,释放内存。
现在加上析构函数再次测试调用并且进行思考,例如最简单的,创建一个默认构造函数p,进行默认构造,然后调用展示信息函数输出信息,最后析构函数,释放内容,防止内存泄漏!
#include
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
//拷贝构造
Person(const Person& p);
//移动构造函数
Person(Person&& p);
//析构函数
~Person();
//展示信息
void ShowPerson();
int m_Age;
int* m_Height;
};
//默认构造
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p):m_Age(p.m_Age),m_Height(new int (*p.m_Height))
{
cout << "拷贝构造函数的调用!" << endl;
/*this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);*/
}
//移动构造函数
Person::Person(Person&& p)
{
cout << "移动构造函数的调用!" << endl;
this->m_Age = p.m_Age;
this->m_Height = p.m_Height;
p.m_Height = NULL;
}
//展示信息
void Person::ShowPerson()
{
cout << "年龄是: " << this->m_Age << endl;
cout << "身高是: " << *(this->m_Height) << endl;
}
Person::~Person()
{
cout << "析构函数的掉用!" << endl;
if (this->m_Height != NULL)
{
delete this->m_Height;
}
}
//返回一个右值的对象
Person GetPerson()
{
Person p;
return p;
}
int main()
{
Person p;
p.ShowPerson();
return 0;
}
发现打错字了,懒得改了!后面有时间总结一下深浅拷贝,虚函数,虚析构等等,到时候再总结吧!以上就是我总结的内容,欢迎大佬指正,谢谢!
移动构造的测试解释 std::move函数可以把左值改成右值,所以测试其代码
#include
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
//拷贝构造
Person(const Person& p);
//移动构造函数
Person(Person&& p);
//析构函数
~Person();
重载运算符=
//Person& operator=(Person p);
//展示信息
void ShowPerson();
int m_Age;
int* m_Height;
};
//默认构造
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p):m_Age(p.m_Age),m_Height(new int (*p.m_Height))
{
cout << "拷贝构造函数的调用!" << endl;
/*this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);*/
}
//移动构造函数
Person::Person(Person&& p)
{
cout << "移动构造函数的调用!" << endl;
this->m_Age = p.m_Age;
this->m_Height = p.m_Height;
//delete p.m_Height;
p.m_Height = nullptr;
}
//重载运算符=
//Person& Person::operator=(Person p)
//{
// this->m_Age = p.m_Age;
// this->m_Height = new int(*p.m_Height);
// return *this;
//}
//展示信息
void Person::ShowPerson()
{
cout << "年龄是: " << this->m_Age << endl;
cout << "身高是: " << *(this->m_Height) << endl;
}
Person::~Person()
{
if (this->m_Height != NULL)
{
cout << "析构函数的调用!" << endl;
delete this->m_Height;
}
}
int main()
{
Person p(18, 180);
Person p2(move(p));
return 0;
}
是不是感觉觉得有问题,首先p有参构造,然后move把p变成右值,后调用移动构造函数,在移动构造函数中把p的*m_Height指向空,所以就看不到析构这个的提示了,但是它确实发生了析构,他会有生命周期的结束,就比如把移动构造的NULL用new出一个,它还是会发生析构。这个就不展开了。
这里查阅了一些资料,主体是学习B站上黑马程序员C++的讲解例子,进行自己总结,并且借鉴了一些csdn大佬上面的一些内容,继续学习,Keep energetic!加油