简介
指针是 C 语言中非常重要的概念。指针是一个变量,它存储的不是具体的数据值,而是另一个变量的内存地址。
简单地说,指针就是指向内存中某个地址的一个变量。通过使用指针,我们可以间接访问和操作内存中的数据。
首先,我们需要理解内存的基本结构。计算机的内存可以看作是一个线性的字节序列,每个字节都有一个独特的地址。这些地址通常用十六进制数表示,如 0x1000、0x1001 等。
指针就是一个存储内存地址的变量。比如,我们定义一个整型变量 int x = 10;。在内存中,这个变量 x 会被分配一个地址,比如 0x1000。我们可以声明一个指针变量 int* p = &x;。这样,指针变量 p 就会存储变量 x 的地址 0x1000。
通过指针变量 p,我们可以间接访问存储在地址 0x1000 的值 10。这种通过指针访问内存的方式,被称为"间接寻址"。
几个重要的运算符
& 运算符
返回变量的地址。如 &x 会返回变量 x 的内存地址。
* 运算符
解引用运算符,用于访问指针指向的值。如 *p 会返回指针 p 指向的值。
此外
我们还可以对指针进行加减运算。比如 p++ 会使指针 p 指向下一个内存单元,p-- 会使其指向上一个内存单元。这在处理数组和链表等数据结构时非常有用。
应用
函数参数传递
通常,我们将变量作为值传递给函数,函数只能访问该变量的副本。但如果我们传递变量的地址(指针),函数就可以直接修改调用者的变量。这种传递方式被称为"引用传递"。
其他
动态内存分配和释放
实现复杂的数据结构,如链表、树等
在函数之间传递大型数据结构
访问硬件设备的寄存器
简单示例
假设我们要实现一个可以动态增长的整型数组。我们需要定义以下数据结构:
typedef struct {
int* data; // 指向动态分配的数组
size_t size; // 当前数组大小
size_t capacity; // 当前数组容量
} DynArray;
下面是一些常见的操作函数:
// 初始化动态数组
void init_dyn_array(DynArray* arr) {
arr->data = NULL;
arr->size = 0;
arr->capacity = 0;
}
// 向动态数组中添加元素
void push_back(DynArray* arr, int value) {
// 如果数组已满,先扩容
if (arr->size == arr->capacity) {
size_t new_capacity = (arr->capacity == 0) ? 1 : arr->capacity * 2;
arr->data = realloc(arr->data, new_capacity * sizeof(int));
arr->capacity = new_capacity;
}
arr->data[arr->size++] = value;
}
// 获取动态数组中的元素
int get(DynArray* arr, size_t index) {
if (index >= arr->size) {
// 越界处理
return 0;
}
return arr->data[index];
}
// 释放动态数组
void free_dyn_array(DynArray* arr) {
free(arr->data);
arr->data = NULL;
arr->size = 0;
arr->capacity = 0;
}
下面是一个使用示例:
int main() {
DynArray arr;
init_dyn_array(&arr);
push_back(&arr, 1);
push_back(&arr, 2);
push_back(&arr, 3);
printf("Array size: %zu\n", arr.size);
for (size_t i = 0; i < arr.size; i++) {
printf("%d ", get(&arr, i));
}
printf("\n");
free_dyn_array(&arr);
return 0;
}
在这个例子中,我们使用指针定义了一个动态数组结构 DynArray。通过对指针的操作,我们实现了数组的动态扩容、元素的添加和访问,以及最终的内存释放。
##DynArray* arr与DynArray arr
DynArray* arr;
这是一个指针变量,用于指向 DynArray 类型的对象。
当你声明这个指针变量时,它只是分配了存储地址的空间,但并没有实际分配 DynArray 对象的内存。
要使用这个指针,你需要通过 malloc() 或 calloc() 等函数动态分配 DynArray 对象的内存,然后将指针指向这块内存。
例如: DynArray* arr = (DynArray*)malloc(sizeof(DynArray));
使用完毕后,需要手动释放内存: free(arr);
DynArray arr
这是一个 DynArray 类型的变量,会自动在栈上分配内存空间。
当你声明这个变量时,它会自动初始化为一个默认的 DynArray 对象。
你可以直接使用这个对象,不需要手动分配内存。
当这个变量超出作用域时,它会自动被销毁,不需要手动释放内存。
小结
DynArray* arr; 使用动态内存分配,需要手动管理内存,灵活性更高。
DynArray arr; 使用栈上的内存,更简单易用,但不太灵活。
指针与二级指针
指针用于存变量的内存地址,二级指针用于存指针的内存地址。
指针
指针存储的是变量的内存地址。
通过解引用指针 (*ptr) 可以访问变量的值。
例如:
int x = 10;
int* ptr = &x; // ptr 存储了变量 x 的地址
*ptr = 20; // 通过 ptr 修改了变量 x 的值
二级指针
二级指针存储的是指针变量的内存地址。
二级指针指向一个指针变量,通过二级指针可以间接访问和修改指针变量所指向的值。
例如:
int x = 10;
int* ptr = &x;
int** ptr_to_ptr = &ptr; // ptr_to_ptr 存储了指针 ptr 的地址
**ptr_to_ptr = 30; // 通过二级指针修改了 ptr 所指向的值
在这个例子中:
ptr 是一个指针,指向变量 x。
ptr_to_ptr 是一个二级指针,它存储了指针 ptr 的地址。
通过解引用二级指针 **ptr_to_ptr,我们可以间接修改 ptr 所指向的值,也就是变量 x 的值。
二级指针的常见用途
在函数中传递指针参数,以便函数能够修改指针所指向的值。
处理动态分配的二维数组或链表等复杂数据结构。
总之,指针用于存储变量的地址,而二级指针则用于存储指针变量的地址。掌握指针和二级指针的概念和用法,对于编写复杂的 C 程序非常重要。
指针和二级指针的使用约定
关于指针和二级指针的使用约定,确实不是一成不变的,但是有一些公认的最佳实践和常见用法。
指针的使用约定
在大多数情况下,我们都会遵循将指针命名为 ptr 的约定。这是一个相当普遍的 C 编程习惯。
但这并不是强制性的,开发者也可以根据具体情况选择其他更有意义的命名,只要保持一致性即可。
二级指针的使用约定
对于二级指针,通常会遵循将其命名为 ptr_to_ptr 或 dptr 等形式的约定。
这种命名约定可以更清晰地表达二级指针的用途,增加代码的可读性。
arr->size 与arr.size
在 C 语言中,arr->size 的使用方式与其他面向对象语言中的 a.b 非常相似。
在 C 语言中,当我们有一个指向结构体的指针时,我们可以使用 -> 运算符来访问结构体内的成员变量。这种访问方式与点号 . 运算符非常相似,但又有所不同。
使用点号 . 运算符
当我们有一个直接的结构体变量时,可以使用点号 . 来访问其成员变量。
例如: struct DynArray arr; arr.size;
使用箭头 -> 运算符
当我们有一个指向结构体的指针时,需要使用箭头 -> 来访问其成员变量。
例如: struct DynArray* arr; arr->size;
这两种访问方式在功能上是等价的,只是语法略有不同。使用哪种方式取决于你是否有一个直接的结构体变量,还是一个指向结构体的指针。
其他面向对象语言中的 a.b 的相似之处
在 Java 或 C# 中,你可以使用 a.b 来访问对象 a 的成员变量 b。
在 C++ 中,你也可以使用 a->b 来访问指向对象 a 的指针的成员变量 b。