C/C++语言面试题详细解答一

时间:2022-12-24 17:07:07

以下提到的题目全部来自C语言面试题大汇总.本文章将对其中的题目根据自己的理解进行详细的解答。存在不足或错误的地方敬请指正出来。

static用途

:static有什么用途?(请至少说明两种)

1.限制变量的作用域
2.设置变量的存储域

最完整的回答应该是:
1,限制变量/函数的作用域(空间)
2,设置变量的存储域(存活时间)

限制变量/函数的作用域(空间)

static单词在英文字典中的解释是:静止的,静态的。按照字面上的理解是:在变量/函数之前加入static,那么这个变量/函数就是静止的,不能跑到其他地方去(也就是文件外),以此来限制变量/函数的作用域。
我用下面两段小程序来做这个测试:

file2.c

#include <stdio.h>

int g_var = 6;
static int s_g_var = 5; // 声明一个static 的全局变量

//定义个一个static的函数s_function,这个函数只能在本文件中被调用,其他文件无法调static void s_function(void)
{
    printf("I'm static function\n");
}

//定义个一个非static的函数function,这个函数可以在任何文件中被调用
void function(void)
{
    int l_var = 1;              // 定义一个非static的局部变量,每调用一个值都为1
    static int s_l_var = 1;     // 定义一个static的局部变量,每调用一次,它的值>都是上一次的值。
    printf("I'm not static function\n");
    printf("l_var = %d\n", ++l_var);
    printf("s_l_var = %d\n", ++s_l_var);

}

file1.c

#include <stdio.h> extern int g_var;

int main(void)
{
    function();
    function();
    //s_function();
    //printf("s_g_var = %d\n", s_g_var);
    printf("g_var = %d\n", g_var);
    return 0;
}

在file1.c中的main函数中如果调用s_function()这个函数

s_function();

编译就会报出如下错误:没有定义对s_function()函数的引用。

C/C++语言面试题详细解答一

虽然我们不能调用s_function()函数,但是我们却可以很妥当的调用function()函数。

那我们再看看在main函数中对file2.c中的变量s_g_var的使用会怎样?

printf("s_g_var = %d\n", s_g_var);

在编译的时候出现如下错误:

C/C++语言面试题详细解答一

我们在main中不能使用变量s_g_var,但是我们却可以使用g_var变量。
很明显,上述的程序验证了我们的说法,被static修饰的变量s_g_var和被static修饰的函数s_function()只能在file2.c中使用,无法在其文件外(file1.c)中使用。


设置变量的存储域(存活时间)

我们知道变量有局部变量和全局变量。前面我们说了

static + 全局变量 = 限制变量的作用域

那么(static + 局部变量)会变成什么鬼?
我现在就可以做个解答:
static修饰的局部变量就变成了静态局部变量,它存储在全局存储区.data,它存活于整个程序的生命周期。而不像普通的局部变量当函数调用完毕之后就会被释放,普通的局部变量命比较短。每次调用静态局部变量的时候都用上次调用后的值,它只会初始化一次。

还是上面的程序,我们在main中调用两次function()函数,而在function函数中对一个普通的局部变量l_var进行++操作,对一个静态局部变量s_l_var同样进行++操作。上述的运行结果如下:

C/C++语言面试题详细解答一

从上面可以看出,调用两次function之后,l_var始终都为2,但是第一次调用,s_l_var的值为2。第二次调用函数,s_l_var的值为3,也就是在上一次结果(s_l_var = 2)的基础上加加的。


为什么要用限制变量/函数的作用域(空间)?

在C++和Java中,都有private和public这种关键字,它们的出现是为了防止数据被破坏,保证安全性,也就是所谓的封装。在一个类中被private修饰的属性或方法,那么这个属性或方法只能在这个类中使用。很常见的一种做法所把属性声明为private类型,如果想修改这个属性(比如人的身高),只能经过getter或setter方法来进行操作。在getter或setter方法有可能会对你所要进行该属性进行有效性的判断(如果你想要把身高设置成4米,我想没人会同意的),以此来保护数据的安全。
同样的道理,static修饰的变量/函数就算不太想让别人随意使用。我觉得应该所这样的。。。


进程的内存布局

之所以要在这里提这个问题,所因为在static修饰的局部变量会涉及到变量在内存中的位置。但是如果要写又可以写一章了,这里暂时不写,详见这篇博文进程内存布局


引用与指针

:引用与指针有什么区别

1、引用必须被初始化,指针不必。
2、引用初始化以后不能被改变,指针可以改变所指的对象。
3、不存在指向空值的引用,但是存在指向空值的指针。

注意,C语言中并不存在引用这种东西,引用存在于C++中。因此对于这个问题,我们要用C++进行相关的测试。
这篇博文C++中引用与指针的区别(详细介绍)给出了两者只之间的详细介绍这里主要摘抄其中的知识进行解释。

★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;
引用是某块内存的别名。

★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终” ^_^
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为类成员名称时,其占用空间与指针相同4个字节(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;

★ 联系
1. 引用在语言内部用指针实现(如何实现?)。
2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。

对于习惯了C语言的人来说,一下子接触到&(引用)还真是不习惯,总会把它理解为取地址操作
在进行解释之前,我们来看看指针和引用的定义。


指针的定义

我在wikipedia,指针上找到了指针的定义如下:

In computer science, a pointer is a programming language object, whose value refers to (or “points to”) another value stored elsewhere in the computer memory using its address.[2]

在《C和指针》这本书中,给出的指针定义如下:

C/C++语言面试题详细解答一

这里之所以要列出《C和指针》中对于指针的描述是因为里面也讲到了变量。下面说到的引用的定义与变量有关。

变量的值存储于计算机内存中,每个变量都占据一个特定的位置。

也就是说指针是指向存储在内存地址中的值,指针的值是内存地址。


引用的定义

同样也是在wikipedia,引用中给出了引用的详细定义:

In computer science, a reference is a value that enables a program to indirectly access a particular datum, such as a variable or a record, in the computer’s memory or in some other storage device.

引用是一个值,它可以在程序中间接的去访问一个特定的值,像在内存中的变量或者record。唉!英语有点烂,看完之后还不能完全搞懂这个是什么鬼。
百度百科,引用的答案更加的直接:

引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

好,既然引用是某一变量的一个别名,那么我们就可以引申出很多东西(纯属臆测。。)


1、引用只能在声明的时候必须初始化

因为你既然是个别名,那么就要指明是谁的(哪个变量)别名,总不能就只说我是个别名吧。

#include <iostream>

using namespace std;

int main(void)
{
    int var = 1;
    int &ref_var;                           // 声明一个引用但并不初始化

    cout<< "var = " << ++var << endl;       // 对var进行自加1操作
    cout<< "ref_var = " << ref_var << endl;

    getchar();
    return 0;
}

上述code是编译不通过的:

C/C++语言面试题详细解答一

编译器都提示说是引用必须要初始化。
上述应该换成

int &ref_var = var;                     // 一个变量var的引用ref_var

2、引用必须从一而终

很明显,既然是一个变量的别名,那就说明两者指的是同一件事物。比如说小东是小西的别名,那么如果我称呼小东或者小西就是指同一个人。想象一下,如果小东又是小南的别名,称呼小东的话到底是叫谁呢,恐怕谁也说不清。(我感觉我好像在绕口令)


3、引用不能为空

#include <iostream>

using namespace std;

int main(void)
{
    int var = 1;
    int *pointer = NULL;

    int &ref_pointer = *pointer;            // 引用指向一个空值
    int &ref_var = var;                 // 一个变量var的引用ref_var

    cout << "var = " << ++var << endl;      // 对var进行自加1操作
    cout << "ref_var = " << ref_var << endl;
    cout << "ref_pointer = " << ref_pointer << endl;  // 这句话在运行的时候会出错

    getchar();
    return 0;
}

上面的是可以编译通过,我们甚至是可以声明一个指向为空的引用ref_pointer,但是在运行的时候,我们对该引用ref_pointer进行操作(打印输出)程序就会挂掉。根据手册上面说的:

in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer,which causes undefined behavior.

对ref_pointer的操作就相当于对(*pointer)的操作,因为pointer的初始化值是一个空指针NULL,对空指针进行解引用的后果是未定义的。并不建议这么做。
注:对于其他的区别点再次就不做过多的解释。


4、Cpp引用的实现机制

我觉得我提这个问题简直是给自己找罪受,很明显这涉及到编译原理。好吧,既然提出来了,那么我们就找找看有没有想过的资料。
从底层汇编理解 c++ 引用实现机制这篇文章从汇编代码的角度来理解C++引用的实现机制。我在VS2008的平台上进行下述程序的验证:

#include <iostream>

using namespace std;

int main(void)
{
    int var = 1;
    int &ref_var = var;                     // 一个变量var的引用ref_var
}

反汇编之后的代码为:

#include <iostream>

using namespace std;

int main(void)
{
00414080  push        ebp  
00414081  mov         ebp,esp 
00414083  sub         esp,0D8h 
00414089  push        ebx  
0041408A  push        esi  
0041408B  push        edi  
0041408C  lea         edi,[ebp-0D8h] 
00414092  mov         ecx,36h 
00414097  mov         eax,0CCCCCCCCh 
0041409C  rep stos    dword ptr es:[edi] 
    int var = 1;
0041409E  mov         dword ptr [var],1 
    int &ref_var = var; // 一个变量var的引用ref_var
004140A5  lea         eax,[var] 
004140A8  mov         dword ptr [ref_var],eax 
}

总结出一句话(摘自该博客的内容):

所以从汇编层次来看,的确引用是通过指针来实现的。虽然从底层来说,引用的实质是指针,但是从高层语言级别来看,我们不能说引用就是指针,他们是两个完全不同的概念。有人说引用是受限的指针,这种说法我不赞同,因为从语言级别上,指针和引用没有关系,引用就是另一个变量的别名。对引用的任何操作等价于对被引用变量的操作。从语言级别上,我们就不要去考虑它的底层实现机制啦,因为这些对你是透明的。所以在面试的时候,如果面试的人问到这个问题,可以先从语言级别上谈谈引用,深入的话就从底层的实现机制进行分析。而不能什么条件没有就说:引用就是指针,没有差别,……之类的回答


PS:如果按照这种速度更新下去,我想我要哭了。。。