C++类构造函数初始化列表

合集下载

C++——类继承以及类初始化顺序

C++——类继承以及类初始化顺序

C++——类继承以及类初始化顺序对于类以及类继承, ⼏个主要的问题:1) 继承⽅式: public/protected/private继承.这是c++搞的, 实际上继承⽅式是⼀种允许⼦类控制的思想. ⼦类通过public继承, 可以把基类真实还原, ⽽private继承则完全把基类屏蔽掉.这种屏蔽是相对于对象层⽽⾔的, 就是说⼦类的对象完全看不到基类的⽅法, 如果继承⽅式是private的话, 即使⽅法在基类中为public的⽅法.但继承⽅式并不影响垂直⽅向的访问特性, 那就是⼦类的函数对基类的成员访问是不受继承⽅式的影响的.⽐较(java): java是简化的, 其实可认为是c++中的public继承. 实在没必要搞private/protected继承, 因为如果想控制,就直接在基类控制就好了.2) 对象初始化顺序: c++搞了个成员初始化列表, 并确明确区分初时化跟赋值的区别. c++对象的初始化顺序是:(a) 基类初始化(b) 对象成员初时化(c) 构造函数的赋值语句举例:假设 class C : public A, public B {D d;//}则初始化的顺序是A, B, D, C的构造函数.这⾥基类的初始化顺序是按照声明的顺序, 成员对象也是按照声明的顺序. 因此 c(int i, int j) : B(i), A(j) {} //这⾥成员初始化列表的顺序是不起作⽤的;析构函数的顺序则刚好是调过来, 构造/析构顺序可看作是⼀种栈的顺序;⽐较(java): java中初始化赋值是⼀回事. ⽽且对基类的构造函数调⽤必须显⽰声明, 按照你⾃⼰写的顺序.对成员对象, 也叫由你初始化.没有什么系统安排的顺序问题, 让你感觉很舒服;3) 多继承问题: c++⽀持多继承, 会导致"根"不唯⼀. ⽽java则没有该问题;此外c++没有统⼀的root object, java所有对象都存在Object类使得很多东西很⽅便. ⽐如公共的seriall, persistent等等.4) 继承中的重载: c++中, 派⽣类会继承所有基类的成员函数, 但构造函数, 析构函数除外.这意味着如果B 继承A, A(int i)是基类构造函数, 则⽆法B b(i)定义对象. 除⾮B也定义同样的构造函数.c++的理由是, 假如派⽣类定义了新成员, 则基类初始化函数⽆法初始化派⽣类的所有新增成员.⽐较(java): java中则不管, 就算有新增对象基类函数没有考虑到, ⼤不了就是null, 或者你⾃⼰有缺省值. 也是合理的.5) 继承中的同名覆盖和⼆义性: 同名覆盖的意思是说, 当派⽣类跟基类有完全⼀样的成员变量或者函数的时候, 派⽣类的会覆盖基类的.类似于同名的局部变量覆盖全局变量⼀样. 但被覆盖的基类成员还是可以访问的.如B继承A, A, B都有成员变量a,则B b, b.a为访问B的a, b.A::a 则为访问基类中的a. 这对于成员函数也成⽴.但需要注意的是, 同名函数必须要完全⼀样才能覆盖. int func(int j)跟int func(long j)其实是不⼀样的. 如果基类,派⽣类有这两个函数, 则不会同名覆盖.最重要的是, 两者也不构成重载函数. 因此假如A有函数int func(int j), B有函数int func(long j). 则B的对象b.func(int)调⽤为错误的. 因为B中的func跟它根本就不构成重载.同名覆盖导致的问题是⼆义性. 假如C->B=>A, 这⾥c继承B, B继承A. 假如A, B都有同样的成员fun, 则C的对象c.fun存在⼆义性. 它到底是指A 的还是B的fun呢?解决办法是⽤域限定符号c.A::fun来引⽤A的fun.另外⼀个导致⼆义性的是多重继承. 假设B1, B2都继承⾃B, D则继承B1, B2. 那么D有两个B⽽产⽣⼆义性.这种情况的解决办法是⽤虚基类. class B1 : virtual public B, class B2:virtual public B, D则为class D : public B1, public B2. 这样D中的成员只包含⼀份B的成员使得不会产⽣⼆义性.⽐较(java). java中是直接覆盖. 不给机会这么复杂, 还要保存基类同名的东西. 同名的就直接覆盖, 没有同名的就直接继承.虚基类的加⼊, 也影响到类的初始化顺序. 原则是每个派⽣类的成员化初始化列表都必须包含对虚基类的初始化.最终初始化的时候, 只有真正实例化对象的类的调⽤会起作⽤. 其它类的对虚基类的调⽤都是被忽略的. 这可以保证虚基类只会被初始化⼀次.c++没有显式接⼝的概念, 我觉得是c++语⾔的败点. 这也是导致c++要⽀持组件级的重⽤⾮常⿇烦. 虽然没有显式的接⼝, 但c++中的纯虚函数以及抽象类的⽀持, 事实上是等同于接⼝设施的. 当⼀个类中, 所有成员函数都是纯虚函数, 则该类其实就是接⼝.java c++接⼝类(所有成员函数都是纯虚函数)抽象类类(部分函数是虚函数)对象类对象类C++构造函数调⽤顺序1. 如果类⾥⾯有成员类,成员类的构造函数优先被调⽤;2. 创建派⽣类的对象,基类的构造函数优先被调⽤(也优先于派⽣类⾥的成员类);3. 基类构造函数如果有多个基类,则构造函数的调⽤顺序是某类在类派⽣表中出现的顺序⽽不是它们在成员初始化表中的顺序;4. 成员类对象构造函数如果有多个成员类对象,则构造函数的调⽤顺序是对象在类中被声明的顺序⽽不是它们出现在成员初始化表中的顺序;5. 派⽣类构造函数,作为⼀般规则派⽣类构造函数应该不能直接向⼀个基类数据成员赋值⽽是把值传递给适当的基类构造函数,否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。

C++类的定义和对象

C++类的定义和对象

C++类的定义和对象C++类的定义和对象类的成员变量称为类的属性(Property),将类的成员函数称为类的⽅法(Method)。

在⾯向对象的编程语⾔中,经常把函数(Function)称为⽅法(Method)。

类的定义class Student{public://成员变量char *name;int age;float score;void say(){cout<<name<<age<<score<<endl;}};类只是⼀个模板(Template),编译后不占⽤内存空间.class C++ 中新增的关键字,⽤来定义类。

成员变量和成员函数,它们统称为类的成员(Member)创建对象:Student Lilei; //创建对象Student是类名,liLei是对象名。

和使⽤基本类型定义变量的形式类似, 从这个⾓度考虑,我们可以把 Student 看做⼀种新的数据类型,把 liLei 看做⼀个变量。

在创建对象时,class 关键字可要可不要class Student LiLei; //正确Student LiLei; //同样正确还可以创建对象数组:Student allStu[100];使⽤对象指针:Student stu;//pStu 是⼀个指针,它指向 Student 类型的数据,通过 Student 创建出来的对象Student *pStu = &stu;在堆上创建对象,这个时候就需要使⽤前⾯讲到的new关键字Student *pStu = new Student;使⽤ new 在堆上创建出来的对象是匿名的,没法直接使⽤,必须要⽤⼀个指针指向它,再借助指针来访问它的成员变量或成员函数.对象指针后,可以通过箭头->来访问对象的成员变量和成员函数;两种创建对象的⽅式:⼀种是在栈上创建,形式和定义普通变量类似;另外⼀种是在堆上使⽤ new 关键字创建,必须要⽤⼀个指针指向它,读者要记得 delete 掉不再使⽤的对象C++类的成员变量和成员函数在定义类的时候不能对成员变量赋值,因为类只是⼀种数据类型或者说是⼀种模板,本⾝不占⽤内存空间,⽽变量的值则需要内存来存储.类的成员函数也和普通函数⼀样,都有返回值和参数列表,它与⼀般函数的区别是:成员函数是⼀个类的成员,出现在类体中,它的作⽤范围由类来决定;⽽普通函数是独⽴的,作⽤范围是全局的,或位于某个命名空间内.只在类体中声明函数,⽽将函数定义放在类体外⾯,如下图所⽰:class Student{public://成员变量;char *name;int age;float score;//声明成员函数;void say();};//定义成员函数void Student::say(){cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;}类体内成员函数默认为内联函数; inline 关键词是多余的;类体外定义 inline 函数的⽅式,必须将类的定义和成员函数的定义都放在同⼀个头(源)⽂件中.C++类成员的访问权限以及类的封装成员访问限定符publicprivateprotected只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分类的内部(定义类的代码内部):⽆论成员被声明为 public、protected 还是 private, 都是可以互相访问的,没有访问权限的限制.在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。

如何处理C++构造函数中的错误——兼谈不同语言的错误处理_转

如何处理C++构造函数中的错误——兼谈不同语言的错误处理_转

如何处理C++构造函数中的错误——兼谈不同语⾔的错误处理_转⽤C++写代码的时候总是避免不了处理错误,⼀般来说有两种⽅式,通过函数的返回值或者抛出异常。

C语⾔的错误处理⼀律是通过函数的返回值来判断的,⼀般是返回0、NULL或者-1表⽰错误,或者直接返回错误代码,具体是哪种⽅式没有统⼀的规定,各种API也各有各的偏好。

譬如fopen函数,当成功时返回⽂件指针,失败时返回NULL,⽽POSIX标准的open函数则在成功时返回0或者正数,失败时返回-1,然后需要再通过全局变量errno来判断具体错误是什么,配套的还有⼀系列perror、strerror这样的函数。

C++的错误处理⽅式C++号称向下兼容C语⾔,于是就将C语⾔通过返回值的错误处理⽅式也搬了进来。

但C++最⼤的不同是引⼊了异常机制,可以⽤throw产⽣⼀个异常,并通过try和catch来捕获。

于是就混乱了,到底是什么时候使⽤返回值表⽰错误,什么时候使⽤异常呢?⾸先简单谈论⼀下异常和返回值的特点。

异常的优点1. 错误信息丰富,便于获得错误现场2. 代码相对简短,不需要判断每个函数的返回值异常的缺点1. 使控制流变得复杂,难以追踪2. 开销相对较⼤返回值的优点1. 性能开销相对⼩2. 避免定义异常类返回值的缺点1. 程序员经常「忘记」处理错误返回值2. 每个可能产⽣错误的函数在调⽤后都需要判断是否有错误3. 与「真正的」返回值混⽤,需要规定⼀个错误代码(通常是0、-1或NULL)使⽤异常还是返回值我的观点是,⽤异常来表⽰真正的、⽽且不太可能发⽣的错误。

所谓不太可能发⽣的错误,指的是真正难以预料,但发⽣了却⼜不得不单独处理的,譬如内存耗尽、读⽂件发⽣故障。

⽽在⼀个字符串中查找⼀个⼦串,如果没有找到显然应该是⽤⼀个特殊的返回值(如-1),⽽不应该抛出⼀个异常。

⼀句话来概况就是不要⽤异常代替正常的控制流,只有当程序真的「不正常」的时候,才使⽤异常。

反过来说,当程序真正发⽣错误了,⼀定要使⽤异常⽽不是返回⼀个错误代码,因为错误代码总是倾向于被忽略。

c++中冒号(:)的用法

c++中冒号(:)的用法

c++中冒号(:)的⽤法(1)表⽰机构内位域的定义(即该变量占⼏个bit空间)typedef struct _XXX{unsigned char a:4;unsigned char c;} ; XXX(2)构造函数后⾯的冒号起分割作⽤,是类给成员变量赋值的⽅法,初始化列表,更适⽤于成员变量的常量const型。

struct _XXX{_XXX() : y(0xc0) {}};(3) public:和private:后⾯的冒号,表⽰后⾯定义的所有成员都是公有或私有的,直到下⼀个"public:”或"private:”出现为⽌。

"private:"为默认处理。

(4)类名冒号后⾯的是⽤来定义类的继承。

class 派⽣类名 : 继承⽅式基类名{派⽣类的成员};继承⽅式:public、private和protected,默认处理是public。

2、类构造函数(Constructor)的初始化列表先说下什么叫构造函数吧(是不是啰嗦了?C++的⼈应该都知道了吧,还是以防万⼀⼀下)。

所谓构造函数,就是与类同名的函数,它与普通函数的区别在于,它没有返回类型。

在构造函数后⾯紧跟着冒号加初始化列表,各初始化变量之间以逗号(,)隔开。

下⾯举个例⼦。

class myClass{public :myClass();// 构造函数,⽆返回类型,可以有参数列表,这⾥省去~myClass();// 析构函数int a;const int b;}myClass::myClass():a(1),b(1)// 初始化列表{}上⾯的例⼦展⽰了冒号的这个⽤法,下⾯对这个⽤法进⾏⼏点说明:1)初始化列表的作⽤相当于在构造函数内进⾏相应成员变量的赋值,但两者是有差别的。

在初始化列表中是对变量进⾏初始化,⽽在构造函数内是进⾏赋值操作。

两都的差别在对于像const类型数据的操作上表现得尤为明显。

我们知道,const类型的变量必须在定义时进⾏初始化,⽽不能对const型的变量进⾏赋值,因此const类型的成员变量只能(⽽且必须)在初始化列表中进⾏初始化,即下⾯的代码将会出错:myClass::myClass(){a = 1;// 没错,效果相当于在初始化列表中进⾏初始化b = 1;// 出错,const变量不能进⾏赋值操作;}2)初始化的顺序与成员变量声名的顺序相同。

c++中冒号和双冒号的用法

c++中冒号和双冒号的用法

unsigned char a:4;
unsigned char c;
} ; XXX
(2)构造函数后面的冒号起分割作用,是类给成员变量赋值的方法,初始化列表,更适用于成员变量的常量const型。
struct _XXX{
_XXX() : y(0xc0) {}
在运算符等级中属于最高级的!
在你的问题中,似乎说的只是命名空间作用域符。
using namespace 命名空间名(如,abc);
表示在以下程序代码中所使用的标示符(如果此标示符在abc中定义)是abc中的,包括类型名(类),变量名,函数名,对象名。。。
using abc::标示符(i);
7、汇编指令模板
这个我也不懂,不班门弄斧了,可以参考一下:/articles/2006/43/1144846933898_1.html
改天学习一下。
(1)表示机构内位域的定义(即该变量占几个bit空间)
typedef struct _XXX{
(2)using abc::;万一你的程序中也用到了一个函数(函数名与abc中的这个函数同名),那么系统也不能判断你使用的是abc中的那个函数,还是本程序中的那个函数;
最安全的办法(当然也是最繁琐的)
就是,每当你用到一个变量(函数...)时,你都要明确他的来历(即属于哪个命名空间)除非它没有命名空间
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:
1. 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

C类构造函数初始化列表

C类构造函数初始化列表

类构造函数初始化列表初始化列表地定义在使用编程地过程当中,常常需要对类成员进行初始化,通常地方法有两种:一种是构造函数内对类地成员赋值,一种则是使用初始化列表地构造函数显式地初始化类地成员.构造函数初始化列表以一个冒号开始,接着是以逗号分隔地数据成员列表,每个数据成员后面跟一个放在括号中地初始化式.例如:{: ;;构造函数初始化列表(): ()() {}构造函数内部赋值(){;;}}; 从技术上说,用初始化列表来初始化类成员比较好,但是在大多数情况下,两者实际上没有什么区别.第二种语法被称为成员初始化列表,之所以要使用这种语法有两个原因:一个原因是必须这么做,另一个原因是出于效率考虑初始化列表地必要性初始化和赋值对内置类型地成员没有什么大地区别,像上面地任一个构造函数都可以.但在一些情况下,初始化列表可以做到构造函数做不到地事情:、类里面有类型地成员,它是不能被赋值地,所以需要在初始化列表里面初始化它;、引用类型地成员(也就是名字成员,它作为一个现有名字地别名),也是需要在初始化列表里面初始化地,目地是为了生成了一个其名字成员在类外可以被修改而在内部是只读地对象;、需要调用基类地构造函数,且此基类构造函数是有参数地;、类里面有其他类类型地成员,且这个“其他类”地构造函数是有参数地.举个例子:设想你有一个类成员,它本身是一个类或者结构,而且只有一个带一个参数地构造函数. { : ( ) { ... } }; 因为有一个显式声明地构造函数,编译器不产生一个缺省构造函数(不带参数),所以没有一个整数就无法创建地一个实例. * ; 出错!! * (); 如果是另一个类地成员,你怎样初始化它呢?答案是你必须使用成员初始化列表. { ; : (); };必须使用初始化列表来初始化成员() : () {……} 没有其它办法将参数传递给.情况和其实一样地道理.如果成员是一个常量对象或者引用也是一样.根据地规则,常量对象和引用不能被赋值,它们只能被初始化. 初始化列表与构造函数赋值地效率比较首先把数据成员按类型分类并分情况说明:.内置数据类型,复合类型(指针,引用)在成员初始化列表和构造函数体内进行,两者在性能和结果上都是一样地.用户定义类型(类类型)两者在结果上相同,但是性能上存在很大地差别.因为编译器总是确保所有成员对象在构造函数体执行之前初始化,所以对于用户自定义类型(类),在初始化列表中只会调用类地构造函数,在构造函数体中赋值就会先调用一次类地构造函数,然后再调用一次类地赋值操作符函数.显然后者在性能上有所损失,特别对于构造函数和赋值操作符都需要分配内存空间地情况,使用初始化列表,就可以避免不必要地多次内存分配.举个例子:假定你有一个类具有一个类型地成员,你想把它初始化为" .".你有两种选择:、使用构造函数赋值(){使用赋值操作符(); (" .");} 、使用初始化列表() : ((" .")) {} 编译器总是确保所有成员对象在构造函数体执行之前被初始化,因此在第一个例子中编译地代码将调用来初始化,这在控制到达赋值语句前完成.在第二个例子中编译器产生一个对:: ()地调用并将" ."传递给这个函数.结果是在第一个例子中调用了两个函数(构造函数和赋值操作符),而在第二个例子中只调用了一个函数.在地例子里这是无所谓地,因为缺省构造函数是内联地,只是在需要时为字符串分配内存(即,当你实际赋值时).但是,一般而言,重复地函数调用是浪费资源地,尤其是当构造函数和赋值操作符分配内存地时候.在一些大地类里面,你可能拥有一个构造函数和一个赋值操作符都要调用同一个负责分配大量内存空间地函数.在这种情况下,你必须使用初始化列表,以避免不必要地分配两次内存.在内建类型如或者或者其它没有构造函数地类型下,在初始化列表和在构造函数体内赋值这两种方法没有性能上地差别.不管用那一种方法,都只会有一次赋值发生.有些程序员说你应该总是用初始化列表以保持良好习惯,但我从没有发现根据需要在这两种方法之间转换有什么困难.在编程风格上,我倾向于在主体中使用赋值,因为有更多地空间用来格式化和添加注释,你可以写出这样地语句:;或者(, , ()); 初始化列表地成员初始化顺序初始化类成员时,是按照成员声明地顺序初始化地,而不是按照出现在初始化列表中地顺序.因为一个类可以有多个构造函数,那么初始化列表可能各有不同,但是却只有一个析构函数,析构函数地析构顺序是和构造地顺序相反地.如果按照初始化列表来初始化,而且有多个构造函数地情况下,那么析构地时候就不能确定析构地顺序.只有按照声明地顺序,无论构造函数中初始化列表是何顺序,都可以按照确定地顺序析构.保持一致性最主要地作用是避免以下类似情况地发生:{ ( , ); ; ;}; ( , ) : (), (){} 你可能以为上面地代码将会首先做,然后做,最后它们有相同地值.但是编译器先初始化,然后是,,因为它们是按这样地顺序声明地.结果是将有一个不可预测地值.有两种方法避免它,一个是总是按照你希望它们被初始化地顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明地顺序罗列这些成员.这将有助于消除混淆.b5E2R。

c++派生类的构造函数

c++派生类的构造函数

c++派生类的构造函数C++是一门面向对象的编程语言,它允许程序员使用类和对象来封装数据和行为。

在C++中,派生类是基于已存在的类(称为基类)创建的一种新类。

派生类从基类继承了数据和方法,并且还可以添加新的属性和方法。

在C++中,派生类的构造函数是指创建派生类对象时所调用的函数。

派生类的构造函数负责初始化派生类对象中从基类继承的成员和派生类自己添加的成员。

本文将详细介绍C++派生类的构造函数。

在C++中,派生类的构造函数必须调用基类的构造函数,以初始化从基类继承的成员变量。

在创建派生类对象时,首先创建基类对象,然后再对派生类对象进行初始化。

1. 构造函数必须有与类名相同的名称。

2. 构造函数可以有参数,也可以没有参数。

3. 派生类必须调用基类的构造函数,以初始化从基类继承的成员变量。

下面是一个基类Person和一个派生类Student的定义:```cppclass Person{protected:string name;int age;public:Person(){}void setName(string n){name = n;}void setAge(int a){age = a;}};在定义派生类Student的时候,通过public继承了基类Person。

此时,派生类的构造函数必须调用基类的构造函数,以初始化从基类继承的成员变量name和age。

派生类新增加了一个成员变量grade,需要在自己的构造函数中进行初始化。

派生类构造函数可以有多种调用方式,具体如下:1. 用基类构造函数初始化列表初始化派生类对象初始化列表是C++语言提供的一种简洁的初始化成员变量的语法。

它使用冒号(:)后接一个以逗号分隔的初始化列表,在其中对派生类和基类成员变量进行初始化。

下面是Student类中使用初始化列表对基类成员变量进行初始化的方法:在上面的代码中,派生类Student的构造函数使用冒号后接一个初始化列表来初始化基类成员变量name和age。

显式 调用 构造函数 初始化

显式 调用 构造函数 初始化

显式调用构造函数初始化1.引言1.1 概述在编程中,构造函数是用来创建和初始化对象的特殊成员函数。

在大多数情况下,构造函数会自动调用,以便在创建对象时执行必要的初始化操作。

然而,有时候我们可能想要显式地调用构造函数来进行特定的初始化。

显式调用构造函数的目的是为了更加灵活地控制对象的初始化过程,以满足特定的需求。

通过显式调用构造函数,我们可以传入特定的参数来对对象进行初始化,而不是仅仅依靠默认的构造函数进行初始化。

在C++中,我们可以使用构造函数的初始化列表来显式初始化对象的成员变量。

初始化列表是构造函数的一部分,在构造函数体之前使用冒号进行标识,并列出每个成员变量的初始化方式。

通过初始化列表,我们可以在构造函数中显式地设置每个成员变量的初值。

显式调用构造函数和初始化列表的使用可以提高代码的可读性和性能。

通过显式调用构造函数,我们可以清楚地指定初始化的顺序和方式,并且可以在对象创建的同时完成必要的初始化操作。

使用初始化列表可以减少不必要的临时对象的创建和销毁,提高代码的效率。

总之,显式调用构造函数和初始化列表是在C++中实现对象初始化的重要技术。

通过它们,我们可以更加灵活地控制对象的初始化过程,并且提高代码的可读性和性能。

在接下来的文章中,我们将深入探讨显式调用构造函数和初始化列表的具体用法和注意事项。

1.2文章结构1.2 文章结构本文主要探讨显式调用构造函数初始化的相关内容。

在引言部分,我们将概述本文的主要内容和目的。

接下来的正文部分将详细介绍显式调用构造函数的概念以及其在初始化过程中的应用。

具体地,我们将探讨显式调用构造函数的语法和用法,并举例说明其在不同情况下的实际应用场景。

在第二节中,我们将重点介绍初始化的概念及其与显式调用构造函数的关系。

我们将讨论不同的初始化方式,包括默认初始化、直接初始化以及拷贝初始化,并分析它们与显式调用构造函数的异同点。

我们还将探索显式调用构造函数在初始化过程中的优势和适用性,并与其他初始化方法进行比较。

C++类构造函数初始化列表

C++类构造函数初始化列表

C++类构造函数初始化列表构造函数初始化列表以⼀个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后⾯跟⼀个放在括号中的初始化式。

例如:class CExample {public:int a;float b;//构造函数初始化列表CExample(): a(0),b(8.8){}//构造函数内部赋值CExample(){a=0;b=8.8;}};上⾯的例⼦中两个构造函数的结果是⼀样的。

上⾯的构造函数(使⽤初始化列表的构造函数)显式的初始化类的成员;⽽没使⽤初始化列表的构造函数是对类的成员赋值,并没有进⾏显式的初始化。

初始化和赋值对内置类型的成员没有什么⼤的区别,像上⾯的任⼀个构造函数都可以。

对⾮内置类型成员变量,为了避免两次构造,推荐使⽤类构造函数初始化列表。

但有的时候必须⽤带有初始化列表的构造函数:1.成员类型是没有默认构造函数的类。

若没有提供显⽰初始化式,则编译器隐式使⽤成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使⽤默认构造函数将会失败。

2.const成员或引⽤类型的成员。

因为const对象或引⽤类型只能初始化,不能对他们赋值。

初始化数据成员与对数据成员赋值的含义是什么?有什么区别?⾸先把数据成员按类型分类并分情况说明:1.内置数据类型,复合类型(指针,引⽤)在成员初始化列表和构造函数体内进⾏,在性能和结果上都是⼀样的2.⽤户定义类型(类类型)结果上相同,但是性能上存在很⼤的差别。

因为类类型的数据成员对象在进⼊函数体前已经构造完成,也就是说在成员初始化列表处进⾏构造对象的⼯作,调⽤构造函数,在进⼊函数体之后,进⾏的是对已经构造好的类对象的赋值,⼜调⽤个拷贝赋值操作符才能完成(如果并未提供,则使⽤编译器提供的默认按成员赋值⾏为)Note:初始化列表的成员初始化顺序:C++初始化类成员时,是按照声明的顺序初始化的,⽽不是按照出现在初始化列表中的顺序。

Example:class CMyClass {CMyClass(int x, int y);int m_x;int m_y;};CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y){}你可能以为上⾯的代码将会⾸先做m_y=I,然后做m_x=m_y,最后它们有相同的值。

C++语言程序设计试题和答案

C++语言程序设计试题和答案

C++语言程序设计试题和答案一、单选题(把正确选项写在各小题后面的括号内,每小题1分,共20分)1. 已知: int x, y;下列表达式中,非法的是( ) 。

A. x%yB. y=-xC. --(x*y)D. x+=y2. 已知: int x, y;下列循环的执行次数是( ) 。

for(x(0),y(0);!y&&x<=5;x++,y++){ … }A.1 B. 2 C. 5 D. 无限3. 己知: int a ,*p; 下列操作中,非法的是( ) 。

A. p=aB. p=&aC. *p=9D. p!=a;4.下列有关语句的描述中,错误的是()。

A.条件语句中,else子句可以有0个或1个B.Continue语句只能出现在循环体内C.Switch语句不能出现在循环体内D.循环语句可以自身嵌套,也可以相互嵌套5.下列关于构造函数的描述中,错误的是()。

A.构造函数是没有名字的B.构造函数是可以重载的C.构造函数是系统自动调用的D.构造函数是可以没有参数的6.下列关于运算符new的描述中,错误的是()。

A.new运算符用来创建动态对象B.new运算符用来创建堆对象时可进行初始化C.用new运算符创建的对象可用delete运算符释放D.用new运算符可创建对象数据,并可同时进行初始化7.下列关于公有继承方式下基类成员在派生类中访问性的描述,其中错误的是()。

A.基类中私有成员在派生类中为私有成员B.基类中公有成员在派生类中为公有成员C.基类中保护成员在派生类中为保护成员D.派生类中成员函数不能访问基类私有成员8.类的构造函数的成员初始化列表中,不可以包含()。

A.基类构造函数B.子对象类的构造函数C.静态数据成员初始化D.常数据成员的初始9.已知:B类是A类的公有继承的派生类,下列描述中,错误的是()。

A.A类的对象就是B类的对象B.B类对象可以给A类对象赋值C.B类对象的地址值可以给A类对象指针赋值D.B类对象可以给A类对象引用赋值10.下列关于派生类定义格式的描述中,错误的是()。

C#类和继承(扩展方法、静态类、抽象类、构造函数初始化等)

C#类和继承(扩展方法、静态类、抽象类、构造函数初始化等)

C#类和继承(扩展⽅法、静态类、抽象类、构造函数初始化等)分部类和分部⽅法namespace jiayou1{/*定义分部类需要注意以下问题:在申明和实现申明中都必须包含上下⽂关键字partial,直接放在关键字void的后⾯,返回类型必须是void参数列表不能包含out参数*/partial class MyClass{partial void PrintSum(int x,int y);//申明分部⽅法,没有实现部分public void Add(int x,int y){PrintSum(x,y);}}partial class MyClass{partial void PrintSum(int x, int y)//申明分部⽅法,实现部分{Console.WriteLine("sum is {0}",x+y);}}class Program{static void Main(string[] args){var mc = new MyClass();mc.Add(5,6);}}}类继承namespace类继承{class SomeClass//基类{public string Field1 = "base class field";public void Method1(string value){Console.WriteLine("base class--Method1:{0}",value );}}class OtherClass : SomeClass//派⽣类{public string Field2 = "derived class field";public void Method2(string value){Console.WriteLine("Derived class--Method2:{0}",value);}}class Program{static void Main(string[] args){OtherClass oc = new OtherClass();oc.Method1(oc.Field1 );//以基类字段为参数的基类⽅法oc.Method2(oc.Field2);//以基类字段为参数的基类⽅法oc.Method1(oc.Field2);//以基类字段为参数的基类⽅法oc.Method2(oc.Field1);//以基类字段为参数的基类⽅法}}}隐藏基类成员namespace隐藏基类的成员{/*要隐藏⼀个继承的数据成员,需要声明⼀个新的new相同的类型成员,并使⽤相同的名称要让编译器知道你在故意隐藏继承成员,使⽤New修饰符。

C++实验报告之静态成员、运算符重载

C++实验报告之静态成员、运算符重载

题目1:定义一个复数类,通过重载运算符:*,/,直接实现二个复数之间的乘除运算。

编写一个完整的程序,测试重载运算符的正确性。

要求乘法“*”用友元函数实现重载,除法“/”用成员函数实现重载。

源程序1/*******************第1题*******************//******************单森汉*****************//******************2012-5-1*****************/#include<iostream>using std::cout;using std::endl;class Complex{float Real, Image;public:Complex(float r=0,float i=0) { Real=r;Image=i;}void Show(){cout <<"Real="<<Real<<'\t'<<"Image="<<Image<<'\n';}friend Complex operator *(Complex &, Complex &);Complex operator /(Complex &); //重载运算符+Complex operator +( Complex &);friend Complex operator -(Complex &, Complex &);};Complex operator *( Complex &c1,Complex &c2){Complex t;t.Real=c1.Real * c2.Real - c1.Image * c2.Image;t.Image = c1.Image*c2.Real +c1.Real* c2.Image;return t;}Complex Complex::operator /(Complex &c){Complex t;t.Real =(Real *c.Real+ Image * c.Image)/(c.Real*c.Real+ c.Image * c.Image);t.Image = (Image *c.Real - Real * c.Image)/(c.Real*c.Real+ c.Image * c.Image);return t;}Complex Complex::operator + ( Complex &c){Complex t;t.Real = Real + c.Real;t.Image = Image + c.Image;return t;}Complex operator -(Complex &c1, Complex &c2){Complex t;t.Real=c1.Real-c2.Real;t.Image=c1.Image-c2.Image;return t;}void main(){Complex c1(1,2),c2(3,4),c3;c3=c1*c2;cout<<"两个复数的乘法c3=c1*c2:";c3.Show();c3=c1/c2;cout<<"两个复数的除法c3=c1/c2:";c3.Show();Complex c4(1,2),c5(3,4),c6,c7(1,2),c8(3,0),c9; c6=c4+c5;cout<<"两个复数的加法c6=c4+c5:";c6.Show();c6=c4-c5;cout<<"两个复数的减法c6=c4-c5:";c6.Show();c9=c7+c8;cout<<"一个复数与一个实数的加法c9=c7+c8:"; c9.Show();c9=c7-c8;cout<<"一个复数与一个实数的减法c9=c7-c8:"; c9.Show();}运行结果截图题目2:定义一个向量(一维数组)类,通过重载运算符实现向量之间的加法和减法。

结构体参数初始化列表

结构体参数初始化列表

结构体参数初始化列表在 C++ 中,结构体是一种重要的数据结构,它可以将多个不同类型的数据组合在一起,形成一个整体。

当我们定义一个结构体时,需要为结构体中的每个成员变量分配内存,并指定它们的类型和名称。

在初始化结构体时,我们可以使用参数初始化列表来为结构体的成员变量赋值。

参数初始化列表是一种 C++11 引入的新特性,它允许我们在定义结构体对象时,使用花括号语法为结构体的成员变量赋值。

初始化列表的语法如下:```struct 结构体名 {成员变量名 1 = 值 1;成员变量名 2 = 值 2;.....};```在参数初始化列表中,每个成员变量的值都必须指定,否则编译器会报错。

参数初始化列表中可以使用常量表达式来初始化成员变量,也可以使用字面量来初始化数组成员。

下面是一个使用参数初始化列表定义并初始化结构体的例子:```struct Person {int age;string name;};int main() {Person p1 {18, "Tom"};Person p2 {20, "Jerry"};cout << p1.age << " " << << endl;cout << p2.age << " " << << endl;return 0;}```在这个例子中,我们定义了一个名为 Person 的结构体,其中包含一个整型变量 age 和一个字符串类型变量 name。

在主函数中,我们使用参数初始化列表为两个 Person 类型的对象 p1 和 p2 分别初始化了 age 和 name 成员变量。

最后,我们输出了 p1 和 p2 的age 和 name 成员变量的值。

使用参数初始化列表来初始化结构体对象时,编译器会按照列表中的顺序依次初始化每个成员变量。

c++ 派生类构造函数

c++ 派生类构造函数

c++ 派生类构造函数C++是一种面向对象的编程语言,提供了派生类(子类)的概念,允许我们在已有类的基础上进行扩展并添加新的行为和属性。

派生类的构造函数是创建和初始化派生类对象时调用的函数。

本文将介绍C++中派生类构造函数的概念、使用方法和注意事项。

在C++中,每个类都有一个构造函数,用于创建和初始化该对象。

派生类继承了基类的成员变量和成员函数,但是派生类需要自己的构造函数来初始化它自己的成员变量。

派生类构造函数既可以调用基类构造函数来初始化基类成员变量,也可以初始化自己的成员变量。

派生类构造函数有以下特点:1.派生类构造函数的函数名必须与类名相同。

2.派生类构造函数必须在其成员初始化列表中调用基类构造函数。

3.派生类构造函数只能直接或间接调用基类构造函数,不能调用基类的析构函数。

二、派生类构造函数使用方法1.调用基类构造函数派生类默认情况下会继承基类的构造函数,因此,派生类的构造函数需要在函数体前调用基类的构造函数,以初始化基类成员变量。

调用基类构造函数的写法为构造函数名::构造函数名(参数列表) : 基类构造函数名(参数列表)。

例如下面的代码:```class Base {public:Base(int n) {this->n = n;}protected:int n;};在上面的代码中,Derived继承了Base的属性和方法,但Base的构造函数需要通过Derived的构造函数进行调用。

在Derived构造函数的函数体中,我们可以定义自己的成员变量,并为它们赋初值。

2.初始化自己的成员变量除了要调用基类的构造函数外,派生类的构造函数还要初始化自己的成员变量。

派生类构造函数的成员初始化列表用于初始化自身的成员变量。

例如:在上面的代码中,Derived类有一个名为m的私有成员变量,会在Derived的构造函数中被初始化。

1.在执行派生类构造函数时,如果基类和派生类都有默认构造函数,则默认情况下会先调用基类的默认构造函数,再调用派生类的默认构造函数。

c中冒号和双冒号的用法

c中冒号和双冒号的用法
2、类构造函数(Constructor)的初始化列表 先说下什么叫构造函数吧(是不是啰嗦了?C构造函数,就是与类同名的函数,它与普通函数的区别在于,它没有返回类型。 在构造函数后面紧跟着冒号加初始化列表,各初始化变量之间以逗号(,)隔开。下面举个例子。 class myClass { public : myClass();// 构造函数,无返回类型,可以有参数列表,这里省去 ~myClass();// 析构函数
::标示符(i); 只表示在以下代码中使用的标示符 i 是 abc 中的。 如果你要使用 abc 中的多个标示符的话,你就只能用 using abc::a; using abc::b; using abc::c; ... 等一个一个列举出来! 当然用 using 语句是比较方便的 但是不安全 (1)using namespace;万一不同的两个命名空间中使用了同名的标示符,系统则不能判断, 这个标示符是属于哪个命名空间的; (2)using abc::;万一你的程序中也用到了一个函数(函数名与 abc 中的这个函数同名),那 么系统也不能判断你使用的是 abc 中的那个函数,还是本程序中的那个函数; 最安全的办法(当然也是最繁琐的) 就是,每当你用到一个变量(函数...)时,你都要明确他的来历(即属于哪个命名空间)除非它 没有命名空间 例如: #include &lt;iostream&gt;
所以 上面的代码就等同于 #include &lt;iostream&gt; using std::cout; using std::endl; int main () { cout &lt;&lt; &quot;hello, world!&quot; &lt;&lt; endl; }

C++中const 、static、 static const和const static的初始化以及修改问题

C++中const 、static、 static const和const static的初始化以及修改问题

C++中const 、static、static const和const static的初始化以及修改问题本人系C++初学者,很渣很菜鸟,这也是本人写的第一篇有关C++的博文,说是写,不如说是对网络上高人博文的综合与整理。

(当然,凡是本文涉及到别人博客内容的,都将附上网址链接。

)写这篇博文的目的,是希望对相关知识做出尽可能全面、详尽、简易的解释,以供像我一样的初学者参考,同时也希望得到高人的批评与指正,以此来提高自己。

另外,为保证文章的针对性,同时也限于本人水平,本文只对相关类型的数据做出讨论,并不涉及函数的讨论。

下面,是我的一些整理与见解。

一、const、static、static const、const static变量的初始化Ⅰ.const的初始化(1)只有这一种情况const变量可以不在声明的同时定义,那就是const变量作为类的数据成员出现时。

例如:class Myclass{cons int a;//注意,在任何情况下,const int a与int const a等价,只不过人们习惯写前者};但要注意,这样做是毫无意义的,只是编译能够通过罢了,int const a什么也做不了,因为它没有值。

(2)凡是在函数(包括类中的,main函数及其它)中,const常量必须在声明时初始化,这是因为const被视为常量。

例如:class Myclass(){public:int test(){const int b;……}当输入int const b;一句时,在分号下面出现红色下划线,鼠标移到该处,出现报错:Error:常量变量“b”需要初始值设定项。

(注意,在本文中,以后这种情况我们简称为“下划线报错”)在main函数、全局函数中情况相似。

(3)作为全局常量在全局中写const int b;// 即不在类中,不在函数中写代码时不会出现下划线,但编译时报错:如果不是外部的,则必须初始化常量对象。

c语言子类调用父类构造函数

c语言子类调用父类构造函数

c语言子类调用父类构造函数
c语言中,子类调用父类构造函数,一般采用两种方式:
1. 使用基类构造函数调用:
子类通过声明基类的构造函数,并调用该构造函数,完成父类的构造,被称为使用基类构造函数调用。

这样的优缺点分别:
优点:简单、直观,使用相对简单,效率高,功能强大;
缺点:需要将基类的构造函数的参数准确的传入子类,容易出现参数传入错误或者参数当不凑。

2. 使用继承初始化列表调用:
使用继承初始化列表调用,是指在子类构造函数中,使用冒号将基类构造函数与子类定义相结合,用继承初始化列表完成父类的构造。

这样的优缺点:
优点:不需要手动声明和调用父类的构造函数,使用统一初始化方便;
缺点:父类构造函数只能被调用一次,无法重用,且不能实现多继承。

总之,使用基类构造函数调用比使用继承初始化列表调用有更多的优势,且在实际开发过程中也更加常用。

C++面试必考问题(类)C++中class和struct 的区别,构造,析构,拷贝构造,空类,重载

C++面试必考问题(类)C++中class和struct 的区别,构造,析构,拷贝构造,空类,重载



8.4构造函数和析构函数
面试例题11:静态成员的使用



#include<iostream> using namespace std; class Test { public: static int i; int j; // Test(int a):i(1),j(a){} Test(int a):j(a){} void func1(); static void func2();//静态成员函数





//结构体 spt.print();//成员函数print为public,可以调用 // spt.print1();//成员函数print1为private,不能被调用 cout<<spt.x<<" "<<spt.y<<endl;//合法 return 0; } //结论:C++中的struct包含构造函数和成员函数,其实它还拥有class的其他特性, 如继承、虚函数等。 //因此C++中的struct扩充了C的struct功能。




}; //**************************指定private继承 ****************// struct SDerived2:private CBase {
};

//*****************************主函数*********************************// int main(void) { //类class CDerived1 cd1;//先调用父类构造函数,再调用自己的构造函数 CDerived2 cd2;//先调用父类构造函数,再调用自己的构造函数 // cd1.print();//默认为private继承,所以成员函数print()不能被调用 cd2.print();//指定为public继承,所以成员函数print()能被调用,显示为 CBase:print()…

C++11列表初始化

C++11列表初始化

C++11列表初始化列表初始化:1.旧语法中定义了初始化的⼏种不同形式,如下:int data = 0; //赋值初始化int data = {0}; //花括号初始化int data(0); //构造初始化int data{0}; //花括号初始化2.C++11以旧语法中花括号初始化形式为基础,设计了列表初始化语法,统⼀了不同的初始化形式。

数据类型变量{初始化列表}#include <iostream>#include <iterator>using namespace std;struct Student{char name[256];struct Date{int year;int month;int day;}bday;};class Complex{public:Complex(double r = 0, double i = 0) :m_r(r), m_i(i) {}friend ostream& operator<<(ostream &os, Complex const &that){return os << that.m_r << "+" << that.m_i << "i";}Complex(Complex const &that) :m_r(that.m_r), m_i(that.m_i){cout << "拷贝构造函数" << &that << "->" << this << endl;}private:double m_r, m_i;};int main(){int a{ 123 };cout << a << endl;double b{ 3.4567 };cout << b << endl;int c[]{ 100, 200, 300 };copy(c, c + sizeof(c) / sizeof(c[0]), ostream_iterator<decltype(*c)>(cout, ""));cout << endl;Student d{ "张飞",{ 2000, 1, 1 } };cout << << "," << d.bday.year << "-" << d.bday.month << "-" << d.bday.day << endl;Complex e{ 1.2, 3.4 };cout << e << endl;Complex *f = new Complex{ 1.2, 3.4 };cout << *f << endl;delete f;f = new Complex[3]{ { 1.1, 2.2 },{ 2.2, 3.3 },{ 3.3, 4.4 } };copy(f, f + 3, ostream_iterator<decltype(*f)>(cout, ""));cout << endl;delete[] f;cout << Complex{ 1.2, 3.4 } << endl;//Complex const (&h)[3]{{ 1.1, 2.2 }, { 2.2, 3.3 }, { 3.3, 4.4 }};Complex const h[3]{ { 1.1, 2.2 },{ 2.2, 3.3 },{ 3.3, 4.4 } };copy(h, h + 3, ostream_iterator<decltype(*h)>(cout, ""));cout << endl;Complex i = e;//Complex i = Complex(1.2, 3.4);cout << i << endl;return0;}3. 变长初始化表,initializer_list#include <iostream>#include <iterator>#include <vector>#include <map>using namespace std;class A{public:A(initializer_list<int> li){for (auto it = li.begin(); it != li.end(); ++it){m_vi.push_back(*it);}}friend ostream &operator<<(ostream &os, A const &that){copy(that.m_vi.begin(), that.m_vi.end(), ostream_iterator<decltype(that.m_vi[0])>(os, ""));return os;}private:vector<int> m_vi;};int average(initializer_list<int> scores){if (!scores.size())return0;int sum = 0;for (auto it = scores.begin(); it != scores.end(); ++it)sum += *it;return sum / scores.size();}int main(){char const *a[]{ "张飞", "赵云", "关⽻", "黄忠", "马超" };copy(a, a + sizeof(a) / sizeof(a[0]), ostream_iterator<decltype(a[0])>(cout, ""));cout << endl;vector<const char *> b{ "张飞", "赵云", "关⽻", "黄忠", "马超" };copy(b.begin(), b.end(), ostream_iterator<decltype(b[0])>(cout, ""));cout << endl;map<const char *, int> c{ { "张飞", 100 },{ "赵云", 50 },{ "关⽻", 25 } };for (auto it = c.begin(); it != c.end(); ++it)cout << it->first << ":" << it->second << endl;/*for (map<const char *, int>::iterator it = c.begin(); it != c.end(); ++it)cout << it->first << ":" << it->second << endl;*/A a1{ 1, 3, 5, 7, 9 };cout << a1 << endl;A a2{ 2,4,6,8,10 };cout << a2 << endl;int d = 60, e = 70, f = 80;cout << average({ d,e,f }) << endl;cout << average({ 50,d, e, f,90 }) << endl;getchar();return0;}4.聚合类型(4.1)任意类型的数组(4.2)满⾜特定条件的类:a ⽆⾃定义的构造函数b ⽆私有或者保护的⾮静态成员变量c ⽆基类d ⽆虚函数e ⽆通过“=”或者“{}”在类声明部分被初始化的⾮静态成员变量(4.3)聚合类型的元素或者成员可以是聚合类型也可以是⾮聚合类型(4.4)对聚合类型使⽤列表初始化,相当于对其中的元素逐⼀初始化,⽽对⾮聚合类型使⽤列表初始化,相当于⽤列表初始化的值作为参数,调⽤相应的构造函数。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C++类构造函数初始化列表
初始化列表的定义
在使用C++编程的过程当中,常常需要对类成员进行初始化,通常的方法有两种:一种是构造函数内对类的成员赋值,一种则是使用初始化列表的构造函数显式的初始化类的成员。

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。

例如:
class CExample {
public:
int a;
float b;
//构造函数初始化列表
CExample(): a(0),b(8.8) {}
//构造函数内部赋值
CExample()
{
a=0;
b=8.8;
}
};
从技术上说,用初始化列表来初始化类成员比较好,但是在大多数情况下,两者实际上没有什么区别。

第二种语法被称为成员初始化列表,之所以要使用这种语法有两个原因:一个原因是必须这么做,另一个原因是出于效率考虑
初始化列表的必要性
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。

但在一些情况下,初始化列表可以做到构造函数做不到的事情:
1、类里面有const类型的成员,它是不能被赋值的,所以需要在初始化列表里面初始化它;
2、引用类型的成员(也就是名字成员,它作为一个现有名字的别名),也是需要在初始化列表里面初始化的,目的是为了生成了一个其名字成员在类外可以被修改而在内部是只读的对象;
3、需要调用基类的构造函数,且此基类构造函数是有参数的;
4、类里面有其他类类型的成员,且这个“其他类”的构造函数是有参数的。

举个例子:设想你有一个类成员,它本身是一个类或者结构,而且只有一个带一个参数的构造函数。

class CExampleOld { public: CExampleOld(int x) { ... } };
因为CExampleOld有一个显式声明的构造函数,编译器不产生一个缺省构造函数(不带参数),所以没有一个整数就无法创建CExampleOld的一个实例。

CExampleOld* pEO = new CExampleOld; // 出错!!
CExampleOld* pEO = new CExampleOld(2); // OK
如果CExampleOld是另一个类的成员,你怎样初始化它呢?答案是你必须使用成员初始化列表。

class CExampleNew { CExampleOld m_EO; public: CExampleNew(); };
// 必须使用初始化列表来初始化成员m_EO
//CExampleNew::CExampleNew() : m_EO(2) {……}
没有其它办法将参数传递给m_EO。

情况3和4其实一样的道理。

如果成员是一个常量对象或者引用也是一样。

根据C++的规则,常量对象和引用不能被赋值,它们只能被初始化。

初始化列表与构造函数赋值的效率比较
首先把数据成员按类型分类并分情况说明:
1.内置数据类型,复合类型(指针,引用)
在成员初始化列表和构造函数体内进行,两者在性能和结果上都是一样的
2.用户定义类型(类类型)
两者在结果上相同,但是性能上存在很大的差别。

因为编译器总是确保所有成员对象在构造函数体执行之前初始化,所以对于用户自定义类型(类),在初始化列表中只会调用类的构造函数,在构造函数体中赋值就会先调用一次类的构造函数,然后再调用一次类的赋值操作符函数。

显然后者在性能上有所损失,特别对于构造函数和赋值操作符都需要分配内存空间的情况,使用初始化列表,就可以避免不必要的多次内存分配。

举个例子:假定你有一个类CExample具有一个CString类型的成员m_str,你想把它初始化为"Hi,how are you."。

你有两种选择:
1、使用构造函数赋值
CExample::CExample()
{
// 使用赋值操作符// CString::operator=(LPCTSTR);
m_str = _T("Hi,how are you.");
}
2、使用初始化列表
CExample::CExample() : m_str(_T("Hi,how are you.")) {}
编译器总是确保所有成员对象在构造函数体执行之前被初始化,因此在第一个例子中编译的代码
将调用CString::Cstring来初始化m_str,这在控制到达赋值语句前完成。

在第二个例子中编译器产生一个对CString:: CString(LPCTSTR)的调用并将"Hi,how are you."传递给这个函数。

结果是在第一个例子中调用了两个CString函数(构造函数和赋值操作符),而在第二个例子中只调用了一个函数。

在CString的例子里这是无所谓的,因为缺省构造函数是内联的,CString只是在需要时为字符串分配内存(即,当你实际赋值时)。

但是,一般而言,重复的函数调用是浪费资源的,尤其是当构造函数和赋值操作符分配内存的时候。

在一些大的类里面,你可能拥有一个构造函数和一个赋值操作符都要调用同一个负责分配大量内存空间的Init函数。

在这种情况下,你必须使用初始化列表,以避免不必要的分配两次内存。

在内建类型如ints或者longs或者其它没有构造函数的类型下,在初始化列表和在构造函数体内赋值这两种方法没有性能上的差别。

不管用那一种方法,都只会有一次赋值发生。

有些程序员说你应该总是用初始化列表以保持良好习惯,但我从没有发现根据需要在这两种方法之间转换有什么困难。

在编程风格上,我倾向于在主体中使用赋值,因为有更多的空间用来格式化和添加注释,你可以写出这样的语句:
x=y=z=0;
或者
memset(this, 0, sizeof(this));
初始化列表的成员初始化顺序
C++初始化类成员时,是按照成员声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。

因为一个类可以有多个构造函数,那么初始化列表可能各有不同,但是却只有一个析构函数,析构函数的析构顺序是和构造的顺序相反的。

如果按照初始化列表来初始化,而且有多个构造函数
的情况下,那么析构的时候就不能确定析构的顺序。

只有按照声明的顺序,无论构造函数中初始化列表是何顺序,都可以按照确定的顺序析构。

保持一致性最主要的作用是避免以下类似情况的发生:
class CExample
{
CExample(int x, int y);
int m_x;
int m_y;
};
CExample::CExample(int x, int y) : m_y(y), m_x(m_y){}
你可能以为上面的代码将会首先做m_y=y,然后做m_x=m_y,最后它们有相同的值。

但是编译器先初始化m_x,然后是m_y,,因为它们是按这样的顺序声明的。

结果是m_x将有一个不可预测的值。

有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。

这将有助于消除混淆。

相关文档
最新文档