前言:
通过C语言指针进阶的知识,接下来挑战指针的必刷题。
/知识点汇总/
回顾:数组名的意义
数组名是数组首元素的地址,但是有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)则计算的是整个数组的大小,单位是字节
2.&数组名,这里得数组名表示整个数组,&数组名取出的是整个数组的地址
综述求打印的结果
#include
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16,①
printf("%d\n", sizeof(a + 0));//4/8,②
printf("%d\n", sizeof(*a));//4,③
printf("%d\n", sizeof(a + 1));//4/8,④
printf("%d\n", sizeof(a[1]));//4,⑤
printf("%d\n", sizeof(&a));//4/8,⑥
//a -- 类型:int int *p = a;
//&a --- 类型:int (* )[4] int (*p)[4] = &a;
printf("%d\n", sizeof(*&a));//16,⑦
//sizeof(*&a) 等价 sizeof(a) ,相当于*&抵消了
printf("%d\n", sizeof(&a + 1));//4/8,⑧
printf("%d\n", sizeof(&a[0]));//4/8,⑨
printf("%d\n", sizeof(&a[0] +1));//4/8,⑩
//等价:&a[1] == &a[0]+1 == a+1
return 0;
}
解释说明:
#include
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6,数组名单独放在sizeof内部,计算的是整个数组的大小,单位是字节
printf("%d\n", sizeof(arr + 0));//4/8,此时数组名为首元素的地址,地址的大小就是4/8字节,与类型无关
//指针变量的大小与类型无关,不管什么类型的指针变量,大小都是4/8字节
//指针变量是用来存放地址的,地址存放需要多大的空间,指针变量的大小就是几个字节
//32位环境下,地址是32个二进制位,需要4个字节,所以指针变量的大小就是4个字节
//64位环境下,地址是64个二进制位,需要8个字节,所以指针变量的大小就是8个字节
printf("%d\n", sizeof(*arr));//1,此时arr表示首元素地址,*arr解引用就是首元素的大小1byte
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取出整个数组的地址,&arr+1,得到整个数组之后的地址,地址的大小就是4/8
printf("%d\n", sizeof(&arr[0] + 1));//4/8,得到第二个元素的地址,地址的大小就是4/8
return 0;
}
#include
#include
//strlen:统计的是字符串中'\0',之前出现的字符的个数
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
//'a','b','c','d','e','f',注意不会自动补'\0'
printf("%d\n", strlen(arr));//随机值,无法判断'\0'何时出现,另外此时arr是首元素的地址,从首元素地址开始计算
printf("%d\n", strlen(arr + 0));//随机值,arr是首元素的地址,arr+0还是首元素地址
printf("%d\n", strlen(*arr));//err,strlen参数必须是地址,不能参数值,此时*arr表示首元素'a' --ASCLL: 97
//以strlen的角度,会认为被传参进去的'a' -- 97是地址,所以涉及到了非法访问,err报错
printf("%d\n", strlen(arr[1]));//err,'b' -- 98 同理
printf("%d\n", strlen(&arr));//随机值,&arr取出整个数组的地址,
//类型是char (*)[6] -->实际接收会是 const char* 得到的是,首元素地址,所以依然会从首元素开始计数
printf("%d\n", strlen(&arr + 1));//随机值,&arr取出整个数组的地址,+1,得到整个数组之后的地址,开始计数,但依然无法确定'\0',故随机值
printf("%d\n", strlen(&arr[0] + 1));//随机值,同理,从第二个地址开始计数
return 0;
}
#include
int main()
{
char arr[] = "abcdef";
//"abcdef\0" -- 字符串的格式会自动约束结束标志符'\0'
printf("%d\n", sizeof(arr));//7
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;
}
#include
#include
//strlen 传参接收的是地址
int main()
{
char arr[] = "abcdef";
//"abcdef\0" -- 字符串的格式会自动约束结束标志符'\0'
printf("%d\n", strlen(arr));//6,首元素地址
printf("%d\n", strlen(arr + 0));//6,首元素地址
printf("%d\n", strlen(*arr));//err,传参接收的是地址,非法访问
printf("%d\n", strlen(arr[1]));//err,传参接收的是地址,非法访问
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr + 1));//随机值,得到的整个数组之后的地址开始计数,无法知道何时遇到'\0'
printf("%d\n", strlen(&arr[0] + 1));//5,从第二个元素地址开始计数
return 0;
}
#include
int main()
{
char* p = "abcdef";
// "abcdef\0"
//p指针变量指向的第一个字符的地址
printf("%d\n", sizeof(p));//4/8,首元素的地址,计算的是指针变量的大小
printf("%d\n", sizeof(p + 1));//4/8,p+1地址加1,还是地址
printf("%d\n", sizeof(*p));//1,对首元素的地址解引用,*p == 'a'
printf("%d\n", sizeof(p[0]));//1,p[0] == *(p+0) == *p 首元素的大小
printf("%d\n", sizeof(&p));//4/8,&p,取的指针变量的新开辟空间的起始地址,地址大小就是4/8
printf("%d\n", sizeof(&p + 1));//4/8,对指针变量p的地址加1,指向了指针变量空间地址的下一个地址,
//与数组的元素无关,因为是指针变量p开辟的地址空间的指针偏移
printf("%d\n", sizeof(&p[0] + 1));//4/8,&p[0]取出第一个元素的地址加1,得到第二个元素的地址
//p+1 == &p[0] + 1
return 0;
}
#include
int main()
{
char* p = "abcdef";
// "abcdef\0"
printf("%d\n", strlen(p));//6,首元素的地址,从首元素地址计数
printf("%d\n", strlen(p + 1));//5,p+1地址加1,得到第二个元素地址,开始计数
printf("%d\n", strlen(*p));//err
printf("%d\n", strlen(p[0]));//err
printf("%d\n", strlen(&p));//随机值,此时&p,取出的是指针变量的地址空间,与原数组元素的地址空间无关
//所以无法确定从指针变量p的起始地址开始计数时,何时出现'\0'结束。故,随机值
printf("%d\n", strlen(&p + 1));//随机值,对指针变量p的地址加1,指向了指针变量空间地址的下一个地址,
//与数组的元素无关,因为是指针变量p开辟的地址空间的指针偏移
//所以也无法确定'\0',故随机值
printf("%d\n", strlen(&p[0] + 1));//5,&p[0]取出第一个元素的地址加1,得到第二个元素的地址,开始计数
//p+1 == &p[0] + 1等价
return 0;
}
二维数组理解:其实是数组的数组,是一维数组的数组
#include
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48,数组名a单独放在了sizeof内存,表示整个数组,sizeof(a)计算的整个数组的大小,单位是字节。3*4*4 = 48
printf("%d\n", sizeof(a[0][0]));//4,a[0][0]是数组的第一行第一个元素,计算的就是一个元素的大小
printf("%d\n", sizeof(a[0]));//16,a[0]是第一行这个一维数组的数组名,数组名单单独放在了sizeof内部,
//a[0]就表示整个第一行这个一维数组,sizeof(a[0])计算的整个第一行这个一维数组的大小
printf("%d\n", sizeof(a[0] + 1));//4/8,a[0]并非单独放在sizeof内部,也没有&,所以a[0]表述二维数组首元素,也就是二维数组的第一行的数组的首元素地址
//+1,也就是第一行第二个元素的地址,地址就是4/8
//a[0] == &a[0][0]
//a[0] + 1 == &a[0][1]
printf("%d\n", sizeof(*(a[0]+ 1)));//4,a[0]并非单独放在sizeof内部,也没有& ,所以a[0]表述二维数组首元素,也就是二维数组的第一行的数组的首元素地址
//+1,也就是第一行第二个元素的地址,再解引用得到第一行第二个元素的大小
printf("%d\n", sizeof(a + 1));//4/8,a并非单独放在sizeof内部,也没有& ,所以a表述二维数组首元素,也就是第一行的数组名的首地址,地址+1,得到第二行的数组首地址,地址大小就是4/8.其中a的类型是:int (*)[4]的指针数组
printf("%d\n", sizeof(*(a+1)));//16,a并非单独放在sizeof内部,也没有& ,所以a表述二维数组首元素,也就是第一行的数组名的首地址,再加1,得到第二行的数组首地址,最后解引用得到第二行的数组大小4*4
//*(a+1) == a[1]
//以这个角度理解:sizeof(a[1]),a[1]作为第二行的数组名,而数组名单独放在sizeof内部,则计算的是第二行的整个数组大小4*4
printf("%d\n", sizeof(&a[0]+ 1));//4/8,a[0]是第一行的地址,&a[0]取出第一行一维数组的地址,地址加1,得到第二行的数组地址,类型是int (*)[4]
printf("%d\n", sizeof(*(&a[0] + 1)));//16,同理,计算的第二行的数组元素大小
printf("%d\n", sizeof(*a));//16,a并非单独放在sizeof内部,也没有&,a表示第一行的首地址,*a得到第一行的数组元素大小
//*a == *(a+0) == a[0]
printf("%d\n", sizeof(a[3]));//16,不会越界,因为sizeof内部是不会进行表达式的计算,只计算表达式的类型大小,所以这里a[3]表示第四行的数组名,sizoef计算的是第四行的整个数组大小4*4
return 0;
}
指针就是地址,单元编号 = 地址 = 指针,相互等价
指针大小、指针类型、字符指针、数组指针、函数指针、指向函数指针数组的指针…
#include
int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
//&a表示取出整个数组的地址,加1,跳过整个数组的地址,类型都是int (*)[4],因为取数组名的地址是数组指针类型(指针指向数组名)。
//所以用int* 类型的指针变量接收需要将int (*)[4]强转为int*
printf("%d, %d",*(a+1),*(ptr - 1));//2 , 5
//*(a+1) == a[1]等价
return 0;
}
#include
//这里结构体大小是20字节
//基于X86环境下:
struct Test
{
int Num;//4byte
char* pcName;//4byte
short sDate;//2byte
char cha[2];//2byte
short sBa[4];//8byte
}*p;
//假设p的值为0x100000,如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
//0x开头的是十六进制的数字
//p + 0x1表示p指针十六进制加1
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);//00100014,结构体指针加1,跳过20个字节,但注意20的16进制表示为0x14
printf("%x\n", p + 0x1);//100014
printf("%p\n", (unsigned long)p + 0x1);//0x100001,因为结构体类型被强转位整型,整型加1,就是加1即可
printf("%p\n", (unsigned long*)p + 0x1);//0x100004,结构体被强转为无符号的指针类型,指针类型的p加1,则跳过4个字节即可
return 0;
}
#include
int main()
{
int a[4] = { 1,2,3,4 };
int* ptr1 = (int*)(&a + 1);
//&a取出整个数组的地址,地址加1,得到跳过整个数组之后的地址 -->ptr1也就是指向整个数组之后的地址,ptr[-1] == *(ptr-1)就得到了,a数组的最后一个元素4
int* ptr2 = (int*)((int)a + 1);
//a数组名表示数组首元素的地址,(int)a,将地址数据强转为int类型,再加1,整型数据加1就是加1,比如地址大小以十进制表示为152236,加1就是152237,这里以十六进制打印同理,直接加1即可
//也就是说,从数组首元素的地址的第一个字节加1,得到第二个字节的地址,则ptr2就指向该位置,最后解引用4个字节,从第二个字节开始计4个字节,然后以%x格式输出(以%x格式不会保留,前置的0)
// 1 2 3 4
//小端存储:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
// a (a+1)== ptr2 再解引用4个字节:00 00 00 02,小端存储并输出格式为%x:02000000--->2000000(%x格式前置的0被省略)
printf("%x , %x", ptr1[-1], *ptr2);//4, 200000
//ptr1[-1] == *(ptr-1)
return 0;
}
#include
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5) };//逗号表达式
//a[3][2]实际存放元素为: {1,3,5};
//[3][2]:
//1 3
//5 0
//0 0
int* p;
p = a[0];
//a[0] 表示第一行的数组名,表示第一行的首元素地址--> 1 3
printf("%d", p[0]);//1
return 0;
}
#include
int main()
{
int a[5][5];
int (*p)[4];//数组指针
p = a;//类型合适?
//虽然可以接收数组指针类型,但存在一定的类型差异
//a -- int(*)[5];
//p -- int(*)[4];
//类型参数[]有差异,会有一个警告
printf("%p,%d\n", &p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);//FFFF FFFC , -4
//&p[4][2] - &a[4][2] --->小地址减大地址 -4,%p是打印地址,认为地址中存储的补码就是地址,-4就是一个很大的数
//1000 0000 0000 0000 0000 0000 0000 0100 --- -4原码
//1111 1111 1111 1111 1111 1111 1111 1011 --- -4反码
//1111 1111 1111 1111 1111 1111 1111 1100 --- -4补码
//F F F F F F F C --- 十六进制表示FFFFFFFC
//指针-指针 = 元素个数
//%p是打印地址,认为地址中存储的补码就是地址
return 0;
}
#include
int main()
{
int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};
int* ptr1 = (int*)(&aa + 1);
//&aa取出整个数组的地址,加1,得到跳过整个数组之后的地址
//*(ptr1-1)得到整个数组之后的前一个地址,也就是数组最后一个元素的地址,再解引用得到10
int* ptr2 = (int*)(*(aa + 1));
//aa表述数组名,二维数组的首元素地址是第一行的数组地址,加1,得到第二行的数组地址,再解引用得到第二行的元素,也就是5
printf("%d,%d",*(ptr1 - 1),*(ptr2 - 1));//10,5
return 0;
}
#include
int main()
{
char* a[] = { "work","at","alibaba" };
//指针数组:指向每个字符串的第一个字符
char** pa = a;
//用pa二级指针接收a的地址,a地址又是第一个字符串的地址,所以是二级指针
pa++;
//pa一开始指向第一个字符串首地址
//pa++则指向第二个字符串的首地址
printf("%s\n", *pa);//at
return 0;
}
#include
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
//cpp指向cp的首元素地址,cp指向c的首元素地址
//++cpp指向cp的第二个元素的地址,cp的第二个元素地址,解引用得到(c+2),c+2指向c的第三个字符串首地址,解引用得到P
printf("%s\n", **++cpp);//得到的P的地址 ---- POINT
//cpp在上条语句执行后,指向cp+1的首元素地址,cp+1指向c的第三个字符串首元素地址
//则++cpp指向cp的第三个元素的地址,解引用得到(c+1),c+1指向c的第二个字符串的首元素地址,
//解引用的得到N,再减减得到C的第一个字符串首元素地址,解引用得到E,最后再+3,指针偏移3得到第一个字符串的第四个元素E
printf("%s\n", *-- * ++cpp + 3);//得到的E的地址 -- EN
//cpp[-2] == *(cpp -2)等价,表示在上条语句的基础上,cpp指向cp的第三个元素地址,然后cpp-2得到cp的首元素地址,
//解引用得到c+3,而c+3指向了c的第四个字符串首元素地址,解引用得到了F的地址,最后加3,指针偏移3得到了第四个字符串第四个元素地址S
printf("%s\n", *cpp[-2] + 3);//得到的是S的地址 - ST
//*cpp[-2] + 3 === * *(cpp - 2) + 3等价
//cpp[-1] == *(cpp -1)等价
//cpp[-1][-1] == *(cpp[-1] -1)等价
//cpp[-1][-1] == *(*(cpp - 1) - 1)等价
//cpp[-1][-1] +1 == *(*(cpp - 1) - 1) + 1
//所以cpp此时依然根据上条语句指向cp的第三个元素地址,然后cpp-1,则指向cp的第二个首元素地址,
//解引用得到(c+2),(c+2)-1就得到了c+1,则c+1指向了c的第二个字符串的首字符地址,再解引用得到N,最后加1,得到E的地址
printf("%s\n", cpp[-1][-1] + 1); //得到的是E的地址 --- EW
//cpp[-1][-1] +1 == *(*(cpp - 1) - 1) + 1
return 0;
}
数组名是数组首元素的地址,但是有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)则计算的是整个数组的大小,单位是字节
2.&数组名,这里得数组名表示整个数组,&数组名取出的是整个数组的地址
熟练的掌握指针有助于C语言之路得心应手
半亩方糖一鉴开,天光云影共徘徊。
问渠哪得清如许?为有源头活水来。–朱熹(观书有感)