指针的进阶

在开始总结指针的进阶之前,先说几个小知识点。

  1. 无论是几维数组,在传参时都会发生退化问题,退化成指针。
  2. 所有的数组都是一维的,可以把多维数组想象成一维数组。
  3. 任何一个函数,只要函数有参数,就要形参实例化,形成临时变量。
  4. 在函数内定义的变量为局部变量,局部变量都具有 临时性。
  5. 每调用函数都会形成栈帧,返回函数释放栈帧,所以递归如果写的不好会导致栈溢出。

下面我们开始这次总结的内容。

字符指针

在指针的类型中有一种指针类型为字符指针char*

一般是这样使用的:

int main(void)
{
    char ch = 'w';
    char* pc = &ch;
    *pc = 'w';
    return 0;
}

这两个变量在main的栈帧上开辟空间,ch的地址高,pc的地址低。

还有另一种使用方式:

int main(void)
{
    char* pstr = "hello Gerald!";
    printf("%s\n", *pstr);
    return 0;
}

这里并不是将字符串放到了指针里,而是在字符串常量区创建了这个字符串,将这个字符串的首地址给pstr指针,而且,这个字符串是在只读区,所以不能修改此字符串。

面试题练习:

#include <stdio.h>

int main(void)
{
	char str1[] = "hello Gerald!";
    char str2[] = "hello Gerald!";
    char* str3 = "hello Gerald!";
    char* str4 = "hello Gerald!";
    
    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是指针类型,这个字符串是在字符常量去创建好,并且将它的首地址给str3和str4,所以他们两个的首地址是相同的。

指针数组

指针数组是一个数组,数组里面的元素是指针。

例如:

int* arr1[10];
char* arr2[4];
char** arr3[5];

数组指针

数组指针是一个指针,这个指针指向数组的首元素地址。

我们先看一下数组指针指针数组的区别:

int* p[10];//指针数组
int (*p)[10];//数组指针

解释一下:数组指针中()的优先级最高,所以p先和*结合,说明p是一个指针,在于[]结合,说明这个指针指向的是一个大小为10的整型数组。因为[]的优先级大于*所以要加圆括号。

&数组名 VS 数组名

我们先看一段代码:

#include <stdio.h>

int main(void)
{
    int arr[10] = {0};
    printf("%p\n", &arr);
    printf("%p\n", arr);
    
    return 0;
}

运行结果:

在这里插入图片描述

运行结果一样,那么他们两个的含义一样吗?

在看一段代码:

#include <stdio.h>

int main(void)
{
    int arr[10] = {0};
    printf("arr + 1 -> %p\n", arr);
    printf("&arr + 1 -> %p\n", &arr);
    
    printf("arr+1 -> %p\n", arr+1);
    printf("&arr+1 -> %p\n", &arr+1);
    
    return 0;
}

运行结果:

在这里插入图片描述

对指针+1是加上其所指向类型的大小。

对2级及其以上指针+1都是+4个字节,因为他们指向的都是指针。

所以根据上面的结果,我们可以确定数组名和**&数组名**是不一样的。

数组名代表的是首元素的地址,而&数组名代表的是整个数组的地址。

一般情况下数组名为首元素的地址,但是有两种情况代表整个数组:

  1. sizeof(数组名)
  2. &数组名

数组指针的使用

数组本身元素的类型加元素的个数共同决定数组类型。

数组传参时会降维成指向其内部元素类型的指针,前提是我们认为所有的数组都是一维的。

数组传参时只能省略一维数组的下标(第一个方括号)的值。

看代码:

#include <stdio.h>

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

void print2(int(*arr)[5], int row, int col)
{
	int i_ = 0;
	for (i_ = 0; i_ < row; i_++)
	{
		int j_ = 0;
		for (j_ = 0; j_ <col; j_++)
		{
			printf("%3d ", arr[i_][j_]);
		}
		printf("\n");
	}
	printf("\n");
}

int main(void)
{
    int arr[3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    print1(arr, 3, 5);
    /*数组名arr,表示首元素的地址
      而二维数组的首元素是第一行的地址
      所以这里传递arr,可以用数组指针来接收*/
    print2(arr, 3, 5);
    
    return 0;
}

运行结果:

在这里插入图片描述

所以这两种接收参数的方式都是对的,数组指针就是在接收参数时,接收一个数组。

int arr[5];   //数组
int* parr1[10];//指针数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//指针数组的指针
//最后一个优点绕,意思就是一个数组有是个元素,每个元素都是一个指针,每个指针指向一个大小为5的数组

当一个函数的参数部分为一级指针的时候,函数可以接收三种参数:

  1. 数组
  2. 一级指针
  3. &整型

当一个函数的参数部分为二级指针的时候,可以接收的参数类型:

  1. 二级指针
  2. 数组指针
  3. &一级指针

函数指针

先看代码:

#include <stdio.h>

void test()
{
    printf("LOLLLLLLLLLLLL!");
}

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

运行结果:

在这里插入图片描述

根据结果,我们可以看到函数名和**&函数名**在输出地址时都是代表函数的地址。

那么如果我们想把这个函数的地址保存起来,怎么保存呢?

void test(void)
{
    ;
}

void (*pfun1)(void);

pfun1可以存放函数的地址,pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

函数指针数组

函数指针数组,顾名思义,它是一个数组,这个数组里的元素是函数指针类型。

那应该如何定义呢

int (*pfun[10])();

pfun先和[]结合表示为一个数组,然后在和*结合,意为指针数组,最后与()结合,表示为一个数组,数组的元素是指向函数的指针。

那么该怎么用呢?

看代码:

#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
void menu()
{
	printf("*****************************\n");
	printf("***  1. add      2. sub  ****\n");
	printf("***  3. mul      4. div  ****\n");
	printf("***              0. exit ****\n");
	printf("*****************************\n");
	printf("Please Enter:");

}

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)
{
	if (y == 0)
		exit(-1);
	return x / y;
}


int main(void)
{
	int x;
	int y;
	int input = -1;
	int ret = 0;
	int (*pfun[5])(int x, int y) = { 0, add, sub, mul, div_ };

	while (input)
	{
		menu();
		scanf("%d", &input);

		switch (input)
		{
		case 0:
			printf("ByeBye!\n");
			break;
		case 1:
			printf("Please Enter Numbers:");
			scanf("%d%d", &x, &y);
			ret = (*pfun[1])(x, y);
			break;
		case 2:
			printf("Please Enter Numbers:");
			scanf("%d%d", &x, &y);
			ret = (*pfun[2])(x, y);
			break;
		case 3:
			printf("Please Enter Numbers:");
			scanf("%d%d", &x, &y);
			ret = (*pfun[3])(x, y);
			break;
		case 4:
			printf("Please Enter Numbers:");
			scanf("%d%d", &x, &y);
			ret = (*pfun[4])(x, y);
			break;
		default:
			printf("ERROR!\n");
			break;

		}
		if (input == 0)
			break;
		printf("Result -> %d\n", ret);
	}
    
    system("pause");
    return 0;
}

这样就可以将函数的地址放进一个数组里,需要调用哪个只需要访问下标就好。

指向函数指针数组的指针

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

那么该如何定义呢?

void test(const char* str)
{
    printf("%s\n", str);
}

int main(void)
{
    //函数指针pfun
    void (*pfun)(const char*) = test;
    //函数指针的数组pfunArr
    void (*pfunArr[5])(const char* str);
    pfunArr[0] = test;
    //指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[5])(const char*) = &pfunArr;
    return 0;
}

回调函数

可以看上一篇博客《qsort函数排序字符串数组》。

猜你喜欢

转载自blog.csdn.net/weixin_42678507/article/details/83993944