1.编译器在什么条件下会自动生成默认构造函数?
在我们没有对象指定构造函数的时候,编译器会为我们生成默认构造函数,拷贝构造函数,默认析构函数。
拷贝构造函数和赋值构造函数的区别,请看下例:
class Obj{}; // 声明一个对象Obj
Obj a; //调用默认构造函数来构造对象
Obj b(a);//调用默认拷贝构造函数来构造对象
Obj c = b;//调用的也是拷贝构造函数,最好将其写做 Obj c(b)。
C++11允许我们使用=default来要求编译器生成一个默认构造函数:
2.自动生成的构造函数主要做了什么?和产生一个默认构造函数的情况
>1.内部的成员变量拥有默认构造函数,如果有多个成员变量,那么会按照成员变量声明的顺序来调用成员变量的默认构造函数。
>2.基类拥有默认构造函数。在子类构造的时候,需要先构造父类。
>3.类中声明有虚函数,因为编译器需要为类中的虚函数表指针指定正确的地址。
>4.带有虚基类(virtual base class)。因为编译器需要确定下来虚基类在对象中的偏移,以方便调用虚基类中数据。
编译器合成的默认构造函数并没有初始化成员变量,如果要为成员变量在构造是指定特定的值,需要在自定义的构造函数中来指定
3.拷贝构造函数的调用时机
拷贝构造函数(copy constructor)是构造函数,是拷贝已经存在的对象来创建一个新的对象。此方法的声明形式:object(const object&)。
例如:
class Object
{
Object(const Object &);
};
注意:参数的传递是引用传递的。因为如果在此处使用值传递,会造成递归引用。
其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动被调用来生成函数中的对象(符合拷贝构造函数调用的情况)。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象,这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出(Stack Overflow)。
在以下三种情况下会调用对象的拷贝构造函数
>1.以一个对象的值作为另一个对象的初值。例如:
class Obj{}; Obj a; Obj b = a; Obj c(a);
>2.一个对象以值传递的方式传入函数体 。例如:
class Obj{}; void Foo(Obj obj);
>3. 一个对象以值传递的方式从函数返回 。例如:
class Obj{}; Obj foo() { Obj obj; return obj;//调用拷贝构造函数。 }
4.拷贝构造函数的行为
a. 默认拷贝构造函数:
默认拷贝构造函数主要作用是按位拷贝
如果对象没有提供显式的拷贝构造函数,编译器在发现需要使用拷贝构造函数函数的时候,会生成一个来使用,而生成的拷贝构造函数会有trival和nontrival之分,区别在于对象是否应该被按位拷贝(bitwise copy semantics)。
如果类中的成员变量都是内建类型的,初始化直接按位拷贝,编译器不用生成默认构造函数。但是,如果类的成员变量中有成员变量声明了显式的拷贝构造函数,那么编译器就需要合成构造函数来完成初始化操作。
需要注意的是,在以下的四种情况是不需要进行按位拷贝的:
-
- class内部的成员变量声明有显式的拷贝构造函数(不论是定义者声明(例如string)还是编译器合成的(例如包含string的Word定义))。
- class的基类有显式的拷贝构造函数(无论是被声明的还是被合成的)。
- class中有虚函数,因为编译器需要为对象设置虚函数表,所以无法按位拷贝。
- class继承虚基类。、
b.用户自定义拷贝构造函数
顾名思义,是用户自己定义的拷贝构造函数,编译器发现由用户定义的拷贝构造函数的时候,是不会生成拷贝构造函数的。即使用户声明的构造函数有错误。但是在使用自定义构造函数的时候需要注意几点:
1.注意深拷贝和浅拷贝,在前面的例子中不含string的Word的定义中,有一个char *的指针,如果不考虑深拷贝,直接按位赋值的话,会造成隐含的bug。
2.在构造函数中不要调用虚函数,因为在此时对象没有创建完成,不能保证调用到正确的函数,同时,也会引起其他的错误(例如,类内部的成员变量没有初始化完毕就是用)。
3.在拷贝构造函数中需要考虑异常的发生。
5.浅拷贝和深拷贝
浅拷贝就比如像引用类型,而深拷贝就比如值类型。位拷贝又称浅拷贝
浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。只是将数据成员的值进行简单的拷贝
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。
在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
#include<iostream>
#include <string.h>
using namespace std;
class CA
{
public:
CA(int b,char*cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA &C)
{
a=C.a;
str=new char[a];
// if(str!=0)
// strcpy(str,C.str);//深拷貝
str=C.str;//淺拷貝
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
//delete str;//刪除时,浅拷贝会运行出错,因为两次删除同一指针;而深拷贝时则不会出错
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}
6.委托构造函数
委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说他把自己的一些(或全部)职责委托给了其他构造函数。
在委托构造函数内,成员初始值列表只有一个唯一的入口,就是类名本身。和其他成员初始值一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须跟类中另外一个函数匹配。
#include<iostream>
using namespace std;
class Date
{
public:
//非委托构造函数使用对应的实参初始化成员
Date(int year,int month,int day)
:_year(year)
, _month(month)
, _day(day)
{}
//其余构造函数全都委托给另一个构造函数
Date() :Date(1990,1,1)
{}
Date(int year) :Date(){}
private:
int _year;
int _month;
int _day;
};
void FunTest()
{
Date d;
Date::Date(2016);
}
int main()
{
FunTest();
system("pause");
return 0;
}
分析:在Date类中,除了第一个构造函数外,其他的都委托了他们的工作,第一个构造函数接受三个实参,使用这些实参初始化数据成员,然后结束工作。其他两个构造函数把自己的初始化工作全都委托给了第一个构造函数去完成。这点可以通过对以上程序的调试来验证,在调试过程中,但需要第二个或者第三个构造函数来完成初始化工作时,再按F11调试都会跳转到第一个构造函数中去。
7.聚合类 字面值常量类
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。当一个类满足如下条件时,我们说它是聚合的:
- 所有成员都是public的
- 没有定义任何构造函数
- 没有类内初始化
- 没有基类,也没用virtual函数。
数据成员都是字面值类型的聚合类是字面值常量类。如果一个类不是聚合类,但它符合下述要求,则它也是一个字面值常量类:
- 数据成员都必须是字面值类型。
- 类必须至少含有一个constexpr构造函数
- 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。
- 类必须使用析构函数的默认定义,该成员负责销毁类的对象。
constexpr构造函数
尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数。事实上,一个字面值常量类必须至少提供一个constexpr构造函数。
constexpr构造函数可以声明成=default的形式(或者删除函数的形式)。否则,constexpr构造函数就必须既符合构造函数的要求(意味着不能包含返回语句),又符合constexpr函数的要求(意味着它能拥有的唯一可执行语句就是返回语句)。综合这两点可知,constexpr构造函数体一般来说应该是空的。我们通过前置关键字constexpr就可以声明一个constexpr构造函数了:
class Debug{
public:
constexpr Debug(bool b=true):hw(b),io(b),other(b) {}
constexpr Debug(bool h,bool i,bool o):hw(h),io(i),other(o) {}
constexpr bool any() {return hw||io||other;}
void set_io(bool b) {io=b;}
void set_hw(bool b) {hw=b;}
void set_other(bool b) {hw=b;}
private:
bool hw;
bool io;
bool other;
};
constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式。
constexpr构造函数用于生成constexpr对象以及constexpr函数的参数或返回类型:
constexpr Debug io_sub(false,true,false); //调试IO
if(io_sub.any()) //等价于if(true)
cerr<<"print appropriate error messages"<<endl;
constexpr Debug prod(false); //无调试
if(prod.any()) //等价于if(false)
cerr<<"print an error message"<<endl;