指针vs引用

基础:

1.存储方式

1.1指针:是变量,存储的是内存地址。

1.2引用:是变量的别名,不存储地址,通过别名直接访问原始变量。

2.初始化

2.1指针:可以不初始化(但应避免,以免成为野指针),初始化时需要显式地给出地址。

2.2引用:必须在定义时初始化,且一旦初始化后不可再改变为引用另一个变量。

3.空值:

3.1指针:可以指向空(nullptr或NULL),表示不指向任何对象。

3.2引用:必须总是指向某个对象,不存在空引用的概念。

4.多级引用/指针:

4.1指针:可以有多级指针(如int**),指向另一个指针。

4.2引用:不支持多级引用,即不能定义引用的引用。

5.函数参数与返回值:

5.1指针:作为参数传递时,可以修改原始数据(如果指向了非const对象)。作为返回值时,需要注意生命周期问题。

5.2引用:作为参数传递时,同样可以修改原始数据(如果非const)。作为返回值时,更安全,因为不需要担心指针的生命周期问题。(这里的生命周期是指引用作为参数传递,作为作为返回值返回时,生命周期安全问题)

使用场景:

1.当需要指向空值或重新指向另一个对象时,使用指针。

2.当需要在函数间安全地传递大型对象或需要修改函数参数时,优先考虑引用(除非有指向空或重新指向的需求)。

3.在需要动态内存分配或管理内存时,使用指针。

4.在需要避免拷贝大型对象以提高效率时,使用引用(特别是作为函数参数或返回值)。

内存

new/delete/free

new 在上开辟连续的内存空间

1. delete p(匹配 new T

  1. 先调用 p 指向对象的析构函数,释放对象内部资源;

  2. 回收堆内存还给操作系统。

2. delete[] arr(匹配 new T[5]

  1. 根据 new 时隐藏记录的元素个数,循环调用每个元素的析构函数

  2. 整块连续数组内存回收。

致命匹配规则(必考)

  • new T → 只能 delete,不能 delete[]

  • new T[] → 只能 delete[],不能 delete 混用产生未定义行为(内存泄漏 / 程序崩溃)

1. C 语言有没有 delete?

没有。 new / delete 是 C++ 独有的关键字,C 语言语法完全不认识,编译直接报错。

2. C 标准配对:malloc/calloc/reallocfree

C 只有堆内存原始分配函数,只开辟裸内存,不涉及对象构造析构。

3. C++ 是不是不能用 malloc?

不是禁止,只是不推荐混用,分场景:

  • 自定义类、带构造 / 析构的对象:必须用 new/delete

  • 纯内置类型(int、char 数组):语法上 malloc+free 能跑,但 C++ 工程规范优先 new/delete。

4. C++ 标准配对:newdeletenew[]delete[]

严格配对,不能交叉。

malloc/calloc/realloc

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

void func(int *arr,size_t n){
    for (size_t i=0; i < n; i++){
        printf("%d - ",arr[i]);
    }
}


int main(int argc, char const *argv[])
{
    int *mal=(int* )malloc(5 * sizeof(int));
    func(mal,5);
    printf("\n");

    int *cal=(int*)calloc(5, sizeof(int));
    func(cal,5);
    printf("\n");

    int *rel=(int*)realloc(mal,3);
    func(mal,5);
    

    return 0;
}

malloc 开辟空间,内部都是杂乱的数据
calloc 开辟空间,内部全部initial为0

realloc缩容或者扩容

内存分区

内存四区简单区分(程序运行时)

  1. 栈 stack 函数局部变量、函数形参、函数返回地址;自动分配自动释放;容量很小。
  2. 堆 heap malloc /calloc/realloc /new 手动申请,free /delete 手动释放;空间大。
  3. BSS全局 / 静态数据段 全局变量、static 静态局部变量、字符串常量。
  4. CODE代码段 函数二进制指令、const 只读常量。

必须用堆:

场景 1:数组太大,栈装不下(栈空间极小)
场景 2:数组长度运行时才确定(动态长度)
场景 3:函数结束后,数组数据还要给外部使用(最关键区别)
场景 4:数据需要长期保存,不随函数结束销毁(Cache)

sizeof 求数组长度 + malloc 动态数组长度与越界

一、sizeof 计算数组长度(仅栈固定数组可用)

1. 正确公式(局部完整栈数组)

c

运行

int arr[6] = {1,2,3,4,5,6};
// 总字节 ÷ 单个元素字节 = 元素个数
size_t len = sizeof(arr) / sizeof(arr[0]);

原理:栈上完整数组,sizeof(数组名) 算出整块数组总占用字节。

2. 两种场景 sizeof 完全失效

1)数组作为函数形参 函数内 int arr[] 等价 int* arrsizeof(arr) 只得到指针大小(64 位 8 字节),无法求长度。 解决:把数组长度单独作为参数传入函数。

2)malloc 堆动态数组 int* p = malloc(n * sizeof(int)); p 是指针,sizeof(p) 永远是指针宽度,和分配多少元素无关,不能算长度。

二、malloc 堆数组如何保存、判断长度

1. 核心结论

堆内存不会自动记录分配的元素数量,没有内置长度标记,只能手动用变量记录容量。 示例:

c

运行

int cap = 5; // 记录堆数组总容量
int* arr = (int*)malloc(cap * sizeof(int));

// 遍历、边界判断全部依靠 cap 变量
for(int i = 0; i < cap; i++){
    arr[i] = i;
}
free(arr);

2. realloc 动态扩容配套处理

扩容时同步更新容量变量 cap:

c

运行

cap *= 2;
int* temp = realloc(arr, cap * sizeof(int));
arr = temp;

三、数组下标越界问题(栈数组 / 堆数组统一规则)

  1. 无论栈数组还是 malloc 堆数组,访问超过分配范围的下标都属于未定义行为 UB,标准不规定固定后果。

  2. 栈数组越界后果:篡改其他局部变量、覆盖函数返回地址,程序乱码、崩溃、栈溢出攻击风险。

  3. 堆数组越界后果:破坏堆内存管理头部信息,后续调用 free () 大概率直接程序崩溃,内存结构损坏。

四、避坑总结背诵

  1. 只有函数内部固定长度栈数组,才能用 sizeof(arr)/sizeof(arr[0]) 求长度;

  2. 函数形参数组、malloc 堆指针,sizeof 失效,堆必须手动变量存容量;

  3. 栈 / 堆数组都不能下标越界读写,均是未定义行为,会造成程序异常崩溃。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐