1.究竟是不是每个类都有构造函数
按照C++标准规定,每个类都有显式的或者隐式的构造函数和拷贝构造函数,但是编译器会区分这个类中的构造函数和拷贝构造函数是有用或者无用的,如果判定为有用,编译器就会将这个构造函数生成代码,如果没用,就不会生成代码。(简单来说就是C++规定了每个类都有构造和拷贝构造函数,但是编译器不一定为每个类生成构造或拷贝构造函数,下面的内容都是指编译以后是否是否具有构造函数,所以也有可能没有)
如果类中已经自定义了构造函数,但是如果编译器判定为有用的构造函数代码仍然会自动加在自定义的构造函数后面。
2.编译器自动生成的构造函数有什么用
①调用member object或者base object的构造函数,也就是调用类成员对象的构造函数,以及父类的成员对象。
②创造虚函数表和指向虚函数表的指针vptr,以及创造指向虚继承的父类的指针。
3.什么样的类存在有用的构造函数
根据上面第2条的内容,可以明确知道什么样的类算是有用类,编译器会帮助编写构造函数代码。
①如果这个类中带有类成员对象,并且这个类具有构造函数。
②如果这个类继承了带有构造函数的类
③这个类中带有虚函数或者父类中带有虚函数
④如果这个类虚继承了其他类,或者其父类虚继承了其他类
(具体例子可以看书中39页开始)
注意:即便以上这些类中已经有自定义的构造函数了,编译器仍然会在自定义的构造函数下增加默认的构造函数代码
4.对构造函数常见的误解
①任何类如果没有定义构造函数,都会有一个默认的构造函数(这是错误的)
②编译器默认的构造函数会设定每一个成员变量的值
注意:其实第二点是由程序员来完成的,而不应该交给编译器去做。
5.拷贝构造函数使用时机
拷贝构造函数在这三种情况下会使用
①使用等号操作符初始化时
class X;
X xx=x;
注意:只有在初始化时用等号,是调用拷贝构造函数,其他时候等号调用的是“=”运算符的函数
class X;
X xx;
xx=x;//这样就不属于初始化,调用的就是等号运算符函数。
②作为形参传入函数
class X;
void foo(X x);
void bar()
{
X xx;
foo(xx);
return;
}
③作为函数返回值
class X;
X bar()
{
X xx;
return x;
}
6.位逐次拷贝(bitwise copy semantics)
在第一条说到,不是所有类经过编译以后,都有拷贝构造函数的,那么没有拷贝构造函数的类,就不能拷贝了嘛?
其实不是的,没有拷贝构造函数的类也可以拷贝,只是不需要使用函数来拷贝,而是使用位逐次拷贝的方法,也就是把一个对象中的所有内容直接拷贝到另一个对象之中而已。
7.什么样的类存在拷贝构造函数
存在拷贝构造函数的类就是位逐次拷贝方法不适用的类,一下四种类就有拷贝构造函数
①类中含有类对象,并且该类有拷贝函数(不管这个拷贝函数是自定义还是编译器合成的)
②类继承一个具有拷贝函数的父类
③类中声明有虚函数,或者父类中有虚函数
这种类中,拷贝函数有一个非常重要的作用,虚拟指针不是单纯的拷贝,而是会找到类对应的虚函数列表并指向它。
以示意图来说明:
这是使用位逐次拷贝法的内存结构图,注意Bear是带虚函数的类
这是使用编译器自动生成的拷贝函数的内存结构图:
④类虚继承父类,或者有父类虚继承了其他类
8.利用拷贝构造函数和利用指针的区别
class animal{
public:
virtual void add()
{
cout << "animal\n";
}
};
class bear : public animal{
public:
virtual void add()
{
cout << "bear\n";
}
};
//int point3d::a = 10;
int main()
{
bear a;
animal b = a;//子类初始化(拷贝)父类对象
animal *c = &a;//父类指针对象指向子类
a.add();
b.add();//调用父类函数,无论子类父类中函数是否是虚函数,都是一样的结构,都是调用animal::add
c->add();//调用子类函数
system("pause");
}
结果如图:
其中b运用拷贝构造函数,入口参数是子类对象的情况下,并不是全盘拷贝的,还是保留父类对象的方法,和指针不一样。
9.类对象的程序语意转化
注意:下面所有的转化都是编译器的操作,并且只适合于类对象,普通数据对象不适用
①显示初始化操作:
经过编译器处理以后:
②参数初始化,作为实际参数传入函数中
经过编译器处理以后:
③返回值初始化
经过编译器处理以后:
注意:由上面的转换过程,能引用或者使用指针的尽量引用和使用指针,普通的形参效率非常低
10.NRV的例子
第9条第三种情况经过NRV优化后的程序
但是对于NRV优化还是没有一个明确的定义,很疑惑???
11.初始化列表
必须使用初始化列表的情况
①类成员中有引用类型,或者const类型
#include <iostream>
using namespace std;
class A
{
public:
A(int &v) : i(v), p(v), j(v) {}
void print_val() { cout << "hello:" << i << " " << j << endl;}
private:
const int i;
int p;
int &j;
};
int main(int argc ,char **argv)
{
int pp = 45;
A b(pp);
b.print_val();
}
②当该类的基类构造函数有输入参数时
#include <iostream>
using namespace std;
class Base
{
public:
Base(int a) : val(a) {}//有一个int型输入参数
private:
int val;
};
class A : public Base
{
public:
A(int v) : p(v), Base(v) {}
void print_val() { cout << "hello:" << p << endl;}
private:
int p;
};
int main(int argc ,char **argv)
{
int pp = 45;
A b(pp);
b.print_val();
}
③当该类中有类对象成员,并且该类对象成员的构造函数有输入参数时
#include <iostream>
using namespace std;
class Base
{
public:
Base(int a) : val(a) {}
private:
int val;
};
class A
{
public:
A(int v) : p(v), b(v) {}
void print_val() { cout << "hello:" << p << endl;}
private:
int p;
Base b;//base类有入口参数
};
int main(int argc ,char **argv)
{
int pp = 45;
A b(pp);
b.print_val();
}
12、拷贝构造函数的参数类型必须是引用
因为如果参数类型是普通变量,就会无休止的递归下去,因为将普通参数作为入口参数时,需要执行一次拷贝构造函数,而拷贝函数中又是普通参数,又要执行拷贝函数,这样就会循环往复。所以如果拷贝构造函数是普通变量,编译器不会通过的。
而如果是指针,那么这个函数编译器就不认为是拷贝构造,而是一个普通带指针入口参数的构造函数。测试程序如下:
class A
{
public:
A(int t){ a = t; cout << "constructor" << endl; }
A(A &b){ a = b.a; cout << "copy constructor" << endl; }
A(A *b){ a = b->a; cout << "ptr constructor" << endl; }
int a;
};
int main()
{
A t(3);
A b(t);
A c(&t);
system("pause");
}
可以参考这篇博客:https://blog.csdn.net/hackbuteer1/article/details/6545882