C语言——指针

目录

指针是什么

野指针

字符指针

指针数组

数组指针

        &数组名和数组名

        数组指针的使用

数组参数、指针参数

        一维数组传参

        二维数组传参

        一级指针传参 

        二级指针传参

函数指针

函数指针数组 

 回调函数

        qsort函数的使用 

        模拟实现qsort函数 

结语


指针是什么

指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址。
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
也就是是说,指针是变量,用来存放地址的,也就是内存

简单来说的指针:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针运算。
C语言——指针_第1张图片

 (*)这个像乘号的符号在这里是解引用的意思,NULL是空指针的意思。


野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
成因1:指针未初始化
C语言——指针_第2张图片

 

成因2:指针越界访问

C语言——指针_第3张图片

 成因3:指向的空间释放


字符指针

C语言——指针_第4张图片

char* 就是字符指针,用来存放字符的。
这里通过修改 *pc 和 *j 来改变了 ch 和 i 的内容。
C语言——指针_第5张图片

这里是不是感觉把整个字符串放进了字符指针中?其实只是把字符串的首字母放了进去,前面用const会更严谨一些。 

再来看下面这段代码,可以看出指针变量会把相同的内容放在同一块地址中,而下面两行会创建两个数组,两个数组的首元素地址是不一样的,所以把地址打印出来就会得到下面的结果。

C语言——指针_第6张图片


指针数组

首先先理解一下这个名字,方便接下来的介绍。

比如:好老师。好是形容词,老师是名词,最终是老师。

那指针数组:存放的是指针,它是一个数组,最终是数组。

下面有两个例子:

C语言——指针_第7张图片

 C语言——指针_第8张图片

 用指针数组就可以模拟出一个二维数组

 C语言——指针_第9张图片

如同创建了一个三行五列的二维数组 

 C语言——指针_第10张图片

 这里也有不同的写法 

 


数组指针

按照规则:
这是指针,指向数组的
C语言——指针_第11张图片

之后要是判断都要通过这个解释,(*)的优先级要低于( [ ] ),所以要加上()

        &数组名和数组名

C语言——指针_第12张图片

虽然前两个的结果一样,通过加1我们可以知道,arr + 1跳过了一个元素,但是 &arr + 1 跳过了一整个数组。

        数组指针的使用

C语言——指针_第13张图片

数组参数、指针参数

在写代码的时候可能要把 [数组] 或者 [指针] 传给函数

        一维数组传参

C语言——指针_第14张图片

形参写成数组的形式:

C语言——指针_第15张图片

此处的形参的数值可写可不写。

形参写成指针的形式:

无论是数组传参还是指针传参,传过去的都是数组首元素的地址,然后从首元素依次向后访问

         二维数组传参

C语言——指针_第16张图片

形参写成数组的形式:

C语言——指针_第17张图片

这里二维数组的行可写可不写,只要不乱写,但是列必须写, 总之传过来的都是数组首元素的地址,对于二维数组来说,数组首元素的地址是二维数组第一行的地址。

所以也可以这样写:

C语言——指针_第18张图片

         一级指针传参 

看下面这个例子: 

C语言——指针_第19张图片

 这里函数的参数部分是指针的形式,所以我们只要传一个地址过来就可以实现这个函数;

C语言——指针_第20张图片

        二级指针传参

二级指针就是存放一级指针的指针

C语言——指针_第21张图片

这里函数的参数就是二级指针,所以要传入一个二级指针

C语言——指针_第22张图片

可以传入一个指针数组 ,因为数组中每个元素都是指针。

函数指针

当然就是指向函数的指针

C语言——指针_第23张图片

既然是指针,那就会有地址,函数本身就是有地址的。

对于函数指针该怎么使用,()这个符号的意思是函数调用符号,所以既然使用函数指针,就要用(*p),要把Add这个函数存进(*p)中,函数参数有两个,都是int类型,最后函数的返回值是int。

C语言——指针_第24张图片

函数指针就可以这样用,这里说一下,这个(*p)的括号是不可以省略的,但是这个 * 是可以省略的,为了方便理解,最好还是把这个 * 加上。

 C语言——指针_第25张图片

函数指针数组 

 C语言——指针_第26张图片

下面模拟了一个简单的计算器

C语言——指针_第27张图片

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;
}
void menu()
{
	printf("**************************\n");
	printf("***** 1.Add   2.Sub ******\n");
	printf("***** 3.Mul   4.Div ******\n");
	printf("******** 0.exit **********\n");
	printf("**************************\n");
}
int main()
{
	int ret = 0;
	int input = 0;
	int x = 0;
	int y = 0;
	int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div };
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出程序\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数\n");
			scanf("%d%d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误,请重新选择\n");
		}
	} while (input);
	return 0;
}

如果这里想写一个指向函数指针数组的指针该怎样去写呢? 

C语言——指针_第28张图片

C语言——指针_第29张图片

 

C语言——指针_第30张图片 C语言——指针_第31张图片

所以这样写也是可以的 

 回调函数

       回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
        简单来说就是在函数中调用另一个函数。
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;
}
void calc(int (*pf)(int, int))
{
	int ret = 0;
	int x = 0;
	int y = 0;

	printf("请输入两个操作数:\n");
	scanf("%d %d", &x, &y);
	ret = (*pf)(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出程序\n");
			break;
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
				
	} while (input);
	return 0;
}

 继续拿上面那个例子,在calc函数中调用各个函数,也就是回调函数的简单用法。

        qsort函数的使用 

这个函数是需要引头文件的:#include

#include 
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);//这里要强制类型转换一下,因为void不知道是几个字节,排序什么样的数据就要强制类型转换成什么
}
void test()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
int main()
{	
	test();
	return 0;
}

C语言——指针_第32张图片这个qsort函数默认是排升序的,如果想要排降序,只需要把e1 和 e2的位置换一下,也可以在cmp_int函数返回值前加一个符号,就如同:C语言——指针_第33张图片

        模拟实现qsort函数 

C语言——指针_第34张图片

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
void bubble_sort(void* base, int num, int width, int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{   //cmp是一个函数指针,指向cmp_int
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)//排为升序,前面的大于后面的就交换,这里的解释下面有图
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
void test()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//这就是模拟的qsort函数
	print_arr(arr, sz);//打印函数

}
int main()
{
	test();
	return 0;
}

C语言——指针_第35张图片

C语言——指针_第36张图片

       这里的模拟qsort函数使用冒泡排序的方法实现的,这只是一种实现方法,并不是唯一的方法。

结语

        对于C语言指针方面的内容就介绍完了,对于指针来说,让人又爱又恨,爱是它可以说是C语言的灵魂,恨是它真的比较难懂。不管怎样还是要把它学明白,这对我们程序员可是非常有帮助的。

你可能感兴趣的:(c语言,开发语言,后端)