C++入门学习:构造函数,拷贝构造函数(特殊情况,浅拷贝与深拷贝),析构函数

构造函数:在定义对象的时候对 对象 进行初始化的函数

构造函数的特点:

1、函数名和类名相同

2、构造函数没有返回值

3、构造函数不需要手动调用,在创建对象的时候编译器自己会调用

4、构造函数可以被重载

注意事项:如果类中没有写构造函数,在创建对象的时候编译器会自动创建一个无参构造函数(形式:类名() 函数参数列表中没有参数)是一个空的函数,如果你写了构造函数(编译器默认的无参构造消失),并且形式不跟编译器默认的无参函数形式一样,则在定义对象时要初始化对象的成员(初始化形式:类名 对象名(参数列表),即括号法,下面有介绍)参数要和希望使用的构造函数的参数一致,否则系统报错:没有合适的默认构造函数进行初始化。

eg:

class Student
{
public:
Student()  //无参构造函数
{
_id = 0;
_name = NULL;
cout << "无参构造函数 Student() 被调用" << endl;
}
Student(int id)
{
_id = id;
_name = NULL;
cout << "构造函数 Student(int id) 被调用" << endl;
}
Student(char *name)
{
_id = 0;
_name = name;
cout << "构造函数 Student(char *name) 被调用" << endl;
}
Student(int id, char *name)             //以上4种都是自定义的构造函数
{
_id =id;
_name = name;
cout << "构造函数 Student(int id, char *name) 被调用" << endl;

}
private:

int _id;
char *_name;

};

构造函数的使用形式

int main()
{
    Student  s;                  //无参构造函数 Student() 被调用
1.括号法:
    Student  s1(1, "笑笑");   //构造函数 Student(int id, char *name) 被调用
    Student  s2(2);              //构造函数 Student(int id) 被调用

2.等号法 =   注意只能调用有且仅有一个参数的构造函数
    //2、等号法 =   只能调用有一个参数的构造函数
    Student s3 = 10;                 //相当于=======> Student s3(10);
    Student s4 = "西卡";           //相当于=======> Student s4("西卡");
    Student s5 = (3, "爱纳米");    //后面扩号中的内容相当于逗号表达式,取最后的值==> Student s5("爱纳米")


3.显示调用的构造函数
    Student s6 = Student(4, "杰杰杰");  //相当于===> Student s6(4, "杰杰杰");                                     

    return 0;
}


拷贝构造函数:用另一个对象 对当前对象进行初始化

固定形式: 类名(const 类名& 变量名)

析构函数: 在对象被释放的时候编译器自动调来释放对象资源

形式:~类名

特点:1.没有返回值 2.没有参数 3. 不能被重载 4.先构造的函数后析构

这两个和构造函数一样,如果没有自定义的拷贝构造/析构函数,编译器会自动生成默认的拷贝构造/析构函数

eg:

class Student
{
public:
Student()  //
无参构造函数
{
_id = 0;
_name = NULL;
cout << "无参构造函数 Student() 被调用" << endl;
}
Student(int id)
{
_id = id;
_name = NULL;
cout << "构造函数 Student(int id) 被调用" << endl;
}
Student(char *name)
{
_id = 0;
_name = name;
cout << "构造函数 Student(char *name) 被调用" << endl;
}
Student(int id, char *name)             //以上4种都是自定义的构造函数
{
_id =id;
_name = name;
cout << "构造函数 Student(int id, char *name) 被调用" << endl;
}


        Student (const Student &obj)//拷贝构造函数
{
_id = obj._id;
_name = obj._name;
cout << "拷贝构造函数被调用" << endl;
}
~Student()                      //析构函数
{
cout << "析构函数被调用" << endl;
}
private:
int _id;
char *_name;

};

int main()
{
cout << "111111111" << endl;//输出  111111111
Student s;                                //无参构造函数 Student() 被调用

                                                      
        Student s2(s);                          //拷贝构造函数被调用
cout << "22222222222" << endl;//22222222222
                                                         //析构函数被调用(析构s2)
                                                          //析构函数被调用(析构s)
return 0;
}

特殊情形:在构造函数中调用构造函数

eg:

class A
{
public:
    A(int a, int b)
    {
        _a = a;
        _b = b;
        A(a, b, 30);//构造函数中调用构造函数不会达到预期的效果,运行时产生一个匿名对象(也叫临时对象,不用构造函数进行初始化),为这个匿名对象赋值,结束后析构了匿名函数
    }
    A(int a, int b, int c)
    {
        _a = a;
        _b =b;
        _c = c;
    }
    
    void print()
    {
        printf("_a = %d, _b = %d, _c = %d\n", _a, _b, _c);
    }
    ~A()
    {
        printf("调用析构时,_a = %d, _b = %d, _c = %d\n", _a, _b, _c);
    }
};



int main()
{
A a(10, 20);
a.print();//最后a=10,b=20,c是一个垃圾值
        A(10, 20, 30);//也是一个临时对象,不调用构造函数,在a之前被析构

        //最后一个析构是,a被析构(先构造的后析构);
return 0;

}



当对象做函数参数传递的时候,会调用拷贝构造函数
void func(Student s1)
{

    ;  /*原因:直接传变量名,在外函数func中会定义s1,而在参数传递的时候,会有一个值传递,即用s给s1赋值,满足初始化s1的条件,也就满足了调用拷贝构造函数的条件(用另一个对象去 给当前对象进行初始化)*/

}

void func2(Student &s1)
{

    ;/*传引用的可以避免拷贝构造函数的调用

    原因:传参的时候是将变量代表的空间传了过来,函数形参为这个空间取了别名s1,并不存在*/
}

int main()
{
    func1(s);//调用无参构造/拷贝构造/析构函数(析构函数func1里的s1)
    cout << "*******" << endl;
    func2(s);//没有调用构造/拷贝构造/析构函数

    //最后析构了s
    return 0;

}


调用拷贝构造函数的特殊情况:函数的返回值是对象
eg:延用上面的Student类
Student  func1()
{
    Student s;
    return s;
}
//处理函数返回值的三种情形

/*1、不使用函数的返回值
      函数返回的时候会产生一个匿名对象,用返回的对象 对匿名对象 进行初始化,从而达成调用拷贝构造函数的条件调用,调用拷贝构造函数。因为这个匿名对象没人使用,所以被立马释放掉了*/

int mian()
{
    func1();/*无参构造函数 Student() 被调用
                  拷贝构造函数被调用
                  析构函数被调用(析构s)
                  析构函数被调用(析构匿名)*/
}

/*2、用新的对象去收返回值(相当于用匿名对象给新的对象s1 初始化)
1:函数返回的时候会产生一个匿名对象,用返回的对象 对匿名对象初始化,调用拷贝构造函数
2 : 然后将变量名s1 给这个匿名对象使用,匿名对象变成了有名对象*/
int mian()
{
    Student s1 = func1(); /*无参构造函数 Student() 被调用
                                      拷贝构造函数被调用
                                      析构函数被调用(析构s)
                                      析构函数被调用(析构s1即匿名),main中并没有为s1单独的开辟空间,
                                      所以不会调用三个析构函数*/

}

/*3、用旧的对象去收返回值
1: 函数返回的时候会产生一个匿名对象,用返回的对象 对匿名对象初始化,调用拷贝构造函数
2 :  用匿名对象
对s1 进行赋值(不会调用构造函数)
3:匿名对象还是匿名的 所以释放了
*/
int main()
{
Student s1(10);//构造函数 Student(int id) 被调用

s1 = func1();/*赋值!不调用构造函数
                            无参构造函数 Student() 被调用
                            拷贝构造函数被调用
                            析构函数被调用(析构s)
                            析构函数被调用(析构匿名)
                            析构函数被调用(析构s1)
                            */
return 0;
}

浅拷贝与深拷贝

浅拷贝的原因:编译器在调用默认拷贝构造函数时,只能复制值,不能复制空间。下面举例说明

eg:

class Student
{
public:
Student(int id = 0, char *name = NULL)
{
_id = id;
//给_name分配空间
        _name = (char*)malloc(sizeof(char)*20);
strcpy(_name, name);
}
void show()
{
printf ("id = %d, name = %s\n", _id, _name);
}

~Student()
{
if (_name != NULL)
free(_name);
_name = NULL;
}

private:
int _id;
char *_name;
};

int main()
{
Student s(1, "小明");
s.show();

Student s1 = s;//调用默认的拷贝构造函数(不调用构造函数),不复制空间,
                                /*将s的_id 复制给 s1的_id,将s的_name指针指向的地址
                                    复制给s1的_name指针,两个指针指向了同一块空间
                                    在s,s1分别使用完后调用析构函数进行释放空间时,
                                    对同一块空间(定义s时开辟的空间)释放了两次,运行时出错。
                                 */

s1.show();
return 0;

}

解决方案:深拷贝(自定义一个拷贝构造函数,进行堆上空间的复制)

class Student
{
public:
Student(int id = 0, char *name = NULL)
{
_id = id;
//给_name分配空间
_name = (char*)malloc(sizeof(char)*20);
strcpy(_name, name);
}
//自定义拷贝构造函数
Student(const Student &obj)
{
_id = obj._id;
//给当前对象的_name分配空间
_name = (char*)malloc(sizeof(char)*20);
strcpy(_name, obj._name);
}
void show()
{
printf ("id = %d, name = %s\n", _id, _name);
}


~Student()
{
if (_name != NULL)
free(_name);
_name = NULL;
}


private:
int _id;
char *_name;

};

int main()
{
Student s(1, "小明");
s.show();


Student s1 = s;/*调用自定义的拷贝构造函数,s1的_name指针指向自己开辟的空间,析构自己开辟的空间,避免了浅拷贝的错误*/
s1.show();


return 0;

}


Tip:

如果不是特别需要,不要用一个对象去初始化另一个对象(避免 拷贝构造的操作)

可以将 拷贝构造设成私有的,(不让外部使用),声明就可以,不需要实现。

eg:

class B
{
private:
B(const B &);//私有拷贝构造函数声明
public:
B(){}
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值