1、默认构造函数
如果没有为类提供任何构造函数,那么编译器将自动成一个默认的无参构造函数。一旦用户为类定义了构造函数,哪怕只是一个,那么编译器将不再生成默认的构造函数。即当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。
1.1自定义默认构造函数的方式
1、给已有的构造函数的所有参数提供默认值;(推荐此种)
class People{
public:
People(string n="", int a= -1): name(n), age(a){} // 提供了一个默认 构造函数
private:
string name;
int age;
};
2、通过函数重载来定义另一个构造函数,先提供一个没有参数的构造函数,再在其基础上进行重载;
class People{
public:
People(){} // 提供了一个默认 构造函数
People(string n, int a): name(n), age(a){} // 普通构造函数重载
private:
string name;
int age;
};
注:由于只有一个默认构造函数,因此不能同时使用上述两种方式定义默认构造函数;比如People p1;将出现错误
若没有提供默认函数,则定义People p2;将出现错误;
3、在C++11中使用=defalut来要求编译器生成默认构造函数,=default既可以和声明一起出现在类的内部也可以出现在类外部的函数的定义上
class A{
private:
int a;
char b;
public:
A() = default;
A(int a1, char b1) : a(a1), b(b1){};//定义默认构造函数请对其进行重载;
};
1.2为什么需要默认构造函数呢?
1、只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。
2、对于某些类来说,合成的默认构造函数可能执行错误的操作。。含有内置类型或复合类型成员的类应该在类的内部初始化这些成员,或者定义一个自己的默认构造函数。否则,用户在创建类的对象时就可能得到未定义的值。
3、编译器不能为某些类合成默认的构造函数。。如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。
请看ANSYS公司经典例题
一下代码若存在bug请改正,改正之后在给出输出值?
class A{
public:
A(int data):data_(data){
cout << "Constructor:" << data_ << endl;
};
void func(int i){
data_ += i;
cout << "func:" << data_ <<endl;
}
void func(int i) const{
data_ = i + 1;
cout << "func const:" << data_ <<endl;
}
private:
int data_;
};
class B : public A{
};
int main(){
// 不产生临时变量,直接调用构造函数!!!
// 用一个临时对象来初始化一个新对象时,编译器一般会优化为直接使用临时对象的参数来创建新对象,
// 即const A aa(3); 实际上不会生成临时对象。
const A aa = A(3);// 不产生临时变量,直接调用构造函数!!!
//A aa(10);
aa.func( 5 );
B bb;
bb.func(5);
return 0;
}
解析:
// 错误1:const修饰 不能改变数据成员
// 错误2: bb创建时先调用基类构造, 发现 A 缺少默认构造函数
// 正确输出如下
Constructor:3
func const:3
func:5
2、拷贝构造函数
2.1拷贝构造函数的定义
若构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
如果不显示定义的话,编译器默认为我们自动定义拷贝函数(浅拷贝);
1、如果类带有指针成员变量,并有动态内存分配,则它必须有一个拷贝构造函数;
2、拷贝构造函数的首个形参是T&、const T&、volatile T&、const volatile T&且无其他参数,或任何额外的参数都必须带有默认实参;(注:const 为可选项,但在Dev C++5.11中必须加上)
class Foo()
{
Foo();
Foo(const Foo& obj);
//...
}
2.2拷贝构造函数类型
拷贝构造函数分为浅拷贝和深拷贝。
浅拷贝:指的是在对象(赋值)时,仅仅对对象中的数据成员的值简单的赋值给另一个同一类对象应的数据成员,编译器提供的默认拷贝构造函数 和 默认赋值运算符重载函数( operator=() ) 执行的是浅拷贝。
深拷贝:对于对象中动态成员(申请了动态内存),就不能仅仅简单地赋值了,而应该重新动态分配空间
#include<iostream>
#include<cstring>
using namespace std;
class C_person{
public:
C_person(char *p, int age = -1, int height = -1)//构造函数
{
cout << "调用构造函数" << endl;
this->height = height;
p_age = new int;//--------------------存在分配内存!!!
*p_age = age;
p_name = new char[strlen(p) + 1];//注意此处和使用sizeof是不同
strcpy(p_name, p);
}
C_person(const C_person& obj)//拷贝构造函数
{
cout << "调用--拷贝构造--函数!!!" << endl;
this->height = obj.height;
//浅拷贝!!!
p_age = new int;
*p_age = *obj.p_age;
//深拷贝 !!!
if(p_name != NULL)
delete p_name;//断开原有的连接
p_name = new char[strlen( obj.p_name ) + 1];//重新分配一样的大小
strcpy(p_name, obj.p_name);//将原来内存中的内容复制一份到新开辟的内存中去
}
void print_info()
{
cout <<" 姓名:"<< p_name
<<" 年龄:"<< (*p_age)
<<" 身高:"<< height << endl;
}
~C_person()//析构函数
{
cout << "析构函数释放\n\n";
delete p_age;
delete []p_name;
}
private:
char *p_name; //------------------------------存在指针!!!
int *p_age;
int height;
};
int main()
{
C_person *p_person = new C_person("xufangakjgksdgh",10 , 799);
p_person->print_info();
delete p_person;//此形式只有使用了delete才执行析构函数
C_person P1("xufang",20, 188);//使用直接初始化
P1.print_info();
C_person P2 = P1;//使用拷贝初始化
P2.print_info();
return 0;
}
2.3拷贝构造函数发生的情况
若存在类 A,则有:
1、一个对象需要通过另外一个对象进行初始化。
A a;
A b(a);或 A b = a; // 调用拷贝构造函数
A a;
A b(1,2,3);
a = b; // 这是赋值操作啊!!调用默认赋值运算重载函数
2、一个对象以值传递(指针和引用不行)的方式传入函数体
void func( A x );
int main(){
A a;
func( a ); //
}
3、一个对象以值传递的方式从函数返回
A func( A x );
int main(){
A a;
A b = func( a );
}
拷贝构造函数经常会和 赋值构造函数混淆,,下面是一个综合例子,,可以加深大家对这两这着的区别,,特别要注意对象的定义、声明、初始化与赋值等等一系列名词的区别。
#include <iostream>
using namespace std;
class Data {
public:
Data(){
cout << "default constructor run...\n";
}
Data(int _years, int _months, int _days):years(_years),months(_months),days(_days){
cout << "user constructor run..." << endl;
}
Data(const Data& d) //拷贝构造函数
{
cout << "copy constructor run..." << endl;
this->years = d.years;
this->months = d.months;
this->days = d.days;
}
Data& operator = (const Data& d) // = 运算符重载
{
cout << "operator=() run..." << endl;
this->years = d.years;
this->months = d.months;
this->days = d.days;
return *this;
}
int get_years(){return this->years;}
int get_months(){return this->months;}
int get_days(){return this->days;}
void print()const{
cout << years<< "年"<< months<< "月"<< days<< "日"<<endl<<endl;
}
private:
int years;
int months;
int days;
};
void print_class(Data d){
cout << d.get_years()<<"-"<<d.get_months()<<"-"<< d.get_days()<<endl<<endl;
}
Data test(Data d){
Data res = d;
return res;
}
int main()
{
// 初始化
Data data1(2019,9,22);
data1.print();
Data a(data1); // 一、调用拷贝构造函数
a.print();
Data b = data1; // 一、调用拷贝构造函数
b.print();
Data c = Data(2020,2020,2020);// 由编译器优化为Data c(2020,2020,2020);不会产生临时变量
c.print();
// 赋值操作
Data d; // 定义
d = data1; // 赋值:调用 = 运算符重载函数 (a 为调用者,data1为参数,相当于a.operator=(data1);)
d.print();
Data data2(20,20,20);
print_class(data2); // 二、调用拷贝函数
Data data3 = test(data2); // 三、调用拷贝函数2次
print_class(data3); // 调用拷贝函数1次
return 0;
}
接下来再看一道题,,请问输出是什么?,,欢迎评论区留言。。一起学习进步!!
struct C{
C(){cout << "1 ";}// 构造函数
C(const C& other){cout << "2 ";}// 拷贝构造函数
C& operator=(const C& other){cout << "3 "; return *this;}// 赋值运算符重载
};
void main_5()
{
C c1;
C c2 = c1;
cout << endl;
C c3;
c3 = c2;
}
欢迎大家加C/C++ Linux 技术栈开发群:786177639,一起交流学习。