单片机之从C语言基础到专家编程 - 4 C语言基础 - 4.14指针

单片机之从C语言基础到专家编程 - 4 C语言基础 - 4.14指针

文章目录

  • 单片机之从C语言基础到专家编程 - 4 C语言基础 - 4.14指针
      • 4.14 指针
        • 1 指针的概念
        • 2 指针的声明与初始化
        • 3 指针的算术运算
        • 4 NULL指针
          • 1)定义和使用
          • 2)NULL指针的用途
          • 3)常见误区
          • 4)示例代码
          • 5)总结
        • 5 字符串操作
          • 1)字符串的定义与初始化
          • 2)常用字符串操作函数
          • 3)字符串常量与指针
        • 6 void*指针
          • 1)类型转换
          • 2)实现通用数据结构
          • 3)通用函数参数
          • 4)回调函数
          • 5)使用 `void*` 指针的注意事项
        • 7 指针与数组的关系
          • 1)数组名与指针
          • 2)通过指针访问数组元素
          • 3)多维数组与指针

4.14 指针

1 指针的概念

指针是一个变量,其值是另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。当你声明一个指针时,你实际上是在创建一个存储内存地址的变量,而不是存储数据本身。

指针的基本语法如下:

int *p;  // 声明一个指向整数类型的指针 p

在这个例子中,p 是一个指向 int 类型变量的指针。你可以将一个变量的地址赋给 p,然后通过 p 访问或修改该变量的值。

例如:

int value = 10;
int *p = &value;  // 将变量 value 的地址赋给指针 p

现在,p 指向 value 的内存地址,你可以通过 *p 来访问或修改 value 的值。

指针是用来存储变量地址的变量。简单来说,指针指向存储在内存中的一个数据值。

我们已知变量是计算机为数据开辟的存储空间,也就是一个个以字节为单位组成的小盒子,变量名就是这个小盒子独一无二的标识,那么变量名和指针有什么关系呢,变量名会不会占用空间呢?

  • 变量名:在C语言中,变量名是程序员用来标识内存中某个特定位置的符号。变量名本身并不占用实际的存储空间,它只是编译器用来定位和访问内存地址的一个标签。换句话说,变量名在编译过程中被解析为一个内存地址,编译完成后,在机器码中实际上并不存在变量名,只有内存地址。

  • 指针:指针是一个特殊的变量,它存储的是另一个变量的内存地址。通过指针,我们可以间接访问和操作该内存地址上的数据。指针在内存中占用存储空间,因为它本质上是一个存储地址的变量。指针的类型决定了它所指向的内存位置的数据类型。

  • 变量名不占用存储空间

如上所述,变量名是编译器用来识别和访问内存位置的符号标识,它在源代码中存在,但在编译后的机器码中并不存在。因此,变量名本身不占用实际的存储空间。

  • 指针占用存储空间

指针是一个变量,因此它占用存储空间。指针变量存储的是一个内存地址,其大小取决于目标平台的地址长度。例如,在32位系统上,指针占用4个字节,在64位系统上,指针占用8个字节。

  • 示例说明
#include 

int main() 
{
    int age = 25; // 声明一个整数变量age,并赋值为25
    int *p = &age; // 声明一个指向整数的指针变量p,并将其指向age的地址

    printf("Age: %d\n", age); // 打印变量age的值
    printf("Pointer p points to address: %p\n", (void*)p); // 打印指针p指向的地址
    printf("Value at the address pointed by p: %d\n", *p); // 打印指针p指向的地址上的值
    return 0;
}

在这个例子中:

  • int age = 25;:声明一个整数变量age,编译器为age分配内存空间并存储值25。

  • int *p = &age;:声明一个指针变量p,并将p指向age的内存地址。指针变量p在内存中占用存储空间,用来存储age的地址。

  • 内存布局

假设age的地址是1000,在32位系统上,内存布局如下:

代码地址     内容
1000     25 (age的值)
2000     1000 (指针p存储的地址)
  • age占用4个字节来存储整数值25。
  • p占用4个字节来存储地址1000(在32位系统上)。

总结来说,变量名是编译器的符号标识,不占用实际的存储空间;而指针是存储内存地址的变量,占用存储空间。通过指针,我们可以间接访问和操作变量的内存地址,从而实现更灵活的内存管理和数据操作。

2 指针的声明与初始化
  • 指针声明

在C语言中,声明一个指针的语法是:

int *ptr;

这里,int *ptr表示ptr是一个指向整数类型的指针。

  • 获取变量地址

要获取一个变量的地址,可以使用取地址符号&。例如:

int a = 10;
int *ptr;
ptr = &a;

在这个例子中,变量a的值是10,而ptr是一个指向a的指针。通过ptr = &a;,我们将a的地址赋值给了指针ptr

  • 使用指针访问变量

一旦指针指向了一个变量,就可以通过指针来访问这个变量的值。可以使用解引用运算符*来获取指针指向的值。例如:

int a = 10;
int *ptr;
ptr = &a;

printf("a的值是: %d\n", *ptr);  // 输出: a的值是: 10

在这个例子中,*ptr表示指针ptr指向的变量的值,因此*ptr等于a的值,也就是10。

3 指针的算术运算
  • 指针可以进行算术运算,如加减操作。
  • 指针的加减操作以指针类型的大小为单位。
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // p指向数组的第一个元素

p++; // p现在指向数组的第二个元素
printf("%d\n", *p); // 输出2

p--; // p现在指向数组的第一个元素
printf("%d\n", *p); // 输出1
  • 指针与数组的关系

    • 数组名是指向数组第一个元素的指针常量。
    • 可以通过指针访问数组的元素。
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr; // p指向数组的第一个元素
    
    for (int i = 0; i < 5; i++) 
    {
        printf("%d ", *(p + i)); // 通过指针访问数组元素
    }
    
4 NULL指针

在C语言中,NULL指针是一个特殊的指针常量,用于表示一个指向“无效”或“空”地址的指针。NULL指针在程序中具有重要的用途,包括表示未初始化的指针、指向未分配的内存、指向结束标记等。以下是关于NULL指针的详细解释:

1)定义和使用

在C标准库中,NULL通常定义如下:

#define NULL ((void *)0)

它表示一个值为0的指针常量。NULL指针可以用于任何类型的指针变量:

int *p = NULL;     // 指向int类型的NULL指针
char *str = NULL;  // 指向char类型的NULL指针
2)NULL指针的用途
  1. 初始化指针:

    • 在声明指针变量时,如果不立即赋予其有效地址,可以将其初始化为NULL,以防止其指向随机的内存地址。
    int *ptr = NULL;
    
  2. 指针检查:

    • 使用NULL指针可以检查指针是否已被赋值,从而避免使用未初始化指针引发的错误。
    if (ptr == NULL) 
    {
        // 指针未初始化或未分配内存
    }
    
  3. 内存分配失败检查:

    • 在动态内存分配时,如果内存分配失败,malloc或calloc函数将返回NULL。可以通过检查返回值来判断分配是否成功。
    int *arr = (int *)malloc(10 * sizeof(int));
    if (arr == NULL) 
    {
        // 内存分配失败
    }
    
  4. 结束标记:

    • 在处理字符串或链表等数据结构时,NULL指针常用作结束标记。
    char *names[] = {"Alice", "Bob", "Charlie", NULL}; // NULL作为数组结束标记
    
3)常见误区
  1. NULL和0的区别:

    • NULL虽然在底层通常定义为0,但在语义上表示一个空指针。而直接使用0来表示空指针在某些情况下可能会导致混淆。
    int *ptr = 0;    // 合法,但建议使用NULL
    
  2. NULL和’\0’的区别:

    • '\0’是字符常量,表示字符串结束符,而NULL是指针常量,两者在用法和含义上有明显区别。
    char *str = NULL; // 空指针
    char ch = '\0';   // 空字符
    
4)示例代码

以下是一个使用NULL指针的示例程序:

#include 
#include 

int main() 
{
    int *ptr = NULL; // 初始化指针为NULL

    ptr = (int *)malloc(sizeof(int));
    if (ptr == NULL) 
    {
        printf("内存分配失败\n");
        return 1; // 退出程序
    }

    *ptr = 42; // 使用动态分配的内存
    printf("值: %d\n", *ptr);

    free(ptr); // 释放内存
    ptr = NULL; // 将指针重新置为NULL,避免悬空指针

    return 0;
}

在这个示例中,我们展示了如何初始化一个指针为NULL,如何检查内存分配是否成功,以及如何在释放内存后将指针重新置为NULL,以防止其成为悬空指针。

5)总结

NULL指针是C语言中一个重要的概念,用于表示无效或空地址。正确使用NULL指针可以提高程序的健壮性,避免因未初始化指针或无效内存访问引发的错误。在编写C程序时,习惯性地将未使用的指针初始化为NULL,并在释放内存后将指针重新置为NULL,是一种良好的编程实践。

5 字符串操作

字符串是以空字符('\0')结尾的字符数组。C语言提供了一些常用的字符串操作函数。

1)字符串的定义与初始化
  • 字符串可以用字符数组或指向字符的指针来定义。
char str1[] = "Hello"; // 用字符数组定义字符串
char *str2 = "World";  // 用指针定义字符串
2)常用字符串操作函数
  • strlen:计算字符串的长度(不包括结尾的空字符)。
  • strcpy:将一个字符串复制到另一个字符串。
  • strcat:将一个字符串连接到另一个字符串的末尾。
  • strcmp:比较两个字符串的大小。
char str1[20] = "Hello";
char str2[] = "World";

// 计算字符串长度
int len = strlen(str1);
printf("Length of str1: %d\n", len);

// 复制字符串
strcpy(str1, str2);
printf("str1 after strcpy: %s\n", str1);

// 连接字符串
strcat(str1, "!");
printf("str1 after strcat: %s\n", str1);

// 比较字符串
int cmp = strcmp(str1, str2);
if (cmp == 0) 
{
    printf("str1 and str2 are equal\n");
} 
else 
{
    printf("str1 and str2 are not equal\n");
}
3)字符串常量与指针
  • 字符串常量是只读的字符数组,使用双引号定义。
  • 指针可以指向字符串常量,但不能修改字符串常量的内容。
char *str = "Hello, World!";
printf("%s\n", str); // 输出字符串常量
// str[0] = 'h'; // 错误:不能修改字符串常量的内容
6 void*指针

void* 指针是C语言中的一种特殊类型的指针,它可以指向任何数据类型的内存地址,但自身不具备具体的数据类型信息。void* 指针通常用于通用数据处理、数据结构实现或函数参数传递等场景。以下是对 void* 指针及其用法的详细介绍:

  • 什么是 void* 指针?

void* 指针是C语言中的通用指针类型,可以存储任意类型的内存地址。由于它没有具体的数据类型,void* 指针无法直接解引用(访问指向的数据),需要先转换为具体类型的指针。

例如:

int a = 10;
void *p = &a;  // p 可以指向任意类型的数据,此处指向一个整型变量
  • 为什么要使用 void* 指针?

    使用 void* 指针有以下几个主要原因:

    • 通用性和灵活性: void* 指针提供了一种通用方式来处理不同类型的数据。这在编写需要处理不同数据类型的通用代码时非常有用,例如通用的排序函数、数据结构(如链表、栈、队列)等。
    • 实现数据抽象: 在实现数据结构或模块化设计时,使用 void* 指针可以隐藏具体的数据类型,实现数据抽象。这对于编写模块化、可扩展的代码非常有帮助。
    • 函数参数传递: 当函数需要接受不同类型的参数时,可以使用 void* 指针来传递这些参数,然后在函数内部进行类型转换。例如,在回调函数或库函数中经常会看到 void* 指针的使用。
  • void* 指针的用法

void* 指针的常见用法包括以下几个方面:

1)类型转换

由于 void* 指针没有类型信息,所以在使用它之前需要先将其转换为具体类型的指针。可以通过显式类型转换(type casting)来实现。

int a = 10;
void *p = &a;  // 将 p 指向整型变量 a
int *intPtr = (int*)p;  // 将 void* 指针转换为 int* 指针
printf("%d\n", *intPtr);  // 输出 10
2)实现通用数据结构

在实现通用数据结构(如链表、栈、队列)时,void* 指针非常有用,因为它可以指向任何类型的数据。这使得数据结构能够存储不同类型的数据,而不需要针对每种类型单独实现。

例如,一个简单的链表节点定义:

struct Node 
{
    void *data;  // 使用 void* 指针来存储任意类型的数据
    struct Node *next;
};
3)通用函数参数

void* 指针常用于函数参数,以实现对不同类型数据的处理。例如,C标准库中的 qsort 函数就是一个例子,它使用 void* 指针来实现通用排序功能。

int compare(const void *a, const void *b) 
{
    return (*(int*)a - *(int*)b);  // 将 void* 转换为 int* 后再进行比较
}

int main() 
{
    int arr[] = {5, 3, 2, 4, 1};
    qsort(arr, 5, sizeof(int), compare);  // 使用 void* 指针进行通用排序
    for(int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}
4)回调函数

在嵌入式系统中,回调函数常用于事件驱动编程。void* 指针可以用来传递任意类型的上下文数据给回调函数。

void callback(void *context) 
{
    int *value = (int*)context;
    printf("Callback value: %d\n", *value);
}

int main() 
{
    int data = 42;
    callback(&data);  // 将数据传递给回调函数
    return 0;
}
5)使用 void* 指针的注意事项

虽然 void* 指针非常灵活,但在使用时需要特别注意以下几点:

  • 类型安全: 由于 void* 指针没有类型信息,错误的类型转换可能导致运行时错误或数据损坏。在使用 void* 指针时,要确保转换的类型与实际数据类型匹配。
  • 解引用限制: 不能直接解引用 void* 指针,必须先将其转换为具体类型的指针。
  • 内存管理: 使用 void* 指针时要特别注意内存管理,尤其是在动态内存分配和释放时,确保不发生内存泄漏或非法访问。
7 指针与数组的关系

指针和数组在C语言中有紧密的关系,可以相互替换使用。

1)数组名与指针
  • 数组名是指向数组第一个元素的指针常量,但不能改变数组名的值。
  • 可以通过数组名和指针两种方式访问数组元素。
int arr[5] = {1, 2, 3, 4, 5};
printf("%d\n", arr[0]); // 使用数组名访问元素
printf("%d\n", *arr);   // 使用指针访问元素
2)通过指针访问数组元素
  • 使用指针可以方便地遍历和操作数组元素。
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;

for (int i = 0; i < 5; i++) 
{
    printf("%d ", *(p + i)); // 通过指针访问数组元素
}
3)多维数组与指针
  • 多维数组可以看作是数组的数组,可以使用指针访问多维数组的元素。
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // p是指向包含3个int类型元素的数组的指针

for (int i = 0; i < 2; i++) 
{
    for (int j = 0; j < 3; j++) 
    {
        printf("%d ", *(*(p + i) + j)); // 通过指针访问多维数组元素
    }
}

你可能感兴趣的:(单片机之从C语言基础到专家编程,单片机,c语言,嵌入式硬件)