1.sizeof和strlen 对比
1.1sizeof
sizeof用来计算变量所占内存空间大小,单位是字节,操作数是类型的话,计算的是使用类型创建的变量所占空间的大小
sizeof只关注占用内存空间大小,不在乎内存中存放什么数据
int main()
{
int a = 0;
printf("z%d\n", sizeof(a));
printf("%zd\n", sizeof(int));
printf("%d\n", sizeof a);
return 0;
}
- 注:当类似是变量a的形式,就可以省略括号
1.2strlen
strlen是c语言库函数,功能是求字符串长度
函数原型
size_t strlen ( const char * str );
解释:统计的是从strlen函数的参数str中这个地址开始向后,\0之前字符串中字符的个数,strlen函数会一直向后找\0字符,直到找到为止,所以可能存在越界查找
#include <stdio.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(arr1));
return 0;
}
解释:由于arr1字符数组中没有存放\0,所以strlen会一直向后查找,直到找到\0为止,arr2字符串数组中,是默认结尾存放\0的,所以可以正常打印,因为arr1是三个字符,占用3个字节,sizeof(arr1)结果是3,arr2包括abc和\0所以是4个字符,占4个字节,所以结果是4
1.3sizeof和strlen对比
sizeof
- 1.sizeof是操作符
- 2.sizeof计算操作数所占内存大小,单位是字节
- 3.不关注内存中存放什么数据
strlen - 1.strlen是库函数,使用需要包含头文件string. h
- 2.strlen是求字符串长度的,统计的是\0之前字符的个数
- 3.关注内存中是否有\0,没有就会一直找,直到找到为止
2.数组和指针练习题
2.1一维数组
//数组名是数组首元素地址
//&arr,数组名表示的是整个数组,取出的是整个数组的地址
// sizeof(arr),数组名表示整个数组,计算的是整个数组的大小,
//单位是字节
int main()
{
int a[] = { 1,2,3,4 };
printf("%zd\n", sizeof(a));//结果是16,sizeof(数组名)表示整个数组,a数组中有4个int类型的元素,
printf("%zd\n", sizeof(a + 0));//结果是4/8,a表示数组首元素地址,a+0,还是数组首元素地址
//a+0====&a[0]
printf("%zd\n", sizeof(*a));//结果是4,a是数组首元素地址,解引用表示第一个元素
//*a是第一个元素,a[0]
printf("%zd\n", sizeof(a + 1)); //结果是4/8,a表示数组首元素地址,+1表示第二个元素地址
//a+1====&a[1]
printf("%zd\n", sizeof(a[1]));//结果是4,a[1]表示第二个元素
printf("%zd\n", sizeof(&a));//结果是4/8,&a表示数组的地址
printf("%zd\n", sizeof(*&a));//结果是16, *&a,两者抵消,也就是sizeof(a)
//抵消,16,&a-int(*p)[4]=&a
//*p访问一个数组的大小,p+1跳过一个数组
printf("%zd\n", sizeof(&a + 1));//结果是4/8,&a表示数组地址,&a+1表示跳过整个数组的地址
//4/8
printf("%zd\n", sizeof(&a[0]));//结果是4/8,a[0]表示第一个元素,&a[0]表示第一个元素的地址
printf("%zd\n", sizeof(&a[0] + 1));//结果是4/8,&a[0]表示首元素地址,+1表示第二个元素地址
return 0;
}
2.2字符数组
练习1
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//结果是6,sizeof(arr)表示整个数组
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,第二个元素地址
return 0;
}
练习2
#include<string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%zd\n", strlen(arr));//随机值,字符数组中结尾没有\0
printf("%zd\n", strlen(arr + 0));//随机值
printf("%zd\n", strlen(*arr));//非法访问,只能传地址,传的是首字符,也就是访问97的地址,err
printf("%zd\n", strlen(arr[1]));//非法访问,传的是第二个元素,也就是访问98的地址,err
printf("%zd\n", strlen(&arr));//随机值,整个数组地址,起始位置地址
printf("%zd\n", strlen(&arr + 1));//随机值,跳过一个数组的地址,
printf("%zd\n", strlen(&arr[0] + 1));//随机值,第二个元素的地址,
return 0;
}
练习3
#include<string.h>
int main()
{
char arr[] = "abcdef";
printf("%llu\n", sizeof(arr));//结果是7,数组大小
printf("%llu\n", sizeof(arr + 0));//结果是4/8,首元素地址
printf("%llu\n", sizeof(*arr));//结果是1,第一个元素
printf("%llu\n", sizeof(arr[1]));//结果是1,第二个元素
printf("%llu\n", sizeof(&arr));//结果是4/8,数组的地址
printf("%llu\n", sizeof(&arr + 1));//结果是4/8,跳过一个数组的地址
printf("%llu\n", sizeof(&arr[0] + 1));//结果4/8,第二个元素地址
return 0;
}
练习4
int main()
{
char arr[] = "abcdef";
printf("%lld\n", strlen(arr));//结果是6,数组首元素地址,也就是起始位置从a开始
printf("%lld\n", strlen(arr + 0));//结果是6,首元素地址开始,也就是起始位置从a开始
printf("%lld\n", strlen(*arr));//结果是非法访问,首元素a,也就是访问97的地址,err
printf("%lld\n", strlen(arr[1]));//结果是非法访问,第二个元素b,也就是访问98的地址,err
printf("%lld\n", strlen(&arr));//结果是6,数组地址,也就是起始位置从a开始
printf("%lld\n", strlen(&arr + 1));//结果是随机值,跳过一个数组的地址
printf("%lld\n", strlen(&arr[0] + 1));//结果是5,第二个元素地址,也就是起始位置从b开始
return 0;
}
练习5
int main()
{
char* p = "abcdef";
printf("%lld\n", sizeof(p));//结果是4/8,指针变量
printf("%lld\n", sizeof(p + 1));//结果是4/8,b的地址
printf("%lld\n", sizeof(*p));//结果是1,首元素a
printf("%lld\n", sizeof(p[0]));//结果是1,首元素a,p[0]===*(p+0)
printf("%lld\n", sizeof(&p));//结果是4/8,指针变量p的地址
printf("%lld\n", sizeof(&p + 1));//结果是4/8,跳过p变量的地址
printf("%lld\n", sizeof(&p[0] + 1));//结果是4/8,第二个元素地址
return 0;
}
练习6
int main()
{
char* p = "abcdef";
printf("%lld\n", strlen(p));//结果是6,首元素地址起始
printf("%lld\n", strlen(p + 1));//结果是5,第二个元素b的地址开始
printf("%lld\n", strlen(*p));//结果是非法访问,首元素a,也就是访问97的地址,err
printf("%lld\n", strlen(p[0]));//结果是非法访问,首元素a,也就是访问97的地址,err,*(p+0)==p[0]
printf("%lld\n", strlen(&p));//结果是随机值,p的地址,从p所占空间起始位置查找
printf("%lld\n", strlen(&p + 1));//结果是随机值,p+1的地址,跳过p的地址位置查找
printf("%lld\n", strlen(&p[0] + 1));//结果是5,第二个元素b的地址开始
return 0;
}
2.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,第一行数组名,即第一行元素的大小
//第一行数组名单独放在sizeof内部,计算的是第一行大小
printf("%d\n", sizeof(a[0] + 1));//结果是4/8,第一行第二个元素地址
//a[0]是第一行数组的数组名,但是数组名并非单独放在sizeof内部,
// 所以数组名是数组首元素地址,+1就是第一行第二个元素地址
printf("%d\n", sizeof(*(a[0] + 1)));//结果是4,第一行第二个元素
printf("%d\n", sizeof(a + 1));//结果是4/8,第二行地址
//没有单独放在sizeof内部,没有&,就是首元素地址,第一行地址
printf("%d\n", sizeof(*(a + 1)));//结果是16,第二行大小==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,首元素
//首元素地址解引用
// *(a+0)==a[0]
printf("%d\n", sizeof(a[3]));//结果是16,第四行大小,不会越界
return 0;
}
注意:
- 1.sizeof(数组名)表示整个数组,计算整个数组大小
- 2.&数组名,表示整个数组,整个数组的地址
- 3.除此之外,所有数组名都是首元素地址
3.指针运算练习题
练习1
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}//2 5
解释:&a+1是跳过整个数组的地址,强制转换为int*复制给ptr, (a+1)表示第二个元素,(ptr-1)表示最后一个元素
练习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);//p+1 //跳过一个结构体,结构体是20个字节,0x100014
printf("%p\n", (unsigned long)p + 0x1); //0x100001
// 无符号整型
printf("%p\n", (unsigned int*)p + 0x1);//0x100004
//整形指针+1(整型)
return 0;
}
练习3
int main()
{
int a[4] = { 1, 2, 3, 4 };//a数组
int* ptr1 = (int*)(&a + 1);//ptr1存放的是跳过数组的地址
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
//ptr[-1],*(ptr-1), 向前移动一个整形,指向4,解引用得到4,十六进制打印,和十进制4一样
//小端
//a指向01000000,强换成int, +1就是1,在转换成int*, 指向01后面的,00,给ptr2,取四个整型, 00000002
//取出来还原成0x02000000,即*ptr2是20000000
return 0;
}
练习4
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];//&a[0][0]
printf("%d", p[0]);//*(p+0) 1
return 0;
}
练习5
int main()
{
int a[5][5];//a数组5行5列
int(*p)[4];//p是一个存放有4个整型元素数组的数组指针
p = a;//p指向a
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//&p[4][2]-&a[4][2]表示(p+4*4+2)-(a+4*5+2)=p-a-4,p和a的起始地址相同,差值为-4
//FFFFFFFC,-4
//跳过一个整型,指针-指针=元素个数
//%p是输出地址,输出-4的补码0xFFFFFFFC, %d输出整数,即为-4
return 0;
}
练习6
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//aa是一个2行5列的数组
int* ptr1 = (int*)(&aa + 1);//ptr1指向跳过整个数组的地址
int* ptr2 = (int*)(*(aa + 1));//第二行地址
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
//-一个整型,10,-一个整型,5
return 0;
}
练习7
int main()
{
char* a[] = { "work","at","alibaba" };//字符指针数组a,分别存放指向3个字符串的首字符地址
char** pa = a;//首元素地址
pa++;//第二个元素地址
printf("%s\n", *pa);//at
return 0;
}
练习8
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
//字符指针数组.分别是指向"ENTER","NEW","POINT","FIRST"的首字符地址
char** cp[] = { c + 3,c + 2,c + 1,c };
//cp存放的是{c+3, c+2, c+1, c},分别是存放,指向FIRST的地址, 指向POINT地址, 指向NEW地址,指向,ENTER的地址,
char*** cpp = cp;
//cpp指向cp,即首元素地址,c+3的地址
printf("%s\n", **++cpp);
//++cpp, 跳过第一个元素指向cp的第二个元素c+2,
//*++cpp表示c+2,指向POINT的首字母地址,**++cpp拿出c+2里面的内容,,从P开始打印,表示POINT,
printf("%s\n", *-- * ++cpp + 3);
//++cpp指向,指向cp第三个元素,指向c+1, ,*++cpp得到c+1,
//--*++cpp,c+1, --c,得到c,指向变为ENTER
//*--*++cpp, 拿到E的地址,
// *--*++cpp+3指向ENTER第四个元素
//从E开始得到ER
printf("%s\n", *cpp[-2] + 3);
//cpp[-2],是 *(cpp-2)
//指向c+3, 解引用得到c+3,
//*cpp[-2],**(cpp-2)+3
//解引用得到FIRST,
//从首字母地址开始+3
//*cpp[-2]+3, 从第四个位置开始,即ST
printf("%s\n", cpp[-1][-1] + 1);
//指向还是c
//cpp[-1][-1] + 1,*(*(cpp-1)-1)+1
//cpp-1, 指向c+2, 解引用得到c+2, 指向POINT的地址
//*(*(cpp-1)-1),指向NEW得地址,首字母地址+1, 指向E, 从E开始EW
return 0;
}