6. 对象特性之构造函数和析构函数


对象的初始化和处理也是两个非常重要的安全问题,C++利用构造函数和析构函数解决上述问题。这两个函数会被编译器自动调用,如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用域创建对象时为对象成员属性赋值,构造函数由编译器自动调用,无须手动调用
  • 析构函数:主要作用于销毁前系统自动调用,执行一些清理工作

一、构造函数和析构函数

1. 构造函数语法:类名(){}

  • 构造函数,没有返回值也不写void
  • 函数名称于类名相同
  • 构造函数可以由参数,因此可以发生重载
  • 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

2. 析构函数语法:~类名(){}

  • 析构函数,没有返回值也不写void
  • 函数名称于类名相同,在名称前加上符号~
  • 析构函数不可以由参数,因此不可以发生重载
  • 程序在对象**销毁前*会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream>
using namespace std;

//对象的初始化和处理
class Person 
{
public:
    //1.构造函数,初始化操作
    Person()
    {
        cout <<"Person 构造函数的调用" <<endl;  //如果我们不写,此函数就为空
    } 
    
    //2.析构函数,进行清理操作
    ~Person()
    {
        cout <<"Person的析构函数调用" <<endl;//如果我们不写,此函数就为空
    }
};

//小结:构造和析构都是==必须有的实现==,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
    Person p; //局部变量,在栈上的数据,test01执行完毕后,释放这个对象
}
int main()
{
    test01(); //析构、构造都调用
    Person p;// 在全部执行完后,调用析构函数
    system("pause");
    return 0;
}

二、构造函数的分类及调用

两种分类方式:

  • 按参数分为:有参构造和无参构造
  • 按类型分为:普通构造和拷贝构造

三种调用方式:

  • 括号法
  • 显示法
  • 隐式转换法
#include <iostream>
using namespace std;
//1. 构造函数的分类及调用
class Person 
{
public:
    //构造函数,类型有参与无参(默认构造)
    Person()
    {
        cout <<"Person 无参(默认)构造函数的调用" <<endl;  
    }
    Person(int a)
    {
        age = a;
        cout <<"Person 有参构造函数的调用" <<endl;  
    }  
    //拷贝构造函数
    Person(const Person &p)
    {
        age = p.age; //将传入人身上的所有属性,拷贝到我自己身上
        cout <<"Person 拷贝构造函数的调用" <<endl;  
    }
    ~Person()
    {
        cout <<"Person的析构函数调用" <<endl;//如果我们不写,此函数就为空
    }
    int age;
};
//调用
void test01()
{
    //1.括号发
    Person p; //默认构造函数调用
    Person p2(10); //有参构造函数
    Person p3(p2); //拷贝构造函数
    cout << "p2的年龄" << p2.age << endl;
    cout << "p3的年龄" << p3.age << endl; //拷贝构造函数复制全部属性

    //2.显示法
    Person p1;
    Person p2 = Person(10); //有参构造
    Person p3 = Person(p2); //拷贝构造
    
    //3.隐式转换法
    Person p4=10; //相当于写了 Person p4 = Person(10);有参构造
    Person p5=p4;//拷贝构造
}
int main()
{
    test01();
    system("pause");
    return 0;
}

注意事项:

  • 调用默认构造函数时,不要加(),因为Person p();,编译器会认为这是一个函数的声明
  • 显示法中,单独拿出右侧对象(eg:Person(10))是匿名对象。特点:当前执行结束后,系统会立刻回收匿名对象
  • 不要利用拷贝构造函数 初始化匿名对象,例如编译器会认为Person (p3)等价于Person p3;

1. 拷贝构造函数

C++中拷贝构造函数的调用时机通常由三种情况

  • 使用一个已经创建完毕的调用对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 值方式返回局部对象
  • 由于编辑器和版本的不同,地址返回可能会有所不同
#include <iostream>
using namespace std;
class Person 
{
public:
    Person()
    {
        cout << "person 默认构造函数调用"<<endl;
    }
    Person(int age)
    {
        cout << "person 有参构造函数调用"<<endl;
        m_age=age;
    }
    Person(const Person &p)
    {
        cout << "person 拷贝构造函数调用"<<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); //值传递方式,形参不影响实参
}
//3. 以值方式返回局部对象
Person dowork2()  //返回值为p1的拷贝对象
{
    Person p1;
    cout << (int*)&p1 << endl;
    return p1;
}
void test03()
{
    Person p = dowork2();
    cout << (int*)&p << endl;
}
int main()
{
    test01();
    test02();
    test03();
    system("pause");
    return 0;
}

2. 构造函数的调用规则

默认情况下,C++编译器至少给一个类添加3个函数

  • 默认构造函数(无参,函数体为空
  • 默认析构函数(无参,函数体为空
  • 默认拷贝构造函数,对属性进行值拷贝


    构造函数调用规则如下:
  • 如果用户定义有参构造函数,C++不再提供默认无参构造,但会提供拷贝构造;
  • 如果用户定义拷贝构造函数,对属性进行值拷贝,C++不会提供其他构造函数;


3. 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作 ————> 问题:由于栈的先进后出,堆区的重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作 ————> 解决浅拷贝带来的问题
小结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

#include <iostream>
using namespace std;
class Person 
{
public:
    Person()
    {
        cout << "person 默认构造函数调用"<<endl;
    }
    Person(int age,int height)
    {
        m_age=age;
        m_height=new int (height)
        cout << "person 有参构造函数调用"<<endl;
    }
    Person(const Person &p)
    {
        cout << "person 拷贝构造函数调用"<<endl;
        m_age = p.m_age;
        m_height = p.m_height; //编译器默认实现就是这行代码
        //深拷贝操作
        m_height=new int (*p.m_height); //重新开辟一块堆区内存
    }
    ~Person()
    {
        //析构代码,将堆区开辟数据做释放操作
        if (m_height != NULL)
        {
            delete m_height;
            m_height = NULL; //防止野指针
        }
        cout << "person 析构函数调用"<<endl;
    }
    int m_age; //年龄
    int *m_height; //身高,堆区数据
};
void test01()
{
    Person p1(18);
    cout << "p1的年龄:" << p1.m_age <<"身高为:"<<*p1.m_height<< endl;
    Person p2(p1);
    cout << "p2的年龄:" << p2.m_age <<"身高为:"<<*p2.m_height<< endl;
}
int main()
{
    test01();
    system("pause");
    return 0;
}
posted @ 2020-04-29 11:49  by1314  阅读(210)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end