构造函数:
(1) 构造函数的定义和作用:
构造函数用于对一个对象进行初始化操作。在对象被创建的时候,会自动调用构造函数(大多数情况下会被调用,请注意是大多数)。构造函数与类名相同,不能有返回值,甚至不能有return语句。不能对构造函数取地址。对于一般情况下,构造函数必须定义为public,尽管定义为private不会编译出错(如果不使用类),但是没法定义类的对象。没有定义构造函数的类,编译器会默认生成一个构造函数。不能在程序中自己手动调用构造函数。
示例代码:
/*constructor*/
#include <iostream>
using namespace std;
class TTT
{
public:
TTT();
};
TTT::TTT()
{
cout<<"constructor called"<<endl;
}
TTT global; //全局变量
int main()
{
cout<<"main begins"<<endl;
TTT a; //局部变量
TTT* b=new TTT(); //堆上分配内存的对象
// a.TTT(); //error!
return 0;
}
(2) 构造对象的书写方式:
类的构造函数的书写和普通的内部类型(如int)的初始化写法一样,如int a或int a=int(3)或int a(3);对于类,同样有这三种书写方式。
方式一:TTT a; 方式二:TTT a=TTT(); 方式三:TTT a();
方式三是方式二的简写形式,方式一是方式三的特例而已,如果类提供的构造函数都带有参数,那么没有方式一的写法(关于多个构造函数,即重载,参考下面)。所以实质上只有两种写法。对于有参数的构造函数,提供对应的参数列表即可。
但是请注意:不是上面的方式都会自动调用构造函数。方式三的写法并没有调用构造函数(如果自己实现构造函数,可以运行程序分析,对于默认的构造函数,目前我也不清楚有没有调用)。
!!注意:经过测试,发现,方式二的定义编译通过,但是具体含义不对,这种写法并没有创建一个TTT类的对象,因为如果给TTT定义方法,发现a.method()是不可行的,编译提示a不是一个类类型或union类型。但是对于有参数的构造函数,是可以这样定义的,如TTT a=TTT(1,3);所以方式二不适用于无参数构造函数(包括默认生成构造函数或者自定义无参数构造函数)。对于带默认参数的构造函数,也不能采用方式二定义,除非仍然加上了参数。
#include <iostream>
using namespace std;
class TTT
{
public:
TTT();
};
TTT::TTT()
{
cout<<"constructor called"<<endl;
}
int main()
{
cout<<"main begins"<<endl;
TTT a; //方式一
TTT b(); //方式三 //没有调用构造函数
TTT c=TTT(); //方式二
TTT* d=new TTT; //方式一
TTT* f(); //方式三 //没有调用构造函数
TTT* e=new TTT(); //方式二
return 0;
}
(3) 关于编译器生成构造函数的问题:
对于没有写构造函数的类,编译器会自己生成构造函数(假设称为编译器构造函数)。一旦用户(程序员)定义了一个或多个构造函数(只要是构造函数),那么编译器就不会自动生成构造函数了。请注意:编译器构造函数!=默认构造函数,编译器构造函数!=无参构造函数。解释如下:
关于默认构造函数的解释下面将单独分析,这里只说明无参构造函数。编译器构造函数是无参数的,但是无参数的构造函数不一定是编译器构造函数,因为用户(程序员)也可以自己定义无参构造函数,用户定义无参构造函数,编译器也不会再生成构造函数。
/*示例代码*/
#include <iostream>
using namespace std;
class TTT
{
public:
TTT(int a);
};
TTT::TTT(int a)
{
cout<<a<<endl;
}
int main()
{
/*
由于定义了一个构造函数,编译器不再生产构造函数,所以除非自己定义一个无参
构造函数,否则下面的写法编译出错。其中只有两种方式没有出错,是因为不会
调用构造函数。
*/
cout<<"main begins"<<endl;
// TTT a; //error
TTT b(); //方式三 //没有调用构造函数
// TTT c=TTT(); //error
// TTT* d=new TTT; //error
TTT* f(); //方式三 //没有调用构造函数
// TTT* e=new TTT(); //error
return 0;
}
(4) 构造函数的重载(多个构造函数):
构造函数符合和一般的函数一样的重载规则。只要构造函数在参数类型上存在足够大的差异,编译器就能为每个使用选出一个正确的。
(5) 构造函数的默认参数
构造函数可以和一般函数一样定义默认参数,使用和一般的函数一样,比如可以全部采用默认参数或者部分默认参数,对于部分默认参数,默认的参数必须在参数列表后面等等。
(6) 关于默认构造函数:
默认构造函数(default constructor)就是在没有显式提供初始化式时调用的构造函数。它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义。如果定义某个类的变量时没有提供初始化式就会使用默认构造函数。
如果用户定义的类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数,称为合成的构造函数(synthesized default constructor)。
如果类包含内置或复合类型的成员,则该类就不应该依赖于合成的默认构造函数,它应该定义自己的构造函数来初始化这些成员。
事实上,关于默认构造函数还有一些更复杂的问题。从更深层次来说,在程序员没有定义默认构造函数的时候,编译器会自动生成默认构造函数的说法并不正确。只有在“需要时”编译器才会为我们生成默认构造函数。详细探讨可参考http://dev.firnow.com/course/3_program/c++/cppsl/2008222/100475.html
(7) 构造函数的初始化列表:
转载自:http://www.cppblog.com/baggio1984/archive/2007/09/30/33270.html
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
Example::Example() : ival(0), dval(0.0) {}//ival 和dval是类的两个数据成员
上面的例子和下面不用初始化列表的构造函数看似没什么区别:Example::Example()
{
ival = 0;
dval = 0.0;
}
的确,这两个构造函数的结果是一样的。但区别在于:上面的构造函数(使用初始化列表的构造函数)显示的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显示的初始化。
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。但有的时候必须用带有初始化列表的构造函数:
(1)成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
(2)const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
(8) 什么是默认构造函数
什么是无参构造函数
什么是带参构造函数
什么是拷贝构造函数
什么是赋值构造函数
(9) 派生类中构造函数对基类和子对象的调用问题:
http://hi.baidu.com/christianolove/blog/item/42ce98f7379a9328730eeca4.html
说明:派生类的对象创建需要先构造子类的对象,所以派生类的构造函数必须调用基类的构造函数,然后初始化派生类自己的成员,如果派生类中有子对象(内嵌对象,即另一个类的对象作为成员变量,那么有必要构造子对象,初始化子对象即可)。
(10) 拷贝构造函数(类对象的复制之一)
对于类的对象,可以复制。特别是可以用一个类的对象的复制对该类的其他对象进行初始化。这样就相当于完成构造函数的作用。完成此功能是通过调用类的拷贝构造函数完成的。默认情况下,编译器会给类生成一个拷贝构造函数。默认的拷贝构造函数完成的功能是对象的成员逐个的复制。对于数据成员都是值类型的时候,默认的拷贝构造函数足以完成功能,但是对于具有指针成员的类的时候,这样会与预料不一样的。这时候一般是自己实现拷贝构造函数,这也是c++经常提到的深浅拷贝的问题。拷贝构造函数的格式为:ClassName(const ClassName&);
拷贝构造函数的使用格式为:
ClassName a=ClassName(1,2); //调用构造函数
ClassName b(a); //调用拷贝构造函数
ClassName c=a; //仍然调用拷贝构造函数 (切记:这里不是调用赋值构造函数)
测试代码(以下类只包含值类型成员数据,默认的拷贝构造函数也可以完成其功能):
#include <iostream>
using namespace std;
class TTT
{
public:
TTT(int a);
TTT(const TTT& copy);
private:
int data;
};
TTT::TTT(int a)
{
data=a;
cout<<"constructor called"<<endl;
}
TTT::TTT(const TTT& copy)
{
data=copy.data;
cout<<"copy constructor called"<<endl;
}
int main()
{
TTT a(1);
TTT b(a);
TTT c=a; //注意:不是调用赋值构造函数,仍然是调用拷贝构造函数
return 0;
}
(11) 赋值构造函数(类对象的复制之二)
http://www.360doc.com/content/09/0502/15/137454_3345528.shtml(拷贝构造函数与赋值构造函数的异同)
赋值构造函数,个人觉得严格来说,并不能称为一种构造函数,构造函数是用来完成初始化的,而赋值构造函数是用于赋值的,就是初始化与赋值之间的区别。所以赋值构造函数更多的说法是赋值(复制)操作。就是对类的“=”运算符的处理。编译器同样会默认生成赋值构造函数(或者说默认定义了等于运算符),跟拷贝构造函数一样的规则,对于值类型和指针类型结果要注意。
对于有些情况,我们希望一个类能提供对其的引用和拷贝两种方式,一般是用拷贝构造函数实现拷贝功能,赋值构造函数提供引用的功能,当然,大部分情况下,赋值构造函数也是完整的数据拷贝,和拷贝构造函数的区别主要是赋值和初始化的区别。
赋值构造函数是等于运算符重载,格式为:TTT& operator=(const TTT&);//复制赋值
对于赋值构造函数实现的标准步骤要注意:(1)防止自赋值(2)释放原来对象的内存分配(删除老元素)(3)初始化(4)赋值新元素
/*示例代码*/
#include <iostream>
using namespace std;
class TTT
{
public:
TTT(char a,int size);
TTT(const TTT& copy);
TTT& operator=(const TTT& equal);
public:
void printfData()
{
for(int i=0;i<sz;i++)
cout<<data[i]<<endl;
}
void modifyData(char b)
{
for(int i=0;i<sz;i++)
data[i]=b;
}
private:
char* data;
int sz;
};
TTT::TTT(char a,int size)
{
data=new char[size];
sz=size;
for(int i=0;i<size;i++)
data[i]=a;
cout<<"constructor called"<<endl;
}
TTT::TTT(const TTT& copy)
{
sz=copy.sz; //不要忘记这一点
data=new char[copy.sz];
for(int i=0;i<copy.sz;i++)
data[i]=copy.data[i];
cout<<"copy constructor called"<<endl;
}
TTT& TTT::operator=(const TTT& equal)
{
if(this!=&equal) //防止自赋值
{
delete[] data; //释放
sz=equal.sz;
data=new char[sz]; //初始化
for(int i=0;i<sz;i++) //拷贝
data[i]=equal.data[i];
}
return *this;
}
int main()
{
TTT a('a',10);
TTT b(a);
b.printfData();
TTT c('d',10);
c=a;
c.modifyData('b');
c.printfData(); //如果采用默认的拷贝构造函数或赋值操作
a.printfData(); //都会modify掉a的数据内容,不是我们希望的操作。
return 0;
}