1.什么是指针?
当我们提起指针的时候,可能第一反应会露出惊喜的表情
(但是我们其实没必要那么慌,因为当我们随着我们学习的越来越深入就会发现,指针虽然看起来难,实际上也不怎么简单。哈哈哈开玩笑的,我们都要有迎难而上的勇气哦)
好了我们进入正题:
内存会划分为一个个的内存单元,每个内存单元都有一个独立的编号-编号也称为地址。
地址在C语言中也称为指针,指针(地址)需要存储起来,这个变量就被称为指针变量。
2.指针变量的大小
指针(地址)的大小是固定的4/8个字节(在32位平台上是4个字节,在64位平台上是8个字节)。
3.什么是数组指针
其实数组指针并没有我们想象的那么难以理解,可以类比一下:
(1)整型指针—指向整型变量的指针,存放整型变量的地址的指针变量
(2)字符指针—指向字符变量的指针,存放字符变量的地址的指针变量
数组指针—指向数组的指针,存放的是数组的地址的指针变量
这里我们需要搞清楚谁才是主体,就比如好孩子,他主要是在讲孩子;同理,数组指针,他的主体是指针,而不是数组。
4.二维数组
如果我们要定义一个一维数组,我们只需要定义空间的大小就可以了。
int arr[10]={1,2,3,4,5,6,7,8,9,10};
那如果我们要定义一个二维数组 ,还需要定义它的行数和列数
int arr[3][3]={1,2,3,4,5,6,7,8,9};
我们可以把这个二维数组抽象成这样来理解:
二维数组的每一行可以理解为二维数组的一个元素,每一行又是一个一维数组。所以,二维数组其实是一维数组的数组。
二维数组的数组名就是其首元素的地址,也就是第一行的地址。
5.结合strlen和sizeof来加深对数组和指针的理解
(1) strlen是库函数,是求字符串长度的,统计的是在字符串中\0之前的字符的个数,如果没有\0就会一直往后找。
(2)sizeof是一个单目操作符,主要用来计算类型的大小。
(3)数组名是数组首元素的地址
但是有2个例外:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
接下来我们一起来看几组代码
int main()
{
int a[]={1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
return 0;
}
在开始看解析之前大家可以自己先猜一下运行结果分别是什么。
(1)首先我们来看第一个,a是数组名,单独放在sizeof里面,所以它计算的是整个数组的大小,这是一个整型数组,每个数组元素4个字节,共有4个元素,所以它的大小是4* 4 = 16个字节。
(2)第二个,sizeof里面放的不是单独的数组名,所以它表示的是首元素的地址,是地址就是4/8个字节。这里前往不能想当然了,觉得a+0跟单独的a没什么区别,要额外小心。
(3)第三个,数组名是首元素的地址,对它解引用就能找到数组的首元素,所以它表示首元素的大小,4个字节。
(4)第四个,数组名是首元素的地址,数组名加一就是第二个元素的地址,是地址就是4/8个字节。
(5)第五个,表示数组下标为1的元素,也就是第二个元素的大小,4个字节。
(6)第六个,&数组名取出的是整个数组的地址,是地址就是4/8个字节。
(7)第七个,这里的*和&会相当于会相互抵消,所以其实这个表达式是sizeof(a),所以这里表示整个数组的大小,16个字节。
(8)第八个,&数组名取出的是整个数组的地址,所以它加一跳过的是整个数组,但是就算跳过了整个数组,还是地址,是地址就是4/8个字节。如下图所示:
(9)第九个,&a [0]取出的是数组首元素的地址,是地址就是4/8个字节。
(10)第十个,&a [0] +1取出的是数组第二个元素的地址,4/8个字节。
我们来看一个字符数组:
int main()
{
char arr[]={'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
return 0;
}
大家可以带着自己的答案来看解释。
(1)第一个,数组名单独放在sizeof里面,表示计算整个数组的大小,这是一个字符数组,每个数组元素大小是1个字节,共有6个数组元素,所以是6个字节。
(2)第二个,不是数组名单独放在sizeof里面,所以它表示首元素的地址,是地址就是4/8个字节。
(3)第三个,数组名表示首元素的地址,对它解引用就是首元素,所以这里计算的是首元素的大小,1个字节。
(4)第四个,这里计算数组下标为1,也就是第二个元素的大小,1个字节。
(5)第五个,&数组名,取出的是整个数组的地址,是地址就是4/8个字节。
(6)第六个,&数组名+1,表示跳过整个数组指向的那个地址,是地址就是4/8个字节。
(7)第七个,&arr[0]表示取出第一个元素的地址,再加一就是第二个元素的地址,是地址就是4/8个字节。
我们再来看一组 strlen的,注意, strlen是求字符串长度的,统计的是在字符串中\0之前的字符的个数,如果没有\0就会一直往后找。
int main()
{
char arr[]={'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
return 0;
}
(1)第一个,求’\0’之前的字符串长度,但是我们可以看到,这个数组中并没有’\0’,所以它在统计完数组中所有的元素后,还会继续往后面统计,直到遇到’\0’为止,但是在这个数组的后面我们并不知道存放着什么,所以这里产生的结果就是随机值。
(2)第二个,arr+0是数组首元素的地址,但是从首元素开始一直到最后都没有’\0’,没有’\0’,它就不会停下来,所以这里还是随机值。
(3)第三个,*arr找到的是数组的第一个元素,但是strlen()它的参数应该是地址,如果将a传进去,a的ASCII码值是97,那它就会从地址是97的地方开始统计,但是我们并不知道地址是97的内存里面存放了什么,这样就会造成非法访问,所以这里的写法是错误的,error。
(4)第四个,arr [1]表示数组的第二个元素,写法错误,error。
(5)第五个,&数组名取出整个数组的地址,整个数组的地址也是从首元素开始,所以这里还是随机值。
(6)第六个,&数组名+1,跳过整个数组,我们并不知道数组后面的内存情况,所以是随机值。
(7)第七个,&arr [0] +1找到的是第二个元素的地址,结果是随机值。
来看另外一组字符串的,字符串后面默认会有一个’\0’。
int main()
{
char arr[]="abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
return 0;
}
这就是数组里面实际的情况。
(1)第一个,统计数组中的字符个数,我们可以看到在’\0’前面共有6个字符,所以结果就是6。
(2)第二个,表示首元素地址,从首元素一直到’\0’有6个字符,所以结果是6。
(3)第三个,*arr表示第一个元素,非法访问,error。
(4)第四个,arr [1]表示第二个元素,非法访问,error。
(5)第五个,&arr,从首元素开始到’\0’,共6个字符,结果是6。
(6)第六个,&arr+1,会跳过整个数组,指向’\0’后面,所以结果是随机值。
(7)第七个,&arr[0] +1,表示指向第二个元素,从第二个元素开始到’\0’共有5个字符,所以结果是5。
我们再来看一组
int main()
{
char arr[]="abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
return 0;
}
(1)第一个,计算的是整个数组的大小,前面的6个字符再加上后面隐藏的’\0’,共7个字符,所以结果是7。
(2)第二个,表示首元素地址,4/8个字节。
(3)第三个,表示第一个元素大小,1个字节。
(4)第四个,表示第二个元素大小,1个字节。
(5)第五个,表示整个数组的地址,4/8个字节。
(6)第六个,表示指向跳过整个数组之后的地址,4/8个字节。
(7)第七个,表示指向数组的第二个元素的地址,4/8个字节。
我们再来看一组指针的:
int main()
{
char* p="abcdef";
printf("%d\n",sizeof(p));
printf("%d\n",sizeof(p+1));
printf("%d\n",sizeof(*p));
printf("%d\n",sizeof(p[0]));
printf("%d\n",sizeof(&p));
printf("%d\n",sizeof(&p+1));
printf("%d\n",sizeof(&p[0]+1));
return 0;
}
z这里先提前说明一点,指针指向一个字符串,其实指向的是字符串的第一个字符的地址。所以这里p指向的是a的地址。
(1)第一个,p指向的是a的地址,所以是4/8个字节。
(2)第二个,p指向的是a的地址,p+1指向的就是b的地址,4/8个字节。
(3)第三个,p指向的是a的地址,对它解引用找到的就是a,所以这里计算的是a的大小,1个字节。
(4)第四个,p [0] = *(p+0) = *p,和上面的一样,计算的是a的大小,1个字节。
(5)第五个,&p表示取p的地址,4/8个字节。
(6)第六个,&p+1,表示指向p后面的地址,4/8个字节。
如果大家对这里比较疑惑的话,可以看图理解:
(7)第七个,&p [0]表示取出a的地址,再加一表示指向b的地址,4/8个字节。
我们再来下一组:
int main()
{
char* p="abcdef";
printf("%d\n",strlen(p));
printf("%d\n",strlen(p+1));
printf("%d\n",strlen(*p));
printf("%d\n",strlen(p[0]));
printf("%d\n",strlen(&p));
printf("%d\n",strlen(&p+1));
printf("%d\n",strlen(&p[0]+1));
return 0;
}
(1)第一个,因为p指向数组的第一个字符,所以从a开始一直到字符串最后的’\0’,共6个字符,所以结果是6。
(2)第二个,p+1指向第二个字符,结果是5。
(3)第三个,*p表示第一个字符,这里strlen 需要的参数是地址,所以是非法访问,error。
(4)第四个, p [0] = *(p+0) =*p,表示第一个字符,非法访问,error。
(5)第五个,&p表示取出p的地址,p里面的情况我们并不清楚,所以是随机值。
(6)第六个,&p+1,表示指向p后面的地址,p后面的情况我们并不清楚,所以是随机值。
(7)第七个,p [0] = *p,表示第一个字符,再对它取地址就是取a的地址,再加一就是指向b的地址,5个字节。
好啦,我们再来看最后一组重量级的压轴选手:
int main()
{
int a[3][4]={0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
return 0;
}
这里给大家一张二维数组的抽象图来帮助理解。每一行就相当于一个一位数组,如果想描述一个具体元素的位置,就必须标明它的行和列,比如a [1] [1] ,就是第一行第一列的元素,但如果是a [1] ,则表示第一行的那个数组的数组名,而我们找到数组名通常表示数组首元素的地址,那一维数组a[1] 的首元素就是a[1] [0] ,对照上图理解会更容易。
(1)第一个,数组名单独放在sizeof里,表示整个数组的大小,这个数组共有12个元素,每个整型元素4个字节,所以结果是12 * 4 = 48个字节。
(2)第二个,表示求第0行第0列元素的大小,4个字节。
(3)第三个,a [0] 是第0行这个一维数组的数组名,所以这里是数组名单独放在sizeof里的情况,表示整个数组的大小,第0行共有4个元素,所以大小是16个字节。
(4)第四个,a [0]作为第0行的数组名,没有单独放在sizeo内部,没有&,a[0]表示数组首元素的地址,也就是a[0][0]的地址,所以a [0] +1是第0行第2个元素的地址,是地址就是4/8个字节。
(5)第五个,a [0] +1则表示第0行第2个元素的地址,对它解引用就可以找到第0行第2个元素,其大小是4个字节。
(6)第六个,数组名a表示首元素的地址,对二维数组来说,第0行就是它的首元素,a+1指向它的第二个元素的地址,也就是第1行的首元素的地址,是地址就是4/8个字节。
(7)第七个,* (a+1)= a [1] ,这个[ ]的运算在前面也出现过几次,需要我们格外小心注意,(我错好多次了呜呜呜),所以这里算是一维数组的数组名单独放在sizeof里的情况,计算第一行的元素大小,第一行4个元素,结果是16个字节。
(8)第八个,&a [0]表示取第0行的地址,加一跳过第0行,指向第1行的地址,即a [1] 的地址,是地址就是4/8个字节。这里不是整型指针,而是整型数组指针,即 int (*) [4] ,加一要跳过一个数组。
(9)第九个,对上面的数组指针解引用,得到的就是第一行的数组的元素,第一行共4个元素,16个字节。
(10)第十个,数组名a表示二维数组首元素,即第0行的地址,对其解引用得到的就是第0行的元素,16个字节。
(11)第十一个,看到a [3] 我们的第一反应是不对劲,这个数组一共才2行,哪里来的第三行,肯定是越界了,会出错。但如果我们去运行代码的话,会发现它还是会正常运行,是因为sizeof其实不会真正去访问内存,它只负责识别类型,然后计算它的大小,这里的a [3] 表示的情况还是数组名单独放在sizeof里的情况,计算的是整个数组的大小,我们给出的这个数组一行有4个元素,所以结果是16个字节。
本次的分享就到此为止啦,希望能够对你有帮助哦!