笔试错题集(1)
新的一学期就这样悄无声息的开始了。这个学期除了上课还有一项重要的事情就是找工作,所以要静下心来好好准备准备,这几天为这学期的学习环境纠结,究竟是去图书馆的实验室还是呆在宿舍学习好。走了一圈内环边走边想,觉得我不应该为该在哪里学习而焦头,在哪里学习最关键的都是一颗安静的心,我不能老是习惯把自己的效率低的原因推到周围的环境,更多的是要对自身的思考。不管在哪里只要能让自己静下心来做事,这里就是战斗的阵地!
首先我要做的就是笔试这方面的准备。下面是近期在做C/C++笔试练习题过程中做错的题目,记录下来以便复习,不再同一个地方犯同样的错误。
第(1)题:
分析:自己印象中栈的增长方向是跟处理器的架构有关的,印象终归印象,看来这才是答案:栈的生长方向和堆的生长方向相反,堆生长时地址增大,栈的就反过来了。至于第4个答案,想想每一个进程有自己独立的栈,要是谁都很大那还得了。
第(2)题:
分析:其实这一题靠排除都可以把它作对,当时自己会做错就是受A选项的影响,inline函数只是A选项的内容和B选项并没有关系,只能说明自己对inline函数了解得还不够到位,因为即使是内联函数B选项也是城里的,内联函数不过就是在编译时把这片代码嵌入到调用处而不是函数的地址,函数里边的和全局变量同名的变量我想在编译的时候编译器会做特殊处理。
第(3)题:
分析:我的答案是4,4,4,4,4,标准答案是21,4,4,4,4。问题出在sizeof对数组名进行操作时,结果是数组的空间大小,虽然我们知道数组名代表数组的起始地址(指针)。还有一个特殊情况:char p[5];sizeof(p[5])的值也是4,其他只要sizeof操作的变量是指针,32位系统对应的值就是4。
第(4)题:
分析:要点有两方面:①单目运算符优先级高于双目运算符;②或运算在第一个条件成立时他是不会去执行第二个条件的。答案就可想而知的。
第(5)题:
分析:这题做错主要原因就是粗心。数组里第6个值是0,意味着字符串的结束。粗心了两次!下次遇到strlen函数时一定要多长心眼啦!
第(6)题:
分析:涉及相关知识点:
第(1)数据在内存中是以二进制的补码的形式存储的,正数补码就是他本身,负数的补码是其绝对值的反码加1。
第(2)变量的溢出分为正溢出和负溢出,正溢出——有符号型变量运算之后的值超出其范围的最大值;负溢出——有符号型变量运算之后的值超出其范围的最小值。
第(3)x86架构是存储时小端模式,即低地址存放低字节。
在这一题中共同体test的a成员是char型,有符号,test.a[0]=256(1,0000,0000)相当于发生正溢出,存储到a[0]中就只有(0000,0000)即值0,test.a[1]=(1111,1111),这样一来,test.b=(1111,1111,0000,0000),有符号型数据,因此这个补码的意思就是个负数,计算负数的值:~( (1111,1111,0000,0000)-1 )=(0000,0001,0000,0000)=256,因此就是(-256).
第(7)题:
分析:什么情况下会使用拷贝构造函数?答:①函数值传递时调用的是拷贝构造函数;②函数返回值赋给另一个变量时是拷贝构造函数;③弱拷贝——变量声明和赋值同时进行时。答案就出来了。
第(8)题:
1 #include <iostream> 2 using namespace std; 3 4 class A { 5 public: 6 ~A() { 7 cout << "~A()"; 8 } 9 }; 10 class B{ 11 public: 12 virtual ~B() { 13 cout << "~B()"; 14 } 15 }; 16 class C: public A, public B { 17 public: 18 ~C() { 19 cout << "~C()"; 20 } 21 }; 22 int main() { 23 C * c = new C; 24 B * b1 = dynamic_cast<B *>(c); 25 A * a2 = dynamic_cast<A *>(b1); 26 delete a2; 27 }
结果是:执行出错。
分析:关键字dynamic_cast——将一个基类对象指针(或引用)投掷到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。
1 #include <iostream> 2 using namespace std; 3 class A { 4 public: 5 int b; 6 char c; 7 virtual void print() { 8 cout << "this is father’s fuction! " << endl; 9 } 10 }; 11 class B: public A { 12 public: 13 virtual void print() { 14 cout << "this is children’s fuction! " << endl; 15 } 16 }; 17 int main(int argc, char * argv[]) { 18 cout << sizeof(A) << "" << sizeof(B) << endl; 19 return 0; 20 }
分析:类B虚拟继承类A。
A中的内存方式是:4(int)+4(char对齐)+4(A中的虚指针)=12 ;B中的方式是:4(A::int)+4(A::char对齐)+4(B中的虚指针)=12。
其中A的虚指针和B的虚指针不一样,并且各种的虚函数表也不一样。如果将class B:public A 改成 class B:virtual public A,则B中会多一个指向虚继承的指针:12+4(Ptr_B->A)=16。
第(12)题:
拷贝构造函数的形参是引用数据类型,这是死规定。
第(13)题:
分析:ASIIC码的转义字符表示形式“\0xx”,即用八进制来表示,\065就是一个字符。
第(14)题:
分析:等价线程就是运行在同一个进程中的两个代码完全一样的线程。D选项出现的情况:假设线程1先执行完if条件判断a==0,此时切换到线程2执行,由于a未被改变线程2同样会进入if的条件中执行a++,结果a==1,之后立马又切换回线程1来执行a++,线程1打印的a就是2了,接着切换回线程2执行,a此时还是等于2。其他选项一样推理。
温故知新:进程切换过程中要做的事情:
①保护换出进程的上下文:保存程序计数器和其他寄存器状态值、栈指针、下一条即将执行的程序。
②改变换出进程到相应状态,然后把它移到对应运行状态的队列中去。
③从运行队列中选择另一个即将换入的进程,将其状态更新为“运行态”:涉及页目录的切换以使用新的地址空间。
④恢复换入进程的上下文。
期间伴随2次模式切换:用户->内核,内核->用户。
线程切换过程中要做的事情:线程分为用户线程和内核线程。
用户线程切换:有关线程管理的所有工作都“隐藏”在用户程序中完成,内核并不知道用户线程的存在。不需要内核支持而在用户程序中实现的线程,不依赖于操作系统核心,用户进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快。一个用户线程执行系统调用会导致进程的阻塞。
内核线程切换:有关线程管理的所有工作都由内核来完成,应用程序无权限来参与管理——其实就是内核进程(轻量级进程)个进程运行时有2种执行环境:用户空间和内核空间,也就是说用户进程和内核进程是一一对应的关系,都属于同一个进程。
由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。
第(15)题:
分析:C选项,因为数组是按行顺序进行存储的,所以先遍历x,再遍历y相对要快一点,这涉及到预取操作。 D选项,利用率=花在有意义工作的时间/花费的总时间x100%。
第(16)题:
分析:条件概率的计算。条件:在取出6红4蓝的情况下,所有球是从黑袋子取出来的概率是多少。