2018秋招面试问题(六、C++基础问题)

本文汇总了C++面试中常见的基础问题,包括虚继承、接口实现、参数传递、抽象类与接口的区别、编译链接过程、动态静态库调用、调试技巧等。深入探讨了C++语法、内存管理、程序结构等多个方面,是C++开发者面试准备的宝贵资料。
摘要由CSDN通过智能技术生成

注:面试过程中整理的学习资料,如有侵权联系我即刻删除。

目录

C++虚继承的概念

C++如何实现接口?

C++中如何传参?

抽象类和接口的联系和区别?

简述一下程序的编译链接过程

怎么调用静态链接库和动态链接库

VS的调试技巧:

运算符的优先顺序?

怎么判断大端小端模式?

结构体和联合的区别?

cpp文件中经常都有#if #endif,这个的作用是什么呢?

C++包含头文件的时候,&t;>和“ ”的区别

C++头文件什么时候加.h什么时候不加?

#ifdef _DEBUG   #define new DEBUG_NEW     #endif                         的作用是什么,cpp头经常包含?

如何判断一个数是2的n次方?

操作系统中的堆栈和数据结构中的堆栈的区别

堆和栈的溢出问题

宏有什么作用?从编译器角度去理解

宏与const的区别

类中成员函数声明后面跟const的意义?

const修饰this指针的时候,如cassA * const this;

关于const成员函数的注意点

不能同时用const和static修饰成员函数,virtua和static也不能同时修饰成员函数。

内联函数inine和宏的区别

在c++程序中调用c语言编译的函数为什么要用extern c?

深拷贝和浅拷贝

实现深拷贝的两种方法

重写

静态static函数可以被重写吗?

覆盖和隐藏

char一个100的数组,怎么防止数组越界?

C++中struct和cass的区别?

二叉树的遍历方式有哪些?

C++的初始化

C++函数模板和类模板

函数模板和类模板有什么区别?

时间复杂度和空间复杂度,说一下各大排序的时间复杂度和空间复杂度


C++虚继承的概念

虚继承和虚函数是两个完全不相关的两个概念。

普通的多重继承,当两个或多个父类中包含有同名函数或者同名变量时,这时用子类构造的对象来调用这个同名函数就不行,会有二义性

< 比如类B、C都继承于类A,然后类D继承于B,C,这时候用D的对象来调用同名函数就不行,就具有二义性。>

为了解决这个普通多重继承的二义性,继承的时候将共同基类设置为虚基类,继承的时候加。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。这样不仅就解决了二义性问题,也节省了内存,避免了数据不一致的问题。

虚继承也会产生虚指针。每个虚继承的子类都有一个虚基类指针和虚基类表(不占用类对象的存储空间);当虚继承的子类被当做父类继承时,虚基类指针也会被继承,多继承的话会继承两个指针,D会继承两个指针,而同名数据成员只有一份。

C++如何实现接口?

抽象类可以作为接口。基类声明纯虚函数,每个派生类根据自己的特点重写纯虚函数,这样就可以获取同一接口的不同实现。

C++中如何传参?

  1. 值传递

值传递中,实参和形参是两个不同的地址空间,参数传递的实质是将原函数中变量的值,复制到被调用函数形参所在的存储空间中,这个形参的地址空间在函数执行完毕后,会被回收掉。整个被调用函数对形参的操作,只影响形参对应的地址空间,不影响原来函数中的变量的值,因为这两个不是同一个存储空间。

  1. 引用传递

引用就是变量的别名,对引用的操作就等价于对实参的操作。函数执行完后,会直接改变实参的值。

  1. 指针传递

实参是变量的地址,形参是指针,对指针变量的操作,就是对指向地址所对应的实参进行操作。

抽象类和接口的联系和区别?

在c++中,抽象类是包含了一个或者多个纯虚函数的类,是不能被实例化的类。在c++中,用抽象类来实现接口。

接口是java中的概念。

区别:

  1. 接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。
  2. 继承类对于抽象类所定义的抽象方法,可以不用重写,也就是说,可以延用抽象类的方法;而对于接口类所定义的方法或者属性来说,在继承类中必须要给出相应的方法和属性实现。
  3. 一个类一次可以实现若干个接口,但是只能扩展一个抽象类。

简述一下程序的编译链接过程

编译链接过程包括:预处理(预编译)、编译、汇编、链接。

预处理(预编译):主要是做一些代码文本的替换工作,展开所有的宏定义、处理条件编译、编译头文件等。

编译:把预处理完的文件做语法检查,对代码进行优化,将文本文件.i翻译成.s文件,得到汇编代码。

汇编:把.s文件(汇编代码)->机器指令(二进制代码),结果保存在目标文件.obj中。

链接:将多个目标文件、库打包到最终的可执行文件。

分为动态链接和静态链接。

静态库和动态库:

静态链接库和动态链接库都是共享代码的一种方式。

  1. 静态链接库是一组.obj文件的集合,不需要动态加载,已经固化在程序中,在编译时就打包进了可执行文件,这样程序在运行时候就不会再和静态库有任何关系,这样的好处是快,但是造成了空间的浪费,exe文件会很大。而动态链接库是动态加载dll的,在运行的时候再加载,不用一直占内存。

如果对静态库进行了更新,那么需要重新编译代码,并且提示用户进行下载整个软件,这样会造成很多不便。而动态链接库的话,只需要更新替换dll就行,维护很方便,但是缺点在于,如果dll丢失或者误删,程序就无法运行了。

怎么调用静态链接库和动态链接库

动态链接库和静态链接库的创建,是在建立工程的时候勾选的。再在项目属性中点击输出目录。

静态链接库的调用:

  1. 把.h文件加入工程中,在要调用此库的cpp文件中包含这个头文件
  2. 然后在工程属性/VC++目录/库目录中添加.lib所在文件夹的路径
  3. 在工程属性/链接器/输入/附加依赖库中添加此库的文件名(.lib),或者在调用此库的.cpp文件中添加#pragma comment(lib, “.lib”)。

动态链接库的调用:

  1. 除了包含头文件,库目录中添加.lib路径链接器中添加.lib文件名,还要把.dll文件拷贝到工程的debug文件下。

VS的调试技巧:

a悬停鼠标查看变量值;

b调试时程序死锁或者死机,点击全部中断,调用堆栈看卡死的信息。

c调试的时候想改变一个变量的值来看发生什么情况,可以双击变量直接输入一个新值。

d设置断点调试时跑过了,可以直接拖动黄色箭头或者右击设置下一条语句,就到想要那句了。

运算符的优先顺序?

从高到低:

[  ](下标运算符)

!(判断运算符)

++   - -

*(指针)  &(与) |(或) ~(取反)  ^(亦或)  (位操作)

+  -  *    /    %(算术运算符)

<   <=   >    >=    ==    !=(关系运算符)  

&&  ||     !(逻辑非运算符)

表达式1 ? 表达式2 : 表达式3     (条件运算符,也就是三目运算符)

= (赋值运算符)

, (逗号运算符)

总之,初等运算符>单目运算符>算术运算符>关系运算符>逻辑运算符>赋值运算符>逗号运算符。

怎么判断大端小端模式?

小端模式是高字节放高地址,低字节放低地址。大端模式就是高字节放低地址,低字节放高地址。把一个int值赋值给char型变量,再打印char型变量的值,看看这个变量的值是int型的高字节还是低字节。比如 int a = 1;char b=a;打印b出来b是1,那就是小端模式,是0则是大端模式。

结构体和联合的区别?

联合中的变量都占用的同一个内存,各个变量不能同时存在。联合内存长度是联合中的最长的成员所占的内存长度,结构体的内存长度是变量所占内存长度的总和。

cpp文件中经常都有#if #endif,这个的作用是什么呢?

条件编译:符合标识符1,就编译程序段1,否则编译程序段2。

条件编译:还可以防止一个头文件被重复引用。

被重复引用的意思就是一个头文件在同一个cpp中被include了很多次。比如:在a.h文件中包含了#include“b.h”,在c.h文件中又包含了#include“a.h”和#include“b.h”,这样b.h就被重复引用了。被重复引用会增加编译工作的工作量,导致编译效率低。而且如果被重复引用的头文件中声明了全局变量,就会报变量重复定义的错。

C++包含头文件的时候,&t;>和“ ”的区别

表示编译器搜索头文件的顺序不一样,<>表示只到库文件(系统目录)中去找头文件。” ”表示现在本文件(默认目录)中找,找不到再去库文件中找。

所以,使用系统标准库,包含<>,使用自定义的头文件,用” ”比较好。

C++头文件什么时候加.h什么时候不加?

  1. 如果使用的是新的C++标准库,不加.h。旧的C++库现在已经不支持了。
  2. 仍使用C的库,可以加.h,比如string.h。
  3. 对C改进成C++的库,会在前面加个C,表示来自C语言,例如CString。

      用的自己的头文件,都得加.h。

#ifdef _DEBUG   #define new DEBUG_NEW     #endif                         的作用是什么,cpp头经常包含?

在debug模式下,我们分配内存时的new被DEBUG_NEW代替,这个DEBUG_NEW不仅要传入内存块的大小,还要传入源文件名和行号,这样发生内存泄露时,我们在调试模式下可以定位到问题代码处。

而在release版本下,new就是new,并不会传入文件名和行号。

如何判断一个数是2的n次方?

思路:这个数的二进制表达式中如果只有一个1,那就是2的n次方。

所以就转换为求二进制表达式中有几个1的问题。

操作系统中的堆栈和数据结构中的堆栈的区别

数据结构中的堆栈以下说了,操作系统中的堆由程序员分配释放,效率比栈要低但是可分配空间大,是由低地址到高地址扩展的,是不连续的内存区域。

操作系统中的栈是由系统自动分配的,速度快但是可支配空间小,是由高地址向低地址扩展的,是连续的内存区域。

堆和栈的溢出问题

操作系统分配给进程的栈空间是2M,而分配堆空间可以有4G(32位)。

栈溢出:递归调用(每调用一个函数都会使调用点入栈)过深;

局部变量占用空间过大。

解决办法是:加static或者是使用堆

堆溢出:比如new了一个10个字节的大小,但是确往里存了20个字节的数据。

宏有什么作用?从编译器角度去理解

  1. 可以使用宏来定义常量#define MAXSIZE 200
  2. 可以引入预处理设置和条件编译,#ifndef _HEAD_H_防止多重包含,避免头文件被重复引用。          #define _HEAD_H_     #endif

ifdef _DEBUG,是指在调试的时候才会执行以下编译。

  1. 定义宏函数。宏函数在预编译时,同函数定义的代码来替换函数名,将代码段嵌入到当前程序,不会产生函数调用,所以会省去普通函数保留现场恢复现场的时间,但因为要将定义的函数体嵌入到当前程序,所以不可避免的会占用额外的存储空间。

<函数的调用必须把调用点地址存放到栈中,等函数的程序内容执行完之后,再返回到执行该函数之前的地方。这种操作要求执行函数之前保存现场,记忆执行地址,执行之后要恢复现场,并按着原来保存的地址继续执行。这样就有了一定时间空间上的开销。而宏只是在预处理的地方把代码展开,不需要额外的空间和间方面的开销,所以调用一个宏比调用一个函数更有效率。>

宏与const的区别

#define MAXSIZE 200 //宏定义

const int MAXSIZE=200;//const定义

a.const定义常量需要指出类型,而宏定义不用。实现的是相同的功能。

b.编译器会对const定义的常量进行静态类型安全检查,而宏定义只是简单的替换,不会进行安全检查。

c.当定义一个局部变量,const定义的常量的作用域只是const定义所在的函数段,而宏定义是整个源文件。

d.const定义的常量在编译时会为该变量分配存储空间,而宏定义是在预编译时编译,不分配存储空间。

类中成员函数声明后面跟const的意义?

比如int GetCount(void) const;

这是一个const成员函数,意义是指

  1. 这个函数不能调用非const函数;
  2. 这个函数中不能修改类中的数据成员。

const修饰this指针的时候,如cassA * const this;

代表的是this指针指向实例并且指向不可修改,但是可以通过this修改实例指向的成员变量。

如果const修饰返回值,那么接收返回值的变量也得是const。

关于const成员函数的注意点

  1. const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数。
  2. 非const成员函数可以访问非const对象的所有数据成员,但不可以访问const对象的任意数据成员。
  3. const对象的成员变量不可以修改。
  4. mutable修饰的成员变量,在任何情况下都可以修改。也就是说,const成员函数也可以修改mutable修饰的成员变量。

const成员函数只是用于非静态成员函数,不能用于静态成员函数。

不能同时用const和static修饰成员函数,virtua和static也不能同时修饰成员函数。

static void fun() const {}; ×

const的成员函数的时候为了确保该函数不能修改类的实例,而成员函数为static时,函数中是没有this指针的,static和const是冲突的

static virtual void fun() const {}; ×

static virtual void fun() {}; ×

virtual通过相应对象中的虚函数表指针需找到相应的虚函数,然而static中并没有this指针。

内联函数inine和宏的区别

1、内联函数在编译时展开,而宏在预编译时展开。

2、在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。

3、内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。

4、宏不是函数,而inline是函数。

5、宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。

6、inline可以不展开,宏一定要展开。因为inline指示对编译器来说,只是一个建议,编译器可以选择忽略该建议,不对该函数进行展开。

在c++程序中调用c语言编译的函数为什么要用extern c?

假设某个C函数的声明如下:
void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能直接调用C函数。C++提供了一个C连接交换指定符号extern“C”来解决这个问题,这就告诉C++编译译器,函数foo是个C连接,应该到库中找名字_foo而不是找_foo_int_int。

1.引用头文件前需要加上 extern “C”,如果引用多个,那么就如下所示
extern “C”
{
    #include “A.h”
    #include “B.h”
    #include “C.h”
    #include “D.h”
};
然后在调用这些函数之前,需要将函数也全部声明一遍。
2.C++调用C函数的方法,将用到的函数全部重新声明一遍
extern “C”
{
    extern void A_app(int);
    extern void B_app(int);
    extern void C_app(int);
    extern void D_app(int);

}

深拷贝和浅拷贝

深拷贝:复制对象的同时,重新分配资源。
浅拷贝:只是复制了对象,依然指向同一块内存,如果通过原来的对象释放了内存,那么就导致新的对象变成野指针。

实现深拷贝的两种方法

一个是重写拷贝构造函数,另一个还可以重载赋值运算符。

重写

重载是编译多态,重写是运行多态。子类重写父类方法,函数名前面加virtual,就是重写。如果没加virtual进行重写,那就是隐藏。

静态static函数可以被重写吗?

不能!(声明为static、final的都不能被重写)。

static成员函数是全局调用的,不属于任何一个类的实例,父类的static成员变量和函数在子类中依然可用,只是受访问修饰符限制,如父类private中的在子类就不可访问。对于static变量来说,父类子类是共用内存空间的,有静态的全局性质在,父子类继承的说法就不存在了。

覆盖和隐藏

子类继承父类时重写父类中没有加virtual的方法,具有相同的名字、参数、返回类型,没有加virtual。那么子类中的方法就会覆盖掉父类中的方法,若需要用父类的方法,需要使用super关键字。

char一个100的数组,怎么防止数组越界?

最后一个字符赋值的时候加上结束符 \0。

C++中struct和cass的区别?

C++中的struct对c的struct进行了扩充,可以包含成员函数、可以继承、可以实现多态。与class最本质的区别就是默认访问权限,struct默认public,class默认private,struct可以继承class,同样class也可以继承struct,具体的访问权限是看子类到底用的是struct还是class。

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

二叉树的遍历方式有哪些?

有前序遍历、中序遍历、后序遍历、层次遍历。

C++的初始化

1、类里面的任何成员变量在定义时是不能初始化的。
2、一般的数据成员可以在构造函数中初始化。(构造初始化列表初始化和构造函数体内赋值初始化)
3、const数据成员必须在构造函数的初始化列表中初始化。(道理很简单,const成员第一次数据初始化就是发生在类构造数据初始化时,一旦过了此时就会出现问题)
4、static在类的定义外面初始化。(int Test::cCount = 0;) 
5、数组成员是不能在初始化列表里初始化的。
6、不能给数组指定显式的初始化。

C++函数模板和类模板

函数模板:

当函数功能相同但是参数类型不同的时候,可以使用函数模板

函数模板的定义:在函数定义之前加上template <typename T>,template 关键字告诉编译器我要使用模板了,<typename T>是指在下面的第一个函数中,用数据类型T来代替函数中的数据类型。

编译器处理函数模板的机制是:

编译器是根据函数模板调用时的具体类型产生不同的函数; 编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译(这次只进行简单纠错,替换),在调用的时候还会进行一次编译,这次会根据调用的具体类型来产生具体的函数体,并调用之。

类模板:

类模板的定义

模板类是抽象的,并没有在内存中分配存储空间。如果想创建类A的对象,

A<int> a1(11);必须显示地指定类型。模板类做函数参数也要先具体化,

模板类的继承如果不类型具体化,那么派生的也是模板类,继承时加上类型,那就派生出普通类。

函数模板和类模板有什么区别?

函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化,必须由程序员在程序中显式地指定。

时间复杂度和空间复杂度,说一下各大排序的时间复杂度和空间复杂度

空间复杂度是指一个算法在运行过程中占用存储空间的大小。

时间复杂度是指执行算法所需要的计算工作量

空间复杂度中,除了快排是O(nlog2n),基数排序是O(rd+n),归并排序是O(n),别的都是O(1)。

时间复杂度中,冒泡是O(n^2),最好的时候是O(n),快排、堆排序、归并排序都是O(nlog2n)。

插入、冒泡、归并都是稳定的。

二叉排序树查找的时间复杂度是O(n)~O(log2n)。

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值