C语言指针详解

本文介绍了C语言中的指针概念,包括指针的定义、基础使用、不同类型指针的区别,以及如何避免野指针、越界等问题。通过实例和总结,帮助读者掌握指针在编程中的关键作用。
摘要由CSDN通过智能技术生成

众所周知,指针是c语言的灵魂,学会指针的运用,将会非常轻松的理解一些难以理解的问题,下面进入正题。

一、什么是指针?

说起指针,我们不妨先从日常生活说起,我们去一个陌生的地方,这个地方有许多房间,我们要去其中一个房间,假设什么也不告诉你,你能找到我们要去的房间是哪一个吗?显然不能。所以我们通常要给每个房间编号,我们可以通过这个编号找到具体的房间。计算机中有内存,内存有许多内存单元,每个内存单元大小为1个字节,如果我们定义一个char类型的变量ch,会在内存中申请1字节的空间,但当我们想要找到ch时,该怎么找到它呢?我们可以想象每个内存单元相当于一个房间,我们要找到这个房间,就要给房间编号,同样的我们想要找到具体的内存单元,要给每个内存单元编号,通过这个编号,CPU就可以快速的找到对应的内存空间,我们通常不叫编号,称为地址,我们也给这个地址取了个新名字:指针。所以我们可以理解为:内存单元的编号 == 地址 == 指针  通过指针就可以找到ch。

二、指针的基础

我们了解了指针的概念后,下面说一说指针该怎么使用?C语言中创建变量就是向内存中申请空间。

我们知道a其实是占4个字节的空间,那么我们就会产生一个疑惑,每一个字节都有一个地址,那a的地址该怎么算?其实第一个字节的地址就是a的地址(较小的地址),我们知道了a的地址,该怎么得到它呢?有一种操作符&,它的全称是取地址操作符,我们想要拿出a的地址,只需要&a即可。

#include <stdio.h>
int main()
{
    int a = 0;
    printf("%p\n",&a);//这里就能取出a的地址
}

怎么得到地址我们应该明白了,但怎么把它存储起来呢?char类型可以吗?显然不行。那用short?int? long? float? double?显然统统不行。这时候指针就站出来了。

#include <stdio.h>
int main()
{
    int a = 1;
    int* pa = &a;
    return 0;
}

这里的pa就是指针变量。指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。至于pa前面的*就是指出它是指针,int就是pa这个指针变量指向的类型是int类型对象。

这就是两者的联系。

这时候相信大家又会产生出来一个疑问,我们把地址存起来,怎么用呢?在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。
C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

#include <stdio.h>
int main()
{
    int a = 1;
    int* pa = &a;
    *pa = 3;
    return 0;
}

*pa 的意思就是通过pa中存放的地址,找到指向的空间,*pa其实就是a变量了;所以*pa=3,这个操作符是把a改成了3。解引用操作符未来有很大用处,逐渐的我们会体会到的。

接着,指针变量的大小是多少?我们知道指针是用来存放地址的,那么指针变量的大小就是地址大小,在32位机器下,地址是32bit位的,所以指针变量的大小就是4字节,在64位机器下,指针大小就是8字节。注意指针变量的大小和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。

三、指针变量类型的意义

我们又学习到了指针的大小和平台有关,那么char* ,short* ,int*等,它们有什么区别吗?下面请看两端代码:

代码一中pa是int*,它解引用会访问4个字节,通过调试我们可以看到将44 33 22 11改成00 00 00 00

代码二中pa是char*,它解引用会访问1个字节,通过调试我们可以看到将44 33 22 11改成00 33 22 11

这就是它们的区别。

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

其次,指针也可以加减整数。我们看下面代码:

pa是int*,pa+1地址变化了4字节。

pc是char*,pc+1地址变化了1字节。

结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

四、指针的运用

我们知道数组名是首元素地址,数组中的元素是顺序存储的,那么我们就可以用指针来访问整个数组。请看下面代码:

#include <stdio.h>
int main()
{
    int arr[]={0,1,2,3,4,5,6,7,8,9};
    int sz=sizeof(arr)/sizeof(arr[0]);
    for(int i=0;i<sz;i++)
    {
        printf("%d ",*(arr+i));
    }
    printf("\n");
    return 0;
}

这段代码就可以看出指针的"妙用",arr是数组名即数组首元素地址,arr+0就是数组中第一个元素的地址,arr+1就是指向数组中第二个元素的地址,以此类推可以找到数组中每个元素的地址,再通过解引用操作符找到数组所有的元素。这段代码也反应出了其实arr[i]与*(arr+i)是等价的,在这个数组中,arr+5是元素5的地址,arr是元素0的地址,arr+5-arr就是这两个指针之间元素的个数,但前提是在同一个数组中,我们可以利用这点,处理一些问题。

两个指针也可以进行比较。请看代码:

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    while(p<arr+sz) //指针的⼤⼩⽐较
    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}

在while循环中,我们可以利用指针的大小比较来控制打印数组中的元素。

五、使用指针会遇到的问题

下面介绍一个特殊的指针---野指针,所谓野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

我们知道使用指针可以用来指向一片内存空间,在生活中,我们去酒店开房间会得到一个房间号,假设是101,我们通过房间号找到属于我们居住的房间,但如果你去了102,这肯定不行,你会被赶出来的,甚至会骂你是“流氓”,所以你要去到属于你自己的房间,指针也一样,你可以指向一片内存空间,但你不可以指向不属于你的内存空间,这就会“违法”,如果指向不属于你的内存空间,就称其为“野指针”。

野指针有以下几种:

1.指针未初始化就解引用,具体代码如下:

#include <stdio.h>
int main()
{
    int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
    return 0;
}

我们并不知道p指针指向的位置就冒然解引用操作,此时p就是野指针。

2.指针越界访问,代码如下:

#include <stdio.h>
int main()
{
    int arr[5]={1,2,3,4,5};
    int* p=&arr[0];
    for(int i=0;i<=5;i++)
    {
        printf("%d ",*(p+i));
    }
    printf("\n");
    return 0;
}

想必大家对这段代码已经熟悉了,当p指针为p+5时已经发生越界,解引用访问时就会拿不属于我们的“东西”,此时p+5就是野指针。

3.指针指向的内存空间释放,代码如下:

#include <stdio.h>
int* test()
{
    int n = 100;
    return &n;
}
int main()
{
    int*p = test();
    printf("%d\n", *p);
    return 0;
}

当我们调用完test函数时,n的那块空间已经不属于我们了,我们只是记住的它的地址,解引用时就是“违法”的。比如说,你在酒店订了一个房间101,你对你朋友说这个房间很好让他也来,第二天你去退房,过一会你朋友来了,直接进了101房间,这显然是不可以的。你也会被赶出来的。

六、如何解决指针遇到的问题

1.指针初始化

如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。

2.小心指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性。当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。

4.避免返回局部变量的地址。

5.assert断言。assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。
在使用指针之前,我们可以把指针变量作为参数给assert,如果指针为空就直接报错。防止我们解引用时,出现的“违法”行为。

七、总结

指针在c语言中是非常重要的,以上的内容只是一个“引子”,帮助大家熟悉指针,大家在未来写代码时还会进一步了解指针。大家也不要“害怕”指针,慢慢熟悉,试着使用,逐渐加深,最终拿捏它。

祝大家天天开心!


 

  • 30
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值