C++秋招记录(三)——细碎知识点

c++面试

细碎知识点

1、写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。

BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001 if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)

2、typedef 和 define 有什么区别

  • 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义常量,以及书写复杂使用频繁的宏。
  • 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
  • 作用域不同:typedef有作用域限定。define 不受作用域约束,只要是在 define 声明后的引用都是正确的。
  • 对指针的操作不同:typedef 和define 定义的指针时有很大的区别

3、const(enum)与 #define 的比较 ,const有什么优点?

  • 变量/字符替换、有无数据类型、安全检查
    • const 常量有数据类型,而宏常量没有数据类型。
    • 编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,在字符替换可能会产生意料不到的错误(边际效应),有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。

4、const 有什么用途

  • 定义只读变量,或者常量(只读变量和常量的区别参考下面一条),在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const。
  • 修饰函数的参数和函数的返回值,在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;有时候必须指定类的成员函数返回值为const类型,以使得其返回值不为“左值”
  • 修饰函数的定义体,这里的函数为类的成员函数,被const修饰的成员函数代表不能修改成员变量的值,因此const成员只能调用const成员函数;
    class Screen {
    public:
    const char cha; //const成员变量
    char get() const; //const成员函数
    };
    const Screen screen; //只读对象
    

4、内联(inline)函数的优缺点

优点:减少函数调用开销(函数名、参数列表、返回值的入栈出栈,用函数实体代替函数调用
缺点:

  • 增加函数体积,exe太大,占用CPU资源,可导致cache装不下(减小了cache的命中)
  • 不方便调试debug下一般不内联,
  • 每次修改会重新编译头文件增加编译时间
  • inline只是一个请求,编译器有权利拒绝。有7种情况下都会拒绝,虚调用,体积过大,有递归,可变数目参数,通过函数指针调用,调用者异常类型不同,declspec宏等
  • forceinline字面意思上是强制内联,一般可能只是对代码体积不做限制了,但是对于上面的那些情况仍然不会内联,如果没有内联他会返回一个警告
  • 构造函数析构函数不建议内联,里面可能会有编译器添加的内容,比如说初始化列表里面的东西

5、宏和内联(inline)函数的比较?

1). 首先宏是C中引入的一种预处理功能;
2). 内联(inline)函数是C++中引用的一个新的关键字;C++中推荐使用内联函数来替代宏代码片段;
3). 内联函数将函数体直接扩展到调用内联函数的地方,这样减少了参数压栈,跳转,返回等过程;
4). 由于内联发生在编译阶段,所以内联相较宏,是有参数检查和返回值检查的,因此使用起来更为安全;
5). 需要注意的是, inline会向编译期提出内联请求,但是是否内联由编译期决定(当然可以通过设置编译器,强制使用内联);
6). 由于内联是一种优化方式,在某些情况下,即使没有显示的声明内联,比如定义在class内部的方法,编译器也可能将其作为内联函数。
7). 内联函数不能过于复杂,最初C++限定不能有任何形式的循环,不能有过多的条件判断,不能对函数进行取地址操作等,但是现在的编译器几乎没有什么限制,基本都可以实现内联。

6、static有什么作用

C与C++的Const、static区别是对成员函数和成员变量
最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0
(1)隐藏。未加static前缀的全局变量和函数都具有全局可见性,故使用static就会对其它源文件隐藏,在不同的文件中定义同名函数和同名变量,不必担心命名冲突。
(2)保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区全局变量和static变量
(3)默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0X00,某些时候这一特点可以减少程序员的工作量。

6-1、static全局变量与普通的全局变量有什么区别 ?

把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围
  全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。 static全局变量只初使化一次,防止在其他文件单元中被引用。

6-2、static局部变量和普通局部变量有什么区别 ?

把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。普通局部变量存储在栈上超过作用域就被销毁;而 static局部变量只被初始化一次,下一次依据上一次结果值

6-3、static函数与普通函数有什么区别?

  • static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
  • static函数与普通函数作用域不同,仅在当前源文件可见。只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
  • 静态成员变量在类外单独分配存储空间,位于全局数据区,因此静态成员变量的生命周期不依赖于类的某个对象,而是所有类的对象共享静态成员变量;静态成员函数和静态成员,对于整个类的实例对象而言只有一份。
  • 静态成员函数可以访问静态成员变量,但是不能直接访问普通成员变量(需要通过对象来访问,因为其没有this指针);普通成员函数既可以访问普通成员变量,也可以访问静态成员变量;可以通过类名直接调用公有静态成员函数和静态成员,即不需要通过对象,这一点是普通成员函数和普通成员所不具备的。

6-4、类的静态成员变量和静态成员函数各有哪些特性?

静态成员变量
1). 静态成员变量需要在类内声明(加static),在类外初始化(不能加static),如下例所示;

class example{
private:
static int m_int; //static成员变量};
int example::m_int = 0; //没有static
cout<<example::m_int; //可以直接通过类名调用静态成员变量}
//静态成员函数(见上题)
class example{
private:
static int m_int_s; //static成员变量
int m_int;
static int getI() //静态成员函数在普通成员函数前加static即可
{return m_int_s; //如果返回m_int则报错,但是可以return d.m_int是合法的}};
cout<<example::getI(); //可以直接通过类名调用静态成员变量}

7、typename双重含义

(1)申明模板参数时,class 和 typename 可以互换,建议使用 typename,因为从字面更加符合语义;
(2)嵌套从属类型名称(nested dependent type name)须使用 typename 来标识,但不能在所继承的基类成员列表和成员初始化列表中使用
在这里插入图片描述
在这里插入图片描述

8、有哪几种情况只能用intialization list 而不能用assignment?

当类中含有const、reference 成员变量、有基类的构造函数都需要初始化列表
(1) 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
(2) 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
(3) 没有默认构造函数的类类型,若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化

  • 对象成员:A类的成员是B类的对象,在构造A类时需对B类的对象进行构造,当B类没有默认构造函数时需要在A类的构造函数初始化列表中对B类对象初始化
  • 类的继承:派生类在构造函数中要对自身成员初始化,也要对继承过来的基类成员进行初始化,当基类没有默认构造函数的时候,通过在派生类的构造函数初始化列表中调用基类的构造函数实现

类对象的构造顺序显示:

  • 进入构造函数体后,进行的是计算,是对已经构造好的成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化列表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用(在进入函数体之前),和一次拷贝赋值运算符的调用(进入函数体之后),如果是类对象,这样做效率就得不到保障。
  • 初始化是从无到有的过程,先分配空间,然后再填充数据;赋值是对己有的对象进行操作。使用初始化列表的构造函数显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。初始化和赋值对内置类型的成员没有什么大的区别。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表

9、try-catch-finally

try-catch、try-finally、try-catch-finally
try中有代码异常会直接跳到catch,try-catch嵌套,内层的catch处理后,外层就不必了,但外层finally会继续返回值
外层try-内层try-内层catch-内层finally-外层finally
17、volatile关键字
(1)访问寄存器要比访问内存要块,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。
(2)一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他。
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
volatile int iNum = 10;
volatile 指出 iNum 是随时可能发生变化的,每次使用它的时候必须从原始内存地址中去读取,因而编译器生成的汇编代码会重新从iNum的原始内存地址中去读取数据。而不是只要编译器发现iNum的值没有发生变化,就只读取一次数据,并放入寄存器中,下次直接从寄存器中去取值(优化做法),而是重新从内存中去读取(不再优化).
多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常要加volatile说明,因为每次对它的读写可能有不同意义;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值