c++ primer读后记

一.语言基础

1.struct与class的关键字都可以有成员变量、成员函数、继承、多态机制,区别就在于访问权限的不同,struct默认访问权限为public、class为private,继承上struct使用private继承,class使用public继承。
2.继承、class的内存布局 https://www.cnblogs.com/longcnblogs/archive/2017/10/09/7642951.html

a.无继承、无虚函数 只存储成员变量,其中静态成员变量不存储在类中,其余成员变量按照字节对齐的方式进行存储
——————————————无虚函数的普通继承——————————————————
b.单继承,无虚函数 derive继承base,内存布局为base + derive的成员变量
c.多继承,无虚函数 derive继承base1、base2,内存布局为base1 + base2 + derive的成员变量
d.菱形继承、无虚函数 base1继承base、base2继承base、derive继承base1,内存布局为base + base1 + base + base2 + derive
这里会有二义性和内存冗余的问题,若引用base的变量、成员函数等需要指出derive.base1(base2也可以)::base.xxx
——————————————无虚函数的普通继承——————————————————

——————————————有虚函数的普通继承——————————————————
e.单继承,有虚函数 base中存储vptr + base成员变量,其中vptr中存放base的虚函数(包括虚析构函数);
derive继承base,内存布局使用vptr + base的成员变量 + drive的成员变量(另外c++允许父子类中有同名变量、函数,使用的话需要制定所属类)
继承后,derive中的vptr的虚函数表会记录base的虚函数 + derive的虚函数,如果有覆盖(即同名函数)则在vptr中对base的虚函数进行覆盖
备注:为啥基类要有虚析构函数?
为了防止内存泄露,防止使用base指针delete时候,调用的是base的析构函数,可能在派生类derive类中有内存申请。
f.多继承 有虚函数,derive继承base1、base2,内存中则布局为vptr + base1 + vptr + base2 + derive,注意这里有两个vptr,派生类追加的虚函数记录到第一个基类的vptr中(base1)
g.菱形继承 base1、base2继承base,derive继承base1、base2
vptr + base + base1 + vptr + base + base2 + derive,这里存在二义性和内存冗余性
——————————————有虚函数的普通继承——————————————————

——————————————虚继承 无虚函数——————————————————
虚继承是未了解决菱形继承中的二义性和内存冗余性问题
h.单继承 base1虚继承base,base1的内存布局为vbptr + base1 + base 其中总vbptr是虚基类指针,这个指针指向一个虚基表,表中记录着虚基类与vbptr地址的偏移量
i.多虚继承 不含虚函数
derive虚继承base1、base2,vbptr + derive + base1 + base2
j.菱形继承 无虚函数,base1、base2继承base,derive继承base1、base2
vbptr + derive + base + vbptr + base1 + vbptr + base2 注意第二个vbptr(红色)、第三个vbptr(绿色),指向的base地址 一个是-4,一个是-12,但是指向的都是同一个base
在这里插入图片描述——————————————虚继承 无虚函数——————————————————

——————————————虚继承 有虚函数——————————————————
h.菱形虚继承,有虚函数
在这里插入图片描述

3.dynamic_cast、static_cast、const_cast、reinterpret_cast

dynamic_cast是一种RTTI技术,在动态运行时对基类指针转换为派生类指针,如果转换失败则指针为NULL,因此dynamic_cast的转换需要对指针进行判断,如果是对引用的转换则判断是否抛出bad_cast的异常,dynamic_cast当然也可用于类的上行转换,只是效果和static_cast效果一样都是编译时行为
dynamic_cast原理:使用了类的虚函数表,表中前四个字节存放了_RTTICompleteObjectLocator结构的指针,这个结构存放了vptr相对于this指针的偏移、构造函数偏移、type_info指针(类的类型)等信息,用于类型转换时的动态判断,因此操作的时间和空间代价相对较高,设计时应避免使用。
static_cast类似于c语言的强制转换,可用于类的上行、下行转换(不安全),基本类型的转换、例如int转char、空指针转换成目标类型指针、任何类型转换成void等,但是不可以去除const、volatile等属性
const_cast用于消除对象的常量属性
reinterpret_cast是将数据从一种类型转换成另一种类型,通常是将数据以二进制存在形式的重新解释 ,并不对指向内容做检查和类型转换。
综上所说,在面对类(有虚函数的,不含虚函数的类进行转换编译报错)指针、引用转换时为了保证安全可以使用dynamic_cast,但是空间、时间消耗有代价,可以的话通过设计避免;普通类型使用static_cast,const_cast不提倡、reinterpret_cast没用过

4.其他关键字

explict 只能修饰带有一个参数的类构造函数,类默认的是implicit(隐式),声明为explict是防止隐式转换用的
typeid RTTI技术,动态判断类型
const 类中const成员函数不可以修改对象的数据,不管对象是否具有const属性。

5. 默认构造函数

c++编译器在必要时候(1.调用对象成员或基类的默认构造函数 2.为对象初始化虚函数表指针vptr、虚基类指针vbptr) 合成类的默认构造函数,合成的默认构造函数是不会初始化类的内置类型和符合类型的数据成员

6.new/malloc free/delete

malloc是clib库种的函数,最终是调用kernel的系统调用进行内存分配比如brk等,申请了一段连续的虚拟地址空间,如果内存不满足则返回的是一个NULL指针,malloc申请的空间需要free进行释放,malloc申请的空间大小需要程序员自己计算,还可以通过realloc重新分配内存,malloc返回的是一个void*指针,需要用户自己转换。
new是c++的操作符,实际空间分配也是调用的malloc,区别在于申请完空间后悔调用对象的构造函数进行初始化,如果分配失败抛出bad_alloc的异常,对应delete是对对象进行析构,然后调用free释放空间。new也可以设置自己的内存分配器

二.数据类型

1.尽量使用vector而不是数组,除非实在性能达不到要求
2.char数组一定要记得预留/0字符作为字符串结尾
3.可以通过new申请动态数组 int *ptr = new int[10]

三.函数相关

1.函数形参为函数提供了已命名的局部存储空间,实参则是一个表达式可以是变量或字面值常量
2.指针从右向左读 例如 int *&p,p是一个引用,引用了一个指针,这个指针指向一个int类型
3.函数可以有默认实参
4.inline函数是建议编译器预编译时展开
5.类成员函数可以定义const成员函数,不可以修改类中的成员变量;这实际是由于传入的是const对象this指针,因此任何修改都会导致编译不通过
6.类的默认构造函数就是无参的构造函数,意思为没有显式初始化的情况下选择的构造函数
类似于 string str; 空字符串
在编译其需要时候才会调用默认构造函数:
a.类对象数据成员有默认构造函数
b.类的基类有默认构造函数
c.类的基类为虚基类时候 需要默认构造函数初始化虚基表指针
d.类有虚函数 需要默认构造函数初始化虚表指针
7.函数重载,函数名相同,形参表不同(返回值不同不是重载,是错误重定义) 是静态的多态行为,是编译器编译时期指定 c++编译器通过修饰支持重载,可以通过readelf -S 查看符号表,函数符号包含形参列表

四.容器和算法

1.容器分为顺序容器和关联容器(k、v类型的),顺序容器有vector、list、deque,对顺序容器做适配后有queue(fifo)、stack(LIFO)、priority_queue,关联容器有set、map等。另外还有Hash存储unordered_map
2.各类容器的实现原理
vector 使用连续数组存放元素,可以设置初始大小,内存不够时申请一个更大的内存,将原数组copy过来并预留一段buffer。适合于随机读取、向后插入操作的场景。
map是关联容器,底层使用红黑树实现,key的类型需要具有比较函数,遍历map可以升序访问数据,适合大数据量查找,查找效率logN
unordered_map内部元素是无序的,使用hash存储,需要定义hash_value计算key的hash值的函数并且重载==操作符,判断value是否相同
set是关联容器,每个元素包含一个关键字底层同样是红黑树实现
list是非连续空间存储,使用双链表结构,适合高校的插入、删除操作,随机访问效率低
deque是连续存储结构,元素在内存上是连续的,同事维护了容器的首尾地址方便两端插入删除数据。
queue、stack是基于deque的,priority_queue是基于vector的
3.迭代器失效
vector 插入、删除
a.顺序容器在迭代器使用过程中 删除元素 需要更新迭代器 it = vec.erase(it);
b.push_back插入元素,end操作返回的迭代器必定失效
c.push_back插入一个元素后,可能由于容量不够重新分配内存 导致迭代器全部失效
deque 插入、删除
a.在非首尾位置插入
b.非首尾位置删除
c.在首尾删除元素会使指向被删除元素的迭代器失效
map
a.erase(it)删除某个元素后 被删除元素的迭代器失效,在遍历中应该使用 erase(it++),该过程中先传递it给erase,再将iter++,再执行erase原先的iter

五.类

1.访问权限及继承后权限
public、protect、private 其中public成员可以被类外直接调用,protect则只能被类的成员函数、子类的成员函数访问,private则只能被类本身的成员函数访问
继承有public、protect、private继承,public继承表示父类的成员在子类中权限不变,protect继承后父类
protect继承表示父类的public、protect成员变为子类的protect,private仍然是private,private继承则表示父类的所有成员都变为了子类的Private权限。
其实在子类中权限,可以用min(父类成员原始权限,继承方式),其中public>protect>private
这里要注意两个概念,父类定义了成员的权限后,不论子类public、protect、private继承方式的哪一种,子类只能访问父类的Public、protect成员,继承方式只是在子类中变成了哪种对外的权限。
另外继承是除了构造函数、析构函数之外的成员和函数。
2.友元
可以声明友元类,例如A中声明一个friend class B,标识B可以访问A的所有成员。
可以声明友元函数,例如A中声明一个 friend void B::FUNC(),标识B的FUNC成员可以访问A的所有成员。
友元关系不能被继承。
3.构造函数
默认构造函数为无参函数,如果不声明,编译器根据需要生成默认构造函数。
构造函数可以带参数,可以生成拷贝构造函数、赋值操作符重载等构造方式。根据三原则(析构、复制构造函数、赋值操作符重载一般是出现一个则三个都必须定义)
4.智能指针
智能指针是一种RAII技术(资源获取即初始化),智能指针实际是一个对象,是通过引用计数、栈上变量声明周期自动化、类临时变量结束生命周期时候调用析构函数的技术、符号重载等做到智能的删除对象。
shared_ptr、unique_ptr、weak_ptr
shared_ptr多个指针指向相同的对象,每次通过shared_ptr的拷贝增加的指针,内部引用计数就加1,每析构一次就-1,减到0就自动删除锁指向的堆内存。引用计数线程安全,对指向对象的读取需要加锁。最大的陷阱是循环引用,导致无法正确释放。
weak_ptr就是为了配合shared_ptr工作,weak_ptr可以通过一个shared_ptr或者另一个weak_ptr来获取资源观测权,但不会导致引用计数+1,
unique_ptr则是独占一个对象,禁止拷贝语义,当unique_ptr的生命周期结束时,指向的对象声明周期也就结束了。
5.类中的特殊关键字 const、static、inline
const修饰成员函数表示这个函数中不能修改这个类中的成员
static成员、成员函数为静态成员、静态成员函数可以通过A::XXX来直接引用。所有对象共享这个成员,如果有继承,所有继承层次中都是共享这个成员。
6.函数重载、虚函数、纯虚函数、虚析构函数
函数重载是c++编译器的特性支持,在符号表中对函数的参数进行修饰,是编译时期的行为,是一种静态的多态。
虚函数用于动态绑定,是动态的多态,只要声明了虚函数就会在类的地址空间中生成虚函数表指针,并初始化虚函数表,通过运行时决定调用的是哪个函数。一旦基类声明了虚函数FUNC_A,派生类不论声明不声明FUNC_A,它一直都是虚函数。
纯虚函数是用来定义接口的,一旦一个类中存在纯虚函数则这个类就不能实例化,派生类也必须重写定义这个函数后才能被实力化。
当有虚函数时候,是很有可能使用多态的,当delete一个派生类指针,会调用派生类的析构函数,在析构过派生类后会继续调用基类的析构函数释放资源;当delete一个指向派生类的基类指针时候,只会调用基类的析构函数,只有将析构函数定义为虚函数才会通过动态绑定的方式找到派生类的析构函数进行调用,然后再调用基类的析构函数,的是怕资源泄露。

六.模板

1.模板是编译时多态,是编译器行为,在编译过程中进行推断,一旦确定模板形参类型T的具体类型则称为实力化了一个模板的实例。
2.模板中typename、class是一个意思,可以互换 templete <class T1, typename T2>
3.模板特化 templete <>声明下面的函数是一个模板特化,使用具体的参数类型。编译器在识别调用
templete <class T1, class T2>
bool compare(T1 &t1, T2 t2) {
return t1 < t2
}

templete <>
bool compare(int t1, int t2) {
return abs(t1) < abs(t2)
}
compare(int,int类型)时候使用模板特化。
类的成员函数也支持特化。
还可以支持部分特化(偏特化,即只特化部分参数)
templete <>
bool compare(T1 t1, int t2)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值