前言
九、数组和指针
数组–>能够存放一组相同类型的元素,数组的大小取决于数组的元素个数和元素类型
指针–>就是地址或者也说是指针变量,大小是4/8个字节
二者之间的关系:
(1)数组是数组,指针是指针,二者不等价
(2)数组名是数组首元素的地址,这个地址可以存放在指针变量中
(3)我们可以用指针来遍历数组
(4)数组名
大多数情况下,数组名是数组首元素的地址.
但是有两个例外:
一是sizeof(数组名)–>数组名表示整个数组,计算的是整个数组的大小
二是&数组名–>数组名表示整个数组,取出的是数组的大小
1.透彻理解整型数组
以下内容的重点是区分下面三种写法的区别是什么以及sizeof和strlen在这三种写法中的使用
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a[] = { 1,2,3,4 }; printf("%d\n", sizeof(a));//16 //一个整型是四个字节,sizeof(数组名)就是例外情况之一,计算的是数组总大小,单位是字节,所以是16 printf("%d\n", sizeof(a + 0));//4 //注意X86环境32位,X64环境64位,目前是X86环境 //sizeof(a+0)不是把数组名单独放在siaeof内部,所以这里的a表示数组名首元素的地址 //a+0其实是数组首元素的地址 //首元素的地址+0还是首元素的地址,大小是首元素地址的大小,4(X86环境)/8(X64环境)个字节 printf("%d\n", sizeof(*a));//4 //不是数组名单独放在sizeof内部,所以这里的a表示数组首元素的地址,a等价于&a[0] //*a等价于*&a[0]等价于a[0] //首元素的大小也就是一个整型的大小4 printf("%d\n", sizeof(a + 1));//4/8 //不是数组名单独放在sizeof内部,所以这里的a表示数组首元素的地址,a的类型是int* //a+1 跳过一个整型,是第二个元素的地址.是地址的话就是4/8个字节 printf("%d\n", sizeof(a[1]));//4 //数组下标为1的元素的大小,数组每个元素都是整型,大小为4 printf("%d\n", sizeof(&a));//4/8 //&a 取出的是整个数组的地址,是地址就是4个或者8个字节,即使它是整个数组的地址也仅仅是个地址!!! //&a 要是想存起来,需要放到一个数组指针里面 //int (*pa)[4]=&a; 这里&a的类型是int (*)[4] printf("%d\n", sizeof(*&a));//16 //&a 拿到整个数组 //*&a 解引用整个数组 //整个数组的大小是4*4=16字节 //事实上,*和&可以相互抵消,在这里sizeof(*&a)等价于sizeof(a),也就是整个数组的大小16 printf("%d\n", sizeof(&a + 1));//4/8 //&a 拿到整个数组的地址,它的类型是数组指针int (*)[4] //&a+1 数组指针+1指的是地址跳过一个数组的,但是其实它还是指向内存里面的,是地址就是4/8个字节 printf("%d\n", sizeof(&a[0]));//4/8 //取出首元素的地址 4/8个字节 printf("%d\n", sizeof(&a[0] + 1));//4/8 //第二个元素的地址 }
图解:
2.透彻理解字符数组(区分sizeof和strlen)
重点:区分sizeof和strlen
sizeof()👻👻👻
1.sizeof计算的是占用内存空间的大小,单位是字节,不关注内存中到底存放的是什么
2.注意sizeof不是函数,是操作符
strlen()👻👻👻
1.strlen是函数
2.strlen是针对字符串的,求的是字符串的长度,本质上统计的是\0之前出现的字符个数
(1)sizeof和strlen在字符数组中的应用
定义一个字符数组
char arr[] = { ‘a’,‘b’,‘c’,‘d’,‘e’,‘f’ };
sizeof用法:
printf("%d\n", sizeof(arr));//6
sizeof(数组名) 数组名表示整个数组的地址
sizeof计算的是占用内存空间的大小,单位是字节,不关注内存中到底存放的是什么
sizeof(arr) 计算整个数组的大小,6个字符型元素总大小为6字节
printf("%d\n", sizeof(arr + 0));//4/8
数组名没有单独放在sizeof内部,这里是首元素的地址
arr+0 还是数组首元素的地址
不管是什么地址,只要是地址就是4/8个字节
printf("%d\n", sizeof(*arr));//1
这里的arr是首元素地址
arr是首元素,计算的是首元素的大小
数组名的类型是char即数组里面每个元素的类型都是char*这里的arr是首元素的地址,解引用,也就是char*类型的解引用,访问一个字节
printf("%d\n", sizeof(arr[1]));//1
arr[1]是数组的第二个元素,一个字符它的大小为1个字节
printf("%d\n", sizeof(&arr));//4/8
&arr取出的是数组的地址
数组的地址也是地址,为4/8个字节
printf("%d\n", sizeof(&arr + 1));//4/8
&arr 数组的地址 +1 代表跳过一个数组后的地址
但是它还是一个地址,地址的长度为4/8个字节
printf("%d\n", sizeof(&arr[0] + 1));//4/8
&arr[0] 下边为0的元素的地址 +1 跳过这个元素的地址
实质上是第二个元素的地址,但还是地址,为4/8个
strlen的用法
printf("%d\n", strlen(arr));//随机值
arr指向首元素的地址
strlen是针对字符串的,求的是字符串的长度,本质上统计的是\0之前出现的字符个数
\0之前是多少个字符,长度就是多少,strlen会一直往后数,直到遇到\0停止
但是不知道会什么时候遇到\0,所以这个长度是个随机值
printf("%d\n", strlen(arr + 0));//随机值
arr指向首元素的地址 +0 还是指向首元素的地址
传进来首元素的地址,strlen会一直往后数,直到遇到\0停止
但是不知道会什么时候遇到\0,所以这个长度是个随机值
printf("%d\n", strlen(*arr));//非法访问
在之前的学习中,模拟实现strlen功能的函数是这么写的:
my_strlen(const char* str){}
注意传给strlen的是地址,arr指向首元素的地址,解引用得到首元素’a’,所以传进去的是字符a,而字符a的本质是97
把97传给strlen,strlen就把97当成一个地址,所以strlen就从97这个地址编号往后数,97作为地址编号找到的内存空间,不是给我们分配的内存空间,97是我们随便传入的一个地址,就要去访问它吗?这样是不行的
所以这里会形成非法访问的
printf("%d\n", strlen(arr[1]));//非法访问
arr[1]是字符b,这里会把b的ASCII值当成地址,b–98,同上会形成非法访问
printf("%d\n", strlen(&arr));//随机值
&arr是数组的地址,但传给strlen之后还是从数组的起始位置开始往后找\0,它数到的也是随机值
printf("%d\n", strlen(&arr + 1));//随机值-6
&arr+1 跳过整个数组,然后往后数找\0,也不知道什么时候能遇到\0,结果也是个随机值
但是这个随机值一定比从数组原始位置开始往后数时得到的随机值小6,因为开始的位置不同
printf("%d\n", strlen(&arr[0] + 1));//随机值-1
是从第二个元素的地址开始往后数的,往后数找\0,也不知道什么时候能遇到\0,结果也是个随机值
但是这个随机值一定比从数组原始位置开始往后数时得到的随机值小1,因为开始的位置不同
(2)sizeof和strlen在用字符串初始化数组中的应用
用字符串初始化数组:
char arr[] = “abcdef”;
[0] a
[1] b
[2] c
[3] d
[4] e
[5] f
[6] \0
在数组里面实际上放了7个字符
sizeof用法
printf("%d\n", sizeof(arr));//7
arr单独放在sizeof内部,sizeof计算的是整个数组的大小,关心的数组的大小,不关心里面是不是\0
所以它的大小是7
printf("%d\n", sizeof(arr + 0));//1
arr是首元素的地址,首元素的地址+0,还是首元素的地址,是地址就是4个或者8个字节
printf("%d\n", sizeof(*arr));//1
arr是首元素的地址,arr 是数组首元素,每个元素都是char类型,每个元素的大小都是一个字节
补充:首元素可以怎么表示?
arr[0]或者arr或者*(arr+0)计算数组的大小时,可以写成:
int sz=sizeof(arr)/sizeof(arr[0];
int sz=sizeof(arr)/sizeof(*arr);
printf("%d\n", sizeof(arr[1]));//1
arr[1]就是第二个元素的大小
数组每个元素都是char类型,每个元素的大小都是一个字节
printf("%d\n", sizeof(&arr));//4/8
&arr取出的是整个数组的地址,是地址,它的长度就是4或者8个字节
printf("%d\n", sizeof(&arr + 1));//4/8
&arr取出的是整个数组的地址 +1 跳过整个数组之后的地址
是地址就是4/8个字节
注意:与之前I.里面跳过整个数组的地址,在跳过的内容上有所区别,之前跳过整个数组的时候数组里面没有\0.这次跳过的数组里面包含\0
printf("%d\n", sizeof(&arr[0] + 1));//4/8
&arr[0] 取到的是第一个元素的地址,+1 是跳过第一个元素后的地址
是地址,就是4/8个字节
strlen用法
printf("%d\n", strlen(arr));//6
arr是首元素的地址,strlen从首元素的地址开始往后数找\0,\0前面有6个元素,长度为6
printf("%d\n", strlen(arr + 0));//6
arr数组名表示首元素的地址 +0 还是首元素的地址
strlen从首元素的地址开始往后数找\0,\0前面有6个元素,长度为6
printf("%d\n", strlen(*arr));//非法访问
*arr 是第一个元素a的值传给strlen,当成地址去访问会造成非法访问
printf("%d\n", strlen(&arr));//6
&arr 整个数组的地址
&arr的类型是数组指针char(*)[7]
而实际上strlen应该穿进去的是const char 类型的指针(地址),在这里的话非要传的类型是数组指针,这时就会把它强制转换成const char类型的
在这里编译的时候会报警告,但是不影响使用
strlen数的时候还是从起始的位置开始数,直到遇到\0,长度应该为6
printf("%d\n", strlen(&arr[0] + 1));
&arr[0] 首元素的地址 +1 跳过首元素的地址 往后数直到遇到\0.可以数到5个
注意:
strlen()进去的应该是地址,不应该是元素,如果传进去元素,会把元素的值当成地址进行访问,造成非法访问
(3)当把常量字符串的首地址放进指针变量,sizeof和strlen的用法
char* p = “abcdef”;
sizeof:
printf("%d\n", sizeof(p));//4/8
这里算的是一个指针变量的大小,既然是指针变量,指针变量是用来存放地址的,所以指针变量的大小是4或者8
printf("%d\n", sizeof(p + 1));//4/8
p里面是常量字符串的首地址也就是a的地址,p+1 p的类型是char*,char*类型的+1跳过一个字符,实际上也就是字符b的地址
是地址,就是4个或者8个字节
printf("%d\n", sizeof(*p));//1
p存放的是常量字符串的首地址也就是a的地址,解引用访问一个字符,一个字符的大小就是1
printf("%d\n", sizeof(p[0]));//1
p[0]从数组的访问形式可以转成指针
p[0]可以写成*(p+0),也就是*p
p存放的是常量字符串的首地址也就是a的地址,解引用访问一个字符,一个字符的大小就是1
printf("%d\n", sizeof(&p));//4/8
&p 拿到的是地址,是地址长度就是4/8个字节
不过要清楚的是&p是指针变量p的地址,而不是常量字符串的地址
&p是个二级指针
printf("%d\n", sizeof(&p + 1));//4/8
p类型char*
&p类型char**
char* p;//字符指针跳过一个字符
char* pp=&p; //(第二个说明pp是指针,第一个说明pp里面指向的是char类型的数据)pp指向的是char的数据,+1跳过一个char的数据
但是还是地址,所以是四个或者八个字节
printf("%d\n", sizeof(&p[0] + 1));//4/8
&p[0]取出a的地址,+1跳过a的地址,是字符b的地址
但还是地址,是地址就是四个或者八个字节
strlen的用法
printf("%d\n", strlen(p + 1));//5
p+1 跳过a的地址,里面放的是b的地址,从b开始往后数,字符串末尾是\0,所以长度是5
printf("%d\n", strlen(*p));//非法访问
*p是a,传过去但是strlen需要的是地址,那就把a的ASCII码值当做地址,形成了非法访问
printf("%d\n", strlen(p[0]));//非法访问
p[0]和p是一样的,因为p[0]等价于(p+0),都是a,传过去但是strlen需要的是地址,那就把a的ASCII码值当做地址,形成了非法访问
printf("%d\n", strlen(&p));//随机值x
注意&p拿出来的是哪里的地址,
拿出p的地址然后往后数,p里面存的地址是什么以及后面什么时候遇到\0这完全不可知
所以这里只能是随机值
printf("%d\n", strlen(&p + 1));//随机值y
&p+1 跳过p的地址往后数,什么时候遇到\0,这就更无法预测了,所以也是随机值
但是这个随机值y和上面的随机值x毫无关系,完全取决于这个内存块里面放的是什么
printf("%d\n", strlen(&p[0] + 1));//5
&p[0]+1 是b的地址 ,从b的地址往后数,遇到\0,所以长度是5
3.透彻理解二维数组
创建一个二维数组
int a[3] [4] = { 0 };
二维数组:
二维数组的首元素是第零行
printf("%d\n", sizeof(a));//48
a这个二维数组的数组名单独放在sizeof内部,计算整个数组的大小
这个二维数组三行四列,每个元素是整型,一个整型四个字节,434=48字节
printf("%d\n", sizeof(a[0][0]));//4
第一行第一个元素,大小是四个字节
printf("%d\n", sizeof(a[0]));//16
a[0]是第零行的数组名,这时数组名单独放在sizeof内部,计算的是数组的大小,单位是字节
第零行是四个整型,4*4=16字节
printf("%d\n", sizeof(a[0] + 1));//4/8
arr[0]数组名不是单独放在sizeof内部,a[0]表示表示首元素的地址,即第一行第一个元素的地址也就是a[0][0]的地址
+1 第一行第二个元素的地址即a[0][1]的地址
是地址就是4/8个字节
printf("%d\n", sizeof(*(a[0] + 1)));//4
*(a[0]+1) 也就是解引用第一行第二个元素的地址,得到a[0][1] 大小是4个字节
printf("%d\n", sizeof(a + 1));//4/8
a作为二维数组的数组名,并非单独放在sizeof内部,所以表示首元素的地址
二维数组的首元素是第一行,这里的a是第一行的地址,第一行的地址是数组指针int ()[4]
当+1的时候,跳过一行,
a+1 是第二行的地址,注意不是第二行首元素的地址,因为a的类型是数组指针int ()[4],+1之后还是这个类型,是指向第二行的要分清,第二行的地址是数组指针,第二行首元素的地址是整型指针
printf("%d\n", sizeof(*(a + 1)));//16
解读方式1:这里a+1拿到的是第二行的地址,第二行的地址解引用相当于对第二行的数组指针解引用也就是访问一个数组,访问第二行的大小,44=16字节
解读方式2:(a+1)从语法上讲等价于a[1],a[1]是第二行的数组名,sizeof(a[1])数组名单独放到sizeof内部,求出来的是第二行的大小,4*4=16
printf("%d\n", sizeof(&a[0] + 1));//4/8
&a[0] 数组名取地址,取出的是第一行的地址
+1 第二行的地址
是地址就是4/8个字节
printf("%d\n", sizeof(*(&a[0] + 1)));//16
上一个代码已解释&a[0] + 1是第二行的地址,解引用就是第二行,第二行的大小4*4=16
*(&a[0] + 1))等价于a[1]1
printf("%d\n", sizeof(*a));//16
a在这里是首元素的地址,二维数组的首元素地址就是第一行的地址,解引用就是第一行,第一行的大小是4*4=16
*a 就是第一行
*a 等价于 *(a+0)等价于a[0]
printf("%d\n", sizeof(a[3]));//16
这个数字总共只有三行,a[3]是第四行了,a[3]和a[2],a[1],a[0]同样的道理是行的数组名,如果有第四行的话,它的大小一定是16字节,因为每一行都是16字节
这里存在越界吗?
不存在.因为sizeof内部的表达式不会真的去访问或者计算
这里就根本不会去访问第四行,只要根据它的类型属性确定了,假设存在第四行的话,和之前的a[2],a[1],a[0比较一推断,就知道是相同的类型,不会形成越界访问
补充说明
int a = 5; short s = 11; printf("%d\n", sizeof(s = a + 2));//2 //这里按说a是整型,+2之后还是整型,非要把四个字节的整型数据放到2个字节的short变量里面,short变量不过也可以放下7,因为发生了截断,把低位的7留下,高位的0截断丢了,也可以计算出7 //在计算表达式的值所占的空间大小是几个字节,把结果放到short类型的变量里面,结果是short类型的,sizeof(short类型的数据)理论上应该是两个字节,是2 //s = a + 2值属性是7,类型属性是short printf("%d\n", s);//11 //这里运行的结果是11而不是计算之后的7 //说明sizeof内部的表达式不会真的计算, //原因一:因为sizeof通过类型就可以知道表达式的大小是2,根本不需要计算得出2 //原因二:代码test.c---->编译--->链接--->test.exe //如果表达式s=a+2要运算的话必须生成可执行程序test.exe才能运算,遗憾的是像sizeof这样的在编译期间就已经处理了,编译期间根据类型属性就已经确定是2了,就不会把表达式计算了
int a = 10;
a + 3;
//像a+3这样的式子有值属性,类型属性
//值属性是13
//类型属性是int
printf("%d\n", sizeof(*a + 1));//4/8
a是第一行的地址,第一行的地址解引用,*a等价于a[0]
第一行的数组名a[0]表示首元素的地址&a[0][0],+1就是&a[0][0]+1=&a[0][1]
也就是第一行第二个元素的地址,是地址的话长度就是4/8个字节
注意点
1.数组名单独放到sizeof内部,这里的数组名表示整个数组,计算的是整个数组的大小
&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
除上面两个例外情况,所有的数组名都表示首元素的地址
2.二维数组首元素的地址是第一行的地址,
✨sizeof(a[0]) 计算的是这一行的大小
✨sizeof(a[0]+1) 某一行的数组名并没有单独放到sizeof内部的时候,数组名表示首元素的地址指的是这一行的首元素的地址
简单来说就是降级处理:
当二维数组的数组名没有单独放在sizeof内部—>表示的是二维数组首元素的地址(是第一行的地址),如果某一行的数组名并没有单独放到sizeof内部的时候—>数组名表示首元素的地址指的是这一行的首元素的地址
3.sizeof只关注类型,不会计算里面的表达式
总结
指针进阶(下)的内容就到这里啦,创作不易😭如果对友友们有帮助的话,记得点赞收藏博客🥰🥰🥰,关注后续的指针进阶笔试题详解题目篇哦~🐾