一、C:
1.static和const的作用优缺点
限制作用域:
static声明中使用全局变量、函数 ,仅当前文件内可用,其他文件不能引用
static修饰的局部变量只能在本函数中使用.
延长生命周期:
static修饰的变量生命周期为整个程序
存放位置:
static修饰的变量存放在静态区
初始化:
- static变量未赋初值时初值为0,且只初始化一次
const 是常量化的意思;
可以修饰变量,可以修饰指针。
当修饰变量的时候,因为不可以通过变量名对变量的值进行修改所以在定义变量的时候需要给变量初始化;
当修饰指针的时候,const位置不同,修饰的指针的指向或内容不能改变。
char *const a 指向不可修改,内容可以修改
char const *a 内容不可以修改,指向可以修改
const char *a 内容不可以修改,指向可以修改
2.typedef和define的区别
1.处理时机:
typedef:typedef 创建类型别名,编译器在编译时会对其进行处理。
#define:创建宏定义,预处理器在预处理阶段会对其进行处理。即在编译之前 ,预处理器会将代码中的宏定义替换为相应的内容。
3.作用范围:
typedef:typedef 定义的类型别名在作用域内有效,并且可以在多个函数或文件中使用。
#define:#define 定义的宏定义在定义位置后全局有效,可以在整个代码中使用,直到遇到 #undef 指令或代码结束。
4.语法格式:
typedef:typedef 的语法格式为:typedef <existing_type>
<new_type>;
#define:#define 的语法 <macro_name> <replacement>
5.类型安全:
typedef:typedef 创建的别名是类型安全的,因为它们实际上是原类型的别名。
#define:#define 创建的宏定义不进行类型检查,它仅仅是简单的文本替换。
6.适用场景:
typedef:typedef 通常用于创建复杂类型的别名,使代码更易读和可维护。
#define:#define 主要用于创建简单的常量或函数宏,用于简化
代码或定义一些特定的标识符
枚举(enum);
枚举是一种用户自定义的数据类型,它为一组相关的整数常量赋予了有意义的名称,枚举里成员系统会自动赋初值,第一个成员为0,依次类推;如果程序员想赋值的,假设第一个成员赋值为1,那么第二个成员系统会赋值为2,依次类推
3.volatile作用、Extern作用
volatile 主要有以下作用:
volatile是一个关键字,用于在 C 和 C++ 中修饰变量,它主要用于告诉编译器该变量的值可能在程序执行期间发生变化,从而禁止编译器对该变量进行优化。
- 告知编译器不要优化: 优化代码时,会对变量进行优化,如寄存器优化、常量传
- 播等,以提高执行效率。但对于被 volatile 修饰的变量,编译器会认为其值可能在程序执行期间被意外地更改,因此不会进行优化,确保变量的值每次都从内存中读取。
- 防止可能会存在多个线程或处理器同时访问同一个变量。如果该变量不使用 volatile 修饰,编译器可能会将其缓存到寄存器或者优化编译器缓存变量值: 在多线程或中断处理程序中,为常量,这样可能导致线程间不同步或者中断处理程序无法正确获取最新的变量值。而使用 volatile 修饰后,编译器每次都会从内存中读取变量的值,确保变量的值始终是最新的。
- 外部设备的交互与: 当程序与外部设备进行交互时,如硬件寄存器或存储映射 I/O 等,外部设备可能会在任何时候更改这些变量的值。在这种情况下,使用 volatile 修饰这些变量可以确保程序正确读取设备的状态,而不会受到编译器的优化干扰。
Extern
外部引用,引用其他文件中的全局变量或函数
4.sizeof和strlen区别
strlen是函数,用于计算字符串的长度;不包含`\0`;
sizeof是关键字,用于计算变量、数组、其他数据类型所占内存空间的大小;当sizeof计算字符串长度的时候,包含`\0`;
5.数组和链表的区别
数组
数组是一个相同类型的数据集合
数组元素可以使用数组索引随机访问
数组的数据元素在内存中连续存储
插入和删除非常耗时,时间复杂度为O(n)
数组的内存是静态分配的,在编译期间完成
数组的大小必须在数组的声明或初始化的时候指定
链表
链表是一个有相同数据类型的有序集合,其中每个元素使用指针链接
链表不允许随机访问,链表创建一个指针指向相应的数据
链表的插入和删除非常快,时间复杂度为O(1)
链表的内存分配-是动态的,在运行时动态分配
链表的大小随元素的插入或删除动态变化
链表和内核链表的区别:
- 内核链表和链表的区别:“内核链表”特指在操作系统内核环境中使用的链表,它可能有一些针对操作系统需求的特殊设计或优化;
- 而“链表”则是一个更通用的概念,可以应用于任何软件开发场景。
- 内核链表通常是由操作系统内核提供的,并且有一套封装好的函数来支持其操作。
- 当我们在用户空间的程序中使用链表时,通常需要自己定义链表的数据结构以及相关的操作函数。
6.指针相关
指针就是地址,指针变量就是存放地址的变量;指针可以使用简单的运算符操作;指针加一或者自加,代表指向下一个元素;对于32位系统,指针占4字节
数组是同名类型是数组数据的集合,内存连续。数组的首地址,是地址常量,不可以进行自加等操作;
指针数组的本质是数组,数组里存放的是指针。int *p[3]
数组指针的本质是指针,指向数组的指针称为数组指 针。int (*p)[3]可以间接访问二维数组。
函数指针本质是指针,指向函数的指针 ,一般用做函数的参数,实现代码复用,也可以作为结构体成员,指向某个函数。 int (*p)(int,int)
函数指针数组:本质上是一个数组,存储多个类型相同的函数指针
格式: 存储类型 数据类型 (*函数指针数组变量名[常量表达式])(参数列表)
7.结构体和共用体区别
二者都是构造数据类型
1)结构体:让C语言实现面向对象的思想。结构体使用的时候,结构体中每一个成员都有自己的内存空间,计算结构体大小的时候要注意内部字节对齐;
2)共用体又叫联用体,大小等于成员中占内存最大的那个大小。合体,每一个成员都共享内存空间。因此共用体大小等于成员中占内存最大的那个大小。
结构体 (struct)
- 内存分配:每个成员变量都有其自己的内存空间。
- 内存是连续分配的,但可能由于对齐规则导致有填充字节。
- 访问成员:可以同时访问所有成员。
- 大小:结构体的总大小等于所有成员所占空间之和加上任何对齐填充的空间。
结构体的大小并不是简单的将每个成员的大小相加就能得到的
sizeof(struct 结构体名); // 结构体类型的大小
结构体内存对齐规则
- 第一个成员在结构体变量偏移量为0的地址处。(及结构体的首地址处)
- 个数字(对齐数)的整其他成员变量要对齐到某数倍
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大整数的倍数处,结构体整体的大小就是所有最大对齐数的整数倍
对齐数:该结构体成员变量自身的大小与编译器默认(64位是8、32位是4)的一个大小比较,取小值
- 用途:
- 用于组合不同类型的变量以形成一个复合数据类型。
- 常用于表示复杂的数据结构,例如一个人的信息包含姓名、年龄和身高。
共用体 (union)
- 内存分配:所有成员共享同一段内存空间。
- 访问成员:不能同时访问所有成员,因为它们共享相同的内存位置。
- 当改变一个成员的值时,会 影响其他成员的值。
- 大小:共用体的大小等于最长的成员所占的空间。
- 用途:
- 用于节省内存,当多个数据成员不可能同时使用时。
- 常用于实现动态数据类型,如变长数组或实现多态性。
结构体大小计算(字节对齐)
#pragma 是一个预处理指令,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作,#pragma pack 的 主要作用就是改变编译器的内存对齐方式
(1)结构体各个成员变量的首地址必须是 min{自身对齐值,指定对齐值的整数倍。
(2)结构体各个成员相对于结构体起始地址的偏移量(offset)是 min{该成员数据类型大小,指定对齐值} 的整数倍,如有需要,编译器会在成员之间加上填充字节。
(3)结构体分配的总空间大小必须是 min {其最宽基本数据类型成员,指定对齐值} 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
堆和栈的区别
(1)管理方式不同。
栈编译器自动管理,无需程序员手动控制;
堆空间的申请释放工作 由程序员控制,通过 malloc/free申请释放空间,容易产生内存泄漏。
(2)空间大小不同。
栈是向低地址扩展的数据结构,是一块连续的内存区域。
堆是向高地址扩展的数据结构,是不连续的内存区域。
(3)是否产生碎片。
对于堆来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。
(4)增长方向不同。
堆的增长方向是向上的,即向着内存地址增加的方向;栈的增长方向是向下的,即向着内存地址减小的方向。
栈区存放局部变量,函数形参,函数返回值。
用户空间划分:
代码段(Text Segment):存放程序的机器指令。
数据段(Data Segment):存放已初始化的全局变量和静态变量。
BSS段(BSS Segment):存放未初始化的全局变量和静态变量。
堆(Heap):动态分配的内存区域,如使用malloc或new分配的内存。
栈(Stack)-099:用于局部变量、形参、返回值存储。