1,函数指针和指针函数
函数指针是指向函数的指针,用于存储函数在内存中的地址。
用法:回调函数:void (*functionCallback)(int a, int b);
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
int (*operation)(int, int);
operation = add;
printf("加法结果:%d\n", operation(5, 3));
operation = subtract;
printf("减法结果:%d\n", operation(5, 3));
return 0;
}
指针函数就是函数的返回值是指针。
用法:创建一个结构体,返回一个结构体指针
#include <stdio.h>
#include <stdlib.h>
int* create_array(int size) {
int* arr = (int*)malloc(size * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
exit(1);
}
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
return arr;
}
int main() {
int size = 5;
int* array = create_array(size);
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
free(array);
return 0;
}
区别:前两个字,返回函数,就是函数指针;返回指针,就是指针函数。
2,指针的大小
指针的大小取决于编译器和目标平台的架构。通常情况下,指针的大小与处理器的位数有关:
32位系统就是32bit (4B); 64位系统就是64bit(8B)。
直接用sizeof即可获取指针大小。
3,sizeof和strlen的区别
sizeof:编译时运算符,用于计算数据类型或变量所占的内存字节数。它可以作用于任意数据类型,包括基本类型、数组、结构体等。计算结果包含字符串末尾的空字符 '\0'
。
对于数组,sizeof
返回整个数组的大小;而对于指针,sizeof
返回指针本身的大小,与其指向的数据无关。
strlen:运行时函数,定义在 <string.h>
头文件中,用于计算以空字符 '\0'
结尾的字符串的实际长度,即字符的个数,不包含末尾的 '\0'
。
sizeof
在编译时确定大小,而 strlen
在运行时计算长度。
4,c语言中内存分配的方式有几种?
-
栈区(Stack):由编译器自动分配和释放,主要用于存储函数的参数、局部变量等。栈区的内存由操作系统自动管理,特点是存取速度快,但空间有限。
-
堆区(Heap):由程序员手动分配和释放,使用如
malloc
、free
等函数进行管理。堆区适合存储需要动态分配的内存块,生命周期由程序员控制,若未及时释放可能导致内存泄漏。 -
全局/静态存储区(Global/Static Storage):存放全局变量和静态变量。初始化的全局变量和静态变量存放在一块区域,未初始化的则存放在相邻的另一块区域。程序运行期间,这些变量一直存在,内存由系统在程序加载时分配,程序结束时释放。
-
常量区(Constant Area):用于存放常量数据,如字符串常量等。该区域的内存通常是只读的,程序结束后由系统释放。
-
代码区(Code Area):存放程序的机器指令,即编译后的二进制代码。该区域的内存由系统管理,通常也是只读的,以防止程序意外修改自身的指令。
5,struct和union的区别:
内存分配方式:
-
结构体(
struct
):每个成员都有独立的内存空间,结构体的总大小是各成员大小之和(考虑内存对齐)。 -
联合体(
union
):所有成员共享同一块内存,联合体的大小等于最大成员的大小
数据存储特点:
-
结构体:可以同时存储所有成员的值,各成员互不干扰。
-
联合体:在同一时间只能存储一个成员的值,修改一个成员会影响其他成员,因为它们共享内存。
使用场景:
-
结构体:适用于需要同时存储多个不同类型数据的情况,例如表示一个学生的姓名、年龄和成绩。
-
联合体:适用于需要在同一内存位置存储不同类型数据的情况,例如处理多种数据类型的协议数据
#include <stdio.h>
#include <string.h>
struct ExampleStruct {
char a;
int b;
short c;
};
union ExampleUnion {
char a;
int b;
short c;
};
int main() {
struct ExampleStruct s;
union ExampleUnion u;
printf("Size of struct: %zu bytes\n", sizeof(s));
printf("Size of union: %zu bytes\n", sizeof(u));
s.a = 'A';
s.b = 100;
s.c = 10;
printf("Struct values: a = %c, b = %d, c = %d\n", s.a, s.b, s.c);
u.b = 100;
printf("Union values after setting b: a = %c, b = %d, c = %d\n", u.a, u.b, u.c);
u.a = 'A';
printf("Union values after setting a: a = %c, b = %d, c = %d\n", u.a, u.b, u.c);
return 0;
}
6,野指针是什么?什么原因会导致野指针?
野指针是指那些指向已释放或未分配内存区域的指针。
导致野指针的主要原因:
-
指针未初始化:声明指针变量后未对其进行初始化,此时指针包含不确定的值,可能指向任意内存地址。
int *ptr; // 未初始化的指针
*ptr = 10; // 未定义行为,可能导致程序崩溃
2,内存释放后未将指针置为 NULL:使用 free
或 delete
释放内存后,指针仍然指向原来的内存地址,但该内存已不再有效。
int *ptr = (int *)malloc(sizeof(int));
*ptr = 10;
free(ptr); // 释放内存
// ptr 仍然指向已释放的内存,成为野指针
3,指针越界访问:当指针操作超出其合法范围,导致指向未分配或无效的内存区域。
int array[5] = {1, 2, 3, 4, 5};
int *ptr = array;
for (int i = 0; i < 10; i++) {
printf("%d\n", *(ptr + i)); // 当 i >= 5 时,发生越界访问
}
避免野指针的措施:
1,指针初始化:在声明指针时,将其初始化为 NULL
。
2,内存释放后将指针置为 NULL:释放内存后,立即将指针设置为 NULL
,防止再次误用。
3,使用前检查指针有效性:在使用指针前,检查其是否为 NULL
。
4,避免指针越界操作:确保指针操作在合法范围内,防止越界访问。
7,define和typedf的区别
#define
:
-
预处理指令:
#define
是一种预处理指令,用于在编译前进行简单的文本替换。 -
宏定义:常用于定义常量、宏函数或进行条件编译。
-
无类型检查:由于只是文本替换,编译器不会对其进行类型检查,可能导致潜在的错误。
typedef
:
-
类型定义关键字:
typedef
用于为已有类型定义新的类型别名。 -
增加可读性:通过为复杂的类型定义简短的别名,提高代码的可读性和可维护性。
-
类型安全:
typedef
定义的别名在编译时会进行类型检查,确保类型安全。
主要区别:
-
处理阶段:
#define
在预处理阶段进行文本替换;typedef
在编译阶段处理。 -
类型检查:
#define
不进行类型检查,可能引发潜在错误;typedef
会进行类型检查,确保类型安全。 -
作用范围:
#define
的宏在整个代码中全局有效;typedef
定义的类型别名遵循作用域规则,只在定义的作用域内有效。 -
适用场景:
#define
适用于定义常量、宏函数等;typedef
适用于为类型定义别名,特别是复杂类型。
使用 #define
定义指针类型时,可能会引发意想不到的问题。例如:
#define PCHAR char *
PCHAR a, b;
预处理后,a
和 b
的类型分别是 char* a
和 char b
,这可能不是预期的结果。而使用 typedef
定义则不会出现此问题.