在C++中,对象创建后,需要进行初始化;在使用完对象后,也需要及时清理,否则会出现一些问题。(比如:对象在没有初始化时,其使用的后果是未知;而在使用完对象后,如果没有及时清理,会造成一定的安全问题。)
而C++利用了C++利用了构造函数和析构函数解决上述两个问题,这两个函数将会被编译器自动调用; 当然如果我们不提供构造和析构,编译器会提供编译器提供的构造函数与析构函数是空实现。
构造函数的意义:在创建对象时,为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数的意义:作用域对象小回迁系统自动调用,执行清理工作。
关于使用的语法,如下述代码段:
//构造函数语法: 类名(){}
/*
构造函数没有返回值,也不写void
函数名称与类名相同
构造函数可以有参数,因此可以发生重载
程序在调用对象时,会自动调用构造,无需手动调用,而且只会调用一次
*/
//析构函数的语法: ~类名(){}
/*
析构函数没有返回值也不写void
函数析构与类名相同,在名称前加上符号~
析构函数不可以有参数!因此不能发生重载
程序在对象销毁前会自动够细,无需手动调用,而且只会调用一次
*/
在学会其基础的语法后,就可以开始一些实例来进一步认识它们:
在public进行构造函数以及析构函数的书写;然后在函数中创建对象,运行函数后就可以输出构造函数与析构函数的输出值。
当然,正如上面所定义的,析构函数与构造函数会自动空运行(无论如何,它们肯定会在一个程序中运行的!);如果自己定义了它们,那么就会按照定义的那样来运行这两个函数。
class Person
{
//构造函数 (无返回值,也不用写void),(函数名与类名相同!!), 构造函数可以有参数,因此可以发生重载,程序在调用对象时,会自动调用构造,无需手动调用,而且只会调用一次
public: //外界访问的前提
Person()
{
cout << "调用构造函数" << endl;
}
//析构函数:
/*
析构函数没有返回值也不写void
函数析构与类名相同,在名称前加上符号~
析构函数不可以有参数!因此不能发生重载
程序在对象销毁前会自动够细,无需手动调用,而且只会调用一次
*/
~Person()
{
cout << "调用析构函数" << endl;
}
};
void test01()
{
Person p;//这里测试可以发现,在test01中并没有调用构造函数Person,只是创建了一个对象;但是其自动调用了一次!
//析构函数也会自动调用一次,
//对象创建在栈区上,当数据执行完后,释放对象就会自动调用析构函数
}
int main() {
test01();//这里测试可以发现,在test01中没有调用构造函数Person,但是其自动调用了一次!
//如果把对象创建在main函数中,即Person,那么析构函就不会直接释放,因为要等整个main函数结束
//所以在main函数中创建的对象,析构函数不会立即实现,程序会中断,所以在窗口显示不出来析构的调用
cout << endl;
}
构造函数与析构函数都是存在的,且必定会调用,如果没有单独书写,那么编译器会使得其空实现
并且,在对象创建到main函数中时,析构函数不会在窗口发生
原因是:在main函数中,程序并没有执行结束,所以暂时只会运行构造函数,
然后在main函数结束,即窗口结束时,才会运行析构函数!此时窗口关闭,运行析构函数(因此看不到,不过在关闭的一瞬间,窗口依然是可以看得到析构函数的运行的!)
分类如下:
//构造函数
//两种分类:
//1. 按参数分为: 有参构造和无参构造
//2. 按类型分为: 普通构造和拷贝构造
//
//三种调用方式:
//1. 括号法
//2. 显示法
//3. 隐式转换法
关于分类情况以及调用方式的介绍:
class Person
{
public:
//构造函数
Person()//无参构造函数(默认构造函数)
{
cout << "Person的构造函数的调用:" << endl;
}
Person(int a)//有参构造函数()
{
age = 18;
cout << "Person的有参构造函数的调用:" << endl;
}
//拷贝构造函数
Person(const Person &p) //把Person p这个对象来进行拷贝,将其属性全部拷贝到这个Person里
//但是注意,拷贝时,需要用const来修饰这个对象,来确保这个对象是不可修改的
//同时,利用引用的方式去访问
{
//将传入的人的所有属性拷贝到当前的对象身上
age = p.age; //这里,一旦有参构造函数有一些初始化的值,那么拷贝构造时,也需要对成员进行初始化;
cout << "拷贝构造函数的调用: " << endl;
}
~Person()
{
cout << "Person的析构函数的调用:" << endl;
}
int age;
};
//调用 构造函数:
void test01()
{
//1. 括号法————一般都是用括号法去调用构造函数,这样方便且更容易读
//1. 括号法————一般都是用括号法去调用构造函数,这样方便且更容易读
Person p1;//默认构造函数的调用
//那么怎么去调用构造函数的有参调用呢?
Person p2(10);//有参构造函数的括号法调用
//那怎么用括号法去调用拷贝构造函数呢?
Person p3(p2);//直接根据其参数传递类型来确定调用的方法
//注意事项:
//注意事项:
//调用默认构造函数的时候,不要加小括号!否则编译器会认为是一个函数的声明!
cout << "p2的年龄:" << p2.age << endl;
cout << "p3的年龄:" << p3.age << endl;//拷贝构造函数,将对象身上的所有属性进行拷贝
//2. 显示法
Person p4;//默认构造函数的显示法调用
Person p5 = Person(10);//有参构造函数的显示法调用
//如何用显示法调用拷贝构造函数呢?
Person p6 = Person(p2);//显示法调用拷贝构造函数
//注意:Person(10);单独拿出来,被称作匿名对象(未命名); 特点:当前行结束后,系统会立即回收匿名对象(即释放空间)
//注意:不要利用拷贝构造函数,初始化匿名对象
//如Person(p3); 编译器会以为:Person p3,会认为这个过程是对象的声明!
//3. 隐式转换法
Person p7 = 10; //相当于写了: Person p4 = Person(10);//相当于有参构造的调用
Person p8 = p4; //相当于写了: Person p8 = Person(p4);//相当于拷贝构造的调用
}
int main()
{
test01();
cout << endl;
system("pause");
}
拷贝构造函数调用的时机(通常有3种情况)
1. 使用一个已经创建完毕的对象来初始化和一个新对象
2. 值传递的方式给函数参数传值
3. 以值方式返回局部对象
关于构造函数的调用时机(即何时使用它合适)
class Person {
public:
Person()
{
cout << "Person的默认构造函数的调用:" << endl;
}
Person(int age)
{
cout << "有参构造函数的调用: " << endl;
m_age = age;
}
Person(const Person& p)
{
cout << "拷贝构造函数的调用: " << endl;
m_age = p.m_age;
}
~Person()
{
cout << "Person的析构函数的调用:" << endl;
}
int m_age;
};
//1. 使用一个已经创建完毕的对象来初始化和一个新对象
void test01()
{
Person p1(20);
Person p2(p1);//初始化一个新的对象
cout << "p2的年龄为: " << p2.m_age << endl;
}
//2. 值传递的方式给函数参数传值
void dowork(Person p)//作为形参,来传递给函数;这里是直接进行调用拷贝构造函数
{
}
void test02()
{
Person p;//默认构造函数的创建
dowork(p);//这里传递的只是其中的值,如果在传递过后,即dowork函数里,值的大小发生变化;那么dowork的结果也不会变化
}
//3. 以值方式返回局部对象
Person dowork2()
{
Person p1;
return p1;//用值的方式返回的,因为是局部对象,所以创建后会释放空间(析构释放)
//因此返回的值只是拷贝出来的新的对象,然后返回(即调用拷贝构造函数)
}
void test03()
{
Person p = dowork2();
}
int main()
{
test01();
test02();
test03();
system("pause");
}
上述代码段就是关于构造函数调用时机的实例。
如果尝试着去跑上述的代码段,可以发现其并没有输出什么实质性内容。
因为在运行某个对象时,必定会运行一次构造函数与析构函数,而在笔者书写的代码段里,构造函数与析构函数是自己定义的,所以会运行出结果(如果没有进行定义,那么就会空运行);如果自己不去定义它们,那么运行的结果就会是空运行(构造函数空运行定义,析构函数去清理空间)。