记录一下考试中不懂的问题:
1、C的可重入函数
可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
2、strlen和sizeof的区别:
虽然是常识,但我经常忘记哪个是哪个,strlen不会统计‘\0’所占用的一个字节,sizeof会
3、数组和链表的区别
数组:数据顺序存储,固定大小
链表:数据可以随机存储,大小可以动态改变
4、tpyedef 和define
typedef (int*) pINT;
以及下面这行:
#define pINT2 int*
效果相同?实则不同!
实践中见差别:
pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。
pINT2 a,b;的效果同int *a, b;表示定义了一个整型指针变量a和整型变量b。
5、
main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d" , *(a+1),*(ptr-1) );
}
这段程序的输出是:2 5
&a+1 不是表示a的地址(设为0x0010)加1,变为0x0011. 由于a为包括5个int类型的数组,则"&a+1"中的"+1"表示为相当于"1"个a大小的空间(或成为偏移),此时&a+1 表示 a[5].由于&a+1 表示 a[5], 则ptr即为a[5]。
又 ptr 为int型的指针,故 "ptr-1"则会减去"1"个int型指针的空间,此时即为a[5-1]=a[4].
6、逗号表达式
逗号表达式的值就是最后一个元素的值
main()
{
int a, b,c, d;
a=3;
b=5;
c=a,b;
d=(a,b);
printf("c=%d" ,c);
printf("d=%d" ,d);
}
这段程序的输出是:c=3d=5 注意有括号才是都好表达式
7、找错
①
void GetMemory( char *p )
{
p = (char *) malloc( 100 );
}
void Test( void )
{
char *str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}
传入中GetMemory( char *p)函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完
char *str = NULL;
GetMemory( str );
后的
str
仍然为
NULL
(可以改为双重指针,然后改变指针的内容char **p=(char*)malloc(100))而且要记得释放掉指针。
②
char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
char p[] = "hello world";
return p; 中的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放,根本不知道GetMemory()指向哪里。(p加static,就可以了)
8、分别给出BOOL,int,float,指针变量与“零值”比较的 if 语句(假设变量名为var)
解答:
BOOL型变量:if(!var)
int型变量: if(var==0)
float型变量:
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON)
指针变量: if(var==NULL)
剖析:
考查对0值判断的“内功”,BOOL型变量的0判断完全可以写成if(var==0),而int型变量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正确运行,但是未能清晰地表达程序的意思。
一般的,如果想让if判断一个变量的“真”、“假”,应直接使用if(var)、if(!var),表明其为“逻辑”判断;如果用if判断一个数值型变量(short、int、long等),应该用if(var==0),表明是与0进行“数值”上的比较;而判断指针则适宜用if(var==NULL),这是一种很好的编程习惯。
浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。如果写成if (x == 0.0),则判为错,得0分。
9、void Func ( char str[100] )
{
sizeof( str ) = ?
}
答案:4,不是100(若不是作为形参,则100)
Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。(本来数组名是作为一个指针常量,不能自增自减的 )
10、
least = MIN(*p++, b);
解答:
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
MIN(*p++, b)
会产生宏的副作用
剖析:
这个面试题主要考查面试者对宏定义的使用,宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的
“
参数
”
也不是真的参数,
在宏展开的时候对“参数”进行的是一对一的替换
。
宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:
((*p++) <= (b) ? (*p++) : (b))
这个表达式会产生副作用,指针p会作多一次++自增操作。
11、strcpy和memcpy都是标准C库函数,它们有下面的特点。
①strcpy提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。strcpy函数的原型是:char* strcpy(char* dest, const char* src);
②memcpy提供了一般内存的复制。即memcpy对于需要复制的内容没有限制,因此用途更广。
void *memcpy( void *dest, const void *src, size_tcount);
12、
char a,b,c,*d; a=‘\’; b=‘\xbc’; d=“\017”; printf(“%c%c%c\n”,a,b,c,*d)找出编译时出错的地方
别人的答案:
a必然错了,a应写成:a='\\' (\是转义字符) d也是错的,楼主说的很对 d是一个地址,不能这么赋值
(我觉得d没错,注意是双引号啊,是给了那个字符串的首地址常量给d?)
b是一个16进制数
13、交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3;(数学题了。。。)
a = a + b;
b = a - b;
a = a - b;
14、c 和 c++ 中的 struct 有什么不同?
(1)C++类中属性默认访问类型为private,而c++中的struct默认的访问类型为public
(2)c++类可以有继承,虚函数,多态,而c++中struct不可以。
C语言struct里面不可以有函数,只能有变量。 C++给C中的struct功能扩展了
14、以下叙述中错误的是
A.二进制文件打开后可以先读文件的末尾,而顺序文件不可以
B.在程序结束时,应当用函数fclose( )关闭已打开的文件
C.在利用函数fread( )从二进制文件中读数据时,可以用数组名给数组中所有元素读入数据
D.不可以用FILE定义指向二进制文件的文件指针
我选了D,不知道对不对
15、野指针
“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”。有个良好的编程习惯是避免“野指针”的唯一方法。
野指针的成因主要有三种:
一、
指针变量
没有被初始化。
任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
二、
指针p被free或者delete之后,没有置为NULL
,让人误以为p是个合法的指针。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
三、
指针操作超越了变量的作用范围
。比如不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
15、
ifdef与ifndef的区别:
ifdef:如果标识符被定义,则执行程序段1,否则执行其他程序段
ifndef:与ifdef相反,如果标识符未被定义,则执行程序段1,否则执行其他程序段2
避免头文件重复应用(ifndef和ifdef不要弄错)
每次写头文件的时候加上:
#ifndef _XXX_H_
#define _XXX_H_
#endif
16、static 关键字的变量,全局对象未初始化时,为0
17、
什么是字节对齐,为什么要对齐?
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
二.字节对齐对程序的影响:
先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)
short:2(有符号无符号同)
int:4(有符号无符号同)
long:4(有符号无符号同)
float:4 double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
修改代码:
struct A {
// int a;
char b;
short c;
};
struct B {
char b;
// int a;
short c;
};
输出都是4,说明之前的int影响对齐!
18、字节对齐(
在没有#pragma pack宏的情况下,务必看完最后一行
)
对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编
译命令 #pragma
pack(n),n=1,2,4,8,16 来改变这一系数,其中的n 就是你要指定的“对齐系数”。
规则
⒈数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset
为0 的地方,以后每个数据成员的对齐按照#pragma pack 指定的数值和这个数据成员自身长
度中,比较小的那个进行。
⒉结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进
行对齐,对齐将按照#pragma pack 指定的数值和结构(或联合)最大数据成员长度中,比较小
的那个进行。
⒊结合1、2 可推断:当#pragma pack 的n 值等于或超过所有数据成员长度的时候,这个n
值的大小将不产生任何效果。
收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
19、关于临时变量
(1)不要对临时变量进行取地址操作,因为不知道编译器是否将它映射到寄存器
(2)不要返回临时变量的地址,或者是临时指针变量,因为是函数调用结束后,会回收,不知道指向哪里
(3)不要申请过大的临时数组,因为是放在堆栈中的,堆栈可能不够大
20、不用局部变量和全局变量实现strlen
使用递归:
int slen(char *str)
{
if(NULL == str)
return 0;
if(*str == '\0')
return 0;
return slen(str+1)+1;
}
21
try
{
//执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容
}
catch
{
//除非try里面执行代码发生了异常,否则这里的代码不会执行
}
finally
{
//不管什么情况都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally
}