构造析构
构造析构
通过学习构造析构函数来完成对对象的数据成员进行初始化和清理工作
一、构造函数
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");
//Person p = { 18,1.80,"feiyun" };
//p是定义的类变量名
p.showPerson();
getchar();
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 类名& 引用名){}
这里的const不加也可以,但这里是拷贝,也就是复制的意思,所以加上防止被修改
在函数体里面通常的做法是逐一拷贝传进来的对象的成员,赋值新的对象,当然也可以自己定义内容
//示例
class student
{
int id;
public:
student(const student&stu)//引用
{
this->id=stu.id;
//把传进来对象的id赋值给当前调用对象的id
}
}
拷贝构造的调用:
1.在用同种类的对象,去初始化另一个对象的时候,注意是在定义对象初始化,而不是赋值
student stu1;
student stu2=stu1;//显示调用拷贝构造
student stu3(stu2);//隐式调用拷贝构造
student *pstu=new student(stu3);
2.在函数传参时,函数的形参是类对象
void fun(student stu){}
fun(stu1);//函数调用传参时用拷贝构造
3.如果一个函数的返回值类型是对象,在函数调用结束,返回对象的时候调用拷贝构造
student fun1(student stu){return stu;}
//这里在函数调用的时候会调用两次拷贝构造,函数传参一次,函数返回一次
拷贝构造的问题
1.浅拷贝
浅拷贝我们不写,系统也会提供一个默认的拷贝构造,而这个拷贝构造的操作,我们可以理解为,是用“=”一个一个的赋值的,称之为浅拷贝。因为在使用指针的时候可能就会出现问题,因为我们知道两个同等类型的指针之间用"="赋值,是表示两个指针的地址指向同一个内存,那么就会可能出现一个问题,就是两个对象的指针都指向同一个内存,那么如果其中一个对象把该内存释放掉了,就会导致另外一个对象的指针变成野指针
2.深拷贝
也就是存在上述问题的时候才需要深拷贝,而深拷贝也就是自己定义拷贝构造函数,给新的对象申请内存,来存内容,而不是两个对象的指针指向同一个内存
class person
{
char* name;
public:
person(const person&p)
{
//给新的对象的指针申请内存来存内容,逐一赋值给其他成员
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
}
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;
}
二、析构函数
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");//因为这是一个指针,不会死亡,所以析构函数就不会再调用了
//p1->~person();
//p1->setname("qqqqqq");
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;//因为是在栈区创建的,先进后出
//所以是B析构,A析构
}//{}相当于是作用域,出了作用域后,b会死亡,所以会执行析构
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;
}
}
};