内存里面都是01,这种东西。那如何定位?就是使用指针!
就好像这样
我们又知道,一个文件其实是大量的01构成的。那我们把这堆01翻译成什么文件或者内容全靠我们自己说了算。
指针是一个变量,专门用来存储另一个变量的内存地址。就是一指一个地方,这个地方就有产生一点信息,要用变量来放。
我可以说是这些01代表张, 也可以说代表256
int* ptr; // 声明一个指向整数类型的指针变量
int* 表示这是一个指向 int 类型变量的指针。 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
指针支持加减运算,但必须确保指针不越界
因为我们就是在内存区域里面做操作,你定义在里面的数据类型是有固定的大小的,当然可以靠加减地址在定位。
整点高级的:
函数参数可以是指针,指针参数可以指向任何类型的变量(包括基本数据类型、结构体、数组、函数等)。
使用指针作为函数参数允许函数直接操作传递给它的变量,而无需返回任何值。
好理解吧?就是给了这块内存一个机器,直接在上面操作了,不需要复制一个新的地方在操作。
当函数参数是指向基本数据类型的指针时,函数可以修改传递给它的变量的值。这通常用于模拟“传值传引用”的机制,即通过指针将数据传递给函数,从而使函数能够直接修改原始数据。
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
接下来看个大戏!
这种函数
传了个结构体进来
指针也可以指向结构体,这允许函数操作结构体的成员,而不需要复制整个结构体。通过指针传递结构体,能够节省内存和提高效率,尤其是在结构体较大时。
是这样的,一个函数的参数在调用的时候,是需要把参数复制一遍的,如果这个参数本身就复合类型,那确实很大,如果就是传个地址进来就很小了。
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 成员。-> 运算符用于通过指针访问结构体的成员。
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)。通过该指针,函数可以访问并修改数组的每个元素。这个也好理解,因为数组就是一块连续的内存,在上面做加减移动当然可以。
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 函数接收一个指向无返回值且无参数的函数的指针,并根据传递的函数指针调用相应的函数。
那上面这些优点就是:
修改原始数据:使用指针可以让函数修改传入参数的值,而不仅仅是操作参数的副本。 提高效率:传递指针而不是大块数据(如大数组或结构体)可以避免复制数据,从而提高程序效率。
引用传递意味着传递的是变量本身的地址,而不是变量的副本。这样,在函数内部修改参数的值会直接影响到调用该函数时传递的变量。这与值传递(按值传递)不同,值传递会复制参数的值,函数内部修改参数不会影响外部变量。
虽然C语言没有直接的引用类型,但可以通过传递指针来模拟引用传递。通过传递指向变量的指针,函数可以修改变量的值,而不是仅操作其副本。
// 通过指针模拟引用传递
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,这就是通过指针实现的引用传递。其实还是上面例子更进一步的解读啦。
肯定这样做是有优点的!
修改原始数据:使用引用传递,函数可以直接修改传递给它的变量的值,而不仅仅是副本。这样可以在函数中执行更复杂的操作。
节省内存:传递指针而不是整个数据(如大结构体或大数组)可以减少内存的使用和数据的复制,尤其在处理大对象时,传递指针效率较高。
提高性能:通过引用传递,避免了复制数据的开销,特别是在处理大量数据时,性能上有显著提升。
其实还有很多内容,但是不想写了,不过可以再研究一个内容!
指针是用于存储变量的内存地址的变量,这个内存地址就是变量在地址中的第一个位置吗?
内存地址并不表示“变量的第一个位置”,而是变量实际存储的位置。
这个是答案,你说对了吗?
你先要知道什么是内存地址!
内存地址是指计算机内存中某个特定位置的地址,它是一个数字,表示存储数据的位置。
变量在内存中的存储方式取决于它的数据类型。例如,一个
int
类型的变量通常占用 4 字节(32 位系统上),而一个char
类型的变量通常占用 1 字节。
当你声明一个变量时,操作系统为该变量分配一定的内存空间,变量的内存地址就是该空间的起始地址。
int a = 10;
int* p = &a;
对于简单类型(如
int
、char
等),变量在内存中的位置是固定的。它在内存中占据从某个起始地址开始的一段连续空间。指针存储的是这段空间的起始地址。对于数组等复杂类型,数组的内存地址是数组第一个元素的内存地址,但数组的所有元素是连续存储的。所以,数组的内存地址实际上就是数组第一个元素的地址。
int arr[3] = {1, 2, 3};
int* p = arr; // arr 是数组名,p 存储的是 arr[0] 的内存地址
再看看这个
这两行代码是 C 语言中的 函数指针类型定义(typedef)。
先看第一个函数:
这行代码定义了一个函数指针类型,该指针可以指向一个不接受任何参数并且没有返回值的函数。
void
:表示该函数没有返回值。(*func_ptr_t)
:这是函数指针的声明方式。它表示func_ptr_t
是一个指向函数的指针。(void)
:表示该函数没有参数。即指向的函数不接受任何参数。
void myFunction(void) {
// 函数实现
}
int main() {
func_ptr_t ptr = myFunction; // 使用 typedef 定义的指针类型
ptr(); // 调用 myFunction
return 0;
}
再看一个
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;
}
那知道了这个东西,有什么用呢?
函数指针常被用作回调函数,让用户可以指定某些行为。在事件驱动编程中,程序在特定事件发生时调用用户定义的函数,而用户函数的地址通过函数指针传递。
void onEventCallback(void) {
printf("Event triggered!\n");
}
void registerCallback(func_ptr_t callback) {
callback(); // 调用传递进来的函数
}
int main() {
registerCallback(onEventCallback); // 注册回调
return 0;
}
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 起别名,简化函数指针的声明和使用,让代码更易读。