声明定义初始化
声明式
- 所谓声明式(declaration)是告诉编译器某个东西和类型,但略去细节。
- 每个函数都声明表明了其签名式
定义式
- 定义式的任务是提供编译器一些声明式所遗漏的细节。
- 对于对象而言,定义式是编译器为其拨发内存的地点。
- 对于函数或函数模板而言,定义式提供了代码本身。
- 对类或类模板而言,定义式列出了它们的成员。
初始化
- 初始化( lnitialization)是“给予对象初值”的过程。对用户自定义类型的对象而言,初始化由构造函数执行。
拷贝构造函数发生时机
- 函数传参过程
- 添加到容器时,所以容器元素的必要条件是由拷贝构造函数,同时
函数的参数和返回值
参数顺序
- 输入参数在先, 后跟输出参数.
- 对于函数体内不应该修改的量,应该再传入时就加上const,这也能让使用者快速辨别函数功能
函数返回值
函数返回值,若以value形式返回,其经过eax寄存器,变为了右值
静态函数和静态数据成员
- 静态数据成员,多个类对象共享一份,如银行户头类,客户类,利率作为户头类的数据成员,其被N个客户类共享
静态数据成员的定义不可在头文件 - 静态函数没有this指针,所以不能处理非静态数据成员
函数重载
- 泛化,客户使用一个接口可以实现不同的功能,虽然这是在开发者进行了重载,但使用者方便
- cout为什么可以针对不同的类型进行输出,其针对不同的类型重载了不同的<<运算符
运算符重载
当存在多个运算符重载时,如*、++, 如++this,则this作为++操作符的参数使用,而不会发生运算符重载
基于对象编程
- 分为带pointer和不带pointer的类
封装
一个类的结构就是其成员数据的结构,this指向这个整体的成员数据结构
- 从语法层面,将一个事物的属性和功能用class组织起来,通过private来进行控制(访问限定)
- 相同class的各个object互为friends(友元)
带指针的class
- 考虑动态动态分配内存
- 考虑生存期
- 考虑使用还是组成
- 要提供深拷贝拷贝构造(提供移动语义拷贝构造)
- 提供深拷贝赋值
- 提供析构
不带指针的class
- 一般使用编译器提供的默认析构
- 使用编译器提供的默认拷贝构造
- 使用编译器提供的默认赋值
OOP面向对象编程
- 继承和组合的内存模型类似
- 多个类之间的相互关系
1. 继承 is a 是一种
继承编译器默认允许的赋值
- 值赋值
父对象=子对象
Person se;
Student st;
se = st;//赋值兼容,其派生类内具有基类的隐藏对象,相当于截断但这里一般称为切片
- 指针(引用)赋值
父指针=子指针
子类继承父类的成员类型
2. 组合 has a 有一个
3. 关联 一对一
4.继承+组合
- 单独看继承、组合,它们的对象模型是一样的,子类都具有它们的数据成员
继承+关联 一对多
多态
函数的继承不应该从内存的角度理解,而应该从调用权理解
- non-virtual函数:你不希望derived class重新定义(overview覆盖)它
- virtual函数:你希望derived class重新定义(overview覆盖)它,且你对它已有默认定义
- pure virtual函数:你希望derived class一定要重新定义(overview覆盖)它,你对它没有默认定义
final 关键字修饰virtual函数 表示该函数不能被子类覆盖,提到提示作用
动态绑定
一个虚表对应一个类,一个对象有一个自己的虚表指针
- 动态绑定
- 通过指针调用
- 指针符合up-cast
- 虚函数
define、typedef、using
#define T1 int*
typedef int* T2 //
unsigned T1 a //right,其在预处理时产开为 unsigned int* a
unsigned T2 b //error,其在编译阶段为 unsigned 类型 b ,C++是强类型语言,没有类型 类型这种类型
const T1 c //正常产开 const int * c ,其是一个常量指针
const T2 d // const T2 d ,T2本身就是一个类型。const修饰d,其是一个指针常量,即模板里const修饰对应的变量
容器元素的必要条件
- 支持拷贝构造和右值拷贝构造,来支持STL中的高效处理
类封装
类转换为各种内置语言特性
pointer like class
*操作符获取到具体指向的元素内容(数据成员整体结构)
-> 会重复->操作
智能指针
- 为了智能指针支持继承is-a关系,使用成员模板
unique_ptr
-
unique的删除器是通过模板参数列表传入,其在unique_ptr的get_deleter获取删除器对象 —>根据传入的类型()形成对象
-
unique_ptr不支持拷贝动作,从函数中返回一个unique_ptr是调用了移动拷贝函数
shared_ptr
shared的删除器是通过仿函数传入,其在析构时进行延迟回调来实现相应功能
- 正如其名一样,shared_ptr 指针允许让多个该类型的指针共享同一堆分配对象。同时shared_ptr使用经典的"引用计数"方法来管理对象资源,每个shared_ptr对象关联一个共享的引用计数。
- 例如,当用一个shared ptr初始化另一个shred_ptr,或将它当做参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。(拷贝构造和赋值构造,而不能是两个智能指针初始化同一个资源)
- 最安全和高效的方法是调用make_shared 库函数,我们应该优先使用它,该函数会在堆中分配一个对象并初始化,最后返回指向此对象的share_ptr实例
- 通过shared_from_this返回this指针
weak_ptr
- 解决shared_ptr的循环引用问题
- 解决多线程情况下,用户释放资源导致多线程问题
通过智能指针管理第三方库分配的内存
智能指针可以很方便地管理当前程序库动态分配的内存,还可以用来管理第三方库分配的内存。第三方库分配的内存一般需要通过第三方库提供的释放接口才能释放,由于第三方库返回的指针一般都是原始指针,在用完之后如果没有调用第三方库的释放接口,就很容易造成内存泄露。
比如以下代码:
这段代码实际上是不安全的,**在使用第三方库分配的内存的过程中,可能忘记调用Release接口,还有可能中间不小心返回了,还有可能中间发生了异常,导致无法调用Release接口。**这时用智能指针来管理第三方库的内存就很合适了,只要离开作用域内存就会自动释放,不用显式去调用释放接口了,不用担心中途返回或者发生异常导致无法调用释放接口的问题。例如:
迭代器
class like function 仿函数
- 仿函数其本身是一个对象,功能上表现为一个函数,其比函数更加强大,因为其本身能带有成员数据
lambda表达式
- lambda本身也是一个对象,其功能表现为一个函数
- 内接收的看起来都是类的成员数据,当是值接收时,其接收的成员数据是在其能看到的作用域,且对应值的接收,里面id如何变都不会影响外部,[ ]内接收的是& 时,其会受到外部和内部的修改,[ ]传入的参数可见已经做了初始化
- ( )内是传入的参数
如下是编译器将lambda表达式编译成一个隐藏的类
lambda的应用
lambda在没有捕获时,其能自动转换为函数指针,而当其捕捉时,不能被自动转换为函数指针
- 以下告诉我们,作为排序抉择函数,仿函数更好些,Lambda表达式没有默认构造和赋值操作,若写错调用形式,编译出错一般人很难处理
this指针
- this指针的引入是为了使同一个函数入口,针对每个对象有自己的数据成员
- 当使用智能指针返回该管理类的this指针时,需要注意一个资源被二者管理,导致多次释放的问题
- thiscall调用约定,其this指针是通过寄存器传递的,而普通的函数指针没有thiscall调用约定机制,所以不能直接将成员函数赋予函数指针,还需绑定进this
new和delete
new、delete本身是关键字,其本身不能被重载,其会被编译器展开,变为一系列动作,以new来说,其展开分为operator new分配空间和构造函数,operator new可进行重载
嵌套类
参考
Effective C++