C++面向对象
C++面向对象
一、C和C++关于数据和函数


不同类型的数据创建相应的变量,函数就是处理这些变量。
C中的数据是共享的,其他的函数也可以处理这些数据。
C++将数据和函数包起来,放在类或结构体里面,以此为整体创建对象。
二、Object Based(基于对象) 和 Object Oriented(面向对象)
Object Based:面对的是单一class的设计
Object Oriented: 面对的是多重classes的设计,classes和classes之间的关系
三、Classes的两个经典分类
1.不包含指针的class
2.包含指针的class
四、C++代码的基本形式

延伸文件名不一定是.h或.cpp,也可能是.hpp或其他或甚至无延伸名(看平台)。
五、头文件
1.头文件中的防卫式声明

程序第一次include时,如果没有定义过_ COMPLEX _,就定义,否则不进入这个头文件内容。
因为可能很多程序都会用到这个头文件,使用防卫式声明的目的就是避免重复引入。
2.头文件的布局

六、类的声明和定义
1.类模板template

template的作用是在类声明的时候不完全写死一些类型,而是在创建对象的时候指定类型。这样的好处是类更通用。
2.inline(内联)函数
C/C++为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。
1 |
|
for循环中num_check(i)会被替换为 (i%2>0)?”奇”:”偶”,这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。
函数如果在类体中定义完成,就可能是inline函数(是否是inline函数得看编译器,有些复杂的函数编译器无法将其转为inline函数)。
如果函数在类体中声明,在类外定义,也可以在定义该函数时,在该函数前面加inline关键字;告诉编译器尽量将其转为inline函数。
3.访问级别
public: 公有,外界可以通过类对象的成员访问符访问
private: 私有,外界无法直接通过类对象的成员访问符来访问
protected:
4.构造函数
在创建对象时,会自动调用构造函数。
构造函数的写法(2种)


默认实参不只是构造函数可以有,其他函数也可以这样写。
初始值和初始列这种写法只有构造函数才有,写构造函数时也建议用这种写法,这种写法相比于赋值的写法效率高一些。
函数重载-overloading,之所以可以重载,是因为函数编译后的实际名称并不是我们所写的那个函数名,而是加了对形参,返回值等的编码构成的函数名。
构造函数可以有很多个,但是要注意,在下面这种情况下不能共存

即其中1的构造函数有默认实参且默认实参和2相同,就不能有2这个构造函数。因为在创建对象时,如果没有实参,编译器不知道要调用哪个构造函数。

构造函数放在private的情况
单例模式

使用

6.常量成员函数

不改变数据的函数
如果不加const,在下面这种情况会报错

7.参数传递(值传递和引用传递)
引用传递的速度和指针传递差不多,效率一般比值传递高,所以尽量传引用。如果不希望对方改,可以加const


operator表示操作符重载,operator += 表示重载+=操作符
8.返回值传递(值传递和引用传递)
尽量采用引用传递
9.友元(friend)

友元函数可以自由取得private的成员变量
友元打破了封装
相同class的各个对象互为友元

10.什么时候可以使用引用传递,什么时候可以返回引用

如果return的变量是在函数内部创建的,就不能使用引用返回,因为局部变量在函数结束时会自动销毁。
11.操作符重载(成员函数,有this)



this不一定要显式写出。
返回引用时,传递者无需知道接收者是以引用形式接收,return时把值return出去。如果是指针,要把指针指向的object return出去
上面的operator +=返回值可以是void吗?如果是void会有问题,比如使用下面这个连续+=的时候会出错

12.操作符重载(非成员函数,无this)
全局函数,不能return引用,因为返回的是在函数内部创建的

<<不能使用成员函数的方法重载,因为<<作用于cout,而cout是多年前就已经写好的,是ostream类型
13.临时对象 typename();
如上图的complex(real(x)+y, imag(x));创建了complex的临时对象

黄色部分为临时对象
14.拷贝构造、拷贝赋值、析构函数



有指针成员的类一定要有拷贝构造和拷贝赋值

一定要在拷贝赋值中检查是否自我赋值

七、堆、栈与内存管理
1.栈(stack)
stack是存在于某作用域的一块内存空间。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址,在函数本体内声明的非static变量,其所使用的内存块都取自stack
栈中对象的生命周期:离开作用域就会自动销毁。
2.堆(heap)
heap是指由操作系统提供的一块全局内存空间,程序可动态分配从其中获得若干区块。
常见的动态分配堆内存的方式,malloc、new
从堆中申请的内存要手动释放
堆中对象的生命周期:如果用new申请的,在delete之后结束,如果是用malloc申请的,free之后结束
使用new时,编译器的执行步骤是先分配内存,再调用构造函数

使用delete时,先调用析构函数,再释放内存

在VC中动态分配内存时具体得到的内存块

除了Complex对象所占的8个字节之外,还额外分配了一些内存,红色部分是cookie(记录整块的大小),灰色部分是调试模式下分配的内存块,每格4个字节。因为在VC下分配的内存必须是16的倍数,所以增加了三个pad填充(青色部分),最后分配的内存大小是52+12=64字节。
64的16进制表示为40,cookie是41,其中1表示的是这块内存操作系统分配出去了
在非调试模式下

VC中动态分配所得的数组内存分配情况

灰色部分是Complex,除了上面介绍的外,还会增加一个内存块还记录数组的元素个数
array new一定要用array delete

八、静态(static)
静态函数没有this

静态数据成员要在类外部定义,非静态数据成员不能在类外部定义。
单例模式的设计
这种方式不允许外界来创建对象,但是把static A a放在声明里有个缺点是如果没用到这个类也会占用内存

更优的方式

九、函数模板template
和类模板不同在于函数模板不用显式的指出类型,编译器会对函数模板进行自动推导

十、命名空间namespace
可以防止重名,将所有东西放在一个namespace里面

命名空间的使用,三种方式

十一、类与类之间的关系(Composition、Delegation、Inheritance)
1.Composition(组合),表示has-a
类和类之间的关系,一个类里面有另一个类的变量
适配模式Adapter


Composition关系下的构造和析构

构造由内而外
Container的构造函数首先调用Component的default构造函数,然后才执行自己
析构由外而内
Container的析构函数首先执行自己的析构函数,然后才调用Component的析构函数
2.Delegation(委托)
通过指针连接,和Composition的区别在于委托是通过指针连接,连接的两个类生命周期不一样

可以使用引用计数
3.Inheritance(继承),表示is-a
is-a:是一种,比如小轿车是一种车

继承关系下的构造和析构

父类的构造函数必须是virtual函数(虚函数)
构造由内而外
子类的构造函数首先调用父类的default构造函数,然后才执行自己
析构由外而内
子类的析构函数首先执行,再调用父类的析构函数
4.Inheritance + Composition
继承和组合关系下的构造和析构

第一种情况
构造函数调用顺序:Base-Component-Derived
析构函数调用顺序:Derived-Component-Base
第二种情况
构造函数调用顺序:Component-Base-Derived
析构函数调用顺序:Derived-Base-Component
5.Delegation + Inheritance
功能最强大的组合。
可以解决的问题
一份数据,多种不同呈现方式的场景,改变数据时,所有相关呈现方式的内容也跟着变

设计模式之观察者模式

设计模式之组合模式

设计模式之原型模式
创建未来的class

十四、虚函数
非虚函数:不希望子类重新定义(override,覆写)它
虚函数:希望子类重新定义(override,覆写)它, 它已有默认定义
纯虚函数(pure virtual):希望子类一定要重新定义(override,覆写)它(子类不重新定义会报错),它一般没有默认定义(但可以有默认定义)

设计模式之模版模式
父类方法中一些函数内部调用函数的实现由子类来实现
