指针:这块地方是我的了!

乐活   2025-01-05 11:25   内蒙古  

内存里面都是01,这种东西。那如何定位?就是使用指针!

就好像这样

我们又知道,一个文件其实是大量的01构成的。那我们把这堆01翻译成什么文件或者内容全靠我们自己说了算。

指针是一个变量,专门用来存储另一个变量的内存地址。就是一指一个地方,这个地方就有产生一点信息,要用变量来放。

指针具有类型信息(例如 int*、char*、float* 等),表示指向的内存地址存储的数据类型。
也就是说我们说这地址代表的是什么东西,全靠我们自己的定义,也就是类型信息。

我可以说是这些01代表张, 也可以说代表256

指针类型决定了对该地址进行解引用(即访问指针指向的值)时的操作方式。
有了类型信息我们才可以进行操作。
int* ptr;  // 声明一个指向整数类型的指针变量
  1. int* 表示这是一个指向 int 类型变量的指针。
  2. ptr 是指针变量的名字。
*纯粹就是标志。
如何获取?
int a = 10;int* ptr = &a;  // ptr 指向变量 a 的地址

使用 & 操作符可以获得一个变量的地址,然后把这个地址给放到一个指针变量上面。

使用 * 操作符可以访问指针所指向的地址存储的值:

printf("%d\n", *ptr);  // 输出 a 的值:10*ptr = 20;             // 修改指针指向地址的值,a 的值变为 20

我们也可以在地址上面做修改,相当于最底层的操作了

指针未初始化,或指向了被释放的内存,会成为野指针。

int* ptr;  // 未初始化的指针是野指针

好问题,就是你指了一堆01,但是没说这东西到底代表什么

const int a = 10;const int* ptr = &a;*ptr = 20;  // 错误,不能修改常量

指向常量的指针:指针指向的值不可修改

int a = 10, b = 20;int* const ptr = &a;  // 指针本身是常量ptr = &b;             // 错误,不能修改 ptr 的值

常量指针:指针本身的地址不可修改

const就是一个标志,说动不了。就看这个东西在哪里,就修饰什么。

首先是给一片地址起了名字,叫a,然后具体的值是10,它的类型是int,接着我们使用&,取了这个a的地址,存在了在ptr的地址上面,然后也说明了,ptr指的01是int类型,const保护了这个指针。

int* arr = malloc(10 * sizeof(int));  // 动态分配内存free(arr);                            // 释放内存

指向动态分配内存或数组的指针

使用场景:

int* ptr = (int*)malloc(sizeof(int));*ptr = 42;free(ptr);

内存分配

先使用malloc搞了一块内存,就后面括号这么大。然后使用强制类型转换为int类型的指针储存区。接着等号前面是最一开始的起始位置。

int arr[5] = {1, 2, 3, 4, 5};int* ptr = arr;  // 等价于 int* ptr = &arr[0];printf("%d\n", *(ptr + 2));  // 输出 arr[2] 的值:3

数组名可以退化为指针,表示数组的起始地址

void increment(int* p) {    (*p)++;}
int main() { int a = 10; increment(&a); printf("%d\n", a); // 输出 11 return 0;}

通过指针实现参数的引用传递,允许在函数内部修改外部变量的值,因为指针就是在一块内存区域做操作。

int add(int a, int b) { return a + b; }int (*func_ptr)(int, int) = add;printf("%d\n", func_ptr(3, 4));  // 输出 7

函数的地址也可以存储在指针中,用于回调函数或动态调用

有一些注意事项:

if (ptr != NULL) {    *ptr = 10;}

使用指针前,检查是否为 NULL

int arr[5] = {1, 2, 3, 4, 5};int* ptr = arr;ptr = ptr + 1;  // 移动到下一个元素printf("%d\n", *ptr);  // 输出 2

指针支持加减运算,但必须确保指针不越界

因为我们就是在内存区域里面做操作,你定义在里面的数据类型是有固定的大小的,当然可以靠加减地址在定位。

整点高级的:

函数参数可以是指针指针参数可以指向任何类型的变量(包括基本数据类型、结构体、数组、函数等)。

使用指针作为函数参数允许函数直接操作传递给它的变量,而无需返回任何值。

好理解吧?就是给了这块内存一个机器,直接在上面操作了,不需要复制一个新的地方在操作。

当函数参数是指向基本数据类型的指针时,函数可以修改传递给它的变量的值。这通常用于模拟“传值传引用”的机制,即通过指针将数据传递给函数,从而使函数能够直接修改原始数据。

#include <stdio.h>
void modifyValue(int* ptr) { *ptr = 20; // 修改指针指向的值}
int main() { int x = 10; printf("Before: %d\n", x); modifyValue(&x); // 传递x的地址给函数 printf("After: %d\n", x); // 输出20,说明x的值被修改 return 0;}

在这个例子中,modifyValue 函数接收一个指向 int 类型的指针(int* ptr)。通过解引用该指针(*ptr),函数修改了传递给它的变量 x 的值。

你x=10是一个值,也有一块地址

我指针来了,直接在这里进行处理,变成了20

接下来看个大戏!

这种函数

传了个结构体进来

指针也可以指向结构体,这允许函数操作结构体的成员,而不需要复制整个结构体。通过指针传递结构体,能够节省内存和提高效率,尤其是在结构体较大时。

是这样的,一个函数的参数在调用的时候,是需要把参数复制一遍的,如果这个参数本身就复合类型,那确实很大,如果就是传个地址进来就很小了。

#include <stdio.h>
struct Person { char name[50]; int age;};
void updateAge(struct Person* p) { p->age = 30; // 修改结构体指针指向的成员}
int main() { struct Person person = {"Alice", 25}; printf("Before: %s, Age: %d\n", person.name, person.age); updateAge(&person); // 传递结构体的地址给函数 printf("After: %s, Age: %d\n", person.name, person.age); // 输出修改后的年龄 return 0;}

在这个例子中,updateAge 函数接收一个指向 struct Person 的指针,并通过 p->age 修改结构体的 age 成员。-> 运算符用于通过指针访问结构体的成员。

#include <stdio.h>
void modifyArray(int* arr, int size) { for (int i = 0; i < size; i++) { arr[i] = arr[i] * 2; // 修改数组中的元素 }}
int main() { int arr[] = {1, 2, 3, 4, 5}; int size = sizeof(arr) / sizeof(arr[0]); printf("Before modification:\n"); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n");
modifyArray(arr, size); // 传递数组的指针 printf("After modification:\n"); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); // 数组元素被修改 } printf("\n"); return 0;}

在C语言中,数组名本身就是指向数组首元素的指针,因此可以将数组的指针作为函数参数传递。

通过数组指针,函数可以访问和修改数组中的元素。

modifyArray 函数接收一个指向整数数组的指针(int* arr)。通过该指针,函数可以访问并修改数组的每个元素。这个也好理解,因为数组就是一块连续的内存,在上面做加减移动当然可以。

#include <stdio.h>
void greet() { printf("Hello, world!\n");}
void farewell() { printf("Goodbye, world!\n");}
void execute(void (*func)()) { func(); // 调用通过函数指针传递的函数}
int main() { execute(greet); // 传递greet函数的指针 execute(farewell); // 传递farewell函数的指针 return 0;}

指针也可以指向函数,这允许将函数作为参数传递,甚至在运行时决定调用哪个函数。

这个眼熟不?

你看看这是什么?这就是回调

execute 函数接收一个指向无返回值且无参数的函数的指针,并根据传递的函数指针调用相应的函数。

那上面这些优点就是:

  1. 修改原始数据:使用指针可以让函数修改传入参数的值,而不仅仅是操作参数的副本。
  2. 提高效率:传递指针而不是大块数据(如大数组或结构体)可以避免复制数据,从而提高程序效率。
聊聊引用传递怎么样?
在C语言中,引用传递并不像C++那样有直接的引用类型(&)来实现,但是我们可以通过使用指针来模拟引用传递的效果。
指针作为函数参数时,允许函数操作传递给它的变量的内存地址,从而实现对原始数据的修改。

引用传递意味着传递的是变量本身的地址而不是变量的副本。这样,在函数内部修改参数的值会直接影响到调用该函数时传递的变量。这与值传递(按值传递)不同,值传递会复制参数的值,函数内部修改参数不会影响外部变量。

虽然C语言没有直接的引用类型,但可以通过传递指针来模拟引用传递。通过传递指向变量的指针,函数可以修改变量的值,而不是仅操作其副本。

#include <stdio.h>
// 通过指针模拟引用传递void modifyValue(int* ptr) { *ptr = 20; // 修改指针指向的值,相当于修改原始变量}
int main() { int x = 10; printf("Before: %d\n", x); // 输出 10 modifyValue(&x); // 传递 x 的地址 printf("After: %d\n", x); // 输出 20,说明 x 的值被修改 return 0;}

modifyValue 函数接受一个指向 int 类型的指针。

我们在调用 modifyValue 时传递了 x 的地址(&x),函数内部通过解引用该指针(*ptr)修改了 x 的值。因此,x 的值从 10 被修改成了 20,这就是通过指针实现的引用传递。其实还是上面例子更进一步的解读啦。

肯定这样做是有优点的!

  1. 修改原始数据:使用引用传递,函数可以直接修改传递给它的变量的值,而不仅仅是副本。这样可以在函数中执行更复杂的操作。

  2. 节省内存:传递指针而不是整个数据(如大结构体或大数组)可以减少内存的使用和数据的复制,尤其在处理大对象时,传递指针效率较高

  3. 提高性能:通过引用传递,避免了复制数据的开销,特别是在处理大量数据时,性能上有显著提升。

其实还有很多内容,但是不想写了,不过可以再研究一个内容!

指针是用于存储变量的内存地址的变量,这个内存地址就是变量在地址中的第一个位置吗?

内存地址并不表示“变量的第一个位置”,而是变量实际存储的位置

这个是答案,你说对了吗?

你先要知道什么是内存地址!

  1. 内存地址是指计算机内存中某个特定位置的地址,它是一个数字,表示存储数据的位置。

  2. 变量在内存中的存储方式取决于它的数据类型。例如,一个 int 类型的变量通常占用 4 字节(32 位系统上),而一个 char 类型的变量通常占用 1 字节。


当你声明一个变量时,操作系统为该变量分配一定的内存空间,变量的内存地址就是该空间的起始地址。

int a = 10;int* p = &a;
a 是一个 int 类型的变量,它存储在内存的某个位置(比如假设 a 被分配在地址 0x100)。
&a 返回的是 a 变量在内存中的地址,也就是指向 a 的内存地址,比如 0x100。
p 是一个指针变量,存储了 a 的内存地址(即 p = &a)。
但是我上面的问题其实是对了一半,至于为什么这样说,继续看!
  1. 对于简单类型(如 intchar 等),变量在内存中的位置是固定的。它在内存中占据从某个起始地址开始的一段连续空间。指针存储的是这段空间的起始地址。

  2. 对于数组等复杂类型,数组的内存地址是数组第一个元素的内存地址,但数组的所有元素是连续存储的。所以,数组的内存地址实际上就是数组第一个元素的地址。


int arr[3] = {1, 2, 3};int* p = arr;  // arr 是数组名,p 存储的是 arr[0] 的内存地址
数组 arr 中的元素是连续存储的。
数组 arr[0]、arr[1]、arr[2] 在内存中是顺序排列的,arr 本身是数组首元素 arr[0] 的地址。
p = arr 将指针 p 指向了数组 arr 的第一个元素 arr[0] 的地址。
指针存储的是变量或数组等数据结构的内存地址
内存地址表示变量或数组的起始位置
对于基本数据类型,内存地址是该变量在内存中的存储位置;而对于数组、结构体等数据结构,指针指向的是该数据结构的起始位置,元素或成员通常是连续存储的。
我觉得你一定学懂了!

再看看这个

这两行代码是 C 语言中的 函数指针类型定义(typedef)。

先看第一个函数:

这行代码定义了一个函数指针类型,该指针可以指向一个不接受任何参数并且没有返回值的函数。

  1. void:表示该函数没有返回值。

  2. (*func_ptr_t):这是函数指针的声明方式。它表示 func_ptr_t 是一个指向函数的指针。

  3. (void):表示该函数没有参数。即指向的函数不接受任何参数。

func_ptr_t 是一个新的类型名,它代表了一个指向无返回值、无参数的函数的指针类型。
使用 func_ptr_t 时,可以声明一个指向此类型函数的指针,并将其指向一个实际的符合此类型签名的函数。
void myFunction(void) {    // 函数实现}
int main() { func_ptr_t ptr = myFunction; // 使用 typedef 定义的指针类型 ptr(); // 调用 myFunction return 0;}

再看一个

这行代码定义了另一个函数指针类型,该指针可以指向一个接受一个 uint8_t 类型参数并且没有返回值的函数。
void:表示该函数没有返回值。
(*func_ptr_arg1_t):表示 func_ptr_arg1_t 是一个指向函数的指针。
(uint8_t u8Param):表示该函数接受一个类型为 uint8_t 的参数(通常 uint8_t 是无符号 8 位整数,即 unsigned char)。
func_ptr_arg1_t 是一个新的类型名,它代表了一个指向接受一个 uint8_t 类型参数且无返回值的函数的指针类型。
使用 func_ptr_arg1_t 时,可以声明一个指向此类型函数的指针,并将其指向一个符合此类型签名的函数。
void myFunctionWithParam(uint8_t param) {    // 函数实现    printf("Received parameter: %d\n", param);}
int main() { func_ptr_arg1_t ptr = myFunctionWithParam; // 使用 typedef 定义的指针类型 ptr(10); // 调用 myFunctionWithParam 并传递参数 10 return 0;}

那知道了这个东西,有什么用呢?

函数指针常被用作回调函数,让用户可以指定某些行为。在事件驱动编程中,程序在特定事件发生时调用用户定义的函数,而用户函数的地址通过函数指针传递。

#include <stdio.h>
void onEventCallback(void) { printf("Event triggered!\n");}
void registerCallback(func_ptr_t callback) { callback(); // 调用传递进来的函数}
int main() { registerCallback(onEventCallback); // 注册回调 return 0;}
这里 func_ptr_t 被用来注册一个回调函数,当事件发生时触发调用。可以解耦调用者和被调用者,提高灵活性。
使用函数指针作为参数或回调,可以极大地提高模块化设计的灵活性。设计通用的库函数时,可以将实现的具体细节委托给用户提供的函数。
#include <stdio.h>
void defaultHandler(uint8_t param) { printf("Default handler: %d\n", param);}
void runWithCustomHandler(func_ptr_arg1_t handler, uint8_t param) { if (handler) { handler(param); // 调用用户提供的处理函数 } else { defaultHandler(param); // 使用默认处理函数 }}
int main() { runWithCustomHandler(NULL, 5); // 使用默认处理函数 runWithCustomHandler(defaultHandler, 10); // 使用用户提供的函数 return 0;}

这里通过函数指针允许用户自定义处理逻辑。

void (*callback)(void);void (*handler)(uint8_t);

如果每次使用函数指针都显式声明。当函数指针的使用场景多时,会显得繁琐且不清晰。

通过 typedef 起别名,简化函数指针的声明和使用,让代码更易读。

云深之无迹
纵是相见,亦如不见,潇湘泪雨,执念何苦。
 最新文章