面试-程序设计基础

1 static(静态)变量的有什么作用?

在C语言中,关键字static的意思是静态,有3个明显的作用:

  • 在函数体内,静态变量具有“记忆”功能,即一个被声明为静态的变量在这一函数被调用的过程中其值维持不变。
  • 在模块内(但在函数体外),它的作用域范围是有被限制的。只能被模块内的所有函数访问,但不能被模块外其他函数访问。

在C++中,在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。静态数据成员有如下特点:

  • 对于非静态数据成员,每个类对象都有自己的复制品。而静态数据成员被当作是类的成员。无论这个类的对象被定义多少个,静态数据成员在程序中也只有一份复制品,由该类型的所有对象共享。
  • 静态数据成员存储在全局数据区。定义时要分配空间,所以不能在类声明中定义。由于静态数据成员属于本类的所有对象共享,所以它不属于特定的类对象,在没有生产对象时其作用域就可见。
  • 静态数据成员和普通的数据成员一样遵从public、protected、private访问规则。
  • static成员变量的初始化是在类外,此时不能带上static关键字。

与全局变量相比,使用静态数据成员有一下两个优势:

  • 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其他全局名字冲突的可能性。
  • 可以实现信息的隐藏。静态数据成员可以时private,而全局变量不能。

在C++中,当类的成员函数钱吗添加static关键字后就变成了类的静态成员函数,其特点如下:

  • 静态成员函数为类的全部服务,而不是为类的具体对象服务。
  • 静态成员函数是类的内部实现,属于类的一部分。
  • 普通的成员函数一般都隐含了一个this指针,this指针指向类对象本身。静态成员函数则没有this指针。

1.1 引申1:为什么static变量只初始化一次?

对于所有的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直没有被销毁,而保存在内存区域中,所以不会在被初始化。

存放在静态区的变量的生命周期一般比较长,一般与整个源程序“同生死、共存亡”,所以它只需要初始化一次。auto变量,即自动变量,由于存放在栈中,一旦调用过程结束,就会立即被注销。

1.2 引申2:在头文件中定义静态变量,是否可行?

不可行,如该在头文件中定义静态变量,会造成资源的浪费问题,同时也可能引起程序错误。

2 const有哪些作用?

一般而言,const哟一下几方面的作用:

  • 保护被修饰的东西,防止意外的修改,增强了程序的健壮性

    1
    2
    3
    void f(const int i){
    i=10;
    }

    对i赋值会导致编译错误

  • 进行类型检查,使编译器对处理的内容有了更多的了解,消除一些隐患

    1
    void f(const int i){...}

    编译器就知道i在函数过程中不允许修改

  • 节省空间,避免不必要的内存分配。

    1
    2
    3
    4
    5
    6
    #define PI 3.1415926		//宏定义常量
    const double Pi 3.1415926; //此时并未激昂Pi放入只读存储器中
    double i=Pi; //此时为Pi分配内存,以后不再分配
    double I=PI; //编译期间进行宏替换,分配内存
    double j=Pi; //没哟内存分配
    double J=PI; //再次进行宏替换,又一次分配内存

    const定义的常量从汇编的角度来看,只是给出了对应的内存地址,而不是想#define一样给出立即数,所以const定义常量在程序运行的过程中只有一份复制品,而#define定义的常量在内存中有若干个复制品。

  • 提高了程序的效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为编译期间的常量,没有了存储和读写内存的操作,使得它的效率也很高。

3 volatile在程序设计中有什么作用?

被volatile修饰的变量,系统每次用到它的时候是直接从对应的内存中读取,而不是利用cache中的原有数值,以适应它未知何时会发生变化。系统对这种变量的处理不做优化。所以volatile一般用于多线程间的被多个任务共享的变量和并行设备硬件寄存器等。

4 断言ASSERT()是什么?

ASSERT()一般被称为断言,它是一个调试程序时经常使用的宏。通常用于判断程序中是否出现了非法的数据,在程序运行时它计算括号内的表达式的值。如果表达式的值为false(0),程序报告错误,终止运行,一面导致严重的后果。

需要注意的是,ASSERT()只在debug版本中有用,编译的Release版本则被忽略。ASSERT()时宏,而assert()是ANSIC标准中规定的函数,其功能类似,但是可以在Release中使用。

5 C++里面是不是所有的动作都是main()函数引起的

不是。对于C++程序而言,静态变量、全局变量、全局对象的分配早在main()函数之前就以及完成。mian()函数只不过时一个约定的函数入口,在main()函数执行之前,会调用一个由编译器生成的_main()函数进行所有全局对象的构造及初始化工作。

6 C语言中操作符优先级问题

在C语言中,优先级由高到低的排序主要遵循如下规则:

  • 函数符号()、数组下标[]、 结合性从左往右

  • 单目运算符(结合性从右往左)

    • 逻辑非 [!]
    • 按位取反  [~]
    • 自增自减  [++ –] 
    • 互号    [-]
    • 类型转换  [(类型)]
    • 指针运算符和取地址运算符 [* &]
    • 长度运算符 [sizeof]
  • 算术运算符

    1
    * / % 	//结合性从左往右
    1
    + -		//结合性从左往右
  • 移位运算符

    1
    << >> >>>	//结合性从左往右
  • 关系运算符

    1
    < <= > >=	//结合性从左往右
    1
    == !=		//结合性从左往右
  • 逻辑运算符

    1
    &
    1
    ^
    1
    |
  • 三目运算符

    1
    ? :

6.1 引申1:*p++与(*p)++是否等价?

不等价。前者先完成取值操作,然后在对指针地址进行++操作。后者先完成取值操作,然后对该值进行++操作

7 前置运算和后置运算有什么区别

  • 后置++运算符时先将其值返回,然后其值增1
  • 前置++运算符时先将其值增1,在返回其值

需要注意的是,对于迭代器和其他对象使用自增、自减运算符时,一般推荐使用前置运算符,因为前置运算符通常比后置运算符效率更高。

8 a是变量,执行 (a++) +=a语句是否合法

为了更好的说明本题,首先引入两个概念:

  • 左值

    等号左边的值,可以被改变,它是存储数据值的那快内存的地址,也称为变量的地址

  • 右值

    指存储在某内存地址中的数据,也称为变量的数据

左值可以作为右值,但是右值不可以作为左值。

本题不合法,a++不能作为左值使用,++a可以作为左值使用。a++的运算结果并不是a这个变量的引用,而是一个临时变量,其值为a的值(未+1的值)

9 如何进行float、bool、int指针变量与“零值”的比较

  • int 类型

    1
    2
    3
    4
    5
    if (0==n)
    if (0!=n)
    //不推荐如下写法 容易让人误解n为布尔变量
    if (n)
    if (!n)
  • float

    1
    2
    3
    4
    const float EPSINON=0.00001;
    if ((x>=-EPSINON) && (x<=EPSINON))
    //错误写法
    if (x==0.0)
  • bool

    1
    2
    3
    4
    5
    if (flag)
    if (!flag)
    //不推荐写法
    if (flag==TURE)
    if (flag==FALSE)

10 new/delete与malloc/free的区别是什么?

malloc/free是C/C++语言的标准库函数,在C语言中需要头文件<stdlib.h>的支持。new/delete是C++的运算符。对于类的对象而言,malloc/free无法满足动态对象的要求,对象在创建的同时要执行构造函数,对象消亡之前需要自动执行析构函数。而malloc/free不在编译器控制权限之内,无法自动执行构造函数和析构函数。

malloc/free与new/delete的区别主要表现在以下几个方面:

  • new能够自动计算需要分配的内存空间,而malloc需要手工计算字节数

    1
    2
    int *p1=new int[2];
    int *p2=malloc(2*sizeof(int))
  • new/delete 直接带具体类型的指针,malloc/free 返回void类型的指针

  • new是类型安全的,而malloc不是。

    1
    2
    int *p1=new float[2];				//编译时就会报错
    int *p2=malloc(2*sizeof(float)) //编译时编译器无法指出错误
  • new一般有两步构成,分别是new操作和构造。new操作对应于malloc,但是new操作可以重载,可以自定义内存分配策略。而malloc不行。

  • new将调用构造函数,而malloc不能。delete将调用析构函数,而free不能。

注意在释放玩内存后,应该将该指针赋值为NULL。防止野指针。

11 什么时候需要将引用作为返回值?

将引用作为返回值的优点是在内存中不产生被返回值的副本。从而大大提高了程序的安全性与效率。

需要注意一下几点:

  • 不能返回局部变量的引用。局部变量由于存储在栈区,在函数返回后会被销毁,因此被返回的引用就成为了“无所指”的引用。程序会进入未知状态,引起程序的错误甚至崩溃。
  • 不能返回函数内部new分配的内存的引用。这样会导致引用所执行的内存空间无法释放,造成内存的泄露。
  • 可以返回类成员的引用,单最好时常引用类型。
  • 流操作符 << 和 >> 。一般这两个操作符连续使用,因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。

12 回调函数

回调函数与应用程序接口(API)非常接近。它们都是跨层调用的函数。但区别是API时低层提供给高层的调用,一般这个函数高层都是已知的。而回调函数正好相反,它是高层提供给低层的调用,对于低层它是未知的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API。安装后低层不知道这个回调函数的名字,但它通过一个函数指针来保存这个回调函数,在需要调用的时,只需引用这个函数指针和相关参数指针即可。

13 内存分配

14 指针

15 预处理

16 结构体和类

17 位操作

18 函数

19 变量

20 字符串

21 编译

22 面向对象相关

23 虚函数