目录
- sizeof和strlen的对⽐
- 数组和指针笔试题解析
- 指针运算笔试题解析
一、sizeof和strlen的对比
sizeof
1,sizeof是操作符
2,sizeof计算操作数所占内存的大小
3,不关注内存中存放什么数据
strlen
1,strlen是库函数,使用时需要包含头文件string.h
2,strlen是求字符串长度的,统计的是\0之前的字符的个数
3,关注内存中是否有\0,如果没有\0,就会持续往后找,可能会越界
二、数组和指针笔试题解析
注:sizeof内部存在表达式时,表达式不参与计算
习题1
int main()
{
short s = 10;//short占两个字节
int i = 2;//int占四个字节
int n = sizeof(s = i + 4);//此处发生了截断(short=int+int)--将int类型赋给short类型会发生截断
//实际上:int n=sizeof(short);
printf("%d\n", n);
printf("%d\n", s);//s的值并未改变
return 0;
}
数组名的理解:
数组名是数组首元素的地址
但是有两个例外:
1,sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
2,&数组名,数组名表示整个数组,取出的是数组的地址
一维数组习题
tips:只要是地址在内存中都是占4/8个字节
习题1–整型数组
int main()
{
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//4*4=16个字节
//此处的a单独出现在sizeof内部,表示整个数组,计算的是整个数组的大小
printf("%d\n",sizeof(a+0));//4/8个字节
//a没有单独出现在sizeof内部,表示数组首元素的地址
//a+0==&a[0]
printf("%d\n",sizeof(*a));//int类型占4个字节
//a表示数组首元素的地址--*a==a[0]
printf("%d\n",sizeof(a+1));//4/8个字节
//a没有单独出现在sizeof内部,表示数组首元素的地址
//a+1==&a[1]
printf("%d\n",sizeof(a[1]));//4个字节
printf("%d\n",sizeof(&a));//4/8个字节
//&a取出的是整个数组的地址,是地址就占4/8个字节
printf("%d\n",sizeof(*&a));//16个字节
//取出a的地址又对其解引用,*&相互抵消了,只剩下a
//a单独放在sizeof内部,计算整个数组的大小
printf("%d\n",sizeof(&a+1));//4/8个字节
//&a取出的是整个数组的地址,+1跳过整个数组的地址
//指向整个数组的地址之后,但是地址其大小就是4/8个字节
printf("%d\n",sizeof(&a[0]));//4/8个字节
//&a[0]取出首元素的地址
printf("%d\n",sizeof(&a[0]+1));//4/8个字节
//&a[0]+1==&a[1]指向第二个元素的地址
return 0;
}
习题2–字符数组
int main()
{
char arr[] = {'a','b','c','d','e','f'};//字符数组
printf("%d\n", sizeof(arr));//6*1
//数组名单独放在sizeof内部表示整个数组的地址
printf("%d\n", sizeof(arr+0));//4/8
//arr没有单独放在sizeof内部,表示数组首元素地址
//arr+0==&arr[0]
printf("%d\n", sizeof(*arr));//1
//arr表示首元素地址,解引用得到数组首元素
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
//&arr表示整个数组的地址,是地址大小就是4/8个字节
printf("%d\n", sizeof(&arr+1));//4/8
//&arr+1表示跳过整个数组之后指向的地址
printf("%d\n", sizeof(&arr[0]+1));//4/8
//第二个元素的地址
return 0;
}
习题3
int main()
{
//size_t strlen ( const char * str );
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));//随机值
//arr表示数组首元素的地址,strlen遇到\0才会停止
//数组中没有明确的\0所以他会继续往后走,直到遇到\0为止
printf("%d\n", strlen(arr+0));//随机值
//arr+0==arr[0],与上面那个没什么区别
printf("%d\n", strlen(*arr));//非法访问
//*arr==数组首元素a,strlen的参数是char*类型,只要你在strlen内他
//都会将你转化为指针类型,a的ASCII码值是97,97的地址我们并未定义,贸然访问会形成非法访问
printf("%d\n", strlen(arr[1]));//非法访问
//与上面如出一辙
printf("%d\n", strlen(&arr));//随机值
//strlen中应该写char*类型的指针,而&arr是数组指针类型--char (*pc) [6]
//strlen(&arr)在运行是会进行类型转换,会将其转换为char*类型的指针
printf("%d\n", strlen(&arr+1));//随机值
printf("%d\n", strlen(&arr[0]+1));//随机值
return 0;
}
习题4
int main()
{
char arr[]="abcdef";
//这种字符本身就存有\0
//[a b c d e f \0]
printf("%d\n", strlen(arr));//6
//arr表示首元素地址,从a开始数到\0为止
printf("%d\n", strlen(arr+0));//6
//arr+0==&arr[0]
printf("%d\n", strlen(*arr));//非法访问
//*arr=='a'strlen的参数是char*类型,只要你在strlen内他
//都会将你转化为指针类型,a的ASCII码值是97,97的地址我们并未定义,贸然访问会形成非法访问
printf("%d\n", strlen(arr[1]));//非法访问
printf("%d\n", strlen(&arr));//6
//&arr的类型是char(*)[6]而strlen的参数类型是char*类型
//strlen(&arr)在运行是会对其进行类型转换,会将其转换为char*类型
//&arr是整个数组的地址,但是这个地址也是指向数组起始位置的,strlen就从起始位置向后开始找\0
printf("%d\n", strlen(&arr+1));//随机值
//跳过整个数组后所指向的地址
printf("%d\n", strlen(&arr[0]+1));//随机值
//数组第二个元素的地址
return 0;
}
习题5
int main()
{
char arr[]="abcdef";
//这种字符本身就存有\0
//[a b c d e f \0]
printf("%d\n", sizeof(arr));//7
//arr单独放在sizeof内部表示整个数组的地址,计算整个数组的大小
printf("%d\n", sizeof(arr+0));//4/8个字节
//arr+0==&arr[0]
printf("%d\n", sizeof(*arr));//1
//*arr==字符a
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8个字节
//&arr整个数组的地址
printf("%d\n", sizeof(&arr+1));//4/8个字节
//指向整个地址之后的地址
printf("%d\n", sizeof(&arr[0]+1));//4/8个字节
//第二个元素的地址
return 0;
}
习题6
int main()
{
char *p = "abcdef";//常量字符串
//p里面存放的是字符串的首元素地址,abcdef在内存中是连续存放的,可以看作是一个一维数组
printf("%d\n", sizeof(p));//4/8个字节
//p里面存放的是字符串的首元素地址
printf("%d\n", sizeof(p+1));//4/8个字节
//p+1==&p[1]
printf("%d\n", sizeof(*p));//1
//*p表示字符a
printf("%d\n", sizeof(p[0]));//1
printf("%d\n", sizeof(&p));//4/8个字节
//&p取出的是存放首元素地址的地址,&p的类型是char**
printf("%d\n", sizeof(&p+1));//4/8个字节
//&p是char**类型+1跳过一个p对象的大小,&p+1取出的是存放第二个元素地址的地址
printf("%d\n", sizeof(&p[0]+1));//4/8个字节
//&p[0]+1也表示字符串的第二个元素的地址
return 0;
}
习题7
int main()
{
char *p = "abcdef";
printf("%d\n", strlen(p));//6
//p表示字符串的首元素地址,从p指向的元素开始向后数直到遇到\0为止
printf("%d\n", strlen(p+1));//5
//p+1指向字符串的第二个元素的地址
printf("%d\n", strlen(*p));//非法访问
//*p就是字符a
printf("%d\n", strlen(p[0]));//非法访问
printf("%d\n", strlen(&p)); //随机值--这里取的是指针变量p的地址,从p的地址向后找\0
printf("%d\n", strlen(&p+1));//随机值
printf("%d\n", strlen(&p[0]+1));//5
//&p[0]+1表示第二个字符的地址
return 0;
}
二维数组
习题八
int main()
{
int a[3][4] = {0};
printf("%d\n",sizeof(a));//3*4*4个字节
//数组名a单独放在sizeof内部,表示整个数组的地址
//这里计算的是整个数组的大小
printf("%d\n",sizeof(a[0][0]));//4个字节
//a[0][0]数组首元素
printf("%d\n",sizeof(a[0]));//4*4个字节
//a[0]是第一行的数组名,单独放在sizeof内部,计算第一行的大小
printf("%d\n",sizeof(a[0]+1));//4/8个字节
///a[0]并没有单独放在sizeof内部,所以a[0]表示第一行第一个元素的地址,a[0]+1==&a[0][1],表示第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));//4个字节
//表示第二个元素
printf("%d\n",sizeof(a+1));//4/8个字节
//a并没有单独出现在sizeof内部,表示数组首元素的地址,也就是第一行的地址
//a的类型是int(*)[4],+1跳过第一行指向第二行的地址
printf("%d\n",sizeof(*(a+1)));//4*4个字节
//*(a+1)==a[1],第二行的数组名,计算第二行的大小
printf("%d\n",sizeof(&a[0]+1));//4/8个字节
//a[0]是第一行的数组名,&a[0]取出的是第一行的地址,&a[0]+1得到的就是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));//16个字节
//对第二行的地址解引用,得到第二行的大小
printf("%d\n",sizeof(*a));//16
//*a==*(a+0)==a[0],第一行的地址解引用,得到第一行的数组
printf("%d\n",sizeof(a[3]));//16
//a[3]不会存在越界行为,sizeof会类比a[0]来计算它的大小
return 0;
}
三、指针运算的笔试题解析
习题一
#include <stdio.h>
//程序运行的结果是什么呢?
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//解析:
//整型数组a每个元素类型都是int类型
//&a取出的是整个数组的地址,&a+1跳过整个数组之后指向的地址,也就是指向5的后面,对(&a+1)强制类型转换为int*类型的指针,再赋给ptr
//*(a+1),a表示数组首元素地址,+1指向第二个元素,对其解引用得到2,ptr-1跳过一个整型,指向5的地址,因此本题打印的结果为 2,5
习题2
//在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);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
//解析:
//*p是结构体指针变量,这里你不需要管这个结构体里面的內容,你只需要知道这个结构体的大小是20个字节
//将16进制的0x100000强制类型转换为结构体指针类型赋值给p
//p+1,指针加1取决于指针的类型--p+1,p是结构体指针类型,+1就相当于加了20个字节
//p+1==0x100000+0x000014=0x100014---这里的进制转换如果有不清楚的可以看我上篇博客写的操作符里面有进制转换的知识
//(unsigned long)p,p被强制转换为无符号整型,整型+1就是+1
//(unsigned long)p + 0x1==0x100000+0x000001==0x100001
//(unsigned int*)p,p被强制类型转换成无符号整型指针,指针+1取决于指针的类型,整型指针+1就是+4个字节
//(unsigned int*)p+ 0x1==0x100000+0x000004==0x100004
习题3
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
//解析:
//()是逗号表达式,其最终结果是最后一个表达式的结果
//因此数组a初始化內容为 int a[3][2]={1,3,5}
//p中存放的是二维数组的首元素地址a[0]也就是第一行的地址,p[0]就是第一行的第一个元素,也就是1
//p[0]==a[0][0]
习题4
//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
//解析:如下图
//
习题5
#include <stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//解析:
//&aa取出整个数组的地址+1跳过整个数组之后指向的地址,也就是10后面的地址,(int*)(&aa+1)将其强制转换成int*类型的指针赋给ptr1
//aa是二位数组首元素的地址也就是第一行的地址,+1跳过第一行的地址,指向第二行的首元素地址,也就是6的地址
//*(ptr1-1)解引用指向10,*(ptr2-1)解引用指向5
习题6
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};//字符指针数组--存放的是字符的地址
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//解析:
//pa是个二级指针,存放的是字符数组a的地址,a是数组首元素的地址
//pa++==指向字符数组的第二个元素的地址
//对其解引用得到第二个元素
习题7
#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);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}