c语言paragram

99ANYc3cd6
预计阅读时长 21 分钟
位置: 首页 C语言 正文

"Paragram" 应该是 "Pointer" 的拼写错误,指针是 C 语言中最核心、最强大,也最容易让初学者感到困惑的特性,理解了指针,你才算真正入门了 C 语言。

c语言paragram
(图片来源网络,侵删)

指针是什么?

指针就是一个变量,它存储的不是普通的数据(如 int, char),而是另一个变量的内存地址。

你可以把计算机的内存想象成一排排的储物柜,每个储物柜都有一个唯一的编号(地址),你可以在储物柜里放东西(数据),也可以通过储物柜的编号(地址)来找到它。

  • 普通变量:相当于直接把东西放进某个储物柜,并记住柜子里的内容。int a = 10;,你创建了一个名为 a 的柜子,里面放了数字 10
  • 指针变量:相当于拿了一个小本子,用来记录某个储物柜的编号。int *p = &a;,你创建了一个名为 p 的小本子,在上面记下了 a 这个储物柜的地址。

为什么需要指针?

  1. 间接访问:通过指针可以间接地访问和修改其他变量的值。
  2. 高效传递大数据:函数传参时,传递指针(地址)比传递整个大数据结构(如大数组、结构体)要快得多,节省内存。
  3. 动态内存管理:在程序运行时动态地分配和释放内存(如 malloc, free),这是创建灵活、高效程序的基础。
  4. 复杂数据结构:是实现链表、树、图等复杂数据结构的关键。

指针的核心概念与语法

1 & (取地址运算符)

这个符号用于获取一个变量在内存中的地址。

c语言paragram
(图片来源网络,侵删)
int a = 10;
int *p; // 声明一个指针变量 p
p = &a; // 将变量 a 的地址赋值给指针 p
  • a 的值是 10
  • &a 是变量 a 的内存地址。
  • p 存储的就是 &a 这个地址。

2 (解引用/间接寻址运算符)

这个符号用在指针变量前,表示“访问该指针指向地址处的值”。

int a = 10;
int *p = &a;
// 下面两种方式都能得到 a 的值
printf("a 的值是: %d\n", a);         // 直接访问,输出 10
printf("p 指向的值是: %d\n", *p);    // 通过指针 p 间接访问,输出 10
// 通过指针修改 a 的值
*p = 20;
printf("修改后 a 的值是: %d\n", a);  // 输出 20
  • *pa 在这里指向的是同一个内存空间,所以修改 *p 就等于修改 a

3 指针的声明

声明指针的语法是:数据类型 *指针名;

  • int *p;:声明一个指向 int 类型数据的指针,称为 int 指针。
  • char *pc;:声明一个指向 char 类型数据的指针。
  • double *pd;:声明一个指向 double 类型数据的指针。

注意: 在声明中和在使用中的含义不同。

  • 在声明时int *p; 中的 表示 p 是一个指针。
  • 在使用时*p; 中的 是一个运算符,表示解引用。

4 NULL 指针

一个指针在声明后,如果没有被初始化,它会指向一个随机的、未知的内存地址,直接使用这样的指针(如 *p = 5;)会导致程序崩溃或不可预测的行为。

c语言paragram
(图片来源网络,侵删)

一个好的习惯是:声明指针后,立即将其初始化为 NULLNULL 是一个预定义的常量,表示“空指针”,它不指向任何有效的内存地址。

int *p = NULL;
// 在使用 p 之前,最好先检查它是否为 NULL
if (p != NULL) {
    *p = 100;
}

指针与数组的关系

在 C 语言中,数组名和指针的关系非常紧密,数组名 arr 会“退化”为其第一个元素的地址。

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 等价于 int *p = &arr[0];
// 通过指针遍历数组
for (int i = 0; i < 5; i++) {
    printf("arr[%d] = %d\n", i, *(p + i));
    // *(p + i) 等价于 p[i],也等价于 arr[i]
}
  • arr[i]*(arr + i) 是完全等价的。
  • p[i]*(p + i) 也是完全等价的。

数组指针 vs. 指针数组

  • int (*p)[5];:这是一个数组指针,它指向一个包含 5 个 int 元素的数组。
  • int *p[5];:这是一个指针数组,它是一个包含 5 个 int 指针的数组。

前者用得较少,后者比较常见。


指针与函数

1 指针作为函数参数

当函数需要修改外部变量的值,或者需要传递一个很大的数据结构时,使用指针作为参数非常高效。

示例1:交换两个变量的值

#include <stdio.h>
// 如果没有使用指针,函数内部的交换不会影响主函数的变量
// void swap(int a, int b) { ... } // 错误示例
// 使用指针,函数可以访问并修改主函数中的变量
void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}
int main() {
    int a = 10, b = 20;
    printf("交换前: a = %d, b = %d\n", a, b);
    swap(&a, &b); // 传递 a 和 b 的地址
    printf("交换后: a = %d, b = %d\n", a, b);
    return 0;
}

示例2:传递数组给函数 当把数组名传给函数时,实际上传递的是数组首元素的地址,这使得函数可以修改原数组的内容。

#include <stdio.h>
void print_array(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
void modify_array(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2; // 直接修改原数组
    }
}
int main() {
    int my_arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(my_arr) / sizeof(my_arr[0]);
    printf("原始数组: ");
    print_array(my_arr, size);
    modify_array(my_arr, size);
    printf("修改后数组: ");
    print_array(my_arr, size);
    return 0;
}

2 函数指针

函数指针是一个指向函数的指针,它存储的是函数的入口地址,这使得我们可以将函数作为参数传递给其他函数,或者实现回调机制。

#include <stdio.h>
// 定义两个函数
int add(int a, int b) {
    return a + b;
}
int subtract(int a, int b) {
    return a - b;
}
// 一个函数,它接受两个整数和一个函数指针作为参数
int calculate(int x, int y, int (*operation)(int, int)) {
    return operation(x, y);
}
int main() {
    int result;
    // 将 add 函数的地址传递给 calculate
    result = calculate(10, 5, add);
    printf("10 + 5 = %d\n", result); // 输出 15
    // 将 subtract 函数的地址传递给 calculate
    result = calculate(10, 5, subtract);
    printf("10 - 5 = %d\n", result); // 输出 5
    return 0;
}

动态内存分配

使用指针可以在程序运行时向操作系统申请内存,并在使用完毕后释放,这主要通过以下四个函数实现:

  • malloc(size_t size):分配 size 字节的内存,返回一个指向这块内存的指针,内存是未初始化的。
  • calloc(size_t num, size_t size):分配 num * size 字节的内存,并将所有位初始化为 0。
  • realloc(void *ptr, size_t size):重新调整之前分配的内存块的大小。
  • free(void *ptr):释放由 malloc, calloc, realloc 分配的内存。

必须记得 free!否则会导致内存泄漏**。

#include <stdio.h>
#include <stdlib.h> // 包含 malloc 和 free 的头文件
int main() {
    int n;
    printf("请输入要存储的数字个数: ");
    scanf("%d", &n);
    // 1. 分配内存
    int *ptr = (int *)malloc(n * sizeof(int));
    // 使用 (int *) 是为了将 malloc 返回的 void* 指针转换为 int* 指针
    // 2. 检查内存是否分配成功
    if (ptr == NULL) {
        printf("内存分配失败!\n");
        return 1; // 异常退出
    }
    // 3. 使用内存
    printf("请输入 %d 个数字:\n", n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &ptr[i]);
    }
    printf("你输入的数字是: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");
    // 4. 释放内存
    free(ptr);
    ptr = NULL; // 好习惯:将指针置为 NULL,防止成为野指针
    return 0;
}

总结与最佳实践

概念 符号/语法 作用 示例
声明指针 int *p; 声明一个指向 int 类型的指针变量。 int *p = NULL;
取地址 & 获取一个变量的内存地址。 p = &a;
解引用 访问指针指向地址处的值。 printf("%d", *p);
指针与数组 arr[i] == *(arr+i) 数组名可以当作指针使用。 int *p = arr;
指针参数 void func(int *x) 允许函数修改外部变量或高效传递数据。 swap(&a, &b);
函数指针 int (*p)(int, int) 指向一个函数,可以用于回调。 p = add;
动态内存 malloc, free 在运行时分配和释放内存。 int *p = malloc(...); free(p);

最佳实践:

  1. 初始化指针:始终初始化指针,如果暂时没有指向,就设为 NULL
  2. 检查 malloc 返回值malloc 可能会失败,返回 NULL,使用前必须检查。
  3. 记得 free:使用 malloc 分配的内存,一定要用 free 释放,避免内存泄漏。
  4. 将指针置为 NULLfree 一个指针后,最好立即将其设为 NULL,防止成为“野指针”(dangling pointer)。
  5. 避免悬垂指针:不要使用指向已释放内存的指针。

指针是 C 语言的灵魂,虽然一开始很难,但只要多加练习,理解了其“地址”和“间接访问”的本质,你就会发现它的强大和优雅。

-- 展开阅读全文 --
头像
织梦上一篇英文是什么?
« 上一篇 04-18
织梦镜像服务器列表有哪些?
下一篇 » 04-18

相关文章

取消
微信二维码
支付宝二维码

目录[+]