c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关

文章目录

    • 1.字符指针
    • 2.指针数组
    • 3.数组指针
    • 4.数组参数,指针参数
      • 4.1 一维数组传参
      • 4.2 二维数组传参
      • 4.3一级指针传参
      • 4.4二级指针传参
    • 5.函数指针
    • 6.函数指针数组
    • 7.指向函数指针数组的指针
    • 8.解析一些指针题
      • 第1题
      • 第2题
      • 第3题
      • 第4题
      • 第五题
      • 第六题
      • 第七题
      • 第八题

1.字符指针

c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第1张图片

在使用字符指针时,第一种为通常的情况。这次主要关注第二种情况:把字符串赋值给字符指针变量,pa中存储的不是这串字符,而是字符串的首字符的地址。abcdef作为一个常量字符串程序会把其放在一个单独的内存区域,pa存储的是字符a的内存地址,当程序打印时就能通过a的地址打印出整个字符串。

int main()
{
    char str1[] = "hello world.";
    char str2[] = "hello world.";
    const char *str3 = "hello world.";
    const char *str4 = "hello world.";
    if(str1 ==str2)
 		printf("str1 and str2 are same\n");
    else
		 printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
		 printf("str3 and str4 are same\n");
    else
		 printf("str3 and str4 are not same\n");
       
    return 0;
}

这个代码的结果是str1不等str2,str3等于str4。str1与str2作为数组名,表示的是数组首元素的地址,两个数组在内存中开辟的空间不同,地址当然不同。而hello world作为常量字符串,程序将其单独放在一个内存区中,只存储一次,由于字符串相同没有必要多次存储。两个指针储存的都是其首元素地址,所以两指针中的地址相同。

2.指针数组

指针数组是一个数组,存储指针变量的数组。格式如下

int* arr1[5];//一级整形指针数组
int** arr2[5];//二级整形指针数组
char* arr3[5];//一级字符指针数组

c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第2张图片
数组指针的使用,arr1相当于arr1数组的首元素地址,arr[i] 等价于 (arr + i),arr [i][j] 等价于 ( *(arr + i) + j) 。

3.数组指针

数组指针的类型为int (*) [ ],int是指针指向的数组中元素的类型,括号里是指针指向数组的元素个数。

首先要明白数组名与&数组名的区别。数组名是数组首元素的地址,&数组名得到的地址与数组首元素的地址是相同的,它们的不同在于对数组名加1,地址变化的步长是数组中元素类型的字节大小,而对&数组名加1,地址变化的步长是整个数组的字节大小,也就是数组元素类型字节大小乘以数组元素个数。

数组指针是一个指针,指向数组的指针。它的定义格式为
int (*p)[5] 。(*p)先让变量与 * 结合,表示p是一个指针,再与括号结合,说明p是指向一个长度为5的数组的,返回类型是int。如果写成 int *p[5],则表示一个以p为数组名的指针数组,长度为5。

可以使用数组指针访问二维数组,

void print1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

void print2(int(*p)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *( * (p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
	print1(arr, 3, 5);
	print2(arr, 3, 5);
	return 0;
}

print1和print2的作用一样,都是打印arr数组,但print在打印时使用数组指针访问数组元素。从形参的定义中可以得知,p是一个数组指针,指向的数组长度为5,数组元素是int。p指向二维数组的首元素,是一个一维数组,对p指针解引用得到的是一维数组首元素的地址。所以用*(p + i)来访问第i行一维数组,对其加上j就指向第i行第j个元素,所以我们写成*( *(p + i) + j ),这种写法等价于p[i][j]。

int arr[5];          //表示一个int类型长度为5的数组
int *parr1[10];      //表示一个int*类型长度为10的数组
int (*parr2)[10];    //表示一个指针,指向的数组长度为10,数组类型为int
int (*parr3[10])[5]; //表示一个长度为10的数组,数组类型是int(*)[5],也就是每个元素是一个指针,指向一个长度为5的数组,数组类型是int

4.数组参数,指针参数

4.1 一维数组传参

void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
	 int arr[10] = {0};
	 int *arr2[20] = {0};
	 test(arr);
	 test2(arr2);
}

数组名是数组首元素地址,一维数组首元素是一个int或char这样的类型,可以用一级指针,一维数组接收,用一维数组接收是可以不用写数组长度,或者写错都没事。当一维数组中的元素是指针类型时,数组首元素的地址就是一个指针的地址,可以用二级指针接收,也可以直接定义指针数组。

4.2 二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}

void test(int *arr)//ok?二维数组首元素地址是一个一维数组的地址,当然不能用指向int类型的指针接收
{}
void test(int* arr[5])//ok?同上,不能用指针数组接收,要用数组指针
{}
void test(int (*arr)[5])//ok?用一个指向数组的指针接收是可以的,但是数组的长度不能写错
{}
void test(int **arr)//ok?同上,不能用二级指针接收,要用数组指针
{}
int main()
{
	int arr[3][5] = {0};
	test(arr);
}

二维数组传参,可以直接写一个二维数组,不过数组的行可以省略,列一定不能省略且不能写错,与定义二维数组时的规则一样。二维数组在传参时将数组首元素地址穿了过去,二维数组的首元素是一个一维数组的地址,所以要用一个数组指针接收。

4.3一级指针传参

void print(int *p, int sz)
{
	int i = 0;
	for(i=0; i<sz; i++)
	{
	printf("%d\n", *(p+i));
	}
}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9};
	int *p = arr;
	int sz = sizeof(arr)/sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

一级指针作为实参当然是定义一级指针接收,很少会用一维数组接收,当一维数组作为实参时,倒是经常使用一级指针接收。当一级指针作为形参时,实参可以是对应类型变量的地址,或者对应的一级指针,也可以是一维数组的数组名。

4.4二级指针传参

void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
	int n = 10;
	int*p = &n;
	int **pp = &p;
	test(pp);
	test(&p);
	return 0;
}

二级指针作为实参时,同上我们也是定义二级指针来接收。当二级指针作为函数形参时,能作为实参的二级指针,&一级指针,或者是一个一维指针数组的数组名。

5.函数指针

void test()
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
 return 0;
}

c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第3张图片
通过上面的代码,可以看出函数名在程序运行时也是有一个地址的,并且函数名就表示函数名的地址,可以不用写&符合,这点与数组名相似。所以可以创建一个变量,指向函数的地址。函数指针的定义格式,先看要指向的函数的相关消息,返回类型是void,没有形参。对应的函数指针就是
void(*ptest)(),前面的void是指向函数的返回类型,ptest要先与 * 结合,说明它是一个指针,再与()函数调用操作符结合,括号里写对应的参数类型。

如果一个函数是int test (int x,int y),对应的函数指针是int (*ptest)(int,int)。

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

解析这两段代码,首先void(*)()是一个函数指针,先把0强制类型转换成这样的函数指针,现在的0是一个函数指针,指向一个函数, 再把0解引用就找到了0地址处的函数,最后是函数调用操作符,说明这个函数的参数为无参。整个代码就是去调用了0地址处的函数。

代码2是signal函数的声明。sign函数有两个参数,一个是int,一个是void (*)(int)的函数指针,signal函数的返回值也是void ( * )(int)。
c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第4张图片
返回类型是函数指针的函数看着有点变扭。

6.函数指针数组

指针数组的格式:int* arr[5],去掉数组名就是int* [n],再去掉括号是int*表示数组元素的类型,也就是说去掉数组名与括号,剩下的就是数组中元素的类型。那函数指针数组的格式就是:int (*p[10])(int,int),去掉p[10]就是int ( * )(int, int)表示数组的元素是函数指针,因为p先与[ ] 结合,p是数组名,并且该数组有10个元素。

用函数指针数组写一个简单的计算器。

#include 

void menu()
{
	printf("************************\n");
	printf("******1.Add  2.Sub******\n");
	printf("******3.Mul  4.Div******\n");
	printf("*****   0.exit    ******\n");
	printf("************************\n");
}
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
int main()
{
	int input = 0;
	int(*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do {
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		if (input == 0)
		{
			break;
		}
		else
		{
			int x = 0;
			int y = 0;
			scanf("%d %d", &x, &y);
			int ret = p[input](x, y);
			printf("ret == %d\n", ret);
		}
	} while (input);
	return 0;
}

将计算机的加减乘除函数放进一个函数指针数组中,只需要输入函数在数组中对应的下标就能通过数组访问函数。

7.指向函数指针数组的指针

一个指针,指向数组,数组元素是函数指针。

void test(char* str)
{}
int main()
{
	void (*p) (char*) = test;//函数指针p
	void (*parr[5]) (char*);//函数指针数组
	parr[0] = p;
	void(*(*pf)[5]) (char*)=&parr;
	return 0;
}

8.解析一些指针题

两个原则:sizeof单独在数组名中计算的是整个数组的大小,&数组名取出的是整个数组的地址。

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//数组名单独在sizeof中,计算整个数组大小,48
	printf("%d\n", sizeof(a[0][0]));//第一行第一个元素,4
	printf("%d\n", sizeof(a[0]));//可以看成第一行的数组名,16
	printf("%d\n", sizeof(a[0] + 1));//第一行数组名+1,是个指针,4/8
	printf("%d\n", sizeof(*(a[0] + 1)));//第二行第一个元素,4
	printf("%d\n", sizeof(a + 1));//数组首元素地址+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]));//a[n]是一个数组指针,可以看成一维数组数组名,16
	return 0;
}

第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;
}

a是一个有5个元素的数组,&a可以看成数组指针,+1的步长是整个数组,指向了5后面的地址,再强制类型转化成int*类型并赋值给ptr,使其步长变成4字节。

打印时,a作为数组名代表数组首元素的地址,+1访问第二个元素,解引用得到2,ptr指向5后面的地址,-1使其向后访问一个整形,指向5,解引用得到5。
c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第5张图片

第2题

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

该结构体大小为20,p是一个结构体指针,对其+1则跨过一个该结构体的大小,地址+20,地址是由十六进制表示的,所以p是100014。将p强制类型转换成unsigned long,+1得到的是100001。将p强制类型转换成int*的指针,对其+1步长是4,结果是100004。
c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第6张图片

第3题

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);
    return 0;
}

&a取出整个数组的地址,+1使其指向4后面的地址,再强制类型转换成int的指针赋值给ptr1;假设电脑是小端储存a数组中的内容是01000000 02000000 03000000 04000000,刚开始的a指向最前面的0,将a强转成int然后+1,再强转成int赋值给ptr2,两个十六进制数表示一个字节,ptr2指向1后面的那个0。对ptr1-1解引用得到4,对ptr2解引用,注意是小端存储,得到02000000,结果就是2000000
c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第7张图片

第4题

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int* p;
    p = a[0];
    printf("%d", p[0]);
    return 0;
}

a数组中的内容是1,3,5,0,0,0。注意大括号里是圆括号,是逗号表达式,不是大括号,逗号表达式的结果是最后一个数。a作为数组名指向数组第一行,对a+0后解引用得到第一行的数组名,指向第一行第一个元素,结果是1。

c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第8张图片

第五题

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]);
    return 0;
}

a是二维数组数组名,指向第一行数组的指针,赋值给p,但p指向的数组只有4个元素。p[4][2] 就是对* ( *(p+4)+2 ),+4跨过的步长是4 * 4 = 16个整形,解引用后+2,跨过2个整形。a + 4跨过4 * 5 = 20 个整形,解引用后+2,跨过2个整形,两个指针相减得到的是两指针之间相差的元素个数,结果是-4。以地址的形式打印,数据在内存中都是以补码的形式存储的,-4的原码是10000000 00000000 00000000 00000100,反码是11111111 11111111 11111111 11111011,补码是11111111 11111111 11111111 11111100,转换成十六进制是ff ff ff fc。
c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第9张图片

第六题

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));
    return 0;
}

&aa+1指向10后面的地址,强转成int赋值给ptr1,aa+1指向aa数组中的第二行,解引用得到第二行的数组名,指向6,强转成int赋值给ptr2。ptr1-1解引用得到10,ptr2-1解引用得到5。
c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第10张图片

第七题

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

a是一个指针数组,元素是char*,分别指向w,a,a的地址。pa二级指针,指向a这个数组名,也就是首元素的地址。pa++使pa跨过char*指向a数组中第二个元素的地址,对其解引用得到第二个元素的地址,以%s的形式打印结果是at。
c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第11张图片

第八题

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

c是指针数组,元素是char*,分别指向E,N,P,F这四个字母的地址。cp是二级指针数组,分别指向F,P,N,E的地址的地址,cpp是三级指针,指向cp数组首元素的地址。cpp+1指向cp第二个元素的地址,解引用得到cp的第二个元素,也就是c + 2,指向P的地址,再解引用使其指向P,打印出POINT。再对cpp+1,指向cp第三个元素,解引用得到c + 1,-1得到c,指向E的地址,解引用后指向E,+3指向E,打印结果是ER。cpp-2指向cpp第一个元素,解引用后得到c + 3,指向F的地址,解引用指向了F,+3指向了S,打印结果是ST。cpp-1指向cp第二个元素解引用得到c + 2,-1得到c + 1,解引用指向了N,+1指向E,打印结果为EW。

c语言 指针数组 数组指针 函数指针 数组传参 指针练习题 指针进阶相关_第12张图片

你可能感兴趣的:(【C语言】学习总结,c语言,开发语言,后端)