c语言面试常见题
一、typedef
为现有类型创建一个新的名字, 使用最多的地方是创建易于记忆的类型名
typedef int size;此声明定义了一个 int 的同义字,名字为 size
想看http://baike.baidu.com/view/1283800.htm
第12题:考查typedef类型定义,函数指针
例1:typedef int (*test) ( float * , float*)
test tmp;
tmp 的类型是
(a) 函数的指针,该函数以 两个指向浮点数(float)的指针(pointer)作为参数(arguments)
Pointer to function of having two arguments that is pointer to float
(b) 整型
(c) 函数的指针,该函数以 两个指向浮点数(float)的指针(pointer)作为参数(arguments),并且函数的返回值类型是整型
Pointer to function having two argument that is pointer to float and return int
(d) 以上都不是
另外一例:
typedef void(*ptr_tp_func)(int);/*它表示ptr_tp_func是函數指針,該函數接受一個int參數,返回值為void*/
ptr_tp_func signal(int, ptr_tp_func);/*表示signal是一個函數,接受兩個參數,一個int和一個ptr_tp_func,返回值是ptr_tp_func類型*/
二、按位运算等小技巧
1、统计i有多少位为1:for( ; i ; i&=i-1) k++;
2、宏写出swap(x,y):
#define swap(x,y)
x=x+y; y=x-y; x=x-y;
3、一句实现判断x是不是2的若干次幂:x&(x-1)?false:true;
4、实现左移循环移n位:a=(a<<n)|(a>>(8*sizeof(int)-n));
5、快速求一个数的7倍:X<<3-X
三、编译宏
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个#if……#else条件编译块
#error 停止编译并显示错误信息
四、指针
1、 有10个指针的数组 int *p[10];
int (*p)[10]中p是一个指针。它的类型是:指向int x[10]这样的一维数组的指针。
指向函数的指针,函数有一个整型参数并返回一个整数型 int (*p)(int);
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 int (*p[10])(int);
2、char a[9]="abcdefg"; char *p="abcdefg";
其它区别:1.指针保存数据的地址,如p保存的是常量字符串“abcdefg”地址,对p内容的修改是非法的 ;数组保存的是数据,对数组的内容可以直接修改。
五、限定符的用法
1、const
1)作用:a.告知参数的应用目的
b.使优化器产生紧凑的代码
c.保护不希望被改变的参数:防止参数意外改变
2)例子:a. const int a; a是一整型常量
b. int const a; 同上
c. const int *a; 指针a指向的内容不可变
d. int * const a;指针a指向的内容可变,但指针不可变
e. int const * a const; 都不可变
面试的问题:
a.一个参数既可以是const又是volatile吗?
b.一个指针可以是volatile吗?
答:a.可以,例子:只读的状态寄存器,它是volatile因为可能被意想不到的改变,是const因为不希望程序试图去改变它。
b.可以。一个例子是中断服务子程序修改一个指向一个buffer的指针时。
此类考题记住volatile型变量可能随时改变即可。
2、restrict
restrict关键字允许编译器优化某部分代码以更好地支持计算。它只能用于指针,表明该指针是访问该对象唯一且初始的方式。
int ar[10];
int * restrict restar= (int *) malloc(10 * sizeof(int));
int * par= ar;
for (n=0; n<10; n++) {
par[n]+=5;
restar[n] +=5;
ar[n] *=2;
par[n] +=3;
restar[n] +=3;
}
restar是访问它所指向的数据块的唯一且初始的方式,编译器可以把涉及restar的两条语句替换成下面的语句,效果相同:
restar[n] +=8;/*可以进行替换*/
3、register
register关键字请求让编译器将变量a直接放入寄存器里面,以提高读取速度,因为register变量可能不存放在内存中,在C语言中register关键字修饰的变量不可以被取地址,但是c++中进行了优化。
六、精度和优先级
1、类型不同的操作数运算,精度向低级自动转换。无符号+有符号=无符号
2、.设 int a=12,则执行完语句 a+=a-=a*a后,a的值是-264.
所有的赋值符(包括复合赋值)都具有右结合性,就是在表达式中最右边的操作最先执行,然后从右到左依次执行。
3、c中函数参数默认是从右向左压栈的,printf计算参数时也是
int arr[]={6,7,8,9,10}; int *ptr=arr; *(ptr++)+=123;
printf("%d,%d\n",*ptr,*(++ptr));结果为8
即printf内的参数从右向左运算。
4、位运算要考虑机器字长,算术运算符优先级高于移位运算符
unsigned char = 0xA5; unsigned char b = ~a>>4+1; 则b为250.
5、三个float:a,b,c .问值 (a+b)+c==(b+a)+c 和(a+b)+c==(a+c)+b
不一定相等,float存在大数淹没小数的情况,如
float a=100000000000,b=-100000000000,c=0.00000000001;
cout < <a+b+c < <endl;
cout < <a+c+b < <endl;
结果是1e-011和0;
尽量把数量级相近的数先相加
七、大小端问题
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。arm、powerpc
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。intelx86
大小端测试程序1:
int checkSystem( )
{union check
{int i; char ch;
}c; c.i=1; return (c.ch==1);
}
测试大小端的程序2:
在使用little endian的系统中 这些函数会把字节序进行转换:
define HTONS(n) ((((u16_t)((n) & 0xff)) << 8) | (((n) & 0xff00) >> 8))
ltons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序
在使用big endian类型的系统中 这些函数会定义成空宏。
利用linux中自带的宏进行判断:
#if __BYTE_ORDER == __LITTLE_ENDIAN
// 小头字节序
#elif __BYTE_ORDER == __BIG_ENDIAN
// 大字节序
八、内存
1、申请内存的函数
void *calloc ( size_t num_elements, size_t element_size );
void *realloc (void *ptr, size_t new_size );
realloc函数用于修改一个原先已经分配的内存块的大小,可以使一块内存的扩大或缩小。当起始空间的地址为空,即*ptr = NULL,则同malloc。calloc与malloc相比:calloc分配的内存被初始化为0,calloc两个参数:元素个数,元素大小。
2、内存分布
BSS段:未初始化的全局变量和静态变量;数据段:初始化的全局变量和静态变量;代码段(或文本段):可执行文件的指令;局部变量运行时创建并存储于栈,函数结束即释放;静态变量和全局变量则在程序结束后释放;可执行文件中包含BSS段所需要的大小,但不是BSS段,堆栈等在程序运行时创建
九、中断
1、 找出下面一段ISR的问题。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
1)ISR不能传递参数。
2)ISR不能有返回值。
3)ISR应该短且有效率,在ISR中做浮点运算不明智。
4)ISR中不应该有重入和性能上的问题,因此不应该使用pintf()函数。
不可重入函数不可以在它还没有返回就再次被调用。例如printf,malloc,free等都是不可重入函数。因为中断可能在任何时候发生,例如在printf执行过程中,因此不能在中断处理函数里调用printf,否则printf将会被重入。 函数不可重入大多数是因为在函数中引用了全局变量。例如,printf会引用全局变量stdout,malloc,free会引用全局的内存分配表。
十、链表
一个链表不知道头结点,有一个指针指向其中一个结点,请问如何删除这个指针指向的结点:将这个节点复制成下一个节点的值,然后删除下一个节点
typedef struct LinkList {
int Element;
LinkList * next;
}LinkList;
初始化:
linklist * List_init(){
linklist *HeadNode= (linklist*)malloc(sizeof(linklist));
if(HeadNode == NULL) {
printf("空间缓存不足");
return HeadNode;
}
HeadNode->Element= 0;
HeadNode->next= NULL;
Return HeadNode;
}
创建链表:
void CreatList(linklist *HeadNode,int *InData,int DataNum)
{
int i = 0;
linklist *CurrentNode = (linklist*) HeadNode;
for(i = 0;i<DataNum;i++)
{
CurrentNode->Element = InData[i];
if(i< DataNum-1)// 由于每次赋值后需要新建结点,为了保证没有多余的废结点
{
CurrentNode->next =(linklist *)malloc(sizeof(linklist));
CurrentNode= CurrentNode->next;
}
}
CurrentNode->next= NULL;
}
插入节点:
bool InsertList(linklist *HeadNode,int LocateIndex,int InData){
int i=1;// 由于起始结点HeadNode是头结点,所以计数从1开始
linklist *CurrentNode= (linklist *) HeadNode;
//将CurrentNode指向待插入位置的前一个结点(index -1)
while(CurrentNode&& i<LocateIndex-1){
CurrentNode= CurrentNode->next;
i++;
}
linklist *NodeToInsert=(linklist*)malloc(sizeof(linklist));
if(NodeToInsert == NULL){
printf("空间缓存不足");
return ERROR;
}
NodeToInsert->Element= InData;
NodeToInsert->next = CurrentNode->next;
CurrentNode->next = NodeToInsert;
return OK;
}
具体参考:https://blog.csdn.net/u012531536/article/details/80170893
十一、内联函数
内联函数(inline) |
带参数的宏 |
编译(汇编)时展开 |
预编译时替换 |
严格的参数类型检查 |
简单的替换 |
函数体不能太大,不能包含大循环和递归 |
没要求 |
当编译器发现某段代码在调用一个内联函数时,它不是去调用该函数,而是将该函数的代码,整段插入到当前位置。这样做的好处是省去了调用的过程,加快程序运行速度。内联函数适用于函数体较小且频繁使用的函数,调用时进行代码的复制,无需跳转、入栈等操作,以空间换时间而且因为函数有严格的参数类型检查,比宏要安全;内联函数在编译时不单独产生代码 。