C基础笔记_指针专题
一:C 和 C++ 偏爱使用指针的原因
1. 通过指针可以直接操作内存
C 和 C++ 是系统级编程语言,它们的设计目标之一是允许开发者直接与硬件交互并高效地管理资源。
- 指针的本质:指针本质上是一个存储内存地址的变量,它让程序员可以直接访问和操作内存中的数据。
- 底层控制:通过指针,程序员可以精确地控制内存分配、释放以及数据布局,这对于编写操作系统、驱动程序、嵌入式系统等底层软件至关重要。
int x = 10;
int *p = &x; //指针变量 p 存储了 x 的地址
*p = 20; // 修改原始数据 x 的值为 20
2. 高效性和灵活性
指针提供了极高的效率和灵活性,特别是在性能敏感的应用中。
- 减少复制开销:通过传递指针而不是整个数据结构,可以避免大量数据的复制,从而提高性能。
- 动态内存分配:指针结合
malloc
/free
(C)或new
/delete
(C++)可以动态分配和释放内存,这在需要灵活管理内存时非常有用。
struct Node {
int data;
struct Node *next;
};
struct Node *head = (struct Node *)malloc(sizeof(struct Node));
head->data = 42;
head->next = NULL;
Java 和 Python 中也有类似的功能(如对象引用和垃圾回收),但这些功能是由运行时环境自动管理的,程序员无法直接干预。
3. 支持复杂的数据结构
指针是实现复杂数据结构(如链表、树、图等)的核心工具。
- 在 C/C++ 中,通过指针可以轻松构建动态数据结构。例如,链表节点通常包含一个指向下一个节点的指针。
- 在没有指针的语言中,这些数据结构通常由运行时环境或标准库提供,程序员无法直接控制底层实现。
struct LinkedList {
int value;
struct LinkedList *next;
};
在 Java 中,类似的结构可以通过对象引用来实现,但底层的内存管理和指针操作是由 JVM 自动完成的。
4. 兼容底层硬件
C 和 C++ 被广泛用于开发操作系统、编译器、嵌入式系统等底层软件,这些场景需要直接操作硬件寄存器和内存。
- 硬件交互:许多硬件设备通过特定的内存地址进行通信。指针使得程序员可以直接读写这些地址,从而与硬件交互。
- 中断处理:在实时系统中,指针常用于快速响应中断信号并访问共享资源。
#define PORT_A (*(volatile unsigned int *)0x1000) // 映射到硬件寄存器
PORT_A = 0xFF; // 写入数据到硬件寄存器
Java 和 Python 这样的高级语言完全屏蔽了这种底层操作。
5. 提供更高的自由度
C 和 C++ 的设计哲学是“信任程序员”,给予程序员极大的自由来决定如何使用资源。
- 手动内存管理:C 和 C++ 允许程序员完全控制内存的分配和释放,这虽然增加了出错的风险,但也提供了更高的性能和灵活性。
- 零成本抽象:C++ 的核心理念之一是“零成本抽象”(zero-cost abstraction),即高级特性(如类和模板)不会引入额外的运行时开销。指针是实现这一理念的重要工具。
6. 指针的危险性
尽管指针非常强大,但它也带来了潜在的危险性,这也是为什么许多现代语言选择隐藏指针的原因。
- 野指针:未初始化或已释放的指针可能导致未定义行为。
- 内存泄漏:忘记释放动态分配的内存会导致内存泄漏。
- 缓冲区溢出:错误的指针操作可能导致数组越界或其他安全问题。
int *p = malloc(sizeof(int)); // 分配内存 字节大小 sizeof(int)=8byte
*p = 42;
free(p);
*p = 10; // 错误:对已释放的内存进行访问
Java 和 Python 通过垃圾回收机制和边界检查避免了这些问题,但这也牺牲了一部分性能和灵活性。
7. 现代语言的选择
现代语言(如 Java、Kotlin 和 Python)选择隐藏指针的原因在于:
- 安全性:通过封装指针操作,减少了程序员犯错的可能性。
- 易用性:隐藏底层细节后,程序员可以专注于业务逻辑,而无需担心内存管理。
- 自动化管理:垃圾回收器自动管理内存,减少了手动管理的复杂性。
int[] arr = new int[10]; // 创建数组
arr[0] = 42; // 直接操作数组元素
在底层,
arr
实际上是一个指向数组首地址的指针,但 Java 隐藏了这些细节,并提供了边界检查以防止越界访问。
二:XX类型的指针
在C语言中,指针是一种非常强大的工具,它可以指向任何类型的数据。除了
int
类型的指针外,还有多种其他类型的指针,包括但不限于基本数据类型、数组、结构体、联合体和函数指针等。
1. 基本数据类型的指针
除 int
外,还可以有指向其他基本数据类型的指针,如 char
, float
, double
等。
char *cp; // 指向 char 类型的指针变量cp
float *fp; // 指向 float 类型的指针变量fp
double *dp; // 指向 double 类型的指针变量dp
2. 数组指针类型
可以声明一个指针来指向数组。需要注意的是,数组名本身就是一个指向数组第一个元素的指针。
int arr[5];
int (*ap)[5] = &arr; // ap 是一个指向包含5个 int 元素的数组的指针
3. 指向指针的指针(多级指针)
你可以有一个指向指针的指针,甚至更多级别。
int x = 10;
int *p = &x; // p 是指向 int 的指针
int **pp = &p; // pp 是指向 int类型指针p 的指针
4. 结构体指针类型
指针也可以用来指向结构体类型的数据。
struct Point {
int x, y;
};
struct Point pt = {1, 2};
struct Point *ppt = &pt; // ppt 是一个指向 struct Point 的指针
//访问结构体成员时,如果使用指针,则需要使用箭头操作符 -> 而不是点操作符 .
printf("x: %d\n", ppt->x); // 使用 -> 访问成员变量
5. 函数指针类型
函数指针是 指向函数的指针 ,允许你将函数作为参数传递给其他函数或从函数返回。
int add(int a, int b) {
return a + b;
}
int (*fp)(int, int) = add; // fp 是一个指向接受两个 int 参数并返回 int 类型的函数add的指针
int result = fp(5, 3); // 调用通过指针指向的函数
6. Void 指针
void*
是一种特殊类型的指针,它可以指向任意类型的数据,但不能直接解引用,因为编译器不知道它指向的数据类型。
关键字
void
1. 表示“无类型”
void
的字面意思是“空”或“无”,用来表示“没有类型”。它可以用于以下场景:
(1) 函数返回值为 void
当一个函数不需要返回任何值时,可以将其返回类型声明为 void
。
void sayHello() {
printf("Hello, World!\n");
}
(2) 函数参数列表为 void
当一个函数不接受任何参数时,可以将其参数列表声明为 void
(虽然现代 C 中通常省略 void
)
这里的
void
表示函数不接受任何参数。在 ANSI C 标准中,
void
参数列表是显式表明函数没有参数的推荐方式。
int getRandomNumber(void) {
return 42; // 返回一个随机数
}
2. 指针类型:void*
void*
是一种特殊的指针类型,表示“指向未知类型的指针”。
void*
特点:
void*
可以指向任何数据类型的变量。- 它是一种通用指针类型,常用于需要处理多种数据类型的情况。
- 不能直接解引用
void*
,因为它不知道所指向的数据的具体类型。
#include <stdio.h>
//printValue 函数接受一个 void* 类型的指针,并根据类型参数决定如何解引用。
//注意:void* 必须显式转换为目标类型后才能解引用。
void printValue(void *ptr, char type) {
if (type == 'i') {
printf("%d\n", *(int*)ptr); // 将 void* 转换为 int*
} else if (type == 'f') {
printf("%f\n", *(float*)ptr); // 将 void* 转换为 float*
}
}
int main() {
int x = 42;
float y = 3.14;
printValue(&x, 'i'); // 输出整数
printValue(&y, 'f'); // 输出浮点数
return 0;
}
3. 表示“无操作”
在某些情况下,void
用于表示“无操作”或“忽略结果”。
int main() {
printf("This is a test.\n");
(void)printf("This result is ignored.\n"); // 明确忽略返回值
return 0;
}
(void)
明确表示忽略表达式的返回值。这种写法在某些代码风格中被用来消除编译器警告。
4. 空参数列表
在旧版 C(K&R C)中,函数定义中的空参数列表默认表示“任意数量和类型的参数”。为了明确表示函数不接受任何参数,ANSI C 引入了 void
。
// K&R 风格:表示可以接受任意参数
int func() {
return 42;
}
// ANSI C 风格:明确表示不接受参数
int func(void) {
return 42;
}
现代 C 编程中,建议使用
void
来明确说明函数不接受参数。
5. 结合 typedef
使用
void
也可以与其他关键字结合使用,例如与 typedef
定义通用指针类型。
typedef void (*Callback)(int);
void executeCallback(Callback cb, int value) {
cb(value);
}
void myCallback(int x) {
printf("Callback called with %d\n", x);
}
int main() {
executeCallback(myCallback, 42);
return 0;
}
这里,
Callback
是一个函数指针类型,指向一个接受int
参数且返回void
的函数。
void* 的疑惑
1. *
的作用
* 是指针声明符,表示“这是一个指针”。它的作用是修饰变量或类型,表明这个变量是一个指针,而不是普通的数据类型。