C语言指针入门到理解:一篇文章系统梳理指针核心知识(1)

C语言指针入门到理解:一篇文章系统梳理指针核心知识(1)

当我们初学C的时候,一看到指针就容易紧张。
其实指针难的地方,不在语法,而在于它把“变量”这层东西,进一步推进到了“内存”这一层。

这篇文章就带你从内存方面出发,一起梳理一下指针相关的核心知识点,包括一下内容:

  • 指针到底是什么
  • &* 分别在做什么
  • 为什么指针要有类型
  • const 修饰指针怎么区分
  • 什么是野指针,怎么规避
  • 为什么函数想修改外部变量时要用指针

一、什么是指针

想理解指针,先要理解内存和地址

程序运行时,变量都会放在内存中,而内存会被划分为一个个内存单元。每个内存单元都有编号,这个编号就是地址。CPU 正是通过地址去找到对应的数据。

所以从本质上说:

指针就是地址,指针变量就是专门用来存地址的变量。

你可以把内存理解成一栋宿舍楼:

  • 每个房间就是一个内存单元
  • 每个房间号就是地址
  • 知道房间号,才能准确找到房间

在 C 语言里,这个“房间号”就对应指针。


二、取地址操作符 &

定义一个变量时,本质上是在内存中申请空间,比如:

int a = 10;

如果我们想知道变量 a 在内存中的位置,就要使用取地址操作符 &

int a = 10;
printf("%p\n", &a);

这里的 &a 表示“取出变量 a 的地址”。

需要注意的是,a 可能占多个字节,但 &a 取到的是这块空间起始位置的地址,也就是较小地址那个字节的位置。


三、指针变量是什么

地址也是数据,既然是数据,就可以存起来。
存地址的变量,就是指针变量。

例如:

int a = 10;
int *pa = &a;

这里 pa 就是一个指针变量,它里面存的是 a 的地址。指针变量本质上也是变量,只不过它存储的是地址

这句代码可以拆开理解:

  • pa 是变量名
  • * 表示 pa 是指针变量
  • int 表示 pa 指向的是一个 int 类型的对象

也就是说,int *pa 的真实含义是:

pa 是一个指向整型数据的指针。


四、解引用操作符 *

有了地址之后,下一步就是:怎么通过地址访问对应的数据?

答案就是使用解引用操作符 *

int a = 100;
int *pa = &a;
*pa = 0;

这里的 *pa 表示:根据 pa 中存放的地址,找到对应的那块内存空间。

因为 pa 里存的是 a 的地址,所以 *pa 实际上就是 a 本身。
因此 *pa = 0; 最终修改的就是 a

所以要分清:

  • pa 是地址
  • *pa 是地址对应的内容

五、指针为什么要有类型

很多人会问:
既然指针里存的都是地址,那为什么还要区分 int*char*double*

原因很简单:

指针类型决定了编译器如何看待这块地址。

它主要影响两件事。

1. 决定解引用时访问几个字节

例如:

int n = 0x11223344;
int *pi = &n;
char *pc = (char *)&n;
  • *piint 处理,通常一次访问 4 个字节
  • *pcchar 处理,一次只访问 1 个字节

这说明,指针类型决定了解引用权限。

2. 决定指针加减的步长

char *pc = (char *)&n;
int *pi = &n;

pc + 1;   // 向后移动1个字节
pi + 1;   // 向后移动1个int大小

所以:

  • char* + 1 跳过 1 字节
  • int* + 1 通常跳过 4 字节

指针加 1,不是简单数值加 1,而是跨过一个对应类型的元素。


六、指针变量的大小

很多同学以为 char* 小一点,double* 大一点,其实不是。(注意,这里是说的指针变脸本身的大小)

指针变量的大小,和它指向什么类型无关,只和平台有关。

一般来说:

  • 32 位平台下,指针大小是 4 字节
  • 64 位平台下,指针大小是 8 字节

所以在同一平台下,你如果运行下面的代码:

sizeof(char *)
sizeof(int *)
sizeof(double *)

通常结果都是一样的。


七、void* 指针

void* 可以理解成“泛型指针”。

它的特点是:

  • 可以接收任意类型的地址
  • 常用于函数参数,增强通用性
  • 但不能直接解引用
  • 也不能直接进行指针加减运算

void* 它适合在函数参数中接收不同类型的数据地址,从而实现一定程度的泛型效果,但本身缺少具体类型信息,因此不能直接参与解引用和常规指针运算。

例如:

void *p = &a;

这样写可以,但不能直接写:

*p = 10;
p + 1;

真正使用前,需要先转换成具体类型。

比如说

void *p = &a;
(int*) p = 10;//强制类型转换为int* 类型的指针
p + 1;

八、const 修饰指针怎么理解

这是初学者特别容易混淆的内容。

1. const int *p

const int *p = &n;

含义是:

  • p 可以改指向
  • 但不能通过 p 修改它指向的内容

也就是:

指向的内容不能改,指针本身能改。


2. int * const p

int * const p = &n;

含义是:

  • p 不能再指向别处
  • 但可以通过 p 修改内容

也就是:

指针本身不能改,指向的内容能改。


3. const int * const p

两边都限制:

  • 不能改指向
  • 也不能通过它改内容

4. 一个简单记忆方法

const 限制谁:

  • const* 左边,限制的是“指向的内容”
  • const* 右边,限制的是“指针本身”

九、指针运算

指针常见的运算主要有三种:

  • 指针 + / - 整数
  • 指针 - 指针
  • 指针关系运算

1. 指针 + 整数

常用于遍历数组:

int arr[5] = {1,2,3,4,5};
int *p = arr;

for(int i = 0; i < 5; i++)
{
    printf("%d ", *(p + i));
}

这里 p + i 表示移动到第 i 个元素的位置。


2. 指针 - 指针

常用于计算两个位置之间相差多少个元素。

例如模拟 strlen

int my_strlen(char *s)
{
    char *p = s;
    while(*p != '\0')
        p++;
    return p - s;
}

这里 p - s 表示字符串长度。


3. 指针关系运算

例如:

while(p < arr + sz)
{
    printf("%d ", *p);
    p++;
}

通过比较指针位置来判断遍历是否结束。


十、什么是野指针

野指针是指针里最危险的问题之一。

野指针定义是:

野指针就是指针指向的位置不可知,或者是不正确、没有明确限制的位置。

简单理解就是:
这个指针已经不知道自己到底在指向哪里了,但你还试图通过它访问内存,那很容易造成各种各样的错误。


十一、野指针的常见成因

1. 指针未初始化

int *p;
*p = 20;

p 里是随机值,直接解引用非常危险。

2. 指针越界访问

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

一旦越过数组合法范围,p 就成了野指针。

3. 返回局部变量地址

int* test()
{
    int n = 100;
    return &n;
}

函数结束后,局部变量生命周期结束,这个地址就失效了。


十二、如何规避野指针

1. 初始化指针

如果一开始不知道该指向谁,就先赋值为 NULL

int *p = NULL;

NULL 是一个值为 0 的标识符常量,表示空地址。


2. 不要越界访问

指针只能访问合法申请到的那块内存,不能超范围操作。


3. 不再使用时及时置空

p = NULL;

这样做可以减少误用风险。


4. 使用前检查合法性

if (p != NULL)
{
    // 再使用
}

这是非常常见的安全写法。


十三、assert 断言的作用

在使用指针前,经常会先做合法性校验:

assert(p != NULL);

assert 的作用是:如果条件为真,程序继续执行;如果条件为假,程序直接报错终止,并指出问题位置,便于调试。

例如:

int my_strlen(const char *str)
{
    assert(str);
    int count = 0;
    while(*str)
    {
        count++;
        str++;
    }
    return count;
}

这相当于在函数开头加了一道保护。


十四、指针最重要的应用:传址调用

为什么要学指针?
因为有些问题,单纯传值是解决不了的。

例如交换两个变量:

void Swap1(int x, int y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

这样写交换失败,因为这是传值调用,函数里改的是形参副本,不是原变量。

正确写法应该是传地址:

void Swap2(int *px, int *py)
{
    int tmp = *px;
    *px = *py;
    *py = tmp;
}

调用时:

Swap2(&a, &b);

这时传入的是 ab 的地址,函数内部通过解引用直接修改原变量,所以交换成功。

这也是指针最核心的价值之一:

当函数需要修改主调函数中的变量时,就要用传址调用。


十五、总结

学指针,不能只背语法,主要是你得明白这样一条链路:

变量 -> 内存空间 -> 地址 -> 指针变量 -> 解引用访问

你把它理顺了,自然就会明白:

  • 为什么要用 &
  • 为什么要有 *
  • 为什么指针类型很重要
  • 为什么会有野指针
  • 为什么传地址才能改外部变量

ps:各位读完文字后可以思考一下这几个问题哦,读者自证不难(其实答案我上面都算是写出来了)


内容概要:本文围绕“考虑电动汽车聚合可调节能力的含波动性电源电氢耦合系统多目标优化运行”展开研究,提出了一种基于Matlab代码实现的多目标优化模型。该模型深度融合电-氢耦合系统与高比例波动性可再生能源(如风电、光伏),充分挖掘电动汽车(EV)集群作为移动储能单元的灵活调节潜力,通过聚合调控提升系统对新能源的消纳能力与运行经济性。研究系统构建了电动汽车可调度能力、电解水制氢与储氢动态过程、多能源协同互补的优化调度框架,并结合智能优化算法实现经济性、低碳性与运行稳定性等多重目标的协同优化。文中配套提供了完整的Matlab仿真代码、相关数据及可能的论文支撑材料,极大地方便了模型的复现、验证与后续深化研究。; 适合人群:具备电力系统、综合能源系统、优化理论或新能源技术等相关领域基础知识的研究生、科研人员,以及从事新型电力系统规划、清洁能源消纳与智慧能源管理的工程技术人员。; 使用场景及目标:①开展高渗透率可再生能源接入下的综合能源系统多目标优化调度研究;②探究电动汽车集群在电网削峰填谷、平抑新能源出力波动及提供辅助服务方面的应用价值与潜力;③学习并掌握电氢耦合系统的建模方法、多目标优化求解技术及其在Matlab/Simulink环境下的仿真实现流程。; 阅读建议:此资源不仅提供可运行的代码,更蕴含了前沿的科研思路与创新方法,建议读者结合所提供的代码、数据与可能的论文文档,系统性地学习从问题建模、算法设计到仿真分析的完整科研过程,并重点关注其中关于需求侧资源聚合、多能互补协同与绿色低碳运行的核心理念。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值