深入理解指针(五)
1.sizeof和strlen的对比
sizeof 是单目操作符 不是函数!
计算变量所占空间的大小的 单位是字节
如果操作数是类型的话 那么计算的是一个类型所占空间的大小
int a = 0;
printf("%d\n",sizeof a) 如果传进去的是变量 那么不需要括号也行
printf("%d\n",sizeof(int))
综上所述 sizeof不可能是函数
再看一种情况
int a = 0;
short s = 4;
printf("%d\n",sizeof(s = a + 2)); // 2
printf("%d\n",s); // 4
sizeof内的表达式是不回进行计算的
这里对于sizeof来说就是把一个int类型的东西塞到了short类型里面 所以是2
会发生截断现象 就是内存空间里的数据 被截断了一部分 只有一部分存到了short里面
那么为什么sizeof中的表达式不会计算呢?
因为C语言是编辑型语言 在编译的时候 s就被编译器认出是shorts类型 就已经被编译成2了
strlen 是一个函数 使用它需要引入头文件 <string.h>
该函数会去计算一个字符串在遇到\0之前的长度
有种情况会导致产生随机值
char arr[] = {'1','2','3'};
int len = strlen(arr);
这种情况下 是打印出随机值的 因为这种情况并不是字符串
2.数组和指针笔试题解析
2.1数组笔试题
2.1.1 一维数组
//一维数组
int main1()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));// 16 这里表示的是整个数组的地址 也指向首元素地址 但是+1 是跳过整个数组的字节
printf("%d\n", sizeof(a + 0));// 8 // a是首元素的地址 类型是int* 因为这里是 a+0 地址大小就是4/8
printf("%d\n", sizeof(*a));//4 *a是首元素 *a = a[0] = 8(a + 0)
printf("%d\n", sizeof(a + 1));//8 a + 1是第二个元素的地址 是int* 类型
printf("%d\n", sizeof(a[1]));//4 这里就是第二个元素 4个字节
printf("%d\n", sizeof(&a));// 8 &a是数组的地址 地址就是8个字节
printf("%d\n", sizeof(*&a));// 16 这里就是首元素的地址 但是是整个数组的地址 类型是 int (*)[4] 那就是4 * int
printf("%d\n", sizeof(&a + 1));// 8 这里是野指针
printf("%d\n", sizeof(&a[0]));// 8
printf("%d\n", sizeof(&a[0] + 1));// 8
return 0;
}
2.1.2 字符数组
//字符数组
int main2()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));// 6
printf("%d\n", sizeof(arr + 0));// 8 地址就是8/4
printf("%d\n", sizeof(*arr));// 1
printf("%d\n", sizeof(arr[1]));// 1
printf("%d\n", sizeof(&arr));// 8
printf("%d\n", sizeof(&arr + 1));// 8
printf("%d\n", sizeof(&arr[0] + 1));// 8 &arr = &arr[0]
printf("%d\n", strlen(arr));// 首元素地址传进去 数组中没有 \0 结果是随机的
printf("%d\n", strlen(arr + 0));// 这里还是地址
//printf("%d\n", strlen(*arr));// 这里传进去的不是地址 无法执行函数 这里传进去的实际上是字符所对应的ASCII码
我们之前也学习过了 strlen函数的参数是 size_t strlen(const char* s)
//printf("%d\n", strlen(arr[1]));// 无法执行函数
printf("%d\n", strlen(&arr));// 随机值
printf("%d\n", strlen(&arr + 1));// 随机值
printf("%d\n", strlen(&arr[0] + 1));// 随机值
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); // 7 还有一个\0
printf("%d\n", sizeof(arr + 0));// 8 地址做整数+-法之后 还是地址 大小是4/8
printf("%d\n", sizeof(*arr));// 1
printf("%d\n", sizeof(arr[1]));// 1
printf("%d\n", sizeof(&arr));// 8
printf("%d\n", sizeof(&arr + 1));// 8
printf("%d\n", sizeof(&arr[0] + 1));// 8
printf("%d\n", strlen(arr));// 6
printf("%d\n", strlen(arr + 0));// 6
//printf("%d\n", strlen(*arr));// 无法执行 传进去不是地址
//printf("%d\n", strlen(arr[1]));// 无法执行
printf("%d\n", strlen(&arr));// 6
printf("%d\n", strlen(&arr + 1));// 随机值 这里&arr取出的是整个元素的地址 + 1 之后 是一个野指针 是从这个字符串的最后开始寻找\0
//但是根本找不到一点 所以是随机值
printf("%d\n", strlen(&arr[0] + 1));// 随机值 &arr = &arr[0]
char* p = "abcdef";
printf("%d\n", sizeof(p));// 8
printf("%d\n", sizeof(p + 1));// 8
printf("%d\n", sizeof(*p));// 1 这里的p的类型是 const char* *p就是char 1个字节
printf("%d\n", sizeof(p[0]));// 1
printf("%d\n", sizeof(&p));// 8 对于一级指针取地址 接受它的是二级指针 相当于 char* *q = p
printf("%d\n", sizeof(&p + 1));// 8 相当于q + 1 位移量是char* 8个字节 现在指向的是p变量的后面
printf("%d\n", sizeof(&p[0] + 1));// 8 这里是取出首元素的地址 +1就是第二个元素的地址
printf("%d\n", strlen(p));// 6
printf("%d\n", strlen(p + 1));// 5
//printf("%d\n", strlen(*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+1
return 0;
}
2.1.3二维数组
//二维数组
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));// 48 放的是整个二维数组的地址
printf("%d\n", sizeof(a[0][0]));// 4
printf("%d\n", sizeof(a[0]));// 16 相当于放了第一个一维数组进去
printf("%d\n", sizeof(a[0] + 1));// 8 a[0] 相当于第一个一维数组的地址 但是a[0]并没有单独放在sizeof里面
//相当于是一个首元素的地址 那么一维数组的首元素地址 也就是 a[0][0] 的地址 那么+1过后就是a[0][1]的地址 而地址的大小是8/4
printf("%d\n", sizeof(*(a[0] + 1)));// 4 这里相当于解引用a[0][1]的地址 是一个整形 4个字节
printf("%d\n", sizeof(a + 1));// 8 a并没有单独放在sizeof里面 因此 这里相当于首元素的地址 而二维数组的元素是一维数组
//那么这里+ 1 就是指向第二个一维数组的地址 这里地址进行运算后还是地址 那么就是8
printf("%d\n", sizeof(*(a + 1)));// 16 解引用整个二维数组的地址相当于一个一维数组的地址
printf("%d\n", sizeof(&a[0] + 1));// 8 &a[0]是取出了第一行数组的地址 + 1相当于 指向了第二个一维数组的地址 地址的大小就是8/4
printf("%d\n", sizeof(*(&a[0] + 1)));// 这里相当于解引用第二个一维数组的地址 那么就是把第二个一维数组传入 就是16
printf("%d\n", sizeof(*a));// 16 a没有单独放到sizeof里面 这里是二维数组的首元素地址 也就是第一个一维数组的地址
// 解引用之后就是 把第一个一维数组传进去 那么就是16
// 在sizeof里面 这三个是等价的 *a == *(a + 0)== a[0]
printf("%d\n", sizeof(a[3]));// 16 其实这里是越界的 但是sizeof并不计算 只知道这个a[3]是一个什么类型
// 就是拥有四个整数的一维数组
// a[3]是第四行的数组名 单独放在sizeof里面 计算的是第四行的大小
return 0;
}
2.2 指针笔试题
1.第一题
int main4()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);// 为什么要强制转换呢
// 因为这里的&a取出的是整个数组的地址 +1后 还是数组指针 类型是 int (*)[5]
printf("%d,%d", *(a + 1), *(ptr - 1));// 2 5 -1 是因为越界了
return 0;
}
2.第二题
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);// 0x100020 这里+1 跳过一个结构体
printf("%p\n", (unsigned long)p + 0x1);// 0x100021整型值+1 就是+1 这里不是指针+1 这里没有*号
printf("%p\n", (unsigned int*)p + 0x1);// 10x10004 这里把p强制转化成int*类型了 + 1就是4个字节的大小
return 0;
}
3.第三题
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) }; // 注意了这里不是大括号
// 小的括号就变成逗号表达式了 所以 a[3][2] = {1,3,5,0,0,0};
int* p;
p = a[0];// 这里其实是首元素的地址 这里本来是第一个一维数组的地址 所以这里其实是a[0][0] 的地址
printf("%d", p[0]);// 1 p[0] 相当于 *(p + 0) == *p 所以访问的就是 a[0][0] 的地址 就是1
return 0;
}
4.第四题(有点难度)
int main()
{
int a[5][5];
int(*p)[4];// p是一个数组指针
p = a;// a的首元素地址 是第一行数组的地址 类型 是 int (*)[5]
// 但是p 的类型 是 int (*)[4] 这里类型不一样 会警告 但是还是可以运行
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);// p[4][2] 等价于 *(*(p+4)+2)
// FFFFFFFFFFFFFFFC,-4 因为是小地址减去大地址 所以是-4 代表两个地址之间差了四个元素
return 0;
}
图片解析
5.第五题
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
//ptr1 实际指向了数组 a 之后的位置,ptr1[-1] 将指向数组 a 最后一个元素(值为4)。
//*ptr2:由于 ptr2 指向 a 地址后的第一个字节,它并不指向有效的内存位置,因此会产生未定义行为。
return 0;
}
6.第六题
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));// 这里指向第二行一维数组的首元素地址 是6
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); // 10 5
return 0;
}
7.第七题
int main()
{
char* a[] = { "work","at","alibaba" };//指针数组 数组里面存放的是char*类型的指针
char** pa = a; // 将首元素地址传给pa 就是"work"
pa++;// pa+1 让pa指向了at
printf("%s\n", *pa);// at // *pa 解引用就是at
return 0;
}
8.第八题(有难度)
原题:
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;
}
解析:
先把各级指针的关系标出来
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 此时的cpp = cpp + 1
printf("%s\n", *-- * ++cpp + 3);// ER
//先进行++cpp的计算 由于之前已经是cpp+1 了 所以这里会变成 cpp+2
//这个时候cpp指向的就是c + 1的地址 进行解引用 拿到了c + 1的内存空间
//接下来就是 --c + 1 那么就是 c 此时c指向的就是c数组的首元素地址 接着在 *c 拿到ENTER的内存空间
//*c + 3 就是对ENTER的首元素地址+3 那么就是指向E 所以结果就是 ER
printf("%s\n", *cpp[-2] + 3);// ST
// 由于之前的两次 cpp++ 现在的cpp是等于cpp + 2 而cpp[-2] 等价于 *(cpp - 2) 所以此时*(cpp + 2 - 2)
// 相当于对指向c + 3的指针进行解引用 这个时候拿到的就是c + 3 的内存空间 这里*cpp[-2] 现在就是 *(c + 3)
// 那么拿到的就是FIRST 的首元素地址 +3 那么指向的就是S的地址 所以是 ST
printf("%s\n", cpp[-1][-1] + 1);// EW
// 由于之前的两次 cpp++ 现在的cpp是等于cpp + 2 而cpp[-1] 等价于*(cpp - 1) 因此等价于 *(cpp + 2 - 1)
// 那么此时就是指向的c+2的地址 解引用 拿到的就是c + 2的内存空间 此时的cpp[-1][-1]等价于 *(c + 2 - 1)== *(c + 1)
// 对c + 1进行解引用 c + 1指向的是NEW的地址 解引用后拿到的就是NEW的首元素地址 + 1 后 指向的就是E的地址 所以是EW
return 0;
}
那么拿到的就是FIRST 的首元素地址 +3 那么指向的就是S的地址 所以是 ST
printf("%s\n", cpp[-1][-1] + 1);// EW
// 由于之前的两次 cpp++ 现在的cpp是等于cpp + 2 而cpp[-1] 等价于*(cpp - 1) 因此等价于 *(cpp + 2 - 1)
// 那么此时就是指向的c+2的地址 解引用 拿到的就是c + 2的内存空间 此时的cpp[-1][-1]等价于 *(c + 2 - 1)== *(c + 1)
// 对c + 1进行解引用 c + 1指向的是NEW的地址 解引用后拿到的就是NEW的首元素地址 + 1 后 指向的就是E的地址 所以是EW
return 0;
}