C++ 基础 #
一、基本语法 #
1、指针和引用有什么区别? #
- 指针有自己的空间,引用只是别名
- sizeof(指针) = 4;sizeof(引用) = 对象的大小
- 指针可以指向其他对象,引用不行。
2、const 的作用是什么? #
作用:(在 const 后面)的值不可改变。
- 变量;
- 指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);
- 引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改;
- 成员函数,说明该成员函数内不能修改成员变量。
3、const 和 volatile 一起修饰变量? #
表示变量是不可改变的,并且编译器不会优化这个变量。
4、static 的作用是什么? #
- 普通变量:修改存储区域(静态区)、生命周期(main运行前分配空间),初始值
- 普通函数:作用范围。仅在定义该函数的文件内才能使用。多人开发场景,为了防止与他人命名空间里的函数重名,可以将函数定义为 static。
- 成员变量:不需要实例化对象就能访问。多个对象只一份
- 成员函数:同。但不能访问非静态成员
5、Lambda 表达式 #
sort(vec.begin(), vec.end(), [](int a, int b) -> bool { return a < b })
- 捕获外部变量(值,引用)
- 参数 [](int x, int y){ return x + y }(5,4) 输出9
5、了解过 inline 内联函数吗? #
作用:= 宏 ,直接写进去,直接执行。
优点:
- 和普通函数相比,不用参数压栈等动作,提高运行速度。
- 和宏函数相比,内联函数会做类型检查,且在运行时可调试,而宏定义不可以。
缺点:
- 内存换时间。如果执行函数体内代码的时间,远远大于函数调用的开销,那就没有意义。
- 决定权在编译器:不内联包含循环、递归、switch 等复杂操作的函数
编译器对 inline 函数的处理步骤:
- 将 inline 函数体复制到 inline 函数调用点处;
- 为所用 inline 函数中的局部变量分配内存空间;
- 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
5-2、虚函数可以是内联函数吗? #
- 可以。编译器知道调用的对象是哪个类。
- 不可以。虚函数的多态性在运行期,编译器不知道在运行期调用哪个代码,即:虚函数表现多态性的时候不能内联。
二、面向对象 #
0、面向对象概念? #
- 封装
- 继承
- 多态
1、封装的概念? #
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。类继承默认 private, struct默认public。
- public
- protected:本类 + 子类的成员函数
- private:本类的成员函数、友元类或友元函数
2、多态的概念 #
多态,即多种状态(形态)。
- 静态多态:重载,编译期确定。(函数重载、运算符重载
- 动态多态:虚函数
- 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
- 强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换
3、虚函数的概念? #
1、概念 #
用 virtual 修饰的成员函数。当使用基类的引用或指针调用一个虚函数时将在运行期进行动态绑定。
2、实现 #
通过虚函数指针和虚函数表:
- 每个对象,都有一个虚函数指针,指向一张虚函数表,虚函数表是针对类的,表中放了虚函数的地址。
- 子类继承父类时,会继承虚函数表,重写虚函数时,会替换掉函数地址。
虚函数表:所有对象共享这个类的虚函数表。表在程序只读数据段,实际函数在代码段。
3、不能是虚函数 #
这些不能是虚函数:
- 构造函数:因为虚函数指针要在构造函数中初始化
- 静态
- 普通
4、虚析构函数 #
基类的指针指向派生类对象,并用基类的指针删除派生类对象。 如果不是的话,基类指针指向派生类对象,会调基类的析构函数。
5、纯虚函数与抽象类 #
- 纯虚函数是一种特殊的虚函数,在基类中不能实现,只声名,必须被继承并实现。
- 带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。
3、虚继承的概念? #
1、概念 #
虚继承用于解决菱形继承问题(类D同时继承B和C,B和C又继承自A:浪费空间、存在二义性)。
2、实现 #
通过虚基类指针和虚基类表:
- 每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间);虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,
- 虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
5、sizeof(空类)的大小? #
值为1。分配一个字节,可以使这个类的不同实例拥有独一无二的地址。
6、覆盖和重载的区别 #
- 覆盖:和父类完全相同
- 重载:不同参数列表/返回值
7、拷贝构造函数和赋值运算符重载 #
当类中包含指针变量时:
- 拷贝构造函数:用于构造新的对象,Student s1 = s; Student s2(s)
- 赋值运算符重载:拷贝 s1 = s;
8、Struct 和 Class 的区别? #
- 默认继承权限不同,class继承默认是private继承,而struct默认是public继承
- class还可用于定义模板参数,像typename,但是关键字struct不能用于定义模板参数
- C++保留struct关键字
9、强制类型转换 #
- static_cast
- 场景:转换数值型数据类型(如 float -> int)
- 非多态类型的转换,不执行运行时类型检查
- dynamic_cast
- 场景:指针或引用
- 多态类型的转换、执行行运行时类型检查
- 向上转换、向下转换
- const_cast
- 场景:删除 const、volatile 特性(如将 const int 类型转换为 int 类型 )
- reinterpret_cast
函数调用过程 #
0、有哪三个寄存器? #
- eip: 指向下一条即将执行的指令
- ebp: 指向栈底
- esp:指向栈顶
1、参数压栈的顺序 #
从左←右
(1,2,3)先压3!2,1,ESP指向12、保存现场
保存现场:
- 将 eip 压栈,
- 将 ebp 压栈:
然后确定函数的区域:将现在的栈顶作为新的栈底,并往上移一点点
还有将其他main函数的一些现场压栈保护起来。
栈结构:
esp -> 前 edi
64字节隔离区 - 局部变量 x - 局部变量 y
ebp-> 前ebp值 - 前eip值 - 参数1 - 参数23、恢复现场
- 弹出edi、esi等
- 调整 esp 为 ebp
- 弹出前 ebp、eip