C++面向对象高级编程(上)
01、C++编程简介
学习目标:
1.学习良好的编写C++类的形式,包括两种,分别是无指针成员类(如Complex)和有指针成员类(如String)
2.学习类之间的关系,即继承,复合和委托。
02、头文件和类的声明
1.C和C++在数据和函数的区别:
C中存在数据以及函数,函数用来处理数据。缺点是缺少关键字管理数据,数据均为全局,难以作限制
C++中通过类将数据和函数包在一起,以类为个体来创建对象。是面向对象的语言。
2.防卫式声明:
#ifndef _name_
#define _name_
//本体
#endif
优点:防止多次引入同一个文件
3.初识模板
因为可能存在不同的参数类型,所以通过template可以简化很多编写重载操作
03、构造函数
1.inline内联函数
在class内部定义的函数,自动声明为inline,在外部生命的函数在前面加上inline也可以手动声明为inline。不过具体能不能成为inline函数要看编译器。
inline的优点是相对于普通函数更快,一般来说能声明为inline都声明为inline。
2.访问级别
函数一般放在public,数据一般放在private,如果要访问数据,可以在public写单独的获取数据的函数以访问数据。
3.构造函数
学会使用初始化队列来初始化一个对象的数据
使用初始化队列初始化和在{}使用赋值语句(如下图)的区别:
不一样,前者是在初始化,第二个是在赋值,前者更加规范,而且效率更高,也会避免一些错误。比如说有些必须初始化的元素(比如引用),使用赋值可能留下隐患(引用必须在声明时初始化)。
4.函数重载
函数重载仅与以下参数有关:
1.函数名称。函数重载需要函数名称相同
2.函数参数。函数重载需要不同的函数参数列表,列表可以是类型不同,数量不同等
同时也需要注意这些问题
1.如果只有返回值类型不同则不会构成重载
2.对于每种参数,必须要有优先级区分,不然会造成二义性调用。
3.函数重载的参数顺序也会构成重载
04、参数传递与返回值
1.放在private区的构造函数
说明外界不能调用来创建这个类的对象。
如果是这样的形式,那么这个类可能会是一个单例,即这个类仅允许存在一个它的对象。
2.常量成员函数const
如果这个函数内部不会改变数据,那么这个函数应当声明为const。比如下面两个函数仅仅是读取放在private区域的值,就声明为const
如果不声明为const,那么遇到下面这种情况,即声明了这个类的一个const对象时,如果调用了非const的函数,那么就会报错,即留下了隐患。
3.参数传递:值传递和引用传递
1.为什么我们尽量使用引用传递:
如果进行值传递,那么函数会将这个需要传的值本身全部压栈,如果这个值本身很大,那么必然会花费不少时间和空间。
然而,如果进行引用传递,那么不论这个传递的值本身多大,函数也只会传递一个指针大小的值过去(引用实际上就是使用指针实现的),即使这个值本身很大,它的时间和空间复杂度也没有变化。
所以我们需要养成习惯,如果可以的话,尽量使用引用传递
2.如何判断我们能否使用引用传递
因为对引用进行修改会对初始值也造成修改,如果我们不需要对传入值进行修改的话,应该添加const。
如果确定要对引用传递进行修改,切记它也会对原本的对象进行修改。
4.返回传递:值返回和引用返回
为什么我们尽量使用引用返回:基本同参数传递。剩下原因的将在下一章操作符重载中详细介绍
5.友元Friend
1.用法及定义:在类中声明为friend的函数,可以直接访问这个类的私有成员。
2.通过相同class生成的对象,这些对象之间默认互为友元
如图,c2可以直接访问c1(即直接通过点运算符)的成员,而不是通过函数返回值获取成员
6.传值和传引用的易错点
1.应当注意返回值是不是一个临时声明在函数内部的变量
如果返回值是临时创建在函数内部的话,因为它的生命会在函数运行结束之后终止并死亡,所以如果对一个被销毁的对象传引用的话,那么必然会发生错误。
05、操作符重载和临时对象
1.成员函数this
定义:所有的成员函数一定带着一个隐藏的函数参数,叫做this。这个this永远指向函数调用者,并且无法被更改,同时也不能显式的写在参数列表当中,仅能直接使用。
上图函数实际隐藏了参数this,完整函数应该如下
如果调用这个函数重载,那么c2将作为参数this,c1将作为参数r
2.返回传递:引用传递详解
1.接收端无需知道返回的是一个引用还是值,不需要做额外修改
2.返回引用可以实现连续调用的情况。
对于函数+=,举个例子:
我们一般不会使用这个函数(+=)的返回值,那么我们为什么要设置为返回引用,而不是返回void呢?
如果不进行连续赋值,那么确实可以不用返回引用,返回void即可。但是比如说连续赋值如下:
我们希望它应该计算c2+=c1,然后将之后得到的c2进行c3+=c2的操作。即c3 = c3 + c2 + c1。
如果我们没有使用引用传递,那么最后的一开始计算c2+=c1返回的就是一个void,c3将与一个void相加,必然会报错。
这就是我们返回引用的理由
3.关于“<<”操作符的重载的补充
<<运算符会将右边的对象作用于左边的对象身上。最常见的就是
cout<<123;//在屏幕中打印123
但是这里就引出了一个问题,cout必然是标准库已经写好了的函数,如果我们制定了一个新的类,并且实例出了它的对象,那么cout无法直接输出这个函数的对象,如果我们需要打印这个对象,那么我们必须要自己为它指定一个输出函数
这里ostream不能设置为const,因为os一直在接受输入,即一直在改变,所以不能设置为const。
然后,由于我们经常这样使用
cout<< 1 << 2 << 3; //连续使用<<输出打印
我们需要设定它的输出为ostream&,以便于连续使用。
06、复习与总结:复习Complex类的实现过程(即02~05节总结)
1.使用防卫式声明
2.将函数放在public,数据放在private
3.在构造函数中,使用初始化列表来对数据进行初始化
4.尽量使用引用传递来传递参数和返回值
5.如果函数内不会对数据进行修改,那么应该声明为const
6.如果有需要,记得自己再重载<<操作符实现输出
07、三大函数:拷贝构造,拷贝赋值,析构
1.由String引入带指针类的设计
因为在设计类的时候,我们无法确定使用者需要多大的内存空间来存储这个字符。所以在设计这个类的时候,设计者选择在类中存放一个指针,然后创建对