之前在本科的C++学习中,只记得对类进行定义和成员的调用,由于没有具体实践,大作业也很水,对构造函数欠缺理解,接下来就来学习构造函数。
构造函数的作用
首先来看构造函数的作用。类的结构类似于结构体,对于结构体是可以直接进行初始化的,比如:
struct Date
{
int year;
int month;
int day;
};
Date test = {2017,7,12};
而对于类,一般是不可以直接初始化的,其原因在于,类中可能有私有成员。也就是说,当所有成员变量都是公有的,那OK,直接初始化是可以的;而如果存在私有成员,则无法直接初始化。因此出现了构造函数。
简单的来说,构造函数就是为了解决类的初始化问题,在使用类声明对象时,会自动调用构造函数进行初始化。
构造函数的定义
构造函数是类中的一个成员函数,与类同名。以下面代码为例,看下构造函数的定义。
class TComMv
{
private:
Short m_iHor; ///< horizontal component of motion vector
Short m_iVer; ///< vertical component of motion vector
public:
TComMv() //无参构造函数
{
m_iHor = 0;
m_iVer = 0;
}
TComMv( Short iHor, Short iVer ) //有参构造函数
{
m_iHor = iHor;
m_iVer = iVer;
}
}
需要注意的是,构造函数的参数名不能与类成员变量同名,也就是示例中的TComMv( Short iHor, Short iVer )的参数iHor、iVer不能使用m_iHor、m_iVer,否则会造成错误。
构造函数可以通过以上的方式在类中定义,也可以作为一个函数,在类中声明,在类外进行定义。例如:
TComMV::TComMv( Short iHor, Short iVer )
{
m_iHor = iHor;
m_iVer = iVer;
}
构造函数是一种特殊的成员函数,用户不需要调用,也不可以调用它,他会在建立类的时候自动执行。
默认构造函数
在创建一个类时,如果用户没有定义构造函数,那么系统会自动生成一个无参构造函数,函数体为空,作为默认构造函数,在用户创建类时自动调用。
TComMv()
{
}
默认构造函数只有在没有定义构造函数的情况下,系统才会自动定义。因此为了防止出错,在定义了有参构造函数时,最好定义无参构造函数。
构造函数初始化列表
推荐阅读:
http://blog.csdn.net/ianchoi/article/details/6917899
在很多程序代码中,并不使用上述方法定义构造函数,而是采用初始化列表的形式来定义,还是刚才的那个例子,换用初始化列表定义如下:
class TComMv
{
private:
Short m_iHor; ///< horizontal component of motion vector
Short m_iVer; ///< vertical component of motion vector
public:
TComMv() :
m_iHor(0),
m_iVer(0)
{
}
TComMv( Short iHor, Short iVer ) :
m_iHor(iHor),
m_iVer(iVer)
{
}
初始化列表是将初始化的成员列表放在了函数名之后,用冒号隔开,采用变量的括号()赋值方法来对成员进行赋值。
下面直接借用http://blog.csdn.net/ianchoi/article/details/6917899的内容。
其带来的好处有两点:
1.设想你有一个类成员,它本身是一个类或者结构,而且只有一个带一个参数的构造函数。
class CMember {
public:
CMember(int x) { ... }
};
因为Cmember有一个显式声明的构造函数,编译器不产生一个缺省构造函数(不带参数),所以没有一个整数就无法创建Cmember的一个实例。
CMember* pm = new CMember; // Error!!
CMember* pm = new CMember(2); // OK
如果Cmember是另一个类的成员,你怎样初始化它呢?你必须使用成员初始化列表。
class CMyClass {
CMember m_member;
public:
CMyClass();
};
//必须使用成员初始化列表
CMyClass::CMyClass() : m_member(2)
{
...
}
没有其它办法将参数传递给m_member,如果成员是一个常量对象或者引用也是一样。根据C++的规则,常量对象和引用不能被赋值,它们只能被初始化。
2.出于效率考虑,当成员类具有一个缺省的构造函数和一个赋值操作符时。MFC的Cstring提供了一个完美的例子。假定你有一个类 CmyClass具有一个Cstring类型的成员m_str,你想把它初始化为 “yada yada. “。你有两种选择:
CMyClass::CMyClass() {
// 使用赋值操作符
// CString::operator=(LPCTSTR);
m_str = _T( "yada yada ");
}
//使用类成员列表
// and constructor CString::CString(LPCTSTR)
CMyClass::CMyClass() : m_str(_T( "yada yada "))
{
}
在 它们之间有什么不同吗?是的。编译器总是确保所有成员对象在构造函数体执行之前初始化,因此在第一个例子中编译的代码将调用CString:: Cstring来初始化m_str,这在控制到达赋值语句前完成。在第二个例子中编译器产生一个对CString:: CString(LPCTSTR)的调用并将 “yada yada “传递给这个函数。结果是在第一个例子中调用了两个Cstring函数(构造函数和赋值操作符),而在第二个例子中只调用了一个函数。在 Cstring的例子里这是无所谓的,因为缺省构造函数是内联的,Cstring只是在需要时为字符串分配内存(即,当你实际赋值时)。但是,一般而言, 重复的函数调用是浪费资源的,尤其是当构造函数和赋值操作符分配内存的时候。在一些大的类里面,你可能拥有一个构造函数和一个赋值操作符都要调用同一个负责分配大量内存空间的Init函数。在这种情况下,你必须使用初始化列表,以避免不要的分配两次内存。在内部类型如ints或者longs或者其它没有构造函数的类型下,在初始化列表和在构造函数体内赋值这两种方法没有性能上的差别。不管用那一种方法,都只会有一次赋值发生。
|
|