1. sizeof和strlen的对⽐
1.1 sizeof
在学习操作符的时候,我们学习了 sizeof , sizeof 计算变量所占内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
c
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);
printf("%d\n", sizeof(int));
return 0;
}
运行结果

1.2 strlen
strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
c
size_t strlen ( const char * str );
统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。strlen 函数会⼀直向后找 \0 字符 ,直到找到为⽌,所以可能存在越界查找。
c
#include <stdio.h>
#include <string.h>//使用strlen,需要包含头⽂件 string.h
int main()
{
char arr1[3] = { 'a', 'b', 'c' };
char arr2[] = "abc";
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));
return 0;
}

1.3 sizeof 和 strlen的对⽐
(1)sizeof
1.sizeof是操作符
- sizeof计算操作数所占内存的
⼤⼩,单位是字节
- 不关注内存中存放什么数据
(2)strlen
1.strlen是库函数 ,使⽤需要包含头⽂件 string.h
-
srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数
-
关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界
2. 数组和指针笔试题解析
数组名的意义:
1.sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表⽰⾸元素的地址。
2.1 ⼀维数组
c
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); //16 //单独放数组名,计算整个数组的⼤⼩
printf("%d\n",sizeof(a+0)); //4或8//不是单独放,所以这里的a表示首元素的地址,地址的大小只和操作系统位数有关
printf("%d\n",sizeof(*a)); //4 //*a表示首元素
printf("%d\n",sizeof(a+1)); //4或8//a+1表示第二个元素的地址
printf("%d\n",sizeof(a[1])); //4 //a[1]表示第二个元素
printf("%d\n",sizeof(&a)); //4或8//&a表示整个数组的地址,但他还是一个地址
printf("%d\n",sizeof(*&a)); //16 //*&a就表示a,*和&抵消了,单独放数组名,计算整个数组的⼤⼩
printf("%d\n",sizeof(&a+1)); //4或8//&a+1表示跳过整个数组,指向最后一个元素4后面的地址
printf("%d\n",sizeof(&a[0])); //4或8//&a[0]表示首元素地址
printf("%d\n",sizeof(&a[0]+1));//4或8//&a[0]+1表示第二个元素的地址
2.2 字符数组
可以类比:p0== *( p + 0 )==*p
代码1:(求大小)
c
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//表示跳过整个数组,指向最后一个元素f的后面
printf("%d\n", sizeof(&arr[0]+1));//4或8//表示跳过首元素的地址,也就是第二个元素的地址
代码2:(求个数,\0前面的元素个数)
c
#include <stdio.h>
#include <string.h>//strlen是库函数,使⽤需要包含头⽂件 <string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr)); //随机值 //表示首元素的地址,但数组中没有\0,往后找不知道什么时候遇到\0
printf("%d\n", strlen(arr + 0)); //随机值 //表示首元素的地址,但数组中没有\0
printf("%d\n", strlen(*arr)); //非法访问//*arr表示首元素,是'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
printf("%d\n", strlen(arr[1])); //随机值 //从第二个元素开始访问
printf("%d\n", strlen(&arr)); //随机值 //虽然是数组地址,但数值上和首元素地址是一样的,所以还是从首元素往后访问
printf("%d\n", strlen(&arr + 1)); //随机值 //从元素'f'后的地址往后开始访问
printf("%d\n", strlen(&arr[0] + 1));//随机值 //从第二个元素开始往后开始访问
return 0;
}
代码3:(求大小)
c
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7 //数组名单独放在sizeof里面,求整个数组的大小,不要忘记还有个\0
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 //表示最后一个元素f后面的地址
printf("%d\n", sizeof(&arr[0] + 1));//4或8 //表示第二个元素的地址
return 0;
}
代码4:(求个数,\0前面的元素个数)
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", strlen(arr)); //6 //表示首元素地址,从首元素开始统计\0之前的元素个数
printf("%d\n", strlen(arr + 0)); //6 //表示首元素地址
printf("%d\n", strlen(*arr)); //非法访问 //表示首元素'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
printf("%d\n", strlen(arr[1])); //非法访问 //表示元素'b',他的ACSLL是98,传给strlen函数,98会被当成地址,程序会崩溃
printf("%d\n", strlen(&arr)); //6 //表示数组地址,但数值上和首元素地址一样,也是从首元素开始往后访问
printf("%d\n", strlen(&arr + 1)); //随机值 //表示'f'后面的地址,从这个地址往后不知道什么时候遇到\0
printf("%d\n", strlen(&arr[0] + 1)); //5 //表示第二个元素的地址
return 0;
}
代码5:(求大小)
c
#include <stdio.h>
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p)); //4或8 //p是一个指针变量,大小就是4或8
printf("%d\n", sizeof(p + 1)); //4或8 //p+1,是'b'的地址
printf("%d\n", sizeof(*p)); //1 //表示'a'
printf("%d\n", sizeof(p[0])); //1 //表示'a',p[0]== *( p + 0 )==*p
printf("%d\n", sizeof(&p)); //4或8 //表示指针变量p的地址
printf("%d\n", sizeof(&p + 1)); //4或8 //表示指针变量p的地址后面的一个地址
printf("%d\n", sizeof(&p[0] + 1));//4或8 //表示的是'b'的地址
return 0;
}
代码6:(求个数,\0前面的元素个数)
c
#include <stdio.h>
#include <string.h>
int main()
{
char* p = "abcdef";
printf("%d\n", strlen(p)); //6 //表示'a'的地址,从首元素开始统计\0之前的元素个数
printf("%d\n", strlen(p + 1)); //5 //表示'b'的地址
printf("%d\n", strlen(*p)); //非法访问 //表示首元素'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
printf("%d\n", strlen(p[0])); //非法访问 //表示首元素'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
printf("%d\n", strlen(&p)); //随机值 //表示指针变量p的地址
printf("%d\n", strlen(&p + 1)); //随机值 //表示指针变量p的地址后面的一个地址
printf("%d\n", strlen(&p[0] + 1));//5 //表示'b'的地址
return 0;
}
2.3 ⼆维数组
1、二维数组的每个元素是一个一维数组(即一行)。
2、每一行中的元素地址是连续的。
3、不同行之间的地址也是连续的,即整个二维数组在内存中是连续存储的。
c
#include <stdio.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); //48 //a是这个二维数组的数组名,单独放在sizeof表示计算整个数组的大小
printf("%d\n", sizeof(a[0][0])); //4 //表示第一行的第一个元素
printf("%d\n", sizeof(a[0])); //16 //a[0]是第一行这个数组的数组名,单独放,表示求第一行的大小
printf("%d\n", sizeof(a[0] + 1)); //4或8//表示第二行的地址,是一个地址
printf("%d\n", sizeof(*(a[0] + 1)));//4 //*(a[0] + 1)==*(*(a + 0) + 1),表示第一行的第一个元素
printf("%d\n", sizeof(a + 1)); //4或8//表是第二行地址
printf("%d\n", sizeof(*(a + 1))); //16 //*(a + 1)==a[1],表示第二行的数组名,单独放数组名就是求整个数组的大小
printf("%d\n", sizeof(&a[0] + 1)); //4或8//表示第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1)));//16 //表示第二行
printf("%d\n", sizeof(*a)); //16 //表示第一行
printf("%d\n", sizeof(a[3])); //16 //a[3] 的语法是合法的,因为它可以解析为指针运算(等价于 *(a + 3))。实际越界访问发生在解引用时(如 a[3][0])。sizeof的内部表达式是不计算的,因此无运行时错误。
return 0;
}
3. 指针运算笔试题解析
3.1 题⽬1:
c
#include <stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1); //因为&a + 1的类型是int(*)[5],所以要强制转化为int*
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//解析:
//a + 1表示第二个元素的地址,所以*(a + 1)的值为2
//&a + 1表示跳过整个数组,所以他的地址就是元素5后面的地址,把这个地址给了ptr,则ptr - 1就是元素5的地址

3.2 题⽬2
c
//在X86环境下 1
//假设结构体的⼤⼩是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;
}
//解析:
//0x1是十六进制数的1//指针+-数字表示跳过一个类型,构体的⼤⼩是20个字节,地址是16进制,所以要将20转换为0x14,进行加减,所以答案是00100014
//(unsigned long)p + 0x1,是将p储存的地址强制转化为整数,所以就是整数加整数,所以答案是00100001
//(unsigned int*)p + 0x1,是将p储存的地址强制转化为int*,所以跳过,是加4个字节

3.3 题⽬3
c
#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;
}
//解析:
//{ (0, 1), (2, 3), (4, 5) };这里是逗号操作符,所以正真的数组存的是{1,3,5}
//所以p[0]==*(p+0)=*p,所以答案是1

3.4 题⽬4
c
//假设环境是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;
}
//解析
// &p[4][2],4表示按照p的类型向后跳4次,2在表示在最后一次跳跃中,从其中的首元素地址开始向后跳两次
//第一次打印:%p是打印地址的,直接将-4转换成原码,再转换成补码,在转换为16进制,就得FFFFFFFC了
//第二次打印:就是-4



3.5 题⽬5
c
#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 + 1的类型是int(*)[2][5],所以要强制转化为int*
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//解析:
//&aa + 1表示跳过整个数组,指向10后面的地址
//aa + 1表示跳过首元素地址
//所以答案是10和5

3.6 题⽬6
c
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//解释
//第一次pa里面放的是首元素的地址
//后面经过pa++就变成了第二个元素的地址

3.7 题⽬7
c
#include <stdio.h>
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c }; // {"FIRST","POINT","NEW","ENTER"}
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;
}

**"别走!你的三连还在我这儿寄存着呢~点赞是门票,投币是VIP,收藏是复活甲,下次更新记得来取哦!"**
完.