C++春秋季校招面经——基础知识

81.直到C++中的组合吗?它与继承相比有什么优缺点?

1.继承:

        继承是 is a  的关系,比如说student继承person,则说明student is a teacher。继承的优点是,子类可以重写父类的方法来方便地实现对父类的拓展。

继承的缺点如下:

        1)父类的内部细节对子类是可见的。

        2)子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。

        3)如果父类的方法做了修改的话(比如增加一个参数),子类的方法必须做出相应的修改。所以说子类和父类是一种高耦合,违背了面向对象的思想。

2.组合:

      这也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。

组合的优点:

        1)当前对象只能通过所包含的那个对象去调用其他方法,所以所包含的对象的内部细节对当前对象是不可见的。

        2)当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中的代码不需要修改当前对象类的代码。

        3)当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含的对象赋值。

组合的缺点:

        1)容易产生过多的对象

        2)为了能组合多个对象,必须仔细对接口进行定义。

82.函数指针?

        函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分。一个具体函数的名字,如果后面不跟调用符号(即括号),则该名字就是该函数的指针。

        int(*pf)(const int&,const int&);

        上面的pf就是一个函数指针,指向所有返回类型为int,并带有两个const int &参数的函数。注意*pf两边的括号是必须的,否则上面的定义就变成了:

        int *pf(const int &,const int &)

        而这是一个函数pf,其返回类型为int*,带有两个const int&参数。

        函数与数据项相似,函数也有地址。我们希望在同一个函数中通过使用相同的形参在不同的时间使用产生不同的效果。

        一个函数地址是该函数的进入点,也就是调用函数的地址。函数的调用可以通过函数名,也可以通过指向函数的指针来调用。函数指针还允许将函数作为变元传递给其他函数。

        两种方法赋值:指针名=函数名;指针名=&函数名

83.说一说你理解的内存对齐以及原因

        1.分配内存的顺序是按照声明的顺序。

        2.每个变量相对于起始位置的偏移量必须是该变量大小的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止。

        3.最后整个结构体的大小必须是里面变量类型最大值的整数倍。

添加了#progma pack(n)后规则就变成了下面这样:

        1)偏移量要是n和当前变量大小中较小的整数倍

        2)整体大小要是n和最大变量中较小值的整数倍

        3)n值必须为1,2,4,8,...为其他值时就按照默认的分配规则

84.结构体变量比较是否相等

1.重载了“==”操作符

struct foo {

  int a;
  int b;

  bool operator==(const foo& rhs) *//* *操作运算符重载*

  {
    return( a == rhs.a) && (b == rhs.b);
  }
};

2.元素的话,一个个比.

3.指针直接比较,如果保存的事同一个实例地址,则(p1==p2)为真。

85.函数调用过程栈的变化,返回值和参数变量哪个先入栈?

          1.调用者函数把被调用者所需要的参数按照与背调函数的形参顺序相反的顺序压入栈中,即:从左向右依次把被调函数所需要的参数压入栈中。

        2.调用者函数使用call指令调用被调函数并把call指令的下一条指令的地址当成返回地址压入栈中。

        3.在被调函数中,被调函数会先保存调用者函数的栈底地址,然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址。

        4.在背调函数中,从ebp位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的顺序依次减小,即:这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈。

86.define、const、typedef、inline的使用方法?他们之间的区别?

1、const和#define的区别:

        const定义的常量是变量带类型,而#define定义的知识个常数不带类型;

        define只在预处理阶段起作用,简单的文本替换,而const在编译、链接过程中起作用;

        define只是简单的字符串替换没有类型检查。而const是有数据类型的,要进行判断,可以避免一些低级错误;

        define预处理后,占用代码段空间,const占用数据段空间;

        const不能重定义,而define可以通过#undef取消某个符号的定义,重新定义;

        define独特功能,比如可以用来防止文件重复引用;

2.#define和别名typedef的区别

        执行时间不同,typedef是在编译阶段有效,typedef有类型检查的功能;#define是宏定义,发生在预处理阶段,不进行类型检查;

        功能差异,typedef用来定义类型的别名,定义与平台无关的数据类型,与struct的结合使用等等。#define不止是可以为类型取别名,还可以定义常量、变量、编译开关等等。

        作用域不同,#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的定义域。

3.define和inline的区别

        define是关键字,inline是函数;

        宏定义在预处理阶段进行文本替换,inline函数在编译阶段进行替换;

        inline函数有类型检查,更安全;

87.你知道printf函数的实现原理是什么吗?

        在C/C++中,对函数的扫描是从后向前的。

        C/C++的函数参数是通过压如堆栈的方式来给函数传参数的,最先压入的参数是最后出去,在计算机的内存中,数据有两块,一块是堆,一块是栈,而栈是从内存的高低址向低地址生长的,控制生长的就是堆栈指针,最先压入的参数在最下面,结构上看起来是第一个,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。

        printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可以算出数据需要的堆栈指针的偏移量。

88.为什么模板类一般都是放在一个h文件中?

        模板类很特殊。有template<...>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了使用容易,几乎总是在头文件防止全部的模板声明和定义。

        在分离式遍历的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找。这总模式在没有模板的情况下运行良好,但是遇到模板是出错,因为模板仅字啊需要的时候才会实例化出来。

        所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部链接的符号并期待能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去进行实例化,所以整个工程的.obj中就找不到一行模板实例的二进制代码。

89.C++中类成员的访问权限和继承权限问题

三种访问权限:

        1.public:公有成员,可在类内,类外访问,是类对外提供的可访问接口;

        2.private:私有成员,仅类内访问,类外是隐藏状态;

        3.protected:保护成员,类外隐藏,对于该类的派生类,相当于公有成员。

三种继承方式:

        1.public:基类的访问权限在派生类中保持不变;

        2.private:基类所有成员权限在派生类中变为私有权限;

        3.protected:基类中的私有成员在派生类中还是私有,但是其他两类变为protected;

90.cout和printf有什么区别?

        cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据类型。

        输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。

        cout是有缓冲输出:

cout < < "abc " < <endl; 
或cout < < "abc\n "; cout < <flush; 这两个才是一样的.

        flush立即强迫缓冲输出。

        printf是行缓冲输出,不是无缓冲输出。

91.你知道重载运算符吗?

        我们只能重载已有的运算符,无权发明新的运算符;对于一个重载的运算符,其优先级个结合律与内置类型一致才可以;不能改变运算符操作个数;

        两种重载方式:成员运算符和非成员运算符,成员运算符必非成员运算符少一个参数;下标运算符、箭头运算符必须是成员运算符;

        引入运算符重载,是为了实现类的多态性;

        当重载的运算符是成员函数时,this绑定到左侧运算符对象,成员运算符的参数数量比运算符对象的数量少1,至少含有一个类类型的参数;

        从无参数的个数推断到底定义的是哪种运算符,当运算符即是一元运算符又是二元运算符(+,-,*,&);

        下标运算符必须是成员函数,下标运算符通常以所访问元素的引用作为返回值,同时最好定义下标运算符的常量版本和非常量版本;

        箭头运算符必须是类的成员,解引用通常也是类的成员,重载的箭头运算符必须返回类的指针;

92.当程序中有函数重载时,函数的匹配原则和顺序是什么?

1.名字查找

2.确定候选函数

3.寻找最佳匹配

93.定义和声明的区别?

        如果是指变量的定义个声明:声明仅仅告诉编译器,有某个类型的变量会被使用,但是编译器并不会为他分配任何内存,而定义就是分配内存。

        如果是指函数的声明和定义:声明:一般在头文件里,对编译器说:这里我有一个函数交function()让编译器知道这个函数的存在。定义:一般在源文件里,具体就是函数的实现过程,写明函数体。

94.全局变量和static变量的区别?

        两者都是静态存储方式,区别在于非静态全局变量的作用域是整个源程序,当一个源程序是由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态的全局变量就会限制了其作用域,只在定义该变量的源文件内有效,在用以源程序的其他源文件不能使用它。

        static全局变量与普通的全局变量的区别是static全局变量只初始化一次,防止在其他文件单元被引用。

95.静态成员与普通成员的区别是什么?

1.生命周期

        静态成员变量从类被加载开始到类被卸载,一直存在;普通成员变量只有在类创建对象后才开始存在,对象结束,他也结束;

2.共享方式

        静态成员变量是全类共享;普通成员变量是堆每个单个对象单独享用的;

3.定义位置

        普通成员变量存储在堆或栈中,而静态成员变量存储在静态全局区;

4.初始化位置

        普通成员变量实在类中初始化;静态成员变量是在类外初始化;

5.默认实参

        可以使用静态成员变量作为默认实参。

96.说一下你理解的ifdef  endif代表什么?

        一般情况下,源程序中所有的行都参与编译。但是有时候希望对其中一部分的内容只在满足一定条件后才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时希望当满足某条件时对一组语句进行编译,不满足时对另一组进行编译。

        条件编译命令最常见的形式为:

#ifdef 标识符  
程序段1  
#else  
程序段2  
#endif

作用是:当标识符已经被定义过,则对程序段1记性编译,否则编译程序段2.其中#else部分也可以没有。

        在一个大的软件工程中,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件上时,就会出现大量的“重定义”错误。在头文件中使用#define,#ifndef,#ifdef,#endif能避免头文件重定义。

97.隐式转换,如何消除隐式转换?

概念:

        不需要用户干预,编译器私下进行的类型转换行为。

        基本数据类型的转换以取值范围作为转换基础。隐式转换发生在从小范围到大范围的转换。比如char转换为int,int到long。

        C++提供了explicit关键字,在构造函数声明的时候加上,能够禁止隐式转换。

98.C++如何处理多个异常?

        C++中异常有:语法错误(变量未定义、括号不匹配)和运行时错误(数组下标越界,系统内存不足等等)。为了有效处理运行时错误,引入了异常处理机制。

        异常处理机制的基本思想:执行一个函数的过程中发现异常,可以不用再本函数内立即处理,而是抛出该异常,让函数的调用者直接或者间接处理这个问题。异常处理模块由三部分组成:try(检查),throw(抛出),catch(捕获)。

try  {  可能抛出异常的语句;(检查) try 
{ 
可能抛出异常的语句;(检查) 
} 
catch(类型名[形参名])//捕获特定类型的异常 
{ 
//处理1; 
} 
catch(类型名[形参名])//捕获特定类型的异常 
{ 
//处理2; 
} 
catch(…)//捕获所有类型的异常 
{ 
}

99.如何在不使用额外空间的情况下,交换两个数?

1)  算术

x = x + y;
 y = x - y;

x = x - y; 

2)  异或

x = x^y;// 只能对int,char..
 y = x^y;
 x = x^y;
 x ^= y ^= x;

100.你知道strcpy和memcpy的区别是什么吗?

        复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容(字符数组,整型,结构体,类等等)。

        复制的方法不同。strcpy不需要指定长度,他遇到被复制字符的结束符“\0”才结束,所以很容易溢出;memcpy则是根据其第三个参数决定复制的长度。

        用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则使用memcpy。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值