C++ && C 学习八:C++不回避问题,它只是把问题留给使用者

Interview Question 

1、C++里面诡异的问题:关于下面代码说法正确的是:main()中的i是一个未定义值。

#include <iostream>
using namespace std;

int i=1;

int main() 
{
    int i=i;
    cout<<i<<endl;
    return 0; 
}

解析:

①c++会通过编译,但是会在运行时报错;因为 int i=i; i变量从声明那一刻开始就是可见的,所以main中的i不是1,是一个未定义的值。他与main()外的i无关。

②关于作用域,是小范围的同名变量会覆盖外部的变量。如下

#include <iostream>
using namespace std;

int i=1;

int main() 
{
    cout<<i<<endl;
    int i=5;
    cout<<i<<endl;
    return 0; 
}

输出:
1 
5

 

2、下面代码的输出

int main()  
 
{  
 
    int arr[] = {6,7,8,9,10};  
 
    int *ptr = arr;  
 
    *(ptr++) += 123;  
 
    printf("%d ,%d/n",*ptr,*(++ptr));  
 
    return 0;  
 
}  

输出: 8  8 

解析:

int arr[]={6,7,8,9,10};

int *ptr=arr;//现在ptr指向6

*(ptr++)+=123;//现在ptr指向7,第一个元素变为129;*(ptr++)+=123; 展开为*ptr = *ptr + 123;ptr++;

printf("%d,%d",*ptr,*(++ptr)); //考虑从右往左计算,先是*(++ptr),现在ptr指向8,然后*ptr也是8,输出8,8

C中printf计算参数时是从右到左压栈的,所以计算参数从右往左进行。

       printf的参数,函数printf从左往右读取,然后将先读取放到栈底,最后读取的放在栈顶,处理时候是从栈顶开始的,所有从右边开始处理的。
 

3、在C++程序中调用被C编译器编译后的函数,为什么要加extren "C"?

解析:C++支持函数重载,C不支持函数重载。函数被C++编译后在符号表中的名字与C语言的不一样。 因此C++提供了C连接交换指定符号extern "C"来解决名字匹配问题。

4、判断对错

① int *pi;  *pi = 5;  × 错误!

解析:整数指针*pi并没有初始化,没有指向实际的地址。在这种情况下给他赋值是错误的,因为赋的值不知道该放到那里去。

②const double di; × 错误!

解析:const 常量赋值时,必须同时初始化

5、下面代码运行后,会发生什么。(内存泄漏,程序崩溃)

void GetMemory(char *p )

{

    p = (char *) malloc(100 );

}

void Test(void )

{

    char *str = NULL;

    GetMemory(str );

    strcpy(str, "hello world" );

    printf(str );

}

  解析:

       ①指针传递  本质还是是值传递,传递地址的值

       ② 程序首先申请一个char类型的指针str,并把str指向NULL(即str里存的是NULL的地址,*str为NULL中的值为0),调用函数的过程中做了如下动作:1申请一个char 类型的指针p,2把str的内容copy到了p里(这是参数传递过程中系统所做的;这里指针p实际是主函数中str的一个副本,编译器总是要为函数的每个参数制作临时副本,即值传递),3为p指针申请了100个空间(为p申请了新的内存,只是p的值(内存地址)改变了,str并没有变化),4返回Test函数,最后程序把字符串hello world拷贝到str指向的内存空间里.到这里错误出现了!str的空间始终为NULL的地址而并没有实际的空间。

6、①一个引用必须总是指向某个对象。不存在指向空值的引用这个事实意味着使用引用的代码效率要比指针高。

     ②在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。(指针和引用的合法性区别)

7、选择题

下面关于函数的描述正确的是:(D)
A.函数的形参在函数未调用时预分配存储空间。
B.若函数的定义出现在主函数之前,则可以不必在说明。
  //B选项很有迷惑性! 若函数的定义出现在主调函数之前,则可以不必在说明。这样应该就是对的
C.若一个函数没有return语句,则什么值都不返回。
D.一般来说,函数的形参和实参的类型应该一致

解析:

A: 调用时才会分配内存空间

B:函数需要在它被调用之前声明,这个与main函数无关。

C:错误!在主函数中可以不写return语句,编译器会隐式返回0。(但在一般函数中没有return语句是不行的)

 

8、为什么虚函数效率低?

解析:因为虚函数需要一次间接的寻址,而一般的函数可以在编译时定位到函数的地址。 虚函数(动态类型调用)是要根据某个指针定位到函数的地址,多增加了一个过程,效率肯定会低一些,但带来了运行时多态。

9、对于一个空类,编译器默认产生四个成员函数:默认构造函数、析构函数、复制构造函数和赋值函数。

10、什么是虚指针? (美国某移动通信企业面试题)
解析:虚指针或虚函数指针是一个虚函数的实现细节。带有房函数的类中的每一 个对象都有一 一个虚指针指向该类的虚函数表。面试

11、在分布式系统中,不适用TTI的一个合理解能是? 答案:C
A.RTTI太慢了。
B.RTTI不是一个标准行为。
C.RTTI行为不可预期及缺乏扩展性。
D.RTTI函数在运行时会失败。

 

12、 用二进制来编码字符串“abcdabaa”,需要能够根据编码,解码回原来的字符串,最少需要多长的二进制字符串? B

  A.12 B.14 C.18 D.24 

解析:哈夫曼编码问题:字符串"abcdabaa"有4个a.2个b.1个C. 1个d。构造哈夫曼树如下所示。
a编码011位),b编码10(2位),c编码110(3位),d编码111(3位)。这个字符串的总长度为: 1*4+2*2+3*1+3*1=14.

扩展: 哈夫曼编码  //https://blog.csdn.net/sspumyl/article/details/53467604

例题解答:

假设用于通信的电文由字符集{A,B,C,D,E,F}中的字母构成,这些字母在电文中出现的概率分别为{0.10,0.19,0.20,0.35,0.12,0.04},要求: 
1、构造一棵Huffman树,填写下表,要求左结点的权不大于右结点的权 
2、在下表中填入各字符的Huffman编码(左分支为”0”,右分支为”1”) 
3、求带权路径长度 


解析: 
1、哈夫曼树的构造 
将电文概率由大到小依次排序 
0.04,0.10,0.12,0.19,0.20,0.35 
将0.04和0.10作为左子树,右子树构成一棵树。 
则如图所示: 

它们根节点之和作为新的结点,删掉刚才两个结点,加入新结点0.14重新由大到小排序 
0.12,0.14,0.19,0.20,0.35 
将最小的两个结点作为左子树、右子树构成新的树。 
如图所示: 

删掉刚才两个结点,加入新结点0.26,重新排序 
0.19,0.20,0.26,0.35 
将此时最小的两个结点作为左右子树,建立新的树 
如图所示: 

删掉刚才两个结点,加入新结点0.39,重新排序 
0.26,0.35,0.39 
将此时最小的两个结点作为左右子树,建立新的树 
如图所示: 

至此只剩下两个结点,构成满足条件的树,如图: 

2,哈夫曼编码 
(菜鸟的自我理解,有错误还希望大家指正) 
如图所示: 

3,WPL值的计算 
首先给出路径和路径长度的概念,从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称作路径长度。树的路径长度是从树根到每一结点的路径长度之和。 
树的带权路径长度为树中所有叶子结点的带权路径长度之和,通常记作WPL.

带权路径长度 = (0.19 +0. 20 + 0.35) * 2 + 0.12 * 3 + (0.04 + 0.10) * 4 = 2.4


///

13、递归函数最终会结束,那么这个函数一定(不定项选择):选择②
①. 使用了局部变量 
② 有一个分支不调用自身
③ 使用了全局变量或者使用了一个或多个参数

解析:
对于①显然不是,局部变量只在一次调用局部范围有效,出了这次调用的范围就无效了,它不能控制递归的结束。需要注意的就是局部变量不是局部静态变量。
对于②,很自然了,如果没有一个分支不调用自身,递归就不会结束了。(这是在考查递归的定义)
对于③这是最有迷惑性的,因为使用全局变量或使用一个或多个参数的确可以控制递归的结束,但是不是只有这两种方式呢?所以题目中指出了"一定"。答案是并不是只有这两种方式。

[1]我们知道局部静态变量存放在堆中而不是栈中,所以它在程序生命周期内都是存在的,只是只有在函数内才能被访问,其内容是上次处理后的内容或是初始化后的内容,调用多次都同一个变量实例。所以局部静态变量是可以控制递归函数最终结束的
[2]可能通过异常来控制递归的结束。其实这种情况很常见,每个应用程序的缺省栈空间大小是不会太大的,很容易因为堆栈溢出而让递归函数终止。此外,还可以会发生其它的异常,比如内存空间不足、除零等等。这些异常都可以让递归函数终止。
[3]我们一般所说的全局变量都是针对一个应用程序而言的,所以我们还可以利用BIOS或OS的一些数据或一些标准库的全局值来控制递归过程的终止。比如利用日期时间、利用库中的随机数等等。

14、补码   

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
①正整数的补码是其二进制表示,与原码相同;
②求负整数的补码,将其原码除符号位外的所有位取反(0变1,1变0,符号位为1不变)后加1。  补码=反码+1
③所谓原码就是 二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
④反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。

例题:

 十进制数-10的3进制4位补码是多少?
方法一:在二进制中,负数符号位为1,三进制中,负数的符号位就为2。所以-10的三进制原码为2101,然后符号位不变,其他位按三进制取反。3进制数,每位最大只能为2,那么必须保证a+a的反=2,所以0取反为2,1取反为1,2取反为0。所以2101的反码为2121,然后再加1,即为2122就是-10的4位三进制补码。
①先求的-10的3进制表示为2101;
②对-10的3进制表示形式取反为2121;
③对取反后的三进制表示形式加1位2122;

(方法一理解②:10=1*3*3+1,转3进制表示为0101,负数补码=原码求反+1,所谓的反码其实就是3机制对应的最大值2减去当前三进制的值,即反码为(2-0)(2-1)(2-0)(2-1)得2121,补码就是对所求的反码加1,故补码为2122)

方法二:

<   依据:求负数补码,将其原码除符号位外的所有位取反后加1 -->即负数补码+对应正数原码(负数原码去掉符号位) =  N^M   >
对于二进制而言 -10的补码为  (2^8 - |-10|)
对于四位三进制而言 -10的补码为 ( 3^4 - |-10|)    
对于N进制M位数而言 -10的补码为 N^M - | -10|

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 68
    评论
评论 68
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值