初探C语言

前言

本文将详细介绍C语言中的指针基本概念,通过本文,读者能够更深入地理解指针的工作原理及其应用场景,进而在C语言的编程中游刃有余。

什么是指针?

指针是一个存储内存地址的变量。它指向某个变量在内存中的位置。


指针变量

通过&操作符取出变量的地址,将其存入一个变量,这个变量就是指针变量。

代码语言:javascript代码运行次数:0运行复制
#include <stdio.h>

int main() {
    int a = 10; // 在内存中为变量 a 分配空间
    int *p = &a; // 使用 & 操作符取 a 的地址,p 存储这个地址
    return 0;
}
  • 这里,a 占用4个字节的内存空间,p 存储的是 a 的内存地址。
  • p 是一个指针变量,指向 a 的内存地址。

指针类型

定义方式:
代码语言:javascript代码运行次数:0运行复制
type* pointer_name;
常见指针类型:
  • char*
  • int*
  • float*
  • double*

void*:它是一个通用指针类型,可以指向任意类型的地址,但不能直接进行指针运算。


指针类型的意义

指针的类型决定了指针移动的步长(即每次加减操作的单位大小)。

示例:指针加减整数
代码语言:javascript代码运行次数:0运行复制
#include <stdio.h>

int main() {
    int n = 10;
    char *pc = (char*)&n;
    int *pi = &n;
    
    printf("%p\n", &n);      // 输出 n 的地址
    printf("%p\n", pc);      // 输出 pc 指向的地址
    printf("%p\n", pc+1);    // char* 每次移动 1 个字节
    printf("%p\n", pi);      // 输出 pi 指向的地址
    printf("%p\n", pi+1);    // int* 每次移动 4 个字节
    return 0;
}
输出结果分析:
  • char* 步长为 1
  • int* 步长为 4
  • float* 步长为 4
  • double* 步长为 8
  • short* 步长为 2

指针的解引用

解引用操作符 * 用来通过指针访问存储在地址中的值。

代码语言:javascript代码运行次数:0运行复制
*pa 表示通过 pa 存储的地址,找到并访问该地址对应的内存内容。

例如:

代码语言:javascript代码运行次数:0运行复制
int a = 10;
int* pa = &a;
printf("%d\n", *pa);  // 输出 a 的值,10

野指针

定义:野指针是未初始化或指向非法内存的指针,可能导致程序崩溃或内存泄漏。

常见的野指针情况:
1. 指针未初始化:
代码语言:javascript代码运行次数:0运行复制
int main() {
    int *p;  // 未初始化指针
    *p = 20; // 使用未初始化的指针,结果是未定义行为
    return 0;
}
2. 指针越界访问:
代码语言:javascript代码运行次数:0运行复制
int main() {
    int *p;
    *p = 20; // 访问越界内存,可能导致程序崩溃
    return 0;
}
3. 指针指向已释放的内存:
代码语言:javascript代码运行次数:0运行复制
int* test() {
    int a = 10;
    return &a;  // 返回局部变量的地址,局部变量离开作用域后内存被释放
}

int main() {
    int* p = test();
    printf("%d\n", *p);  // 未定义行为
    return 0;
}

注意: 返回局部变量的地址是危险的,因为局部变量在函数结束时会被销毁。


野指针的规避方法

  • 避免未初始化指针: 初始化指针时,可以将其设为 NULL
代码语言:javascript代码运行次数:0运行复制
int* p = NULL;  // NULL 指针是一个特殊的指针值,表示指针未指向任何有效内存
  • 指针指向空间释放后立即将其置为 NULL
代码语言:javascript代码运行次数:0运行复制
free(p);
p = NULL;  // 防止指针悬挂
  • 避免返回局部变量的地址。

指针运算

指针减指针

当两个指针指向同一数组或内存块时,它们的差值表示它们之间的元素个数。

示例:计算字符串长度
代码语言:javascript代码运行次数:0运行复制
int my_strlen(char* str) {
    char* start = str;
    while (*str != '\0') {
        str++;
    }
    return str - start;  // 返回字符串的长度
}

int main() {
    char arr[] = "abcdef";
    int len = my_strlen(arr);
    printf("%d\n", len);  // 输出 6
    return 0;
}

二级指针(和更高层次指针)

二级指针的定义

二级指针(pointer to pointer)是指指向另一个指针的指针。简单来说,二级指针存储的是一级指针的地址。通过二级指针,可以间接地访问一级指针指向的内存地址,从而实现多级间接访问。

代码语言:javascript代码运行次数:0运行复制
type** pointer_name;

这里,type 是指针指向的变量类型,pointer_name 是二级指针的名称。

示例:
代码语言:javascript代码运行次数:0运行复制
#include <stdio.h>

int main() {
    int a = 10;       // 定义一个整数变量 a
    int *p = &a;      // 一级指针 p 存储 a 的地址
    int **pp = &p;    // 二级指针 pp 存储 p 的地址

    // 输出
    printf("a = %d\n", a);           // 输出 a 的值
    printf("*p = %d\n", *p);         // 通过一级指针 p 输出 a 的值
    printf("**pp = %d\n", **pp);     // 通过二级指针 pp 输出 a 的值

    return 0;
}

输出结果:

代码语言:javascript代码运行次数:0运行复制
a = 10
*p = 10
**pp = 10

解释:

  1. a 是一个普通变量。
  2. p 是一个一级指针,存储了变量 a 的地址。
  3. pp 是一个二级指针,存储了一级指针 p 的地址。

通过二级指针 pp,我们可以间接地访问 a 的值。首先通过 **pp 获取 p 指向的地址,再通过 *p 获取该地址存储的值,即 a

应用场景:
  • 动态内存分配: 二级指针常用于动态内存分配时,特别是在函数中修改指针的指向。
  • 多维数组: 二级指针可以用来处理动态的二维数组。
  • 修改指针值: 通过二级指针可以修改一级指针的值,从而改变其指向的内存位置。
二级指针和三级指针
代码语言:javascript代码运行次数:0运行复制
int main() {
    int a = 0;
    int* pa = &a;    // 一级指针
    int** ppa = &pa; // 二级指针
    int*** pppa = &ppa; // 三级指针
    return 0;
}

总结

指针是 C 语言中强大而复杂的工具,理解指针的使用、类型以及相关操作是掌握 C 语言的关键。正确使用指针能提高程序效率和灵活性,但不当的使用则可能导致难以发现的错误,甚至程序崩溃。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-01-13,如有侵权请联系 cloudcommunity@tencent 删除变量程序内存指针存储