浅析C指针

对于很多C语言的学习者来说,指针无疑是一个很让人迷惑的东西,往往让人一头雾水,很多类C的编程语言(Java等)甚至移除了指针这个概念。但其实,指针可以说是C语言的灵魂,运用得当,可以发挥巨大的作用,现在就让我们一起来学习一下指针。

以下内容属于个人理解,有问题请大家指正,谢谢!

首先,让我们来弄清楚,什么是指针?

我们都知道,C语言中,变量是存放在内存中的,而内存可以简单理解为一个有序字节组成的数组,每一个字节都有对应的位置,那么变量就应该有对应的储存地址。而指针,就是储存着变量地址的变量。

这么说可能有些拗口,大家可以参考下图,内存有很多字节都是顺序排布,每个字节的都对应一个地址。

   

假设一个int 类型的变量a储存在内存的0x00000001到0x00000004(int类型占4个字节),那么a的地址的值就是这段内存的首地址,即0x00000001,我们用一个4个字节的变量来储存这个值,这个就是指针,0x00000001就是这个指针的值。

但是我们光知道这段内存的首地址并不能让我们完全确定这个变量,至少我们还需要找到这段内存的长度,这一点可以通过指针的类型来决定。比如double类型的指针就表明指向的这段内存占有8个字节,我们知道了这段内存的首地址,还知道了它的长度,就可以找到这段内存了。

所以指针其实包含两个内容,指针的值和类型,指针的值确定这段内存的首地址,类型可以帮我们确定这段内存的长度。

 

接下来,我们来聊聊为什么要用指针。

  1. 指针可以让不同区域共享数据。

比如说,函数B想要使用变量a的数据,只需要得到a的指针,就可以轻易访问a的内存,进而获取a的数据。有人说,可以直接复制变量a来获取a的数据,但是如果变量a的类型是一个比较复杂的结构体,那么复制a的代价就会很大,需要占用很多的内存。而所有的指针都只占4个字节,所以可以减少内存和性能的消耗。

 

  1. C语言参数传递的时候,是按值传递,我们把a当作参数传入函数B,在函数中修改a,并不会让a本身发生改变,但如果我们将a的指针传入B,就可以在函数B中修改达到修改a本身的目的。
  2. C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等;
  3. 我们在堆上申请内存的时候,必须使用指针。

 

除此之外,指针还有很多的妙用,可以让我们的编程变得更加轻松,并且提高程序的运行效率。

 

知道了为什么要用指针之后,我们来聊一聊指针的基本运算。

  1. &和*

这里&是取地址运算符,*是间接运算符。
&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
*p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址

 

值得注意的是,指针本身也是一个变量,变量都是储存在内存中的,所以指针本身也存在指针,指针的指针我们成为二级指针。

 

2,指针的算数运算

指针支持加减两种算数运算,而且一般只能加减整数。我们先来看一个例子。

char a[20] = “hellow world”;

int* ptr = (int*)a;

ptr++;

 

大家猜测一下现在ptr指向哪里,是不是指向’e’?

其实正确答案是指向’o’。指针ptr 的类型是int*,它指向的类型是int,它被初始化为指向整型变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr 的值加上了sizeof(int),在32 位程序中,是被加上了4,因为在32 位程序中,int 占4 个字节。由于地址是用字节做单位的,故ptr 所指向的地址由原来的变量a 的地址向高地址方向增加了4 个字节。由于char 类型的长度是一个字节,所以,原来ptr 是指向数组a 的第0 号单元开始的四个字节,此时指向了数组a 中从第4 号单元开始的四个字节。

其实指针还可以加减指针,不过本文不做讨论。

 

3,指针的关系运算

<   <=   >   >=  不过前提是它们都指向同一个数组中的元素。根据你所使用的操作符,比较表达式将告诉你哪个指针指向数组中更前或更后的元素。标准并未定义如果两个任意的指针进行比较会产生什么结果。

 

刚刚我们将指针和数组进行了转化,现在让我们来聊一聊指针和数组的关系。

其实,数组名就可以看作一个指针,很多数组操作都可以用指针来代替,一般用指针编写的程序运行速度会快一些,但是指针会让程序更难理解。

对于数组char array[20],数组名array有两层含义:

  1. array代表整个数组,它的类型是char[20];
  2. array也可以看作是一个指针常量,类型是char*,指向的内存的值就是char[0]所在的内存的地址,不过值得注意的是,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
    在表达式*array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。

此外,还有指针数组和数组指针的两个概念。

指针数组是一个数组,数组的每个成员都是指针,需要这样声明:

 

数组指针是一个指针,它指向一个数组,声明方式如下:

 

刚刚提到数组名可以看作一个指针常量,那么什么是常量指针呢?

顾名思义,指针常量就是一个常量,不可以修改他的变量值,即不能修改它指向的地址。

int * const p //指针常量

使用方式如下:

int a,b;

int * const p=&a //指针常量

//那么分为一下两种操作

*p=9;//操作成功

p=&b;//操作错误

 

除此之外,还有常量指针,即指向一个常量的指针,无法修改它指向的内容的值。

const int* p; //常量指针

使用方式如下:

int a,b;

const int *p=&a //常量指针 

//那么分为一下两种操作 

*p=9;//操作错误

 p=&b;//操作成功

 

还有指向常量的指针常量:

const int * const b = &a;//指向常量的指针常量

 

既然指针有这么多用处,那么为啥会被很多语言舍弃呢?最后来让我们一起看看指针的风险和注意事项。

  1. 空指针

NULL 指针是一个特殊的指针变量,表示不指向任何东西。可以通过给一个指针赋一个零值来生成一个 NULL 指针。

我们在进行指针操作的时候,一定要进行判空处理,因为大多数系统是不允许访问空指针的,不然会引发位置错误。

  1. 野指针

野指针是指向无效地址的指针。比如存在局部变量a,当a出栈的时候就会被销毁,我们这时候再使用a的指针对a进行操作,就会引发位置错误。

再比如,我们使用malloc或者new获取了一块指向堆内存的指针,然后释 放掉,但是没有给这个指针赋空值,就属于野指针。

  1. 堆内存的释放

我们再申请了堆内存之后内有手动进行释放,这种系统也不会帮我们释放,就会造成内存泄漏的问题。而且也不能对同一个指针重复进行释放。

  1. 两个指针指向了同一块内存

这种也属于比较常见的问题,我们在进行复制或者拷贝的时候,不小心让两个指针指向了同一块内存,就会引发很多奇怪的问题,比如指针重复释放等等。

虽然C++或者boost增加了很多智能指针的概念,但是仍然不能阻止程序员因为自己的疏忽犯错误,为了排查指针造成的bug,也会浪费大量的时间和精力。

除了上面所介绍的,指针还有着很多种用途,比如函数指针等一系列强大的功能。可以说,指针是一把双刃剑,用好了可以增加程序运行速度,让程序更强大,用错了,则会带来各种各样的bug.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值