注:以下大部分内容来源于 coursera 课程《C++程序设计》
构造函数
成员函数的一种,有以下特点:
- 名字与类名相同,可以有参数,但是不能有返回值(void也不行)。
- 作用:给对象进行初始化。这样就不需要写初始化函数和调用初始化,它会自动被调用。
- 如果没有写构造函数,编译器会生成默认的构造函数。
- 对象生成时构造函数自动被调用。
class Complex{
private:double real,imag;
public :
Complex(double r, double i=0);
};
Complex::Complex(double r, double i)
{
real = r;
imag = i;
}
Complex r2;//报错,构造函数需要初始化,没有给参数,所以没法初始化,出错
Complex r2(0);//正确,第二个参数可以缺省
- 可以有多个构造函数,参数个数和参数类型都可以不同。这些构造函数是重载的关系。调用时根据参数个数和类型自动匹配。
class Complex{
private:double real,imag;
public :
Complex(double r, double i=0);
Complex(double r);
Complex(Complex c1,Complex c2);
};
Complex::Complex(double r, double i)
{
real = r;
imag = i;
}
Complex::Complex(double r)
{
real = r;
imag = 0;
}
Complex::Complex(Complex c1,Complex c2)
{
real = c1.real+c2.real;
imag = c1.imag+c2.imag;
}
Complex c1(3);
Complex c2(0,5);
Complex c3(c1,c2);
- 构造函数在数组中的使用
例子1:
class CSample{
int x;
public:
CSample(){cout<<"Constructor 1 called"<<endl;}
CSample(int n){ x = n;
cout<<"Constructor 2 called"<<endl;}
};
int main(){
CSample array1[2]; //定义了一个 对象数组array 1,这个数组里面有两个元素。
// 每个元素都是CSample对象
//这两个对象初始化的时候所用的参数没有做任何交代,
//编译器就认为 这个对象应该是用无参的构造函数初始化的。
cout<<"step 1"<<endl;
CSample array2[2] = {4,5};
cout<<"step 2"<<endl;
CSample array3[2] = {3};
cout<<"step 3"<<endl;
CSample *array4 = new CSample[2];
//动态分配了一个数组,这个数组里边有两个 对象,
//这两个对象如何初始化呢,我们对参数没有做任何交代
//那这么两个对象都是用无参的构造函数 初始化的
delete []array4;
return 0;
}
输出:
Constructor 1 called
Constructor 1 called
step 1
Constructor 2 called
Constructor 2 called
step 2
Constructor 2 called
Constructor 1 called
step3
Constructor 1 called
Constructor 1 called
例子2:
class Test{
public:
Test(int n){}; //(1)
Test(int n, int m){};//(2)
Test(){}//(3)
};
Test array1[3] = {1,Test(1,2)} //(1)(2)(3)
Test array2[3] = {Test(2,3),Test(1,2),1} //(2)(2)(1)
Test *pArray2[3] = {new Test(4),new Test(1,2)} //(1)(2)(3)
//
难点:
Test *pArray2[3] = {new Test(4),new Test(1,2)} //(1)(2)(3)
定义了这样的一个pArray数组,会不会导致对象生成,会不会引发test的构造函数被调用?
不会,因为这是一个指针数组,它里面的每一个元素都是一个指针,不是一个对象,不会引发任何对象的生成,这个指针可以不初始化的。现在对这个指针数组进行了初始化,而且对它的前两个元素进行 了初始化:new出来两个对象,这个new这个表达式的返回值是指针test *。
所以使用new出来的对象的地址去初始化这个数组里面的元素。
这条语句一共生成了几个对象?
生成了2个对象,而不是3个,为什么呀?因为pArray2的最后这个元素, 我们没有初始化,没有初始化它的话呢,它只不过是一个指针,而且也不知道指向哪,pArray2这个元素生成并不会导致任何对象的生成,所以这条语句, 只是生成了两个对象,这两个对象呢,分别用1,2进行初始化,这个 pArray2呢这个元素,它就是一个未经初始化的指针。
复制构造函数
- 格式:
X::X(X&)
X::X(const X&)
只有一个参数,即对同类对象的引用。
- 如果没有写,编译器会默认生成。这时候主要完成复制工作。
class Complex{
private:
double real, double imag;
};
Complex c1;//调用缺省的无参构造函数
Complex c1(c2);//调用缺省的复制构造函数,将c2复制给c1,c1与c2完全相等。
这里编译器会生成至少两个构造函数,一个是无参构造函数,一个是复制构造函数。
- 也可以定义自己的复制构造函数,这时候的功能
class Complex{
private:
double real, double imag;
Complex(){}
Complex(Complex &c){
real = c.real;
imag = c.imag;
cout<<"Copy Constructor called";
}
};
Complex c1;//
Complex c1(c2);//
c2这个对象就是用复制构造函数初始化的。
- 复制构造函数的三种作用:
1) 用一个对象去初始化同类的另一个对象
Complex c2(c1)
Complex c2 = c1//初始化语句
c2 = c1//赋值
2)如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用。
class A{
public:
A(){};
A(A&a){// 复制构造函数
cout<<"Copy Constructor called"<<endl;
}
};
void Func(A a1){}
int main(){
A a2;
Func(a2);
return 0;
}
void Func(A a1){}函数,形参是类A的对象,如果进到这个函数里面,形参就会被生成。这个形参是用什么构造函数初始化的呢?用复制构造函数初始化的。复制构造函数的时候是需要参数的,这个参数就是a2。
也就是说,形参a1是用复制构造函数初始化的,初始化a1的时候,那个复制构造函数的参数就是实参a2。
以前说过,一个函数的形参和实参是相等的。那么这里,形参a1的值还会等于实参a2吗?不一定。因为a1是用自己编写的复制构造函数去初始化的,这里这个函数并没有做复制的工作,而而仅仅是输出Copy Constructor called。
3) 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数将被调用。
class A{
public:
int v;
A(int n){v = n};
A(const A&a){ // 复制构造函数
v = a.v;
cout<<"Copy Constructor called"<<endl;
}
};
A Func(){
A b(4); //定义局部对象b
return b
}
int main(){
cout<<Func().v<<endl;} //
函数Func()的返回值类A,是一个class A的对象,在生成这个对象的时候,调用了复制构造函数初始化。
这儿复制构造函数进行了赋值工作,所以在 return b执行完了以后,作为返回值存在的这个对象被初始化,它的值和b一模一样。
这个函数的返回值一定就跟这个b相等吗? 那不一定了。就取决于你这个复制构造函数是怎么写的了。 因为这个函数的返回值是用复制构造函数初始化的,所以你那个复制构造函数里面如果没有执行复制的工作, 那么这个返回值的对象它的值当然就未必跟这个b相等了。
- 为什么要写自己的复制构造函数?
网友评论