C++笔记(十五)——构造函数和析构函数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37764129/article/details/83061809

        每个类都具有构造函数析构函数。其中,构造函数在定义对象时被调用,析构函数在对象释放时被调用。如果用户没有提供构造函数和析构函数,系统将提供默认的构造函数和析构函数。一个有着层次结构的类群组,当派生类的对象诞生的时候,构造函数的执行是由最基类至最尾端派生类;当对象要毁灭前,析构函数的执行则是反其道而行。 

一、构造函数

        构造函数是一个与类同名的方法,是一种特殊的类成员函数专门用来初始化对象。可以没有参数,有一个参数或多个参数,但是构造函数没有返回值。如果构造函数没有参数,该函数被称为类的默认构造函数。为一个类确定必要的构造函数是程序设计不可缺少的一部分。重载函数与它类似,但有返回值。是一个可能被重载的用户定义函数,由类设计者提供,是对象诞生后第一个执行(并且是自动执行)的函数,构造函数被自动应用在每个类对象上。

class CUser
{
private:
    char m_Username[120];
    char m_Password[120];
public:
    CUser()//构造函数
    {
        cout << "调用了CUser类的构造函数" << endl;
        strcpy(m_Username, "hello");
        strcpy(m_Password, "123456");

    }
    bool Logic();
    /*Set Username*/
    void SetUsername(const char* pUsername)
    {
        if(pUsername != NULL)
        {
            strcpy(m_Username, pUsername);//将pUsername拷贝给m_Username
        }
    }
    char *GetUsername() const //加const表示对GetUsername中的成员变量不做修改
    {
        return (char*) m_Username;
    }
    /*Set UserPassword*/
    void SetUserPassword(const char* pUserPassword)
    {
        if(pUserPassword != NULL)
        {
            strcpy(m_Password, pUserPassword);
        }
    }
    char* GetUserPassword() const
    {
        return(char*) m_Password;
    }
};

bool CUser::Logic()
{
    if(strcmp(m_Username, "hello") == 0 && strcmp(m_Password, "123456") == 0)
    {
        cout << "登录成功" << endl;
        return true;
    }
    else
    {
        cout << "登录faile" << endl;
        return false;
    }
}

int main()
{
    CUser user;//定义了一个类的对象user
    user.Logic();
    return 0;
}

        注: (1) 一个类可以包含多个构造函数,各个构造函数之间通过参数列表进行区分。

class CUser
{
private:
    char m_Username[120];
    char m_Password[120];
    static const int DefaultaArraySize = 12;
public:
    CUser()//第一个构造函数
    {
        cout << "调用了CUser类的构造函数" << endl;
        strcpy(m_Username, "hello");
        strcpy(m_Password, "123456");

    }

    CUser(const char* pUsername, const char*  pPassword)//第二个构造函数。跟第一个构造函数通过参数列表的数量区分
    {
        if(pUsername != NULL && pPassword != NULL)
        {
            strcpy(m_Username, pUsername);
            strcpy(m_Password,  pPassword);
        }
    }
    
    CUser(int sz = DefaultaArraySize);  //第三个构造函数,该函数被称为缺省构造函数,用为它不需用户提供任何参数

    bool Logic();
    /*Set Username*/
    void SetUsername(const char* pUsername)
    {
        if(pUsername != NULL)
        {
            strcpy(m_Username, pUsername);//将pUsername拷贝给m_Username
        }
    }

    char *GetUsername() const //加const表示对GetUsername中的成员变量不做修改
    {
        return (char*) m_Username;
    }

    /*Set UserPassword*/
    void SetUserPassword(const char* pUserPassword)
    {
        if(pUserPassword != NULL)
        {
            strcpy(m_Password, pUserPassword);
        }
    }
    char* GetUserPassword() const
    {
        return(char*) m_Password;
    }
};

bool CUser::Logic()
{
    if(strcmp(m_Username, "hello") == 0 && strcmp(m_Password, "123456") == 0)
    {
        cout << "登录成功" << endl;
        return true;
    }
    else
    {
        cout << "登录faile" << endl;
        return false;
    }
}

int main()
{
    CUser user;//定义了一个类的对象user
    user.Logic();

    CUser exa("hello", "123478");//第二个对象
    exa.Logic();
    return 0;
}

        (2)类的构造函数通过使用冒号“:”运算符提供了

  • a、初始化成员的方法。

class CBook
{
public:
      char m_BookName[128];
      const unsigned int m_Prise;
      int m_ChapterNum;
      CBook():m_Prise(32), m_ChapterNum(15)//构造函数,为成员变量赋值
      {
          strcpy(m_BookName, "C++");

      }
};

int main()
{
    CBook book;
    cout << book.m_Prise << '\n';
    return 0;
}

另一种初始化方式:

#include <iostream>
using namespace std;

class CBook
{
public:
	CBook(int a, int b)  //构造函数,为成员变量赋值
	{
		m_Prise = 32;
		m_ChapterNum = 15;
		strcpy(m_BookName, "C++");
		cout << m_Prise << '\n' << m_ChapterNum << '\n';
	};
private:
	char m_BookName[128];
	unsigned int m_Prise;
	int m_ChapterNum;
};

int main()
{
	CBook book(32, 15);
	getchar();
	return 0;
}
  • b、表示类的继承

class CWage : public CEmployee //说明CEage是从CEmployee派生来的 

{

};

        关键字public表明派生类共享基类的公有接口。CWage可以看作是CEmployee的扩展,增加了下标范围检查的额外特性

#include <iostream>

using namespace std;
/*****建立一个员工薪资体系,包括职位、名字、薪资记录方法*****/
class CEmployee  //职员
{
public:
	CEmployee();
	CEmployee(const char* nm)
	{
		strcpy(m_name, nm);
		cout << "名字:" << m_name << endl;
	}
	virtual float computePay()//若要调用后面的该函数,则需要在父类定义并实例化才可以
	{ 
		return 0; 
	};

private:
	char m_name[30];
};
//————————————————————————————————————
class CWage : public CEmployee //小时工;需要知道他的名字、工时m_hours,时薪m_wage
{
public:
	CWage(const char* nm) : CEmployee(nm)
	{
		m_wage = 250.0;//时薪
		m_hours = 40.0;//工时
		cout << "wage: " << m_wage << endl << "hours: " << m_hours << endl;
	}
	void setWage(float wg) //成员函数
	{
		m_wage = wg;
	}
	void setHours(float hrs)
	{
		m_hours = hrs;
	}
	virtual float computePay()
	{
		float sale = m_hours * m_wage;
		cout << "BasicMoney: " << sale << endl;
		return sale;
	}//计薪

private:
	float m_wage; //时薪
	float m_hours; //工作时间
};
  • c、派生类构造函数提供向基类构造函数传递参数的接口

//Qt中的构造函数声明
//析构函数的成员初始化
test::test(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this); //this相当于parent参数
}

        上面是Qt中主窗口函数的类,用冒号分割出来的部分称为成员初始化列表,它提供了一种机制,通过该机制我们可以向IntArray的构造函数传递参数。其中text是子类窗口部件,test(QWidget *parent)是子类的构造函数,QMainWindow(parent)是父类的构造函数,即Ui界面的父窗口。两个text构造函数的工作就是把参数传递给相关的QMainWindow构造函数继承过来的,需要析构的QMainWindow成员都由QMainWindow的析构函数处理。

         this指针是一个隐含指针,指向对象本身,代表了对象的地址

    (3)缺省构造函数

        不需要用户提供任何参数构造函数称为缺省构造函数。如:

class IntArray 

{

    public:

            explicit IntArray( int sz = DefaultArraySize); 

            IntArray (int *array, int array_size);//用内置整数数组初始化一个新IntArray类对象,*array是数组,array_size表示数组大小,

            IntArray (const IntArray &rhs);

    private:

            static const int DefaultArraySize = 12;

}

         这里构造函数 IntArray( int sz = DefaultArraySize);中 DefaultArraySize的值已在private中给出,所以不需要用户提供参数。这类函数被称为缺省构造函数explicit是C++中的关键字,出现这个关键字的原因,是在C++中有这样规定的基础上: 当定义了只有一个参数的构造函数时,同时也定义了一种隐式的类型转换。作用主要是用来修饰类的构造函数表明该构造函数是显式的禁止单参数构造函数的隐式转换。使用QT Creator默认生成代码中的类中构造函数前面就会出现此关键字。explicit关键字在类内部的声明中。在外部的实现部分不需要使用。一般界面类、线程类构造函数都加上此关键字

详细参考http://blog.csdn.net/e3399/article/details/7610430

   (4)类域操作符(::)

        在定义类的成员函数,用来指出成员函数属于哪个类。

        在一个函数内被定义的对象是局域的,它只在定义它的函数体内可见每个类维持一个域,在这个域之外,它的成员是不可见的。类域操作符告诉编译器,后面的标识符可在该类的范围内被找到。 

IntArray :: IntArray (int sz)// IntArray ()函数被定义为 IntArray类的成员

{

      //设置数据成员

      size = sz;

      ia = new int [ _size ];

      //初始化内存

      for(int ix = 0;ix < _size; ix++)

          ia [ ix ] = 0;

}

IntArray :IntArray (int *array, int array_size)//用内置整数数组初始化一个新的IntArray类对象,*array是数组,array_size表示数组大小

{

      //设置数据成员

      size = sz;

      ia = new int [ _size ];

      //初始化内存

      for(int ix = 0;ix < _size; ix++)

          ia [ ix ] = 0;

}

IntArray :: IntArray (const IntArray &rhs)

{

     //拷贝构造函数

     _size = rhs._size;

    ia = new int [ _size ];

    for(int ix = 0; ix < _size; ix++)

         iz [ ix ] = rhs.ia [ ix ];

}

        上例中引入了一种新的复合类型:引用,即 IntArray &rhs 。引用是一种没有指针语法的指针。因此我们写成rhs._size,而不是rhs->_size。引用提供对对象间接访问。     

          上面三个构造函数的实现方式相同,为了缩短代码量,我们可以写成下面的形式

class IntArray 
{
    public:
            explicit IntArray( int sz = DefaultArraySize); //explicit暂时不考虑它的意思
            IntArray (int *array, int array_size);//用内置整数数组初始化一个新IntArray类对象,*array是数组,array_size表示数组大小,
            IntArray (const IntArray &rhs);
    private:
            static const int DefaultArraySize = 12;

}

void IntArray :: init(int sz, int *array)
{
    _size = sz;
    ia = new int[ _size ];
    for(int ix = 0; ix < _size; ix++)
       if(!array)
          ia[ix] = 0;
        else ia [ ix ] = array[ ix ];
}

IntArray :: IntArray(int sz)//构造函数1,IntArray(int sz)函数是IntArray类的成员
{
    init (sz, 0);
}

IntArray :: IntArray(int *array, int sz) 
{
    init(sz, array);
}

IntArray :: IntArray(const IntArray &rhs)
{
    init(rhs.size, rhs.ia);
}

注:1)构造函数上唯一的语法限制是不能指定返回类型,甚至void也不行。

        2)构造函数的实例化内容不要太多

二、析构函数

            每个类对象被程序最后一次使用之后,它的析构函数就会被自动调用。即对象即将毁灭但未毁灭前的那一刻,最后执行(是自动执行)的函数。析构函数在对象超出作用范围使用delete运算符释放对象时被调用,用于释放对象占用的空间。如果用户没有显式地提供析构函数,系统会提供一个默认的析构函数。析构函数也是以类名作为函数名,与构造函数不同的是在函数名前添加一个“~”符号,标识该函数是析构函数。析构函数没有返回值,甚至void类型也不可以,析构函数也没有参数,因此析构函数是不能够重载的。这是析构函数与普通函数最大的区别。我们可以为一个类定义多个构造函数但我们只能提供一个析构函数,它将被应用在类的所有对象上。

#include <iostream>
#include <string.h>

using namespace std;

class CDemo
{
public:
	CDemo(const char* str); //构造函数声明
	~CDemo() //析构函数,类内声明形式
    {
        cout << "Destructor called for " << name << '\n';
    }
private:
	char name[20];
};

CDemo::CDemo(const char* str) //构造函数实例化,在类外定义时就要用作用域运算符“::”
{
	strncpy(name, str, 20);
	cout << "Constructor called for " << name << '\n';
}

/*CDemo::~CDemo() //析构函数,类外实例化
{
	cout << "Destructor called for " << name << '\n';
}*/

class CExample : public CDemo
{
public:
    cout << "CExample is " << name << '\n';
private:
    
}

void func() //成员函数
{
	CDemo LocalObjectInFunc("LocalObjectInFunc"); //(5)
	static CDemo StaticObject("StaticObject"); //(6)
	CDemo* pHeapObjectInFunc = new CDemo("HeapObjectInFunc"); //(7)
	cout << "Inside func" << endl; //(8)
} //(9)函数结束,调用析构函数释放构造函数(5)

CDemo GlobalObject("GlobalObject"); //全局变量 (1)

void main()
{
	CDemo LocalObjectInMain("LocalObjectInMain"); //(2)
	CDemo* pHeapObjectInMain = new CDemo("HeapObjectInMain"); //(3)
	cout << "In main, before calling func \n"; //(4)
	func(); 
	cout << "In main, after calling func \n"; //(10)

	getchar();
} //(11)运行完主函数后,调用析构函数释放构造函数(2)、(6)、(1)

总结:

  • 1) 对于全局对象(本例中的GlobalObject),程序一开始,其构造函数就先被执行(比程序进入点更早);程序即将结束前其析构函数被执行。
  • 2) 对于局部对象,当对象诞生时,其构造函数被执行;当程序流程将离开该对象的存活范围(以至于对象将毁灭)时,其析构函数被执行。
  • 3) 对于静态(static)对象,当对象诞生时其构造函数被执行;当程序将结束时(此对象因而将遭到毁灭),其析构函数才被执行,但比全局对象的析构函数早一步执行。
  • 4) 对于以new方式产生出来的局部对象,当对象诞生时其构造函数被执行。析构函数则在对象被delete时执行。

猜你喜欢

转载自blog.csdn.net/qq_37764129/article/details/83061809