C语言常见面试题汇总

C语言常见面试题汇总为变量分配地址和存储空间的称为定义 不分配地址的称为声明

大家好,欢迎来到IT知识分享网。

题1:变量的声明和定义有什么区别

题2:写出bool 、int、 float、指针变量与“零值”比较的if语句

注意:应特别注意在int、指针型变量和“零值”比较的时候,把“零值”放在左边,这样当把“==”误写成“=”时,编译器可以报错,否则这种逻辑错误不容易发现,并且可能导致很严重的后果。

题3:sizeof和strlen的区别

题4: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释放,不要混用。因为两者实现的机理不同。

题5:说明关键字volatile有什么含意

volatile的作用:

作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

一个定义为volatile的变量就是说这个变量可能会被意想不到的改变,这样,编译器就不会去随便假设这个变量的值了。精确的说,优化器在用到这个变量的值的时候,必须每次都小心的重新读取这个变量的值,而不是使用保存在寄存器里面的备份。

下面是volatile变量的几个例子:

1). 并行设备的硬件寄存器(如:状态寄存器)

2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3). 多线程应用中被几个任务共享的变量

题6:写一个“标准”宏MIN

题7:设置地址为0x67a9的整型变量的值为0xaa66

*(int *)0x67a9 = 0xaa66;

题8:链表和数组有什么区别

题9:用变量a定义

题10:引用和指针的区别

题11:static的用法(定义和用途)

1)用static修饰局部变量:使其变为静态存储方式(静态数据区),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。

2)用static修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。

3)用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。

题12:const的用法(定义和用途)

const主要用来修饰变量、函数形参和类成员函数:

1)用const修饰常量:定义时就初始化,以后不能更改。

2)用const修饰形参:func(const int a){};该形参在函数里不能改变

3)用const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。

被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

const int a; int const a; const int *a; //等于 int const *a; int * const a; int const * const a; //等于 const int* const a; 
*为分界点,const*的左边时,实际物体值不变 当const*的右边时,指针不变,, 左物右指 这样来记比较方便!! 

题13:内存四区

#include<stdio.h> #include<malloc.h> int a = 0; //全局初始化区 .data段 char *p1; //全局未初始化区 .bss段 void main() { 
    int b;// 栈  char s[] = "abc"; //s在栈上,大小4字节。 "abc\0"为s的初始值,可以不用纠结其存在哪里 char *p2; //栈  char *p3 = ""; //"\0"在常量区,p3在栈上。  static int c =0; //全局(静态)初始化区 .data段 p1 = (char *)malloc(10); p2 = (char *)malloc(20); //分配得来的10和20字节的区域就在堆区。  strcpy(p1, ""); //"\0"放在常量区,编译器可能会将它与p3所指向的""优化成一个地方。  } 

https://blog.csdn.net/_/article/details/

题14:大小端问题

大端:一个多字节整数,数字的高位部分存放在内存的低地址单元。低地址存高字节
小端:一个多字节整数,数字的低位部分存放在内存的低地址单元。低地址存低字节
大小端格式问题。
方法一:


 void checkCpuMode(void) { 
    int i = 0x; char *cp = (char *)&i; if(*cp == 0x78) printf("little endian"); else printf("big endian\n"); } 

方法二:

 void checkCpuMode(void) { 
    int a = 0x; if((char)a == 0x12) printf("big endian\n"); else printf("little endian\n"); } 

方法三:

void checkCpuMode(void) { 
    union { 
    short s; char c[sizeof(short)]; }un; un.s=0x0102; if(un.[0]==1&&un.c[1]==2) printf("big endian\n"); else printf("little endian\n"); } 

题15:静态局部变量在什么时候分配内存

题16:什么是不可重入函数和可重入函数

题17:#define宏定义与typedef的区别

#define宏定义是字符替换,typedef是定义类型,是声明一种新的类型,等同自带的基本类型。

#define是宏,处理的时候位于编译前阶段,宏处理器基本上对你的C/C++程序不会有任何的感知。它只处理宏的语法。而编译阶段的“程序”得到的是宏处理完的结果。

typedef是编译阶段的一部分。它的意义是单一的。

宏定义只是简单的字符串代换,是在预处理完成的,而typedef是在编译时处理的,它不是作简单的代换,而是对类型说明符进行重新命令。被命名的标识符具有类型定义说明的功能。

#include<stdio.h> #define PIN1 char* typedef char* PIN2; void main() { 
    //使用#define相当于:char* x, y;就是说x是char指针类型,而y是char类型,如果要连续定义两个指针需要写成char *x, *y; PIN1 x, y; //使用typedef相当于:char* x, char* y;就是说x是char指针类型,而y也是char指针类型 PIN2 a, b; printf("By #define :%d %d\n\n",sizeof(x), sizeof(y)); printf("By typedef :%d %d\n\n", sizeof(a), sizeof(b)); //system("pause"); return 0; } 

在这里插入图片描述

值传递改变值这类问题

下面的代码有什么问题?

void GetMem(char *p) { 
    p = (char*)malloc(100); } void main() { 
    char *str = NULL; GetMem(str); strcpy(str, "hello word!"); printf(str); } 

分析:

程序崩溃。在上面已经分析过了,传递给GetMem函数形参的只是一个副本,修改形参p的地址对实参str丝毫没有影响。所以str还是那个str,仍为NULL,这时将字符串常量拷贝到一个空地址,必然引发程序崩溃。下面的方法可以解决这个问题:

void GetMem(char **p) { 
    *p = (char*)malloc(100); } void main() { 
    char *str = NULL; GetMem(&str); strcpy(str, "hello word!"); printf(str); free(str); //不free会引起内存泄漏 } 

看似有点晦涩,其实很好理解。本质上是让指针变量str指向新malloc内存的首地址,也就是把该首地址赋值给指针变量str。前面我们说过,指针传递本质上也是值传递,要想在子函数修改str的值,必须要传递指向str的指针(str本身就是指针,指针的指针是二级指针),因此子函数要传递的是str的地址,这样通过指针方式修改str的值,将malloc的内存首地址赋值给str。

计算sizeof的值

char str1[] = { 
   'a', 'b', 'c', 'd', 'e'}; char str2[] = "abcde"; char *ptr = "abcde"; char book[][80]={ 
   "计算机应用基础","C语言","C++程序设计","数据结构"}; 

sizeof(str1)=?

sizeof(str2)=?

sizeof(ptr)=?

sizeof(book)=?

sizeof(book[0])=?

分析:

sizeof(str1)=5,就是5*sizeof(char)=5;

sizeof(str2)=6,字符串都是以’0’结尾,所以所占字节数为6;

sizeof(ptr)=4,ptr是一个指针,在32位平台上大小为4字节;

sizeof(book)=320,book是一个二维数组,4*80*1

sizeof(book[0])=80,book[0]是第一维数组,因为此80*1

根据sizeof求数组元素的个数也很简单,拿第一个来说,就是sizeof(str1)/sizeof(char)。

计算strlen的值

char arryA[] = { 
   'a','b','c',0,'d','e'}; char arryB[] = { 
   'a','b','c','d','e'}; char arryC[6] = { 
   'a','b','c','d','e'}; char *str = "abcde"; 

分析:

strlen(arryA) = 3,strlen遇到’\0’就会返回,无论后面有多少个字符;

strlen(arryB)长度无法确定,没有人为写入‘\0’,strlen会继续计算直到找到结束符,结果未知;

strlen(arryC)=5,指定了数组大小,编译器会自动在空余地方添加’\0’,这其实跟char arryC[6] = {‘a’,‘b’,‘c’,‘d’,‘e’,‘\0’};等价。

strlen(str) = 5,不包括结尾的’\0’。

char str[]和char *str

1)下面的操作合法么?出错的话,会是在那个阶段?编译时期还是运行时期?

char str[] = "hello"; str[0] = 's'; //合法么 char *str = "hello"; str[0] = 's'; //合法么 

分析:

这两个都可以成功编译,只是第二个会在运行时期出现段错误。下面来分析一下:

首先”hello”是一个字符串常量,存储在静态数据区域(data段),这是在编译时期就确定的。第一个是将字符串常量赋值给了一个变量(全局变量在数据段,局部变量在栈区),实际上是将字符串常量拷贝到了变量内存中,因此修改的只是str[]这个变量的值。

第二个是将字符串常量的首地址赋值给指针str,对指针str操作就是对字符串常量进行修改!因此出现了段错误。

(2)理解了上面的知识,判断一下下面的true or false?

char str1[] = "abc"; char str2[] = "abc"; const char str3[] = "abc"; const char str4[] = "abc"; const char *str5 = "abc"; const char *str6 = "abc"; char *str7 = "abc"; char *str8 = "abc"; cout << ( str1 == str2 ) << endl; cout << ( str3 == str4 ) << endl; cout << ( str5 == str6 ) << endl; cout << ( str7 == str8 ) << endl; 

分析:

结果是: 0 0 1 1

先理解str1,str2,str3,str4,他们是什么?他们是数组名,也就是数组首元素的地址!”str1 == str2“本质就是比较两个数组的地址是不是相同。上面我们说过,编译器给他们分配了新的存储空间来对字符串”abc”进行拷贝,这些变量在内存里是相互独立的,因此他们的地址肯定不同!

再理解str5,str6,str7,str8,他们是什么?他们是指针,他们的值就是字符串常量的地址!它们都指向“abc”所在的静态数据区,所以他们都相等。

数组作为函数参数传递

看看下面的函数有啥问题:

int func(int a[]) { 
    int n = sizeof(a)/sizeof(int); for(int i=0;i<n;i++)    { 
    printf("%d ",a[i]); a[i]++; } } 

sizeof(a)/sizeof(int)一般是计算数组的元素个数,结果却发现n的值总是1,为什么会这样呢?这是因为在C中,将数组传递给一个函数时,无法按值传递,而是会自动退化为指针。下面的三种写法其实是等价的:

“int func(int a[20]);” 等价于 “int func(int a[]);” 等价于 “int func(int *a);”。

数组指针和指针数组的问题

(1)说出下面表达式的含义?

int *p1[10]; int (*p2)[10]; 

第一个是指针数组,首先他是一个数组,数组的元素都是指针。

第二个是数组指针,首先他是一个指针,它指向一个数组。

下面这张图可以很清楚的说明:

在这里插入图片描述
(2)写出下面程序运行的结果

int a[5] = { 
    1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf("%d,%d", *(a + 1), *(ptr - 1)); 

分析:

答案是2,5。本题的关键是理解指针运算,”+1“就是偏移量的问题:一个类型为T的指针移动,是以sizeof(T)为单位移动的。

a+1:在数组首元素地址的基础上,偏移一个sizeof(a[0])单位。因此a+1就代表数组第1个元素,为2;

&a+1:在数组首元素的基础上,偏移一个sizeof(a)单位,&a其实就是一个数组指针,类型为int()[5]。因此&a+1实际上是偏移了5个元素的长度,也就是a+5;再看ptr是int类型,因此”ptr-1″就是减去sizeof(int*),即为a[4]=5;

a是数组首地址,也就是a[0]的地址,a+1是数组下一个元素的地址,即a[1]; &a是对象的首地址,&a+1是下一个对象的地址,即a[5]。

*p++、 (*p)++、 *++p、 ++*p

int a[5]={ 
   1, 2, 3, 4, 5}; int *p = a; *p++ 先取指针p指向的值(数组第一个元素1),再将指针p自增1; cout << *p++; // 结果为 1 cout <<(*p++); // 1 (*p)++ 先取指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2 cout << (*p)++; // 1 cout <<((*p)++) // 2 *++p 先将指针p自增1(此时指向数组第二个元素),* 操作再取出该值 cout << *++p; // 2 cout <<(*++p) // 2 ++*p 先取指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2) cout <<++*p; // 2  cout <<(++*p) // 2 

const 二级指针

给定声明 const char * const *pp;下列操作或说明正确的是?

(A)pp++  (B)(*pp)++  (C)(pp)=c;  (D)以上都不对

分析:

答案是A。

(2)const char *p : p为一个指向char类型的指针,const只限定p指向的对象为只读。这样,p=&a或 p++等操作都是合法的,但如*p=4这样的操作就错了, 因为企图改写这个已经被限定为只读属性的对象。

(3)char *const p : 限定此指针为只读,这样p=&a或 p++等操作都是不合法的。而*p=3这样的操作合法,因为并没有限定其最终对象为只读。

(4)const char *const p :两者皆限定为只读,不能改写。

(2)const char * const *p :限定最终对象和 p指向的指针为只读。这样 *p=?的操作也是错的,但是p++这种是合法的。

(3)const char * const * const p :全部限定为只读,都不可以改写

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/114406.html

(0)
上一篇 2025-12-09 07:10
下一篇 2025-12-09 07:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信