c语言指针与数组

1.数组和指针变量它表示的其实都是地址值,可以用指针p来p[0]这样代表数组
image

2.我们需要malloc来动态申请指针分配内存空间,在不用的时候需要free掉释放内存空间,需要使用标准函数库stdlib.h
3.int const * p;这是一个静态指针,说明p所指向的变量值不能被修改,但是可以修改p的值
int * const p;这也是一个静态指针,说明p的值不可以修改,但可以修改p指向的变量值
区分二者的标志是看const在
的左边还是右边
4.我们在定义指针的时候会给一个预定义的NULL值,表示0
5.由于指针所指向的类型不同,有时候不同指针之间不能赋值,赋值的话需要进行强制类型转化
6.定义一个函数,我们不能反回指针,因为函数中的值是局部变量,调用完函数之后会释放这些局部变量,若此时传给其他变量该地址值,当其他函数再次使用的时候,就会发现使用的值与返回的值不一样
image

深入理解C语言指针与数组的关联与区别

在C语言的学习旅程中,指针与数组绝对是绕不开的核心知识点,也是很多初学者感到困惑的难点。不少人会误以为指针就是数组,数组就是指针,但实际上二者既有紧密的关联,又存在本质的区别。今天这篇文章,就带大家一步步理清指针与数组的“爱恨情仇”,结合实例搞懂它们的用法和底层逻辑。

一、先夯实基础:指针与数组的核心定义

在深入探讨关联之前,我们先明确指针和数组各自的本质,这是理解后续内容的前提。

1. 数组:连续的内存块与固定标识

数组是一组相同类型数据的有序集合,其核心特征是:

  • 内存上连续存储:数组元素在内存中占据一块连续的空间,相邻元素的地址差值等于元素类型的大小(比如int数组,相邻元素地址差4字节,32位系统下)。

  • 数组名是常量:数组名代表数组首元素的地址,但它是一个“常量指针”,不能被修改(比如不能给数组名赋值)。

举个简单的数组定义示例:

#include <stdio.h>
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    printf("数组名arr的地址:%p\n", arr);
    printf("数组首元素&arr[0]的地址:%p\n", &arr[0]);
    return 0;
}

运行结果会发现,arr和&arr[0]的地址完全相同,这就验证了“数组名是首元素地址”的结论。

2. 指针:指向内存地址的变量

指针的本质是一个变量,这个变量专门用来存储内存地址(可以是普通变量的地址、数组元素的地址,甚至是另一个指针的地址)。其核心特征是:

  • 指针是变量:可以修改指针的值(即改变它指向的内存地址)。

  • 指针有类型:指针的类型决定了它指向的数据类型,以及指针移动时的“步长”(比如int*指针移动1位,地址增加4字节)。

指针的基本定义与使用示例:

#include <stdio.h>
int main() {
    int a = 10;
    int *p = &a;  // 定义int*指针p,指向变量a的地址
    printf("指针p存储的地址:%p\n", p);
    printf("指针p指向的值:%d\n", *p);  // *p解引用,获取地址对应的值
    
    p++;  // 指针移动(步长为4字节,因为是int*)
    printf("移动后p的地址:%p\n", p);
    return 0;
}

二、核心关联:指针与数组的“互通用法”

为什么大家会混淆指针和数组?核心原因是二者在很多场景下可以“互通使用”,本质是因为数组名本质是首元素的地址,而指针可以存储这个地址,进而操作数组元素。

1. 通过指针访问数组元素

既然数组名arr是首元素的地址,那么我们可以用一个指针变量接收这个地址,然后通过指针的移动和解引用来访问数组的所有元素,效果和数组下标访问完全一致。

示例代码:

#include <stdio.h>
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;  // 指针p指向数组首元素(等价于p = &arr[0])
    
    // 两种访问方式对比
    printf("下标访问:arr[0] = %d\n", arr[0]);
    printf("指针解引用:*p = %d\n", *p);
    
    // 访问第二个元素
    printf("下标访问:arr[1] = %d\n", arr[1]);
    printf("指针移动+解引用:*(p+1) = %d\n", *(p+1));
    
    // 遍历数组(指针方式)
    printf("指针遍历数组:");
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(p + i));  // p+i指向第i个元素
    }
    printf("\n");
    
    return 0;
}

运行结果会发现,arr[i]*(arr + i)*(p + i) 完全等价。这里要注意:指针的移动步长由指针类型决定,int*指针移动1位(+1),地址增加4字节,正好指向数组的下一个元素。

2. 数组作为函数参数时退化为指针

这是C语言中一个非常重要的特性:当数组作为函数参数传递时,数组名会自动“退化”为指向首元素的指针(失去数组的长度信息)。也就是说,函数内部接收到的并不是整个数组,只是一个指针。

示例代码:

#include <stdio.h>
// 函数参数接收数组(实际是指针)
void printArray(int arr[], int len) {  // arr[]等价于int* arr
    printf("函数内arr的地址:%p\n", arr);
    printf("函数内arr的大小:%d\n", sizeof(arr));  // 输出指针的大小(4或8字节)
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    printf("主函数内arr的地址:%p\n", arr);
    printf("主函数内arr的大小:%d\n", sizeof(arr));  // 输出数组总大小(5*4=20字节)
    printArray(arr, 5);  // 传递数组名(退化指针)和数组长度
    return 0;
}

运行结果分析:

  • 主函数中sizeof(arr)得到20字节(5个int元素,每个4字节),而函数内部sizeof(arr)得到4或8字节(指针的大小,取决于系统位数)。

  • 这说明函数内部并没有接收到整个数组,只是一个指向首元素的指针。因此,传递数组时必须额外传递数组长度,否则函数无法知道数组的实际大小。

三、本质区别:指针≠数组

虽然指针和数组有很多互通场景,但二者的本质完全不同,核心区别体现在3个方面:

1. 存储内容与本质不同

  • 数组名:是常量,代表数组首元素的地址,它不占用额外的内存空间(sizeof(arr)计算的是数组元素的总大小,不是数组名的大小)。

  • 指针变量:是变量,专门存储地址,它会占用一定的内存空间(比如32位系统下4字节,64位系统下8字节)。

验证代码:

#include <stdio.h>
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;
    
    printf("sizeof(arr) = %d\n", sizeof(arr));  // 20字节(数组总大小)
    printf("sizeof(p) = %d\n", sizeof(p));      // 4或8字节(指针大小)
    return 0;
}

2. 是否可修改不同

  • 数组名是常量,不能被修改(不能给数组名赋值)。比如 arr = p; 是错误的,编译器会报错。

  • 指针变量是变量,可以修改它指向的地址。比如p = arr + 2; 是合法的,此时p指向数组的第3个元素。

3. 内存分配方式不同

  • 数组:内存分配在栈区(局部数组)或全局区(全局数组、静态数组),内存空间是连续的,由编译器自动分配和释放。

  • 指针:指针变量本身分配在栈区,它指向的内存可以是栈区(比如指向局部变量)、堆区(比如通过malloc动态分配)、全局区等,灵活度更高,但需要手动管理堆区内存(避免内存泄漏)。

示例:动态内存分配(指针指向堆区)

#include <stdio.h>
#include <malloc.h>
int main() {
    int *p = (int*)malloc(5 * sizeof(int));  // 堆区分配5个int的空间
    if (p != NULL) {
        for (int i = 0; i < 5; i++) {
            p[i] = i + 1;  // 指针用下标方式操作堆区内存
        }
        for (int i = 0; i < 5; i++) {
            printf("%d ", p[i]);
        }
        free(p);  // 释放堆区内存
        p = NULL; // 避免野指针
    }
    return 0;
}

四、常见误区总结

学习指针与数组时,很多错误都源于对二者区别的理解不透彻,这里总结几个常见误区:

  1. 误区1:arr[i] 是数组特有的访问方式?—— 错!arr[i] 本质是 *(arr + i),指针也可以用 p[i] 的方式访问(等价于 *(p + i))。

  2. 误区2:函数参数写 int arr[10] 就能限制传入的数组长度?—— 错!数组作为参数会退化为指针,int arr[10] 等价于 int* arr,编译器不会检查数组长度,传入的数组长度超过10也不会报错(会导致数组越界)。

  3. 误区3:指针和数组完全等价?—— 错!只有在“数组名作为首元素地址”的场景下二者才互通,本质上数组是连续内存块,指针是存储地址的变量,二者不能混淆。

五、总结

指针与数组是C语言的灵魂,理解它们的关联与区别,能让你更深入地掌握C语言的内存管理逻辑。简单来说:

  • 关联:数组名是首元素的常量指针,指针可以接收这个地址来操作数组;数组作为函数参数时退化为指针。

  • 区别:数组是连续内存块(常量标识),指针是存储地址的变量;指针可修改,数组名不可修改;内存分配方式不同。

建议大家多写代码实践,通过调试观察指针和数组的内存地址变化,这样才能真正理解二者的核心逻辑。如果有疑问,欢迎在评论区交流~

7.结束while循环的原理:
image

这个while循环结束的核心原理,是利用malloc的返回值特性,具体拆解如下:

1. 先明确malloc的返回规则

malloc是C语言中从堆区动态分配内存的函数,它的返回值有两种情况:

  • 如果成功分配到内存:返回指向这块内存的指针(非空指针);
  • 如果分配失败(比如系统剩余内存不足、堆空间耗尽):返回NULL(空指针)。

2. 再看while循环的条件逻辑

代码中循环条件是:while( (p=malloc(100*1024*1024)) )

  • 第一步:执行p=malloc(...),把malloc的返回值赋值给指针p
  • 第二步:判断这个返回值是否为“真”——在C语言中,非空指针被视为“真”,NULL空指针被视为“假”

3. 循环的执行与结束

  • 每次循环,程序尝试分配100MB内存(100*1024*1024字节):
    • 若分配成功(返回非空指针):条件为“真”,cnt计数+1,继续循环;
    • 若分配失败(返回NULL):条件为“假”,while循环直接结束。

简单说:当系统没有足够内存让malloc分配时,malloc返回NULL,导致循环条件不成立,循环就结束了

posted @ 2025-12-26 10:36  f-52Hertz  阅读(12)  评论(0)    收藏  举报