你确定你学会指针了? 你确定你明白数组名了?
如果你觉得你学的还不错,就进来看看吧,相信你看完之后一定能收获更多。
数组名的理解一定要弄清楚
数组名是数组首元素的地址 但是有2个例外:
- sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
- &数组名,这里的数组名表示整个数组,&数组名取出的是数组的地址
下面是常见 指针和数组笔试题及解析 (解析以注释的形式写在代码当中)
下面题目的类型都是给你一个数组,你来判断printf到底输出什么。
int main()
{//一维数组int a[] = { 1,2,3,4 };//4个元素,每个元素4个字节(int类型)printf("%d\n", sizeof(a));//16 //数组名a单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小单位是字节,是16字节printf("%d\n", sizeof(a + 0));//4///a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址,a+0还是首元素的地址printf("%d\n", sizeof(*a));//4//a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址,*a是首元素,相当于a[0]printf("%d\n", sizeof(a + 1));//4//a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址,a+1就是第二个元素的地址,相当于a[1]printf("%d\n", sizeof(a[1]));//4//a[1]就是数组的第二个元素,这里计算的就是第二个元素的大小printf("%d\n", sizeof(&a));//4//是取出数组的地址,但是数组的地址也是地址,是地址就是4/8个Byte,数组的地址和数组首元素的地址的本质区别是类型的区别,并非大小的区别//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//&a数组的地址,&a+1还是地址,此时指向的是数组中a[3](4)后面的地址(数组指针+1跳过一个数组)printf("%d\n", sizeof(&a[0]));//4//&a[0]是首元素的地址printf("%d\n", sizeof(&a[0] + 1));//4//&a[0]是首元素的地址,&a[0]+1就是第二个元素的地址return 0;
}
int main()
{//字符数组char arr[] = { 'a','b','c','d','e','f' };//6个字符printf("%d\n", sizeof(arr));//6//数组名arr单独放在sizeof内部,计算的是整个数组的大小printf("%d\n", sizeof(arr + 0));//4//arr是首元素的地址==&arr[0],是地址就是4个字节,指针变量的大小和类型无关,不管什么类型的指针变量,大小都是4/8个字节printf("%d\n", sizeof(*arr));//1//arr是首元素的地址,*arr就是首元素printf("%d\n", sizeof(arr[1]));//1//第二个元素printf("%d\n", sizeof(&arr));//4//&arr是数组的地址,只要是地址就是4/8,sizeof(&arr)就是4/8个字节printf("%d\n", sizeof(&arr + 1));//4//&arr+1 是跳过数组后的地址,即f后的地址printf("%d\n", sizeof(&arr[0] + 1));//4//第二个元素的地址,是地址就是4/8//strlen是求字符串长度的函数,统计的是在字符串中\0之前出现的字符的个数printf("%d\n", strlen(arr));//随机值(>=6)//arr是首元素的地址,往后走不确定到哪里才能到\0,字符数组不像整型数组一样最后一个数字后面就是\0,字符数组的\0不知道在后面什么地方printf("%d\n", strlen(arr + 0));//随机值(>=6)//arr是首元素的地址, arr+0还是首元素的地址printf("%d\n", strlen(*arr));//非法访问,错误//arr是首元素的地址, *arr就是首元素 - 'a'的ASCLL码 - 97,站在strlen的角度,认为传参进去的'a'-97就是地址,97作为地址,直接进行访问,就是非法访问printf("%d\n", strlen(arr[1]));//非法访问,错误//'b' 的ASCLL码- 98,错误与上面相同printf("%d\n", strlen(&arr));//随机值(不知道/0在哪里)//取出的是整个数组的地址,&arr取出的类型是 char (*)[6](数组指针类型),strlen的参数类型是const char*,编译器会报警告但是不影响,类型发生了变化但是值不变。printf("%d\n", strlen(&arr + 1));//随机值//&arr+1,是f后的地址(跳过了整个数组),不知道什么时候遇到/0,所以也是随机值printf("%d\n", strlen(&arr[0] + 1));//随机值//一个字符的地址+1,是b的地址,后面不知道/0在哪里,也是随机值return 0;
}
int main()
{char arr[] = "abcdef";//a b c d e f /0printf("%d\n", sizeof(arr));//7//abcdef和/0一共七个元素printf("%d\n", sizeof(arr + 0));//4//首元素地址+0还是首元素地址printf("%d\n", sizeof(*arr));//1//首元素地址解引用,还是首元素a,一个字节printf("%d\n", sizeof(arr[1]));//1//第二个元素,一个字节printf("%d\n", sizeof(&arr));//4//数组的地址,为4/8printf("%d\n", sizeof(&arr + 1));//4//跳过整个数组的地址,为/0后的地址,是地址就是4/8printf("%d\n", sizeof(&arr[0] + 1));//4//第一个元素的地址+1,就是第二个元素的地址,是地址就是4/8printf("%d\n", strlen(arr));//6//首元素地址传给strlen,往后数因为有/0,所以是6printf("%d\n", strlen(arr + 0));//6//首元素地址+0,还是首元素地址,往后数,因为有/0,所以是6printf("%d\n", strlen(*arr));//非法访问,错误//对首元素地址解引用,其实就是字符a,传进去的是a的ASCLL码值97,会报错printf("%d\n", strlen(arr[1]));//非法访问,错误//传进去的是第二个元素b,穿的是b的ASCLL码,会报错printf("%d\n", strlen(&arr));//6//传进去的是首元素的地址,往后数,因为有/0,所以是6printf("%d\n", strlen(&arr + 1));//随机值//首元素的地址+1,跳过整个数组,即到/0之后,/0之后什么时候遇到下一个/0不知道,为随机值printf("%d\n", strlen(&arr[0] + 1));//5//第二个元素的地址,从b数到/0,有5个return 0;
}
int main()
{char* p = "abcdef";//a b c d e f \0,但p中存放的是a的地址(p能够找到abcdef/0)printf("%d\n", sizeof(p));//4//计算的是指针变量的大小printf("%d\n", sizeof(p + 1));//4//char*指针+1向后偏移一个,但p+1还是一个地址,是地址就是4/8printf("%d\n", sizeof(*p));//1// *p=='a'printf("%d\n", sizeof(p[0]));//1//p[0]等价于*(p+0),等价于*p,即第一个元素aprintf("%d\n", sizeof(&p));//4//&p仍然是一个地址,是地址就是4/8,假设p的地址是0x0012fc60,指向的就是0x00的地址,类型是char**二级指针printf("%d\n", sizeof(&p + 1));//4//&p是地址,&p+1还是地址,是地址就是4/8,假设p的地址是0x0012fc60,指向的就是0x60,类型是char**二级指针printf("%d\n", sizeof(&p[0] + 1));//4//指向第二个元素的地址,是地址就是4/8printf("%d\n", strlen(p));//6//a的地址往后数到/0printf("%d\n", strlen(p + 1));//5//b的地址往后数到/0printf("%d\n", strlen(*p));//非法访问,错误//p指向a的地址,*p就是a,传的a的ASCLL码,错误printf("%d\n", strlen(p[0]));//非法访问,错误//和*p一样的,是a,传的a的ASCLL码,错误printf("%d\n", strlen(&p));//随机值//&p假设取的是0x0012ff40,这个指针变量占4个字节,即从0x0012ff40往后找/0,后面什么位置有/0不知道printf("%d\n", strlen(&p + 1));//随机值//&p假设取的是0x0012ff40,&p+1指的是0x40后的地址,即从0x40往后找/0,后面什么位置有/0不知道printf("%d\n", strlen(&p[0] + 1))//5//第二个元素b的地址,往后数到/0,有五个return 0;
}
int main()
{//二维数组int a[3][4] = { 0 };printf("%d\n", sizeof(a));//48//数组名a单独放在sizeof内部,表示整个数组,一共3*4=12个元素,每个元素4个字节即12*4=48printf("%d\n", sizeof(a[0][0]));//4//a[0][0]是数组的第一行第一列的元素printf("%d\n", sizeof(a[0]));//16//二维数组其实是一维数组的数组,a[0]可以看成第一行的元素,a[1],a[2]可以看成二三行的元素,即整个第一行的大小,即4*4=16printf("%d\n", sizeof(a[0] + 1));//4//a[0]并非放在sizeof内部,所以a[0]表示数组首元素的地址,也就是第一行第一个元素的地址。a[0]+1表示a[0][1]的地址。是地址就是4/8printf("%d\n", sizeof(*(a[0] + 1)));//4//a[0]+1是a[0][1]的地址,解引用就是第二个元素printf("%d\n", sizeof(a + 1));//4//a作为二维数组的数组名,名没有单独放在sizeof内部,a就是首元素的地址,也就是第一行的地址。a+1就是第二行的地址,a的类型是一个数组指针int(*)[4],是地址就是4/8printf("%d\n", sizeof(*(a + 1)));//16//a+1是第二行的地址,解引用就是第二行 4*4=16printf("%d\n", sizeof(&a[0] + 1));//4//a[0]是第一行的数组名,&a[0]取出的是数组的地址,取出的是第一行这个一维数组的地址,类型就是int(*)[4],&a[0]+1就是第二行的地址,是地址就是4/8printf("%d\n", sizeof(*(&a[0] + 1)));//16//对第二行的地址解引用就是第二行,4*4=16printf("%d\n", sizeof(*a));//16//a没有单独放在sizeof内,a表示数组首元素的地址,也就是第一行的地址,解引用就代表第一行,4*4=16printf("%d\n", sizeof(a[3]));//16//a[3]表示第四行,按理说应该越界了,但是没有,sizeof是计算,不会去真的访问第四行,其相当于和a[0]一样,4*4=16return 0;
}
总结
指针和数组名在C语言中非常重要,它们是C语言中最基本的数据类型和数据结构之一。以下是指针和数组名的重要性:
1.内存管理:指针允许我们直接访问和操作内存中的数据。通过指针,我们可以动态地分配和释放内存,有效地管理内存资源。
2.数组访问:数组名本质上是一个指向数组第一个元素的指针。通过数组名,我们可以方便地访问和操作数组中的元素。
3.函数参数传递:通过指针作为函数参数,可以实现对函数外部变量的修改。这在需要在函数内部修改变量的值时非常有用。
4.字符串处理:C语言中的字符串是以null字符结尾的字符数组。通过指针和数组名,我们可以方便地处理字符串,比如拷贝、连接、比较等操作。
5.动态数据结构:指针可以用来创建和操作动态数据结构,比如链表、树等。通过指针,我们可以动态地创建和销毁节点,连接和遍历节点。
6.性能优化:在一些需要高效处理大量数据的场景中,通过指针和数组名可以减少内存拷贝和数据移动的开销,提高程序的执行效率。
总的来说,指针和数组名在C语言中扮演着非常重要的角色,它们是C语言实现高效、灵活和可靠的关键工具。在学习和使用C语言时,理解和掌握指针和数组名的概念和用法是非常重要的。