C++高频面试问题

C++常见面试问题汇总

一、指针和引用的区别

1.指针有自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
5.可以有const指针,但是没有const引用;
6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
7.指针可以有多级指针(**p),而引用只有一级;
8.指针和引用使用++运算符的意义不一样;
(指针做自增运算是指向后面的内存,引用相当于一个别名,引用做自增运算就是对原对象做自增运算)
9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

二、堆和栈的区别

1.堆栈空间分配的区别
栈:由操作系统自动分配和释放,存放函数的参数值,局部变量的值等,其操作方法类似于数据结构中的栈。

堆:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表

2.堆栈缓存方式的区别
栈使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放。

堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定,所以调用这些对象的速度要低一些

3.堆栈数据结构的区别
栈是一种先进后出的数据结构
堆可以被看作一棵树

三、new和delete是如何实现的,new 与 malloc的异同处

new/new[]和delete/delete[]是操作符;是C++用来实现动态内存管理的操作符;

new/new[]操作符是用来申请空间的;
delete/delete[]操作符是用来释放动态申请出来的空间

—new和delete的实现原理
new在底层调用operator new全局函数来申请空间;
delete在底层通过operator delete全局函数来释放空间;

/*该函数实际通过malloc来申请空间,申请成功时直接返回,申请空间失败,尝试
执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。*/

void *_CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc){
	void *p;
	while((p=malloc(size))==0)
	if(_callnewh(size)==0){
		static const std::bad_alloc nomem;
		——RAISE(nomen);
}
	return (p);
}
void oprator delete(void *pUserData){
	_CrtMemBlockHeader *pHead;

	RTCCALLBACK(_RTC_Free_hook,(pUserData,0));

	if(pUserData==NULL)
		return ;
	
	_mlock(_HEAP_LOCK);
	_TRY
		phead =pHdr(pUserData);

		_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
		
		_free_dbg( pUserData, pHead->nBlockUse );
	
__FINALLY
		_munlock(_HEAP_LOCK); 
__END_TRY_FINALLY
	
	 return;

}

#define free(p) _free_dbg(p, _NORMAL_BLOCK)

通过这两个全局函数的实现,知道了operator new 实际是通过malloc来申请空间的,operator delete实际是通过free来释放空间的;

—new和malloc的区别
1.malloc与free是C语言的标准函数,new/delete是C++的运算符

2.他们都可用于申请动态内存和释放内存,new和delete在对象创建的时候自动执行构造函数,对象消亡之前会自动执行析构函数

3.new返回指定类型的指针,并且可以自动计算所需的大小
malloc必须用户指定大小,并且默然返回类型为void*,必须强行转换为实际类型的指针。

四、C和C++的区别

1.C是面向过程的,C++是面向对象的
2.C中的const和C++中的const的用法不同。

-与C语言不同,C++const不是只读变量,c语言中const实质上是只读变量
-C++中的const是一个真正意义上的常量
-C++编译器可能会为const分配空间
-c语言中的const变量是可以通过指针修改,而C++不行

3.C中的static和C++中的static的用法不同。
4.C中函数不能进行重载,C++中进行函数重载。
5.C中不能设置函数默认值,C++中可以设置。(返回值也不同,c中没有返回值类型默认为int,C++中则会报错)
6.C++中有内联函数
7.C中动态申请内存用的是malloc/free,C++中用的是new/delete。
8.C中函数传参的方式有传指针和传值,C++中多了个传引用(底层还是传指针)。
9.C中作用域是全局和局部,C++中多了个命名空间和类内作用域。

五、Struct和class的区别

1.默认的继承访问权不同。class默认的是private,struct默认的是public

2.默认访问权限。struct作为数据结构的实现体,它默认的数据访问控制是public,而class作为对象的实现体,它默认的成员变量访问控制是private的

3.class这个关键字还用于定义模板参数,就像“typename”,但关键字“struct”不用于定义模板参数

从上面的区别,我们可以看出,struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。

六、define 和const的区别

1.就起作用的阶段而言:#define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用

2.就起作用的方式而言:#define只是简单的字符串替换,没有类型检查,而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误

3.就存储方面而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份,const定义的只读变量在程序运行过程中只有一份备份

4.从代码调试的方便程度而言:const常量可以进行调试,define是不能进行调试的,因为在预编译阶段就已经替换掉了

七、在C++中const和static的用法

1.static的作用
**-可以隐藏:**全局静态变量只能在本文件内使用
**-默认初始化:**默认初始化为0

变量
全局变量:修饰的全局变量,指定其内部链接,也就是只能本文件使用。

局部变量:修饰的局部变量,改变其生命周期,并不会修改器作用域。

成员变量:只属于类,不属于对象。使用的适合可以通过类名或者对象引用。修饰的成员变量必须在类外单独初始化,如果同时被const 修饰则可以在定义的适合进行初始化。

函数
普通函数:修饰的普通函数,指定其内部链接,也就是只能本文可见。

类成员函数:静态成员函数只属于类,不属于对象。没有this指针,所以它不能访问非静态成员函数 ,和非静态成员变量。 它是用来处理静态成员数据,如果我们非要使用静态成员函数访问非静态成员函数或者非静态成员变量,我们可以间接使用类进行引用。

2.const的作用
修饰变量:c语言中const将一个变量转化为常变量,存储在静态文本段,只有读取权限,C++中同样会将一个变量转化成常量,C++会对其进行优化,将其放入寄存器中,如果想去内存中读取该数据时,我们可以使用volatile关键字进行修饰,保证其可见性。

修饰指针变量:如果const位于* 左侧时,不能修改指针所指的对象(但是可以改变内容),如果const位于* 右侧时,不能修改指针的指向,所以必须初始化。

修饰参数:作用是原参数在该函数中不可被改变。

修饰的返回值:是用const来修饰返回的指针或引用,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根究底就是使得函数调用表达式不能作为左值。

修饰成员变量:该变量只能在初始化列表里初始化。

修饰成员函数:在成员函数后面加上const,const修饰this指针所指的的对象,也就是保证调用该成员函数的对象,在成员函数内部不会改变。(改变权限,权限可缩小,但是不可扩大)

八、C++的顶层const和底层const

1.指向常量的指针:代表不能改变其指向内容的指针,声明时const可以放在类型名前后都可,声明时:const int和int const是等价的,声明指向常量的指针也就是底层const

int num_a = 1;
int const  *p_a = &num_a; //底层const
//*p_a = 2;  //错误,指向“常量”的指针不能改变所指的对象

注意:指向“常量”的指针不代表它所指向的内容一定是常量,只是代表不能通过解引用符(操作符*)来改变它所指向的内容。上例中指针p_a指向的内容就不是常量,可以通过赋值语句:num_a=2; 来改变它所指向的内容。

2.指针常量:代表指针本身是常量,声明时必须初始化,之后它存储的地址值就不能再改变,const必须放在指针符号后面,即const,声明常量指针就是顶层const

int num_b = 2;
int *const p_b = &num_b; //顶层const
//p_b = &num_a;  //错误,常量指针不能改变存储的地址值

区分顶层const和底层const的作用
1.执行对象拷贝时有限制,常量的底层const不能赋值给非常量的底层const

int num_c = 3;
const int *p_c = &num_c;  //p_c为底层const的指针
//int *p_d = p_c;  //错误,不能将底层const指针赋值给非底层const指针
const int *p_d = p_c; //正确,可以将底层const指针复制给底层const指针

2.使用强制类型转换函数const_cast时,需要能够分辨底层const和顶层const,因为const_cast只能改变运算对象的底层const

int num_e = 4;
const int *p_e = &num_e;
//*p_e = 5;  //错误,不能改变底层const指针指向的内容
int *p_f = const_cast<int *>(p_e);  //正确,const_cast可以改变运算对象的底层const。但是使用时一定要知道num_e不是const的类型。
*p_f = 5;  //正确,非顶层const指针可以改变指向的内容
cout << num_e;  //输出5

九、final和override关键字

1.final

final限定某个类不能被继承或某个虚函数不能被重写,如果修饰函数只能修饰虚函数,且要放到类或函数后面

struct A
{
    virtual void fun() final; //该虚函数不能被重写
    virtual bar() final; //err: 非虚函数不能被final修饰
};

struct B final : A
{
    void fun(); //err: 该虚函数不能被重写,因为在A中已经被声明为final
};

struct C : B //err: B是final
{
};

2.override
override关键字保证了派生类中声明重写的函数与基类虚函数有相同的签名,可避免一些拼写错误,如加了此关键字但基类中并不存在相同的函数就会报错,也可以防止把本来想重写的虚函数声明成了重载。同时在阅读代码时如果看到函数声明后加了此关键字就能立马知道此函数是重写了基类虚函数。保证重写虚函数的正确性的同时也提高了代码可读性。

struct A
{
    virtual void fun();
};

struct D : A
{
    void fun() override;//显示重写
};

十、拷贝初始化和直接初始化

C++语言有几种不同的初始化方式。如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器将等号右边的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化(direct initialization)。

string s1 = "hiya";    // 拷贝初始化
string s2("hello");    // 直接初始化
string s3(10, 'c');    // 直接初始化

当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数(其实也包括拷贝构造函数)。当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。

拷贝初始化通常使用拷贝构造函数来完成。拷贝构造函数不仅在我们用等号=定义变量时调用,在下列情况下也会调用:

(1) 根据另一个同类型的对象显式或隐式初始化一个对象;

(2) 将一个对象作为实参传递给一个非引用类型的形参;

(3) 从一个返回类型为非引用类型的函数返回一个对象;

(4) 用花括号列表初始化一个数组中的元素或一个聚合类中的成员;

(5) 标准库容器初始化,或者调用insert或push成员时,容器会对其元素进行拷贝初始化。

十一、extern "C"的用法

1.extern “C”的功能为了能够正确的在C++代码中调用C语言的代码

2.哪些情况下使用extern “C”:
~C++代码中调用C语言代码
~在C++中的头文件中使用
~在多个人协同开发时,有人使用C,有人使用C++

十二、模板函数和模板类的特例化

引入原因:编写单一的模板,它能适应大众化,使每种类型都具有相同的功能,但对于某种特定类型,如果要实现其特有的功能,单一模板就无法做到,这时就需要模板特例化。

定义:是对单一模板提供的一个特殊实例,它将一个或多个模板参数绑定到特定的类型或值上。

函数模板特例化:必须为原函数模板的每个模板参数都提供实参,且使用关键字template后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参。

template<typename T> //函数模板
int compare(const T &v1,const T &v2)
{
    if(v1 > v2) return -1;
    if(v2 > v1) return 1;
    return 0;
}

//模板特例化,满足针对字符串特定的比较,要提供所有实参,这里只有一个T
template<> 
int compare(const char* const &v1,const char* const &v2)
{
    return strcmp(p1,p2);
}

本质:特例化的本质是实例化一个模板,而非重载它。特例化不影响参数匹配。参数匹配都以最佳匹配为原则。

类模板特例化:原理类似函数模板,不过在类中,我们可以对模板进行特例化,也可以对类进行部分特例化。对类进行特例化时,仍然用template<>表示是一个特例化版本,例如:

template<>
class hash<sales_data>
{
    size_t operator()(sales_data&);
    //里面所有T都换成特例化类型版本sales_data
};

按照最佳匹配原则,若T != sales_data,就用普通类模板,否则,就使用含有特定功能的特例化版本。

十三、C++中的重载和重写的区别

重载:是指同一可访问区内被声明的几个具有不同参数列的同名函数,根据参数列表确定调用哪个函数,重载不关心函数的返回值类型。

隐藏:是指派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。

重写(覆盖):是指派生类中存在重新定义的函数,其函数名、参数列表、返回值类型,所有都必须同基类中被重写的函数一致只有函数体不同,派生类调用时会调用派生类的重写函数。重写的基类中重写的函数必须有virtual修饰

重载和重写的区别:

(1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。

(2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同。

(3)virtual的区别:重写的基类必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有。

隐藏和重写,重载的区别:

(1)与重载范围不同:隐藏函数和被隐藏函数在不同类中。

(2)参数的区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是被重写。

十四、介绍面向对象的三大特性,并且举例说明每一个

1.理解面向对象

经典案例:把大象塞进冰箱
1>>面向过程的做法:1.打开冰箱门 2.把大象塞进去 3.关闭冰箱门
当有两个不同的人用不同的方法实现这样的步骤,我们需要为不同的人量身定做不同解决事情的方法。

2>>面向对象,找个对象帮你做事
我们把冰箱作为对象,1.冰箱门可以被打开 2.大象可以被塞进去 3.冰箱门可以被关闭(面向对象写出通用的方法,屏蔽了所有人的差异)

经典案例:关门
面向过程:
张三踹开门
李四轻轻关上了门

面向对象:
门可以被关闭(可以避免上述面向过程的差异)

封装: 所谓的封装,就是把客观的事物封装成抽象类,例如把密某个人需要做某件事,我们把某个人看成一个类,把他需要用的工具带在身上,他用到的方法装在脑子里,我不管他是怎么完成这件事的,他只要给我返回这件事的结构就可以了,不需要关心这件事怎么做,这就是封装。

继承:某些相似的特性,可以从一个类继承到另一个类,类似生活中的继承,例如有个所有的汽车都有4个轮子,那么我们在父类中定义4个轮子,通过继承获得4个轮子的功能,不用再类里面再去定义这4个轮子的功能。

多态: 所谓的多态就是对一个实例的相同方法在不同的情况下有不同表现形式。多态机制使得不同内部结构的对象可以共享相同的外部接口。这意味着,虽然不同类的内部操作不同,但是可以通过一个公共类,他们可以通过相同的方法给与调用。

发布了29 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_36834959/article/details/104422602