类的基本知识大家都知道,这里先说一下构造函数。
类定义完成后系统并不会为类分配空间,而是在定义类的对象时分配空间,就像C语言中定义变量,只有定义了变量,才有空间,为对象分配空间时通过构造函数来完成的。
构造函数可以人为定义,也可以系统默认调用。如果不自己定义构造函数,则系统的调用的默认构造函数只是为对象分配空间,并不会对类的属性进行赋值等操作,或者说此时的值是未知的、随机的。
人为定义的构造函数可以根据有无参数分为无参构造函数和有参构造函数,有参构造函数的参数数量可以不同。值得一提的是,所有的构造函数的函数名都是类名,若在类中可以定义多个构造函数(重载函数)。
#include<iostream>
using namespace std;
class score
{
public:
score();
score(int,int,int);
void show();
private:
int c;
int m;
int e;
};
score::score()
{
cout<<"score()"<<endl;
}
score::score(int c,int m,int e):c(c),m(m),e(e)
{
cout<<"score(int,int,int)"<<endl;
}
void score::show()
{
cout<<this->c<<" "<<this->m<<" "<<this->e<<endl;
}
int main()
{
score s1;
s1.show();
score s2(80,90,100);
s2.show();
}
系统会根据形参选择合适的构造函数为对象赋初值,系统默认的构造函数就如同上面代码中没有参数的构造函数,类的属性的值是未知的,一般将其赋值为0。
值得一提的是,在有参构造函数中,如果是int、float等基本类型可以用上面代码中的初始化列表赋初值,但是对于字符串要注意使用strcpy函数,这是字符串相关知识中的基本内容;同样,对于其他非基本类型如另一个类,就需要考虑更多,需要用到拷贝构造函数,接下来就讲一下拷贝构造函数。
拷贝构造函数:
拷贝构造函数说到底也是构造函数的一种,它也会为对象分配空间,不同的是它的形参,它的形参是同类的对象。和构造函数一样,在未人为定义时,它也是系统默认会调用的函数。
#include<iostream>
using namespace std;
class score
{
public:
score();
score(int,int,int);
// score(score&);
void show();
private:
int c;
int m;
int e;
};
score::score()
{
cout<<"score()"<<endl;
}
score::score(int c,int m,int e):c(c),m(m),e(e)
{
cout<<"score(int,int,int)"<<endl;
}/*
score::score(score& p)
{
*this=p;
cout<<"score(score& p)"<<endl;
}*/
void score::show()
{
cout<<this->c<<" "<<this->m<<" "<<this->e<<endl;
}
int main()
{
score s(80,90,100);
s.show();
score s1(s);
s1.show();
}
运行上面的代码,上图第一个结果就是系统默认调用的拷贝构造函数,第二个是人为定义的拷贝构造函数。通常我们把这种拷贝称为浅拷贝,因为它只是简单的复制原对象所有内容。
深拷贝:
现在我们来考虑一下,如果类中有指针怎么办?有以下代码:
#include<iostream>
using namespace std;
class Abc
{
public:
Abc();
Abc(Abc&);
void set(int);
void show();
private:
int* i;
};
Abc::Abc()
{
this->i=new int(1);
}
Abc::Abc(Abc& p)
{
*this=p;
}
void Abc::show()
{
cout<<this->i<<" "<<*(this->i)<<endl;
}
void Abc::set(int i)
{
*this->i=i;
}
int main()
{
Abc a;
a.show();
Abc b(a);
b.show();
b.set(5);
a.show();
b.show();
#include<iostream>
using namespace std;
class Abc
{
public:
Abc();
Abc(Abc&);
void set(int);
void show();
private:
int* i;
};
Abc::Abc()
{
this->i=new int(1);
}
Abc::Abc(Abc& p)
{
*this=p;
}
void Abc::show()
{
cout<<this->i<<" "<<*(this->i)<<endl;
}
void Abc::set(int i)
{
*this->i=i;
}
int main()
{
Abc a;
a.show();
Abc b(a);
b.show();
b.set(5);
a.show();
b.show();
}
先看上面代码,我们的本意是修改对象b的指针i指向空间的值,但是结果确实如下:
可以卡到,这个系统默认的拷贝构造函数是多么的彻底,把a完完全全复制了一遍,很明显,拷贝完成后对象a的指针i和对象b的指针i指向同一块空间,因此,b修改了空间的值,a也随之改变,不符合我们的要求,那么如何更改呢?这就是深拷贝了。
在上代码中我们申请了一块空间,并将此空间赋值为1,那么我们若想让b和a的指针不指向同一块空间,自然就需要在拷贝时重新申请一块空间,让b的指针i指向它。代码如下:
Abc::Abc(Abc& p)
{
this->i=new int(*p.i);
}
仅仅需要更改拷贝构造函数即可,这句代码的意思是:申请一块新的空间,空间长度为int型数据的长度,将被拷贝的对象的指针指向的空间的值赋值给这块新申请的空间,并且将此空间的首地址给新对象的指针i。结果如下:
可以看到,此时修改b的指针指向的空间的值并不会改变a的指针指向的空间的值,因为它们是两块不同的空间。
析构函数:
析构函数和上面的构造函数、拷贝构造函数类似,也是属于系统会自动调用的函数,它会在对象的生命周期结束时被调用,做一些善后清理工作。在有多个对象时,它的调用顺序是需要注意的。
#include<iostream>
using namespace std;
class people
{
public:
people();
people(int);
~people();
private:
int id;
};
people::people():id(0)
{
cout<<this->id<<" people()"<<endl;
}
people::people(int id):id(id)
{
cout<<this->id<<" people(int)"<<endl;
}
people::~people()
{
cout<<this->id<<" ~people()"<<endl;
}
people p1(1);
people p2(2);
int main()
{
people p;
people p3(3);
people p4(4);
}
上面代码的运行结果如图,调用构造函数的顺序遵从定义变量的规则,全局变量在程序一开始就开辟空间,函数中的则是在定义是开辟空间,析构函数的调用过程遵从栈的思想,先调用构造函数的最后调用析构函数。
析构函数的作用是用来作善后清理工作,那么什么时候需要我们做善后工作呢,毫无疑问,当我们申请堆区空间时,是需要我们手动释放的,每次申请空间都逐一手动释放难免太过麻烦,所以可以利用析构函数在对象的生命周期结束时必定被系统调用的特性,让它来帮我们释放空间,以免出现内存泄漏。
#include<iostream>
using namespace std;
class people
{
public:
people(int);
~people();
protected:
int* data;
};
people::people(int data)
{
this->data=new int(data);
cout<<"people(int)"<<endl;
}
people::~people()
{
cout<<"~people()"<<endl;
delete this->data;
}
void fun()
{
cout<<"fun() start"<<endl;
people(1);
cout<<"fun() end"<<endl;
}
int main()
{
fun();
}