原标题:C语言面试题(三)
接着更新C语言面试题,希望能帮助到大家!
1、变量的声明和定义有什么区别
为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,但是只在一个地方定义。加入 extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间,如外部变量。
2、写出 bool 、int、 float、指针变量与“零值”比较的 if 语句
bool 型数据:
if( flag )
{
A;
}
else
{
B;
}
int 型数据:
if( 0 != flag )
{
A;
}
else {
B;
}
指针型数:
if( NULL == flag )
{
A;
}
else {
B;
}
float 型数据:
if ( ( flag >= NORM ) && ( flag <= NORM ) )
{
A;
}
注意:应特别注意在 int、指针型变量和“零值”比较的时候,把“零值”放在左边,这样当把“==” 误写成“=”时,编译器可以报错,否则这种逻辑错误不容易发现,并且可能导致很严重的后果。
3、sizeof 和 strlen 的区别
sizeof 和 strlen 有以下区别:
1 sizeof 是一个操作符,strlen 是库函数。
2 sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数。
3 编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。并且 sizeof 计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。
4 数组做 sizeof 的参数不退化,传递给 strlen 就退化为指针了。
注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就是 sizeof。
4、C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在 C 中 static 用来修饰局部静态变量和外部静态变量、函数。而 C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员和静态成员函数。
注意:编程时 static 的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而 C++的静态成员则可以在多个对象实例间进行通信,传递信息。
5、C中的 malloc 和C++中的 new 有什么区别
malloc 和 new 有以下不同:
(1) new、delete 是操作符,可以重载,只能在 C++中使用。
(2) malloc、free 是函数,可以覆盖,C、C++中都可以使用。
(3) new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
(4) malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
(5) new、delete 返回的是某种数据类型指针,malloc、free 返回的是 void 指针。
注意:malloc 申请的内存空间要用 free 释放,而 new 申请的内存空间要用 delete 释放,不要混用。
因为两者实现的机理不同。
6、写一个“标准”宏 MIN
#define min(a,b)((a)<=(b)?(a):(b))
注意:在调用时一定要注意这个宏定义的副作用,如下调用:
((++*p)<=(x)?(++*p):(x)。
p 指针就自加了两次,违背了 MIN 的本意。
7、一个指针可以是 volatile 吗
可以,因为指针和普通变量一样,有时也有变化程序的不可控性。常见例:子中断服务子程序修改一个指向一个 buffer 的指针时,必须用 volatile 来修饰这个指针。
说明:指针是一种普通的变量,从访问上没有什么不同于其他变量的特性。其保存的数值是个整型数据,和整型变量不同的是,这个整型数据指向的是一段内存地址。
8、a 和&a 有什么区别
#include
void main( void )
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
return;
}
输出结果:2,5。
9、简述 C、C++程序编译的内存分配情况
C、C++中内存分配方式可以分为三种:
(1) 从静态存储区域分配:
内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static 变量等。
(2) 在栈上分配:
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配:
即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
一个 C、C++程序编译时内存分为 5 大存储区:堆区、栈区、全局区、文字常量区、程序代码区。
10、简述 strcpy、sprintf 与 memcpy 的区别
三者主要有以下不同之处:
(1) 操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2) 执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
(3) 实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字符串的转化,memcpy 主要是内存块间的拷贝。
说明:strcpy、sprintf 与 memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。
11、设置地址为 0x67a9 的整型变量的值为 0xaa66
int *ptr; ptr = (int *)0x67a9;
*ptr = 0xaa66;
12、谈谈你对编程规范的理解或认识
编程规范可总结为:程序的可行性,可读性、可移植性以及可测试性。
说明:这是编程规范的总纲目,面试者不一定要去背诵上面给出的那几个例子,应该去理解这几个例子说明的问题,想一想,自己如何解决可行性、可读性、可移植性以及可测试性这几个问题,结合以上几个例子和自己平时的编程习惯来回答这个问题。
13、short i = 0; i = i + 1L;这两句有错吗
代码一是错的,代码二是正确的。
说明:在数据安全的情况下大类型的数据向小类型的数据转换一定要显示的强制类型转换。
14、&&和&、||和|有什么区别
(1)&和|对操作数进行求值运算,&&和||只是判断逻辑关系。(2)&&和||在在判断左侧操作数就能确定结果的情况下就不再对右侧操作数求值。
注意:在编程的时候有些时候将&&或||替换成&或|没有出错,但是其逻辑是错误的,可能会导致不可预想的后果(比如当两个操作数一个是 1 另一个是 2 时。
15、typedef 和 define 有什么区别
(1) 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义常量,以及书写复杂使用频繁的宏。
(2) 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3) 作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在 define 声明后的引用都是正确的。
(4) 对指针的操作不同:typedef 和 define 定义的指针时有很大的区别。
注意:typedef 定义是语句,因为句尾要加上分号。而 define 不是语句,千万不能在句尾加分号。
16、链表和数组有什么区别
数组和链表有以下几点不同:
(1) 存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。
(2) 数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。
(3) 数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
(4) 越界问题:链表不存在越界问题,数组有越界问题。
说明:在选择数组或链表数据结构时,一定要根据实际需要进行选择。数组便于查询,链表便于插入删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。
17、如何避免“野指针”
“野指针”产生原因及解决办法如下:
(1) 指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向 NULL。
(2) 指针 p 被 free 或者 delete 之后,没有置为 NULL。解决办法:指针指向的内存空间被释放后指针应该指向 NULL。
(3) 指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向 NULL。
注意:“野指针”的解决方法也是编程规范的基本原则,平时使用指针时一定要避免产生“野指针”,在使用指针前一定要检验指针的合法性。
18、怎样把一个单链表反序
(1) 反转一个链表。循环算法。
List reverse(List n)
{
if(!n) //判断链表是否为空,为空即退出。
{
return n;
}
list cur = n.next; //保存头结点的下个结点
list pre = n;
list tmp; //保存头结点
pre.next = null; //头结点的指针指空,转换后变尾结点
while ( NULL != cur.next ) //循环直到 cur.next 为空
{
tmp = cur;
}
tmp.next = pre;
pre = tmp;
cur = cur.next;
}
return tmp; //f 返回头指针
}
(2) 反转一个链表。递归算法。
List *reverse( List *oldList, List *newHead = NULL )
{
List *next = oldList-> next; //记录上次翻转后的链表
oldList-> next = newHead; //将当前结点插入到翻转后链表的开头
newHead = oldList; //递归处理剩余的链表
return ( next==NULL )? newHead: reverse( t, newHead );
}
说明:循环算法就是移动过程,比较好理解和想到。递归算法的设计虽有一点难度,但是理解了循环算法,再设计递归算法就简单多了。
19、简述队列和栈的异同
队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是
“后进先出”。
注意:区别栈区和堆区。堆区的存取是“顺序随意”,而栈区是“后进先出”。栈由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收。分配方式类似于链表。
它与本题中的堆和栈是两回事。堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。
20、能否用两个栈实现一个队列的功能
说明:用两个栈能够实现一个队列的功能,那用两个队列能否实现一个队列的功能呢?结果是否定的,因为栈是先进后出,将两个栈连在一起,就是先进先出。而队列是现先进先出,无论多少个连在一起都是先进先出,而无法实现先进后出。
//结点结构体:
typedef struct node
{
int data;
node *next;
}node, *LinkStack;
//创建空栈:
LinkStack CreateNULLStack(LinkStack &S)
{
S = (LinkStack)malloc(sizeof(node)); //申请新结点
if (NULL == S)
{
printf("Fail to malloc a new node.\n");
9
return NULL;
}
S->data = 0; //初始化新结点
S->next = NULL;
return S;
}
//栈的插入函数:
LinkStack Push(LinkStack &S, int data)
{
if (NULL == S) //检验栈
{
printf("There no node in stack!");
return NULL;
}
LinkStack p = NULL;
p = (LinkStack)malloc(sizeof(node)); //申请新结点
if (NULL == p)
{
printf("Fail to malloc a new node.\n");
return S;
}
if (NULL == S->next)
{
p->next = NULL;
}
else
{
p->next = S->next;
}
p->data = data; //初始化新结点
S->next = p; //插入新结点
return S;
}
//出栈函数:
node Pop(LinkStack &S)
{
node temp;
temp.data = 0;
temp.next = NULL;
if (NULL == S) //检验栈
{
printf("There no node in stack!");
return temp;
}
temp = *S;
10
if (S->next == NULL)
{
printf("The stack is NULL,can't pop!\n");
return temp;
}
LinkStack p = S->next; //节点出栈
S->next = S->next->next;
temp = *p;
free(p);
p = NULL;
return temp;
}
//双栈实现队列的入队函数:
LinkStack StackToQueuPush(LinkStack &S, int data)
{
node n;
LinkStack S1 = NULL;
CreateNULLStack(S1); //创建空栈
while (NULL != S->next) //S 出栈入 S1
{
n = Pop(S);
Push(S1, n.data);
}
Push(S1, data); //新结点入栈
while (NULL != S1->next) //S1 出栈入 S
{
n = Pop(S1);
Push(S, n.data);
}
}
21、计算一颗二叉树的深度
深度的计算函数:
int depth(BiTree T)
{
if(!T) return 0; //判断当前结点是否为叶子结点
int d1= depth(T->lchild); //求当前结点的左孩子树的深度
int d2= depth(T->rchild); //求当前结点的右孩子树的深度
} return (d1>d2?d1:d2)+1;
如果能帮助到你,不要忘记给涛哥点赞呀!!!!返回搜狐,查看更多
责任编辑: