全网最全面最精简C/C++高频面试点(C语言版)

引言

本篇结合多段面试经历,在参考大量优质博客基础上,言简意赅地总结出如下C/C++高频面试点

目录

  • 内存分布
  • switch
  • do{…}while(0)
  • 枚举
  • 结构体与共用体
  • 全局变量与局部变量
  • 数据类型
  • 类型转换
  • 指针数组与数组指针
  • 指针函数与函数指针
  • 野指针与空指针
  • 内存泄漏与内存溢出
  • malloc、calloc与realloc
  • sizeof与strlen

1. 内存分布

名称内容
代码段可执行代码、字符串常量
数据段已初始化全局变量、已初始化全局静态变量、局部静态变量、常量数据
BSS段未初始化全局变量,未初始化全局静态变量
栈段局部变量、函数参数
堆段动态内存分配

注:栈段亦由操作系统分配和管理,而不需要程序员显示地管理;堆段由程序员自己管理,即显示地申请和释放空间

选项
存储内容局部变量变量
作用域函数作用域、语句块作用域函数作用域、语句块作用域
编译期间大小是否确定
大小1MB4GB
内存分配方式地址由高向低减少地址由低向高增加
内容是否可以修改
选项数据段/BSS段代码段
存储内容全局变量、静态变量常量
编译期间大小是否确定
内容是否可以修改

2. switch

switch语句允许测试变量与值列表的相等性,每个值称之为案例或者case,程序会检查switch后面的值并且与case后面的值比对,如果相等则执行后面的代码或代码块
switch执行流程

  1. 当switch后面的变量值和case后面的常量值匹配相等后,case后面的代码将会被执行,直到break语句被执行后跳出switch代码块
  2. break不是必须的,如果没有break,则执行完当前case的代码块后会继续执行后面case代码块的内容,直到执行break才可以退出
  3. switch有一个默认的情况,我们用default关键词表示,当switch后面的变量和所有case后面的常量都不匹配的情况下,默认执行default后面的语句

注意:

  1. switch语句中使用的表达式必须具是int或enum类型,否则如float等其他数据类型是无法通过的编译的,因为编译器需要switch后面的语句和case后面的值精确匹配,而计算机无法精确表达一个float数据类型
  2. switch可以任意个case语句(包括没有), 值和语句之间使用:分隔
  3. case后面的值必须是int常量值,或者返回结果为int类型的表达式

3. do{…}while(0)

在实际开发过程中,循环更多采用for和while,而do{…}while()主要有以下作用:

#define是在预处理的时候进行直接替换,缺少相应的语法检查机制,如下:

#define LOG {print();send();};
void print()
{
	cout<<"print: "<<endl;
}
void send()
{
	cout <<"send: "<<endl;
}
int main(){
	if (false)
		LOG
	cout <<"hello world"<<endl;
	system("pause");
	return 0;

if经过预处理替换会变成:

	if (false)
	{
		print();
		send();
	};
	else
	{
		cout <<"hello"<<endl;
	}

因为if语句后面多加了个;而编译不通过
用do{…}while(0);可以包裹住要操作的#define,无论外面如何操作,都不会影响#define的操作:

 #define LOG do{print();send();}while (0);
 int main(){
		if (false)
		LOG
	else
	{
		cout <<"hello"<<endl;
	}
 	cout <<"hello world"<<endl;
	system("pause");
	return 0;
}

if则会变成:

	if (false)
		do{
			print();
			send();
		}while (0);
	else
	{
		cout <<"hello"<<endl;
	}
 	cout <<"hello world"<<endl;

编译通过

4. 枚举

枚举类型是C语言和C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合,所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内
注意:

  1. 第一个名称的值为 0
  2. 默认情况下,每个名称都会比它前面一个名称大 1
enum color1
 { 
 		red, 
 		green, 
 		blue 
 } c1;
c1 = blue;  //red的值为 0,green的值为 1,blue 的值为 2,c1的值为2

enum color2
 { 
 		red, 
 		green=5,
 		blue 
 };c2
 c2 = blue; //red的值为 0,green的值为 5,blue 的值为 6,c2的值为6

5. 结构体与共用体

定义:

  1. 结构体是C/ C++ 中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项
  2. 共用体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型,可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值,共用体提供了一种使用相同的内存位置的有效方式

区别:

  1. 结构体占用的内存是所有的成员各自占用的内存空间之和
  2. 共用体占用的内存则不同,等于占用内存空间最大的那个成员

注:共用体是共用内存空间,所以每个成员都是读写同一个内存空间,那么内存空间里面的内容不停的被覆盖,而同一时刻,都只能操作一个成员变量

union Data{
   int i;
   float f;
   char  str[20];
};
int main( ){
   union Data data;       
   data.i = 10;
   data.f = 220.5;
   strcpy( data.str, "C Programming");
   printf( "data.i : %d\n", data.i);
   printf( "data.f : %f\n", data.f);
   printf( "data.str : %s\n", data.str);
   return 0;
}

结果如下:

data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming

我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因

6. 全局变量与局部变量

定义:

  1. 在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用
  2. 在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的

初始化:

  1. 定义局部变量时,系统不会对其初始化,必须自行对其初始化
  2. 定义全局变量时,系统会自动初始化为下列值

int — 0 char — ‘\0’ float — 0 double — 0 pointer — NULL

注意:局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值

7. 数据类型

32位

类型字节大小
char1
short2
int4
long4
float4
long long8
double8

指针类型:均为4字节

64位

类型字节大小
char1
short2
int4
long4
float4
long long8
double8

指针类型:均为8字节

8. 类型转换

类型转换图

// 普通转换
int sum = 7; 
double mean = sum / 5; //mean为 1.0
// 强制转换
int sum = 17;
double mean = (double) sum/5//mean为1.4

9. 指针数组与数组指针

定义:
指针数组:指针的数组,是一个数组,只不过数组的元素存储的是指针变量
数组指针:数组的指针,是一个指针变量,指向了一个一维数组
注意:在定义的时候,符号优先级:()>[]>*

//指针数组
char *arr[4] = {"hello", "world", "shannxi", "xian"};
//arr就是指针数组,它有四个元素,每个元素是一个char *类型的指针,这些指针存放着其对应字符串的首地址。

//数组指针
char (*pa)[4];
//pa就是指向数组的指针

实际代码开发使用示例:

指针数组
指针数组的排序非常有趣,因为这个数组中存放的是指针,通过比较指针指向的空间的大小,排序这些空间的地址。函数实现如下

void sort(char **pa, int n)//冒泡排序
{
   int i, j;
   char *tmp = NULL;

   for(i = 0; i < n-1; i++){
       for(j = 0; j < n-1-i; j++){
           if((strcmp(*pa+j), *(pa+j+1)) > 0){
               tmp = *(pa + j);
               *(pa + j) = *(pa + j + 1);
               *(pa + j + 1) = tmp;
           }
       }
   }
}

在函数中定义指针数组,并且打印结果如下:

char *pa[4] = {"abc", "xyz", "opq", "xyz"};
[root@menwen-linux test]# ./test 
abc
ijk
opq
xyz

数组指针
数组指针既然是一个指针,那么就是用来接收地址,在传参时就接收数组的地址,所以数组指针对应的是二维数组

void fun(int (*P)[4]);//子函数中的形参,指针数组 
a[3][4] = {0};//主函数中定义的二维数组
fun(a);//主函数调用子函数的实参,是二维数组的首元素首地址

10. 指针函数与函数指针

定义:
指针函数:指针的函数,其本质是一个函数,函数的返回值是一个指针
函数指针:函数的指针,其本质是一个指针变量,该指针指向这个函数

// 指针函数
int *fun(int x,int y);//其返回值是一个 int 类型的指针,是一个地址

//函数指针
int (*fun)(int x,int y);//该指针指向这个函数
//函数指针是需要把一个函数的地址赋值给它,有两种写法:
fun = &Function;
fun = Function;

注意:回调函数是函数指针使用示例

11. 野指针与空指针

定义:

  1. 野指针的值并不为null,野指针会指向一段实际的内存,只是它指向哪里我们并不知情,或者是它所指向的内存空间已经被释放
  2. 空指针是指一个指针的值为null

造成野指针原因:

  1. 指针变量的值未被初始化
void func(){
    int *ptr;    // 野指针
}
  1. 指针所指向的地址空间已经被free或delete
void func(){
    char *p = (char *)malloc(sizeof(char)*100); 
    free(p);//p所指向的内存被释放,但是p指针还会继续指向这段堆上已经被释放的内存
}
  1. 指针操作超越了作用域
void func(){
    int *ptr = nullptr;
    {
        int a = 10;
        ptr = &a;
    } // a的作用域到此结束
     int b = *ptr;    // ptr指向a,a已经被回收,ptr野指针
}

解决办法

  1. 初始化置NULL
void func(){
    int *ptr = NULL;    // 野指针
}
  1. 申请内存后判空(malloc申请内存后需要判空,而在现行C++标准中,如C++11,使用new申请内存后不用判空,因为发生错误将抛出异常)
void func(){
   char *p = (char *)malloc(sizeof(char)*100); 
   assert(p != NULL); //判空,防错设计
   free(p);//p所指向的内存被释放,但是p指针还会继续指向这段堆上已经被释放的内存
}

3.指针释放后置NULL

void func(){
   char *p = (char *)malloc(sizeof(char)*100); 
   assert(p != NULL); //判空,防错设计
   free(p);//p所指向的内存被释放,但是p指针还会继续指向这段堆上已经被释放的内存
   p = NULL; //释放后置空
}

12. 内存泄漏与内存溢出

定义:
内存泄漏:指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
内存溢出:指程序申请内存时,没有足够的内存供申请者使用

堆栈溢出

原因解决办法
开了数据非常大的局部数据结构,比如数组大的数组尽量不要定义在函数内部
过多的递归调用,使用了大量的空间递归注意深度
有死循环,不断的往堆栈中写入数据不要造成函数死循环

13. malloc、calloc与realloc

相同点:三者都是分配内存
不同点:

  1. malloc函数:原型void *malloc(unsigned int num_bytes),num_byte为要申请的空间大小,需要我们手动的去计算
  2. calloc函数:原型void *calloc(size_t n, size_t size),其比malloc函数多一个参数,并不需要人为的计算空间的大小,例如int *p = (int *)calloc(20, sizeof(int))
  3. realloc函数:原型void realloc(void *ptr, size_t new_Size),用于对动态内存进行扩容,ptr为指向原来空间基址的指针, new_size为接下来需要扩充容量的大小

14. sizeof与strlen

sizeof:运算符(编译时计算),其能够返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系
注:由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小

strlen:函数(运算时计算),返回字符串的长度,直到遇到结束符’\0’,返回的长度大小不包括’\0’

char arr[10] = "Hello";
int len_one = strlen(arr);
int len_two = sizeof(arr); 
 cout << len_one << " and " << len_two << endl; //5 and 10

总结:
sizeof返回数组时,只管编译器为其分配的数组空间大小,不关心里面存了多少数据。
strlen只关心存储的数据内容,不关心编译器为其分配的数组空间大小

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值