c/c++相关面试准备笔记1

Posted on 2017-06-22 15:07  禾小白  阅读(866)  评论(1编辑  收藏  举报

在c++程序中调用被C编译器编译后的函数,为什么要加extern  “C”?  

  C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。C++提供了C连接交换指定符号ectern  “C”解决名字匹配问题。

头文件中ifndef/define/endif是干什么用的?

  防止该头文件被重复引用。

评价一下C与C++的各自特点。

  C是一种结构化语言,重点在于算法和数据结构。C程序的设计首先考虑的是如何通过一个过程,对输入进行运算处理得到输出。而对于C++,首先考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程控制。

const用途有哪些?

  在C程序中,const的用法主要有定义常量、修饰函数参数、修饰函数返回值。在C++程序中,它还可以修饰函数的定义体,定义类中的某个成员函数为恒态函数,即不改变类中的数据成员。被const修饰的东西都受到强制保护,可以预防意外的变动,能够提高程序的健壮性。

const与#define相比有什么不同?

  C++语言可以用const定义常量,也可以用#define定义常量,但是前者比后者有更多的优点:

  const常量有数据类型,而宏常量没有数据类型。编译器对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能产生意料不到的错误;

  有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。

mutable关键字:

  在C++中,mutable是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中,甚至结构体变量或者类对象为const,其mutable成员也可以被修改:

class  ST  { 
public:
    int a; 
    mutable int showCount; 
    void Show()const; 
}; 

void ST::Show()const{ 
    //a=1;//错误,不能在const成员函数中修改普通变量 
    showCount++;//正确 
}

  mutable只能修饰非静态数据成员;

volatile修饰符:

  volatile修饰的数据编译器不可对其进行执行期寄存于寄存器的优化。这种特性是为了满足多线程同步、中断、硬件编程等特殊需求。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的直接访问。主要用在如下几个地方:

  中断服务程序中修改的供其他程序检测的变量需要加volatile;

  多任务环境下各任务间共享的标志加volatile;

  存储器映射的硬件寄存器通常也要加volatile说明,因为每次对他的读写都可能有不同的意义;

数据对齐:指的是数据所在的内存地址必须是该数据长度的整数倍。字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:

  结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

  结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;

  结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。如果结构体内包含其他结构,会将 其中的成员打散来看其最宽基本类型成员大小;

  可以使用编译器的pack指令调整结构体对齐方式。

sizeof与strlen的区别:

  sizeof操作符的结果类型是size_t,他在头文件中的typedef为unisigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小;

  sizeof是运算符,strlen是函数;

  sizeof可以用类型做参数,strlen只能用char*作为参数,且必须是以’\0’结尾的。

  数组做sizeof的参数不退化,传递给strlen就退化为指针。

  大部分煸诩程序在编译时就把sizeof计算过了,是类型或是变量的长度;

  strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度,而不是类型占内存的大小;

  sizeof后如果是类型必须加括号,如果是变量名可以不加括号。这是因为sizeof是个操作符而不是函数;

  当使用了一个结构类型或变量时,sizeof返回实际的大小。当使用一静态的空间数组时,sizeof返回全部数组的尺寸。sizeof操作符不能返回被动态分配的数组或外部的数组的尺寸。

  数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,在C++中传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小。如果想在函数内知道数组的大小,需要进入函数时加上一个数组长度的参数;

  计算结构变量的大小就必须讨论数据对齐问题。

  sizeof操作符不能用于函数类型、不完全类型或位字段。不完全类型指具有未知存储大小数据的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型。

sizeof的使用场合:

  sizeof操作符的一个主要用途就是与存储分配和I/O系统那样的例程进行通信。

  用它来看某种类型的对象在内存中所占的单元字节。

  在动态分配一对象时,可以让系统知道要分配多少内存;

  便于一些类型的扩充。在windows中有很多结构类型就有一个专门的字段用来存放该类型的字节大小;

  如果操作数是函数中的数组形参或函数类型的形参,建议在涉及操作数字节大小时用sizeof代替常量计算。

  如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小;

  空类的所占内存为1,单一继承和多重继承的空类的空间还是1。但是涉及到虚继承的空类所占空间大小为4字节,因为有虚表。

内联函数和宏的差别?

  内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。

  内联函数要做参数类型检查,这是内联函数跟宏相比的优势;

  inline是指嵌入代码,就是在跳用函数的地方不是跳转,而是把代码直接写到哪里去。对于短小的代码来说,inline可以带来一定的效率提升,而且和C时代的宏函数相比,inline更安全可靠。可是这个是以增加空间消耗为代价的。至于是否使用inline函数,就需要根据实际情况来取舍了;

内联一边只用于下面的情况:

  一个函数不断被重复调用;

  函数只有简单的几行,且函数不包含for、whlie、switch语句;

  宏在C语言里极其重要,而在C++里面就用的少多了。关于宏的第一规则是绝不应该去使用它,除非你不得不这样做。几乎每个宏都表明了在程序设计语言里、程序里或者程序员的一个缺陷,因为它将在编译器看到程序的正文之前重新摆布这些正文。宏也是许多程序设计工具的主要麻烦。所以,如果你使用了宏,就应该准备只能从各种工具中得到较少的服务;

  用define定义常量可以用const代替,但是主要const修饰的只读变量不能用来定义数组的维度,也不能放在case关键字的后面;

  宏是在代码处不加任何验证的简单替代,而内敛函数是将代码直接插入到调用处,而减少了普通函数调用时的资源消耗。

  宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体。

  inline函数是函数,但是在编译中不单独产生代码,而是将有关代码嵌入到调用处;

指针和引用的区别:

  非空区别:在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。

  合法性区别:在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空;

  可修改区别:指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化是被指向的对象,以后不能改变,但是指定的对象其内容可以改变。

  应用区别:在以下情况下应该使用指针:一是考虑到存在不指向任何对象的可能,二是需要能够在不同的时刻指向不同的对象。如果总是只想一个对象并且一旦指向一个对象后就不会改变其指向,那么应该使用引用。

写出函数指针、函数返回指针:

  void (*f)()、void * f()

C++中有了malloc/free,为什么还需要new/delete?

  malloc与free是c++/c语言的标准函数,而new/malloc是c++的运算符。它们都用于申请动态内存和释放内存;

  对于非内部数据类型的对象而言,只用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

  因此c++需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放工作的运算符delete。new/malloc不是库函数,是运算符;

句柄与指针:

  句柄和指针其实是两个完全不同的概念。windows系统用句柄表及系统资源,隐藏系统的信息。你只要知道有这个东西,然后去调用就行了,他是个32bit的uint。指针则标记某个物理内存地址,两者是不同的概念。

  句柄是一种指向指针的指针。

介绍一下STL和包容器,如何实现?

  c++的一个新特性就是采用了标准模板库。标准模板库是一个基于模版的容器类库,包括链表、列表、队列和堆栈。标准模板库还包含许多常用的算法,包括排序和查找。

  标准模板库的目的是提供对常用需求重新开发的一种替代方法。容器是包容其他对象的对象。可以帮助程序员处理一些常用的编程任务。标准模板库容器类有两种类型,分别为顺序和关联。顺序容器可以提供对成员的顺序访问和随机访问。

什么是泛型编程?

  stl代表用一致的方式编程是可能的。实际上,他完全不同于我们看到的c++编程,也完全不同于大多数教科书里所被描述的方式。stl并不是试图用c++编程,只是试图找一种正确的方式来处理软件,寻找一种可以表达我的想法的语言而已。

  泛型编程是一种基于发现高效算法的最抽象表示的编程方法。也就是说,一算法为起点并寻找能使其工作且有效率工作的最一般的必要条件集。泛型编程假定有某些基本的法则在支配软件组件的行为,并且基于这些法则有可能设计可互操作的模块,甚至还可以使用此法则去知道我们的软件设计。stl就是一个泛型编程的例子。c++一种可是使用泛型编程例子的语言。泛型编程中,算法不与任何特定的数据结构或对象类型系在一起;

  初始化列表的初始化顺序是根据成员变量的声明顺序来执行的。

 

面对对象编程带来的便利有:良好的可复用性、易维护和良好的可扩充性;

面对对象的基本概念是什么面对对象必须提供对象、类和继承;

对于一个空类,编译器默认产生四个成员函数:默认构造函数、析构函数、拷贝构造函数和赋值函数;

结构与类的区别:

  class中变量默认是private,struct中的变量默认是public。struct可以有构造函数、析构函数,之间也可以继承,等。c++中的struct与class其实意义一样,唯一的不同就是struct里面默认的访问控制是public,class中默认的访问控制是private。c++中存在struct关键字的唯一意义就是为了让c程序员有个归属感,是为了让c++编译器兼容以前的用c开发的项目;

多态:

  一种接口,多种方法;允许将子类类型的指针赋值给父类类型的指针。多态性是通过虚函数实现的。虚函数就是允许被其子类重新定义的成员函数。而子类重新定义父类虚函数的做法称为覆盖或重写。

覆盖与重载:

  覆盖是指子类重新定义父类的虚函数的做法。而重载,是指允许存在多个同名函数,而这些函数的参数表不同。重载的函数编译器会给不同参数的函数修饰一个不同的名字是静态的。

  封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块;他们的目的都是为了代码重用;而接口是为了实现接口重用。

友元:

  友元是一种定义在类外部的普通函数,但他需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是他可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

  类对象操作的时候在内部构造是会有一个隐形的this指针。由于Car类是vehicle的派生类,那么当car对象创建的时候,这个this指针就会覆盖到vehicle类的范围,所以派生类能够对基类成员进行操作。

公有继承:

  基类成员对其对象的可见性与一般类及其对象的可见性相同,共有成员可见,其他成员不可见。这里保护成员与私有成员相同;在公有继承中,派生类的对象可以访问基类中的公有成员,派生类的成员函数可以访问基类的公有成员和保护成员;

私有继承:

  积累对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见;基类的公有成员可和保护成员是可见的,基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问;基类的私有成员都是不可见的,派生类不可访问基类中的私有成员。所以在私有继承时,积累的成员只能由派生类访问,而无法再往下继承。

保护继承:

  这种继承类型与私有继承的情况相同。两者的区别仅在于对派生类的成员而言,基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。基类的公有成员和保护成员都作为派生类的保护成员;基类的私有成员是不可见的,派生类不可访问基类中的私有成员。

  基类中的私有成员只能被基类中的成员函数和友元函数访问,不能被其他函数访问;在无继承的类中,protected与private控制符是没有差别的。在继承中,基类的private对所有的外界都屏蔽(包括自己的派生类),基类的protected控制符对应用程序是屏蔽的,但对其派生类是可访问的。

 

  每个对象内都有一个虚表指针,指向虚函数表,虚表内存放了虚函数的地址。虚表是顺序存放虚函数地址的,不需要用到链表。

  vptr和vtble和类对象的关系:每一个具有虚函数的类都有一个虚函数表vtble,里面按照在类中声明的虚函数的顺序存放着虚函数的地址,这个vtble是这个类所有对象所共有的。在每个具有虚函数的类的对象里面都有一个vptr虚函数指针,只想这个vtalbe首地址。

  虚继承是多重继承中特有的概念。虚拟基类是为了解决多重继承而出现的。虚继承与虚函数继承是完全不同的概念。

  纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”:virtual void funtion1()=0;

引入纯虚函数的原因:

  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。2、在很多情况下,基类本身生成对象是不合情理的。例如生成一个人类对象。为了解决上述问题,引入了虚函数的概念,将函数定义为纯虚函数,则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。声明纯虚函数的类是一个抽象类,所以用户不能创建类的实例,只能创建它的派生类的实例。

  定义一个虚函数,不代表虚函数为不被实现的函数;定义它为虚函数是为了允许用基类的指针来调用子类的这个函数。定义一个函数为纯虚函数,才代表函数没有被实现。规范继承这个类的程序员必须实现这个函数。定义纯虚函数的目的在于,使得派生类仅仅只是继承函数的接口。

  在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的;虚函数是c++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的函数。

  友元函数不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但是可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。

  析构函数应当是虚函数,将调用相应对象类型的析构函数,因此如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。

RTTI字面上理解就是执行时期的类型信息,其重要作用是动态判别执行时期的类型。因为虚函数有本身的局限性,当设计类别阶层时,需要判断某个对象所属的类别,而因为类别设计中大量使用了虚函数,所以使得这一工作难以实现,但又极其重要,于是使用RTTI的typrid运算符能使程序员确定对象的动态类型。

 

中缀表达式转后缀表达式:

  规则:从左到右边遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。