C语言指针【笔记】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tianxieeryang/article/details/83784851

内存和地址

如果把计算机的内存比作一条长街上的房屋。每座房屋都可以容纳数据,通过一个房号(门牌号)来标识。
计算机的内存由数以亿万计的位(bit)组成,每个位可以容纳的值位 0 或 1 。因为这么比喻也是有一定的局限性,每个单独的位用处不大。因此通常将许多位合成一组来表示一个单位,这样就可以存储范围较大的值。下面展示现实机器中的一些内存位置。
机器中内存位置
每一个位置被称为一个字节(byte),每个字节包含8位,可以存储的无符号值为 0~255,有符号值为 -128~127。方框中包含一些数据(该图未显示)。
为了存储更大的值,我们通常将两个或者多个字节合为一个更大的内存单位。现在的机器一般以字为单位存储整数,每个字由 2 个或 4 个字节组成。以 4 个字节为例,每个字可以存储的无符号值为 0 ~ 4394967295(2^32 - 1),有符号值为 -2147483648(-2^31) ~ 2147483648(2^31 - 1)

程序员只对两件事情感兴趣:

  1. 内存中的每个位置都由一个独一无二的地址标识。
  2. 内存中的每个位置都包含一个值。

地址与内容

内存与地址
上图显示了 5 个整数,每个都位于自己的字中。图中第一排是根据地址取这个值。但是高级语言的特性之一就是提供了通过名字而不是地址来访问内存位置的功能。第二排就是通过变量名来取这个值。

变量名字与内存位置之间的关联并不是硬件提供的,是我们编译器实现的。硬件仍然通过地址来访问内存位置

未初始化和非法指针

初学者在使用指针时经常会犯一个错误,如下:

int* a;
*a = 20;

int* a;:声明一个指针变量, 指针变量名字叫做 a,类型为 int*
*a:表示 a 所指向的位置所存储的值;
第一条语句是一个声明,声明一个叫做 a 的指针变量,第二条语句把 20 存储到指针 a 所指向的内存位置。

警告: 这个指针 a 究竟指向哪里?我们声明了一个指针变量,但是没有对它初始化,我们并没有看到这个指针变量 a 指向了什么地方,就对这个指针变量进行赋值 12,同样我们没法预测 12 这个值会存储在什么地方。这样看来,指针变量和其他变量并无区别。

  • 如果一个变量是静态的,它会被初始化为 0;
  • 如果这个变量是动态的,它根本不会被初始化。

因此无论哪种情况,声明一个指向整型的指针变量都不会"创建"用于存储整型值的内存空间。
当赋值操作执行时,a 的初始值会是个非法地址,这样赋值语句会出错,终止程序。
但是:在这里我想做个类比。如果因为终止程序,你发现了一个BUG,这就好比在房间里看到了一只老鼠,是不是很可怕;但是我认为这不是最可怕的,可怕的是当你知道房间里有一只老鼠,并且发现了他的位置,可是突然之间这个老鼠不见了!不见了!不见了!!!(自己体会一下)
下面我来说一下这种情况会导致的一种更为严重的情况:这个指针偶尔可能包含一个合法的地址。接下来的事情就是位于这个地址上的值被修改,虽然你并无意去修改他,但是这件事确实发生了。因此引发错误的代码可能和原先用于操作该值的代码完全不相干。所以,在对指针进行间接访问之前,必须非常谨慎,保证指针已经被初始化。 PS: 我在这里入过坑

指针、间接访问和变量

能看懂下面表达式吗?看懂的话,这一小段就不必看了。(纯属技巧,不实用)

*&a = 20;

答案:把值 20 赋值给变量 a 。

下面分析一下:
&操作符表示取变量 a 的地址,是一个指针变量,因此 &a 表示一个地址,也就是 a 的地址。* 操作符会访问 &a 所表示的地址( a 的地址),会取得地址 &a 中的值(就是 a )。这个值就是 20 。
这个过程会涉及到更多的操作,产生的目标代码会更大,而且会使源代码可读性变差。因此很少有人会这么写。

指针常量

假设变量 a 在计算机中存储于地址 100 位置处,下面这条语句是什么作用呢?

*100 = 20;

看上去像是把 20 赋给地址为 100 的位置所存储的变量。其实是错的,因为 100 的类型为整型,而间接访问操作(通过地址来访问变量值)只能用于指针类型表达式。如果想把 25 存储于地址 100 ,必须要用强制类型转换。

*(int *)100 = 20;

也是个基本用不到的操作。强制类型转换把 100 从整型转换成指向整型的指针,这样的地址访问就是合法的了。 基本用不到,了解就行。嵌入式可能会用到。

指针的指针

现在有一下声明:

int a = 20;
int* b = &a;

内存分配如下:
在这里插入图片描述
我们看到指针变量 b 可以访问 a 的地址;( b 中的数据存储的即为 a 的地址 )

c = &b;

内存分配如下:
在这里插入图片描述
是什么意思呢?表示的是取 b 的地址赋给 c;(c 中的数据存储的即为指针 b 的地址,指针 b 中的数据存储的即为指针 a 的地址)
c 是什么类型呢?显然 c 是一个指针
这种表达式合法吗?合法的,指针变量和其它变量一样,占据内存中某个特定的位置,所以用 & 操作符取得它的地址是合法的。
这个变量怎么声明呢?如下:

int** c;

表示变量 a 的地址的地址,也就是指针变量 b 的地址,为一个 int 型指针变量 **c 。

int a = 20;
int* b = &a;
int** c = &b;
表达式 相当的表达式
a 20
b &a
*b a,20
c &b
*c b,&a
**c *b,a,20

指针表达式

首先做以下声明:

char ch = 'a';
char* cp = &ch;

我们有两个变量,初始化如下:
声明:以下虚线 ----- 表示无间接访问操作;实线 —表示有间接访问操作(*)。
在这里插入图片描述
很好理解:指针变量 cp 中的数据存储的为变量 ch 的地址;
从一个简单的表达式开始:

表达式 右值 左值
ch 在这里插入图片描述 在这里插入图片描述

当 ch 作为左值使用时,该表达式的值为该变量 ch 的值’a’
当 ch 作为右值使用时,该表达式的值为该变量ch 内存的地址(而不是该地址包含的值);

表达式 右值 左值
&ch 在这里插入图片描述 非法

当 &ch 作为右值使用时,该表达式的值为变量 ch 的地址;(注意:这个值和变量 cp 中存储的值是一样的,但是这个表达式没提到 cp,因此这个结果不是它产生的,这个地址存在于某个位置的数据里,不一定是 cp );
当 &ch 作为左值使用时,该表达式是非法的,当表达式 &ch 进行求值时,它的结果肯定会存在于某个地方,但是我们无法知道它位于何处。可以结合 ## 未初始化和非法指针 ##的警告看看。

表达式 右值 左值
cp 在这里插入图片描述 在这里插入图片描述

当 cp 作为右值使用时,表示的就是 cp 的值;
当 cp 作为左值使用时,表示的就是 cp 所处的内存地址;

表达式 右值 左值
&cp 在这里插入图片描述 非法

当 &cp 作为右值使用时,表示的是指针变量 cp 的地址,类型是指向字符指针的指针;
当 &cp 作为左值使用时,是非法的。(同样是存储位置未清晰定义)

表达式 右值 左值
*cp 在这里插入图片描述 在这里插入图片描述

当 *cp 作为右值时,通过间接访问操作访问变量 ch 内存地址里面的值,得到‘a’
当 *cp 作为左值时,变量 ch 的地址;

表达式 右值 左值
*cp + 1 在这里插入图片描述 非法

cp + 1 作为右值时,的优先级高于+,所以首先执行间接访问操作,得到的值为a,然后取得这个值的一份拷贝并把它与 1 相加,表达式的最终结果为字符b
当 *cp + 1 作为左值时,不合法。(同样是最终结果的存储位置未清晰定义)
notice:优先级表格证实+的结果不能作为左值。

表达式 右值 左值
*(cp + 1) 在这里插入图片描述 在这里插入图片描述

当 *(cp + 1) 作为右值时,得到的是 ch 之后的内存位置上的值;
当 *(cp + 1)作为左值时,得到的是 ch 之后的内存位置的地址(它本身);

表达式 右值 左值
++cp 在这里插入图片描述 非法

++ 和 – 操作符在指针变量中使用的相当频繁;
当 ++cp 作为右值时,表示 ch 之后的位置的地址,前缀 ++/-- 先增加/减少它的操作数的值再返回这个结果,因此 cp 本身和 ++cp 均指向 ch 之后的位置;
当 ++cp 作为左值时,是一个不合法的左值。(同样是存储位置不清晰,++cp 得到结果后没有指定存储位置)

表达式 右值 左值
cp++ 在这里插入图片描述 非法

当 cp++ 作为右值时,++ 操作先返回 cp 的值的一份拷贝然后再增加 cp 的值,因此这个表达式 cp++ 的值是 cp 原来的值的一份拷贝,仍指向 ch 。但是 cp 本身的值已经是指向 ch 的下一个位置。
当 ++cp 作为左值时,是一个不合法的左值。(同样是存储位置不清晰,cp++ 得到结果后没有指定存储位置)

表达式 右值 左值
*++cp 在这里插入图片描述 在这里插入图片描述

++/-- 的操作符优先级要高于 * 操作符的优先级。
当表达式 ++cp作为右值时,先做地址的自加操作,得到 ch 的下一个位置地址,然后通过间接访问操作符,该表达式得到的是 ch 下一个位置上的值;
当表达式++cp作为左值时,得到的是 ch 下一个位置的地址;

表达式 右值 左值
*cp++ 在这里插入图片描述 在这里插入图片描述

警告:该表达时有点绕
++/-- 的操作符优先级要高于 * 操作符的优先级。
当表达式 *cp++作为右值时,先做地址的自加操作, cp 得到 ch 的下一个位置地址,但是表达式 cp++ 返回的仍是原地址 cp 的拷贝,ch 的地址(参考前面表达式),然后通过间接访问操作符,该表达式得到的是 ch 位置上的值;
当表达式*cp++作为左值时,得到的是 ch 位置的地址;
优先级表格显示后缀 ++ 操作符的优先级高于 * 操作符,这里表达式看上去好像先是执行间接访问操作。
事实上,这里涉及 3 个步骤:

  1. ++ 操作符产生 cp 的一份拷贝;
  2. 然后 ++ 操作符增加 cp 的值;
  3. 最后,在 cp 的拷贝上执行间接访问操作。
表达式 右值 左值
++*cp 在这里插入图片描述 非法

当表达式++*cp作为右值时,在这个表达式中,由于这两个操作符的结合性都是从右向左,所以首先执行的是间接访问操作。然后,cp 所指向的位置的值增加 1 ,变成 b,表达式的结果是这个增值后的值的一份拷贝,也就是b
当表达式++*cp作为左值时,非法的。(同样是存储位置不清晰,没有指定存储位置)。

后面三个表达式应用较少

表达式 右值 左值
(*cp)++ 在这里插入图片描述 非法

后缀 ++ 操作符的优先级高于 * 操作符。
该表达式(*cp)++作为右值时,先执行括号内的间接访问操作,得到 ch 的值,然后执行 ++ 操作符,这时 产生一个拷贝,值为a,ch 的值会被修改为b,但是该表达式返回的值仍未拷贝的值 a
该表达式(*cp)++作为左值时,非法。(原因同上)

以下两个作为自己理解

表达式 右值 左值
++*++cp 在这里插入图片描述 非法
表达式 右值 左值
++*cp++ 在这里插入图片描述 非法

指针变量

指针变量: 本身也是一个变量,用来存储在内存中的地址。
普通变量: 本身也是一个变量,用来存储数据本身的值。

定义

因为C语言是一种相对自由形式的语言,相信你在一些指针代码中可以看到下面两种定义,其实他们是等价的。第二种定义是不是看起来更舒服,更能理解指针的含义呢。其实可以看成一个指针变量pb,他的指针类型是int*。

int* pb;

等价于

int* pb;

但是下面这种看似很舒服的定义在下面这种情况可能会引起另一个误解。下面是这种案例

int* pa, pb, pc;

等价于

int pb, pc, *pa;

等价于

int* pa;
int pb, pc;

现在可以理解定义了吗,如果还是一头雾水,不妨结合下面例子 去理解。

int pa = 10;
int* pb = &pa;
printf("value pa = %d\n", pa);
printf("value *pb = %d\n", *pb);
printf("value &pa = %d\n", &pa);
printf("value pb = %d\n", pb);
printf("value &pb = %d\n", &pb);
printf("value *&pb = %d\n", *&pb);

输出

value pa = 10
value *pb = 10
value &pa = 651283788
value pb = 651283788
value &pb = 651283776
value *&pb = 651283788

我们可以看到*pbpa 是等价的,pb&pa&*pb是等价的。

猜你喜欢

转载自blog.csdn.net/tianxieeryang/article/details/83784851