八股文-C++
1.简述C++的特点:
1.面向对象,同时兼容C语言
2.(1)封装(2)继承(3)多态
3.可读性好
4.效率高
5.更加安全。增加了 **const常量、引用、四类cast转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)、智能指针、try-catch等等[新特性。**]()
6.可复用性高,引入stl(模板)概念。
7.nullptr、auto变量、Lambda匿名函数、智能指针
const知识点:
T* const a;//指针指向不可改,数据可改.
引用知识点:
int &a=b;相当于别名,等价于int* const a=&b;
1.引用必须初始化,且初始化后不可更改
2.引用不能返回局部变量
3.引用的函数调用可以作为左值.
const int &a=10相当于const int* const a=10;//指向不可修改,内容不可修改,防止误操作
四类cast知识点:
1.static_cast:可以转换:基本类型,子父类指针互换转换.子父类引用互相转换.
2.dynamic_cast:可以转换:子父类指针互换转换.子父类引用互相转换.但之前需要检查对象类型.(子类转换成父类一般是安全的)
3.const_cast:指针,对象,对象执政,对象引用,用于取消常量. 4.reinterpret_cast:不同类型之间的转换
2.说说C语言和C++的区别
1.c语言是C++的子集,C++可以很好兼容C语言,但是有新特性
2.c语言是面向过程的语言,C++是面向对象的语言
3.C语言有很多不安全的语言特性,指针危险,强转的不确定性,内存泄漏
而C++引入新特性。
4.C++可复用性高,引入模板更灵活更通用。
面向过程:按照步骤解决问题就是面向过程。自顶向下,逐步细化!
面向对象:任何一个操作或者是业务逻辑的实现都需要一个实体来完成,也就是说,实体就是动作的支配者,没有实体,就肯定没有动作发生!
3.说说struct和Class的区别
1. struct:描述数据结构的集合
class:描述一个对象数据的封装
2.struct : 默认控制权限是public的,而class中默认的访问控制权限是private的
struct A{
int iNum;//访问权限为public
}
class B{
int iNum;//访问权限为private
}
3.struct默认是公有继承,class是私有继承
4.class关键字可以定义模板参数,而struct不行:
template<typename T,typename Y> //可以把typename换成class
int Func(const T&t,const Y&y){
//TODO
}
4.c++和c在struct方面的区别
1.c不能有成员函数,而c++可以有
2.c不能有静态成员,而C++可以有
3.c默认访问控制是public,不可改,而c++都包含(public\private\protected)
4.c不可以继承,而c++可以
5.c不能初始化数据成员,而c++可以
2.使用上,C需要加上关键字struct,而C++可以省略.
struct Student{
int a;
}
struct Student stu1;//c使用
Student stu2;//c++使用
5.说说include头文件的顺序以及双引号""和尖括号<>的区别
1.<>是系统文件,""是自定义文件
2.查找头文件的路径不一样.<>:编译器设置的头文件路径-系统变量
""当前头文件路径-编译器头文件路径-系统变量
6.导入c函数的关键字是什么,C++编译时和C有什么不同?
1.关键字:导入C函数的关键字是extern,
如:
extern void print( int i);
2.编译区别:C++支持函数重载,因此编译器编译函数会把参数类型加入函数名中。而C语言不支持函数重载,因此函数不会带上参数名。
7.简述C++从代码到可执行二进制文件的过程:
都是:预编译、编译、汇编、链接
(1)预编译:操作如下:
1.删除所有#define,展开宏定义
2.处理所有条件预编译指令,如#if、#ifdef、#ifndef(这二者主要用于防止重复包含)
3.处理所有#include,将被包含文件插入到该预编译指令的位置。
4。过滤所有注释
5.添加行号和文件名标识
(2)编译:操作如下:
1.词法分析:分割符号
2.语法分析:产生树
3.语义分析:判断是否有意义
4.代码优化:
5.目标代码生成:生成汇编代码
6.目标代码优化:
(3)汇编:将汇编代码转换为可执行指令
(4)链接:将不同的源文件产生的目标文件进行链接,从而形成一个可执行2进制文件。
链接分为静态和动态:
1.静态:windows下是.lib文件,linux是.a文件
2.动态:执行时链接,windows下是.dll文件,linux是.so文件
8.说一说static关键字的作用
1.定义全局静态变量和局部静态变量:
在变量前加上static,初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存,直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不同
全局静态变量:全局变量在其定义后所有函数都能用
局部静态变量:静态局部变量的生存期虽然为整个源程序,
但是其作用域仍与自动变量相同,即只能在定义该变量的
函数内使用该变量。退出该函数后, 尽管该变量还继续存
在,但不能使用它。
2.定义静态函数:
在函数返回类型前加上static关键字,函数即被定义为静态函数。静态函数只能在本源文件中使用;
3.定义静态变量:
static int a;
static void func();
4.static关键字可以用于定义类中的静态成员变量: 使用静态数据成员,它既可被当成全局变量那样去存储,但又被隐藏在类的内部,类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间。
5.static关键字可以用于定义类中的静态成员函数: 与静态成员变量类似,共享这一块静态存储空间。
9.数组和指针的区别
1.赋值:
同类型指针变量可以相互赋值,而数组只能一个个拷贝
2,存储方式:
数组:内存连续存放,开辟连续空间,根据下标访问,存在静态区或栈上。
指针:可以指向任意类型数据,指针存储空间不确定。
3.求sizeof: 数组所占空间的内存大小:sizeof(数组名)/sizeoof(数据类型)
32bit平台下,sizeof都是4 64bit平台下,sizeof都是8
4.初始化:
int a[5] ={0};
int *p =new int(0);
5.指针操作:
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组
p = a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
//所以数组指针也称指向一维数组的指针,亦称行指针。
//访问数组中第i行j列的一个元素,有几种操作方式:
//*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]。
//其中,优先级:()>[]>*。
//这几种操作方式都是合法的。
10.说一说什么是函数指针,如何定义函数指针,有什么使用场景
1.定义:函数指针就是指向函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针指向的地址。
2.定义形式如下:
int func(int a);
int (*f)(int a);
f= &func;
3.应用场景: 其实函数指针和数组结合(指针数组)可以实现典型的表驱动, 表驱动通过将相同类型的函数以指针的形式储存在数组中, 在调用过程中直接可以通过索引调用即可, 减少了if else条件的嵌套, 大大降低了程序的圈复杂度, 提高了效率:
int Add(int a, int b);
int Minus(int a, int b);
int Multiply(int a, int b);
int Divide(int a, int b);
int main() {
int choice = 0;
int result = 0;
//函数指针类型
typedef int(*T)(int, int);
T menu[] = {
Add,
Minus,
Multiply,
Divide
};
printf("1.加法\n");
printf("2.减法\n");
printf("3.乘法\n");
printf("4.除法\n");
printf("请选择:");
scanf("%d", &choice);
if (choice > 0 && choice < 5) {
//等同result = (*menu)[choice - 1](4, 2);
result = menu[choice - 1](4, 2);
}
printf("%d\n", result);
return 0;
}
int Add(int a, int b) {
return a + b;
}
int Minus(int a, int b) {
return b - a;
}
int Multiply(int a, int b) {
return a * b;
}
int Divide(int a, int b) {
if (b != 0) {
return a / b;
}
return 0;
}
应用场景2:回调, 我们调用别人的API函数,称为call,如果别人的库里面调用我们的函数,就叫Callback
如qsort里面的最后一个参数cmd,传入的就是函数指针。
11.静态变量什么时候初始化:
对于C: 初始化发生在任何代码执行之前,属于编译期初始化
对于C++:首次用到才会构造。
答案解析: 1.作用域:C++里作用域可分为6种:全局、局部、类、语句、命名空间、文件作用域
静态全局变量:全局作用域+文件作用域,所以无法在其他文件中使用。
静态局部变量:局部作用域,只被初始化一次,直到程序结束。
类静态成员变量:类作用域。所有对象共享类的静态成员变量,静态成员函数只能直接访问静态成员变量和静态成员函数
2.所在空间:都在静态存储区,因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。
3.生命周期:静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存,类静态成员变量在静态存储区,超出类作用域时回收内存。
12.nullptr调用成员函数可以吗?为什么?
能,因为: 在编译时对象就绑定了函数地址,和指针空不空无关。
class animal{
public:
void sleep(){cout<< "animal sleep"<<endl;}
void breathe(){cout<<"animal breathe haha"<<endl;}
};
class fish:public animal{
public:
void breathe(){ cout<<"fish bubble"<<endl;}
};
int main(){
animal *pAn=nullptr;
pAn->breathe();
fish *pFish =nullptr;
pFish->breathe();
return 0;
}
因为:在编译时对象就绑定了函数地址,和指针空不空没关系。pAn->breathe();编译时,函数的地址就和pAn就绑定了,调用breathe(*this),this就等于pAn。由于函数中没有需要解引用this的地方,所以函数运行不会出错
但是如果用到了this,因此this=nullptr,运行出错。
13.说说什么是野指针,怎么产生,如何避免?
1.定义:野指针就是指针指向不可知(随机的、不正确的、没有明确限制)
2.产生原因:释放内存后指针不及时置空(野指针)、依然指向该内存,那么可能出现非法访问错误。这些我们都要避免
3.避免方法:
(1)初始化置为NULL;
int*p =NULL;
(2)申请内存后判空
p=(int *)malloc(sizeof(int)*n);
assert(p!=NULL);//申请后内存判空
p = (int *)realloc(p,25);
(3)指针释放后置NULL;
free(p);
p=NULL;
(4)使用智能指针;
智能指针:
一种是共享指针shared_ptr
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。
从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。
一种是独享指针unique_ptr
unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。
一种是弱指针weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。
一种是很久没用过的auto_ptr(被unique_ptr替代了)
14.说说静态局部变量,全局变量,局部变量的特点,以及使用场景
1.作用域:C++里作用域可分为6种:全局、局部、类、语句、命名空间、文件作用域
静态全局变量:全局作用域+文件作用域,所以无法在其他文件中使用。
静态局部变量:局部作用域,只被初始化一次,直到程序结束。
类静态成员变量:类作用域。所有对象共享类的静态成员变量,静态成员函数只能直接访问静态成员变量和静态成员函数。
2.所在空间:都在静态存储区,因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。
3.生命周期:静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存,类静态成员变量在静态存储区,超出类作用域时回收内存。
15.说说内联函数和宏函数的区别: 1.宏定义不是函数,但使用起来想函数,预处理器用复制宏代码的方式代替了函数的调用,省去了函数压栈退栈过程,提高了效率, 2.宏函数是在预编译的时候把所有宏名用宏体替换,简单说就是字符串替换;而内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样就可以省去函数的调用的开销,提高效率。 3.宏定义是没有类型检查的,无论对还是错都是直接替换;而内联函数则在编译的时候进行检查,返回值,参数列表等。
16.说一说i++和++i的区别
1.赋值顺序不同。
2.效率不同。
自定义类
由于要生成临时对象,i++ 需要调用两次拷贝构造函数与析构函数(将原对象赋给临时对象一次,临时对象以值传递方式返回一次); ++i由于不用生成临时变量,且以引用方式返回,故没有构造与析构的开销,效率更高。
所以在使用类等自定义类型的时候,应尽量使用++i。
3,i++不能作为左值,而++i可以:
++i=....;->
//等价于
i=i+1;
i=....;
4.两者都不是原子操作。
17.说说new和malloc的区别,各自底层实现原理
1new是操作符,malloc是函数
2new在调用时先分配内存,在调用构造函数,释放的时候调用析构函数,而malloc采用内存池的管理方式,以减少内存碎片。
3,malloc需要给定申请内存大小,返回的指针需要强转;new会调用构造函数,不用指定内存大小,返回指针不用强转。
4.new可以被重载;malloc不行
5.new分配内存更直接和安全。
6.new发出错误抛出异常,malloc返回null
18.说说const和define的区别
1。定义常量时:
const在编译的时候创建,而define在预处理的时候创建
2.const定义的常量,是存储在内存中,需要额外存储空间,而define运行时直接使用操作数。
3.const的常量带类型,define不带。
19.说说C++函数指针和指针函数的区别。
1.函数指针是指向函数入口地址的指针,指针函数是返回值为指针的函数。
2.写法不同,函数指针需要增加(),而指针函数不用
3.用法不同,函数指针变量可以作为某个函数的参数来使用的,所以函数指针可以用于回调函数,例如qsort源码中,参数的最后一个比较就是函数指针。指针函数应用范围更广
20.const int* a、int const* a、const int a、int const a、const int const a的区别
1.const int*a与2同
2,int const*a代表定义了一个int型指针,指针指向常量,指针指向可以修改,但值不许修改。
3.const int a代表定义了一个int型常量,不可修改
4,int *const a代表定义了一个int型指针常量,指针指向不允许修改,值可以修改
5.const int* const a代表指针指向不可变,值不可变
21.说说使用指针需要注意什么?
1.定义先初始化
2.用new或malloc申请时,应该检查指针是否为NULL.
3.数组赋初值
4.避免越界
5.防止野指针,free()以后设置为NULL
22.说说内联函数和函数的区别,内联函数的作用
1.内联函数比普通函数多inline
2.内联函数不用函数调用,省去开销,不用寻址.
3.内联函数体代码要简单,不然得不偿失
4.内联函数是将表达式用内联函数体替换.
23.简述C++有几种传值方式?之间的区别是什么?
传参方式有这三种:值传递,引用传递,指针传递
1.值传递:传递的是值,形参改变,实参不变.
2.引用传递:传递的是引用,形参改变,实参改变,安全高效,不发生拷贝行为,只是绑定对象.
3.指针传递:传递的是指针,形参改变,实参也变,但不如引用安全,因为指向可能发生改变
内存篇:
1.简述一下堆和栈的区别:(关键词:存储\速度\线程\垃圾回收)
1.堆栈空间分配不同,栈由操作系统自动分配释放,存放函数的参数值,局部变量的值等;堆一般由程序员分配释放. 2.缓存方式不同:栈使用的是一级缓存,通常被调用时处于存储空间中,调用完毕立即释放;堆则是存放在二级缓存中,速度要慢些. 3.数据结构不同:堆类似数组结构,栈类似栈结构,先进后出. 4.栈的要小些,堆要大些.
2.简述C++的内存管理
1.内存分配方式:
内存分为五个区:堆,栈,自由存储区,全局/静态存储区和常量存储区.
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元被释放.
堆:就是那些malloc等分配的内存块,用free来结束自己的生命.
自由存储区:由new分配的内存块,一般一个new就要对应一个delete.
全局/静态存储区:全局变量和静态变量被分配到同一块内存.
常量存储区:这是比较特殊的存储区,里面存放的是常量,不允许修改.
2.常见的内存错误:
(1)内存分配未成功,但是使用了它.
(2)内存分配虽成功,但是尚未初始化就引用它.->先初始化
(3)内存分配成功且已经初始化,但操作越过了内存的边界->避免数字或指针下标越界.
(4)**忘记释放内存,**造成内存泄漏->申请与释放匹配
(5)释放内存却继续使用->置空防止野指针.
3.内存泄漏的处理办法
及时释放.
将分配的内存指针以链表的形式自行管理,使用完毕就从链表删除,程序结束可检查该链表.
智能指针.
3.malloc和局部变量分配在堆还是栈
malloc是在 **堆上分配内存** ,需要程序员自己回收内存
局部变量在**栈中分配内存**,超过作用域就自动回收.
4.程序有哪些section,分别的作用,程序的启动过程,怎么判断数据分配在栈上还是在堆上
section:
1.数据段:
存放程序中已初始化的全局变量和静态变量的一块内存区域
2.代码段
存放程序执行代码的一块内存区域,只读,代码段头部还会包含一些制度的常数类型
3.bss段
存放程序中未初始化的全局变量和静态变量
4.可执行程序在运行时又会多出两个区域:堆区和栈区:
堆区:动态申请内存用,堆从低地址向高地址增长.
栈区:存储局部变量,函数参数值.栈从高地址向低地址增长.是一块连续的空间.
5.共享区:
位于堆和栈之间
程序启动过程:
1.操作系统首先根据相应的进程并分配私有的进程空间.然后操作系统的加载器负责把可执行文件的数据段和代码映射到进程的虚拟内存空间去.
2.加载器读入可执行程序的导入符号表,根据这些符号表可以查找出该可执行程序的所有依赖的动态链接库.
3,加载器针对该程序的每一个动态链接库调用LOADLibrary
(1)查找对应的动态库文件,加载器为该动态链接库确定一个合适的基地
(2)加载器读取该动态链接库的导入符号表和导出符号表,比较应用程序的导入符号是否匹配该库的导出符号.
(3)针对该库的导入符号表,查找对应的依赖的动态链接库,如有跳转,则跳到3
(4)调用该动态链接库的初始化函数
4初始化应用程序的全局变量,对于全局对象自动调用构造函数.
5.进入应用程序入口点函数开始执行.
怎么判断数据分配在栈上还是堆上:
局部变量在栈上,malloc在堆上,new申请的空间在自由存储区,借由堆来实现自由存储.
5.初始化为0的全局变量在bss还是data:
bss上存的都是:未初始化的或全部初始化为0的全局变量和静态变量的一块内存区域,特点是可读可写.在执行之前会全部清零.
data上存放已经全部初始化的全局变量和静态变量.
所以存放在bss
6.什么是内存泄漏,怎么检测?
含义:申请了一片内存,使用完毕没有完全释放掉.
如:
1.new和malloc申请资源后,没有用delete和free掉.
2,子类继承父类时,父类析构函数不是虚函数
3.Windows句柄资源使用后没有释放掉
怎么检查:
1.良好的编码喜欢,及时释放
2.将分配的内存的指针用链表连接起来,使用完毕删除,还可检查
3.使用智能指针
7.请简述一下atomoic内存顺序:
有 1. memory_order_relaxed:在原子类型上的操作以自由序列执行,没有任何同步关系.
memory_order_consume:memory_
8.简述C++的内存对齐使用场景:
一般用于三种结构: struct/class/union
原则有4个: 1.数据成员对齐规则:struct/union的数据成员,第一个数据成员放在offset为0的地方,以后每一个数据成员存储的起始位置都从该成员大小或者成员的子成员大小的整数倍开始.
2.结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储.
3.收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的"最宽基本类型成员的"整数倍
4.sizeof(union)以结构里面size最大元素union的size,因为在某一时刻,union只有一个成员真正存储于该地址.
面向对象
1.简述一下什么是面向对象:
1.面向对象是编程思想,把一切事务看成一个个对象,每个对象有不同的属性,每个对象的属性和行为打包成一个整体,就是一个类.
2.面向过程就是类似流程图,有一个完整的流程,根据逻辑从上至下写代码.
2.简述一下面向对象的三大特征:
封装\继承\多态
1.封装,将属性和行为封装成一个整体, 隐藏属性和实现细节,只需要对外公开接口调用和交互
2.继承:可以使用现有的类的所有功能,无需重写代码就可以实现功能扩展.
3.多态: 用父类型别的指针指向其子类别的实例,然后通过父类的指针调用时机子类的成员函数,实现多态.有两种方式,重写,重载.
3.简述一下C++的重载和重写,以及他们的区别
4.
笔试题目:
(1)派生类可以从基类继承的函数是( D )?
构造函数
析构函数
赋值操作函数
重载函数
(2)