C/C++ 常见面试题目 (一)

C/C++ 常见面试题目

常见的题目,不一定用过,但一定要知道。

1. C语言中C程序运行时的内存分配机制

之前写的参考谭浩强的C语言书,写的没有那么准确,重新修改。
程序的内存分配:
(1)代码段code segment/data segment:
(2)数据段data segment:数据段又分为BSS和.data;
(3)堆head:
(4)栈stack:
如图所示:
这里写图片描述

代码段:一般也成为正文段,包含可执行的指令,代码段一般在堆和栈的存储空间下面,防止堆或栈溢出而覆盖写代码段。通常代码段是可共享的,内存中只存在一个副本,但可以被频繁执行。代码段是只读的,防止其他程序修改指令。常量保存在代码段,所有对常量的赋值,都会报segment fault。

数据段:分为两部分,BSS(block started by symbol),包含所有没有在源程序中初始化或者初始化为0的全局变量和静态变量。
.data段,包含所有被程序员初始化的全局变量和静态变量,.data段又可分为初始化的只读区域和初始化的可读可写的区域。

堆:堆开始于BSS段的结尾,由低地址向高地址增长,堆是由malloc、remalloc、free操作来管理。

栈:栈和堆相邻,栈的增长方向与堆相反。栈,存放程序定义的局部变量(不包括static声明的变量),函数被调用时,其参数、局部变量入栈,调用结束,依次出栈,函数返回值入栈。

从图中可以看出:·text段和.data段是在可执行文件中的,由系统从可执行文件中加载,而BSS段不是在可执行文件中的,由系统初始化。

int main(){
    char a=0, b=0;
    int *p = (int*)&b;
    *p = 258;
    printf("%d %d", a, b);
    return 0;
}

输出值:1 2
a, b属于局部变量,存储在栈空间中,先分配a的地址,再分配b的地址。因为栈是从上往下生长的,所以b的地址比a低一个字节。 然后对b的地址进行赋值258(int是4字节,二进制表示是0x00 00 01 02)。最后1字节0x02赋值给了b,接下来1字节0x01赋值给了a。
(小端模式)
参考:
[1] http://www.geeksforgeeks.org/memory-layout-of-c-program/
[2] http://www.wikiwand.com/en/Data_segment
[3]http://keendawn.blog.163.com/blog/static/888807432010314111152109/
[4] http://harttle.com/2015/07/22/memory-segment.html

2. C语言中全局静态变量和局部静态变量

接问题1,在C语言中,每个变量和函数有两个属性:数据类型和数据存储类型。定义变量,关心的是作用域和生存周期。
定义:
全局静态变量:实际上就是全局变量,一个程序中的全局变量全部存储在静态存储区中;
局部静态变量:指的是在某个函数中用关键字static定义的变量,这种变量的作用范围只在定义它的函数起作用,但是它存储在静态存储区。我们知道,在函数执行结束后,局部变量的内存空间会返回操作系统。但如果定义为静态局部变量,函数执行结束后静态局部变量不会被释放,仍然保存它的值。如果再次 调用这个函数时,我们就可以直接使用这个保存下来的值。
总结:
局部变量和静态局部变量:二者作用域相同,都只在定义的函数体,局部静态变量分配在静态存储区,函数执行结束后内存空间不被释放,局部变量分配在动态存储区,函数执行结束后内存空间被释放;
局部静态变量和全局静态变量:二者的存储区相同都在静态存储区,区别在于作用域,局部静态变量作用域仅限于定义的函数体,全局静态变量作用域在定义全局变量的整个文件中。
注意:
对静态变量的初始化是在编译阶段完成的。
存储方式分为两大类:静态存储和动态存储,具体包含4种,自动的(auto)、静态的(static)、寄存器的(register)、外边的(extern)。
如果想允许其它源文件访问本文件中的全局变量,在其它源文件中通 过使用关键字extern来定义全局变量。

3. C++中程序运行的内存分配机制

C++和C类似,稍微有些不同,用户的内存空间分为5个部分:
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效;

(2)堆(Heap):这里与C不同的是,该堆是由new申请的内存,由delete或delete[]负责释放;

(3)自由存储区(Free Storage):由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收;(常见内存分配算法有:首次适应算法、循环首次适应算法、最佳适应算法和最差适应算法等)

(4)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了初始化变量和未初始化变量了。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。

(5)常量存储区: 这是一块比较特殊的存储区,专门存储不能修改的常量(如果采用非正常手段更改当然也是可以的了)。
参考:
[1] http://www.cnblogs.com/computerg/archive/2012/02/01/2334898.html

4. C语言中static关键字的具体作用

static变量:改变变量的生存周期,见问题1;
static函数:有说是隐藏作用,是限定static函数只能被当前文件内的函数调用,其他文件中可以定义同名函数。

5. C++ static数据成员和成员函数

为什么要有static数据成员?
在C++面向对象编程过程当中,对象与对象之间的数据不是共享,在设计类的时候,有时候需要一些对象之间共享的数据,除了把所要共享的数据设置为全局数据或者函数之外,还可以利用C++的静态机制。
C++ static数据成员:
静态数据成员实际上是类域中的全局变量。
静态数据成员是所有对象共享的,其所占的内存空间不会因为某个对象的产生而分配,也不会因为对象的销毁而消失。跟类中其他的非静态数据成员一样,被定义为private时,不能被外界访问。但是可以被类内任意访问权限的函数访问。
静态数据成员在类内定义,在类外初始化。
静态数据成员的初始化:非静态成员可以在构造函数当中初始化,但是static不能在构造函数当中初始化,它的初始化,只能存在于全局区域,并且要指明是什么类的静态成员,可以用作用域符号“::”来指明。
类名::静态数据成员

C++ static成员函数:
静态成员函数当中不能访问任何权限的非静态数据成员,换句话说它只能访问static。在类外部调用静态成员函数的时候,不是简单的
对象.静态成员函数,这是普通成员函数的做法,类名::静态成员函数。参考[3]讲得好。
参考:
[1] https://msdn.microsoft.com/zh-cn/library/b1b5y48f(v=vs.120).aspx
[2] http://www.cnblogs.com/daoluanxiaozi/archive/2011/12/03/2274636.html
[3] http://blog.sina.com.cn/s/blog_5f0d72800100swkz.html

6. C++ static const

参考[1]对比的好:
const定义的常量在函数执行之后其空间会被释放,而static定义的静态常量在函数执行后不会被释放其空间。
static 表示的是静态的。类的静态成员函数,成员变量是和类相关的,不是和类的具体对象相关,即使没有具体的对象,也能调用类的静态成员函数,成员变量。一般的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。

在c++中,static静态成员变量不能在类内部初始化。
在c++中,const常量成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。

const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。
const数据成员的初始化只能在类的构造函数的初始化表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static const。
参考:
[1] http://blog.sina.com.cn/s/blog_60be7ec80100gzhm.html

7. 构造函数

7.1 构造函数的作用

构造函数主要用来在创建对象时完成对对象的数据成员一些初始化等操作, 当创建对象时, 对象会自动调用它的构造函数。一般来说, 构造函数有以下三个方面的作用:
■ 给创建的对象建立一个标识符;
■ 为对象数据成员开辟内存空间;
■ 完成对象数据成员的初始化。

7.2 默认构造函数

当用户没有显式的去定义构造函数时, 编译器会为类生成一个默认的构造函数, 称为 “默认构造函数”, 默认构造函数不能完成对象数据成员的初始化, 只能给对象创建一标识符, 并为对象中的数据成员开辟一定的内存空间。

7.3 构造函数的特点

无论是用户自定义的构造函数还是默认构造函数都主要有以下特点:
①. 构造函数与类同名;
②. 构造函数没有返回类型,没有返回值;
③. 构造函数可以被重载;
④. 构造函数在程序创建对象时由系统自动调用,不允许在程序中显示调用。
参考:
[1] http://www.cnblogs.com/mr-wid/archive/2013/02/19/2917911.html
[2] http://ticktick.blog.51cto.com/823160/194307

8. 析构函数

与构造函数相反, 析构函数是在对象生存期结束时被自动调用完成对象的清理工作,例如释放在构造函数中使用 new 或 malloc 分配的内存空间。析构函数具有以下特点:
①. 析构函数函数名与类名相同, 紧贴在名称前面用波浪号 ~ 与构造函数进行区分, 例如: ~Point();
②. 构造函数没有返回类型, 也不能指定参数, 因此析构函数只能有一个, 不能被重载;
③. 析构函数不可以被重载;
④. 当对象被撤销时析构函数被自动调用, 与构造函数不同的是, 析构函数可以被显式的调用, 以释放对象中动态申请的内存。
注意:
当用户没有显式定义析构函数时, 编译器同样会为对象生成一个默认的析构函数, 但默认生成的析构函数只能释放类的普通数据成员所占用的空间, 无法释放通过 new 或 malloc 进行申请的空间, 因此有时我们需要自己显式的定义析构函数对这些申请的空间进行释放, 避免造成内存泄露。
另外:
构造函数和析构函数的调用顺序,如果有多个对象同事结束生存期,C++将按照与调用构造函数相反的次序调用析构函数。

Q1:构造函数能否重载,析构函数能否重载,为什么?
A1:构造函数可以,析构函数不可以。
Q2:析构函数为什么一般情况下要声明为虚函数?
A2:虚函数是实现多态的基础,当我们通过基类的指针析构子类对象时,如果不定义成虚函数,那只调用基类的析构函数,子类的析构函数将不会被调用。如果定义为虚函数,则可以调用子类析构函数。

9. sizeof

sizeof是C/C++中的一个操作符,简单的说其作用就是返回一个对象 或者 类型所占的内存字节数。
如:当对数组名做sizeof操作,返回的是整个数组中元素占用的总字节数,如果将数组名做为实参传递给函数,在函数内做sizeof操作,数组名退化成一指针,sizeof返回的保存指针变量占用的字节数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值