7. 指针基础.md 7.8 KB

7 指针基础

7.1 完整概念讲解

内存地址概念

内存是一个线性的字节数组,每个字节都有唯一的地址。程序运行时,变量存储在内存中,编译器为每个变量分配内存空间。指针就是存储内存地址的变量,通过指针可以直接访问和操作内存。

指针变量定义与初始化

int *p;          // 声明一个指向int类型变量的指针
int a = 10;
p = &a;          // 初始化:将a的地址赋值给p
int *q = &a;     // 声明并初始化

解引用(*)和取地址(&)

  • & 取地址运算符:获取变量的内存地址
  • * 解引用运算符:通过地址访问对应的内存值

指针大小

  • 32位系统:指针大小为4字节(可寻址2^32 = 4GB内存)
  • 64位系统:指针大小为8字节(可寻址2^64内存)
  • 指针大小与指向的数据类型无关,只与系统位数有关

NULL指针

  • NULL是宏定义,通常为 (void*)0
  • 表示指针不指向任何有效内存
  • 使用前应检查指针是否为NULL

void指针

  • void* 是通用指针类型,可以指向任意类型的数据
  • 不能直接解引用,必须先转换为具体类型指针
  • 不能进行指针算术运算(C标准规定)

指针赋值规则

  • 指针类型必须匹配(或使用void*中转)
  • 可以将一个指针赋值给另一个指针
  • 可以将NULL或0赋值给指针
  • 不同类型的指针赋值需要显式类型转换

7.2 核心API/语法

指针相关运算符

运算符 名称 作用 示例
& 取地址 获取变量地址 &a
* 解引用 通过地址访问值 *p
-> 箭头 通过指针访问成员 p->member

指针声明语法

int *p;           // 指向int的指针
int **pp;         // 指向int指针的指针(二级指针)
int *arr[10];     // 指针数组:包含10个int指针的数组
int (*p)[10];     // 数组指针:指向包含10个int的数组的指针

内存操作函数

#include <stdlib.h>
void *malloc(size_t size);      // 分配内存
void *calloc(size_t n, size_t size);  // 分配并清零
void *realloc(void *ptr, size_t new_size);  // 重新分配
void free(void *ptr);           // 释放内存

7.3 代码示例(完整可编译,附gcc命令)

示例1:基本指针操作

#include <stdio.h>

int main() {
    int a = 42;
    int *p = &a;

    printf("变量a的值: %d\n", a);
    printf("变量a的地址: %p\n", (void*)&a);
    printf("指针p存储的地址: %p\n", (void*)p);
    printf("通过指针解引用访问的值: %d\n", *p);

    // 通过指针修改变量值
    *p = 100;
    printf("修改后a的值: %d\n", a);

    // 指针大小
    printf("指针大小: %zu字节\n", sizeof(p));
    printf("int大小: %zu字节\n", sizeof(int));

    return 0;
}

编译命令:gcc -o pointer_basics pointer_basics.c

示例2:NULL指针和void指针

#include <stdio.h>
#include <stdlib.h>

int main() {
    // NULL指针
    int *p = NULL;
    if (p != NULL) {
        printf("p指向的值: %d\n", *p);
    } else {
        printf("p是空指针,不能解引用\n");
    }

    // void指针
    int a = 10;
    double b = 3.14;
    void *vp;

    vp = &a;
    printf("通过void*访问int: %d\n", *(int*)vp);

    vp = &b;
    printf("通过void*访问double: %f\n", *(double*)vp);

    // 动态内存分配中的void*
    int *arr = (int*)malloc(5 * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
    }

    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    free(arr);
    return 0;
}

编译命令:gcc -o pointer_types pointer_types.c

示例3:指针赋值和类型匹配

#include <stdio.h>

int main() {
    int a = 100;
    int *p = &a;

    // 类型匹配赋值
    int *q = p;  // 正确:相同类型指针
    printf("q = %d\n", *q);

    // void指针中转
    void *vp = p;      // 正确:任何类型指针都可以赋给void*
    int *r = (int*)vp; // 正确:void*可以赋给任何类型指针
    printf("r = %d\n", *r);

    // 错误的类型赋值(需要强制转换)
    // int *s = (double*)&a;  // 警告:类型不匹配

    // 指针与0
    int *t = 0;      // 正确:0可以赋给指针,表示NULL
    // int *u = 100;  // 错误:非零整数不能赋给指针

    printf("t = %p\n", (void*)t);

    return 0;
}

编译命令:gcc -o pointer_assignment pointer_assignment.c

7.4 注意事项与易错点

野指针

  • 未初始化的指针,指向随机内存地址
  • 危害:可能导致程序崩溃或数据损坏
  • 预防:指针声明后立即初始化

指针类型不匹配

  • 不同类型指针直接赋值可能导致未定义行为
  • 强制类型转换可以绕过编译器检查,但可能引发运行时错误

NULL指针解引用

  • 解引用NULL指针会导致段错误(Segmentation Fault)
  • 使用指针前应检查是否为NULL

指针与数组混淆

  • 数组名在大多数情况下退化为指针,但有例外(sizeof和&操作符)
  • 指针和数组不能完全互换

内存管理

  • malloc/free必须配对使用
  • 重复释放(double free)会导致未定义行为
  • 释放后继续使用指针(悬空指针)是危险的

指针算术

  • 指针加减运算以指向类型的大小为单位
  • 不同数组间的指针算术是未定义行为

7.5 面试要点(3-5个Q&A)

Q1: 指针和数组有什么区别? A: 数组是连续内存块,指针是存储地址的变量。数组名在大多数情况下退化为首元素指针,但sizeof和&操作符对数组有特殊行为。数组大小固定,指针可以指向动态分配的内存。

Q2: 什么是野指针?如何避免? A: 野指针是未初始化或指向已释放内存的指针。避免方法:1) 指针声明时初始化为NULL或有效地址;2) 释放内存后将指针设为NULL;3) 使用前检查指针有效性。

Q3: NULL指针和void指针有什么区别? A: NULL指针是特殊的空指针,表示不指向任何内存;void*是通用指针,可以指向任意类型数据,但不能直接解引用。NULL指针主要用于错误检查,void*主要用于泛型编程。

Q4: 为什么指针大小与数据类型无关? A: 指针存储的是内存地址,地址的长度由系统位数决定(32位系统4字节,64位系统8字节)。不同数据类型的指针只是指向的内存大小不同,但存储地址的指针本身大小相同。

Q5: 指针赋值时需要注意什么? A: 1) 类型匹配:相同类型指针可以直接赋值;2) void*可以与其他类型指针相互赋值;3) 不同类型指针赋值需要强制转换;4) 只有0或NULL可以赋给指针表示空指针;5) 不能将非地址整数值赋给指针。

7.6 完整示例程序

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 1. 基本指针操作
    int a = 10;
    int *p = &a;
    printf("a = %d, *p = %d\n", a, *p);

    // 2. 通过指针修改值
    *p = 20;
    printf("修改后: a = %d\n", a);

    // 3. 指针大小
    printf("指针大小: %zu字节\n", sizeof(p));

    // 4. NULL指针
    int *np = NULL;
    if (np == NULL) {
        printf("np是空指针\n");
    }

    // 5. void指针
    void *vp = &a;
    printf("通过void*访问: %d\n", *(int*)vp);

    // 6. 动态内存分配
    int *arr = (int*)malloc(3 * sizeof(int));
    if (arr != NULL) {
        arr[0] = 100;
        arr[1] = 200;
        arr[2] = 300;

        for (int i = 0; i < 3; i++) {
            printf("arr[%d] = %d\n", i, arr[i]);
        }
        free(arr);
    }

    return 0;
}

编译命令:gcc -o pointer_demo pointer_demo.c