C++笔记-类和对象(下)
1.再探构造函数
1.之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
2.每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
3.引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
4.C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
5.尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
6.初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
创建一个日期类为例,初始化列表就如上图所示,这样写和在构造函数内部写是一样的。
我们在初始化时可以只用初始化列表,也可以只在函数内部赋值,也可以两者混着用。
第二条是初始化列表的关键,这句话就涵盖了初始化列表的作用及意义。
private中的成员变量只是声明,并不是定义。
第三条就是在某些特定条件下必须使用初始化列表才行。
前两种情况在日期类中就可以展示,如:const int n和int& m,我们知道const修饰的变量和引用必须在定义时就初始化,而初始化列表就是定义成员变量,所以这两者必须用初始化列表进行定义。
而第三种情况我们用Stack类和MyQueue类来示例,可以看出目前在Stack类中我们是写了默认构造函数,所以在创建Myqueue对象时,会去调用Stack类中的默认构造函数。
所以我们在MyQueue类中不需要写构造函数,也就不需要写初始化列表就已经可以把MyQueue中的成员变量进行初始化。
但是如果在Stack类中没有默认构造函数,此时就会报错,错误原因如上图。
此时我们就需要在MyQueue类中通过初始化列表的方式来对成员变量进行初始化。
就比如这样进行初始化,这样也是对成员变量完成了初始化,代码也不会报错。
注意:以上的Stack类和MyQueue类只是为了方便理解而写的比较简单。
第四条所说的也是可以不通过初始化列表进行初始化的一种方式。
就如Date类中的n成员变量,这是我们就可以在其声明时就给它一个缺省值,这样即使不把它卸载初始化列表中,它也会以缺省值来初始化。
注意:给缺省值并不是定义,还是声明,只有在初始化列表中才是定义。
相应的,在MyQeue类中如果我们还定义了一个n这样的变量,也可以这样写。
第五条已经在以上例子中说明,这里就不过多赘述。
以上五点用图来表示就是这样,这样理解起来更加方便。
第六条也是一个要记得知识点,我们来看这样一道题。
这道题大家可以思考一下答案是什么?
我们来看一下运行结果:
所以答案选D。
这里可能会有人疑惑,这里就要用到第六条所说的先声明的先初始化。
这道题就和缺省值没关系了,因为两个变量都已经在初始化列表中了。
_a2先初始化,而参数时_a1,但此时_a1还没有初始化,所以就是随机值,也就导致_a2也是随机值。而轮到初始化_a1时,会接收我们所传的1,故结果就是D。
初始化列表总结:
无论是否显示写初始化列表,每个构造函数都有初始化列表;
无论是否在初始化列表显示初始化,每个成员变量都要走初始化列表初始化;
2.类型转换
1.C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
2.构造函数前面加explicit就不再支持隐式类型转换。
3.类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
在A这个类中,我们在main函数用两种方式来进行初始化,第一种我们都清楚,第二种就涉及到我们要讲的类型转换,这里是隐式类型转换。
我们知道在C++中类型转换会产生临时对象,而临时对象再调用拷贝构造函数使aa2完成初始化。
如果按我们上面所说,调用拷贝构造函数运行后应该会有A(const A& a)这段话,可是并没有。
这里其实是编译器会对程序进行优化,会进行直接构造,所以不会调用拷贝构造函数,如果想看到调用拷贝构造函数,需要用linux,有一条禁止编译器构造优化的指令,把指令打开再次运行程序就可以看到调用拷贝构造函数了,这里我就不演示了,感兴趣的可以去试试。
上面是单个数进行传参,下面我们来看两个及多个数进行传参的。
如果要传两个及以上,我们在传时要加上{},这样才能成功传参,单个数和多个数的区别主要就是这里。
这就是第三条所说,自定义类型对象当参数进行传参,传参过程和上面的是一样的。
注意:不能将a1直接当参数传过去,会报错,_b1的接收值只能是int类型的,所以我才用这种方法让_b1来接收。
第二条所说就是如此,如果不想实现这种隐式类型转换,就在函数前加一个explict就行,这样就用不了了。
3.static成员
1.用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化。静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
2.用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
3.静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
4.突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。
5.静态成员也是类的成员,受public、protected、private访问限定符的限制。
6.静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。
如上图所示,static修饰的类的成员要在类内声明,类外初始化,类外初始化不用加static。
第二条所说就是在类内函数前加上static即可,主要注意静态成员函数参数中不含有非静态成员函数所有的隐含形参this指针。
也是因为这个原因导致了第三条所说的静态成员函数只能访问静态成员,不能访问非静态成员。但是非静态成员函数可以访问静态成员变量和非静态成员变量。
第四条和第五条所说就如上图所示,此时的n是public,所以我们可以在外面直接访问n,而访问的方式也如第二张图所示,可以用类的对象访问,也可以通过类名来访问。
而第七条所说的我们可以用例子来演示静态变量是不计算在对象中的:
通过计算a1的大小可以得出,对象的大小只含有前三个int类型的变量,是不含有静态成员变量的,所以静态成员变量不会走初始化列表,也就不能给缺省值。
如果给了缺省值,程序就会报错。
4.友元
1.友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到一个类的里面。
2.外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,它不是类的成员函数。 3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
4.一个函数可以是多个类的友元函数。
5.友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
6.友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
7.友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
8.有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元的基本使用就如上图所示,只需要将函数第一行写入类中,再在前面加上friend关键字即可,这样就完成了友元声明,这个函数就可以访问类中受private或protect保护的成员变量了。
其实理解友元很简单,从它的关键字就可以看出,friend代表的是朋友,只有朋友才能去你家玩,也能用你家的东西,但是陌生人就不行,一样的道理。
第四条所说的就如上图所示,一个函数可以成为多个类的友元,就如你的朋友也可能是其他人的朋友,一样的道理。
第六条所说如上图所示,E是D的友元,所以E可以访问D中的私有成员,但是D不是E的友元,所以D不能访问E中的私有成员,说明友元类的关系是单向的。
第二,三,七条所说的也都是友元的知识点,这些知识点记着即可,这里就不过多演示了。
第八条所说的耦合度其实就是程序之间的关系紧密程度,在以后工作中会有说明程序的可维护性,这东西和耦合度成反比。
耦合度越高,可维护性就越低,耦合度越低,可维护性越高。为什么会成反比呢?
因为如果一个程序出了问题,如果耦合度高,那么和它有关联的程序也会相应地出问题,这时维护起来就比较麻烦,但是耦合度低的话,其他的程序基本不受影响,那么这时候只需要维护这一个程序即可。这也就是为什么友元可以用,但不宜多用。
5.内部类
1.如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
2.内部类默认是外部类的友元类。
3.内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
内部类顾名思义就是在一个类中再定义一个类,就是内部类。
内部类的基本使用就如上图所示。
通过计算也正如第一条所说,对象中同样不包含内部类,所的大小也只有外部类中的int类型的成员变量。
第二条所说也在第一张图展示,内部类默认是外部类的友元,所以可以调用外部类的私有成员。
第三条所说的也是内部类的一种用法,可以定义出一个内部类作为外部类的一个专用类,把其放在private下即可。
内部类C++中用的不多,再java中用的比较多。
6.匿名对象
1.用类型(实参)定义出来的对象叫做匿名对象,相比之前我们定义的类型对象名(实参)定义出来的叫有名对象
2.匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。
匿名对象的基本用法如上图所示,直接用类名加一个括号即可,如果要初始化,就在括号内加数据即可。
注意:传的数据类型要是类中成员变量含所有的。
如第二条所说,匿名对象的生命周期只有当前这一行,如第二张图所示,构造函数调用后接下来直接就调用析构函数了,也印证了这一点。
不过可以用const所修饰的引用来延长匿名对象的生命周期。
如第二张图所示,析构函数在程序结束后才调用,说明匿名对象的生命周期被延长了,会和引用的对象生命周期一样。
以上就是类和对象(下)的内容。