征服C语言之指针和数组笔试题解析

目录

 一,一维数组

二,字符数组

三,二维数组 

四,指针笔试题 


一,一维数组

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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
	printf("%d\n", sizeof(*&a));//16
	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
	return 0;
}
  1.  sizeof(a)中的a是数组名,但是这里有sizeof,所以计算的是整个数组的大小,则结果为16
  2. sizeof(a+0)中的a+0表示这个数组第一个元素的地址,则结果为4/8,
    在32位平台下地址的大小为4,在64位平台下地址的大小为8,
    因为在32位机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,64位机器也同理,
  3. sizeof(*a)中的*a为对数组名进行解引用,则找到数组的第一个元素,则结果为4
  4. sizeof(a+1)中的a为首元素的地址,a + 1是第二个元素的地址,则结果为4/8,
  5. sizeof(a[1])中的a[1]是数组的第2个元素,因为数组下标是从0开始的,则结果为4,
  6. sizeof(&a)中的&a表示取数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
  7. sizeof(*&a)中的&a表示取数组的地址,再解引用,则*&a表示这整个数组,则结果为16,
  8. sizeof(&a+1)中的&a表示取整个数组的地址,&a + 1 表示跳过一个数组,指向的是数组后面的空间的地址,结果为4/8
  9. sizeof(&a[0])中的a[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,则结果为4/8
  10. sizeof(&a[0]+1)中的a[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,结果是4/8,

二,字符数组

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	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
	printf("%d\n", sizeof(&arr[0] + 1));//4/8
	return 0;
}
  1. sizeof(arr)中的arr是数组名,但是这里有sizeof,所以计算的是整个数组的大小,则结果为16
  2. sizeof(arr+0)中的arr+0表示这个数组第一个元素的地址,则结果为4/8,
  3. sizeof(*arr)中的*arr为对数组名进行解引用,则找到数组的第一个元素,则结果为1
  4. sizeof(arr[1])中的arr[1]是数组的第2个元素,因为数组下标是从0开始的,则结果为1
  5. sizeof(&arr)中的&arr表示取数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
  6. sizeof(&arr+1)中的&arr表示取整个数组的地址,&a + 1 表示跳过一个数组,指向的是数组后面的空间的地址,结果为4/8
  7. sizeof(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,则结果是4/8,

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	printf("%d\n", strlen(*arr));//err
	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值-6
	printf("%d\n", strlen(&arr[0] + 1));//随机值-1
	return 0;
}
  1. strlen(arr)中的arr是数组名,表示数组首元素的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
  2. strlen(arr+0)中的arr是数组名,表示数组首元素的地址,arr+0还是数组首元素的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
  3. strlen(*arr)中的*arr表示这个数组的第一个元素a,a字符的ASCII码值是97,相当于传了个97给strlen,strlen认为97是个地址,然后访问,会出错,则结果为err,
    strlen在库函数中为size_t strlen ( const char * str );它只能接收一个字符型的地址,访问从首元素地址到/0结束,然后返回字符串的长度,字符串的长度等于字符串开头和结束的空字符之间的字符数(不包括结束的空字符即/0本身)
  4. strlen(arr[1])中的arr[1]表示这个数组的第二个元素b,b字符的ASCII码值是98,相当于传了个98给strlen,strlen认为98是个地址,然后访问,会出错,则结果为err,
  5. strlen(&arr)中的&arr表示数组的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
  6. strlen(&arr+1)中的&arr表示数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,但是还是不知道\0在哪里,但这个访问的个数又比最上面那个的个数少六,则结果为随机值-6,
  7. strlen(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,strlen从数组的第2个元素的地址开始访问,但是还是不知道\0在哪里,但这个访问的个数又比最上面那个的个数少一,则结果为随机值-1,
    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    int main()
    {
    	char arr[] = "abcdef";
    	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;
    }
  1.  sizeof(arr)中计算的是这个数组的大小,这个"abcdef"中自动补了个\0,且隐藏了,实际上arr数组内容为"abcdef\0" ,则结果为7
  2. sizeof(arr+0)中 arr + 0 表示这个数组第一个元素的地址,则结果为4/8
  3. sizeof(*arr)中的*arr为对数组名进行解引用,则找到数组的第一个元素,则结果为1
  4. sizeof(arr[1])中的arr[1]表示数组的第2个元素,因为数组下标是从0开始的,则结果为1
  5. sizeof(&arr)中&arr表示取整个数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
  6. sizeof(&arr+1)中&arr表示取整个数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,结果为4/8
  7. sizeof(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&arr[0]取出首元素的地址,&arr[0]+1表示数组的第二个元素的地址,sizeof(&arr[0]+1)计算的是数组第2个元素的地址大小,结果是4/8,
    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<string.h>
    int main()
    {
    	char arr[] = "abcdef";
    	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));//随机值
    	printf("%d\n", strlen(&arr[0] + 1));//5
    	return 0;
    }
  1.  strlen(arr)中的arr是数组名,表示数组首元素的地址,strlen是从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
  2. strlen(arr+0)中的arr是数组名,表示数组首元素的地址,arr+0还是表示数组首元素的地址strlen从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
  3. strlen(*arr)中的*arr表示这个数组的第一个元素a,a字符的ASCII码值是97,相当于传了个97给strlen,strlen认为97是个地址然后访问,则会出错,结果为err,
  4. strlen(arr[1])中的arr[1]表示这个数组的第二个元素b,b字符的ASCII码值是98,相当于传了个98给strlen,strlen认为98是个地址然后访问,则会出错,结果为err,
  5. strlen(&arr)中的&arr表示数组的地址,strlen是从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
  6. strlen(&arr+1)中的&arr取出的是数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,但是不知道\0在哪里,所以所以结果为随机值,
  7. strlen(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&arr[0]取出首元素的地址,&arr[0]+1表示数组的第二个元素的地址,strlen从数组的第2个元素的地址开始访问,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为5
    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    int main()
    {
    	char* p = "abcdef";
    	printf("%d\n", sizeof(p));//4/8
    	printf("%d\n", sizeof(p + 1));//4/8
    	printf("%d\n", sizeof(*p));//1
    	printf("%d\n", sizeof(p[0]));//1
    	printf("%d\n", sizeof(&p));//4/8
    	printf("%d\n", sizeof(&p + 1));//4/8
    	printf("%d\n", sizeof(&p[0] + 1));//4/8
    	return 0;
    }
  1.  sizeof(p)中的p是一个字符型指针变量,且指向a的地址,则结果是4/8
  2. sizeof(p+1)中的p+1指向b的地址,则结果是4/8
  3. sizeof(*p)中的*p表示对p进行解引用,指向a的地址,然后访问,则结果是1
  4. sizeof(p[0])中的p[0]等价于*(p+0),表示指向a的地址,然后访问,则结果是1
  5. sizeof(&p)中的&p表示取p的地址,结果为4/8
  6. sizeof(&p+1)中的&p表示取p的地址,&p+1则跳过p的地址,则结果为4/8
  7. sizeof(&p[0]+1)中的&p[0]+1表示b的地址,则结果还是4/8

三,二维数组 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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
	printf("%d\n", sizeof(a[0] + 1));//4/8
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	printf("%d\n", sizeof(a + 1));//4/8
	printf("%d\n", sizeof(*(a + 1)));//16
	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
	return 0;
}
  1.  sizeof(a)中的a是数组首元素地址,但是sizeof(数组名)计算的是数组的大小,则结果是48
  2. sizeof(a[0][0])中的a[0][0]表示这个数组的第一行第一列的元素,则结果是4
  3. sizeof(a[0])中的a[0]表示这个数组的第一行,a[0]相当于第一行的数组名,sizeof(数组名)计算的是数组的大小,则结果为16
    二维数组在内存中是连续存放的

    实际上在内存中是这样的,见下表:
  4. sizeof(a[0]+1)中的a[0]表示这个数组的第一行,a[0]相当于第一行的数组名,a[0]作为数组名并没有单独放在sizeof内部,也没取地址,所以a[0]就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址,则结果为4/8
  5. sizeof(*(a[0]+1)中的a[0]+1就是第一行第二个元素的地址,*(a[0]+1)表示第一行第二个元素,则结果为4
  6. sizeof(a+1)中的a是二维数组的数组名,并没有取地址,也没有单独放在sizeof内部,所以a就表示二维数组首元素的地址,所以表示第一行的地址,a + 1就是二维数组第二行的地址,则结果为4/8
  7. sizeof(*(a + 1))中的a + 1是二维数组第二行的地址,*(a + 1)表示二维数组第二行的数组名sizeof(数组名)计算的是二维数组第二行的大小,则结果为4
  8. sizeof(&a[0] + 1)中a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,&a[0]+1 就是第二行的地址,则结果为4/8
  9. sizeof(*(&a[0] + 1))中&a[0]+1就是第二行的地址,*(&a[0]+1) 就是第二行,所以计算就是第二行的大小,则结果为16
  10. sizeof(*a)中的a作为二维数组的数组名,没有&,没有单独放在sizeof内部,a就是首元素的地址,即第一行的地址,所以*a就是第一行,计算的是第一行的大小,结果为16
  11. sizeof(a[3])中的a[3]其实是第四行的数组名(如果有的话),a[3]的类型属性-int [4],其中sizeof()内部的表达式不计算,所以其实不存在,也能通过类型计算大小的,结果为16
    表达式有两个属性,一个是值属性,另一个是类型属性,
    如3+5,值属性-8,类型属性-int,则sizeof(3+5)等于4

总结: 数组名的意义如下

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

四,指针笔试题 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));//2,5
	return 0;
}
  1.  &a表示取出这个数组的地址,&a+1跳过一个数组,指向的是数组后面空间的地址,它的原类型为Int(*)[5],再将其强制类型转换为int*,再将其赋给指针变量ptr

    *(ptr - 1)解释:ptr向后跳过一个int,然后解引用找到并且访问5
    *(a+1)解释:a为数组首元素,a+1为数组的第二个元素,然后解引用找到并且访问2
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	struct Test
	{
		int Num;
		char* pcName;
		short sDate;
		char cha[2];
		short sBa[4];
	}*p;
	//假设p 的值为0x100000。如下表表达式的值为多少
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

考查的是:指针类型决定了指针的运算 

  1.  p+0x1中p为结构体指针变量,这个结构体的大小为20,0x1实际上就是1,p+1会跳过一个结构体的大小,指向的是数组后面空间的地址0x100000+20=0x100014,结果为0x100014
  2. (unsigned long)p + 0x1中将p强制类型转换为unsigned long,它加1就是加1,0x100000+1=0x100001
  3. (unsigned int*)p+0x1中将p强制类型转换为unsigned long*,p变成了无符号整形指针,它加一就是加一个int,0x100000+4=0x100004
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);//4,2000000
	return 0;
}
  1. ptr1是一个整形指针,指向的是数组后面空间的地址
  2. ptr2是一个整形指针,(int)a + 1中a表示首元素的地址,再将其强制类型转换问int,它加一就是加一,相当于向后偏移了一个字节,在内存中一个字节给一个地址,如:0x0012ff44-->int+1-->0x0012ff45
    在小端机器下,
  3. *ptr2表示对ptr2进行解引用,找到并访问4个字节,ptr1[-1]等价于*(ptr1-1),结果为4,2000000
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);//1
	return 0;
}
  1.  (0, 1), (2, 3), (4, 5)这几个为逗号表达式,等价于1,3,5,
    a[0]表示这个二维数组的首元素的地址,p为整形指针变量,p[0]等价于*(p+0),结果为1
#define _CRT_SECURE_NO_WARNINGS
#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]);//FFFFFFFC,-4
	return 0;
}
  1.  p=a中a的类型为int(*)[5],p的类型为int (*)[4],p[4][2]等价于*(*(p+4)+2),&p[4][2] - &a[4][2]计算的是两者之间的元素个数,随着数组下标的增长,地址是由低地址到高地址变化的,所以结果为-4,

    -4以%d的形式打印还是-4
    -4的原码10000000000000000000000000000100
    -4的反码111111111111111111111111111111111011
    -4的补码111111111111111111111111111111111100
    -4在内存中以补码的形式存储,%p的形式打印,会直接将-4的补码当作原码打印出来所以结果为FFFFFFFC
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10,5
	return 0;
}
  1.  int* ptr1 = (int*)(&aa + 1)中&aa是取出这个数组的地址,&aa+1则跳过一个数组,再将其强制类型转换为int*,再将其赋给指针变量ptr,ptr1是一个整形指针,指向的是数组后面空间的地址,*(ptr1 - 1)的结果是10
  2.  int* ptr2 = (int*)(*(aa + 1))中aa是二维数组的数组名,是第一行的地址,aa+1则跳过第一行,指向第二行,再将其强制类型转换为int*,再将其赋给指针变量ptr2,ptr2是一个整形指针,ptr2为第二行首元素的地址,*(ptr2 - 1)的结果是5
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);//at
	return 0;
}
  1.  char* a[]是一个字符型指针数组,a为数组名,是数组首元素的地址,
    char** pa = a中a是一个一级指针的地址,所以用二级指针接收刚好,pa是一个二级指针变量,指向的是数组首元素的地址,结果是at
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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.  char*c[],char**cp[],char***cpp这三者之间的指向关系如下:
  2. **++cpp表示先cpp+1,再解引用,指向c+2的地址,再解引用,指向P的地址,结果为POINT
  3. *-- * ++cpp + 3表示先cpp+1,由于上面的运算cpp变成了cpp+1,所以这里的cpp变成了cpp+2,再解引用,指向c+1的地址,再减1,指向c的地址,再解引用,指向E的地址,再加3,指向第四个E的地址,结果为ER
  4. *cpp[-2] + 3等价于*(*(cpp-2))+3表示为cpp-2,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp,再解引用,指向c+3地址,再解引用,指向F的地址,再加3,指向S的地址,结果为ST
  5. cpp[-1][-1] + 1等价于*(*(cpp-1)-1)+1,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp+1,再解引用,指向c+2的地址,再减1,指向c+1的地址,再解引用,指向N的地址,再加一指向E的地址,结果为EW

//阅读两段有趣的代码
//代码1
(*(void(*)())0)();//出自《C陷阱和缺陷》
//代码2
void(*signal(int , void(*) (int)))(int);//出自《C陷阱和缺陷》

  1. 解析代码1:突破口在0那里,调用0地址处的函数,该函数无参,返回类型是void
    (1)void(*)() - 函数指针类型
    (2) (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
    (3)*(void(*)())0 - 对0地址进行解引用操作
    (4)(*(void(*)())0)() - 调用0地址处的函数
  2. 解析代码2:突破口在signal那里,是一个函数是声明
    (1)signal先和()结合,说明signal是函数名,
    (2)signal函数的第一个参数的类型是int,第二个参数的类型是,该函数指针指向一个参数为int,返回类型为void的函数
    (3)signal函数的返回类型也是一个函数指针,该函数指针指向一个参数为int,返回类型为void的函数

简化代码2:void(*)(int) signal (int,void(*)(int)) 这样写更好理解,但是在语法上第一个的*必须跟
signal结合在一起,这里我们可以使用typedef,它可以对类型进行重定义,
typedef  void(*)(int) pfun_t但是在语法上第一个的*必须跟pfun_t结合在一起,所以正确写法为
typedef  void(*pfun_t)(int)就像给void(*)(int)取了一个小名,叫pfun_t
最后简化后的代码为pfun_t signal (int , pfun_t)

猜你喜欢

转载自blog.csdn.net/LYC_462857656/article/details/121552223