美文网首页E_C/C++iOS 杂谈iOS那些事
面试必备之C/C++基础问题和答案汇总(二)

面试必备之C/C++基础问题和答案汇总(二)

作者: 我是云峰小罗 | 来源:发表于2016-08-01 18:05 被阅读627次
    云峰小罗手机拍摄

    刚刚毕业找工作时,整理了一些C/C++基础问题和答案,有些是日常遇到的问题,有些是网上其他人的分享,其中涉及到程序输出的问题我都亲自编程验证过,在问题后面也用红色字体标注了“验证by云峰小罗”。最近应该还有些毕业生还在找工作,所以分享出来,希望能有些帮助。毕竟我当年毕业时就是对这些题目特别留心,然后顺利校招进入鹅厂的。本文介绍关于计算机、网络、操作系统原理和常考算法,关于C/C++语法类原理类题目请参考:C/C++基础问题和答案汇总(一)

    1、为什么内存对齐可以提高访问内存的速度?

    一个周期存取一个字长(32位机就是32位),但是这个字长,是对齐的,就是只能从00-01等等,所以要是一个单位跨了两个这样的单元,就要多读一次。

    例如:大家都知道一个byte是8个bit,而现在流行的32位机指的是一次可以存取32个bit,也就是4个byte,在这种情况下,最有效率的作法当然是一次读4个byte。也就是即便你只取一个byte的内容,实际上,机器一次也是取了4个byte,然后把其中的一个byte给你。当然取4个byte并不是随机组合的,而是按照一定的次序,比如一次取0、1、2、3四个单元的内容,下次访问就是4、5、6、7。由此,如果你的数据恰好在0、1、2、3,则机器只需访问一次,就可以把所有的内容取出来,然而,如果你的数据跨越了这个边界,比如在2、3、4、5,机器在第一次访问的时候,只能取出2、3的内容,还需要进行一次访问才能将4、5的内容取出。如此一来,必须进行两次访问才能取出,所以效率当然会降低。如果读一个数据值(32bit)要读两次cache或者要读两个页,甚至引发缺页中断,是非常划不来的,计算机系统的设计中要尽量避免可能导致执行时间不确定的情况。

    2、全局变量和局部变量

    一个程序将操作系统分配给其运行的内存块分为4个区域:

    程序内存空间示意

    (1)代码区,存放程序的代码,即程序中的各个函数代码块。

    (2)全局数据区,存放程序的全局数据和静态数据。

    (3)堆区,存放程序的动态数据。

    (4)栈区,存放程序的局部数据,即各个函数中的数据。

    全局变量是整个程序都可访问的变量,谁都可以访问,生存期在整个程序从运行到结束(在程序结束时所占内存释放);而局部变量存在于模块(子程序,函数)中,只有所在模块可以访问,其他模块不可直接访问,模块结束(函数调用完毕),局部变量消失,所占据的内存释放。操作系统和编译器,可能是通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载.局部变量则分配在堆栈里面。

    3、什么是预编译?

    预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。编译系统在对程序进行通常的编译之前,先进行预处理。提供的预处理功能主要有以下三种:1)宏定义2)文件包含 3)条件编译

    4、堆栈溢出一般是什么原因导致的?

    1、没有对数组进行边界检查,数组越界访问;

    2、没有回收垃圾资源导致内存泄露,最后内存耗尽

    3、循环的递归调用或者太深层次的递归调用

    4、如果使用占用内存过大的数据结构的局部变量,导致堆栈溢出

    5、堆与栈的去区别

    A.申请方式不同

    Stack由系统自动分配,而heap需要程序员自己申请,并指明大小。

    B.申请后系统的响应不同

    栈:只要栈的剩余空间大于申请空间,系统就为程序提供内存,否则将抛出栈溢出异常。堆:当系统收到程序申请时,先遍历操作系统中记录空闲内存地址的链表,寻找第一个大于所申请空间的堆结点,然后将该结点从空间结点链表中删除,并将该结点的空间分配给程序。另外,大多数系统还会在这块内存空间中的首地址处记录本次分配的大小,以便于delete语句正确释放空间。而且,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动将多余的那部分重新放入空闲链表。

    C.申请大小限制的不同

    Stack:在windows下,栈的大小是2M(也可能是1M它是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

    D.申请效率的比较:

    栈由系统自动分配,速度较快。但程序员是无法控制的。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

    E.堆和栈中的存储内容

    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

    6、不用数组,输出二进制数。

    //以下两种解法都在VC++6.0中验证by云峰小罗

    解1:int main()

    {

         int n;

         cout << "input n:";

          cin >> n;

          for(int i=1; i<=32; i++){

          cout << (n<0?1:0) << (i%8==0?" ":"");

           n = n<<1;

    }

    解2:void bit_print(int a)

    {

         int i;

         int n = sizeof(int) * CHAR_BIT;

         int mask = 1 << (n - 1);

        for(i = 1; i <= n; ++i)

        {

              putchar(((a & mask) == 0) ? '0' : '1');

                a <<= 1;

               if(i % CHAR_BIT == 0 && i < n)

               putchar(' ');

           }

    }

    7、求1000!的末尾有几个0.

    (用素数相乘的方法来做,如72=2*2*2*3*3);只要求出1,2,。。。,1000中含因子2的个数比含5的个数多,一个2和一个5相乘得一个0,所以求出所有含因子5的个数,相加就行了。

    int find5(int x)

    {

          int num=0;

          while (x%5==0)

          {

                 num++;

                  x/=5;

             }

              return num;

    }

    void main()

    {

               int result=0;

               for(int i=5;i<=1000;i+=5)

               result+=find5(i);

              cout<<result<<endl;

    }

    //输出249,VC++6.0验证 by云峰小罗

    8、不用任何局部和全局变量(递归)实现int strlen(char *a)

    int strlen(char *a)

    {

            if('\\\\\\\\0' == *a)

                  return 0;

             else

                   return 1 + strlen(a + 1);

    }//VC++6.0验证 by云峰小罗

    9、实现任意长度的整数相加或者相乘功能

    void bigadd(char* num,char* str,int len)

    {

               for(int i=len;i>0;i--)

             {

                    num[i] += str[i];

                    int j = i;

                    while(num[j]>=10)

                     {

                            num[j--] -= 10;

                             num[j] += 1;

                       }

               }

    }//VC++6.0验证 by云峰小罗

    10、写一算法检测单向链表中是否存在环。

    注意,要求算法复杂度是O(n)并只使用常数空间。你只知道一个指向单向链表头的指针。链表的长度是不定的,而且环出现的地方也是不定的,环有可能在头,有可能在中间。而且要求是检测,不能破坏环的结构.

    答:用两个指针来遍历这个单向链表,第一个指针p1,每次走一步;第二个指针p2,每次走两步;当p2指针追上p1的时候,就表明链表当中有环路了。

    int testLinkRing(Link *head)

    {

         Link *t1=head,*t2=head;

          while( t1->next && t2->next)

          {

                  t1 = t1->next;

                   if (NULL == (t2 = t2->next->next))

                    return 0; //无环

                    if (t1 == t2)

                       return 1;

                }

              return 0;

    }//VC++6.0验证 by云峰小罗

    如果要定位环路在链表当中的开始点,发现p2和p1重合,确定了单向链表有环路了。接下来,让p2回到链表的头部,重新走,P1也继续走,每次步长都走1,那么当p1和p2再次相遇的时候,就是环路的入口了。

    相关文章

      网友评论

      • 我是云峰小罗:码字不易,当年我找工作时真的对这些题目额外留意,然后顺利通过了腾讯的面试。

      本文标题:面试必备之C/C++基础问题和答案汇总(二)

      本文链接:https://www.haomeiwen.com/subject/abzxsttx.html