第一: private,public,protected的访问范围:
private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
注:友元函数包括两种:设为友元的全局函数,设为友元类中的成员函数三种继承
public继承,父类成员在子类中保持原有的访问方式
protected继承,父类中public成员会变成protected父类中protected成员仍然为protected 父类中private成员仍然为private
private继承,父类成员在子类中变为private成员
private成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不能直接使用基类的私有成员。
虚函数实现机制和底层原理
C++中的虚函数的作用主要是实现了多态的机制。
实现原理:虚函数表+虚表指针
编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。使用虚函数后的变化:
- 对象将增加一个存储地址的空间(32位系统为4字节,64位为8字节)。
- 每个类编译器都创建一个虚函数地址表
- 对每个函数调用都需要增加在表中查找地址的操作。
虚函数的注意事项 - 基类方法中声明了方法为虚后,该方法在基类派生类中是虚的。
- 若使用指向对象的引用或指针调用虚方法,程序将根据对象类型来调用方法,而不是指针的类型。
- 如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚。
STL相关
- vector:底层数据结构为数组 ,支持快速随机访问。
- list:底层数据结构为双向链表,支持快速增删。
- deque:底层数据结构为一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,也支持随机访问。
即双端队列。是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,相比list增加[]运算符重载。
- stack:底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
- queue:底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)
- priority_queue:的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
- set:底层数据结构为红黑树,有序,不重复。
- map:底层数据结构为红黑树,有序,不重复。
std::vector的底层(存储)机制。
vector就是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时,会自动申请另一片更大的空间(一般是增加当前容量的50%或100%),然后把原来的数据拷贝过去,接着释放原来的那片空间;当释放或者删除里面的数据时,其存储空间不释放,仅仅是清空了里面的数据为什么vector的插入操作可能会导致迭代器失效?
vector动态增加大小时,并不是在原空间后增加新的空间,而是以原大小的两倍在另外配置一片较大的新空间,然后将内容拷贝过来,并释放原来的空间。由于操作改变了空间,所以迭代器失效。你怎样理解迭代器?
Iterator(迭代器)用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示,相当于智能指针。vector每次insert或erase之后,以前保存的iterator会不会失效?
理论上会失效,理论上每次insert或者erase之后,所有的迭代器就重新计算的,所以都可以看作会失效,原则上是不能使用过期的内存
但是vector一般底层是用数组实现的,我们仔细考虑数组的特性,不难得出另一个结论,insert时,假设insert位置在p,分两种情况:
a) 容器还有空余空间,不重新分配内存,那么p之前的迭代器都有效,p之后的迭代器都失效
b) 容器重新分配了内存,那么p之后的迭代器都无效咯
erase时,假设erase位置在p,则p之前的迭代器都有效并且p指向下一个元素位置(如果之前p在尾巴上,则p指向无效尾end),p之后的迭代器都无效vector中erase方法与algorithn中的remove方法区别
vector中erase方法真正删除了元素,迭代器不能访问了
remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。vector、list、map、deque用erase(it)后,迭代器的变化。
vector和deque是序列式容器,其内存分别是连续空间和分段连续空间,删除迭代器it后,其后面的迭代器都失效了,此时it及其后面的迭代器会自动加1,使it指向被删除元素的下一个元素。
list删除迭代器it时,其后面的迭代器都不会失效,将前面和后面连接起来即可。
map也是只能使当前删除的迭代器失效,其后面的迭代器依然有效。红黑树的性质
- 节点是红色或黑色。
- 根节点是黑色。
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
智能指针
负责自动释放所指向的对象,防止内存泄漏
封装普通指针使其表现的像普通指针一样。超过类的作用域,将会自己调用析构函数,自动释放资源shared_ptr: 多个指针指向相同的对象
shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。 创建方式:1. 使用函数make_shared(会根据传递的参数调用动态对象的构造函数)。 2. 使用构造函数(可从原生指针、unique_ptr、另一个shared_ptr创建) 拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。 注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存。 注意避免循环引用: 两个对象互相使用一个shared_ptr成员变量指向对方的会造成循环引用。 解决循环引用:使用弱引用的智能指针打破这种循环引用。 强引用和弱引用 一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。 boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。 弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
auto_ptr:不可以用于容器,不建议使用,不支持复制和赋值,如果进行了赋值和复制操作,并不报错
unique_ptr:则“独占”所指向的对象,不支持复制和赋值,直接赋值会报错,同一时刻对象仅能拥有一个unique_ptr指向自身
weak_ptr:它是一种弱引用,指向shared_ptr所管理的对象,不控制对象生命周期的智能指针,只提供了管理对象的访问手段,用于协助shared_ptr的工作,用于观测资源的使用情况。use_count()可以一观察资源的应用数。强制类型转换
static_cast 用于基本类型之间、有继承关系的类对象之间、类指针之间的转换不能用于基本类型指针之间的转换
const_cast 用于去除变量的只读属性 强制转换的目标类型必须是指针或引用
reinterpret_cast 用于指针类型之间、整数和指针类型之间的转换
dynamic_cast 用于有继承关系的类指针之间、有交叉关系的类指针之间的转换,具有类型检查的功能,需要虚函数的支持static_cast 和 dynamic_cast 的区别
static_cast 没有运行时检查- 用于类层次结构中基类和子类之间指针或引用的转换
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。 - 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成void类型。
dynamic_cast 有运行时检查,需要虚函数表
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
- 用于类层次结构中基类和子类之间指针或引用的转换
C++ 11 了解多少
nullptr 关键字,专门用来区分空指针、0。
auto 和 decltype 这两个关键字实现了类型推导
序列for循环 变长參数的模板 更加优雅的初始化方法
constexpr 将变量声明为constexpr类型以便由编译器来验证变量的值是否为一个常量表达式,必须在编译期间计算出它的值并且它的值不可以被改变
Const只能保证在运行时是常量,即具有运行时数据的不可更改性。
lambada 表达式子类调用父类的构造函数
- 子类默认调用父类的无参构造
- 若父类定义了有参构造函数,没有定义无参构造函数,会报错
- 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式
类默认的6个方法
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值运算符号重载函数
- 取地址操作符重载
- const修饰的取地址操作符的重载
- C++primer