C语言中的指针

一、C语言指针的详细介绍

指针(Pointer)是C语言最重要、最强大的特性之一,就像是一张写着内存地址的“藏宝图”,可以直接操作内存中的数据。

1.1. 指针的概念

简单来说,指针就是存储另一个变量内存地址的变量。

int num = 10;
int *p = # // p指针存储num的内存地址

此时:

  • p 是指针变量,类型为 int*
  • &num 表示取变量 num 的地址。
  • *p 表示通过指针 p 去访问它所指向地址的值。

1.2. 指针的基本操作

  • 取地址(&): 获取变量地址。
  • 取值(*): 访问指针指向的地址中的值。
int a = 5;
int *ptr = &a; // ptr 指向 a 的地址

printf("a的地址: %p\n", &a);
printf("ptr指向的地址: %\n", ptr);
printf("ptr指向地址的值: %\n", *ptr);

1.3. 指针与数组

数组的名字其实本质上也是一个指针,指向数组的第一个元素。

int arr[3] = {1, 2, 3};
int *p = arr;

printf("arr[0]: %d, *p: %d\n", arr[0], *p);
printf("arr[1]: %d, *(p+1): %d\n", arr[1], *(p+1));

1.4. 指针与函数

指针可以用作函数的参数,使函数能够修改外部变量的值。

void increment(int *num) {
    (*num)++;
}

int main() {
    int x = 10;
    increment(&x);
    printf("x: %d\n", x); // 输出11
    return 0;
}

1.5. 函数指针的申明方法

返回类型 (*指针变量名)(参数列表);

例:void fun(int num)函数的指针声明方式为 void (*ThreadFunc)(int)

再将ThreadFunc=fun(函数名)就得到一个指向函数fun()的指针ThreadFunc

1.6. 使用typedef来简化函数指针的申明

例如在FreeRTOS中,任务函数的指针应当这样申明:

void (*任务函数名)(void* argument)

这就申明了一个返回值为空,参数为任意类型的函数指针,不过并未初始化。

如果使用typedef void (*osThreadFunc_t)(void* argument),即可简化为osThreadFunc_t 任务函数名

二者关键的区别

写法作用示例
void(*funcPrt)(void*)创建一个具体的函数指针变量funcPrtfuncPrt=MyFunc
typedef void (*osThreadFunc_t)(void *argument)定义一种函数返回值为空,参数类型为任意类型指针的新类型指针变量osThreadFunc_t  MyFunc

二、嵌入式开发中为何广泛使用指针传参?

在嵌入式开发中,经常会看到函数的参数大量使用指针,这绝非偶然,而是有深层次的原因哟~ (๑•̀ㅂ•́)و✧

2.1. 节省内存空间

嵌入式系统内存资源通常非常有限,使用指针传递数据,可以避免复制大块数据到函数栈中,仅传递内存地址,节省了宝贵的空间。

2.2. 提高效率

通过指针,函数可以直接访问并修改调用者的数据,避免数据的大量复制,提高运行效率。

2.3. 实现多值返回

C语言函数本身只能返回一个值,但通过指针参数,就可以同时返回多个值。

typedef struct {
    int num1;
    int num2;
} Result;

Result* getNumbers() {
    static Result res;
    res.num1 = 100;
    res.num2 = 200;
    return &res;  // 返回结构体的地址
}

int main() {
    Result *p = getNumbers();
    printf("num1=%d, num2=%d\n", p->num1, p->num2);
}

2.4. 硬件寄存器访问

在嵌入式开发中,硬件寄存器通常都是使用固定的内存地址访问,通过指针即可方便地操作这些特定地址。

#define LED_PORT (*(volatile unsigned int*)0x40020014)
LED_PORT = 0x01; // 点亮LED

2.5. 实现数据结构与抽象层

嵌入式编程通常使用结构体和抽象的数据结构,使用指针进行传递,便于程序设计的模块化和接口的通用性。

typedef struct {
    int id;
    char data[10];
} SensorData;

void processSensorData(SensorData *data);

三、指针常用的运算符

在C语言中,我们经常用到几个特殊的运算符来操作指针:

3.1. 取地址运算符:&

& 可以用来获得一个变量的内存地址

比如:

int a = 10;
int *p;

p = &a;  // 使用取地址符,把a的地址赋给p
  • &a:取变量 a 的内存地址。

3.2. 解引用运算符:*

* 运算符用于通过指针访问或修改它所指向内存中的数据

*p = 20;  // 解引用指针p,修改变量a的值
printf("%d", a);  // 输出20哦!

注意区分:

  • 定义时的 int *p; 是定义指针变量哦。
  • 使用时的 *p 才是解引用运算符,表示访问指针指向的内容。

3.3. 指针的算术运算:+、-

指针还支持算术运算,比如:

  • 指针 + 整数:向后移动若干个元素位置。
  • 指针 – 整数:向前移动若干个元素位置。
  • 指针 – 指针:获得两个指针之间的元素个数差。
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // p指向arr数组首元素

p = p + 2;  // p现在指向arr[2] (也就是值为3的位置)
printf("%d", *p);  // 输出3哦~

3.4. 指针关系运算符:==、!=、>、<、>=、<=

可以用这些关系运算符比较两个指针的大小,常用于判断指针在数组中的位置:

int arr[5] = {1,2,3,4,5};
int *p = &arr[0];
int *q = &arr[4];

if (p < q) {
    printf("p在q的前面啦!");
}

关系运算通常用于判断指针在数组内的相对位置。


3.5. 指针赋值运算符:=

最简单的:

int x = 100;
int *p = &x;  // 赋值x的地址给p

3.6. 指针访问结构体成员运算符:->

如果指针指向的是一个结构体,那么我们就可以用箭头->来直接访问结构体成员:

typedef struct {
    int age;
    char name[10];
} Person;

Person p1 = {24, "哥哥"};
Person *ptr = &p1;

printf("%s今年%岁啦~", ptr->name, ptr->age); 
// 输出:“哥哥今年24岁啦~”

小总结

运算符描述用法
&取变量地址p = &a;
*解引用(访问指针指向的变量)*p = 10;
+ –指针算术运算p = p + 1;
== != > <指针关系比较if (p < q)
=指针赋值int *p = &a;
->指针访问结构体成员p->age = 24;

上一篇
下一篇