C++面试题系列一:简答题(1)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LuyaoYing001/article/details/81702020

1.C++支持多重继承,请问多重继承中的菱形继承(diamond problem)指什么,请举例说明并指出一个解决方案。

菱形继承是在继承关系层次图中,构成一个菱形的回路,比如C++标准库中形成iostream类的多重继承:ios有两个派生类ostream和istream,而类iostream同时继承ostream和istream,因此iostream类中可能包含重复子对象(即ostream和istream从ios继承的数据)。这个问题可能在iostream指针向上转换为ios指针时出现,这时会导致歧义性,造成语法错误 ambiguous conversion from 'class iostream *' to 'class ios *'
这种重复子对象问题可以用virtual继承解决。基类用virtual继承时,派生类中只出现一个子对象,这个过程称为虚拟基类继承。
class ostream: virtual public ios{};
class istream: virtual public ios{};

2.递归函数能否声明为inline?

inline在编译期间会在调用处被展开,由于编译器在编译时无法确定递归调用的次数,所以无法展开inline函数。除非能够给编译器指定递归函数被调次数,否则inline函数不能被递归调用。

3.解释关键字volatile和mutable。

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。简单地说,就是防止编译器对代码的优化。
mutable:const_cast运算符允许强制转换常量性,C++提供了一个存储类说明符(storage class specifier)mutable代替const_cast。即使在const对象和const成员函数中,mutable数据成员也是可以修改的,不需要强制转换常量性。

4.#include<>与#include”“的区别?

优先搜索路径不同。引号编译器会优先搜索当前工作目录下的头文件,尖括号会优先搜索标准库头文件所在目录。

5.怎样定义一个纯虚函数?纯虚函数与抽象类的关系?抽象类的特点?

将虚函数赋值为0即可得到一个纯虚函数;包含纯虚函数的类是抽象类。抽象类有以下特点:不能实例化;不能作为函数参数及函数返回类型;可以定义抽象类类型的指针。
抽象类一般作为基类使用。对于一个抽象基类,如果其派生类没有重新定义基类的虚函数,则派生类也还是抽象类,只有派生类重新定义了基类的虚函数,派生类才不再是抽象类,才是一个可以建立对象的具体类。

6.malloc/free与new/delete的区别?

1)malloc/free是C/C++标准库函数,而new/delete是C++中的操作符
2)malloc申请的内存用free函数释放,而new生成的对象用delete操作符释放
3)malloc申请内存时,需要我们指定申请的空间大小,且返回的类型为void*,需要将其强制转换为所需类型指针;new申请内存时,会根据所申请的类型自动计算申请空间的大小,且可直接返回指定类型的指针
4)malloc/free申请释放内存时,不需要调用构造/析构函数,而new/delete申请释放内存时需要调用构造/析构函数

7.动态分配与静态分配的区别?

动态分配是指在编译期间无法确定需要分配的内存大小,在程序运行中,等内存大小确定以后才申请内存。静态分配在编译期间就分配好已知大小的内存空间。动态分配可以发生在堆或栈上,而静态分配只能是栈。

8.构造函数的列表初始化顺序?哪三种情况是必须的?

  1. const数据成员 2.引用数据成员 3. 没有默认构造函数的成员对象

9.深拷贝与浅拷贝的区别?

浅拷贝就是两个对象共享一块内存,任何一方的变动都会影响到另一方,而且当析构一个源对象后,拷贝对象也不复存在,如果再使用它就会发生错误。深拷贝就是完完全全的复制出一个对象,两者在内存上互相独立。

10.请解释静态局部变量(在函数中定义)与静态数据成员(在类中定义)的不同特点。

静态局部变量:
1. 在程序执行到该对象的声明处时被首次初始化,且以后不再初始化;
2. 一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
3. 始终驻留在全局数据区,直到程序运行结束,但作用域是局部的,当定义它的函数或语句块结束时,其作用域随之结束。
静态数据成员:
1. 只分配一次内存,供所有对象实例共享。但不属于特定的类对象,在没有产生类对象时其作用域就可见。
2. 在定义时要分配空间,所以不能在类声明时进行初始化(const static变量除外)。静态数据成员的初始化格式为:数据类型 类名:: 静态数据成员名=值。

11.堆和栈的区别?

1)栈,由编译器自动管理,无需我们手工控制;堆,申请释放工作由程序员控制
2)堆的生长方向是向上的,也就是向着内存地址增加的方向;栈的生长方向是向下的,是向着内存地址减小的方向增长。 3)对于堆来讲,频繁的
new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题 4)一般来讲在 32
位系统下,堆内存可以达到4G的空间,但是对于栈来讲,一般都是有一定的空间大小的(2M)。

12.#define与typedef的区别?

1) #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查。
2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名。
typedef int * pint ;
#define PINT int *
const pint p ; //pint是一种指针类型, p不可更改,但p指向的内容可更改
const PINT ; //p可更改,但是p指向的内容不可更改

13.dynamic_cast的作用?

dynamic_cast将一个基类对象指针(或引用)(需指向派生类或为派生类的引用)cast到派生类指针。dynamic_cast会根据基类指针是否真正指向继承类对象来做相应处理,即会作一定的判断。对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针; 对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。

14.STL算法中partition算法的用法?

对指定范围内的元素重新排序,使用输入的函数,把结果为true的元素放在结果为false的元素之前。stable_partition版本保留原始容器中的相对顺序。如使数组中奇数位于偶数前面。

15.malloc、alloc、calloc、realloc的区别?

alloc:唯一在栈上申请内存的,无需释放;
malloc:在堆上申请内存,最常用;
calloc:malloc+初始化为0;
realloc:将原本申请的内存区域扩容,参数size大小即为扩容后大小,因此此函数要求size大小必须大于ptr内存大小。

16.不能声明为虚函数的几种情况?

1)普通函数(非类成员函数)(不能被覆盖)
2)友元函数(C++不支持友元函数继承)
3)内联函数(编译期间展开,虚函数是在运行期间绑定)
4)构造函数(没有对象不能使用虚函数,先有构造函数后有虚函数,虚函数是对对象的动作(构造函数不能继承))
5)静态成员函数(只有一份大家共享)

17.memcpy的用法与strcpy之间的区别?

memcpy函数的功能是从源指针所指的内存地址的起始位置开始拷贝n个字节到目标指针所指的内存地址的起始位置中。
void *memcpy(void *dest, const void *src, size_t n); //函数返回指向dest的指针
strcpy和memcpy主要有以下3方面的区别。
1)复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2)复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符”\0”才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3)用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。

18.static全局变量与全局变量的区别?

1)全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern 全局变量名的声明,就可以使用全局变量。
2)静态全局变量是显式用static修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用extern声明也不能使用。

19.rand()函数的用法?srand函数的用法?

rand();
srand((unsigned int)(time NULL));

20.什么是常量表达式?

常量表达式是指不会改变并且在编译过程就能得到计算结果的表达式。
新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

21.友元函数及友元类?

类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。 友元声明只能出现在类的内部。

22.explicit关键字的作用?

关键字explicit只能作用于只有一个实参的构造函数(需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit的)。只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复。
当使用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用。而且,编译器将不会在自动转换过程中使用该构造函数。

23.智能指针有哪几种?

shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。

24.什么时候会递增递减shared_ptr的引用计数?

赋值,拷贝,向函数传递一个智能指针,或函数返回一个智能指针都会增加当前智能指针的计数;向一个shared_ptr赋予一个新值或者一个shared_ptr被销毁时,计数器就会递减。

25.默认构造函数?

默认构造函数(default constructor)就是在没有显式提供初始化式时调用的构造函数。它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义。
如果提供任何构造函数,那么编译器将自动生成一个默认的无参构造函数,一旦定义了构造函数,将不再生成默认的构造函数。 有多种原因,需要提供默认构造函数:
1) 当你使用静态分配的数组,而数组元素类型是某个类的对象时,就要调用默认的构造函数。
2) 当你使用动态分配的数组,而数组元素类型是某个类的对象时,就要调用默认的构造函数,因为new操作符要调用Object类的无参构造函数类初始化每个数组元素。
3) 当你使用标准库的容器时,如果容器内的元素类型是某个类的对象时,那么这个类就需要默认的构造函数,原因同上。
4) 一个类A以另外某个类B的对象为成员时,如果A提供了无参构造函数,而B未提供,那么A则无法使用自己的无参构造函数。
5) 类A定义了拷贝构造函数,而没有提供默认的构造函数,B继承自A,所以B在初始化时要调用A的构造函数来初始化A,而A没有默认的构造函数,故产生编译错误。

26.STL算法中的copy算法

原型:copy(vec.begin(),vec.end(),dest.begin());dest容器的空间要足够大。另有copy_n(vec.begin(),n,dest.begin());move(vec.begin(),vec.end(),dest.begin())与此类似。

27.什么时候使用拷贝构造函数?

一个对象显式或隐式初始化另一个同类型的对象。
函数的非引用传参。
函数非引用返回一个对象。
初始化顺序容器中的元素。
根据元素初始化列表初始化数组元素。

28.类模板的优点?

可用来创建动态增长和减小的数据结构
类型无关,因此具有很高的可复用性
在编译时而不是运行时检查数据类型,保证了类型安全
平台无关,可移植性强

29.如何用位方法求x%y?

通用公式为x&(y-1)

30.假定CSomething是一个类,执行下面这些语句之后,内存里创建了__个CSomething对象?

CSomething a();// 没有创建对象,这里不是使用默认构造函数,而是定义了一个函数
CSomething b(2);//使用一个参数的构造函数,创建了一个对象。
CSomething c[3];//使用无参构造函数,创建了3个对象。
CSomething &ra=b;//ra引用b,没有创建新对象。
CSomething d=b;//使用拷贝构造函数,创建了一个新的对象d。
CSomething *pA = c;//创建指针,指向对象c,没有构造新对象。
CSomething *p = new CSomething(4);//新建一个对象。

31.void *memset(void *s, int ch, size_t n)的作用?

将s中前n个字节用ch替换并返回s。 作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法,通常为新申请的内存做初始化工作。

32.为什么拷贝构造函数的类对象的形参必须是引用类型?

若不是引用类型:为了调用拷贝构造函数,必须拷贝它的实参,为了拷贝它的实参,我们又需要调用拷贝构造函数,如此无限循环。则我们的调用永远不会成功。

33.合成的拷贝构造函数?

没有定义拷贝构造函数,编译器就会为我们合成一个。与合成的默认构造函数不同,即使我们定义了其他构造函数,也会合成拷贝构造函数。合成拷贝构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本(浅拷贝)。编译器将现在对象的每个非static成员,依次复制到正创建的对象。合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。 由此可见,当使用合成的拷贝构造函数时,由于浅复制的问题,容易出现错误。

34.编写拷贝赋值运算符时应该注意的问题?

1:形参列表应为const &类型:常量确保在函数内不改变传入实例的状态,引用避免调用拷贝构造函数,以提高效率
2:返回应该为引用类型,以方便连续赋值
3:判断传入的参数和当前实例是否是同一个实例,以避免出现删除自身内存的情况
4:删除实例自身已有的内存,避免出现内存泄露
更高级的方法是:创建一个临时实例,作为传入实例的副本,然后将当前实例数据成员和临时实例数据成员进行交换。当程序运行出临时实例的创建范围后,程序会自动调用析构函数析构临时实例。

35.重载递增++递减–运算符。

前置版本返回引用,后置版本返回值。为解决前置和后置运算符无法区分的问题,后置版本接收一个额外的(不被使用)int类型的形参。当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参。这个形参的唯一作用就是区分前置版本和后置版本的函数,而不是真的要在实现后置版本时参与运算。

//前置运算符,返回引用
A &operator++(){v++;return *this}
A &operator--(){v--;return *this}
//后置运算符,返回值
A operator++ (int x){int temp=v;v++;return tmp;}
A operator-- (int x){int temp=v;v--;return tmp;}

36.int i=-2147483648;则~i,-i,1-i,-1-i分别为什么?

计算机中以补码表示数据和运算:
-2147483648(-2^31)的二进制表示为:1000 0000 0000 0000 0000 0000 0000 0000
则~i=2^31-1=2147483647
对一个数求负,相当于对其求补运算,即仍为1000 0000 0000 0000 0000 0000 0000 0000,也可以这样理解(-2^31+2^32=2^31,2^31不能由正数表示,还需用负数表示,为-2^31)
1-i相当与-i+1,即拿-i的补码和1相加,为-2147483647
-1-i即拿-1的补码和-i的补码相加:-1的补码为1111 1111 1111 1111 1111 1111 1111 1111,相加得0111 1111 1111 1111 1111 1111 1111 1111为2^31-1=2147483647

(整理自网络)

猜你喜欢

转载自blog.csdn.net/LuyaoYing001/article/details/81702020