c++面向对象高级编程(上)
防卫式声明:
#ifndef_xxxxxxx_
#define_xxxxxxx_
...
#endif
inline
只是一种建议,编译器并不一定会执行
构造函数
初始化列表的方法设置参数
complex (double r=0,double i=0): re (r), im (i){ }----
//拥有更高的效率
轻易不要用友元函数,打破了封装
相同class的各个object互为friends
-----------------------------------------------------
小结:
- 数据尽量在private
- 参数尽可能以reference传递
- 返回值尽量以reference传递
- 类的本体函数应该加const就要加
- 构造函数尽量用初始化列表的方法
###一种不可以传reference的情况: 函数里面local的东西
eg:一个函数处理a+b ,最后的结果返回到a或b时,可以用reference传递
但是,如何要返回在函数内部运算中创建的c时,则不能使用reference的方式进行传递
因为在函数执行完毕后c的本体会被消解,而依赖于本体的引用也不复存在。
操作符重载(有成员函数和非成员函数两种)与临时对象:
所有的成员函数一定带着一个隐藏的参数----this(是一个指针
指向这个调用这个函数的对象本身
return by reference 语法分析
传递者无需之调接受者是以reference形式来接收
单纯的+=并不在乎最后返回什么 ,为什么要用complex& ?
想一想如果需要 c3+=c2+=c1;
####设计操作符重载时一定要多考虑使用者连续使用的情况(如上例)
2.操作符重载-非成员函数的写法
为了应付用户三种可能的写法,需要开发三个函数:
c2=c1+c2;
c2=c1+5;
c2=7+c1;
分别对应不同额度操作符重载函数。
Q&A:
这三个函数为什么不能 return by reference ?
因为加的结果是在函数里面创建出来的。---在这里是创建了一个temp object
(临时对象)。
temp object--
typename( )------ eg: complex( ) 直接类名加参数 不生成具体对象,生命周期在下一行就会结束。
临时对象是一种特殊语法,一般人少用,但标准库用的较多。
<<操作符重载的特别之处,不能用成员函数的方式进行重载,因为成员函数的操作符重载必须作用于左边
而左边是cout,而它不能识别这种复数类。
解刨cout--
注意操作符不能使用const,因为在输入的过程中,他的状态会不断的发生改变。
ex,这里的返回类型可以用void吗,想想为什么。
BIG Three, 拷贝构造函数,拷贝赋值函数,析构函数--三种特殊函数:
析构函数--构造函数中如果有动态分配内存,必须自定义析构函数,
同时析构函数中必须释放分配的内存。
有指针时,赋值函数一定要设置自己的拷贝赋值函数,避免造成浅拷贝(如上图
此时既有内存泄露,又有两个东西指向同一个东西,十分危险!
Q&A:
那么怎么进行深拷贝呢?
0.检测自我赋值(self-assessment) 。 good robust!!!
1.delete自己
2.重新分配一个足够大的内存
3.把内容拷贝过来
字符串的output 函数,
直接把指针给ostream即可
堆、栈与内存管理
stack: 用于某个作用域的一块内存空间,自动分配,自动释放
heap:又叫 system heap ,是由os提供的一块global的内存空间,程序可
dynamic allocated,从中获得若干blocks
四种object的生命周期:
- stack object 的生命期 ,作用域结束后就结束
- static local object,其生命在作用域结束后仍然存在,知道整个程序结束
- global objects , 类似static
- heap objects , 其生命周期直到被 deleted才被释放。必须主动结束,不然会导致内存泄露
new:先分配memory ,再调用ctor
delete:先调用dtor,再释放memory
动态分配所得到的内存块(memory block),in VC,不同编译器具体情况不同
分配的内存必须是16的倍数
顶部和底部是cookie,用来标记的大小(16进制),最后一个位来标记内存是否被使用
如果是1,则被使用,例如 41,40表示64个字节的内存(4*16),1表示被分配,
想想为什么最后一位可以用来表示内存的分配情况?
第二图,第四图是非调试模式下的常规概念。
字符串只占用一个指针,所以大小是四个字节,为了满足16的倍数,需要补充4个字节,
把12变为14.
动态分配得到的array。
内存泄漏不是泄漏了左边,而是右边的。
小结----构建一个类
定义成员变量;
构造函数;
big three:拷贝构造,拷贝赋值,析构(都没有const);
是否需要其余辅助成员函数;
进一步 补充 static
静态成员变量就是通过this point 来同步需要取得的不用的对象
静态成员函数没有this point ,所以它只能处理静态的成员变量数据
静态的数据在类的外部要对其进行定义,并不一定要赋初值。
静态函数的调用方式有两种:
1.通过类名直接调用; Account:: set_rate(5.0)
2.通过对象进行调用。a.set_rate(7.0)
进一步补充:把ctors放在private区 singleton
改进版:
想想好在哪里。
进一步补充:cout
组合与继承
composition(组合),表示has-a
我里面有一个另一种东西
adapter-一种设计模式,封装改造之前的东西,使其功能
delegation 委托--compositon by reference
有这个指针并不一定代表此刻已经创建了这样一个对象,只是在需要的时候才会创建
并调用。所以叫通过引用进行组合。
piml point to implement
copy on write
在上图中,如果a要修改hello 里的内容,编译器并不会让他直接修改,而是先copy一份一样的
然后让他基于这个副本进行修改。
Inheritance继承,表示is-a
构造的时候先父,后他,最后是自己。
虚函数与多态
虚函数 inheritance with virtual functions
no-virtual fun :不希望子类重新定义
virtual fun:希望子类在需要的时候重新定义
pure virtual fun:子类必须重新定义才可以使用
template method ,设计模式的一种,父类中有一些方法不知道如何实现,便设计为虚函数,
留待子类去实现。 框架设计中比较常见。
delegation+inheritance
Observer的设计模式就是用delegation+inheritance所实现的
composite(设计模式) -父类指针指向子类对象
prototype
problem:父类不知道子类长什么样,因此不知道如何去创建
solution:子类创建一个静态的自己,交给父类让他能够知道自己
又一个私有的构造函数,并重写一个clone函数,方便父类创建自己
Q&A:
Q:为什么还有一个protected的构造函数?
A:因为第一个构造函数构造的时候,因为需要调用addPrototype(),让父类存储这种类型,
而之后clone的时候并不需要,所以需要另一个构造函数,并通过增加一个参数(这个参数并没有用)
的方式来作以区分。