通过学习构造析构函数来完成对对象的数据成员进行初始化和清理工作
一、构造函数
1.知识点
构造函数是一种特殊的函数,主要用来在创建对象是初始化对象,即为对象的成员变量赋初始值
2.构造函数的定义
1.构造函数名和类名相同
2.构造函数没有返回值类型和返回值
3.构造函数可以重载,需要满足函数重载的条件
class student
{
public:
student(){}//无参(默认)构造
student(int a){}//有参(带参)构造
}
3.调用时机
1.在一个新的对象被创建的时候,系统会主动调用这个构造函数来对对象里的数据成员进行初始化操作
//调用无参构造
student stu;
student* p=new student;
//调用带参构造
student stu(10);//使用栈区内存
student* p=new student(10);//使用堆区内存
#include <stdio.h>
#include<iostream>
using namespace std;
class Person
{
int age;
char name[20];
float hight;
public:
Person()//自定义的无参构造
{
age = 18;
strcpy(name, "yunfei");
hight = 1.78f;
}
Person(int x, float y,const char* s)//带参构造
{
age = x;
hight = y;
strcpy(name, s);
}
void showPerson()
{
cout << age << "\t" << name << "\t" << hight << endl;
}
};
int main()
{
Person* p1 = new Person;//调用无参构造
p1->showPerson();
delete p1;
p1 = new Person(18, 1.80f, "yunfei");//调用带参构造
p1->showPerson();
delete p1;
Person stu;//实例化对象-隐式调用无参构造
stu.showPerson();
//传参是按照带参数的构造函数的形参定义顺序来传参的
Person p(18, 1.80, "feiyun");
p.showPerson();
getchar();
return 0;
}
4.构造函数的特点
1.如果一个类中没有显示的给出构造函数,系统会自动地给出一个缺省的(隐式)什么都不干的构造函数
2.如果类中有多个构造函数,那么通常会有不同的参数列表和函数体
3.如果用户提供了无参(有参)构造,那么系统就不在提供默认构造,一般会自己再写一个无参构造
4.类中如果只有有参构造,没有无参构造,那么就不能调用默认构造的方式初始化对象,想用这种方式初始化对象那么就提供无参构造
5.如果构造函数不在公有属性下面,那么会导致类外不可以访问,那么就不能再类外定义对象了(因为你定义了对象之后是不能对他进行初始化操作的)
6.类中必须有构造函数,只有在创建对象的时候才会调用构造函数
#include<iostream>
#include <stdio.h>
using namespace std;
class A
{
int a;
int b;
public:
A()
{
cout << "A构造" << endl;
}
void showA()
{
cout << "构造析构" << endl;
}
};
//两个对象之间如果要进行交互,并不是直接把对象传过去,而是通过一个管理者来管理两个对象
//类和类之间的关系——包含 (友元 继承)
//如果一个类中要包含一个其他类的对象,那么不直接包含对象,而是包含一个类成员的指针(只有四个字节,所占内存小)
class B
{
A *a;
//构造函数里面new出对象,然后在析构函数里面进行释放操作,或者自己写个功能释放
public:
B()
{
cout << "B构造" << endl;
}
};
int main()
{
A* p = new A;
//(*p).showA();
p->showA();
delete p;
getchar();
getchar();
return 0;
}
如果一个类中要包含一个其他类的对象,那么不直接包含对象,而是包含一个类成员的指针(只有四个字节,所占内存小)
5.拷贝构造
(一种特殊的构造函数)
1.拷贝构造是一种特殊的构造函数,用自身这种类型来构造自身
作用:在定义一个新的对象的时候,用自身这种类型的对象来初始化新的对象,拷贝的是成员变量的值,函数是大家都有的
//示例
class student
{
};
student stu1;
student stu1=stu2;//这里会调用系统系统提供的拷贝构造函数(浅拷贝)
拷贝构造的定义:
1.如果自己没有写拷贝构造,系统会提供一个隐式的拷贝构造,而这个拷贝构造的操作,可以理解为,是用"="号一个一个的将已存在于对象的数据成员赋值给新创建的对象中(但是有指针的时候,指针是不能用“=”来相互赋值的,所以要自己写一个拷贝构造函数)(深拷贝)
2.自定义拷贝构造:
类名(const 类名& 引用名){}
1.这里的const不加也可以,但是只是使用原有的对象给新的对象赋值,不会改变原有的对象,所以使用const,是防止原有的对象被修改。
2.使用&引用,就不会在传递参数的时候,调用拷贝构造
//示例
class student
{
int id;
public:
student(const student&stu)//引用
{
this->id=stu.id;
//把传进来对象的id赋值给当前调用对象的id
}
}
拷贝构造的调用:
1.在用同一类型的对象,去初始化另一个对象的时候,注意是在定义对象时初始化,而不是赋值
student stu1;
student stu2=stu1;//隐式调用拷贝构造
student stu3(stu2);//显式调用拷贝构造,相当于用stu2这个对象里的数据去初始化stu3这个对象里相同的数据
student *pstu=new student(stu3);
2.在函数传参时,函数的形参是类对象
void fun(student stu){} fun(stu1);//函数调用传参时用拷贝构造
3.如果一个函数的返回值类型是对象,在函数调用结束,返回对象的时候调用拷贝构造
student fun1(student stu) { return stu; } //这里在函数调用的时候会调用两次拷贝构造,函数传参一次,函数返回一次
6.拷贝构造的问题
1.浅拷贝
浅拷贝我们不写,系统也会提供一个默认的拷贝构造,而这个拷贝构造的操作,我们可以理解为,是用“=”一个一个的赋值的,称之为浅拷贝。
但是在使用指针的时候可能就会出现问题,如果对象h1里面有一个指针addr指向一块内存,那么通过调用浅拷贝创建其他的对象(和h2)时,h2中的addr和h1的addr指向相同的内存地址。这样是非常危险的,如果其中一个对象把该内存释放掉了,就会导致另外一个对象的指针变成野指针
2.深拷贝
也就是存在上述问题的时候才需要深拷贝,而深拷贝也就是自己定义拷贝构造函数,给新的对象申请内存,来保存内容,而不是两个对象的指针指向同一个内存
class person
{
char* name;
public:
person(const person&p)//拷贝构造
{
//给新的对象的指针申请内存来存内容,逐一赋值给其他成员
//strlen是获取字符串的大小,不包括'\0'
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
//字符串可以使用strcpy进行复制内容,但是其他数据类型就不可以了,要使用memcpy()
}
person(const char* n)//构造函数
{
name = new char[strlen(n) + 1];//+1是为了保留'\0'
strcpy(name, n);
cout << name << endl;
}
~person()
{
if (name != NULL)
{
delete[]name;
name = NULL;
}
cout << "person析构" << endl;
}
};
void fun(person p)//相当于是p=p1
{
}
int main()
{
{
person p1("sssss");
person p2=p1;//调用的是自己写的显示拷贝构造
fun(p1);
}
system("pause");
return 0;
}
3.赋值构造函数
human f1,f2;
f2=f1;//调用赋值构造函数-浅拷贝
//自己实现赋值构造函数-相当于是重载=,这个赋值运算符
human& operator=(const human& other)
{
//防止 human f1;f1=f1,自己给自己赋值
if(this==&other)
{
return *this;
}
//还有如果有必要,要把this对象的的动态内存释放掉,再重新分配内存,并且赋值
//具体实现
.......
return *this;
//为什么要返回对象本身引用,为了方便做连续赋值,比如:f1=f2=f3;
}
注意:
1.每次函数传参是对象的时候,为了不去创建一个新的对象增加开销,一般都是引用传参,而且为了函数内部不会更改传进去的实参对象,一般会用const修饰,防止实参对象被修改
2.在函数返回的是一个类对象的时候,如果不用引用就会调用拷贝构造创建一个新的对象,而且形参是const常量成员,所以需要使用 const human&
const human& getBetter(const human& man1,const human& man2)
{
if(man1>man2)
{
return man1;
}
else
{
return man2;
}
}
3.对象数组的初始化列表中,使用对象初始化时会调用拷贝构造
human f1,f2,f3,f4; human f[4]={f1,f2,f3,f4}; //这样也会调用拷贝构造函数
二、析构函数
主要就是用来释放内存的,一般在使用了指针,才会去自己写析构函数,释放内存
1.知识点
析构函数和构造函数一样,也是一种特殊的函数,主要的作用是在对象结束生命周期时,系统自动调用析构函数,来做一些清理工作,比如上面,在对象有申请内存时,那么需要自己去释放内存,这个释放内存的操作就可以写在析构函数中,在对象死亡的时候自动调用析构函数释放内存,那么这种就不用担心忘记释放内存了
2.析构函数的定义
1.函数名和类名相同,在前面加上一个~
~student(){}
2.没有返回值类型和返回值,也没有参数
3.如果类中没有自己写析构函数,那么系统将会自动给出一个隐式什么都不干的析构函数
3.析构函数的调用时机
1.析构函数必须是在公有属性下
2.在对象死亡时,会主动调用他的析构函数
3.一般是类中有指针申请内存的时候,那么我们就会定义析构函数来释放内存(释放堆区内存)
4.析构函数的特点
1.析构函数做的事是对对象做一些清理工作
2.一个类只有一个析构函数(不能重载)
3.析构函数可以调用任意次数
#include<iostream>
#include <stdio.h>
using namespace std;
class person
{
char* name;
public:
person(const char* n)//构造函数
{
name = new char[strlen(n) + 1];//+1是为了保留'\0'
strcpy(name, n);
cout << name << endl;
}
void setname(const char* n)
{
name = new char[strlen(n) + 1];//+1是为了保留'\0'
strcpy(name, n);
cout << name << endl;
}
~person()
{
if (name != NULL)
{
delete[]name;
name = NULL;
}
cout << "person析构" << endl;
}
};
int main()
{
{
//person *p1 = new person("sssss");//因为这是一个指针,不会死亡,所以析构函数就不会自己调用了
person p2("sss");//定义一个类对象
p2.~person();//调用析构函数
cout << "11111" << endl;//这样写析构函数被调用了两次,p2死亡的时候调用了一次
}//p处理{}作用域,析构函数执行
cout << "22222" << endl;
getchar();
getchar();
return 0;
}
调用顺序
class A
{
public:
A()
{
cout << "A构造" << endl;
}
~A()
{
cout << "A析构" << endl;
}
};
class B
{
A a;
public:
B()
{
cout << "B构造" << endl;
}
~B()
{
cout << "B析构" << endl;
}
};
int main()
{
{
B b;//因为是在栈区创建的,先进后出
//所以是A构造,B构造,B析构,A析构
}//{}相当于是作用域,出了作用域后,b会死亡,所以会执行析构
/*输出:
A构造
B构造
B析构
A析构
*/
getchar();
getchar();
return 0;
}
三、this指针
1.知识点
1.this指针是系统自动生成且隐藏,我们看不到定义,但是可以使用
2.this指针并不是对象本身的一部分,它的作用域在类的内部。当类的普通函数在访问类的普通成员的时候,该this指针总是指向调用者对象
2.this指针的使用
1.必须在类中使用,在类外是使用不了的
2.this->成员名;或者(*this).成员名;表示调用者的某个成员
3.return this;表示返回当前调用者对象的首地址
4.return *this;表示返回当前调用者对象
3.this指针在代码中的表现
//在类中函数的形参和类中的成员同名 void myclass::fun(int sum) { this->sum=sum; } //这样我们就能通过this指针指向sum,来表示this指向的这个sum是当前对象的sum,如果sum=sum;那么这两个sum都是表示的形参
在形参和类中成员有重名的时候,那么用this指针来区分,this指针指向的成员是类中的成员
class person
{
int age;//age类中成员
char* name;
public:
person(int age, const char* name)//age形参
{
this->age = age;
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
}
~person()
{
if (this->name != NULL)
{
delete[]this->name;
this->name = NULL;
}
}
};