<font color = red size = 5>欢</font>
<font color = orange size = 5>迎</font>
<font color = yellow size = 5>您</font>
<font color = gree size = 5>下</font>
<font color = blue size = 5>次</font>
<font color = pistac size = 5>再</font>
<font color = purple size = 5>来</font>
我不知道这样的理解是否正确,可能以后回过头来看荒谬得一批,但为什么还是要写呢?我认为掌握知识的路程更像是不断修正的过程,纵观科学的发展,以前总认为地心说是正确的,后来随着历法的核证,让人们开始怀疑,经过哥白尼的发现,逐渐修正了以前错误的观点,日心说是正确的啦。以后可能会随着探索的宇宙范围更大,日心说的理论也会面临着颠覆性的认知而被修改。这启发我们要时刻保持怀疑的态度,就像达尔文那样,不断寻找证据去摧毁自己心爱的观点,相较而言,就会接近真理。那写这些可能错误的有什么必要嘛,有必要的,因为空中不可能建起楼阁,它就是不断接近真理的必经方式,而从无到有的探索中,只要探索,就会出现错误,但只要保持怀疑的心态和科学的方法,达到过某一个高度,我们再次想要从0到这一高度就会轻驾救赎,这是因为我们从错误中得到了经验,知道怎么做才会更好,而如果能从别人的错误中得到经验就会提升得更快,这也是我为什么写的原因。
随着对指针和数组这块的学习,我似乎有了一些想法,空间和地址这些概念进入了我的脑中,试图把这些联系起来,我尝试着把这些整理了一下,有了初步的想法,下面就是我整理的一些思路,关于更加完善的思考,留到后面打磨,这里面仅仅是一个初步的轮廓。
首先讲一下变量,变量是一个数据的空间,而在现实中是有很多数据的,很多同类数据和很多不同类型的数据,所以数组和结构体应用而生。而提到数组,似乎指针就必须和它相伴,指针操作与硬件挂钩,具有天然操作神速的优势,在一维数组中尽展本领,而在一维向多维的发展中,采用了矩阵的思想,这个数据在第几行第几列一目了然,理解起来更加容易,但是在理解指代方面(什么时候是指代这个数据的地址,什么时候是这个数据的值)就变得复杂起来。而数组、指针和函数等一些列的组合使得这些非常复杂,一组指针数组和数组指针,第二组指针函数和函数指针便是最典型的。关于这里要讲得重点是变量、数组(一维,二维)和指针,以及系列的组合,其他的有涉及的只做提及。
变量和地址是贯穿始终的,在接下来的变量、数组、和指针中会不断贯穿这两个概率。
对于一个点的地址,是非常容易理解的,它的地址就是一个点,比如射箭的靶心,这是非常明确的,不会让人产生疑惑。而把它放在坐标系里面,就可以用一个坐标点把它描述出来。
但是对于实物来说,它是具有空间大小的,所以对于实物的地址就不像点那样只用一个坐标就能描述得清楚,而且随着实物越来越大,就越难描述清楚这个实物对应的地址。
变量、变量名、变量的地址和变量的值
理解一个知识,就是把不了解的知识拴在已经了解的知识上面,用自己熟悉的知识去解读不了解的知识。
我们用一个鲜明的例子来说明变量、变量名、变量的地址和变量的值。比如今天你看剧的时候渴了,把拿了一个盘子,里面盛装了几个苹果,放到了床单上面。
这时候,你妈妈问你,你拿的是那个盘子,是不是她盛菜的盘子,你告诉她,并不是她盛菜的盘子,而为了区分你拿的盘子和妈妈盛菜的盘子,于是便给这两盘子分别起了名字,妈妈盛菜的盘子就是菜盘,你拿的盘子就叫果盘,这样就给生活带来便利,在描述问题上变得明确了,这时候你可以回应妈妈,你拿得是果盘,这种区分这个和那个的区别而起的名字,叫做标识符。。
总结一下:
知识补充:
(1)变量的命名规则
1.变量名只能是字母(A-Z,a-z)、数字(0-9)或下划线。 2.第一个字母不能是数字。 3.不能是C语言关键字,要见名知义。
(2)C语言中的变量分别有哪些?
注意:
已经熟悉了这几个概念就讲一下变量在程序中运用。在计算机中,对于空间的处理将会简单很多,并不像现实中那样复杂,甚至简化了。在空间方面:现实中关注空间的大小是关注它的长宽高,在计算机中关注空间大小就是关注长度。在地址方面:现实中考虑的地址它的占地面积,即关注长宽两个维度,而在计算中关注地址也就是关注空间对应的长度就行了。
空间方面:
但在C语言中,我们知道变量是根据所存放的东西开辟空间的,但是仅有它一个是开辟不了空间的,很显然对于所存放东西的大小不确定,让它变得很犹豫,这时候需要它需要一把尺子,来度量所存放东西的大小,然后才会在计算的内存中开了空间。
(1)a = 5;//错误,a没有度量5的工具,所以很并不会开创空间。
(2)int a = 5;//正确,把5这个东西存放进了空间为4个字节容器里面。
补充知识——这把尺子:
地址方面:
在谈及普通变量时,并不会谈及首地址的问题,这是我学习数组后回来头来看最惊奇的地方。在谈及地址问题前,先看一下现实中怎么处理地址这个问题的,我们知道只要是个东西,有大小的东西,但凡放到地上,它就会占去这个地上的一块面积,而这个面积的轮廓就是这个东西在地上的地址,但是描述起来是非常困难的,这不像一个点那么简单,只用一个坐标就可以指代的非常清楚,就拿老家的房子来说,你怎么让一个陌生人知道你家在哪里呢?现实中我们是这样解决的,我们有大门,而门上有个门牌号,然后用这个门牌号,再加上你家是由围墙围起来的这么大空间时,他就会明白你家的地址了,那些地方是是你家的,那个地方是别人家的,一目了然。而在计算机中无需关注占地面积,即长和宽这两个维度,因为计算机中的空间就是指代长度,地址也是这个空间所对应的占计算机内存的长度,只需要关注长度这一个维度就好了,对于准确寻址的计算机来说,这是一个好事情,但是也面临着一个问题——因为长度带来的问题。
int *p,a = 5;
p = &a;//把变量a的地址赋值给p
我们知道一旦在计算机中的这个内存长条上开辟一个空间时,但凡这个空间有长度就会出现两个地址,这个空间的头部有一个,尾部有一个,但是取应该是那一个呢?根据数组的处理方式,也应该是这个这个变量的首地址。还有一个问题是这个空间首尾是怎么确认的,这个首尾能不能换过来,我想这个应该是按照寻址方向来的,当计算机CPU发出寻址命令后,最先被寻到的那一头就是首地址,而计算机在处理地址问题时都把首地址作为了指代这个空间的地址。计算机在处理地址问题时都把首地址作为了指代这个空间的地址
注意:
讲完了变量,下面开始数组和指针。
我们知道有的果盘可以装几个苹果,有的果盘却能装很多个苹果,根据苹果的多少我们把盘子分成了小盘子,中盘子。而在计算中,根据所放东西的多与少,同与不同,我们把容器也做了划分。
------所放东西---------------------容器
int a[3] = {1,2,3};
关于一维数组,这里的重点介绍关于地址方面的两个问题:
1、什么是数组的地址?
小知识:为什么数组下标要从0开始编号?
为什么数组下标要从0开始编号,而不是从1开始呢?从1开始不是更符合人类的思维习惯吗?
从数组存储的内存模型上来看,下标实际上指的是偏移量(offset)。例如:一个整型数组 int arr[LEN],
* 从0开始编号,元素arr[i]的寻址计算公式是:address(arr[i]) = address(arr) + i * sizeof(int)。
* 从1开始编号,元素arr[i]的寻址计算公式是:address(arr[i]) = address(arr) + (i - 1) * sizeof(int)。
对比两个公式,从1开始编号会多一次减法运算,对应到CPU就会多一条减法指令。而数组取下标是一个高频操作,故从0开始效率更高。另外,C语言的设计者从0开始编号数组元素,之后的C++、Java、Python等高级语言也沿用了C的编号习惯,这也在一定程度上降低了C语言程序员学习其他编程语言的成本。当然,并不是所有语言的数组都是从0开始编号,比如MATLAB。
int a[3] = {1,2,3};
&a就是数组的地址,是指数组a这么一整大块空间的大小所占内存长条条绿色部分的长度就是数组a的地址。
而用仅用它首地址表示,而它的下一个地址将是以这个绿色长条为步长移动。
注意:
看上面图片:因为装1的空间和数组a的空间是对齐的,它们的地址也是指向同一个点的,可能会误解成它们两者的地址一样,这是一个误区,因为它们两个的空间大小并不一样,数组a的空间大于装1元素的空间,且是装1元素的空间的3倍,所以在考虑地址时,请正确理解地址的含义,地址是这个空间在内存中所占的长度,而为了准确指代就是用了首地址的方法。
数组名a是该数组的首元素的地址,下面为测量过程。
#include
int main(int argc, char **argv) {
int a[3] = {1,2,3},*p1,*p2,*p3;
p1 = a;
p3 = &a[0];
printf("p1=%d,&a=%d,p3=%d",p1,&a,p3);
}
#include
int main(int argc, char **argv) {
int a[3] = {1,2,3},*p1,*p3;
p1 = a;
p3 = &a[0];
printf("p1=%d,&a=%d,p3=%d\n",p1,&a,p3);
printf("p1+1=%d,&a+1=%d,p3+1=%d",p1+1,&a+1,p3+1);
}
总结一下:
在一维度数组中向多维数组发展过程中,采用了矩阵的思想,使得理解更加容易,第几行第几列是什么元素,一目了然,但在程序实现指代方面变得更加复杂,不知道指代的是这行的元素还是该元素的地址。所以需要记住下面这几组数据。
表示形式1 | 表现形式2 | 表示地址 |
---|---|---|
a[0]+0 | *(a+0) | 第0行0列 |
a[1]+0 | *(a+1) | 第1行0列 |
a[1]+1 | *(a+1) +1 | 第1行1列 |
a[1]+2 | *(a+1) +2 | 第1行2列 |
a[2]+1 | *(a+2) +1 | 第2行1列 |
a[2]+2 | *(a+2) +2 | 第2行2列 |
a[n]+m | *(a+n) +m | 第n行m列 |
表示形式1 | 表现形式2 | 表示值 |
---|---|---|
a[0][0] | * (* (a+0)) | 第0行0列 |
a[1][0] | * ( *(a+1)) | 第1行0列 |
a[1][1] | *( *(a+1) +1) | 第1行1列 |
a[1][1] | * ( *(a+1) +2) | 第1行2列 |
a[2][1] | * ( *(a+2) +1) | 第2行1列 |
a[2][2] | * ( *(a+2) +2) | 第2行2列 |
a[n][m] | * ( *(a+n) +m) | 第n行m列 |
#include
int main(int argc, char **argv) {
int a[2][3] = {{1,2,3},{4,5,6}},*p;
printf("a[0][0]=%d,a[0][2]=%d,a[1][2]=%d\n",a[0][0],a[0][2],a[1][2]);
printf("&a[0][0]=%d,&a[0][2]=%d,&a[1][0]=%d\n",&a[0][0],&a[0][2],&a[1][0]);
printf("a=%d,a+1=%d,&a=%d,&a+1=%d\n",a,a+1,&a,&a+1);
printf("*a=%d,*(a+1)=%d,*(&a)=%d,*(&a+1)=%d\n",*a,*(a+1),*(&a),(&a+1));
return 0;
}
//a是数组a的首行的地址
//&a是数组a的整个的地址
补充:在二维数组中 * & 不要单纯的认为是解引和取址
* ——>指向列指针
&——>指向行指针
#include
int main(int argc, char **argv) {
int a[2][3] = {{1,2,3},{4,5,6}},*p;
//*-----> 列
printf("*-----> 列\n");
printf("a=%d\n",a);//数组a的首行地址
printf("*a=%d\n",*a);//a[0][0]的地址
printf("*a+1=%d\n",*a+1);
printf("a+1=%d\n",a+1);
//&-----> 行
printf("&-----> 行\n");
printf("a[0]=%d\n",a[0]);//a[0][0]的地址
printf("a[0][0]=%d\n",a[0][0]);//a[0][0]的值
printf("&a[0][0]=%d\n",&a[0][0]);//a[0][0]的地址
printf("&a[0]=%d\n",&a[0]);//数组a的首行地址
printf("&a[0]+1=%d\n",&a[0]+1);//
return 0;
}
关于指针单独其实没有什么讲的,但是它和其他的组合会打出的招式确实多彩多样的!
int a = 5,*p;
p = &a;
int a = 5,*p;
*p = a;
//对于指针的理解,有两点
//一则:用p把a空间的地址获取到(p = &a;)。
//二则:通过a空间地址,用*解引指向a空间的值。
#include
int main(int argc, char **argv) {
int a = 5,*p;
p = &a;
printf("&a=%d\n",&a);
printf("p=%d\n",p);
printf("*p=%d\n",*p);
}
#include
int main(int argc, char **argv) {
int a = 5,*p;
*p = a;
printf("&a=%d\n",&a);
printf("&p=%d\n",&p);
printf("*(&p)=%d\n",*(&p));
printf("**(&p)=%d\n",**(&p));
printf("*p=%d\n",*p);
}
我们知道数组名代表地址,同样的代表地址的还有函数名,所以也来看一下这个。
优先级顺序:()>*
int (*p)();//函数指针,指向函数入口的指针;可以调用函数做事情。
int *p();等价于int *(p());//指针函数,返回值为指针的函数;可以接收函数返回的值。
今天的探索就到此为止吧,下一次继续!
留作业:
一级指针与二维数组
二级指针与一维数组
二级指针与二维数组
写的过程难免出现错误,如果你发现了,那么恭喜你,你在这方面的理解超过了很多人,但如果你能用自己的语言通俗易懂地给别人讲清楚,那么你是真正掌握了。我非常提倡你这么做,不管其他地方如何,我的留言区非常欢迎你去指正这篇文章错误之处,谈论自己的独特见解,当然你可以推荐书籍,推荐好文,摘自一些文章的片段在此处。此处期待你的足迹。
欢
迎
您
下
次
再
来