C语言面试高频(二)

1.结构体和共用体的区别

1.定义

结构体:

  • 成员在内存中独立存储,每个成员占用独立的内存空间。
  • 内存占用是成员之和,每个成员都占用独立的空间。
  • 成员可以同时被访问,通过成员名字来访问。
  • 适合存储和处理多个不同类型的数据,如员工信息、图形对象等。

共用体:

  • 成员共享同一块内存空间,只能存储一个成员的值。
  • 内存占用是最大成员的大小,所有成员共享该空间。
  • 成员只能同时访问其中的一个,存取时要明确指定。
  • 适合存储和处理只使用其中一种类型的数据,可以节省内存空间或进行数据类型转换。

2.举例(便于理解)

#include <stdio.h>
struct MyStruct {
  int x;
  float y;
};
union MyUnion {
  int a;
  float b;
};
int main() {
  struct MyStruct myStruct;
  union MyUnion myUnion;
  printf("结构体的大小: %zu 字节\n", sizeof(myStruct));
  printf("共用体的大小: %zu 字节\n", sizeof(myUnion));
  
  return 0;
}

结构体的大小: 8 字节
共用体的大小: 4 字节

2.简述C++有几种传值方式之间的区别⭐

值传递(pass by value):参数以值的方式传递给函数,函数内部对参数的修改不会影响到原始数据。函数会创建参数的副本,在函数的作用域内使用副本进行操作。常用于传递简单类型的参数。

#include <iostream>
void modifyValue(int x) {
  x = 10;
}
int main() {
  int num = 5;
  modifyValue(num);
  std::cout << "原始值: " << num << std::endl;
  return 0;
}
 输出结果:原始值: 5

引用传递(pass by reference):参数以引用的方式传递给函数,函数内部对参数的修改会影响到原始数据。函数直接操作原始数据,没有创建副本。可以用于传递任意复杂类型的参数,比如对象或容器。

#include <iostream>
void modifyValue(int& x) {
  x = 10;
}
int main() {
  int num = 5;
  modifyValue(num);
  std::cout << "修改后的值: " << num << std::endl;
  return 0;
}
输出:修改后的值: 10

指针传递(pass by pointer):参数以指针的方式传递给函数,函数内部可以通过指针来访问和修改原始数据。函数通过指针间接操作原始数据,需要注意空指针和指针的解引用。可以用于传递需要动态分配内存的参数,或者需要返回多个值的情况。

#include <iostream>
void modifyValue(int* x) {
  *x = 10;
}
int main() {
  int num = 5;
  modifyValue(&num);
  std::cout << "修改后的值: " << num << std::endl;
  return 0;
}
输出:修改后的值: 10

3.数组指针与指针数组的区别⭐

数组指针(pointer to an array):数组指针是指向数组的指针变量。数组指针的类型声明中,指针符号 * 出现在数组名之前,用于表示数组指针的类型。数组指针可以指向整个数组,而不仅仅是数组的第一个元素。

#include <stdio.h>
int main() {
  int arr[5] = {1, 2, 3, 4, 5};
  int (*ptr)[5];  // 声明一个指向包含5个整数的数组的指针
  ptr = &arr;  // 数组指针指向数组
  printf("数组元素通过指针访问: ");
  for (int i = 0; i < 5; i++) {
    printf("%d ", (*ptr)[i]); // 使用指针访问数组元素需要解引用
  }
  return 0;
}
数组元素通过指针访问: 1 2 3 4 5

指针数组(array of pointers):指针数组是一个数组,数组的元素都是指针。指针数组的类型声明中,指针符号 * 出现在数组名的后面,用于表示指针数组的类型。指针数组的每个元素指向一个独立的内存块,可以指向不同的变量或数据。

#include <stdio.h>
int main() {
  int num1 = 1, num2 = 2, num3 = 3, num4 = 4, num5 = 5;
  int* arr[5];  // 声明一个包含5个整型指针的指针数组
  arr[0] = &num1;
  arr[1] = &num2;
  arr[2] = &num3;
  arr[3] = &num4;
  arr[4] = &num5;
  printf("指针数组元素的值: ");
  for (int i = 0; i < 5; i++) {
    printf("%d ", *arr[i]); // 解引用指针数组的元素以获取其值
  }
  return 0;
}
指针数组元素的值: 1 2 3 4 5

4.指针函数与函数指针的区别⭐

指针函数(function returning a pointer):指针函数是一种返回指针类型的函数。指针函数的返回类型是一个指针,指向特定类型的数据。调用指针函数时,会返回一个指向函数计算结果的指针。

#include <stdio.h>
int* addIntegers(int a, int b) {
  int* result = (int*)malloc(sizeof(int));
  *result = a + b;
  return result;
}

int main() {
  int* sum = addIntegers(5, 3);
  printf("和: %d\n", *sum);
  free(sum);
  return 0;
}
输出:和: 8

函数指针(pointer to a function):函数指针是指向函数的指针变量。函数指针的类型声明中,指针符号 * 出现在函数名之前,用于表示函数指针的类型。函数指针可以用于直接调用指向的函数,或者作为参数传递给其他函数。

#include <stdio.h>
int addIntegers(int a, int b) {
  return a + b;
}
int main() {
  int (*ptr)(int, int);  // 声明一个指向以两个整数为参数并返回整数的函数的指针变量
  ptr = addIntegers;  // 将函数地址赋值给函数指针
  int sum = ptr(5, 3);  // 通过函数指针调用函数
  printf("和: %d\n", sum);
  return 0;
}
输出:和: 8

5.原码、反码、补码的定义

原码:最高位表示符号,其余位表示数值的绝对值。正数的原码就是二进制表示,符号位为0。负数的原码符号位为1,数值位根据绝对值的二进制表示。

反码:正数的反码与原码相同,负数的反码是对原码除符号位外的每一位取反(0 变为 1,1 变为 0)。

补码:正数的补码与原码和反码相同,负数的补码是对反码加 1。

6.内存分布模型⭐

上图是比较经典的内存分布的模型图,下面将对上图中的不同的组成部分进行详细解释(从低地址到高地址)注:必须知道组成结构但是具体的含义只需要理解。

  1. 代码段:存放程序的机器指令(即二进制代码)。通常是只读的,因为程序的指令在执行过程中不应该被修改。
  2. 数据段:存放已初始化的全局变量和静态变量。这些变量在程序开始运行时已经赋予了初始值。
  3. BSS 段:存放未初始化的全局变量和静态变量。它们在程序开始运行时会自动初始化为0或者空指针。
  4. 堆区:动态分配的内存空间,用于存放程序运行时动态申请的内存。(程序员可以通过函数(如malloc、calloc等)或者操作系统提供的接口来申请和释放堆内存,堆从低地址向高地址增长。
  5. 栈区存放函数的局部变量、函数参数值以及函数调用和返回时的相关信息。栈区是按照“先进后出”的原则进行管理,内存的分配和释放是自动进行的,栈从高地址向低地址增长。是一块连续的空间
  6. 共享区:也称为文件映射或共享内存,用于实现不同进程之间的内存共享。

7.malloc和calloc的区别

malloc

  • malloc 分配的内存是未初始化的,其中的字节内容是不确定的(可能是随机值)。
  • 如果内存分配失败,malloc 返回一个空指针 NULL,可以通过检查返回值来判断是否分配成功。

calloc 

  • calloc 分配的内存会被初始化为全0。
  • calloc 在分配失败时会自动抛出错误(异常),可以使用异常处理机制来捕获和处理错误。

8.malloc的底层原理

1.结论:

  1. 当开辟的空间小于 128K 时,调用 brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)
  2. 当开辟的空间大于 128K 时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。

2.具体实现

  1. 当调用 malloc(size) 时,它首先计算需要分配的内存块大小,

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

c++/嵌入式面经专栏 文章被收录于专栏

本人2022年毕业于山东大学,目前就职intel。打算把之前校招时做的笔记通过专栏发出来,本专栏适合于C/C++、嵌入式方向就业的同学,本篇面经总结数千篇面经的知识集合,实时更新全网最新的嵌入式/C++最新内容,囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,在我看来这些是求职者在面试中必须掌握的知识点。最后呢祝各位能找到自己合适的工作。

全部评论
订阅专栏,每天会更新面试考点。
13 回复 分享
发布于 2023-10-04 17:47 山东
大佬总结的很全,有些在面试中我也遇到了
点赞 回复 分享
发布于 2023-10-07 10:27 北京
相见恨晚
点赞 回复 分享
发布于 2023-10-07 10:28 北京
原码,反码,补码的例子写错了吧,三个都是一样的嘛?
点赞 回复 分享
发布于 2023-10-12 10:39 陕西
有个问题,指针数组的类型声明,指针符号出现在数组名的后面,但写的是:int* arr[5]; // 声明一个包含5个整型指针的指针数组,这里*还是在数组名前面啊
点赞 回复 分享
发布于 09-03 14:38 广东
补一个。 因为引用只是别名的原因,所以引用的嵌套等同于没有嵌套。 但指针是实实在在的对象,所以存在嵌套指针的用法。
点赞 回复 分享
发布于 09-10 10:03 四川

相关推荐

昨天 11:46
已编辑
南京理工大学 C++
都没出结果,问问是各位会如何选择补充一下,华为应该是14左右县是南通市下面的一个县局
在敲键盘的布莱克很积极:兄弟,我给你提供一个角度,其他评论可能更多是从待遇和发展前景来看的,但是我建议你想一想你走哪条路更能产生对自己的身份认同。 如果你乐于钻研技术,喜欢工程实践,即使从华为失业你也能认可自己工程师的身份,不因为工作上不顺而失去对自己的尊重(因为这是你几乎必然会面对的情况),那就可以去华为,毕竟广阔天地大有可为。 但是如果你并不能因为技术产生自我的身份认同,那建议还是公务员吧,因为这种职业自带身份认同,不需要你有很强的兴趣与心理建设来支持,即使每月钱不多,你比较容易做到自己尊重自己,也容易在社会上得到他人的尊重。 不管钱多钱少,待遇怎么样,最终还是要自己过得开心,享受到了生活,才算是好的一生
投递华为等公司10个岗位 >
点赞 评论 收藏
分享
11 29 评论
分享
牛客网
牛客企业服务