侯捷C++八部曲:C++面向对象程序设计
2021-11-22 00:07 cascle 阅读(938) 评论(0) 编辑 收藏 举报1. C++编程简介
2.头文件与类的声明
c语言的数据暴漏的太多,任何函数都能访问,不容易维护
类分两种,内部带指针的和不带指针的
类里有指针要非常小心
.h和.cpp是一部分,因为角色的区分,分成两部分
自己的头文件用双引号,库的用尖括号
可以把.h拿掉,c的stdio要写成cstdio
3. 构造函数
private:主要作用是要封装起来内部细节
访问级别可以任意交错
创建对象的时候构造函数自动被调用。不需要返回值,因为不需要自己创建本对象。初始化列表构造函数独有。数值设定分两阶段,用初始化和赋值,先初始化再复制。初始化列表属于初始化,构造函数体是赋值
初始化列表的使用显得大气上档次
不带指针的类,多半不用写析构函数
重载是给函数改名
构造函数一个是有默认参数,一个是没有参数,这样不行,两者冲突,因为编译器不知道选哪个函数,结果不唯一
c1和c2()这两种方式创建对象都可以
4.参数传递与返回值
函数在参数和body之间加const,代表函数不会改变对象内容
const对象调用非const方法会报错
不改值的函数用const修饰是个好习惯
尽量不要pass by value,复制浪费时间、空间。引用像指针但是更漂亮,使用更自然。引用、指针一般更快、更省空间,但和小字节数据比还是差
按值传递是复制参数,改了参数也不会改原值,引用为了达到这种效果,要用const在变量类型前修饰,这样确保不会改变实参
返回引用效率更高,返回值尽量是reference,有的情况不能返回ref
友元比用函数拿效率高点,但是打破了封装
正规写法:
1.数据放private
2.参数尽可能以reference来传,根据要不要改实参决定要不要带const
3.返回值尽量以reference来传
4.在类本体里定义的函数,不改值的加const
5.构造函数用初始化列表初始化
返回reference:返回的是一个本来就有的东西,不会有错。local对象就不能做为reference返回
reference主要用在传递东西上
5. 操作符重载与临时对象
编译器自动把c2地址传进来当成this,this是函数的隐藏参数
返回的是引用,接收端怎么接收不用在乎,用引用和value接收都可以,方便书写,只是效率有差别。传回指针就不行了,必须用指针接收,无法连着用。如果不加引用返回一个已存在对象,相应的会生成一个临时对象,构造、拷贝、析构分别来一次(造一个对象,要不用来常量引用要不用来拷贝,对左值修改影响不了原值,引用直接编译报错;NRVO可以优化不生成对象,直接走拷贝,效率一致,但NRVO可能失效),引用则是指针赋值或者拷贝一次。返回临时对象少复制一次只有一次构造,有编译器优化(RVO)
重载只有一个能符合,多了编译器报错
多个参数,顺序不定,用全局的容易掌控
参数没有this pointer
临时对象,没有名字,声明在下一行已经结束了
运算符 全局重载,靠参数个数确定函数名.只有一个参数放对象左边的,可以设计为全局函数也可以设计为类函数,运算符重载函数的结合性和默认的是一样的
有些类不能修改,要用全局运算符重载其运算符
不是自己写的,参数别加const
6.复习Complex类的实现过程
7. 三大函数:拷贝构造,拷贝复制,析构
拷贝构造与拷贝赋值区别:一个是构造初始化一个是赋值,拷贝构造的对象之前不存在,所以与赋值构造处理不同,不用清理以前的垃圾
默认的是按bit复制,没有指针、引用就够用
s1()也会调用构造函数
浅拷贝,内存泄漏和数据别名
拷贝构造函数只是复制,不想改变外面对象,所以要const参数
判断是否是自己,清空已有数据,拷贝新数据。给已有对象重新赋值
没有自我赋值检测,一是慢,二是产生悬垂指针
8,堆、栈与内存管理
对象的内存,要不位于栈区要不位于堆区
析构函数自动被调用
global对象构造函数早在main执行前就被调用
类中的静态变量与全局变量相同。它的构造函数将在进入 main 函数之前被调用。
- 结论(适用于 g++、Windows 环境):
- 全局变量 和 类中的静态成员 : 在输入之前调用构造函数 主 功能 (1) .
- 局部静态变量 : 构造函数仅在第一次执行到达其声明时调用。
- 如 局部静态变量为 POD 类型 , 那么在输入 之前也是初始化的主 功能 (1) .
POD 类型示例:static int number = 10;
(1) :正确的状态应该是:“在调用来自同一翻译单元的任何函数之前”。但是,为了简单,如下例所示,它是主要功能。
内存泄漏:没机会再delete堆上对象了,失去了控制
"operator new",函数名就叫这个
删除两块堆上的内存
上下红色cookie用于记录整块大小,方便回收
最后一位,0x41, 1用来表示已分配不可用,0用来表示可用未分配。最后一位可以借位是因为地址一定是16的倍数,所以最后一位必为零
数组个数排在数据前面
这里指针对象、数组指针还是指向对象内存地址的,而不是cookie等附加数据,相应的delete函数会自己去找
写不写中括号不影响内存删除,但是影响析构函数调用,内存泄漏的是数据里的指针指向的堆内存。如果是复数类而不是字符串,加不加中括号都行
9. 复习String类的实现过程
10. 扩展补充:类模板,函数模板,及其他
静态成员数据是类只有一份,静态函数只能操作静态对象成员
静态成员数据要在类外面初始化,不然编不过。
这也叫静态成员的定义,定义会开内存空间。类里面的是声明而已,因为静态成员脱离于对象,是属于类的
懒汉与饿汉模式区别
template里typename和class相通
类模板明确指出type,函数模板是根据参数自行推导
namespace解决同名问题
11. 组合与继承
面向对象编程:类和类之间产生关系
黑色菱形代表里面有东西
编译器帮助调用部件的构造、析构。
各自管各自的构造和析构即可,编译器全做好了
如果调用默认的构造、析构不合适,就自己在初始化列表上调用组件的构造函数
白色菱形是委托,用指针指向,有些虚
二者寿命不一致
pImpl模式:左边是接口,真正的实现在委托那里,方便实现变动、切换,这样就具有了弹性
这种手法也叫做编译防火墙
还有就是cow技术,改的时候才真正开辟新内存进行拷贝,不然就是复制一个指针
白色三角形代表继承
基类析构不是virtual,可能发生只调用基类析构不调用子类析构
12. 虚函数与多态
继承主要作用是要搭配虚函数,内存布局倒是其次
函数的继承不应该从内存的角度理解,函数的继承继承的是调用权
子类可以调用父类的函数
形状很抽象,没什么东西就直接是形状,所以要定义有纯虚函数
虚函数:希望子类来做,父类不做或者做的默认实现比较简单
关键动作延缓到子类去做,叫做template method
本质是虚函数通过vptr来调用,看是this指针是哪个对象就调到哪个函数
既有基类又有Component,先调用基类构造函数
13. 委托相关设计
组合模式:容器可以容纳单体及容器
C++容器里要放指针,因为必须一样大小,放对象可能基类、子类不一样大
原型模式:应对未来之对象,要求其继承已有对象,用静态对象创建自己,构造函数里将其add进框架,框架提供虚函数,子类实现copy子类自己创造的东西
LSAT:带下划线,是静态的,所以类自己创造了自己
类图先写名字再写类型
-代表private,#代表protected
通过存储的指针调用相应类的clone方法,clone里调用protected构造函数防止调用私有的构造函数又注册一次
框架应有之意:实现框架预置的方法,框架先写好了又要让他知道自己