1.题 考查隐式数据类型转换 , C 默认的是将需要自动转换的从低级转换为高级具体情况如下:
那么什么时候会发生隐式数据类型转换呢 , 主要有下列的情况 ~:
a>算数运算式中低类型能转换为高类型.
b>复制表达式,右边表达式的值自动转换为左边变量的类型并赋值给它.
c>函数调用中参数传递时,系统隐式地将实参转换为形参类型,并赋值给形参.
d>函数有返回值时, 系统将隐式地返回值表达式类型转换为返回值类型,复制给调用函数.
注:不同的数据类型的数据进行操作时,应该先将其转换成相同的数据类型,然后进行操作,转换规则是由低级转换为高级.
2. 考查sizeof 计算结构和联合所占的字节数
3.考查位运算
看到题目,开始是让求平均数, 我们可以很简单地进行相加除以2,这里却用 a + (b-a)/2 这样做的好处就是防止相加的时候两个数过大, 上溢出.关于下面的功能(当然,需要给后面整体加上括号,符号优先级问题).关于这个为什么这样写, (a&b) + ((a^b)>>1) , 大家知道右移1位 , 相当于除以二. 但是 a&b和a^b是什么意思呢. 我们先来看一个用位运算时现任何两个数的加法的操作.
:
首先一位的加法(不产生进位的情况), 用^处理就好了. 关于处理进位情况,当两个位都为1的时候,产生进位. 那么这时就可以用&操作, 保留所有需要进位的位 , 然后对结果进行一次左移, 我们知道左移就相当于进位了. 然后其它位的处理,就使用^进行, 我们把操作分为了两部分, 一部分用来处理进位情况, 一部分用来处理不进位的那些位. 然后我们进行迭代. 直到不产生进位的时候, 也就是& <<1 操作的值为0 的时候. 代码附一下 :
01 #include <stdio.h>
02
03
int
cal (
int
a ,
int
b) {
04
05
return
b ? cal ((a ^ b) , (a & b)<<1) : a ;
06 }
07
08
int
main(
int
argc,
char
*argv[])
09 {
10
int
a , c ;
11
12
scanf
(
"%d%d"
,&a , &c) ;
13
14
printf
(
"val = %d\n"
, cal (a , c) );
15
16
return
0;
17 }
这是一个迭代的操作, 所以我们可以使用递归来完成 . 同样的想实现减法, 给b取非 +1, 很简单就完成了.
然后大家再看这个题 前面那个a&b同样的是判断有没有进位, 如果有进位,那么就保留那一位,因为要除以二,所以不用向左平移 , 后面是处理不用进位的部分 , 同样的需要除以二 , 用向右移位完成. 好啦 ~ 就是这个思路. 至于乘法 , 除法 ,我就没有写了.大家可以研究研究.
4.考查sizeof与strlen的区别,大家应该都知道了. sizeof 在编译后就已经没有了, 已经被计算出来的值替换了. 而strlen则是运行时计算的.sizeof计算的是后面对应的数据类型的字节大小,值得一提的是数组是一个构造数据类型,它是一个整体.应该以整体计算.可以看一个例子.
01 #include <stdio.h>
02
03
int
main( )
04 {
05
int
a=3 , b ;
06
07 b = a +
sizeof
(a) ;
08
09
return
0;
10 }
看一下反汇编以后的代码:
080483f0 <main>: 80483f0: 55 push ebp 80483f1: 89 e5 mov ebp,esp 80483f3: 83 ec 10 sub esp,0x10 80483f6: c7 45 fc 03 00 00 00 mov DWORD PTR [ebp-0x4],0x3 80483fd: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 8048400: 83 c0 04 add eax,0x4 8048403: 89 45 f8 mov DWORD PTR [ebp-0x8],eax 8048406: b8 00 00 00 00 mov eax,0x0 804840b: c9 leave 804840c: c3 ret 804840d: 66 90 xchg ax,ax 804840f: 90 nop
5.考查位域
C 不允许每个位域成员超过基类型的大小.使用sizeof计算位域的大小,将返回基类型的大小,即字节对其以后的大小.关于位域,这里有很多需要注意的点,我在这一一列举一下:
1>.linux下位域可以横跨两个字节,即一个位域可以超过8bit , 但是不能超过自己的类型的sizeof.
2>.不允许对位域成员进行取地址.
3>.如果相邻字段位域类型相同,且前后加起来的大小不超过sizeof类型的大小 , 则后面字段将紧临前一字段存储.
4>.如果相邻字段类型相同但是前后加起来大于类型的sizeof大小,则另起炉灶.
5>.相邻字段间的类型不同,则不同的编译器处理不同.
6>.整个结构体大小为最宽的类型的大小的整数倍.
numbering (or sometimes bit endianness)
is the convention used to identify the bit positions
in a binary
number or a container for such a value. The bit number starts with zero and is incremented by one for each subsequent bit position.
1 #include <iostream>
2 #include <cstring>
3
using
namespace
std;
4
struct
A
5 {
6
int
a:5;
7
int
b:3;
8 };
9
int
main(
void
)
10 {
11
char
str[100] =
"012345"
;
12
struct
A c;
13
memcpy
(&c, str,
sizeof
(A));
14 cout << c.a << endl;
15 cout << c.b << endl;
16
return
0;
17 }
猜猜结果会是怎么样,我给大家画一下(字符0对应的ASCII 值为48 也就是00110000)
自己敲一下上面的代码,应该可以得到结果 ,然后小小猜一下,也差不多出来了。
都知道的是float的精度小于double的精度,很简单么,double占得字节多呗!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include <stdio.h> int main () { float PI = 3.14000000000000000; printf ( "PI = %.10f \n" , PI) ; printf ( "double PI = %.10lf \n" , 3.14) ; if (PI == 3.14) { printf ( "That's impossible !\n" ) ; } return 0 ; } |
[tutu@localhost 面试]$ ./a.out PI = 3.1400001049 double PI = 3.1400000000 [tutu@localhost 面试]$
结果很明显了
关于i为什么分配在数组之前,我测试过放在数组后定义,但是汇编代码是一样的.在崔娇娇学姐那里却不对了,i分配的时候分在了数组的下面.呵呵, 这个就有些让人费解了,找原因,最后看看gcc版本不同. 还有,在64位gcc编译的时候,不会出现这样的溢出.详细的有兴趣大家去研究研究吧~~~ .
12题考查的还是缓冲区问题,但是这个是输入缓冲区
gets是有缓冲的函数,意思是输入会先保存到一个buffer中,当buffer满或者强制刷新的时候(如:换行,fclose操作),buffer会刷新.
缓冲分为:全缓冲,行缓冲,无缓冲 .分别举例说一下吧.
全缓冲:指的是系统在填满标准IO缓冲区之后才进行实际的IO操作;注意,对于驻留在磁盘上的文件来说通常是由标准IO库实施全缓冲。
行缓冲:标准IO在输入和输出中遇到换行符时执行IO操作;注意,当流涉及终端的时候,通常使用的是行缓冲。
无缓冲:无缓冲指的是标准IO库不对字符进行缓冲存储;注意,标准出错流stderr通常是无缓冲的。
我们可以分别实验一下:(摘UNIX环境高级编程8章)
#include <stdio.h> #include <string.h> #include <unistd.h> char buf[] = "Let's start !\n"; int main() { pid_t pid; if(write(STDOUT_FILENO, buf, strlen(buf)) != strlen(buf)) { fprintf(stderr, "write error"); return 0; } printf("before fork()...\n"); if((pid = fork()) == -1) { fprintf(stderr, "fork error"); return 0; } sleep(2); printf("parent process id:%d ", getppid()); printf("process id:%d\n", getpid()); return 0; }
直接运行的结果和运行时重定向,结果是有区别的, 这个例子让你很好理解行缓冲和全缓冲.当我们的程序有交互的时候,e.g.和终端交互,缓冲类型为行缓冲.来看一下下输出:
tutu@localhost 面试]$ ./a.out Let's start ! before fork()... parent process id:2004 process id:2991 parent process id:2991 process id:2992 [tutu@localhost 面试]$
我们再用重定向到文件看看:
[tutu@localhost 面试]$ ./a.out > test.txt [tutu@localhost 面试]$ vim test.txt Let's start ! before fork()... parent process id:2004 process id:2996 before fork()... parent process id:2996 process id:2997 [tutu@localhost 面试]$
这样就多了一次before fork () ... , 没有和终端交互,那么就是全缓冲. 也看到了write 是无缓冲的(就暂时这样去理解),它直接写到对应的流中,如输出流.
还有要注意的时gets函数,绝对不推荐大家使用,因为这货不对输入长度检查,输入多少就往栈里尽可能扔多少,完全不估计溢出的情况,所以就是这货引发了1988年一次规模比较大的蠕虫病毒.原因什么的,大家有兴趣可以自己研究,欢学长的博客建议大家一定要看看.以后就赶快扔了gets吧,全部使用fgets (char *s , int size , FILE *stream) .
13.就不用讲了, 大家都会的 . 排序重组么.
14.考查static 的作用, 首先应该知道的是static 变量保存在全局静态区 , 是全局静态区 , 生存期整个程序 , 全局静态变量作用域本文件.局部的那就只能在局部.需要注意的是static 变量初始化的时候必须以常量或者常量表达式.不初始化时,编译器给你自动初始化为0 .
题目中的初始化方式不对, 由于逗号表达式的值是不定的,并非常量或者常量的表达式.
15. 考查大端小端,实际上就是数据在内存中的存放顺序. 不同的处理器生产商有自己不同的设计. 可以参见这位大神的ce123的博客,很经典的说.我在这里也是retell.
一般小端的机器有:x86 DEC
大端机器有:POWER PC , IBM , SUN
可能是大端也可能是小端:ARM MAC
现代的CPU都可以大端小端共存的 , 通过跳线来对不同的情况作出处理. 现在我们来大概看一下自己的pc是哪种endian .
#include <stdio.h> int main(int argc, char *argv[]) { int a = 0x12345678 ; char * p = (char *) &a ; printf ("%x\n" , *p) ; printf ("%x\n" , *(p+1)) ; printf ("%x\n" , *(p+2)) ; printf ("%x\n" , *(p+3)) ; return 0; }
方法很多,这样只是简便一点吧 . 看一下结果 :
[tutu@localhost 面试]$ ./a.out 78 56 34 12 [tutu@localhost 面试]$
简单画一下 :
可以看到高位自己放在了高地址, 低位字节放在了低地址.这是典型的高高低低.我们这里的单位是字节即8bit为atomic elements.
对应大端呢, 就是倒过来了.
JAVA和网络传输都使用的大端, 所以,我们在做网络相关的开发时, 总是需要这几个函数:htonl(), htons(), ntohs(),ntohl() , 就是用来转换字节序的.详细的大家自己去google吧.
16题考查C预处理都做了什么即 , 宏替换在先还是去除注释在先 .这个在C99文档里(147页左右)有相关说明.这里我大概说一下这里有前辈的链接C预处理的步骤
1.三连符替换成相应的单字符
2.把用\
字符续行的多行代码接成一行比如
#define STR "hello, "\ "world"
替换成一行
3.把注释(不管是单行注释还是多行注释)都替换成一个空格。
4.处理Token(记号)和空白字符 (即分割)
5.宏展开,包含源文件
...
后面的大家去研究吧,更细的东西在链接里.大家有兴趣去看看吧.
17题考查枚举常量,这个相信大家都会了 , 就不多讲了.
18题又考查sizeof , 这个上面已经给大家将了,可以参照上面的方法.做一下,一目了然.
19.纯粹时考查你的细心程度. 当然这也告诉大家 , 注意自己的代码风格,格式.别到最后自己都看不懂自己的代码了.
21题 我相信大家一定会的.
20题23题24题25题都是编程题目, 这个我觉得要是我讲的画, 你的思路就会被我的想法占据. 我也相信大家都会的. 这里只简单说一下.
int my_strcmp (const char * dest ,const char * source ) { if ( NULL == dest || NULL == source ) { printf ("error\n") ; exit (-1) ; } while (( 0 != *dest ) && ( 0 != *source )) { if (tolower(*dest) == tolower(*source)) { dest++ ; source++ ; } else break ; } return *dest - *source ; }
思路比较简单.实现的也很简单.
23题 用两个变量(max , less_max)分别保存最大和次大,假定第一个节点最大,赋值给max , 并初始化less_max为INT_MIN , 每次向前比较, 发现比当前最大值大 , 将max更新,并同时更新less_max为max之前值, 当发现当前值比max小,这时也要比较是否比less_max大,如果大,则更新less_max.嗯 , 思路就是这样.
24.字符串匹配问题,有库函数可以调的 char * strstr(const char * , const char *),返回第一次出现子串的地址. 这里让大家手动实现,有两种方法, BF , KMP , 这是两种不同的思想.BF 是暴力去匹配, 也是我们平时用的多的. KMP则是使用一定的算法(呜呜, 有点难理解) . 这个有小组张续学长的博客在,大家上去可以仔细研究.KMP详细过程
25.实现一个范型的swap , 方法很多, 我这里直接调的库函数
void swap (void * dest , void * source , int size ) { void * p = malloc (size) ; memcpy (p , dest , size) ; memcpy (dest , source , size) ; memcpy (source , p , size) ; free (p) ; }