在创建对象时,如何通过调用构造函数对对象中各个成员变量进行初始化?
虽然在上述构造函数调用之后,对象已经有了一个初始值,但是不能将其称为对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。
因为初始化只能初始化一次,而构造函数体内可以多次赋值。
所以,我们想要对对象中各个变量初始化,就需要初始化列表这种方法。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或者表达式。
分析上图,我们可以看见
我在创建d1时,在构造函数没有赋值功能时,会执行初始化列表,给对象各个成员变量初始值。
一旦有了赋值功能,就会将赋的值覆盖住初始值。
看到这里,可能会有说人,初始化列表看起来挺鸡肋的,没什么作用,还不如就在构造函数里面赋值。
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.类中包含以下成员,必须在初始化列表位置进行初始化:
1)引用成员变量
2)const成员变量
3)自定义类型成员,且该类没有默认构造函数时
1.引用成员变量的情况
对于引用成员变量,在构造函数不用初始化列表,而是通过赋值来进行初赋值是行不通的。
正和此图情况一样,引用成员是必须需要初始值的。引用就好像一个别名、外号来替代你的真名,要是你连真名都没有,那该如何称呼你,别名和外号也就都失去了意义。
因此,引用成员变量必须在初始化列表中进行初始化。
2.const成员变量的情况
3.自定义类型成员,且该类没有默认构造函数
在使用D类是,会自动调用A类的构造函数,但是A没有默认构造函数,无法进行初始化,所以必须在初始化列表中进行初始化。
虽然只有上述三种情况需必须使用初始化列表,我推荐大家尽量使用初始化列表进行初始化。
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
让我们来看看这串代码,定义d1,会调用Time类中的构造函数
Time类中的构造函数是一个全缺省函数,那它还会进行初始化列表吗???
让我们进入调试一探究竟
我们可以看到t1是按初始化列表中的值进行了初始化,而没有采用函数中的缺省值。
4.成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的次序无关
答案: B
解析:
如果我们只看初始化列表的次序,就会简单的认为,a1的初始化值为x,而a2的初始值为a1的值
那a1为1,a2也为1.
但答案却并非如此,而是1和随机值。
因为成员变量在类中的次序就是其在初始化列表中的初始化顺序,与初始化列表中的次序无关
那就是先初始化a2再初始化a1,而不是先对a1进行初始化。
首先,a2初始化为a1的值,而a1并没有被初始化,也就是随机值;
接着,a1初始化为x的值,1;
explicit(显式的)的作用是"禁止单参数构造函数"被用于自动型别转换,其中比较典型的例子就是容器类型。在这种类型的构造函数中你可以将初始长度作为参数传递给构造函数。
这里的第一次构造函数调用是声明Book类的实例A,第二次构造函数调用是因为用 string类型的 “AA”作为IsSameBook函数的形参,这个函数本来的形参应该是一个Book的类对象,而类Book又满足构造函数只含有一个未初始化的形参的条件(可以含有多个已经有初始化值的形参),所以这里编译器就默认执行了一次隐式转换,把“AA”作为形参,实例化了一个Book类的临时对象object,这样,对象A就和这个object对象判断名字是否一样,从而有了两本书重名的判断。这个临时object对象在函数结束的时候就销毁了。
为了防止这种意料之外的隐式转换,在类的构造函数前加上explicit关键字,可以防止形参隐式转换为该类类型的转换
构造函数加上explicit关键字之后,使用A.IsSameBook(string(“AA”))的写法,编译器会报错。explicit的作用就是防止其他类型对该类类型的隐式转换,但仍可以以显示转换的方式调用, 如 Book B=Book(“AA”)的写法。
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;
用static修饰的成员函数,称之为静态成员函数。
静态成员变量一定要在类外进行初始化
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2.静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3.类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5.静态成员也是类的成员,受public、protected、private 访问限定符的限制
静态成员函数只能访问静态成员
可以通过类名:函数名;对象.函数名来访问,两种方法都可以。
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类
简单来说,如果我们使用在类外的一个函数时必须访问类中的成员变量,我们该怎么办
这时候需要突破封装,可以将类中的成员变量访问限定符全改成public,但这个办法太挫了,不推荐使用。
使用友元函数,所谓友元,就是朋友的意思,都成了朋友借你点东西不会有问题吧 .
在下面的运算符重载中,均需要访问类中的成员变量
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在
类的内部声明,声明时需要加friend关键字。
看一下运行结果
PS:
1)友元函数可访问类的私有和保护成员,但不是类的成员函数
2)友元函数不能用const修饰
3)友元函数可以在类定义的任何地方声明,不受类访问限定符限制
4)一个函数可以是多个类的友元函数
5)友元函数的调用与普通函数的调用原理相同
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
1)友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
2)友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
3)友元关系不能继承
在一个类里声明另一个类为友元类是一种方法,
还可以直接把一个类写在另一个类中。
就像下面这样
B天生就是A的友元类
B类的访问受A的类域和访问限定符的限制。
A不能直接访问B类中的成员变量,B可以直接访问A类中的成员变量。
匿名对象,就是没有名字的对象?
我们之前学过匿名结构体,这里的匿名对象是否与匿名结构体相似呢?
首先让我们来看看类的定义方式
这是匿名构造,我们可以这样定义对象,匿名构造的特点就是可以不用去名字
但是匿名构造出的对象生命周期只有这一行,离开这一行会自动调用析构函数。
匿名对象在某些特定的场景下用起来还是非常方便的。
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还
是非常有用的。
现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现
实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创
建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要 :1.用户先要对现实中洗衣机实体进行抽象—即在人为思想层面对洗衣机进行认识,洗衣机有什
么属性,有那些功能,即对洗衣机进行抽象认知的一个过程2.经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清
楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、
Java、Python等)将洗衣机用类来进行描述,并输入到计算机中3.经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣
机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才
能洗衣机是什么东西。4.用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了
在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义型就可以实例化具体的对象