目录
[1.1 函数体内赋值](#1.1 函数体内赋值)
[1.2 初始化列表](#1.2 初始化列表)
[1.3 explicit关键字](#1.3 explicit关键字)
[2.1 概念](#2.1 概念)
[2.2 特性](#2.2 特性)
[3.1 友元函数](#3.1 友元函数)
[3.2 友元类](#3.2 友元类)
1、再谈构造函数
1.1 函数体内赋值
在前面我们学到的构造函数中,都是在定义对象时,将成员变量先定义出来,然后再赋值给他们

但若是类中的成员变量比较特殊,像引用、const、自定义类型(且该类没有默认构造函数),若有这三者其一,都是必须在定义的同时就赋值,不能像上面一样先定义,然后再赋值
1.2 初始化列表
上面的这种构造函数称为函数体内赋值,为了适应这三种情况,下面的这种构造函数成为初始化列表

1、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2、类中包含以下的成员变量,必须放在初始化列表中初始化:
a. 引用成员变量
b. const成员变量
c. 自定义类型成员变量(且该类没有默认构造函数时)
原因:引用和const都必须在定义时就初始化
若对类进行初始化,那么必然会去调用类的构造函数,而若这个类没有默认的构造函数,也就是说要取调用这个类的构造函数,必须传参(前面也有说过这种情况下使用之前的函数体内赋值会报错),所以必须使用初始化列表来传一个值过去(也不一定是一个值,要看这个类的构造函数需要几个参数),让其能够调用类中的构造函数
当然,初始化列表也是最好使用全缺省


成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关,实际中,建议声明和定义的顺序写成一样,这样不容易出错

答案是D,因为在类中,是先定义了_a2,所以初始化列表中也会先初始化_a2
1.3 explicit关键字
构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。接收单个参数的构造函数具体表现:
-
构造函数只有一个参数
-
构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
-
全缺省构造函数
C++98在类型转换时,只支持构造函数只有一个参数的情况

若想关闭掉这个隐式类型转换的功能,可以在构造函数前加explicit

在C++11中,加入了针对构造函数有多个参数的情况下的做法

在构造函数前加上explicit的效果是一样的
2、static成员
2.1 概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化(因为静态成员变量在程序刚开始运行时就有了,也就是对象还没创建时就有了,所以要在类外全局的地方定义,不能在构造函数中定义,构造函数是创建对象时才调用)
面试题:实现一个类,计算程序中创建出了多少个类对象。
解题关键:对象一定是由构造函数或拷贝构造产生的

此时会有一个问题,就是谁都可以修改n的值(缺少封装)
若将n定义为成员变量,那么此时那个对象调用了构造函数或拷贝构造,++的就是这个对象中的n
所以应该将n定义为成员变量,且前面加上static

但是此时依然不是很完美,因为获取n并不方便,所以可以将GetN也定义成static,这样可以直接A::GetN()访问n,不用再通过对象(原因是static成员函数没有this指针,所以不使用对象就可以直接调用),也正因为static没有this指针,所以不能访问非静态的成员(成员函数和成员变量)

2.2 特性
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
静态成员函数没有隐藏的this指针,不能访问任何非静态成员
静态成员也是类的成员,受public、protected、private 访问限定符的限制
【问题】
静态成员函数可以调用非静态成员函数吗? 不行,因为没有this指针
非静态成员函数可以调用类的静态成员函数吗? 可以

静态的成员变量不能在定义时就给缺省值

3、友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类
3.1 友元函数
友元函数就是在类外面可以访问类中的private成员的函数

此时会发现f1函数会报错,因为d是Date类的对象,Date类中的成员变量是private的,而f1去访问了private的对象,所以报错了。这个时候的解决办法可以是将f1定义在类中,使之变成成员函数,另一种方法就是定义为友元函数

加friend后变成了友元函数,不受访问限定符的限制
访问限定符的用处
了解一点背景知识
cout是iostream中定义的一个全局类型的对象,类型是ostream
cin是iostream中定义的一个全局类型的对象,类型是istream
'<<'输出运算符,'>>'输入运算符
现在重载这两个运算符,让自定义类型不用去调用类中的Print函数就可以完成输出
此时会发现3种写法中,前两种是可以的,但是前两种的可读性都太差了,为什么第三种不行呢?
因为类中的成员函数会默认左边操作对象为this指针, 所以这里d1要放在左边,但是这样可读性太差,所以此时可以使用友元函数

此时会发现没办法连续输出,因为没有返回值,应当cout<<d1后,返回一个cout,就可以输出d2

再来实现">>"

cout和cin能自动识别类型,是因为函数重载
int x = 1;
double y = 1.11;
cout<<x; // cout.operator(&cout , x);
cout<<y; // cout.operator(&cout , y); //&cout就是this
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰(因为没有this,这里说的this是指加在函数最后面的那个this,这个this都是用来修饰this指针的)
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同
3.2 友元类
某个类想要访问另一个类

4、内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
-
内部类可以定义在外部类的public、protected、private都是可以的。
-
注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
-
sizeof(外部类)=外部类,和内部类没有任何关系。
5、再次理解面向对象
现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:
-
用户先要对现实中洗衣机实体进行抽象---即在人为思想层面对洗衣机进行认识,洗衣机有什么属性,有那些功能,即对洗衣机进行抽象认知的一个过程
-
经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、Java、Python等)将洗衣机用类来进行描述,并输入到计算机中
-
经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才能洗衣机是什么东西。
-
用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。
C++还追求高内聚、低耦合
低耦合:类与类之间的关系越少越好
高内聚:一个类里面联系的越紧密越好
6、匿名对象
通常,我们要去调用类中的函数,会去创建对象。但是如果只是需要调用一次这个函数,去创建一个对象未免有些太麻烦了,这时候可以创建匿名对象,匿名对象。
