C语言面试必问的经典问题(纯”gan“货)

C语言面试必问的经典问题

1.预处理

1. 预编译,编译过程最先做的工作是啥?何时需要预编译 ?指令有什么

答:预编译就是预处理,就是把一些文本的替换工作工作
预编译指令:#include、#ifdef 、#ifndef、#else 、#endif

编译#字开头的指令,如
拷贝#include包含的头文件代码,#define宏定义的替换,条件编译ifndef
答:
①总是经常使用但是不经常改动的大型代码。
②程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项,将所有包含文件预编译为一个 “预编译头”。

2.用一个宏来表示一年中有多少秒?

#define SEONDS_PER_YEAR(60*60*24*365)UL

3. c语言中 # 与 ##的区别以及作用

答:
# :把宏参数变成一个字符串;
## :把两个宏参数连接到一起(只能两个)
例:

#define hehe(x,y)  x##y
int main()
{

  char string[]="hello world!";

  printf("%s\n",hehe(str,ing)); 
 
 
   system("pause");
   return 0;
}

3. 写一个宏函数表示返回两个参数的最小值?

取两个数的最大值,用一个宏来表示,取两个数或者交换两个数的值,用一个宏来表示等这样的一些类问题

#define MIN(a,b) ((a)<(b) ? (a):(b))

4. 写一个宏求数组元素个数?

#define EKENEBTS(A) (sizeof(A)) / (sizeof(A[0]))

5.如何避免头文件被重复包含?

答:解决方法:
应用#ifndef #define #endif

2. 关键字

1.static关键字的作用?

分三种情况:

  1. static修饰的是一个全局的一个变量,它的作用域在我们整个.c文件里,并不属于我们整个工程。
  2. static修饰函数体内部,作用域也在函数体内部,它与局部变量的区别是,它的内存地址在全局区,局部变量的内存在栈区,而且它生命周期会变长,随着整个程序完事后才会消失。
  3. static修饰函数,作用域在我们整个.c文件里,并不属于我们整个工程。

答:static最主要功能是隐藏,其次因为static变量存放在静态存储区,具备持久性和默认值为0

①隐藏作用,可以在不同的文件中定义同名变量和同名函数。
②对于变量来说,保持变量持久,静态数据区的变量会在程序刚刚运行时就完成初始化,也是唯一一次初始化;储存在静态数据区,静态存储区只有两种变量(全局变量和static静态变量)。
③默认初始化为0x00,和全局变量一样的属性,减少程序员的工作量。

2.const关键字的作用

答:

①对变量加以限定不能被修改,常量必须在定义的时候同时被初始化。
②const和指针一起使用,

const int *p1;
int const *p2;
int *const p3;

在三种情况中,第三种指针是只读的,p3本身的值不能被修改;
第一二种情况,指针所指向的数据是只读的,p1,p2的值可以修改,但指向的数据不能被修改。
③const和函数形参一起使用
使用const单独定义变量可以用#define命令替换,const通常放在函数形参中。
如果形参是一个指针,为了防止在函数内部修改指针指向的数据就可以用const来限制。

3.volatile关键字的作用?

答:我们可以实时获取它这个变量的最新值

volatile它就是一个“易变”的一个变量,我们每次获取它值的时候,都是从它内存里去读取。

更深层次含义: 用volatile这样来告诉编译器,叫它不要对我这个变量做过分优化,也就是告诉它我这个变量用在哪,就实打实的访问它的内存,而不是访问这个变量所构建的一个寄存器

我们用这样的变量时候呢,它可能同时被好几个中断或者好几个线程同时调用,我们用volatile这样的变量可以保证获取这个变量值的实时性

4.extern关键字的作用?

答:
①函数内的局部变量,函数外定义的变量为全局变量,为静态存储方式,生存周期为整个程序,有效范围为定义变量的位置开始到本源文件结束。
如果在定义前想要引用该全局变量,则应该加上 extern作为 “外部变量声明”。

多个源文件的工程想要引用一个源文件的外部变量也只许引用变量的文件中加入extern关键字加以声明,但是可以在引用的模块内修改其变量的值,慎用。

②extern “C”: C++代码调用C语言代码。在C++的头文件中使用。

5.sizeof关键字的作用?

答:sizeof 在 编译阶段处理,作用为取得一个对象(数据类型或数据对象)的长度(即占用内存的大小,以1字节为单位)。

①指针可以看做变量的一种,32位操作系统sizeof 指针都为4,例子:

 int  *p; 
sizeof(p) =4;
sizeof(*p) = sizeof(int )=4;

②对于静态数组,sizeof可以直接计算数组大小,例:

int a[10];
char b[ ]= “hello”;
Sizeof (a) = 4 * 10 =40;
Sizeof (b) = 6;     (求字符串长度时要加上字符串结束符/0)

③数组作为函数形参时候,数组名当做指针 使用,例:

Void fun (char p[ ])
{
    Sizeof (p) ;  //结果为4
}

sizeof 与 strlen 的区别:

sizeof 是操作符, strlen为函数;
sizeof 可以用数据类型作为参数,如int char;
strlen只能使用 char*做参数且以\0为结尾
sizeof 为数组时候,不退化, 传递给strlen时数组会被退化成指针;

3. 结构体

1.结构体的赋值方式:

①初始化:如:

struct  st{
       char a;
       int  b;
}x={'A', 1};

②定义变量后按字段赋值,如:

struct st{
   char a;
   int  b;
};
struct st  x;
x.a  = 'A';
x.b  =  1;

③结构体变量的赋值,如:

 struct st {
      char a;
      int  b;
};  

struct st x,y;
x.a= 'A';
x.b=1;
y=x;

2.结构体位域

位域类型: char、 short、int 可带上(signed或者unsigned)
位域的定义:

struct st{
       Unsigned char a:7;    //字段a占用一个字节的 7bit(位)
       Unsigned char b:2;   //字段b占用一个字节的 2ibt(位)
       Unsigned char c:7;                  
}s1;

位域的好处:

①并不需要完整的字节,节省存储空间,处理简单;
②方便利用位域把一个变量按位域分解;
但是不利于程序的移植!!

3.计算一个结构体大小,sizeof(struct s1)

①结构体偏移量的概念:
结构体中的偏移量指的是一个成员的实际地址和结构体首地址之间的距离。

②结构体大小的计算方法:
结构体会涉及到字节对齐,(目的是为了让计算机快速读取,用空间交换速度),即最后一个成员的大小+最后一个成员的偏移量+末尾的填充节数。

③结构体内偏移规则
第一步:每个成员的偏移量都必须是当前成员所占内存大小的整数倍,如果不是整数倍,编译器会在前填充字节。
第二步:当所有成员大小计算完毕后,编译器会对当前结构体大小是否是最宽的结构体成员的大小的整数倍,如果不是整数倍,编译器会填充字节。

例:

Struct s1{      成员的大小       成员的偏移量

     int   a;           4                0          
     char  b;           1                4
     int   c;          4                 5+3(填充字节)
     long  d;           8                12+4(填充字节)
     char  e;           1                24
}

sizeof(s1)=24+1+7(填充字节)

4.结构体成员数组大小

struct st {
     int a;
     int b;
     char c[0];
}st_t;  

sizeof(st_t) = 8

struct book {
       char name[5];
       float price;
}book[2];

sizeof( book ) = 12;
sizeof (book[2]) = 24;

C语言面试必问的经典问题(纯”gan“货)_第1张图片

第一步:结构体成员占字节数族大的元素为 sizeof ( float ) = 4;则用4 来分配其他成员;
第二步:数组超过四个字节,换行继续,剩下是哪个字节不够float型数据使用,则换行。

如果还不清楚,看下面的详细案例:

int : 4个字节
float: 4个字节
char: 1个字节
double:8个字节

在实际生活中,保存的数据一般不会是同一一种类型,所以引入了结构体。而结构体的大小也不是成员类型大小的简单相加。需要考虑到系统在存储结构体变量时的地址对齐问题。

由于存储变量地址对齐的问题,结构体大小计算必须满足两条原则

一. 结构体成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
二. 结构体大小必须是所有成员大小的整数倍(数组 和 结构体 除外)。

三. 对齐方式很浪费空间 可是按照计算机对内存的的访问规则这样的对齐却提高了效率

练习一:

struct s1{ 
     char ch1;    //char 1字节
     char ch2;    //char 1字节
     int i;       //int  4字节 
};

遵循结构体运算法制第一条,偏移量必须是当前成员变量的大小整数倍,逻辑 偏移2 实际按照对其的规则,要偏移4

这个结构体的大小容易计算,满足两个原则即可,为8

练习二:

struct s2{ 
     char ch1;    //char 1字节      【】 +【】【】【】     【】【】【】【】
     int i;       //int  4字节      //4+3
     char ch2;    //char 1字节      //1    8+1=9? 因为第2个原则 所以变成12;
};

这个结构体大小是12,为社么呢?仔细看看两个原则,要满足偏移量是成员的整数倍,ch1偏移量是0 ,i的偏移量不可能是1,因为1不是i大小4的倍数,所以i的偏移量是4,ch2的偏移量就变成了8,所以为了满足结构体大小是成员大小整数倍,就是12;

练习三:成员包含数组的结构体

struct s3{
      char ch;       //char  1  【】 +【】【】【】         【】【】【】【】
      int i;         //4+3
      char str[10];  //  10     10+8=18     因为第2个原则 所以变成20;
};

这个结构体的大小是20,先看前两个成员,大小是8,毋庸置疑,这个char类型的数组,只需要把它看做成十个char连在一起即可,加起来就是18,在满足结构体大小为成员整数倍,所以大小就是20;

练习四:成员包含结构体的结构体

struct s4{  
      char ch;   //1
      int i;        //4+3
      struct s{
              char ch1;   //1
              int j;      //4+3
           };
float f;    //4    8+8+4=20?   是8+4=12
//因为内嵌的结构体它只是声明,不占空间。所以是不算它的大小所以是8+4=12
};

这个内嵌的结构体的大小是8,那么是否结构体大小就要向8对齐?

这个结构体大小是12,为什么看这是20,确是12呢,因为内嵌的结构体它只是声明,不占空间。只是作为一个代码,所以是不算它的大小。

若代码改为

struct s4{
      char ch;      //1
      int i;        //4+3
      struct s{
             char ch1;   //1
             int j;      //4+3
          }stmp;
float f;                 //4        8+8+4=20
};

这个内嵌的结构体的大小是8,那么是否结构体大小就要向8对齐?

这个结构体的大小是20.很明显不是8的倍数。所以计算结构体大小时是把里面这个结构体就看做是一个char 和一个int,不是看做一个整体.

练习五:成员包含联合体的结构体

struct s5{         //4+3+1=8
        char ch;   //1
        int i;       //4+3
        union{          //联合体按最大成员的字节数算    所以是4
            char ch1;   //1
            int j;      //4
        };
};                                     

联合体大小就是成员中最大类型的大小,8+4=12,所以这个结构体大小是12

练习六:指定对齐值

(1)对齐值小于最大类型成员值
//当成员的大小超过了pack要求指定大小 就按pack要求的对齐
#pragma pack(4) //指定向4对齐 最大是8 超过了指定大小

struct s6 {
       char ch;      //1
       int i;        //4+3
       float f;      //4
       double d;     //8    8+4+8=20 --24     由于是指定向4对齐  所以是20
};

(2)对齐值大于最大类型成员值
//当成员的大小没有超过pack要求指定大小 结构体的总大小就按成员最大的对齐
#pragma pack(10) //指定向4对齐 最大是8 没有超过了指定大小

struct s7 {
       char ch;      //1
       int i;        //4+3
       float f;      //4
       double d;     //8        8+4+8=20 --24    所以是24
};

我们指定的对齐值是10 最大为8, 是否就向10对齐? 不是 当对齐值大于最大类*型成员值时 向自身对齐值对齐 大小为24

总体来说,向指定对齐值和自身对齐值中较小的那个值对齐

4.基本C问题

1.数据类型有哪些,分别占多少字节
C语言面试必问的经典问题(纯”gan“货)_第2张图片

int 占 4个字节 short 占 2个字节 long 占 4个字节
char 占 1个字节
float 占4个字节
double 占8个字节

2. 1Mb等于多少kb?

1个元器件称为1比特(Bit)或1位8个元器件称为1字节(Byte) 8 bit = 1B
B(字节),英文字符是1个字节,也就是1B,1个汉字为2个字符,也就是2B
1024字节为 1KB1KB=1024B
M 是兆,以1024进一位, 也就是说 1024KB=1MB
1024 MB = 1 GB

3. 指针占几个字节,为什么?

一个指针在32位的计算机上,占4个字节; 一个指针在64位的计算机上,占8个字节。

指针就是地址,地址就是指针,而地址是内存单元的编号。所以,一个指针占几个字节,等于是一个地址的内存单元编号有多长
我们一般需要32个0或1的组合就可以找到内存中所有的地址,而32个0或1的组合,就是32个位,也就是4个字节的大小,因此,我们只需要4个字节就可以找到所有的数据。所以,在32位的计算机中,指针占4个字节

1.引用与指针的区别:

①引用必须初始化,指针不必初始化
②引用初始化后不能改变,但是指针可以改变所指的对象
③不存在空值的引用,但是存在空值的指针

2.写一个strcpy()函数

//表示把src所指向的字符串复制到dest。
void mystrcpy(char *dest , char *src){
if(str == NULL  ||  dest ==NULL){
   return NULL;
} 
//字符串结尾‘\0’
while(*str != '0'){
  *dest = *str;
  dest++;
  str++;
  *dest ='\0';
  }
}

2…h头文件中, ifndef /define /endif的作用

①防止头文件被重复调用

3.include和includ”file.h”的区别?

①前者从编译器自带的库函数中寻找文件,从标准库路径开始搜索文件
②后者是从自定义的文件中寻找文件,寻找不到再到库函数中寻找文件

4.为一个绝对地址为0x8877的整形变量赋值为0x7788

int *a;
a = (int*)0x8877;
*a = 0x7788;

4.全局变量和局部变量的区别?区别是什么?

全局变量存储在静态数据区占用静态的存储单元 ,如果没加static
关键字的话,它是使用在我们整个项目工程里的,如果加了static的话,它就只能在这个本本文件.c里面。

局部变量存储在栈中只有在函数被调用过程中才开始分配存储单元

5.堆栈溢出的原因有哪些?

函数调用层次太深,函数递归调用时,系统要在栈中不断保存函数调用时的线程和产生的变量,递归调用太深,会造成栈溢出,这是递归无法返还。
动态申请空间使用后没有释放。由于C语言中没有垃圾资源自动回收机制,因此需要程序员主动释放已经不再使用的动态地址空间。
数组访问越界,C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,运行过程中可能会存在内存访问错误。
指针非法访问,指针保存了一个非法地址,通过这样的指针访问所指向的地址时会产生内存访问错误。

6.队列和栈的区别

相同点: 栈和队列是限定插入和删除只能在表的“端点”进行的线性表
不同点: 栈 元素必须 “先进后出”, 队列 元素必须 “先进先出”

7.局部变量能否与全局变量重名?

能,
局部变量 屏蔽 全局变量就是我们在一个函数体,如果有一个变量(局部变量),它在我们全局变量也同时被定义了,他所赋的值就局部变量赋的值,局部变量里面就是以局部变量为优先级最高。
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。

8.如何引用一个已经定义了的全局变量?

①用extern 关键字方式
②用引用头文件方式,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。

9.全局变量可不可以定义在可被多个.c文件包含的头文件中,为啥?

可以,在不同的C文件中各自用static声明的全局变量,变量名可能相同,但是各自C文件中的全局变量的作用域为该文件,所以互不干扰。

10.Do…while 和while …do的区别?

①do …while :循环一次再进行判断
②while …do : 先判断再进行循环

11.static 关键字在 全局变量、局部变量、函数的区别?

①全局变量+static:改变作用域,改变(限制)其使用范围。 只初始化一次,防止在其他文件单元中被引用。
全局变量的作用域是整个源程序,在各个源文件中都是有效的,而加了静态后的全局变量的作用域 仅限于一个源文件中。
②局部变量+static:改变看它的存储方式,也就是改变了它的生存期。
③普通函数+static :作用域不同,仅在本文件。
只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义,对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。

12.程序的内存分配(常考)

前言:c语言程序.c文件经过编译连接后形成编译、链接后形成的二进制映像文件由堆,、栈、数据段(只读数据段,未初始化数据段BSS,已初始化数据段三部分)、代码段组成。

①栈区 (stack):由编译器进行管理,自动分配和释放,存放的是 函数调用过程中的各种参数,局部变量,返回值以及函数返回地址。
②堆区(heap) :用于程序 动态申请分配和释放空间,malloc和free,程序员申请的空间在使用结束后应该释放,则程序自动收回。
全局(静态)存储区:分为 DATA(已经初始化),BSS(未初始化)段,DATA段存放的是全局变量和静态变量;
BSS(未初始化)存放未初始化的全局变量和静态变量。 程序运行结束后自动释放,其中BSS(全部未初始化区)会被系统自动清零。
④文字常量区 :存放常量字符串,程序结束后由系统释放。
⑤程序代码段:存放程序的二进制代码。

5.程序的内存分配

程序的内存分配(常考)

代码在内存种从高地址—>低地址分区栈区,堆区,全局区,常量区,代码区

  • 栈区:函数的参数局部变量和它的返回值的是存在栈区的,栈区的内容都是由系统自动分配,由系统自动释放的。栈区的好处是,执行效率高,速度快
  • 堆区:它是由程序员去开辟和释放的,由堆申请的一块内存属于动态内存,好处是申请比较灵活,而且使用非常方便。一般用malloc 或者 new
    去开辟一段空间。存在堆区,在使用完这个区域后必须要用free或者delete去释放这个区域。不然就会造成内存泄漏,造成我们堆区越用越少,直到最后整个程序崩溃。
    还有就是频繁的申请不同大小的堆空间,也会造成我们的内存碎片越来越多。
  • 全局区: 全局区的内存是我们程序被编译的时候就已经分配了,同时也是在我们程序运行前就已经分配好了,它在我们整个运行期间都是存在的,在全局区里,很多内容基本上是我们全局变量和static修饰的变量,(全局区又称为静态存储区,分为初始化后的变量存储区和未初始化的变量存储区。)
  • 常量区: 数字常量和字符常量所有常量都存在常量区里
  • 代码区: 写代码的时候,必不可免的遇到很多函数,所有这些函数的二进制代码都会存在我们的代码区。
int a = 0;    //全局初始化区
char *p1;     //全局未初始化区
int main()    //代码区
{
   int b;     //栈区(局部变量)
   char s[]="abc";  //s数组在栈区   'abc\0'在常量区
   char *p2;  //栈区(局部变量)
   char *p3 = "123456";  //指针p3在栈区(局部变量)   '123456\0'在常量区
   static int c = 0;    //全局(静态)初始化区
   p1 = (char *)malloc(10);  //分配的10字节区域在堆区   p1在全局区
   p2 = (char *)malloc(20);  //分配的20字节区域在堆区   p2在栈区
   strcpy(p1,"123456");  //'123456\0'在常量区  系统可能会把它和p3的字符放一起
   free(p1);
   free(p2);
} 

13.解释”堆”和”栈”的区别:

注:被问到这个问题的时候可以从这几个方面进行阐述
①申请方式②申请后的系统反应③申请内存的大小限制④申请效率⑤存储内容⑥分配方式

①申请方式:
Strack(栈): 由编译器自带分配释放,存放函数的参数值,局部变量等。
Heap(堆):程序员自己申请,并指名大小–>malloc函数。
②申请后的系统响应
Strack(栈):只要栈剩余空间>所申请空间,都会提供。
Heap(堆):操作系统有记录空闲内存的链表:收到申请->遍历链表->寻找->申请空间的堆结点
③申请内存的大小限制
Strack(栈):向低地址扩展的数据结果,连续内存区域,栈 获得的空间较小。
Heap(堆):向高地址扩展的,不连续内存区域;链表遍历方向为(低地址->高地址)。 栈获得空间灵活,空间也大。
④申请效率
Strack(栈):系统自由分配,速度快。 Heap(堆):速度慢,容易产生内存碎片。
⑤存储内容 Strack(栈)
第一进栈 :主函数中的下一条指令的地址
–>函数的各个参数,参数由右往左进栈。–>函数的局部变量(静态变量不入栈)。调用结束后,顺序相反,局部变量先出栈。 Heap(堆): 程序员自己安排
⑥分配方式 Strack(栈):
栈有两种分配方式,静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配,动态分配由alloca函数进行分配,但栈的动态分配和堆是不同的,栈的动态内存由编译器进行释放,无需手工实现。Heap(堆):堆都是动态分配的,没有静态分配的堆。

14.结构体和联合体的区别:

①结构体和联合体:
都是由不同的数据类型成员组成,但是在同一时刻,联合体只存放了一个被选中的成员(所有成员公用一块地址);而结构体成员都存在(不同成员存放地址不同)。
例子:

union abc{
     int i;
     char m;
};

*在联合体abc中,整形量i和字符m共用一块内存位置。
*当一个联合体被说明时,编译程序自动产生一个变量,其长度为联合体中最大的变量长度

②联合体不同成员赋值,会对其他成员重写,原来成员的值会不存在。
结构体的不同成员赋值是互不影响的。

你可能感兴趣的:(C语言,c语言,面试题,c基础)