目录
一.sizeof和strlen的对⽐
1.sizeof
sizeof:是单目操作符,sizeof 计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。//都是计算的类型的长度
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
代码举例:
int main()
{
int a = 10;
printf("zd\n", sizeof(a));//4
printf("zd\n", sizeof(int));//4
printf("zd\n", sizeof a);//4 变量可以去掉括号,
//printf("zd\n", sizeof int);错误写法,类型不能去掉括号
int arr1[4] = { 0 };
int arr2[4] = { 0 };
printf("zd\n", sizeof(arr1));//16
printf("zd\n", sizeof(arr2));//16
printf("zd\n", sizeof(int []));//16
printf("%zd\n", sizeof(char[4]));//16
return 0;
}
总结:
1.sizeof:计算的是类型所占的空间大小,或者是这个变量或者数组占了几个字节。
2.数组去掉名字是类型,sizeof也可以计算数组的类型。
2.strlen - 针对字符串
strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
size_t strlen(const char* str);
| |
| 参数
|
返回类型
strlen函数统计的\0之前的字符个数,strlen函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。
代码举例:
int main()
{
char arr[20] = "abcdef";
size_t len = strlen(arr);
printf("%d\n", len);//6
len = sizeof(arr);
printf("%zd\n", len);//20
char arr1[] = { 'a','b','c' };
//char arr[6] = "abcdef"错误写法,只能放下6个字符的大小,但你要放7个
printf("%zd\n", strlen(arr1));//随机值 - 找到\0为止
return 0;
}
3.sizeof 和 strlen的对比
sizeof:
1.sizeof是单目操作符
2.sizeof计算操作数所占内存的⼤⼩,单位是字节
3.不关注内存中存放什么数据
4.sizeof不挑类型
strlen:
1.strlen是库函数,使用需要包含头文件strlen.h
2.strlen是求字符串长度的,统计的是\0之前字符的个数
3.关注内存中是否有\0,如果没有\0,就会持续往后找,可能会越界。
4.只能针对字符串
二. 数组和指针笔试题解析
1.⼀维数组
数组名的理解:
数组名一般表示数组首元素的地址
但有2个列外:
1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名,数组名表示整个数组,取出的数组的地址。
3.除此之外,所有遇到的数组名都是数组首元素的地址。
代码举例:地址根据x86或者64位环境显示结果
int main()
{
int a[] = { 1,2,3,4 };//a数组有4个元素,每个元素是int类型的数据
printf("%d\n", sizeof(a));//sizeof(数组名)的情况,计算的是整个数组的大小,单位是字节 - 16
printf("%d\n", sizeof(a + 0));//a表示的就是数组首元素的地址,a+0还是首元素的地址 - sizeof计算的是第一个元素的地址大小 - 4/8
printf("%d\n", sizeof(*a));//a表示数组首元素的地址,*a就是首元素,大小就是 - 4
printf("%d\n", sizeof(a + 1));//a表示数组首元素的地址,a+1就是第二个元素的地址,- sizeof计算的是第一个元素的地址大小 - 4/8
a == &a[0],类型:int*
printf("%d\n", sizeof(a[1]));//计算第二个元素的大小 - 4
printf("%d\n", sizeof(&a));//&a - 取出的是数组的地址,但是数组的地址也是地址,是地址,大小是4/8
printf("%d\n", sizeof(*&a));//sizeof(a) - 16
1.&和*抵消了,
2.&a的类型是数组指针,int(*)[4],*&a就是对数组指针解引用访问一个数组的大小,- 16
printf("%d\n", sizeof(&a + 1));//&a+1是跳过整个数组后的地址,是地址,大小就是 - 4/8
printf("%d\n", sizeof(&a[0]));//*a[0]是数组第一个元素的地址,大小就是 - 4/8
printf("%d\n", sizeof(&a[0] + 1));//&a[0]+1是数组第二个元素的地址,大小就是 - 4/8
2.字符数组
代码1:
代码举例:地址根据x86或者64位环境显示结果
int main()
{
char arr[] = { 'a','b','c','d','e','f' };//arr数组有6个元素
printf("%d\n", sizeof(arr));//计算的是整个数组的大小 - 6
printf("%d\n", sizeof(arr + 0));//arr+0 是数组第一个元素的地址 - 4/8
printf("%d\n", sizeof(*arr));//*arr是首元素,计算的是首元素的大小 - 1
printf("%d\n", sizeof(arr[1]));//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
}
代码2:
代码举例:
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//遇到\0为止 - 随机值
printf("%d\n", strlen(arr + 0));//随机值
printf("%d\n", strlen(*arr));//程序会崩掉
//strlen:参数是地址,* arr是元素'a', 'a'的ASCLL码值是97,97被当成地址了访问了,没有分配给你去使用,程序会崩掉
printf("%d\n", strlen(arr[1]));//传递的第二个元素 - 程序会崩掉
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//随机值
}
代码3:
代码举例:
int main()
{
char arr[] = "abcdef";
printf("%zd\n", sizeof(arr));//7
printf("%zd\n", sizeof(arr + 0));//arr是数组首元素的地址,地址大小是4/8个字节
printf("%zd\n", sizeof(*arr));//*arr是数组的首元素,这里计算的是首元素的大小 1
printf("%zd\n", sizeof(arr[1]));//1
printf("%zd\n", sizeof(&arr));//&arr - 取出的是数组的地址,数组的地址也是地址,大小是4/8
printf("%zd\n", sizeof(&arr + 1));//&arr + 1是跳过整个数组后的地址,是地址,大小就是 - 4/8
printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 - 4/8
}
代码4:
代码举例:
int main()
{
char arr[] = "abcdef";
printf("%zd\n", strlen(arr));//6
printf("%zd\n", strlen(arr + 0));//arr+0是数组首元素的地址 - 6
printf("%zd\n", strlen(*arr));//传递的是'a'-97,把97传给strlen访问97地址内存找字符串,不可访问,错误代码
printf("%zd\n", strlen(arr[1]));//'b' - 98,会对98解引用访问的,错误代码
printf("%zd\n", strlen(&arr));//&arr虽然是数组的地址,但是也是指向数组的起始位置 - 6,这样的代码会有警告 ,类型有所差异
//&arr取出数组的地址,应该放在数组指针里面
//char(*p)[7] = &arr;数组指针
//char(*)[7]; 类型
printf("%zd\n", strlen(&arr + 1));//随机值
printf("%zd\n", strlen(&arr[0] + 1));//从第二个元素地址开始访问 - 5
}
代码5:
代码举例:
int main()
{
char* p = "abcdef";//常量字符串
//p的类型char*
printf("%zd\n", sizeof(p));//计算的是指针变量的大小 - 4/8
printf("%zd\n", sizeof(p + 1));//p+1是'b'的地址,是地址大小就是 - 4/8
printf("%zd\n", sizeof(*p));//*ps'a',大小是 - 1
printf("%zd\n", sizeof(p[0]));//p[0]-->*(p+0) -- *p - 1
printf("%zd\n", sizeof(&p));//&p也是地址,是指针变量p的地址,大小是 - 4/8
//p是首元素的地址,&p是指针变量的地址,用二级指针存储一级指针的地址
printf("%zd\n", sizeof(&p + 1));//&p+1是指向p指针变量后面的空间,也是地址 - 4/8
//char* p;
//char** q = &p;char**是指针变量的类型 - 指向对象的类型是char*
//q+1//跳过一个char*的变量
printf("%zd\n", sizeof(&p[0] + 1));//&p[0]+1 -->(p+1)-->是'b'的地址,是地址就是 - 4/8
}
代码6:
代码举例:
int main()
{
char* p = "abcdef";//常量字符串
printf("%zd\n", strlen(p));//6
printf("%zd\n", strlen(p + 1));//5
printf("%zd\n", strlen(*p));//'a'-97-97作为地址,接应用访问,所以错误代码
printf("%zd\n", strlen(p[0]));//p[0]-->*(p+0)-->*p,接应用访问,所以错误代码
printf("%zd\n", strlen(&p));//随机值
printf("%zd\n", strlen(&p + 1));//随机值
printf("%zd\n", strlen(&p[0] + 1));//5
}
3.⼆维数组
代码举例:地址根据x86或者64位环境显示结果
int main()
{
//二维数组也是数组,之前对数组名理解也适合
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//12*4 = 48,数组名单独放在sizeof内部,计算的整个数组的大小
printf("%d\n", sizeof(a[0][0]));//计算的第一个元素 - 4
printf("%d\n", sizeof(a[0]));//a[0]是第一行这个一维数组的数组名,数组名单独放在sizeof内部
//计算的是第一行的大小 - 16
printf("%d\n", sizeof(a[0] + 1));//a[0]第一行这个一维数组的数组名,这里表示数组首元素
//也就是a[0][0],a[0]+1是a[0][1]的地址 - 4/8
printf("%d\n", sizeof(*(a[0] + 1)));//第一行第二个元素,a[0][1] - 4
printf("%d\n", sizeof(a + 1));//a是二维数组的数组名,但是没有&,也没有单独放在sizeof内部
//所以这里a是数组首元素的地址,应该是第一行的地址,a+1是第二行的地址,所以大小 - 4/8
printf("%d\n", sizeof(*(a + 1)));//*(a+1)==>a[1] - 第二行的数组名,所以计算的第二行的大小 - 16
printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,&a[0]+1就是第二行的地址 - 4/8
printf("%d\n", sizeof(*(&a[0] + 1)));//对第二行地址解引用,访问的是第二行,计算的是第二行的大小 - 16
printf("%d\n", sizeof(*a));//这里的a是第一行的地址,*a就是第一行,sizeof(*a)计算的是第一行的大小 - 16
//*a-->*(a+0)-->a[0]
printf("%d\n", sizeof(a[3]));
//这里不存在越界,因为sizeof内部的表达式不会真实计算的。
//计算的是第四行的大小 - 16 - 计算的就是a[0]
return 0;
}
三. 指针运算笔试题解析
题⽬1
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);//强制转换成int*赋给ptr,解引用(+-)会发生变化
printf("%d,%d", *(a + 1), *(ptr - 1));//2 5
//*(a+1) a表示数组首元素地址,a+1表示第二个元素的地址,解引用得到 - 2
//*(ptr - 1)) 取出整个数组的地址+1,跳过整个数组,(ptr - 1)拿到元素5的地址,解引用 - 5
return 0;
}
题⽬2
在X86环境下 - x64环境结构体指针的大小会不一样
假设结构体的⼤⼩是20个字节
程序输出的结果是啥?
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;//0x100000是整形,要强制类型转换成结构体指针类型,放到指针变量
int main()
{
printf("%p\n", p + 0x1);//+1跳过一个结构体的大小,0x100000+20 == 0x100014
printf("%p\n", (unsigned long)p + 0x1);//p被强制转换整形+1,0x100000+1 == 0x100001
printf("%p\n", (unsigned int*)p + 0x1);//p被强制类型转换成无符号整形指针+1,0x100000+4 ==0x100001
return 0;
}
考察指针+1到底加几 - 指针类型变了,解引用和一次访问权限也要跟着变
注意十六进制0x1的1也是1
题⽬3
int main()
{
//注意逗号表达式
int a[3][2] = { (0, 1), (2, 3), (4, 5) };//真正初始化的是{1,3,5} <==>{{1,3},{5,0},(0,0)}
//每一行数组得用大括号初始化,用小括号,是逗号表达式,会从左往右以此计算,整个表达式是最后个表达式结果
int* p;
p = a[0];//数组名表示首元素地址,没有放在sizeof内部,也没有&
printf("%d", p[0]);//1
return 0;
}
题⽬4
假设环境是x86环境,程序输出的结果是啥?
int main()
{
int a[5][5];
int(*p)[4];//p是数组指针,指向的数组4个int类型的元素,一次访问4个整形,或是跳过4个整形
p = a;//a表示首元素的地址,就是二维数组第一行的地址,类型会有差异,a的地址类型应该是:int(*p)[5]
//但是p也能接收a的地址,一次访问4个整形,或是跳过4个整形
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//这里就是低地址 - 高地址 = //中间元素个数
//%d = -4 - 打印整数的源码,内存存储的是补码
//%p = -4 - 把-4的补码翻译成16进制打印 - FFFFFFFC
//10000000000000000000000000000100 - 源码-4
//11111111111111111111111111111011 - 反码
//11111111111111111111111111111100 - 补码-4
//FFFFFFFC - 转成16进制
return 0;
}
分析:
1.分析指针变量的类型和二维数组的类型是不一样的
2.先找&a元素位置,再找&p的元素的位置,因为数组指针类型不一样,一次跳过的整形个数也会不一样
3.指针 - 指针得到的是中间元素个数
4.内存存储的补码,以补码形式打印出来的地址,转换成16进制。
题⽬5
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);//取出整个数组的地址+1,跳过整个数组
int* ptr2 = (int*)(*(aa + 1));//aa表示首元素地址,也就是第一行的地址,aa+1就是第二行的位置
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//5 10
return 0;
}
题⽬6
int main()
{
char* a[] = { "work","at","alibaba" };//a是char*类型的指针数组,初始化了3个元素
char** pa = a;//pa指向对象是char*类型,pa指向a的第一个元素,第一个字符w起始位置
pa++;//pa指向a第二个元素,第一个字符a起始位置
printf("%s\n", *pa);//at
return 0;
}
题⽬7(有点难)
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;
}
分析:
1.c是个char*类型的数组,里面有4个元素,放的是4个字符串首字母的地址。
2.cp是个char**类型的数组,里面有4个元素,数组名表示首元素地址,分别指向c数组4个元素
3.cpp是个char***类型的数组,是个3级指针,把2级指针地址存在了3级指针,数组名表示数组首元素地址,存了cp的地址 -相当于指向了cp起始位置
再分析:
1. **++cpp:指向cp起始位置++指向cp1的位置,第一次解引用访问到cp数组c+2这块空间,第二次解引用访问到c+2指向c2的空间。
2. * -- * ++cpp + 3:先看运算符优先级,+号是最低的,++cpp:指向cp2的位置,解引用访问到cp数组c+1这块空间,--:c+1-1里面值-1 = c,
指向了c0,解引用访问到这块空间,然后地址+3,拿到E
3.cpp[-2] -->*(cpp-2),cpp-2指向了cp0,解引用访问到cp数组c+3这块空间,再解引用访问到c3这块空间,然后F地址+3,拿到S
4.cpp[-1]-->*(cpp-1),cpp-1指向cp1,解引用访问到cp数组c+2这块空间,再*(c+2-1),访问到c1这块空间N的地址+1,拿到E
5.cpp[-1][-1]:相当于*(*(cpp-1)-1),先算* (cpp - 1),得到*(c+2-1)再解引用。
总结:画图能帮我们快速解决这些指针运算题,要清楚指针指向了谁,每次地址的访问权限。