内容不多,讲的不深不广却是自己所见的面试官最爱最爱问的C++八股问题
(1)编译过程
答:预处理->编译->汇编->链接
·预处理会处理#语句进行文本替换等一系列处理
·编译器编译代码形成汇编语言代码进行下一步操作
·将汇编低级代码编译成二进制机器代码
·链接工程中所依赖项(例如:库目标文件)
(2)指针和引用的定义
指针:
指向变量内存地址的一个变量,其数据类型取决于指向变量的数据类型。指针的本质是一个整数,代表指向变量的内存地址而已。
引用:
引用是给一个变量取别名,并且在创建时必须被初始化绑定。与指针不同的是引用并不是单独的变量去保存内存地址,而是直接对变量取别名。
(3)预处理
预处理指在编译过程中的第一个步骤进行预处理操作,比如头文件包含、宏定义文本替换、条件编译等等。
#define--宏定义,形式:#define 昵称 所替换文本
#include--包含文件,形式:#include<>,#include" "
#ifndef、#ifdef、#endif--条件编译,例如:#ifndef _FILE_H \n #define _FILE_H \n #endif
(防止头文件重复包含)
(4)#include<>和#include" "的区别
<>:用于包含IDE或者makefile文件配置好依赖项和编译器所带的标准头文件和系统头文件。
" ":优先扫描当前文件夹中的依赖项头文件,再搜索配置上的头文件。
提高了预处理效率。
(5)内存分配
内存分区:
·栈--栈区中存放函数调用信息、参数和局部变量等数据信息。
·堆--用于用户动态分配空间,需要手动管理内存空间
·全局区--存放静态变量、全局变量和常量等数据信息
非初始化的变量存储在BSS段,反之在数据段
·代码区--存放二进制代码
动态内存分配:
程序运行时根据需要进行动态的内存分配,通常使用堆内存空间。使用new和delete关键字来动态分配内存空间和释放内存。动态内存具有灵活性,但是需要手动得去释放内存避免内存泄漏。C++11中提倡使用智能指针去管理动态内存分配工作。
(6)new/delete关键字和malloc/free的区别:
(1)new/delete是操作符,malloc()/free()是C语言库函数。
(2)new/delete是可以被重载去自定义开辟空间以及自定义释放资源。
(3)new/delete会调用对象构造函数和析构函数,malloc()/free()需要手动得去调用。
(如果是比较偏向C/C++交叉编译的嵌入式就业方向,malloc/free的底层需要了解透)。
(7)const关键字
const是C++中的一种修饰符,使变量属性成为只读状态。
const在指针中的应用:
const int *ptr; //指针指向的值为只读,指针可以指向别的变量
int *const ptr; //指针无法指向别的变量,指针指向的值可以被修改
const int *const ptr;//指针指向的值为只读,指向无法指向别的变量
const在函数中的应用:
const int function_example(const std::string& str)const;
//返回值 修饰参数不能 修饰成员函数,内部成员变量不能在
//为 在函数体内 函数体内被修改,除非被mutable
//只读 被修改 关键字修饰
(8)const和#define的区别:
预处理环节中,#define相当于只做简单的文本替换,而不做类型安全检查,边界安全检查。
而const是有数据类型的一个常量。
(9)static关键字
一,函数和全局变量被static修饰:
将变量和函数的作用域限定在当前文件夹内,防止重名变量/函数在链接时出现二义性。
二,局部变量被static修饰:
表示变量在程序运行过程中仅仅被初始化一次,不会在调用过程中重复初始化。另外,变量的生命周期将延长到程序结束,但变量作用域依旧限定在栈的生命周期。
三,类内部成员变量和成员函数被static修饰:
成员变量:使得变量被所有的实例(对象)所共享使用。
成员函数:函数不需要被实例化的对象调用,而是直接通过类名调用。
例子:
#include<iostream>
static void function_example();
static int mark1=5; //链接时被外界代码文件无法访问mark1和function_example
class Example{
public:
static int mark4;
static void example2()
};
int Example::mark4=0;
void Example::example2(){
mark4++;
}
int main()
{
while(1){
static int mark2=100;
int mark3=mark2;
mark3-=10;
if(mark3==10)break;
} //括号内为一个栈帧,static可以使mark2只被初始化一次且延长其生命周期
Example::example2();
Example::example2();
return 0;
}
(10)面向对象
·封装:
将数据属性和操作打包在一个单元,通过公开接口提供对对象的访问。
·继承:
一个子类(派生类)从另一个父类(基类)继承基础属性和方法
·多态:
静态多态:
通过函数重载实现同一个作用域内定义多个相同函数,以参数列表区分实现多态
动态多态:
将基类的函数定义为虚函数,子类中定义其函数,再通过基类指针绑定到派生类对象来实现。
(11)虚函数和纯虚函数
#include<iostream>
class Father{
public:
virtual void Function1(){std::cout<<"father\n";}//虚函数
virtual void Function2()=0;//纯虚函数
}
class Children:public Father
{
public:
void Function1()override{std::cout<<"children1\n";}
void Function2()override{std::cout<<"children2\n";}
}
int main(){
Father* ft = new Children;
ft->Function1();
ft->Function2();
return 0;
}
另外,基类的析构函数也需要被声明为虚函数,派生类写属于自己的析构函数。实现多态过程中编译器先调用子类析构函数再调用父类析构函数。(析构函数不可被继承)
(12)虚函数表
虚函数表是用来实现动态多态的一种机制。每个含有虚函数的类都包含一个指向虚函数表的指针,该表内包含了虚函数指针。当一个对象被实例化时,其虚函数表被创建且初始化。当一个函数被声明为虚函数时,编译器通过虚函数表确定被调用的函数是哪个派生类中所实现的而实现动态多态。
(13) 虚基类-菱形继承
菱形继承问题:
菱形继承导致数据在继承过程中出现二义性。
A
/ \
/ \
B C
\ /
\ /
D
虚继承:
虚继承保证在继承树中的基类只被实例化一次,避免相同数据重复被继承。
class A{};
class B:virtual public A{};
class C:virtual public A{};
class D:public B,public C{};
(14)final关键字
类被修饰为final说明该类是不可被继承的
虚函数被修饰为final表示该虚函数不能被重写