每个类都具有构造函数和析构函数。其中,构造函数在定义对象时被调用,析构函数在对象释放时被调用。如果用户没有提供构造函数和析构函数,系统将提供默认的构造函数和析构函数。一个有着层次结构的类群组,当派生类的对象诞生的时候,构造函数的执行是由最基类至最尾端派生类;当对象要毁灭前,析构函数的执行则是反其道而行。
一、构造函数
构造函数是一个与类同名的方法,是一种特殊的类成员函数,专门用来初始化对象。可以没有参数,有一个参数或多个参数,但是构造函数没有返回值。如果构造函数没有参数,该函数被称为类的默认构造函数。为一个类确定必要的构造函数是程序设计不可缺少的一部分。重载函数与它类似,但有返回值。是一个可能被重载的用户定义函数,由类设计者提供,是对象诞生后第一个执行(并且是自动执行)的函数,构造函数被自动应用在每个类对象上。
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时执行。