c语言指针讲解

指针的定义

指针是c语言中的一个重要概念,所谓指针,就是指向内存中某个变量的地址的变量。这好比你家庭地址。当有朋友要来做客的哈,那么就需要地址。c语言中就是当系统要使用数据的话,就需要指针来快速找到数据。指针就可以简单理解为一个地址。

指针变量

取地址操作符(&)

现在经过指针的定义,我们现在知道指针可以理解为一个地址,那么如何取得地址呢。(二分钟过后)大家都想到了吧。我们以前学习过的一个操作符"&"取地址操作符。

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

这样大家可以看到,我在数组名a前加了一个&,那么我们就取出了常量a的地址。

指针变量

当我们可以通过&来拿到一个数值,我们不能一直拿着呀,所以我们需要将这个数值储存起来,方便后期需要的时候使用。那么我们要将地址值放在那里嘞,这就引出了我们接下来要将的知识:指针变量。

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

这样大家应该看到了a的地址放在了*pa中。所以*ps就是指针变量。但大家不用认为指针变量就很特别,相较于以前讲的变量来讲指针变量只是用来存放地址的。

指针类型

大家可以看见我在上一小段写过,指针变量与变量只有内部储存内容的区别,那么数据类型其实与变量,相差无几的,大家可以看下面的图片。

int main()
{
 int a = 100;
 int* pa = &a;
 *pa = 0;
 return 0;
}

这⾥pa左边写的是 int* , * 是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int)类型的对象。当然char类型的指针变量也是差不多的。

char ch = 'w';
pc = &ch;

这样大家自己也可以判断出其他指针变量的类型了。

解引用操作符

当我们将指针变量存储起来了,那我们需要使用,应该怎么办呢?所以这就引出我们的解引用操作符(*),这个操作符也是c语言中的相乘。所以大家使用的时候需要甄别一下。

int main()
{
 int a = 100;
 int* pa = &a;
 *pa = 0;
 return 0;
}

上面的 *pa就是使用了解引用操作符了,解引用操作符(*)只会取首元素的地址,如果解引用的是数组的话,那么解引用操作的就是数组首元素的地址了。当然这也与指针类型有区别,如int类型与char类型。

5751709f81ee459b9e4c6e3d7e7a4ab0.jpeg

       大家可以看到上面的这一张图片,两个代码的区别是解引用操作符前一个是int类型一个char类型但是最终的结果却是不一样的,一样int类型是4个字节,但是char类型是一个字节的,所以当int类型改变a的时候是4个字节改变,但char类型改变a的时候只会改变一个字节。不信的话,大家可以在编译器上调试,看一下内存改变是不是这样的。

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

void类型

        现在讲了这么多,一直都忘了一个特殊的指针类型,“void *” 类型。,大家都知道void什么类型的不是所以我们可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进 ⾏指针的+-整数和解引⽤的运算。

7643027c9c9140cb8a39b5cbe7185c13.png

大家可以在下方的输出中看见当float类型指针接收int类型指针的时候,报了一个警告,说“int*”类型到“float *”的类型不兼容。虽然这里没有报错,但是警告也相当于一个错误,在后面有一系列计算时很有可能出现BUG。

e92566b452124d66b3f7021ae96ac2e8.png

但是现在大家可以看到使用void类型来接收的时候,输出成功,且没有报错,那么这就验证了,“void*”可以接收其他类型地址。

60ba7119142c43d49a204987c72bbbf1.png

        在右下方大家也可以看见相较于上一周图片,代码没有编译成功,报出的错误就是“void *”与“int”的间接级别不同。这就表现出了,“void*”可以接收任意类型的指针,但“void*”不能进行指针计算。

const

不知道大家在以前是否有了解过。它的意思是“恒定的”,那恒定就说明没办法改变,是吧,恒定嘛,就是不变,那么在c语言中有什么意义呢?(二分钟过后)const通俗一点的解释就是,加了const的话,就不能改变了。

4dbe3eef3482406bb81571e01b38b624.jpeg

大家可以看到左图是正常写,没有任何的报错。但是右边的图片则报错了,区别就是我在创建常量a的时候加了const。常量a就不能被修改了。如果强行要写的话,那么就会报错。

接下来就来看一下const在指针变量有哪些作用吧 

e08479d889354cdeb1e4c5e245686d22.jpeg

 这里我们在指针类型左右都尝试加了const来对比区别,当然const的作用是没有改变的,都导致了一个数据无法改变,那么如果在*左右都加入const会有什么效果呢?别急接下来我就为各位总结一下:

const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。

const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

注:数组名就是数组⾸元素(第⼀个元素)的地址。并且sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩的地址是有区别的单位是字节。&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素)

指针的运算

上面讲的都是一些指针使用前的准备吧,那么接下来就讲一些指针的运算,例如指针+-整数,指针-指针,指针的关系运算。这三个也是指针的三种基本运算,也是使用的较多的。那么我们就分别来讲解一下,使用方法和效果是什么样的吧。

指针+-整数

这样大家可以看到在没有加a(5)前打印出来的值为0;但是加a(5)后,打印出来的值为5。这就表明指针至少可以加减整数。因为我们在前面讲过解引用操作符(*),是取首元素地址,在这里已就是数组arr的首元素地址,因为数组在内存是连续存放的,当首元素地址知道后,数组其他元素也就可以知道了,只需要改变下标就可以了,所以指针+-整数就是改变解引用操作符处理的下标。

指针-指针

指针减指针可以理解为地址减地址,a与c相差3个,那么我们可以得出指针-指针得出得结果就是两个指针之间的距离。

指针的关系运算

数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。所以指针的关系运算其实就是比较大小。

野指针

定义

大家都知道,在农村里面有一些没人认领的狗,在外面到处觅食,久而久之就成为人们口中的“野狗”了。没有人知道它从何而来,不知道到哪去。野指针与这个道理也是差不多的,野指针是随机,不正确,没有明确限制的。

成因

形成野指针的常见几种原因有:未初始化,越界,指向的空间被释放等。

未初始化

这可以看出编译失败的原因是“*p”为定义,但看图片可以看出我们有对“*p”进行定义呀,那为什么还会报错啊,这就是指针为初始化,所以系统会提醒,为初始化“p”。

越界

大家可以看到取地址的数组,定义arr的时候只有10个元素,但是for循环里面却有11个数,超过了10,这就超出了规定的大小。所以编译的时候直接表明了栈溢出了。

指向空间释放
int* test()
{
 int n = 100;
 return n;
}
int main()
{
 int*p = test();
 printf("%d\n", *p);
 return 0;
}

这里大家可以看到“*p”得到的值是从test中得到的,但是test子函数生命周期短,出来test的值就不能传到“*p”里面了。所以这就是指针指向的空间被释放。

如何规避野指针

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

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

(3)使用后及时置NULL:指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

(4)避免局部变量地址:如造成野指针的空间释放,不要返回局部变量的地址。

二级指针

大家知道前面我们写了指针变量,那么是变量也应该有一个地址存储吧。所以这就引出了我们接下来的知识点,二级指针

int main()
{
int a=18;
int *pa=&a;
int **pc=&pa;
return 0;
}

不知道大家有没有看出有什么区别,“*pa”取地址a;但是“**pc”的取地址是“pa”。那依照这个食物链依次向下

对“**pc”解引用找到“*pa”,然后对“*pa”解引用就找到了“a”。当然不止有二级指针,还要三级,四级指针等,需要依据编程员自己依据实际情况来写。

数组指针

我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。 数组指针变量是指针变量?还是数组?答案是:指针变量。那数组指针有什么用,如何使用嘞。

 数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

int *p1[18];
int (*p2)[18];
大家可以尝试猜一下,这两个代码哪个是数组指针变量

大家应该已经想到了吧,“int (*p)[18]”是数组指针变量,因为p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫 数组指。这样大家就知道了吧判断是否是数组指针的依据是看“*”与谁结合。

:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。

数组指针变量初始化

现在大家已经知道判断数组指针了,那我们想要写一个数值指针变量应该怎么办呢?

 这样子大家是否恍然大悟,哦,又是取地址操作符(&),没错又是我们前面讲过几次的取地址操作符。因为指针不能自己定义,所以需要在定义指针前,确定需要指向的数组,然后创建数组指针并且指向数组。这里大家也看到了数组“arr”的内容与数组指针“*p”的内容是一样的。

      这就是今天想与大家分享的指针见解了,当然还有很多纰漏,希望大家可以在评论区写下来,鄙人一定会改正。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值