C++构造函数(详细)

一、普通类的构造函数

class A
{
  int a,b;
public:
  A(){cout<<"默认构造函数"<<endl;}//自己定义的默认构造函数,什么也不坐
  A(int x):a(x){cout<<"转换构造函数1"<<endl;}//转换构造函数1
  A(int x,int y):a(x),b(y){cout<<"转换构造函数2"<<endl;}//转换构造函数2
  A(A& _A){
    a=_A.a;
    b=_A.b;
    cout<<"复制构造函数"<<endl;
  }
};

上面的类A有三个显示定义的构造函数,和一个显示定义的复制构造函数。它们的作用是构造对象,并进行一定的初始化。只要有构造函数,默认构造函数就不会生成;只要定义了复制构造函数,默认复制构造函数就不会生成。所以如果不想舍弃默认构造函数,就定义一个什么都不做的构造函数吧。因为我们希望知道哪个构造函数被执行了,所以都显示定义出来了。

使用无参构造函数初始化对象时,不准用空括号
A a;//调用无参数构造函数
A a();//虽然没有报错,其实没有创建对象

1、转换构造函数可生成临时对象
一般使用转换构造函数1、转换构造函数2时,我们并不会意识到“转换“的意思,如下面定义的两个对象所示,正常执行了两个构造函数

A a1(1);
A a2(1,2);

运行结果

但如果这样使用转换构造函数,就能体现转换的意义(叫单参数构造函数的隐形类型转换更适合)

A a1=1;//相当于A a1=A(1);
A a2={1,2};

这种情况编译器将数字类型转换成了临时对象,然后复制给a1、a2。需要注意的是,虽然有临时对象的生成,但并没有多执行构造函数,运行结果与上面一样。
理解了转换构造函数的意义,我们在定义对象数组时就可以很方便,但是这种方便可能会带来混乱,explicit关键字可以禁止这种隐形类型转换;(详细见文尾巴)

A array1[2];//默认构造函数执行两次
A array2[2]={1,2}//转换构造函数1执行两次
A array3[2]={{1,2},{3,4}}//转换构造函数2执行两次
A array4[2]={{1,2}}//转换构造函数2执行一次,默认构造函数执行一次

注意,在定义对象的指针数组时,不new就没有构造函数的调用,也就没有对象的生成。(new返回的就是地址,定义指针其实就是定义了整型,只不过这个整型可以存放地址)

A* ptr[3]={new A(1),new A(1,2)};//调用转换构造函数1一次,转换构造函数2一次,共生成两个对象

2、复制构造函数稍不小心就会被执行
复制构造函数简介:
1、用来复制已有的对象,产生一个新对象。
2、只有一个人参数,即同类对象的引用(为什么不能是对象呢,在下面会解释)
3、参数可以是 A&或const A&。推荐使用const A& 作为参数,因为这样既可以保证不改变被复制对象的值,也可以将常量对象作为参数。
4、不定义复制构造函数,编译器会自己生成一个默认的复制构造函数,这易于形成指针悬挂问题。
复制构造函数执行的三种情况:
1、利用复制构造函数初始化对象

A a2(a1);
A a2=a1;//这两种是一样的,都是初始化语句,不是赋值语句

2、某函数的参数是类的对象,那这个函数在执行时就会先调用类的复制构造函数。

void print(A _A)
{
  cout<<_A.a;
}
A a=1;
print(a);//先执行A _A=a;调用一次类的复制构造函数

可见2与1中,复制构造函数执行情景是一样的,都是产生了新对象。可以回答类的复制构造函数为啥只能传引用了,因为传对象的话必须要调用复制构造函数,这时复制构造函数还没完成呢,就会陷入循环调用复制构造函数。
3、如果某函数(包括成员函数)的返回值是类的对象,那么函数在返回时会调用类的复制构造函数。

A A::fun()
{
  return *this;
}
A a;
a.fun();//会执行复制构造函数,A a.fun() =a; "a.fun()"可以看成一个对象名。

可见3与1中,复制构造函数执行情景是一样的,都是产生了新对象。这在重载运算符时很有用,要实现连续赋值,a=b=c时,返回引用就可以使(a=b)=c也不违背连续赋值。

二、封闭类的构造函数。
含有成员对象的类叫做封闭类。
1、封闭类的构造函数。

class A
{
public:
 A(int x) :a(x) { cout << "A构造" << endl; }
 int a;
};
class B
{
 int b;
 A _A=1;//不能写成A _A(1);会默认为函数。
public:
 B(int x):b(x){ cout << "B构造" << endl; }
}

类B为一封闭类,含有成员对象_A。C++11之后允许在成员变量定义时初始化,这和在构造函数的初始化列表中进行初始化是一样的。
在类B实例化时,先执行类A的构造函数,再执行类B的构造函数。如果想指定使用A的哪个构造函数,就要在B构造函数的初始化列表中加以说明。不说明就使用A的默认构造函数(没有就报错)
2、封闭类的复制构造函数。
如果封闭类没有显性定义复制构造函数,那么在使用默认复制构造函数去初始化封闭类对象时,编译器会主动调用成员对象的复制构造函数来生成成员对象,然后使用默认构造函数来生成封闭类的对象。

class A
{
public:
 A(){cout<<"A默构"}
 A(int x) :a(x) { cout << "A构造" << endl; }
 A(A& oA) { a = oA.a; cout << "A复制构造" << endl;}
 int a;
};
class B
{public:
 int b;
 A _A;
public:
 B(){}
 B(int x):b(x),_A(2*x){ cout << "B构造" << endl; }
};
int main()
{
 B b1(3);
 B b2(b1);
 cout << b2.b << "," << b2._A.a;
 system("pause");
 return 0;
}

在这里插入图片描述
可见完成了使用b1初始化b2。

但如果在封闭类中定义了复制构造函数

B(B&oB) { b = oB.b; }

再执行上述程序时
在这里插入图片描述
编译器并没有自动调用A的复制构造函数(而是默认在初始化列表中执行了A的默认构造函数),来帮助b2._A完成初始化。需要自己在复制构造函数的初始化列表中显示的调用A的复制构造函数

B(B&oB):_A(oB._A) { b = oB.b;}

输出是
在这里插入图片描述可见完成了使用b1初始化b2。编译器的意思可能就是如果你定义了复制构造函数,那你就自己完成成员对象的初始化,我也不知道你到底要不要使用复制构造函数,你可以使用默认构造函数,也可以使用转换构造函数,也可以使用复制构造函数来完成成员对象实例化。如果你不定义封闭类的构造函数,那我就使用成员对象的复制构造函数。
三、继承和派生时的构造函数
B继承了A,其实就是B的对象内存中含有一个A的对象。很像是封闭类,但使用场景又不一样。
1.构造函数问题
先执行A的构造函数再执行B的构造函数。如果想指定使用A的哪个构造函数,就要在B构造函数的初始化列表中加以说明。不说明就使用A的默认构造函数(没有就报错)

class A
{
public:
 A(){ cout << "A默构" << endl; }
 A(int x) :a(x) { cout << "A构造" << endl; }
 int a;
};
class B:public A
{public:
 int b;
public:
 B(){ cout << "B默构" << endl; }
 B(int x):b(x),A(3){ cout << "B构造" << endl; }
};
int main()
{
 B b1;
 B b2(3);
 cout << b1.b << "," << b1.a<<endl;
 cout << b2.b << "," << b2.a;
 system("pause");
 return 0;
}

在这里插入图片描述
2、复制构造函数问题
派生类不显性定义复制构造函数,编译器就会自动调用基类复制构造函数,来初始化基类那部分。如果编译器显性定义了复制构造函数,就要在该函数中指明如何初始化基类那部分,可以使用构造函数,也可以使用复制构造函数,不说就是使用默认构造函数。

总结:
1、封闭类和派生类的构造函数问题是一样的,因为它们内部都有一个子对象。当一个类既是派生类又含成员对象时,先执行基类构造函数。因为在内存里基类那部分排在前面(也在虚函数表指针后面)。写复制构造函数时既要指明成员对象初始化方式,也要指明基类那部分对象的初始化方式。
2、只有知道产生了哪些对象,才知道调用了哪些构造函数。

四、关键字explicit
1、explicit关键字只需用于类内的单参数构造函数前面。由于无参数的构造函数和多参数的构造函数总是显示调用,这种情况在构造函数前加explicit无意义。
用以禁止类型转化,如 A a=1;是利用单参数转换构造函数将1隐形类型转换为了A类型对象,如果我们在单参数构造函数前加上explicit,就可以禁止这种类型转化。这样A a=1;就是错误的了,但我们可以进行显示类型转换

A A=(A)1;

隐形类型转换在我看来是好东西,在优秀程序员看来是祸害(笑哭.jpg)如:

google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换,缺点无。所以google约定所有单参数的构造函数都必须是显示的,只有极少数情况下拷贝构造函数可以不声明称explicit。例如作为其他类的透明包装器的类。

2、 也有一个例外, 就是当除了第一个参数以外的其他参数都有默认值的时候, explicit关键字依然有效, 此时, 当调用构造函数时只传入一个参数, 等效于只有一个参数的类构造函数。
3、复制构造函数,参数类型为const默认允许临时对象的产生,如果不是常引用,则不允许进行类型转换。

3、复制构造函数前加explicit,可以禁止在初始化时利用“=”
来调用复制构造函数。因为等号就应该是重载的那个等号,这种混乱的东西应该禁止。

  • 26
    点赞
  • 132
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值