[指针]从浅到深了解指针---第五课---最后一课

一、sizeofstrlen的对比

1.1sizeof

  对于sizeof()函数,我们知道它是计算变量所占内存空间的大小,单位为字节。如果操作数是类型的话,计算的是使用该类型创建的变量所占内存空间的大小。
  而且sizeof().只关心占内存的空间大小,不在乎内存空间内存放的数据。重要的是sizeof是操作符,不是函数

#include<stdio.h>
int main()
{
	int a = 10;
	printf("%zd\n", sizeof(a));
	printf("%zd\n", sizeof(int));//操作数是类型,int
	//结果都为4个字节
	
	int arr[10] = { 0 };
	printf("%zd\n",sizeof(arr));//结果为40个字节,4*10
	return 0;
}

1.2strlen

  strlen函数求字符串的长度,只能针对字符串(字符数组)。它统计的字符串长度是统计字符串中\0之前的字符个数。注意:向strlen()函数传参是,传递的参数应该是地址

  下面让我们练习练习

#include <stdio.h>
int main()
{
 char arr1[] = {'a', 'b', 'c'};
 char arr2[] = "abc";
 printf("%d\n", strlen(arr1));//随机值
 printf("%d\n", strlen(arr2));//3
 
 printf("%d\n", sizeof(arr1));//3
 printf("%d\n", sizeof(arr2));//4
 return 0;
}

  通过上面的代码我们可以知道,strlen统计的确实是\0之前的字符个数。因为数组arr1中只存放了a,b,c3个字符,没有\0,所以在没有找到\0(字符串结束标记符)统计就不会停止。即strlen(arr1)得到的是随机值arr2数组存放了a,b,c,\04个字符,所以strlen计算arr2的字符个数为3
  因为arr1数组中有3个char类型的数据,所以整体占内存空间的大小为1*3=3个字节空间;arr2数组中有4个char类型的数据,所以整体占内存空间的大小为1字节*4个数=4个字节空间。

1.3sizeofstrlen的对比

在这里插入图片描述


二、数组和指针的笔试题解析

2.1一维数组

  在开始前我们在那回忆一下对数组名的理解。

数组名是数组首元素的地址,但是有2个例外

  • sizeof(数组名) ,计算的是整个数组的大小
  • &数组名,取出的是整个数组的地址,地址大小与数组首元素的地址一样,但是类型不一样,导致±整数的步长不一样
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//结果为16。数组名单独放在sizeof内部,a表示整个数组,计算整个数组的大小,单位是字节
printf("%d\n",sizeof(a+0));//结果为4或8。数组名没有单独放在sizeof内部,a表示数组首元素的地址,a+0还是表示数组首元素的地址。那么sizeof计算的是数组的首元素的地址的大小,而地址的大小与机器位数有关。32位下为4个字节,64位下为8个字节
printf("%d\n",sizeof(*a));//结果为4。数组名a没有单独放在sizeof内部,a表示数组首元素的地址,*a找到的是首元素也就是1,而1是int,占空间大小为4个字节
printf("%d\n",sizeof(a+1));//结果为4或8。数组名a没有单独放在sizeof内部,a表示数组首元素的地址,a+1表示数组第二个元素的地址,那么地址的大小可能为4或8
printf("%d\n",sizeof(a[1]));//结果为4。a[1] --- *(a+1)表示找到数组第二个元素也就是2,而2是int,占内存空间4个字节
printf("%d\n",sizeof(&a));//结果为4或8。&a,取出的是整个数组的地址,而数组的地址也是地址,而地址的大小只与机器位数有关,可以为4或8字节
printf("%d\n",sizeof(*&a));//结果为16。&a,取出的是整个数组的地址,地址大小与数组首元素的地址一样,但是类型不一样。数组首元素的地址(指针)类型为int *,而整个数组的地址(指针)类型为int (*)[4]。还有不要忘了,解引用操作符*也和指针类型有关,指针类型决定了解引用操作符*一次可以访问多少的字节空间,由于是对整个数组的地址进行解引用操作,所以一个访问4*4=16个字节
//还可以这样理解,*和&抵消了,a数组名单独放在sizeof内部,表示计算整个数组的大小,16个字节
printf("%d\n",sizeof(&a+1));//结果为4或8。&a,取出的是整个数组的地址,而整个数组的地址(指针)类型为int (*)[4],&a+1的指针偏移量是跳过16个字节,即跳过整个数组,但&a+1还是地址,而地址的大小为4或8
printf("%d\n",sizeof(&a[0]));//结果为4或8。a[0]代表数组的首元素,&a[0]表示首元素的地址,4或8
printf("%d\n",sizeof(&a[0]+1));//结果为4或8。&a[0]表示首元素的地址,类型为 int*,那么+1表示指针偏移量为1*sizeof(int)=1*4=4个字节,即指针指向了第二个元素,即&a[0]+1表示第二个元素的地址,地址大小为4或8

2.2字符数组

//代码一
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//结果为6。
printf("%d\n", sizeof(arr+0));//结果为4或8。
printf("%d\n", sizeof(*arr));//结果为1。
printf("%d\n", sizeof(arr[1]));//结果为1。
printf("%d\n", sizeof(&arr));//结果为4或8。
printf("%d\n", sizeof(&arr+1));//结果为4或8。
printf("%d\n", sizeof(&arr[0]+1));//结果为4或8。
//代码二
char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n", strlen(arr));//结果为随机值。arr数组名没有单独放在sizeof内部,也没有&,arr表示数组的首元素的地址
printf("%zd\n", strlen(arr+0));//结果为随机值。arr数组名没有单独放在sizeof内部,也没有&,arr表示数组的首元素的地址,arr+0表示首元素的地址。
printf("%zd\n", strlen(*arr));//代码有问题。arr是数组名表示数组的首元素的地址,而*arr是首元素也就是字符a---'a'---97--字符a的acsll码值为97。
//由于向strlen传参的是地址,那么strlen库函数就将97当作地址去访问该地址的内容,非法访问,程序报错。
printf("%zd\n", strlen(arr[1]));//结果为代码有问题。
printf("%zd\n", strlen(&arr));//结果为随机值。虽然&arr取出的是整个数组的地址,但是strlen函数还是从首元素地址开始向后开始统计字符个数。因为strlen库函数的形参部分为const char* str,就算是将实参&arr(类型为char(*)[6])传递过去,str指针依旧是(因为类型为char *)一个字节一个字节的访问空间。
printf("%zd\n", strlen(&arr+1));//结果为随机值。
printf("%zd\n", strlen(&arr[0]+1));//结果为随机值。
//代码三
char arr[] = "abcdef";//细节:字符串里面还有\0字符,共7个字符
printf("%zd\n", sizeof(arr));//结果为7。arr是数组名,数组名单独放在sizeof内部,表示计算整个数组的大小,由于sizeof不管内存空间里存放的数据,只在乎内存空间的大小,单位是字节。字符串共7个字符,即空间大小为7个字节。此时arr的类型是char[7]
printf("%zd\n", sizeof(arr+0));//结果为4或8。arr是数组名,数组名没有单独放在sizeof内部,所以arr表示数组首元素的地址,arr+0还是表示数组首元素的地址,而地址的大小为4或8字节。arr+0的类型是char *
printf("%zd\n", sizeof(*arr));//结果为1。arr是数组名,数组名没有单独放在sizeof内部,所以arr表示数组首元素的地址,*arr就是数组的第一个元素字符a---'a',字符a的类型是char,占空间的大小为1字节。
printf("%zd\n", sizeof(arr[1]));//结果为1。arr[1]表示数组的第二个元素---字符'b',大小为1个字节
printf("%zd\n", sizeof(&arr));//结果为4或8。&arr表示取出的是整个数组的地址,而地址的大小为4或8
printf("%zd\n", sizeof(&arr+1));//结果为4或8。&arr表示取出的是整个数组的地址,&arr+1表示跳过整个数组,但是表示的还是地址,大小为4或8
printf("%zd\n", sizeof(&arr[0]+1));//结果为4或8。&arr[0]表示首元素的地址,&arr[0]+1表示第二个元素的地址,地址的大小为4或8
//代码四
char arr[] = "abcdef";//细节:字符串里面还有\0字符,共7个字符,strlen统计的是\0字符前的字符个数
printf("%d\n", strlen(arr));//结果为6。arr表示数组首元素的地址,结果为6
printf("%d\n", strlen(arr+0));//结果为6。arr表示数组首元素的地址,arr+0还是表示数组首元素的地址,字符串的长度为6
printf("%d\n", strlen(*arr));//非法访问,程序崩溃。arr表示数组首元素的地址,*arr表示字符a----ascll码值为97--将97作为地址然后去访问该地址的内存空间,会导致非法访问,程序崩溃。
printf("%d\n", strlen(arr[1]));//非法访问,程序崩溃。
printf("%d\n", strlen(&arr));//结果为6。虽然&arr取出的是整个数组的地址,但是strlen函数还是从首元素地址开始向后开始统计字符个数,直到统计到\0之前。
printf("%d\n", strlen(&arr+1));//结果为随机值。&arr取出的是整个数组的地址,&arr+1跳过整个数组
printf("%d\n", strlen(&arr[0]+1));//结果为5。&arr[0]表示首元素的地址,&arr[0]+1表示第二个元素的地址,从字符b开始统计,直到\0之前,一共b,c,d,e,f 5个字符,结果为5
//代码五
char *p = "abcdef";//"abcdef"是常量字符串,内容是不可以被修改的,表达式的值为首字符的地址,即就是字符a的地址,即p指针指向a
printf("%d\n", sizeof(p));//结果为4或8。p是字符指针变量,是存放地址的变量,而地址和指针所占空间的大小一样,单位是字符,4或8字节。p表示第一个元素的地址
printf("%d\n", sizeof(p+1));//结果为4或8。p是字符指针变量,是存放地址(字符a的地址)的变量,p+1表示指向字符b,即p+1是第二个元素的地址,地址的大小为4或8
printf("%d\n", sizeof(*p));//结果为1。p的类型是char*,所以*p只能访问一个字节
printf("%d\n", sizeof(p[0]));//结果为1。p[0]--*(p+0)--*p ,p的类型是char*,所以*p只能访问一个字节
printf("%d\n", sizeof(&p));//结果为4或8。p是一级指针变量,类型是char*,p也有自己的地址,&p是p指针变量的地址,类型是char* *,地址的大小是4或8
printf("%d\n", sizeof(&p+1));//结果为4或8。&p的类型是char**,&p+1表示跳过1*sizeof(char *)=1*4或8=4或8个字节,但还是地址。&p是p的地址,&p+1是跳过p变量,指向了p的后边
printf("%d\n", sizeof(&p[0]+1));//结果为4或8。&p[0]+1表示字符b的地址,即第二个元素的地址, 地址的大小为4或8
//代码六
char *p = "abcdef";
printf("%d\n", strlen(p));//结果为6。
printf("%d\n", strlen(p+1));//结果为5。
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));//结果为5。

2.3二维数组

  我们需要记得二维数组传参的本质:二维数组的数组名,表示的是首元素地址,也就是第一行(a[0]是第一行,类型是int [4])的地址,类型为int (*)[4]

int a[3][4] = {0};
printf("%d\n",sizeof(a));//结果为48。a是二维数组的数组名,单独放在sizeof内部,表示计算整个数组的大小,类型是int [3][4],占内存空间3*4*4=48个字节。
printf("%d\n",sizeof(a[0][0]));//结果为4。a[0]表示第一行的数组名,类型是int[4],a[0][0]表示第一行的首元素,类型是int,大小是4个字节
printf("%d\n",sizeof(a[0]));//结果为16。a[0]表示第一行的数组名,单独放在sizeof内部,计算的是(第一行)数组的大小,4*4=16。a[0]类型是int[4]
printf("%d\n",sizeof(a[0]+1));//结果为4或8。a[0]类型是int[4],而a[0]+1类型是int*,因为a[0]是第一行的数组名,没有单独放在sizeof内部,表示第一行数组的首元素地址,类型是int*,那么a[0]+1表示第一行数组的第二个元素的地址。
//a[0]+1 == &a[0][0]+1 == &a[0][1]表示第一行数组的第二个元素的地址。
printf("%d\n",sizeof(*(a[0]+1)));//结果为4。a[0]+1表示第一行数组的第二个元素的地址,*(a[0]+1)表示第一行数组的第二个元素,类型是int,大小是4个字节
printf("%d\n",sizeof(a+1));//结果为4或8。a是二维数组的数组名,没有单独放在sizeof内部,表示的是首元素地址,也就是第一行(a[0]是第一行,类型是int [4])的地址,a+1表示跳过第一行数组,指向第二行数组(a[1])的首元素地址,类型为int(*)[4],地址大小为4或8
printf("%d\n",sizeof(*(a+1)));//结果为16。a+1表示跳过第一行数组,指向第二行数组(a[1])的首元素地址,类型为int(*)[4],解引用操作后*(a+1)表示访问第二行的数组,类型为int[4],大小为4*4=16字节
printf("%d\n",sizeof(&a[0]+1));//结果为4或8。a[0]是第一行数组名,&a[0]表示取出第一行数组的地址,类型是int(*)[4],&a[0]+1表示跳过第一行数组,指向第二行的数组首元素,类型是int(*)[4],地址的大小是4或8
printf("%d\n",sizeof(*(&a[0]+1)));//结果为16。&a[0]+1表示跳过第一行数组,指向第二行的数组首元素,类型是int(*)[4],解引用操作后*(&a[0]+1))表示访问整个第二行数组,类型为int[4],占内存空间的大小为16个字节
printf("%d\n",sizeof(*a));//结果为16。a是二维数组的数组名,没有单独放在sizeof内部,表示的是首元素地址,也就是第一行(a[0]是第一行,类型是int [4])的地址(类型是int(*)[4]),解引用操作后*a表示访问整个第一行数组,int[4],4*4=16
printf("%d\n",sizeof(a[3]));//结果为16。a[3]表示第四行的数组名,如果存在,类型是int[4]
//没有越界,sizeof内部的表达式是不会真实计算的,是根据类型来判断内存空间的大小的

  那为什么没有越界,sizeof内部的表达式是不会真实计算的,是根据类型来判断内存空间的大小的?看下面的代码:

int main()
{
short s = 8;//占2个字节
int n = 12;//占4个字节
printf("%zd\n",sizeof(s = n + 5));//结果为2,表达式的类型取决于s
printf("%d\n",s)//结果为8
return 0;
}

  有人可以会疑惑,s的值不应该是17吗,为什么是8?其实sizeof中的表达式是不会执行的。那么sizeof是怎么判断变量占内存空间的大小的呢?其实根据数据类型就可以了。


三、指针运算笔试题解析

//题一
#include <stdio.h>
int main()
{
 int a[5] = { 1, 2, 3, 4, 5 };
 int *ptr = (int *)(&a + 1);//&a是类型是int(*)[5]
 printf( "%d,%d", *(a + 1), *(ptr - 1));//结果为2  5。
 return 0;
}
//题二
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{
int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
 printf("%p\n", p + 0x1);//结果为:Ox100014。因为p+1跳过20个字节,20的十六进制为14,即Ox100000+20=Ox100000+Ox14=Ox100014
 printf("%p\n", (unsigned long)p + 0x1);//结果为:Ox100001。p被强制类型转换为无符号长整型,p+1就是整数加整数,Ox100000+Ox1=Ox100001
 printf("%p\n", (unsigned int*)p + 0x1);//结果为:Ox100004。p被强制类型转换为无符号长整型指针,p+1跳过4个字节,Ox100000+4=Ox100000+0x4=Ox100004
 return 0;
}
//题目三
#include <stdio.h>
int main()
{
 int a[3][2] = { (0, 1), (2, 3), (4, 5) };//注意(a,b,c)是逗号表达式,{}大括号才是赋值
 //逗号表达式从左依次计算,但逗号表达式的值等于最右边的表达式的值,其{ (0, 1), (2, 3), (4, 5) }等于{1,3,5},属于不完全初始化数组,其余的数值都初始化为0。1 3 5 0 0 0
 int *p;
 p = a[0];//a[0]表示第一行的数组名,没有放在sizeof内部,也没有&数组名,即a[0]表示第一行数组的首元素地址,类型是int *,所以p指针也指向第一行数组的首元素。
 printf( "%d", p[0]);//结果为1。p[0]---*(p+0)---表示第一个元素,数据为1,类型为int
 return 0;
}
//题目四
//假设环境是x86环境(32位),程序输出的结果是啥?
#include <stdio.h>
int main()
{
 int a[5][5];//二维数组,共25个元素,有五行,一行有5个
 int(*p)[4];//数组指针,p是指针变量,是指向数组的指针,指向的数组有4个元素,元素的类型是int
 p = a;//a表示数组名,没有放在sizeof内部,也没有&数组名,表示第一行数组的首元素的地址,类型是int(*)[5],指针p的类型是int(*)[4],将a赋值给p,表示指针p也指向二维数组的起始地址
 printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//0xFFFFFFFC  -4
 //地址减地址,得到的是地址之间元素的个数
 //由画图可以知道,&p[4][2] - &a[4][2]之间有4个元素,但是&p[4][2]是低地址,&a[4][2]是高地址,&p[4][2] - &a[4][2]是负数,所以&p[4][2] - &a[4][2]的值是-4。将-4以%p和%d的形式打印出,由于数据在内存中的存放是以补码的形式存在的,-4的补码为11111111111111111111111111111100,%d是打印有符号的数,需要将补码还原为原码再将原码写成真值,也就是-4,%p是打印地址,地址没有正负,就是将-4的补码当成了地址打印出来。
 return 0;
}

  记住一个比较容易错误的点:

  在数组名,既没有放在sizeof内部,也没有&数组名时,分种情况:

  • 数组名是一维数组的数组名时:假设一维数组为int arr[10]={0},arr数组名表示数组首元素的地址,也就是第一个元素的地址,类型是int *
  • 数组名是二维数组的数组名时:假设二维数组为int a[3][2]={0},a数组名表示数组首元素的地址,也就是第一行数组名的地址,类型是int(*)[2]。而a[0]是数组名,不过是第一行的数组名(一维数组的数组名),表示该行的首元素的地址,类型是int *,所以在二维数组的时候,要注意区分aa[0]这两个数组名
//题目五
#include <stdio.h>
int main()
{
 int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 int *ptr1 = (int *)(&aa + 1);//aa表示数组名,&aa表示取出整个二维数组的地址,&aa + 1表示跳过整个二维数组,指向二维数组的后边。
 int *ptr2 = (int *)(*(aa + 1));//aa表示数组名,既没有放在sizeof内部,也没有&,表示第一行的地址,类型是int(*)[5],aa + 1表示二维数组第二行数组的地址,地址值和二维数组第二行数组首元素的地址值一样,类型是int(*)[5],*(aa + 1)表示访问二维数组的整个第二行数组元素,*(aa + 1)类型是int[5]
 printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));//结果为10  5,结果好算,但是类型有点难搞,希望别晕了
 return 0;
}
//题目六
#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};//数组a存放了三个字符串的三个首字符的地址,类型是char*
 char**pa = a;//a表示首元素的地址,类型是char* *,p二级指针指向数组a的首元素
 pa++;//pa+1指向数组a的第二个元素,数组a的第二个元素是地址,指向字符串"at"
 printf("%s\n", *pa);//结果为at。因为此时pa指向数组a的第二个元素,*pa表示数组a的第二个元素,元素也就是字符串"at"的地址,%s从给的地址开始打印字符串,直到\0之前
 return 0;
}
//题目七
#include <stdio.h>
int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};
 char***cpp = cp;
 printf("%s\n", **++cpp);//结果为POINT。
 printf("%s\n", *--*++cpp+3);//结果为ER。
 printf("%s\n", *cpp[-2]+3);//结果为ST。
 printf("%s\n", cpp[-1][-1]+1);//结果为EW。
 return 0;
}

谢谢观看!希望以上内容帮助到了你,对你起到作用的话,可以一键三连加关注!你们的支持是我更新地动力。
因作者水平有限,有错误还请指出,多多包涵,谢谢!

  • 31
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值