对变量、指针和数组的一些理解

文章目录

  • 前言
  • 空间和地址
    • 变量
    • 数组和指针
    • 数组
      • 一维数组
      • 二维数组
    • 指针
      • 一级指针与一维数组的结合
        • 指针数组和数组指针
        • 指针函数和函数指针
  • 后记

颜色小技巧:

<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张图片

空间和地址

变量和地址是贯穿始终的,在接下来的变量、数组、和指针中会不断贯穿这两个概率。

对于一个点的地址,是非常容易理解的,它的地址就是一个点,比如射箭的靶心,这是非常明确的,不会让人产生疑惑。而把它放在坐标系里面,就可以用一个坐标点把它描述出来。

但是对于实物来说,它是具有空间大小的,所以对于实物的地址就不像点那样只用一个坐标就能描述得清楚,而且随着实物越来越大,就越难描述清楚这个实物对应的地址。

变量

变量、变量名、变量的地址和变量的值

理解一个知识,就是把不了解的知识拴在已经了解的知识上面,用自己熟悉的知识去解读不了解的知识。

对变量、指针和数组的一些理解_第2张图片

我们用一个鲜明的例子来说明变量、变量名、变量的地址和变量的值。比如今天你看剧的时候渴了,把拿了一个盘子,里面盛装了几个苹果,放到了床单上面。

  • 变量(盘子):这个盛装苹果的盘子就是变量,它是一个容器,用来放东西的空间。
  • 变量的值(苹果):而这个盘子里面盛装的苹果就是变量的值,就是实物。

这时候,你妈妈问你,你拿的是那个盘子,是不是她盛菜的盘子,你告诉她,并不是她盛菜的盘子,而为了区分你拿的盘子和妈妈盛菜的盘子,于是便给这两盘子分别起了名字,妈妈盛菜的盘子就是菜盘,你拿的盘子就叫果盘,这样就给生活带来便利,在描述问题上变得明确了,这时候你可以回应妈妈,你拿得是果盘,这种区分这个和那个的区别而起的名字,叫做标识符。。

  • 变量名(果盘):而对盘子(这个容器)所起的“果盘”(这个标识符)就是变量名字。
  • 变量的地址:就是指代放置这个盘子的位置,即你家看剧这个房间的床单上,值得强调是,这个地址是有大小的,它就是盘子覆盖床单的大小,可以理解成盘子占地面积。

总结一下:

  • 变量————盘子——容器
  • 变量值———苹果——容器所装的东西
  • 变量名———果盘——容器名字
  • 变量地址——床单——放置容器的位置

知识补充:
(1)变量的命名规则

        1.变量名只能是字母(A-Z,a-z)、数字(0-9)或下划线。
        2.第一个字母不能是数字。
        3.不能是C语言关键字,要见名知义。

(2)C语言中的变量分别有哪些?
注意

  • 在计算中,变量指的就是在内存区域中一块空间大小,它是我们看不见的,而用变量名指代的就是这块空间大小,而我们也对变量名字操作也就是对这个变量操作,所以变量和变量名字是一个东西,这就像我们的名字能代表我们本身一样,后面介绍不再区分这两个了统一叫作变量。
  • 为什么要叫做变量呢?可能是可以这个这个容器可以用来装不同的东西,但绝不是和这个容器本身没有关系,因变量一旦定义,它的空间大小和地址已经确认了。

已经熟悉了这几个概念就讲一下变量在程序中运用。在计算机中,对于空间的处理将会简单很多,并不像现实中那样复杂,甚至简化了。在空间方面:现实中关注空间的大小是关注它的长宽高,在计算机中关注空间大小就是关注长度。在地址方面:现实中考虑的地址它的占地面积,即关注长宽两个维度,而在计算中关注地址也就是关注空间对应的长度就行了。

空间方面:
但在C语言中,我们知道变量是根据所存放的东西开辟空间的,但是仅有它一个是开辟不了空间的,很显然对于所存放东西的大小不确定,让它变得很犹豫,这时候需要它需要一把尺子,来度量所存放东西的大小,然后才会在计算的内存中开了空间。

1)a = 5;//错误,a没有度量5的工具,所以很并不会开创空间。2int a = 5;//正确,把5这个东西存放进了空间为4个字节容器里面。

补充知识——这把尺子:
对变量、指针和数组的一些理解_第3张图片
地址方面:
在谈及普通变量时,并不会谈及首地址的问题,这是我学习数组后回来头来看最惊奇的地方。在谈及地址问题前,先看一下现实中怎么处理地址这个问题的,我们知道只要是个东西,有大小的东西,但凡放到地上,它就会占去这个地上的一块面积,而这个面积的轮廓就是这个东西在地上的地址,但是描述起来是非常困难的,这不像一个点那么简单,只用一个坐标就可以指代的非常清楚,就拿老家的房子来说,你怎么让一个陌生人知道你家在哪里呢?现实中我们是这样解决的,我们有大门,而门上有个门牌号,然后用这个门牌号,再加上你家是由围墙围起来的这么大空间时,他就会明白你家的地址了,那些地方是是你家的,那个地方是别人家的,一目了然。而在计算机中无需关注占地面积,即长和宽这两个维度,因为计算机中的空间就是指代长度,地址也是这个空间所对应的占计算机内存的长度,只需要关注长度这一个维度就好了,对于准确寻址的计算机来说,这是一个好事情,但是也面临着一个问题——因为长度带来的问题。
对变量、指针和数组的一些理解_第4张图片

int *p,a = 5;
p = &a;//把变量a的地址赋值给p

我们知道一旦在计算机中的这个内存长条上开辟一个空间时,但凡这个空间有长度就会出现两个地址,这个空间的头部有一个,尾部有一个,但是取应该是那一个呢?根据数组的处理方式,也应该是这个这个变量的首地址。还有一个问题是这个空间首尾是怎么确认的,这个首尾能不能换过来,我想这个应该是按照寻址方向来的,当计算机CPU发出寻址命令后,最先被寻到的那一头就是首地址,而计算机在处理地址问题时都把首地址作为了指代这个空间的地址。计算机在处理地址问题时都把首地址作为了指代这个空间的地址

注意

  • 前面变量中所讲的容器空间都是一个东西,都是根据尺子的长度在计算机内存中这个长条上面占取其部分区域,也可以把容器理解成一个空间放到内存长条上,而这个空间的地址就是这个内存体上的地址。
  • 许多计算机(包括PC和Macintosh)都是按字节编址,意思是内存中的每个字节都是按顺序编号,所以寻址的时候也是按照顺序寻址的。

数组和指针

讲完了变量,下面开始数组和指针。

我们知道有的果盘可以装几个苹果,有的果盘却能装很多个苹果,根据苹果的多少我们把盘子分成了小盘子,中盘子。而在计算中,根据所放东西的多与少,同与不同,我们把容器也做了划分。
------所放东西---------------------容器

  • 一个数据———————变量
  • 多个同类数据—————数组
  • 多个不同类数据————结构体

对变量、指针和数组的一些理解_第5张图片

数组

一维数组

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};

对变量、指针和数组的一些理解_第6张图片
&a就是数组的地址,是指数组a这么一整大块空间的大小所占内存长条条绿色部分的长度就是数组a的地址。
而用仅用它首地址表示,而它的下一个地址将是以这个绿色长条为步长移动。

注意

  • 区分开数组的地址和数组的首地址,它们指向同一个点,但是移动的时候,数组的地址按照数组的大小移动的,数组的首地址是按照首元素的大小移动的,它们的移动步长不一样。
  • 指针名、数组名、函数名,它们分别表示指针所指向元素的地址、数组的首地址和函数的入口地址。
    2、什么是数组中元素的地址?

对变量、指针和数组的一些理解_第7张图片

看上面图片:因为装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);
}

对变量、指针和数组的一些理解_第8张图片
再+1,各自对比一下,这些值到底指代什么?

#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);
 	
}

对变量、指针和数组的一些理解_第9张图片

总结一下:

  • 数组的地址(数组本身的地址)、数组的首地址(数组首元素的地址)
  • 取地址两种方式:
    • 对于变量,采用&取地址符
    • 对于数组名,函数名,指针名,它们本身就是地址,直接当地址使用就好了。

二维数组

在一维度数组中向多维数组发展过程中,采用了矩阵的思想,使得理解更加容易,第几行第几列是什么元素,一目了然,但在程序实现指代方面变得更加复杂,不知道指代的是这行的元素还是该元素的地址。所以需要记住下面这几组数据。

表示形式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的整个的地址

对变量、指针和数组的一些理解_第10张图片

补充:在二维数组中 * & 不要单纯的认为是解引和取址
* ——>指向列指针
&——>指向行指针

#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;	
}

对变量、指针和数组的一些理解_第11张图片

指针

关于指针单独其实没有什么讲的,但是它和其他的组合会打出的招式确实多彩多样的!

	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);
}

对变量、指针和数组的一些理解_第12张图片

#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);
}

对变量、指针和数组的一些理解_第13张图片

一级指针与一维数组的结合

指针数组和数组指针

对变量、指针和数组的一些理解_第14张图片
我们知道数组名代表地址,同样的代表地址的还有函数名,所以也来看一下这个。

指针函数和函数指针

优先级顺序:()>*
int (*p)();//函数指针,指向函数入口的指针;可以调用函数做事情。
int *p();等价于int *(p());//指针函数,返回值为指针的函数;可以接收函数返回的值。

今天的探索就到此为止吧,下一次继续!

留作业:
一级指针与二维数组
二级指针与一维数组
二级指针与二维数组

后记

写的过程难免出现错误,如果你发现了,那么恭喜你,你在这方面的理解超过了很多人,但如果你能用自己的语言通俗易懂地给别人讲清楚,那么你是真正掌握了。我非常提倡你这么做,不管其他地方如何,我的留言区非常欢迎你去指正这篇文章错误之处,谈论自己的独特见解,当然你可以推荐书籍,推荐好文,摘自一些文章的片段在此处。此处期待你的足迹。







你可能感兴趣的:(c语言)