【C 语言进阶必学】指针从入门到精通:吃透地址、类型与数组(超详细笔记整理)

一、先搞懂:指针的本质 —— 内存地址的 “代言人”

在学指针之前,必须先理解计算机内存的底层逻辑,这是搞懂指针的核心:

  1. 内存的基本单位:计算机内存以「字节(Byte)」为最小存储单元,1Byte=8bit(位),每个 bit 可存储 0 或 1;内存容量换算:1TB=1024GB,1GB=1024MB,1MB=1024KB,1KB=1024Byte

  2. 内存地址的分配规则

    • X86(32 位)计算机有 32 根地址线,每根地址线的电荷状态(0/1)组合成唯一的 32 位二进制编号,对应内存中一个字节的地址,总寻址空间为 232Byte=4GB;
    • X64(64 位)计算机有 64 根地址线,总寻址空间为 264Byte(理论值);
    • 内存中每一个字节都有唯一的地址编号,指针的本质就是 “保存这个地址编号的变量”
  3. 核心结论

    • 任何一片内存空间(比如int a)的地址,都是它第一个字节的地址
    • 指针变量的作用:存储某块内存的首字节地址,通过这个地址可以访问对应内存的值
二、指针的基础语法:定义、取地址、解引用
1. 指针的定义格式

指针的定义必须指定「类型」,格式:Type* 指针变量名;

#include <stdio.h>

int main() {
    int a = 10;        // 定义int类型变量,占用4个字节
    int* p = &a;       // 定义int*类型指针,存储a的首字节地址
    char c = 'A';      // 定义char类型变量,占用1个字节
    char* pc = &c;     // 定义char*类型指针,存储c的首字节地址
    
    // 指针变量本身的大小:X86下所有指针都是4字节,X64下都是8字节
    printf("int*指针大小:%zd\n", sizeof(p));   // X86下输出4
    printf("char*指针大小:%zd\n", sizeof(pc)); // X86下输出4
    return 0;
}
2. 两个核心运算符:&(取地址)和 *(解引用)
运算符作用示例
&提取某块内存的首字节地址,返回Type*类型int* p = &a; // 取 a 的地址赋值给 p
*解引用:通过指针保存的地址,访问对应内存的值(访问长度 = sizeof (Type))*p = 20; // 修改 a 的值为 20

关键示例(对应笔记核心)

#include <stdio.h>

int main() {
    int a = 10;  // 内存布局(小端存储):
                 // 地址0X00000001:00001010(10的二进制低8位)
                 // 地址0X00000002:00000000
                 // 地址0X00000003:00000000
                 // 地址0X00000004:00000000
    
    int* p = &a; // p存储a的首地址0X00000001
    printf("a的值:%d\n", a);    // 输出10
    printf("a的地址:%p\n", &a); // 输出0X00000001(示例地址)
    printf("p存储的地址:%p\n", p); // 输出0X00000001
    printf("解引用p:%d\n", *p);  // 输出10(访问p指向的4个字节)
    
    // 解引用修改值
    *p = 20;
    printf("修改后a的值:%d\n", a); // 输出20
    return 0;
}
三、指针的 “能力”:由类型决定(核心重点)

指针的类型(如char*/int*/double*)不影响指针变量本身的大小,但决定了两个关键能力:

  1. 解引用能力*p 能访问的字节数 = sizeof(Type)

    • char* p:解引用仅访问 1 个字节;
    • int* p:解引用访问 4 个字节;
    • double* p:解引用访问 8 个字节。
  2. 加减能力p±n 实际移动的字节数 = sizeof(Type) * n

#include <stdio.h>

int main() {
    int a = 10;
    char* p1 = &a;
    short* p2 = &a;
    int* p3 = &a;
    double* p4 = &a;
    
    printf("p1+1 = %p\n", p1 + 1); // 移动1字节
    printf("p2+1 = %p\n", p2 + 1); // 移动2字节
    printf("p3+1 = %p\n", p3 + 1); // 移动4字节
    printf("p4+1 = %p\n", p4 + 1); // 移动8字节
    return 0;
}
四、指针与数组:核心关联(笔记重点梳理)

数组和指针是 C 语言中密不可分的部分,核心结论先记住:

数组名arr在绝大多数情况下会 “退化” 为数组首元素的地址(Type*),仅两种情况例外:sizeof(arr)&arr

1. 数组名的两种特殊情况

c

运行

#include <stdio.h>

int main() {
    int arr[5] = {1,2,3,4,5};
    
    // 情况1:sizeof(arr) —— arr代表整个数组,计算数组总大小
    printf("数组总大小:%zd\n", sizeof(arr)); // 输出20(5*4)
    
    // 情况2:&arr —— arr代表整个数组,返回数组类型的指针(int(*)[5])
    int(*parr)[5] = &arr;
    printf("&arr+1 = %p\n", &arr + 1); // 移动20字节(整个数组大小)
    
    // 普通情况:arr退化为首元素地址(int*)
    printf("arr+1 = %p\n", arr + 1);   // 移动4字节(int大小)
    return 0;
}
2. 数组访问的本质:arr[i] == *(arr+i)

数组的[]运算符是语法,底层本质是指针运算,这也是指针能遍历数组的核心:

#include <stdio.h>

int main() {
    int arr[5] = {1,2,3,4,5};
    int* p = arr; // p指向数组首元素
    
    // 三种等价的数组访问方式
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d | *(arr+%d) = %d | p[%d] = %d\n",
               i, arr[i], i, *(arr+i), i, p[i]);
    }
    
    // 甚至可以反着写(仅演示,不推荐)
    printf("i[arr] = %d\n", 2[arr]); // 等价于*(2+arr),输出3
    return 0;
}
3. 二维数组与指针(进阶重点)

二维数组可理解为 “数组的数组”,比如int arr[3][4]

  • arr 退化后类型为 int(*)[4](指向包含 4 个 int 的一维数组);
  • arr[i] 退化后类型为 int*(指向第 i 行的首元素);
  • arr[i][j] == *(*(arr+i)+j)
#include <stdio.h>

int main() {
    int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    
    // 访问第2行第3列的元素(6)
    printf("arr[1][2] = %d\n", arr[1][2]);          // 直接访问
    printf("*(*(arr+1)+2) = %d\n", *(*(arr+1)+2));  // 指针运算访问
    return 0;
}
五、指针的避坑指南:野指针、指针越界
  1. 野指针:指向未申请 / 非法内存的指针,解引用会导致程序崩溃
// 错误示例:野指针解引用
int* p = 0; // p指向地址0(非法内存)
printf("%d", *p); // 程序崩溃
  1. 指针越界:访问超出数组 / 内存范围的地址
// 错误示例:数组越界
int arr[5] = {1,2,3,4,5};
int* p = arr + 10; // 超出数组范围
printf("%d", *p); // 访问非法内存,结果未知
六、实战案例:指针的经典应用
1. 指针实现交换函数(笔记核心案例)
#include <stdio.h>

// 指针交换两个变量的值(无临时变量也可用位运算,见之前的补充)
void swap(int* a, int* b) {
    if (a == b) return; // 避免地址相同导致错误
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 1, y = 2;
    printf("交换前:x=%d, y=%d\n", x, y);
    swap(&x, &y); // 传入地址
    printf("交换后:x=%d, y=%d\n", x, y); // 输出x=2, y=1
    return 0;
}
2. 指针遍历数组(高效写法)
#include <stdio.h>

void printArray(int* arr, int len) {
    int* p = arr;
    while (p < arr + len) {
        printf("%d ", *p++); // *p++ 先解引用,再移动指针
    }
    printf("\n");
}

int main() {
    int arr[10] = {0,1,2,3,4,5,6,7,8,9};
    printArray(arr, 10); // 输出0 1 2 3 4 5 6 7 8 9
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值