C/C++经典面试题

时间:2022-08-31 09:15:32

1.下面大家看一个小程序 看看输出什么

#include <stdio.h>

 

int main()

{

   int a[5][5];

   int (*p)[4];

    p= a[0];

   printf("%d\n", &p[3][3] - &a[3][3]);

   return 0;

}

析:

这个问题我来说说
首先这个问题考大家指针运算
所以大家要知道下面的等价公式

C/C++经典面试题

数组 int a[5][5]; 可以等价于 
typedef  int(AA)[5];
AA a[5];

所以 a[3] 的地址 其实是 (unsigned int)a + 3 * sizeof(AA) + 3 * sizeof(int) ==>>(unsigned int)a + 3 * 20 + 3 * 4;

由p的定义 可以知道 p[3][3] 的地址其实是 (unsigned int)a + 3 * 16 + 3 * 4;

所以相减的结果是  ( 3*16 - 3*20) / 4 = -3

2 阅读下面函数的实现,确定fn(200)的返回值。

 C/C++经典面试题

想要快速确定fn(200)的返回值必须首先搞清楚该函数的核心行 n = n& (n - 1)的意义。

我们都知道在计算机的世界里任何数字都是以二进制的形式来表示的,那么从二进制的角度我们就可以得到下面的规律:

任意二进制数减1等价于把这个二进制数最低位的10,然后将这位后的所有01

 

所以这个函数就是确定实参的二进制表示中1的个数而已了

3. 编写一个函数

该函数的参数是一个长整型数,返回值是长整型,且函数的返回值是参数中各个十进制位的从大到小排序的整数(性能要求:以最少的空间和时间完成)。

如:

原型:long f(longn);

调用: long num =f(1302181);

函数返回后num的值为8321110

主要思想:十进制数的每一位出现的数字只可能为0—9,因此可以先统计各个位上的数字出现的次数,然后根据这些统计信息重新组合为一个符合要求的十进制数返回。

在这个算法中,循环次数为2n,所需要的执行时间与问题规模n成线性关系,算法复杂度为O(n)。

C/C++经典面试题

4.下面程序的输出结果是什么?为什么?

C/C++经典面试题

相信大家一眼就可以看出本题的主要问题是数组a只有5个元素,访问其元素的下标分别是01234。当循环变量i的值为5的时候将访问a[5],这个时候产生了一个数组越界的错误。但问题是,为什么会产生死循环,当i的值为5的时候,程序究竟做了什么?在C语言中产生死循环只有一个原因,就是循环条件一直为真。在本题中也就是i<=5将一直成立。可问题是,循环变量i在每次循环结束后都做了i++。理论上,不可能产生死循环。为了弄清楚问题的本质,我们先弄清楚a[5]究竟代表什么?在Ca[5]其实等价于*(a+5),也就是说当i的值为5的时候,将对a+5这个内存空间进行赋值。很不幸,在本题中a+5这个内存空间正好就是i的地址,也就是说当i的值为5的时候,在for循环中的赋值语句其实等价于i=-i,即将i赋值为-5,因此i<=5永远无法满足。更进一步,为什么i正好在a+5这个位置呢?

我们都知道在C语言中,每次函数调用都会使用掉一部分的栈空间,且函数返回后会自动释放掉这部分栈空间。那这些栈空间拿来干什么用呢?局部变量!C语言中的局部变量是在栈空间自动分配的,也就是说函数调用的时候所使用的栈空间就是用于创建局部变量的。

因此我们可以改写一下程序:

 C/C++经典面试题

一个可能的结果如下

C/C++经典面试题

从运行结果可以知道:局部变量i确实紧跟着数组中的第4个元素a[4]在栈上分配空间,因此a[5]就是i。

 

5. 下面用于交换两数的函数有问题吗?为什么?华为09年12月的笔试题

void swap1(int p, int q)
{
    int t = p;
    p = q;
    q = t;
}

void swap2(int* p, int* q)
{
    int* t = p;
    p = q;
    q = t;
}

void swap3(int* p, int* q)
{
    int* t;
    *t = *p;
    *p = *q;
    *q = *t;
}

void swap4(int* p, int* q)
{
    int t = *p;
    *p = *q;
    *q = t;
}

void swap5(int& p, int& q)
{
    int t = p;
    p = q;
    q = t;
}

这个5个函数中

 

1,交换的是参数的值,实参是不会被交换的

2,交换的是参数指针的值,参数指针指向的变量值不会被交换

3,存在野指针,可能导致程序崩溃

4,可以交换实参变量的值,C语言中的用法

5,可以交换实参变量的值,C++中的用法

 

6. 下面程序会输出什么?为什么?

struct A
{
    int i;
    int j;
};

struct B
{
    int i;
};

void f(struct B* pb)
{
    printf("%d\n", pb->i);
}

int main()
{
    struct A a = {3, 4};

    f((struct B*)&a);
}

 

结构体A 结构体B其实在内存布局上前4个字节两个结构体是相同的 

pb->i 指的是从结构体开始便宜4个字节的整数值

因此即使传递的是a结构体变量那么前4个字节的值自然就是3

 

7. 下面程序输出什么?为什么?

class A
{
    int i;
    int j;
public:
    A(): i(1), j(2) {}

    void f()
    {
        cout<<i<<" "<<j<<endl;
    }
};

class B
{
    int i;
public:
    A(): i(3) {}

    void f()
    {
        cout<<i<<<<endl;
    }
};

int main()
{
    A a;
    B* pa = (B*)&a;

    pa->f();
    return 0;
}

公司招人不会管你学没学过,因为人家只想招合适的人才。

这个题目最终输出是  1  原因很简单在C++编译器内部 会做一系列的处理 如下


C/C++经典面试题
因此这个题的本质和前一个题一致

答案为1

8.下面的程序有没有问题,如果有问题,问题在哪里呢?(Motorola笔试题)


struct S
{
    int i;
    int* p;
};

int main()
{
    struct S s;
    
    int* p = &s.i;
    p[0] = 4;
    p[1] = 3;
    s.p = p;
    s.p[1] = 1;
    s.p[2] = 2;
    return 0;
}

p指向的是结构体s的首地址

 

并且的 s.p 指针

其实就是 p[1]

所以,当执行到 s.p[1] = 1;之后 s.p指向地址0x00000001的地方 所以当执行 s.p[2] = 2的时候其实是想0x00000001的地方写数字 2

 

你说死不死?

 

9. 下一个问题  数据结构的


编写函数 比较两课二叉树是否相等

int BTree_Equal(BTree* t1, BTree* t2);

当t1和t2完全相等, t1每个节点中保存的值与t2每个节点中保存的值都相等时,t1与t2完全相等。

相等返回1,否则0.

这个是我为我们公司出的面试题之一,要求现场写代码的。

 

这个题目主要考点是

 

你是不是学过数据结构

你是不是熟练掌握递归

你的编码功底到什么程度

 

面试是一种博弈我需要从问题判断他们想要什么答案

需要什么答案就给什么答案

 

国嵌唐老师(22134670) 21:52:29

答案其实弱爆了,看看我的答案:

 

 

int BTree_Equal(BTree*t1, BTree* t2)

{

    if( (t1 != NULL) && (t2 != NULL) )

    {

        return ( t1->value == t2->value )&& BTree_Equal(t1->left, t2->left) &&BTree_Equal(t1->right, t2->right);

    }

       else if( (t1 == NULL) && (t2 ==NULL) )

    {

        return 1;

    }

    else

    {

        return 0;

    }

}

 

 

假设有函数调用f(f1(),f2()),这样的调用是否合法,如果合法那么调用顺序是则么样的

 

 

国嵌唐老师<delphi_tang@qq.com> 20:06:49

合法,但是要注意两个问题

第一个在默认情况下函数参数的入栈顺序是从右向左 

第二个函数参数的求职秩序是不确定的 也就是说f1 f2的调用先后不是不确定的

 

学生-007-吖碁(103862095) 20:14:39

再问一个问题,假如有一个主函数               

int main()

{

return;

}

 

什么也不做 就return  那么它返回什么

 

国嵌唐老师<delphi_tang@qq.com> 20:15:00

随机数

我在C语言深度剖析中讲解过函数的调用过程你可以去看看

 

这里没有给返回值空间赋值因此里面的内容是随机的

 

学生-007-吖碁(103862095) 20:16:47

还有,返回值的作用是为了结束函数、或者说出错时做一些判断处理?是这样子吗

 

国嵌唐老师<delphi_tang@qq.com> 20:18:47

作用是这样但是不能这么理解

一个函数显然需要有一个执行结果

return是为了提交这个执行结果

 

曹绍坚(2438341515)20:26:04

有这样一段代码  添加一些打印信息程序执行结果正确  不加打印信息结果就不正确  这是什么原因啊  唐老师

 

 

 

国嵌唐老师<delphi_tang@qq.com> 20:27:15

这个显然跟时序相关 

打印信息的时候

其实需要耗时的

 

曹绍坚(2438341515)20:29:14

还有一个问题   问调用一个库函数  但是却得不到你想要的结果?你会怎么去定位这个问题

 

国嵌唐老师<delphi_tang@qq.com> 20:29:53

不会先确定函数是不是用对了

先查阅函数的API手册

 

 

李峰(290237721)  20:28:01
唐老师,你好!请问:int *p=3,这种写法对吗?若错为什么?

国嵌唐老师<delphi_tang@qq.com>  20:28:20
不对

 

******************************************************************

----------------------------2013.10.16

******************************************************************


10-23-1.1 大家来看一个PPLive面试题

 

要求现场写代码的  第1问:有一个链表,要求快速找到倒数第K个结点中结点的数据,你会怎么做?

这个是个实际的面试问题有第一问也就是说会追问看你解决问题的能力。所以一般这样的面试题会先让你写代码考察你编码能力 然后根据你的代码来继续问问题的。

 

已经有一些同学有示例代码了,呵呵,我想说的是,这样你们的面试肯定不会拿高分。

 

因为你们没有做到先思考再动手。既然是面试为什么不先问链表带不带表头呢?带表头和不带表头的做法完全不同。

带表头的链表,只需要遍历一次就可以得到倒数第K个结点的数据,因为表头中有链表的长度信息,所以直接找第 length - k即可

而不带表头的链表,则需要遍历2编链表

 

国嵌唐老师<delphi_tang@qq.com>  20:37:30

你要知道你面试时面对的是工程技术人员他们不会管你是不是知道表头怎么用只想知道你会不会用表头 不会那就只有say goodbye

国嵌唐老师<delphi_tang@qq.com>  20:39:05

呵呵学校教的肯定只是思想与工程应用相距甚远

那么表头有什么作用呢?

现在假设你有了一个方案面试看来后可能问你一下关于编程细节的问题也就是考察你的编码能力了

 

国嵌唐老师<delphi_tang@qq.com>  20:41:59

10-23-1.2 接着就是第2问:

如果这个链表在大多数的时间只做访问的作用,很少修改,但多个线程都会同时查找倒数第k个结点的数据,你会怎么改进这个链表,怎么提高效率呢?

2一般都是深入的问题,大家要看好题目,要求的是:怎么改进,怎么提高效率!!!!

这里涉及了多线程,那么就需要考虑临界资源的问题,因为题目中说了,多个线程同时访问,并且可能修改链表元素。

所以说,我们必须把链表改成有表头的形式,并且要在表头中设置一个mutex或者互斥信号量,因为访问的时候必须互斥才行。

现在要提高效率,由于大多数情况都是只访问查找倒数第k个元素,因此我们可以在表头中设置一个指针数组,分别指向对应的链表元素,这样子以后访问时就可以在O(1)的复杂度内得到想要查找的元素了。

 

因为面试一般都会有一定的开放性考察你解决问题的能力和解决的层度

国嵌唐老师<delphi_tang@qq.com>  21:00:40

说过了工程中的问题不是书上能找到答案的

多线程并发访问并且可能修改链表当然需要作为临界资源保护的

国嵌唐老师<delphi_tang@qq.com>  21:02:13

这个问题貌似考数据结构  其实考编码能力,交流能力,操作系统,多线程

10-23-2 下一个,甲骨文面试题。

下面的代码所分配的堆空间一样多吗?为什么?

 

void f1()

{

   int* p = new int[100];

}

 

void f2()

{

   int* p[100];

 

   for(int i=0; i<100; i++)

       p[i] = new int;

}

 

注意:

 

这个题目其实隐含的考察了栈空间和堆空间的概念,如果你的答案是不一样多,但是原因是因为一个只用了1个指针另一个用了

100个指针,那么你显然没机会了。当然可能只是当时粗心,但是,面试不是做慈善。

正确答案是不一样,因为其实每次申请堆空间的时候都会多出一个操作系统所用的“信息”空间,这些空间附带在你申请到的空间之前,用于标识这个空间的大小等信息。因此,一次性申请100个空间那么这个“信息”空间只有一个,而分开申请100次,那么就有了100个“信息”空间。

所以说,两种申请方法,你能用的堆空间一样多,但是真正从系统中消耗的空间是不同的,f2消耗的堆空间比较多。

 

国嵌唐老师<delphi_tang@qq.com>  21:21:40

呵呵面试嘛显然要全面考察你了 希望你知其然也知其所以然

 

10-23-3 下一个是面试常规考题,需要大家写代码了。

编程实现二叉树的层次遍历。

 

struct BTreeNode

{

   int v;

   BTreeNode* left;

   BTreeNode* right;

}

 

void printBTree(BTreeNode* root)

{

}

 

注意:这个题的重点是层次遍历二叉树,因此可以写为代码。

voidprintBTree(BTreeNode* root)

{

    Queue queue;

 

    if( root )

    {

        queue.push(root);

 

        while( queue.size() )

        {

            BTreeNode* node = queue.pop();

       

            cout<<node->v<<endl;

       

            if( node->left )queue.push(node->left);

            if( node->right ) queue.push(node->right);

        }

    }

}

其实面试是什么?

面试主要考察你的编码能力,解决问题的能力,交流能力以及以前的项目经验。并不是谈一谈就可以了的,也不是学校考试,只要之前好好看书就行。

公司是以盈利为目的的,所以招人自然就需要你帮他赚钱,怎么帮??? IT行业多半要写代码。所以大家不要小看临场白纸写代码的能力,它直接绝对你的面试成绩。

 

10-23-4 下一题:算法

一个N+2的数组中保存了1--N的自然数,但是,其中有两个数重复了,编程求出这两个重复的数。

 

要求,复杂度越低越好。

解法:

设这两个重复的数分别为  b

 

1,遍历数组求和得到s1,计算1--N的自然数的和s2,差为 n = s1 - s2

2,因此 a +b = n

3,遍历数组求元素的平方和r1,计算1--N的自然数的平方和r2,差为 m = r1 - r2

4,因此 a*a+ b*b = m;

5,求解这个二元二次方程即可,a b为自然数的解即是重复的数。

 

按照这个步骤可以实现数学计算法方法,其时间复杂度为 O(n),最快!

 

10-30-1. 第一个问题

 

32位机上根据下面的代码,问哪些说法是正确的?

char a = 0xe0;

unsigned int b = a;

unsigned char c = a;

 

 

A. a>0 && c>0 为真

B. a == c 为真

C. b 的十六进制表示是:0xffffffe0

D. 上面都不对

 

(延伸题:%d%x各输出什么??)

答案:

 

A错,因为a是有符号数 而最高位是1 显然小于0

B错,有符号数和无符号数比较 向高类型转换后 就是一个正数和一个负数比较不等

C对,char转换到int后,负数的高位补1

D错

 

10-30-2. 第二个问题

int a[10]; 问下面哪些不可以表示 a[1] 的地址?

A. a+sizeof(int) 
B. &a[0]+1 
C. (int*)&a+1 
D. (int*)((char*)&a+sizeof(int))

 

答案:

不可以的只有 A

这题考指针运算,大家可以看看下面我总结的公式

C/C++经典面试题
大家要知道  指针运算是必考的题目的 要重视

 

10-30-3. 问下面的程序输出什么,为什么?

int main()

{

    char p[] ="hello,world";

    char* p1 ="hello,world";

    char* p2 ="hello,world";

 

    printf("%d\n",p == p1);

    printf("%d\n",p2 == p1);

    printf("%d\n",p == p2);

 

    return 0;

}

(延伸题:sizeof(p)是多少呢??)

 

这个题的答案是:

0 1 0

 

p是数组,存在于栈上,这个写法是用hellow,world初始化数组元素,等价一个拷贝;

p1是一个指针,指向字符串常量helloworld,而这个字符串常量存在于全局常量区;

p2是一个指针,指向字符串常量hello, world,而这个字符串已经存在于全局常量区了,所以p2保存的地址与p1相同;

 

编译器是这样实现的相同的字符串常量在全局数据区只有一份拷贝

10-30-4. 下面哪些函数调用必须进入内核才能完成?为什么?

A. fopen

B. exit

C. memcpy

D. strlen

 

答案:

在我看来,我认为答案是fopen和exit
fopen涉及文件读写,以及一些硬件设备的IO操作,所以需要内核完成
exit涉及进程操作,显然肯定要内核完成

而memcpy是内存拷贝,可以在user space完成,有一个原则是尽量在用户空间完成函数功能,因为内核空间到用户空间的切换开销很大
strlen求字符串长度,也是应该在user space完成的

 

(网搜:fopen是打开文件的函数,文件也可以看成是一个设备,打开一个设备将导致给设备所属的驱动程序发送一个IRP,而与真实硬件相关的驱动程序都运行于内核.
exit
函数是结束进程的函数,结束进程需要访问PCB(进程控制块)TCB(线程控制块)等等一些数据结构,而这些数据都存在于内核中.

10-30-5. 下一个 杀脑细胞的题目 有点意思

有两个线程,最初 n=0,一个线程执行n++; n++;另一个执行 n+=2;问,最后可能的 n值?

A. 1

B. 2

C. 3

D. 4

 

答案:

情况1:两个线程串行执行,无交叉,结果显然是4
情况2:线程1执行n++后被打断,线程2开始执行,于是线程开始执行n+2,再将结果3写回内存前被打断,执行线程1的第二个n++,这个时候内存中的n为2,然后线程2开始执行写回3,结果是3
情况3,线程2先执行取得n为0后开始运算加2操作,之后在将结果写回内存前被打断,执行线程1,线程1执行结束n的内存为2,这个时候线程2被恢复,将运算结果2再次写回内存,最后结果是2,

由于在线程1和线程2中,都会对内存操作,使得n净增加2,所以n的最后结果不可能是1

(网搜:原子操作,就是不能被更高等级中断抢夺优先的操作。由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。这里++和+=都不是原子操作)