分享面试总结,涉及C++、算法、数据结构、操作系统、计算机网络、Linux、数据库、设计模式等,后面持续更新~
内容多为一问一答式,多数来自收集。整理总结,视频、书籍学习所得,如有错误请指出,万分感谢!!!
学习建议:针对八股文,不太了解的可以网上扩展,自己总结,拿来主义最好能消化成自己的。
※代表高频问题(参考)
# C++篇--- √5
81. volatile关键字的作用?
关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。volatile用在以下的几个地方:
1)中断服务程序中修改的供其它程序检测的变量需要加volatile;
2)多任务环境下各任务间共享的标志应该加volatile;
3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义。
82. strcpy和memcpy的区别?
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
※扩展:手写这两个函数。
83. ※讲讲大端小端,如何检测?
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址端。小端模式,是指数据的高字节保存在内存的高地址中,低位字节保存在在内存的低地址端。
1) 直接读取存放在内存中的十六进制数值,取低位进行值判断
int a = 0x12345678;
int *c = &a;
c[0] = 0x12 大端模式
c[0] = 0x78 小端模式
2) 用共同体来进行判断
union共同体所有数据成员是共享一段内存的,后写入的成员数据将覆盖之前的成员数据,成员数据都有相同的首地址。Union的大小为最大数据成员的大小。
union的成员数据共用内存,并且首地址都是低地址首字节。int i= 1时:大端存储1放在最高位,小端存储1放在最低位。当读取char ch时,是最低地址首字节,大小端会显示不同的值。
84. 空类会默认添加哪些?
1) Empty(); // 缺省构造函数2) Empty( const Empty& ); // 拷贝构造函数
3) ~Empty(); // 析构函数
4) Empty& operator=( const Empty& ); // 赋值运算符
扩展:怎么写?
85. ※空类的大小是多少?为什么?
1) C++空类的大小不为0,不同编译器设置不一样,vs设置为1;2) C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址;
3) 带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定;
4) C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。
86. C++标准库?
1) C++标准库可以分为两部分:标准函数库:这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自C语言。
面向对象类库:这个库是类及其相关函数的集合。
2) 输入/输出I/O、字符串和字符处理、数学、时间、日期和本地化、动态分配、其他、宽字符函数。
3) 标准的C++I/O类、String类、数值类、STL容器类、STL算法、STL函数对象、STL迭代器、STL分配器、本地化库、异常处理类、杂项支持库。
87. 为什么拷贝构造函数必须传引用不能传值?
1) 拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。2) 参数传递过程到底发生了什么?
将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)。
值传递:
对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);
如void foo(class_type obj_local){},如果调用foo(obj);首先class_type obj_local(obj),这样就定义了局部变量obj_local供函数内部使用。
引用传递:
无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值。而地址总是指针类型(属于简单类型),。显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型)。
上述1),2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用,内存溢出。
拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归。
88. 大端小端了解吗?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择,是大端模式还是小端模式。
请网上自行查阅、扩展!包括代码实现。
89. this指针调用成员变量时,堆栈会发生什么变化?
当在类的非静态成员函数访问类的非静态成员时,编译器会自动将对象的地址传给作为隐含参数传递给函数,这个隐含参数就是this指针。即使你并没有写this指针,编译器在链接时也会加上this的,对各成员的访问都是通过this的。例如你建立了类的多个对象时,在调用类的成员函数时,你并不知道具体是哪个对象在调用,此时你可以通过查看this指针来查看具体是哪个对象在调用。this指针首先入栈,然后成员函数的参数从右向左进行入栈,最后函数返回地址入栈。90. ※设计一个类计算子类的个数?
这题面微信手撕代码问过。1. 为类设计一个static静态变量count作为计数器;
2. 类定义结束后初始化count;
3. 在构造函数中对count进行+1;
4. 设计拷贝构造函数,在进行拷贝构造函数中进行count+1操作;
5. 设计复制构造函数,在进行复制函数中对count+1操作;
6. 在析构函数中对count进行-1;
91. 怎么快速定位错误出现的地方?
1. 如果是简单的错误,可以直接双击错误列表里的错误项或者生成输出的错误信息中带行号的地方就可以让编辑窗口定位到错误的位置上。2. 对于复杂的模板错误,最好使用生成输出窗口。多数情况下出发错误的位置是最靠后的引用位置。如果这样确定不了错误,就需要先把自己写的代码里的引用位置找出来,然后逐个分析。
92. 虚函数的代价?
1) 带有虚函数的类,每一个虚函数类会产生一个虚函数表,用来存储指向虚成员函数的指针,会增大类的大小;2) 带有虚函数的类的每一个对象,都会有有一个指向虚表的指针,会增加对象的空间大小;
3) 不能再是内联的函数,因为内联函数在编译阶段进行替代,而虚函数在运行阶段才能确定到低是采用哪种函数。
93. 类对象的大小?
1) 类的非静态成员变量大小,静态成员不占据类的空间,成员函数也不占据类的空间大小;2) 内存对齐另外分配的空间大小,类内的数据也是需要进行内存对齐操作的;
3) 虚函数会在类对象插入vptr指针,加上指针大小;
4) 当该该类是某类的派生类,那么派生类继承的基类部分的数据成员也会存在在派生类中的空间中,也会对派生类进行扩展。
通常笔试题会考察此类。
94. 移动构造函数?
1)通常会遇到这样一种情况:我们用对象a初始化对象b后,对象a我们就不在使用了,但是对象a的空间还在(析构之前)。既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本,这就是移动构造函数设计的初衷;2) 拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制;
3) C++引入了移动构造函数,专门处理这种情况,用a初始化b后,就将a析构的情况;
4) 与拷贝类似,移动也使用一个对象的值设置另一个对象的值。但是和拷贝不同的,移动实现的是对象值真实的转移(源对象到目的对象)。即源对象将丢失其内容,其内容将被目的对象占有。移动操作的发生的时候,是当移动值的对象是未命名的对象的时候。这里未命名的对象就是那些临时变量,甚至都不会有名称。典型的未命名对象就是函数的返回值或者类型转换的对象。使用临时对象的值初始化另一个对象值,不会要求对对象的复制,因为临时对象不会有其它使用。因此,它的值可以被移动到目的对象。想做到这些,就要使用移动构造函数和移动赋值(当使用一个临时变量对象进行构造初始化的时候,调用移动构造函数)。类似的,使用未命名的变量的值赋给一个对象时,可调用移动赋值操作。
95. 何时需要合成构造函数?
1) 如果一个类没有任何构造函数,但含有一个成员对象,该成员对象含有默认构造函数,那么编译器就为该类合成一个默认构造函数,因为不合成一个默认构造函数那么该成员对象的构造函数不能调用;2) 没有任何构造函数的类派生自一个带有默认构造函数的基类,那么需要为该派生类合成一个构造函数,只有这样基类的构造函数才能被调用;
3) 带有虚函数的类,虚函数的引入需要进入虚表,指向虚表的指针,该指针是在构造函数中初始化的,所以没有构造函数的话该指针无法被初始化;
4) 带有一个虚基类的类:
并不是任何没有构造函数的类都会合成一个构造函数。
编译器合成出来的构造函数并不会显示设定类内的每一个成员变量。
何时需要合成复制构造函数?
有三种情况会以一个对象的内容作为另一个对象的初值:
1) 对一个对象做显示的初始化操作,X xx = x;
2) 当对象被当做参数交给某个函数时;
3) 当函数传回一个类对象时;
理解:
1) 如果一个类没有拷贝构造函数,但是含有一个类类型的成员变量,该类型含有拷贝构造函数,此时编译器会为该类合成一个拷贝构造函数;
2) 如果一个类没有拷贝构造函数,但是该类继承自含有拷贝构造函数的基类,此时编译器会为该类合成一个拷贝构造函数;
3) 如果一个类没有拷贝构造函数,但是该类声明或继承了虚函数,此时编译器会为该类合成一个拷贝构造函数;
4) 如果一个类没有拷贝构造函数,但是该类含有虚基类,此时编译器会为该类合成一个拷贝构造函数。
96. 构造函数的扩展过程?
1) 记录在成员初始化列表中的数据成员初始化操作会被放在构造函数的函数体内,并与成员的声明顺序为顺序;2) 如果一个成员并没有出现在成员初始化列表中,但它有一个默认构造函数,那么默认构造函数必须被调用;
3) 如果class有虚表,那么它必须被设定初值;
4) 所有上一层的基类构造函数必须被调用;
5) 所有虚基类的构造函数必须被调用。
构造函数的执行算法?
1) 在派生类构造函数中,所有的虚基类及上一层基类的构造函数调用;
2) 对象的vptr被初始化;
3) 如果有成员初始化列表,将在构造函数体内扩展开来,这必须在vptr被设定之后才做;
4) 执行程序员所提供的代码。
析构函数被扩展的过程?
1) 析构函数函数体被执行;
2) 如果class拥有成员类对象,而后者拥有析构函数,那么它们会以其声明顺序的相反顺序被调用;
3) 如果对象有一个vptr,现在被重新定义
4) 如果有任何直接的上一层非虚基类拥有析构函数,则它们会以声明顺序被调用;
5) 如果任何虚基类拥有析构函数。
97. ※哪些函数不能是虚函数?
1) 构造函数,构造函数初始化对象,派生类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每一个类有一个虚表,每一个对象有一个虚表指针,虚表指针在构造函数中初始化;2) 内联函数,内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数;
3) 静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。
4) 友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
5) 普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。
98. sizeof和strlen的区别?
1) strlen计算字符串的具体长度(只能是字符串),不包括字符串结束符,返回的是字符个数。2) sizeof计算声明后所占的内存(字节大小),不是实际长度。
3) sizeof是一个取字节运算符,而strlen是个函数。
4) sizeof的返回值=字符个数×字符所占的字节数,字符实际长度小于定义的长度,此时字符个数就等于定义的长度。若未给出定义的大小,分类讨论,对于字符串数组,字符大小等于实际的字符个数+1;对于整型数组,字符个数为实际的字符个数。字符串每个字符占1个字节,整型数据每个字符占的字节数需根据系统的位数类确定,32位占4个字节。
5) sizeof可以用类型做参数,strlen只能用char*做参数,且必须以‘\0’结尾,sizeof还可以用函数做参数;
6) 数组做sizeof的参数不退化,传递给strlen就退化为指针。
99. 简述strcpy、sprintf与memcpy的区别?
1) 操作对象不同① strcpy的两个操作对象均为字符串
② sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串
③ memcpy的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
2) 执行效率不同
memcpy最高,strcpy次之,sprintf的效率最低。
3) 实现功能不同
① strcpy主要实现字符串变量间的拷贝
② sprintf主要实现其他数据类型格式到字符串的转化
③ memcpy主要是内存块间的拷贝。
100. 局部变量和全局变量的问题?
1) 局部会屏蔽全局。要用全局变量,需要使用"::"局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。2) 如何引用一个已经定义过的全局变量,可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。
3) 全局变量可不可以定义在可被多个.C文件包含的头文件中,在不同的C文件中以static形式来声明同名全局变量。可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。
未完待续~
需资料分享,可私聊哈。
全部评论
(2) 回帖