# 对象成员初始化 ## 初始化器列表 下面是一个示例类的构造函数,其中包含初始化器列表: ``` class Example { public: int x; double y; std::string z; Example(int a, double b, std::string c) : x(a), y(b), z(c) {} }; ``` 在这个示例中,构造函数使用了初始化器列表来分别初始化 `x`、`y` 和 `z` 成员变量。可以看到,每个成员变量的初始值都在冒号后跟着相应的变量名和初始值。这样就可以方便地初始化多个成员变量,并提高代码可读性和效率。 ## 默认成员初始化器 默认成员初始化器 ``` class Example { public: int x = 0; // 默认成员初始化器 Example(int a) : x(a) {} }; ``` 在这个示例中,`x` 成员变量被赋予了默认值 `0`,因此即使没有在构造函数中显示地对其赋值,在对象创建时它仍将被初始化为 `0`。如果在构造函数中提供了显式的初始值,则该值将覆盖默认初始化值。 --- # 必须使用初始化器列表: 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化器列表里面 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要使用初始化器 没有默认构造函数的类类型,因为使用初始化器列表可以不必调用无参构造函数来初始化,而是直接其他构造函数初始化 --- # 成员变量声明的顺序 成员是按照他们在类中`声明的顺序`进行初始化的,而不是按照他们在初始化器列表出现的顺序初始化的: ``` class foo { public: int i ;int j ; foo(int x):j(x), i(j){} // i值未定义 }; ``` 这里i的值是未定义的因为虽然j在初始化器列表里面出现在i前面,但是i先于j定义,所以先初始化i,而i由j初始化,此时j尚未初始化,所以导致i的值未定义。一个好的习惯是,按照成员声明的顺序进行初始化。 --- # 内存布局 全局数据区(data area) 放全局变量,静态数据和常量。 代码区(code area) 所有类成员函数和非成员函数代码 栈区(stack area) 为运行函数而分配的局部变量、函数参数、返回数据、返回地址等 堆区(heap area)(即自由存储区) 申请的空间 --- # 对象方法静态联编 指在编译阶段,就能直接使用代码段函数地址调用动态对象的方法。该方法仅需要向非静态成员函数传送this指针,即可用静态函数调用实现动态调用效果。 date1.Increment(…) 实际上为 DATE:: Increment(&date1, …) --- # 拷贝构造函数 在定义语句中用同类型的对象初始化另一个对象。 ``` //假定C为已定义的类,则 C obja; //调用C的(1)无参构造函数,如无(1)(2)构造函数则用默认构造函数 C obja(1,2) //调用C的有参(2)普通构造函数 /*调用C的(3)拷贝构造函数用对象obja初始化对象objb。如果有为C类明确定义拷贝构造函数,将调用这个拷贝构造函数;如果没有为C类定义拷贝构造函数,将调用默认拷贝构造函数。*/ C objb(obja); C objb = obja ; //两者等价(注意:不是赋值运算) ``` 用类类型本身作形式参数。 该参数传递方式为按引用传递,避免在函数调用过程中生成形参副本。 该形参声明为const,以确保在拷贝构造函数中不修改实参的值 ``` C::C(const C& obj); ``` 例如: COMPLEX(const COMPLEX& other); 形参类型为该类类型本身且参数传递方式为按引用传递。 用一个已存在的该类对象初始化新创建的对象。 每个类都`必须`有拷贝构造函数: 用户可根据自己的需要显式定义拷贝构造函数。 若用户未提供,则该类使用由系统提供的缺省拷贝构造函数(可用=default),也可用 =delete 弃置该函数。 缺省拷贝构造函数按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制,完成新对象的初始化。即逐一调用成员的拷贝构造,如果成员是基础类型,则复制值(赋值)。 ## 隐式调用复制构造(1):对象作为函数值参 ``` void fun(C temp) { … } fun(obja);//obja传递给fun函数,创建形参对象temp时,调用C的拷贝构造函数用对象obja初始化对象temp,temp生存期结束被撤销时,调用析构函数 ``` ## 隐式调用复制构造(2):对象作为值从函数返回 生成一个临时对象作为函数的返回结果:当函数返回一对象时,系统将自动创建一个临时对象来保存函数的返回值。创建此临时对象时调用拷贝构造函数,当函数调用表达式结束后,撤销该临时对象时,调用析构函数。 ``` C fun2() { C t; return t ; } b = fun2( ); ``` t --> 临时对象 --> b 但现在的gcc/g++不这么处理,会做一个优化。在C函数里有个t变量,离开func时不撤销这个对象,而是让new C和这个对象关联起来。也就是说t的地址和new C是一样的。 ## 复制策略:拷贝构造函数自定义 1. 对于不含指针成员的类,使用系统提供(编译器合成)的默认拷贝构造函数即可。 2. 缺省拷贝构造函数使用浅复制策略,不能满足对含指针数据成员的类需要。 3. 含指针成员的类通常应重写以下内容: •构造函数(及拷贝构造函数)中分配内存,深复制策略 •= 操作重写,完成对象深复制策略 •析构函数中释放内存 (浅拷贝只复制成员指针的值,而不复制指向的对象实体,导致新旧对象成员指针指向同一块内存。但深拷贝要求成员指针指向的对象也要复制,新对象跟原对象的成员指针不会指向同一块内存,修改新对象不会改到原对象。) Loading... # 对象成员初始化 ## 初始化器列表 下面是一个示例类的构造函数,其中包含初始化器列表: ``` class Example { public: int x; double y; std::string z; Example(int a, double b, std::string c) : x(a), y(b), z(c) {} }; ``` 在这个示例中,构造函数使用了初始化器列表来分别初始化 `x`、`y` 和 `z` 成员变量。可以看到,每个成员变量的初始值都在冒号后跟着相应的变量名和初始值。这样就可以方便地初始化多个成员变量,并提高代码可读性和效率。 ## 默认成员初始化器 默认成员初始化器 ``` class Example { public: int x = 0; // 默认成员初始化器 Example(int a) : x(a) {} }; ``` 在这个示例中,`x` 成员变量被赋予了默认值 `0`,因此即使没有在构造函数中显示地对其赋值,在对象创建时它仍将被初始化为 `0`。如果在构造函数中提供了显式的初始值,则该值将覆盖默认初始化值。 --- # 必须使用初始化器列表: 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化器列表里面 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要使用初始化器 没有默认构造函数的类类型,因为使用初始化器列表可以不必调用无参构造函数来初始化,而是直接其他构造函数初始化 --- # 成员变量声明的顺序 成员是按照他们在类中`声明的顺序`进行初始化的,而不是按照他们在初始化器列表出现的顺序初始化的: ``` class foo { public: int i ;int j ; foo(int x):j(x), i(j){} // i值未定义 }; ``` 这里i的值是未定义的因为虽然j在初始化器列表里面出现在i前面,但是i先于j定义,所以先初始化i,而i由j初始化,此时j尚未初始化,所以导致i的值未定义。一个好的习惯是,按照成员声明的顺序进行初始化。 --- # 内存布局 全局数据区(data area) 放全局变量,静态数据和常量。 代码区(code area) 所有类成员函数和非成员函数代码 栈区(stack area) 为运行函数而分配的局部变量、函数参数、返回数据、返回地址等 堆区(heap area)(即自由存储区) 申请的空间 --- # 对象方法静态联编 指在编译阶段,就能直接使用代码段函数地址调用动态对象的方法。该方法仅需要向非静态成员函数传送this指针,即可用静态函数调用实现动态调用效果。 date1.Increment(…) 实际上为 DATE:: Increment(&date1, …) --- # 拷贝构造函数 在定义语句中用同类型的对象初始化另一个对象。 ``` //假定C为已定义的类,则 C obja; //调用C的(1)无参构造函数,如无(1)(2)构造函数则用默认构造函数 C obja(1,2) //调用C的有参(2)普通构造函数 /*调用C的(3)拷贝构造函数用对象obja初始化对象objb。如果有为C类明确定义拷贝构造函数,将调用这个拷贝构造函数;如果没有为C类定义拷贝构造函数,将调用默认拷贝构造函数。*/ C objb(obja); C objb = obja ; //两者等价(注意:不是赋值运算) ``` 用类类型本身作形式参数。 该参数传递方式为按引用传递,避免在函数调用过程中生成形参副本。 该形参声明为const,以确保在拷贝构造函数中不修改实参的值 ``` C::C(const C& obj); ``` 例如: COMPLEX(const COMPLEX& other); 形参类型为该类类型本身且参数传递方式为按引用传递。 用一个已存在的该类对象初始化新创建的对象。 每个类都`必须`有拷贝构造函数: 用户可根据自己的需要显式定义拷贝构造函数。 若用户未提供,则该类使用由系统提供的缺省拷贝构造函数(可用=default),也可用 =delete 弃置该函数。 缺省拷贝构造函数按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制,完成新对象的初始化。即逐一调用成员的拷贝构造,如果成员是基础类型,则复制值(赋值)。 ## 隐式调用复制构造(1):对象作为函数值参 ``` void fun(C temp) { … } fun(obja);//obja传递给fun函数,创建形参对象temp时,调用C的拷贝构造函数用对象obja初始化对象temp,temp生存期结束被撤销时,调用析构函数 ``` ## 隐式调用复制构造(2):对象作为值从函数返回 生成一个临时对象作为函数的返回结果:当函数返回一对象时,系统将自动创建一个临时对象来保存函数的返回值。创建此临时对象时调用拷贝构造函数,当函数调用表达式结束后,撤销该临时对象时,调用析构函数。 ``` C fun2() { C t; return t ; } b = fun2( ); ``` t --> 临时对象 --> b 但现在的gcc/g++不这么处理,会做一个优化。在C函数里有个t变量,离开func时不撤销这个对象,而是让new C和这个对象关联起来。也就是说t的地址和new C是一样的。 ## 复制策略:拷贝构造函数自定义 1. 对于不含指针成员的类,使用系统提供(编译器合成)的默认拷贝构造函数即可。 2. 缺省拷贝构造函数使用浅复制策略,不能满足对含指针数据成员的类需要。 3. 含指针成员的类通常应重写以下内容: •构造函数(及拷贝构造函数)中分配内存,深复制策略 •= 操作重写,完成对象深复制策略 •析构函数中释放内存 (浅拷贝只复制成员指针的值,而不复制指向的对象实体,导致新旧对象成员指针指向同一块内存。但深拷贝要求成员指针指向的对象也要复制,新对象跟原对象的成员指针不会指向同一块内存,修改新对象不会改到原对象。) Last modification:March 30, 2023 © Allow specification reprint Like 如果觉得我的文章对你有用,请随意赞赏