C++笔记(八)——类2:构造函数和析构函数及拷贝构造函数

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

一、构造函数

1.1 构造函数的定义

  •         构造函数是一个与类同名的方法,是一种特殊的类成员函数专门用来初始化对象。是一个可能被重载的用户定义函数,由类设计者提供,是对象诞生后第一个执行(并且是自动执行)的函数,构造函数被自动应用在每个类对象上。
  •         每个类都必须有一个构造函数,可能有0个、一个或多个参数。如果没有给类定义构造函数,则编译系统自动地生成一个空的构造函数(参数和函数体均为空)即类的默认构造函数
  • 重载函数与它类似,但有返回值。

1.2 构造函数的特性

  • 构造函数的名字必须与类名相同
  • 定义对象时被系统自动调用
  • 可以有任意类型的参数(也可不带参数),但不能有返回值,void也不可以
  • 被定义为公有的,但其他时间无法被调用

下面是一个账户密码登录的小demo:

a、在一个文件中实现

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;
}

b、在.h和.cpp文件中实现

头文件 user.h

#ifndef __USER_H__
#define __USER_H__

class CUser
{
private:
	char m_Username[120];
	char m_Password[120];

public:
	CUser();//构造函数,注意它的前面没有返回值

        /*设置用户名输入*/
	void SetUsername(const char* pUsername);
	char const *GetUsername(); //加const表示对GetUsername中的成员变量不做修改

	/*Set UserPassword*/
	void SetUserPassword(const char* pUserPassword);
	char const *GetUserPassword() ;

        /*设置用户名、密码比对*/
        bool Logic(); 

};

#endif

user.cpp文件

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

using namespace std;

CUser::CUser()//构造函数,注意它的前面没有返回值
{
	cout << "调用了CUser类的构造函数" << endl;
	strcpy(m_Username, "hello");
	strcpy(m_Password, "123456");

}

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

/*Set UserPassword*/
void  CUser::SetUserPassword(const char* pUserPassword)
{
	if (pUserPassword != NULL)
	{
		strcpy(m_Password, pUserPassword);
	}
}
char const *CUser::GetUserPassword()
{
	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;
	}
}

main.h文件

#include <iostream>
#include "user.h"

using namespace std;

int main()
{
	CUser user;//实例化对象user

	user.Logic();

	getchar();
	return 0;
}

运行结果:

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

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;
}

1.3 调用构造函数的条件

  • 1.定义对象直接调用

        Complex A(2,4);

  • 2.动态分配对象空间时

        Complex *p = new Complex(3,8);

  • 3.定义无名对象(没有名字的对象)

        Complex (2,9);

        new Complex (6,70); //new申请了空间,没有指针变量去接,内存会泄露

int main()
{
        //实例化对象
	CStu *p = new CStu; //申请内存,对象为指针形式

	p->setNum(34);
	p->setName((char*)"Lily");
	cout << "num =" << p->getNum() << endl;
	cout << "num =" << p->getName() << endl;

	delete p; //释放内存

        //实例化对象
        CStu Lucy;
	Lucy.setNum(100);
	Lucy.setName((char *)"Lucy");
	cout << "Lucy.num = " << Lucy.getNum() << endl;
	cout << "Lucy.name = " << Lucy.getName() << endl;

	getchar();
	return 0;
}

三、拷贝构造函数:旧对象给新对象赋值

        主要用于当类中指针数据成员时需要自己定义拷贝构造函数,拷贝构造函数与构造函数名字相同,同样也不可以有返回值形参为该类的引用而且形参前要加const限制

这里采用登记名字的那个demo来解释:

在头文件中其析构函数定义如下;

private:
    char *name; //指针类型的数据成员变量,在构造函数中定义其内存空间

public:
CStu(char *str)
{
	cout << "构造函数" << endl;
	m_name = new char[strlen(str) + 1];//传的参数有多大,数组就定义多大再加一
	strcpy(m_name, str);
	
}

其拷贝构造函数定义如下:


CStu(const CStu &ob) //&ob表示对象的引用,即ob是对CStu类中一个对象的引用

{

    cout << "拷贝构造函数” << endl;

    m_name = new char[strlen(ob.m_name)+1];//这句话是定义名字的内存空间。因为ob是一个对象的引用,所以这里用ob.name的形式表示这个名字

    strcpy(m_name, ob.m_name);

}

 

在.cpp文件中实例化对象

int main()
{
	{
		CStu Lucy((char*)"Lucy");
		cout << "lucy.m_name =" << Lucy.getName() << endl;
		cout << "-------------------" << endl;

		CStu Bob = Lucy; //&**旧对象给新对象赋值,调用拷贝构造函数**&
		cout << "Bob.m_name = " << Bob.getName() << endl;

	}
	
	getchar();
	return 0;
}

注意所谓的旧对象给新对象赋值,就是通过下面这句话来实现的

CStu Bob = Lucy;

如果实例化了对象Bob后再将Lucy赋值给Bob就不对了,这就不是旧对象给新对象赋值了

int main()
{
	{
		CStu Lucy((char*)"Lucy");
		cout << "lucy.m_name =" << Lucy.getName() << endl;
		cout << "-------------------" << endl;
		
		CStu Bob((char*)"Bob");
		Bob = Lucy; //旧对象给旧对象赋值,不会调用拷贝构造函数,(bob.m_name = lucy.m_name指向同一一个地方)
		cout << "Bob.m_name = " << Bob.getName() << endl;

	}
	
	getchar();
	return 0;
}

旧对象给旧对象赋值出现两个对象的指针指向错误,没有调用拷贝构造函数,出现段错误

lucy释放内存空间后,Bob再释放它的空间时就是段错误。这个过程是旧对象赋值给旧对象,说明它没有调用拷贝构造函数

这有个有问题的代码:

STU.h文件

#ifndef __STU__
#define __STU__

#include <iostream>
#include <string>

using namespace std;

class CStu{  //类名以大写字母c开头
private:
	int m_num; //成员变量以“m_”为前缀
	char m_name[32];
	char *m_Sub;

public:
	CStu(char *str); //构造函数
	~CStu(); //析构函数
	CStu(const CStu &ob);//拷贝构造函数

	void setNum(int x);
	int getNum(void);

	//void setName(char *str);
	//char* getName(void);

	void setSub(char *str);
	char* getSub(void);

};

#endif

STU.cpp文件

#include "STU.h"

CStu::CStu(char *str)//构造函数
{
	cout << "构造函数" << endl;
	m_Sub = new char[strlen(str) + 1]; //申请内存空间
	strcpy(m_Sub, str);
}

CStu::~CStu()
{
	delete[] m_Sub;
	cout << "析构函数" << endl;
}

CStu::CStu(const CStu &ob)
{
	cout << "拷贝构造函数" << endl;
	m_Sub = new char[strlen(ob.m_Sub) + 1];
	strcpy(m_Sub, ob.m_Sub);
}

void CStu::setNum(int x)
{
	m_num = x;
	//return;
}
int CStu::getNum(void)
{
	return m_num;
}

//void CStu::setName(char *str)
//{
//	strcpy(m_name, str);
//
//	//return;
//}
//char* CStu::getName(void)
//{
//	return m_name;
//}

void CStu::setSub(char *str)
{
	strcpy(m_Sub, str);

	//return;
}
char* CStu::getSub(void)
{
	return m_Sub;
}

main.cpp文件

#include <iostream>
#include "STU.h"

using namespace std;

int main()
{
	//{
	//实例化对象
	//CStu Lucy((char*)"Lucy");
	CStu Bob((char*)"Bob");

	//Lucy.setNum(100);
	//Lucy.setName((char *)"Lucy");
	//Lucy.setSub((char *)"数学");

	Bob.setNum(30);
	//Bob.setName((char *)"Bob");
	Bob.setSub((char *)"语文");//这里CStu(const CStu &ob);是拷贝构造函数所以不用再次调用
	/*cout << "Bob.num = " << Bob.getNum() << endl;
	cout << "Bob.name = " << Bob.getName() << endl;*/


	CStu Mary = Bob;
	cout << "Mary.m_name =" << Mary.getSub() << endl;
	//cout << "--------------------" << endl;

	//cout << "Lucy.num = " << Lucy.getNum() << endl;
	//cout << "Lucy.name = " << Lucy.getName() << endl;
	//cout << "Lucy.Sub = " << Lucy.getSub() << endl;

	cout << "Bob.num = " << Bob.getNum() << endl;
	//cout << "Bob.name = " << Bob.getName() << endl;
	//cout << "Bob.Sub = " << Bob.getSub() << endl;
	//}
	getchar();
	return 0;
}

当主函数中加上Bob.setSub((char *)"语文");这句话时,就会出现下面的问题

为什么?

注:

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

  • 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 //说明CWage是从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指针是一个隐含指针,指向对象本身,代表了对象的地址

    (2)缺省构造函数

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

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

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

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

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

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时执行。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值