基础语法
inline内联函数
在编译过程中,没有函数的调用开销了,在函数的调用点直接把函数的代码进行展开处理了。
不是所有的inline都会被编译器处理成内联函数,比如“递归”。
inline只是建议编译器把这个函数处理成内联函数。
debug版本上,inline是不起作用的;inline只有在release版本下才能出现。符号表中不生成符号了。
C++和C语言代码之间如何互相调用?
C++和C的符号生成规则不同,所以无法直接调用。注意初学要用大型编译器搞,命令行让其跑起来的方式我还不会。
const
C里面:const可以不初始化。const 修饰的量不是常量,是常变量。不能作为左值。const int a = 10;
仅仅是语法上的不能被修改(a 这个符号不能被改变,内存可以变)
**C++:**const 必须初始化,初始值是立即数的叫做常量;是一个变量的可以被看成常变量。所以可以被用来定义数组的大小。
C中const就是被当作一个变量来编译生成指令的。
C++中,所有出现const变量名字的地方,都被常量的初始化替换了。
同样一份代码,C++ 结果为 10 20 10
但是内存被改变了。
*(&a) 是被编译器优化了,直接是 a
让C++ 变为 C 的结果方式为将 const 由常量变为常变量。
可以理解成将所有的 a 变成了 变量 b。
const 和 一二级指针的结合应用
const 和一级指针
const 修饰的量常出现的错误:
- 常量不能再作为左值
- 不能把常量的地址泄露给一个普通的指针或者普通的引用变量
C++语言规范:const修饰的是离它最近的类型。
不想把常量的地址泄露给一个普通指针,用这种方法:
const 和指针的类型转换公式:
int* ⇐ const int* 错
const int* ⇐ int* 对
const 如果右边没有指针 * 的话,不参与类型
const 和二级指针
int** ⇐ const int ** 错误
const int** ⇐ int ** 错误
int ** <= int*const*
错误 const修饰右边的指针,相当于一级指针的 int* <= const int*
int*const* <= int **
正确 相当于一级指针的const int* <= int*
volatile
详解volatile在C++中的作用 - teof - 博客园 (cnblogs.com)
volatile对基本类型和对用户自定义类型的使用与const有区别,比如你可以把基本类型的non-volatile赋值给volatile,但不能把用户自定义类型的non-volatile赋值给volatile,而const都是可以的。还有一个区别就是编译器自动合成的复制控制不适用于volatile对象,因为合成的复制控制成员接收const形参,而这些形参又是对类类型的const引用,但是不能将volatile对象传递给普通引用或const引用。
左值引用和右值引用
引用和指针的区别?
-
引用必须初始化的,指针可以不初始化。反汇编:引用和指针的底层一样。
int* p = &a;
00D71FF9 lea eax,[a]
00D71FFC mov dword ptr [p],eax
int& b = a;
00D71FFF lea eax,[a]
00D72002 mov dword ptr [b],eax
-
引用只有一级引用,没有多级引用。指针可以有多级指针。
-
定义一个引用变量和定义一个指针变量,其汇编指令是一模一样的。都可以修改内存的值。
使用引用变量时会解引用。array 和 q 是一回事儿。
左值:有内存,有名字,值可以被修改
右值:没内存(立即数,放在寄存器里面),没名字
C++11提供了右值引用。
int &&c = 10;
指令上,可以自动产生临时量然后直接引用临时量
- 一个右值引用变量本身是一个左值。
- 右值引用不能引用左值,左值引用可以引用右值。
这两种方式的指令一样:
00391FF2 mov dword ptr [ebp-18h],14h
00391FF9 lea eax,[ebp-18h]
00391FFC mov dword ptr [a],eax
const、指针、引用的结合使用
&和* 右边的一个&
将左边的一个 *
变为&
new 和 delete
- malloc 和 free,称作 C 的库函数;new 和 delete,称作运算符
- new 不仅可以做内存开辟,还可以做内存初始化操作
- malloc开辟内存失败,是通过返回值和nullptr作比较;new开辟内存失败,是通过抛出bad_alloc类型的异常来判断的。
类和对象
构造函数和析构函数
先构造的后析构。
析构函数不带参数,所以析构函数只能有一个;构造函数可以提供多个,叫做重载。
堆上的对象要手动析构, 也就是new 和 delete
this指针
类的成员方法一经编译,所有的方法参数都会加一个this指针, 接收调用该方法的对象的地址
对象的深拷贝和浅拷贝
有指针指向对象的外部资源时,浅拷贝的析构会出问题:先构造的指针变成了野指针。
CTest t3 = t1;
默认拷贝构造函数,浅拷贝
拷贝时扩容用 for 循环, 不用 memcpy 原因:避免指向外部的指针指向同一块
类的各类成员方法以及区别
普通的成员方法 =》编译器会添加一个 this 形参变量
- 属于类的作用域
- 调用该方法时,需要依赖一个对象
- 可以任意访问对象的私有成员变量
static静态成员方法 =》 不会生成 this 形参
- 属于类的作用域
- 用类名作用域来调用方法
- 可以任意访问对象的私有对象,仅限于不依赖对象的成员(只能调用其它的static静态成员)
const常成员方法 =》 const CGoods *this
- 属于类的作用域
- 调用依赖一个对象,普通对象或者常对象都可以
- 可以任意访问对象的私有成员,但是只能读不能写
指向类成员(成员变量和成员方法)的指针
模板编程
理解函数模板
模板只定义,不编译。如果将其放到另外一个文件中,如果没有对应的特例化将出错。
所以模板代码都是放在头文件中,然后在源文件中include 包含。
类模板
演示下 template 用 T 以外的其它参数
可以加默认 T 参数。 但是声明时 <>
要写
构造和析构函数名不用加<T>
,其它出现模板的地方都要加上类型参数列表。
类模板无非就是将存数据的类型改为 T
实现STL向量容器vector代码
理解容器空间配置器allocator的重要性
上个代码存在以下问题:
构造:需要把内存开辟和对象构造分开处理
析构:析构容器有效的元素,然后释放_first指针指向的堆内存
pop_back:只需要析构对象。要把对象的析构和内存释放分开
运算符重载
模拟实现C++的string类代码
string字符串对象的迭代器iterator实现
vector容器的迭代器iterator实现
泛型算法:参数接收的都是容器的迭代器
内置类
什么是容器的迭代器失效问题
迭代器在 erase 后失效;迭代器在 insert 之后失效
失效原因
- 当容器调用 erase 后,当前位置到容器末尾元素的所有的迭代器全部失效了
- 当容器调用 insert 后,当前位置到容器末尾元素的所有的迭代器全部失效了。
- 如果 insert 引起扩容,原来容器的所有的迭代器全部失效。
- 不同容器的迭代器不能进行比较运算
面试题:vector迭代器什么时候会失效?_vector 迭代器什么时候失效_clw_18的博客-CSDN博客
new和delete重载实现的对象池应用
继承与多态
重载、隐藏、覆盖
子类可以调用基类的函数,但是一旦子类自定义同名函数,父类的该函数名不能被用。
1.重载关系
一组函数要重载,必须处在同一个作用域当中;并且函数名字相同,参数列表不同
2.隐藏(作用域隐藏)关系
在继承结构当中,派生类的同名成员,把基类的同名成员给隐藏调用了
3.覆盖关系
基类和派生类的方法,返回值、函数名以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就自动处理成虚函数,它们之间成为覆盖关系。
在继承结构中进行上下的类型转换,默认只支持从下到上的转换
虚函数、静态绑定和动态绑定
静态绑定
动态绑定
虚析构函数
哪些函数不能实现成虚函数?
- 构造函数不能用 virtual,因为构造完成了才产生对象
- 构造函数中调用的任何函数都是静态绑定,不会发生动态绑定 派生类对象构造都是先调用的基类构造
- static 静态,因为static 不依赖对象
**虚函数依赖: **
- 虚函数能产生地址,存储在vftable当中
- 对象必须存在(vfptr → vftable → 虚函数地址)
虚析构函数
析构函数可以成为虚函数。因为析构函数调用时对象存在。
基类的指针指向堆上 new 出来的派生类对象时,它调用析构函数的时候必须发生动态绑定,否则会导致派生类的析构无法调用。
pb 的类型是 Base,析构函数是普通函数,静态绑定。
解决方案:将基类的析构函数变为虚函数。
再跑一遍,结果为
再谈动态绑定
是不是虚函数的调用一定就是动态绑定? 不是
- 在类的构造函数中,调用虚函数,也是静态绑定(构造函数中不会发生动态)
- 如果不是通过指针或引用变量来调用虚函数,那就是静态绑定
理解多态到底是什么
静态(编译时期)的多态:函数重载、模板(函数模板和类模板)
动态(运行时期)的多态:在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖 方法(虚函数)。基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法,称为多态。
多态底层是通过动态绑定来实现的。pbase 访问谁的 vfptr 就继续访问谁的 vftable,当然调用的是对应的派生类对象的方法了。
软件设计“开-闭”原则:对修改关闭,对拓展开放
继承的好处
- 代码的复用
- 在基类中提供统一的虚函数接口,让派生类进行重写,然后就可以使用多态了
理解抽象类
拥有纯虚函数的类叫做抽象类
抽象类不能再实例化对象了,但是可以定义指针和引用变量
笔试实战
第一题
前四个字节是 vfptr ,指向的是当前对象的 vftable
第二题
这个输出我第一次看到感到难以理解。为什么调用show()
函数输出的是派生类,而输出的 i 是 10 ?
编译阶段p 只是base* ,因此入栈的是base 的参数
参数是编译时期压栈
008B2923 push 0Ah => 函数调用,参数压栈是在编译时期就确定好的
008B2925 mov eax,dword ptr [p]
008B2928 mov edx,dword ptr [eax]
008B292A mov ecx,dword ptr [p]
008B292D mov eax,dword ptr [edx]
008B292F call eax
==派生类的构造函数的默认值没有用==
第三题
派生类的构造函数为 private
正常调用
最终能调用带Derive::show(),是在运行时期才确定的。
成员方法能不能调用,就是说方法的访问权限是不是public的,是在编译阶段就需要确定好的
如果把基类的构造函数标为private将编译出错
第四题
构造函数的左大括号写入的虚指针
多重继承的那些坑
多重继承:代码的复用 一个派生类有多个基类
理解虚基类和虚继承
抽象类:有纯虚函数的类
虚基类:被虚继承的类,称作虚基类
virtual:
- 修饰成员方法是虚函数
- 可以修饰继承方式,是虚继承。被虚继承的类,称作虚基类
当一个类有虚函数,这个类生成 vfptr ,指向 vftable
vbptr 指向 vbtable,派生类虚继承而来
C++ 虚继承实现原理(虚基类表指针与虚基类表)_c++基类实现_longlovefilm的博客-CSDN博客
菱形继承
采用虚继承解决菱形继承
好处:
- 可以做更多代码的复用
- D → B, C B *p = new D() C *p = new D() 使用起来更灵活
C++的四种类型转换
C++语言级别提供的四种类型转换方式
C++ | C++的四种类型转换_c++去常转换是所有的const吗_瘦弱的皮卡丘的博客-CSDN博客
const_cast : 去掉(指针或引用)常量属性的一个类型转换
static_cast : 提供编译器认为安全的类型转换(没有任何联系的类型之间的转换就被否定了)。编译时期的类型转换
reinterpret_cast : 类似于C风格的强制类型转换,谈不上安全
dynamic_cast : 主要用在继承结构中,可以支持RTTI类型识别的上下转换。运行时期的类型转换
reference