1 c++中拷贝构造函数被调用的时机
拷贝构造函数被调用的几种情况:
(1)当用类的一个对象去初始化该类的另一个对象时,系统会自动调用拷贝构造函数;
(2)将一个对象作为实参传递给一个非引用类型的形参,系统会自动调用拷贝构造函数;
(3)从一个返回类为非引用的函数返回一个对象时,系统会自动调用拷贝构造函数;
(4)用花括号列表初始化一个数组的元素时,系统会自动调用拷贝构造函数。
下面逐个举例:
1.1 当用类的一个对象去初始化该类的另一个对象时,系统会自动调用拷贝构造函数
#include<ctime>
#include<cstdlib>
#include<iterator>
#include<algorithm>
#include<iostream>
#include<numeric>
using namespace std;class MyClass {
public:MyClass():data(0){}MyClass(const MyClass& a){data = a.data;cout << "拷贝构造函数调用\n";}MyClass& operator=(const MyClass&a){data = a.data;cout << "调用赋值函数\n";return *this;}int data;
};int main() {MyClass a;MyClass b(a); // 用类的一个对象a去初始化另一个对象bMyClass c = a; // 用类的一个对象a去初始化另一个对象c,注意这里是初始化,不是赋值return 0;
}
测试
1.2 将一个对象作为实参传递给一个非引用类型的形参,系统会自动调用拷贝构造函数
#include<ctime>
#include<cstdlib>
#include<iterator>
#include<algorithm>
#include<iostream>
#include<numeric>
using namespace std;class MyClass {
public:MyClass():data(0){}MyClass(const MyClass& a){data = a.data;cout << "拷贝构造函数调用\n";}MyClass& operator=(const MyClass&a){data = a.data;cout << "调用赋值函数\n";return *this;}int data;
};void fun1(MyClass a) {return ;
}int main() {MyClass a;fun1(a); // 形参为类对象,实参初始化形参,调用拷贝构造函数return 0;
}
测试
1.3 从一个返回类为非引用的函数返回一个对象时,系统会自动调用拷贝构造函数
第3点我看网上都这么说,但是实际调试的时候,发现不会调用拷贝构造函数,于是查了一下。这里默认情况下一般会被编译器优化,减少不必要的拷贝构造,所以,具体的返回值可能会因编译器及编译选项的不同而不同。使用g++编译器,关闭优化g++ xxx.cpp -fno-elide-constructors后执行结果如下(-fno-elide-constructors选项就是用来关闭拷贝优化的)
g++ xxx.cpp -fno-elide-constructors
#include<ctime>
#include<cstdlib>
#include<iterator>
#include<algorithm>
#include<iostream>
#include<numeric>
using namespace std;class MyClass {
public:MyClass():data(0){}MyClass(const MyClass& a){data = a.data;cout << "拷贝构造函数调用\n";}MyClass& operator=(const MyClass&a){data = a.data;cout << "调用赋值函数\n";return *this;}int data;
};MyClass fun2() {MyClass a;return a;
}int main() {MyClass a;MyClass d = fun2(); // 函数返回一个类对象时, 这里可能会被编译器优化,从而可能没有调用拷贝构造return 0;
}
不优化编译
这里拷贝构造调用两次,一次是返回非引用的对象时调用的,一次时用返回值初始对象d时调用的。
默认优化编译
什么都不调用。
编译器具体是怎么优化的,一般编译器会先看支不支持拷贝优化,如果不支持,再看有没有定义移动构造函数,如果都没有,就调用拷贝构造函数。更具体的细节可以参考移动语义及拷贝优化的内容。
1.4 用花括号列表初始化一个数组的元素时,系统会自动调用拷贝构造函数
#include<ctime>
#include<cstdlib>
#include<iterator>
#include<algorithm>
#include<iostream>
#include<numeric>
using namespace std;class MyClass {
public:MyClass():data(0){}MyClass(const MyClass& a){data = a.data;cout << "拷贝构造函数调用\n";}MyClass& operator=(const MyClass&a){data = a.data;cout << "调用赋值函数\n";return *this;}int data;
};int main() {MyClass a;MyClass b;MyClass arr[2] = {a, b};return 0;
}
编译
2 c++中有哪几种构造函数
- 默认构造函数
- 初始化构造函数(有参数)
- 拷贝构造函数
- 移动构造函数(move和右值引用)
- 委托构造函数
- 转换构造函数
#include <iostream>
using namespace std;
class Student{
public:// 默认构造函数,没有参数Student(){this->age = 20;this->num = 1000;}// 初始化构造函数,有参数和参数列表Student(int a, int n):age(a), num(n){};// 拷贝构造函数,这里与编译器生成的一致Student(const Student& s){this->age = s.age;this->num = s.num;}// 转换构造函数,形参是其他类型变量,且只有一个形参Student(int r){this->age = r;this->num = 1002;}~Student(){}
public:int age;int num;
};
int main(){Student s1;Student s2(18,1001);int a = 10;Student s3(a);Student s4(s3);printf("s1 age:%d, num:%d\n", s1.age, s1.num);printf("s2 age:%d, num:%d\n", s2.age, s2.num);printf("s3 age:%d, num:%d\n", s3.age, s3.num);printf("s2 age:%d, num:%d\n", s4.age, s4.num);return 0;
}
执行
- 默认构造函数和初始化构造函数在定义类的对象,完成对象的初始化工作
- 复制构造函数用于复制本类的对象
- 转换构造函数用于将其他类型的变量,隐式转换为本类对象
3 有哪些情况必须用到成员列表初始化?作用是什么?
1) 必须使用成员初始化的四种情况:
- 当初始化一个引用成员时;
- 当初始化一个常量成员时;
- 当调用一个基类的构造函数,而它拥有一组参数时;
- 当调用一个成员类的构造函数,而它拥有一组参数时;
2) 成员初始化列表做了什么
- 编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前;
- list中的项目顺序是由类中的成员声明顺序决定的,不是由初始化列表的顺序决定的;
4 如果有一个空类,它会默认添加哪些函数
1) Empty(); // 缺省构造函数
2) Empty( const Empty& ); // 拷贝构造函数
3) ~Empty(); // 析构函数
4) Empty& operator=( const Empty& ); // 赋值运算符
5 如何设计一个类计算子类的个数
- 为类设计一个static静态变量count作为计数器;
- 类定义结束后初始化count=0;
- 在构造函数中对count进行+1;
- 设计拷贝构造函数,在进行拷贝构造函数中进行count +1,操作;
- 在析构函数中对count进行-1;
6 如何阻止一个类被实例化?有哪些方法
- 将类定义为抽象基类或者将构造函数声明为private;
- 不允许类外部创建类对象,只能在类内部创建对象
7 如何禁止程序自动生成拷贝构造函数?
定义一个base类,在base类中将拷贝构造函数和拷贝赋值函数设置成private(或者delete掉),那么派生类中编译器将不会自动生成这两个函数,且由于base类中该函数是私有的(delete的),因此,派生类将阻止编译器执行相关的操作。
8 什么情况会自动生成默认构造函数
1) 带有默认构造函数的类成员对象,如果一个类没有任何构造函数,但它含有一个成员对象,而后者有默认构造函数,那么编译器就为该类合成出一个默认构造函数。不过这个合成操作只有在构造函数真正被需要的时候才会发生;如果一个类A含有多个成员类对象的话,那么类A的每一个构造函数必须调用每一个成员对象的默认构造函数而且必须按照类对象在类A中的声明顺序进行;
2) 带有默认构造函数的基类,如果一个没有任务构造函数的派生类派生自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;
3) 带有一个虚函数的类
4) 带有一个虚基类的类
9 什么时候合成构造函数
1) 如果一个类没有任何构造函数,但他含有一个成员对象,该成员对象含有默认构造函数,那么编译器就为该类合成一个默认构造函数,因为不合成一个默认构造函数那么该成员对象的构造函数不能调用;
2) 没有任何构造函数的类派生自一个带有默认构造函数的基类,那么需要为该派生类合成一个构造函数,只有这样基类的构造函数才能被调用;
3) 带有虚函数的类,虚函数的引入需要进入虚表,指向虚表的指针,该指针是在构造函数中初始化的,所以没有构造函数的话该指针无法被初始化;
4) 带有一个虚基类的类。