【C语言】指针由浅入深全方位详解

发布于:2024-07-13 ⋅ 阅读:(167) ⋅ 点赞:(0)

指针定义

1. 指针是内存中一个最小单元的编号,也就是地址。

2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

3. 我们可以通过 &(取地址操作符) 取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。

4. 指针的大小在32位平台是4个字节,在64位平台是8个字节

int a = 100;
int * pa = &a; 
//*表示pa是指针变量。
//int表示 1.pa指向的类型是int 2.pa解引用的时候访问的对象大小是sizeof(int)。

 

指针类型

1. 指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)

int* 的指针解引用访问4个字节。

char* 的指针解引用访问1个字节

2. 指针类型决定指针加1减1操作时的步长

整型指针+1跳过4个字节,字符指针+1跳过1个字节


 

野指针

1. 概念

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

2. 野指针成因

//1.指针未初始化
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;

//2.指针越界访问
 int arr[10] = {0};
 int *p = arr;
 int i = 0;
 for(i=0; i<=11; i++)
 {
     //当指针指向的范围超出数组arr的范围时,p就是野指针
     *(p++) = i;
 }

//3.指针指向的空间释放

3. 如何规避野指针? 

1.明确知道指针应该初始化为谁的地址就直接初始化,不知道的就初始化为NULL。

2.小心指针越界。

3.指针指向的空间释放后,及时置NULL。

4.避免返回局部变量的地址。

5.指针使用前检查有效性。


指针运算 

1. 指针加减整数

int arr[10] = {0};
int* p = &arr[0];
for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
{
    *p = i;
    p = p + 1; //指针加1,加一个sizeof(int)
}

p = arr;
for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++) { printf("&d ", *(p+i)); }

//*(p+i) == arr[i]
//*(arr+i) == arr[i] == *(i+arr) == i[arr]

2. 指针减指针 

int arr[10] = {0};
printf("%d\n", &arr[9] - &arr[0]); //等于9
//指针减指针的绝对值是指针和指针之间的元素个数
//指针和指针相减的前提是两个指针指向了同一块空间

3. 指针的关系运算 

1.地址是有大小的,指针的关系运算就是比较指针的大小

2.标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。


函数指针数组

1. 类比

int* arr[5] //整型指针数组

char* arr[5] //字符指针数组

函数指针数组:数组的每个元素都是函数指针类型

2. 用函数指针实现计算器 

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

void menu() { printf("0.exit, 1.add, 2.sub, 3.mul, 4.div\n"); }

int main()
{
	int (*pf_arr[])(int, int) = { NULL, add, sub, mul, div }; //函数指针数组,转移表
	int input, a, b;
	int sz = sizeof pf_arr / sizeof pf_arr[0];
	while (1)
	{
		menu();
		scanf("%d", &input);
		if (input == 0) break;
		else if (input > 0 && input <= sz)
		{
			scanf("%d %d", &a, &b);
			printf("%d\n", pf_arr[input](a, b));
		}
		else printf("input err\n");
	}

	return 0;
}

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

指向函数指针数组的指针是一个指针,这个指针指向一个数组,这个数组的元素都是函数指针。 如何定义?

int (*pf)(int, int); //函数指针
int (*pf_arr[4])(int, int); //函数指针数组
int (*(*p_pf_arr)[4])(int, int) = &pf_arr; //函数指针数组的地址
//p_pf_arr就是指向函数指针数组的指针

回调函数

1. 什么是回调函数

回调函数就是一个通过函数指针调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2. 利用回调函数实现计算器 

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

void menu() { printf("0.exit, 1.add, 2.sub, 3.mul, 4.div\n"); }

int Cal(int (*pf)(int, int))
{
	int a, b;
	scanf("%d %d", &a, &b);
	printf("%d\n", pf(a, b));
}

int main()
{
	int input = 1;
	while (input)
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		case 1:
			Cal(add);
			break;
		case 2:
			Cal(sub);
			break;
		case 3:
			Cal(mul);
			break;
		case 4:
			Cal(div);
			break;
		}
	}

	return 0;
}

3. qsort函数

qsort函数特点:

1. 采用快速排序的方法

2. 适合任意类型数据的排序

.

qsort函数参数

void qsort(void* base, //指向需要排序的数组第一个元素

                 size_t num, //排序个数

                 size_t size, //一个元素的大小

                 int (*cmp)(const void*, const void*) //函数指针类型,指向的函数能比较base中元素

                );

.

qsort使用

//void*的指针可以接收任意类型的地址
//但是不能直接解引用和指针运算
//需要强制转换成对应类型
int cmp_int(const void* v1, const void* v2) { return *(int*)v1 - *(int*)v2; }

int main()
{
	int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
	int sz = sizeof arr / sizeof arr[0];
	qsort(arr, sz, sizeof arr[0], cmp_int);

	return 0;
}

4. 利用冒泡排序的思想模拟实现qsort函数

冒泡排序:两两相邻的元素比较,不满足条件就交换

	//冒泡排序
int main()
{
	int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
	int sz = sizeof arr / sizeof arr[0];
	int tmp;
	//需要比较sz-1趟
	for (int i = 0; i < sz - 1; i++)
	{
		//每一趟完成后少一个数 
		for (int j = 0; j<sz-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}

	return 0;
}

模拟实现

问题1:第一个参数如何接收不同类型。

解决1:void*的指针解决并给出个数和每个的大小

问题2:不同类型元素比较方式可能不同

解决2:将两个元素的比较方法作为函数参数传递

问题3:不同的数据类型进行交换时不一样

解决3:利用char*一次只交换一个字节,循环目标字节大小次即可

//比较
int cmp_int(const void* v1, const void* v2) { return *(int*)v1 - *(int*)v2; }
//交换
void swap(char* p1, char* p2, int sz)
{
	char tmp;
	for (int i = 0; i < sz; i++)
	{
		tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
//排序
void bubble_sort(void* base, int num, int sz, int (*cmp)(const void*, const void*))
{
	//冒泡思想
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			//不同类型比较方式不一样所以不能写死
			if (cmp((char*)base + j * sz, (char*)base + (j + 1) * sz) > 0)
			{
				//不同类型交换方式不一样所以不能写死
				swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
			}
		}
	}
}

int main()
{
	int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
	int num = sizeof arr / sizeof arr[0];
	bubble_sort(arr, num, sizeof arr[0], cmp_int);

	return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到