C++ 对象的初始化

C++中变量初始化包括三种方式:默认初始化直接初始化拷贝初始化。在C++中每个类都定义了对象被初始化的方式,构造函数的任务是初始化类的数据成员,只要类的对象被创造就会执行构造函数。

一个类被创建出来即使什么东西都不定义,编译器也会为我们生成下面的4个函数:

  1. 默认构造函数(default constructor)
  2. 析构函数(destructor)
  3. 拷贝构造函数(copy constructor)
  4. 拷贝赋值操作符(copy-assignment operator)

默认初始化

默认初始化依赖于默认构造函数。

默认构造函数(default constructor)

默认构造函数无须任何实参,如果一个类没有显示定义构造函数,则编译器会隐式地定义一个默认构造函数,称为合成的默认构造函数(synthesized default constructor),但是合成默认构造函数可能会失效,对于任何类都应定义默认构造函数。

构造函数初始值列表(cosntructor initializer list)

对于构造函数,建议使用初始值列表,例如对于下面这个类:

class A{
private:
    int i;
    double j;
public:
    /*...*/
}

有下面两种构造函数的写法:

A():i(0),j(0){}//列表初始化,直接将i、j显示地初始化为0,相当于拷贝构造函数,对内置类型直接拷贝,对类类型使用该类的拷贝构造函数
A():{i=0;j=0;}//先将i、j默认初始化再将i、j赋值为0

第二种写法会先进行默认初始化再进行赋值,如果有些成员无法默认初始化(例如引用&和常量const)只能显示地初始化则会造成错误,因而建议都使用列表初始化以防止出错。

直接初始化和拷贝初始化

当用于类类型对象时,初始化的拷贝形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数。拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个临时对象拷贝到正在创建的对象。

拷贝构造函数(copy construcor)

拷贝构造函数的声明为classname(const classname &op),与合成默认构造函数不同,即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。例如我们在自定义的类内声明了一个形参为(const)classname 的构造函数,例如对于下面的类:

class A{
private:
    int i;
public:
    A():i(0){}
    A(A op){i}        
}

则编译器会提示:[Error] invalid constructor; you probably meant 'A (const A&)',说明编译器已经默认创建了声明为A(const A &)的拷贝构造函数,即合成拷贝构造函数(synthesized copy constructor),一般合成拷贝构造函数会将其参数的成员诸葛拷贝到正在创建的对象中,对类类型的成员使用该类的拷贝构造函数来拷贝,而内置类型的成员则是直接拷贝。

区分拷贝初始化和直接初始化

使用直接初始化时,我们实际上时要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数;而使用拷贝初始化时,我们要求编译器将右侧运算对象通过拷贝构造函数拷贝到正在创建的对象中。

关于拷贝构造函数和拷贝赋值运算符

拷贝赋值运算符本质上是一个函数。一般情况下我们总是以为在=运算符出现的地方都是调用copy assignment operator,但是当一个新对象被定义的时候,即便这个时候是使用了'='运算符,它真实调用的是初始化函数copy constructor,而不是调用copy assignment operator去进行赋值操作。

class A{
    /*...*/ 
}
A a;//默认初始化
A b(a);//拷贝初始化
A c=a;//虽然使用了=运算符,但是此处调用的时A(const A &)即拷贝构造函数
A d;//默认初始化
d=a;//这里才是使用了拷贝赋值运算符,是赋值

何时发生拷贝初始化

  1. 将一个对象作为实参传递给一个非引用类型的形参。
  2. 从一个返回类型为非引用类型的函数返回一个对象。
  3. 用花括号列表初始化一个数值中的元素或一个聚合类中的成员。

以例代文

为了更好地理解三种初始化方式,使用下面这个例子

#include<cstdio>
#include<iostream>
using namespace std;
class A{
private:
    int i;
    int j;
public:
    A()//默认构造函数 
    {cout<<"  A()"<<endl;}  
    A(const A &)//拷贝构造函数 
    {cout<<"  A(const A&)"<<endl;}
    A& operator =(const A &)//拷贝赋值操作符
    {cout<<"  A &operator =(const A &)"<<endl;}
    A(int k)//
    {cout<<"  A(int k)"<<endl;} 
    A(int k,int m)
    {cout<<"  A(int k,int m)"<<endl;}
}; 
A func(A temp){
    return temp;
}
int main()
{
    cout<<"p1:"<<endl; 
        A p1;//默认初始化 
    cout<<"p2:"<<endl;
        A p2(1);//直接初始化 
    cout<<"p3:"<<endl;
        A p3(1,2);//直接初始化
    cout<<"p4:"<<endl;
        A p4=1;//被编译器优化为A p4(1),直接初始化 
    cout<<"p5:"<<endl;
        A p5(p1);//拷贝初始化
    cout<<"p6:"<<endl;
        A p6=p1;//仍然时拷贝初始化,虽然使用了=
    cout<<"p7:"<<endl;
        A p7;//先默认初始化
        p7=p1;//使用了拷贝赋值运算符,是赋值 
    cout<<"p8:"<<endl;
        A p8;//默认初始化 
        p8=func(p1);//较复杂下面分析 
return 0 ;
}

得到运行结果

p1:
        A()//默认初始化 
p2:
        A(int k)//直接初始化 
p3:
        A(int k,int m)//直接初始化
p4:
        A(int k)//被编译器优化为A p4(1),直接初始化 
p5:
        A(const A&)//拷贝初始化
p6:
        A(const A&)//仍然时拷贝初始化,虽然使用了=
p7:
        A()//先默认初始化
        A &operator =(const A &)//使用了拷贝赋值运算符,是赋值 
p8:
        A()//先默认初始化
        A(const A&)//将一个对象作为实参传递给一个非引用类型的形参,发生拷贝初始化,将temp拷贝初始化为p1
        A(const A&)//从一个返回类型为非引用类型的函数返回一个对象,当func函数结束时要将返回值拷贝到临时对象,执行拷贝初始化
        A &operator =(const A &)//将临时对象通过拷贝赋值运算符赋值给p8
        //注意这个时候有可能会被编译器优化!

原文链接: https://www.cnblogs.com/chinono/p/13055342.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    C++ 对象的初始化

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/353253

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月2日 上午7:58
下一篇 2023年3月2日 上午7:58

相关推荐