C语言 - 指针进阶

1. 数组和指针

1.1 区分数组和指针

数组是一组相同类型元素的集合,而指针实际上是一个存储地址的变量

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	// 数组arr
	int arr[] = { 1,2,3,4,5 };
	int a = 0;
	// 指针变量 pa
	int* pa = &a;

}

如例,数组arr是一组整数的集合,指针pa是一个存储整形变量a地址的变量

 数组和指针是两个不同的概念,它们之间有什么联系?

1.2 数组和指针的关联 

数组和指针之间唯一的联系是,数组名 = 数组首元素的地址,这个地址可以用指针进行存储

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	// 数组arr
	int arr[] = { 1,2,3,4,5 };
	int* pa = arr;
	printf("%p %p %p\n",&arr[0], arr, pa);
	
}

如图,&arr[0] = arr ,数组首元素地址 = 数组名

在这个例子中,数组首元素的类型是int,所以可以用整形指针pa存储数组名所表示的数组首元素地址

2.1 指针数组

2.1 指针数组的概念和写法

指针数组是存储一组指针的数组,写法如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int a, b, c;
    // 指针数组arr
	int* arr[] = { &a,&b,&c };
	return 0;
	
}

arr[]表示数组,int*表示数组每一个元素的类型是整形指针

2.2 指针数组的使用

使用指针数组模拟一个二维数组

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* arr[] = { &arr1,&arr2,&arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}

3. 数组指针

3.1 数组名 VS &数组名

数组名等于数组首元素的地址,&数组名等于什么?

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int arr[10];
	printf("%p %p\n", arr, arr + 1);
	printf("%p %p\n", &arr, &arr + 1);

}

如图,arr +1,跳过4个字节,而 &arr + 1,跳过了40个字节

因为指针步长由指针类型决定,所以 &arr 取出的是整个数组的地址

3.2 数组指针的概念和写法

&arr 取出的数组地址可以由变量存储,这个变量叫做数组指针

数组指针,写法如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int arr[10];
    // 数组指针
	int(*pa)[10] = &arr;

}

(*pa) 表示pa是一个指针

[10] 表示pa存储的是一个数组的地址,且数组存放10个数据

int 表示数组每一个数据的类型是整型

3.3 二维数组数组名等于什么

已知,一维数组数组名 = 数组首元素的地址,&一维数组数组名 = 数组的地址,二维数组数组名等于什么?

二维数组数组名 = 二维数组 第一行 一维数组的地址

如图,二维数组arr + 1,直接从第一行跳到了第二行

3.4 数组指针的使用

数组指针通常用于二维数组传参,如下例

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void print(int(*pa)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", pa[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7} };
	print(arr, 3, 5);
}

二维数组数组名 = 二维数组 第一行 一维数组的地址,所以形参用 数组指针 int(*pa)[5] 来接收

4. 数组传参,指针传参

总结数组,指针传参,函数参数如何设计

4.1 一维数组传参

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[])//ok? 对 - 数组传参 形参可以写成数组形式 且因为arr本质上传递的是首元素地址,所以可以不指定数组大小
{}
void test(int arr[10])//ok? 对 
{}
void test(int* arr)//ok? 对 - arr传递数组arr首元素地址 所以形参可以写成整形指针
{}
void test2(int* arr[20])//ok? 对 - 数组传参 形参可以写成数组形式
{}
void test2(int** arr)//ok? 对 - 数组arr2每个元素类型是int*整形指针,所以数组名arr2 = &整形指针int* = 二级指针,形参用int**二级指针接收 
{}
int main()
{
    int arr[10] = { 0 };
    int* arr2[20] = { 0 };
    test(arr); 
    test2(arr2);
}

4.2 二维数组传参

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[3][5])//ok? 对 
{}
void test(int arr[][])//ok?错 - 二维数组的行可以省略,列不能省略
{}
void test(int arr[][5])//ok? 对
{}
void test(int* arr)//ok?错 - 二维数组数组名 = 二维数组 第一行 一维数组的地址 是一个数组指针 而这里形参arr是整形指针int*
{}
void test(int* arr[5])//ok?错 - 这里的形参arr是个指针数组
{}
void test(int(*arr)[5])//ok?对 - arr是数组指针 指针指向存放5个元素的数组 数组每个元素类型是整形int 
{}
void test(int** arr)//ok?错 - 形参arr 是个二级指针
{}
int main()
{
    int arr[3][5] = { 0 };
    test(arr);
}

4.3 一级指针传参

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 一级指针传参,用一级指针p接收
void print(int* p)
{
}
int main()
{
    int a = 0;
    int arr[10] = { 1,2,3,4,5,6,7,8,9 };
    int* p = arr;
    // 可以传整形变量地址
    print(&a);
    // 传一级指针
    print(p);
    // 传数组名
    print(arr);
    return 0;
}

4.4 二级指针传参

当函数的参数为二级指针的时候,可以接收什么参数?

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(char** p)
{

}
int main()
{
    char c = 'b';
    char* pc = &c;
    char** ppc = &pc;
    char* arr[10];
    // 可以传 &一级字符指针pc
    test(&pc);
    // 可以传 二级字符指针
    test(ppc);
    // 可以传 每个元素类型为char*字符指针的arr数组名 
    test(arr);
    return 0;
}

5. 函数指针

5.1 函数指针的概念和写法

函数指针与数组指针类型,是一个存储函数地址的变量

写法如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
    // 函数指针pa,pb
	int (*pa)(int, int) = add;
	int (*pb)(int, int) = &add;
}

(*pa) 表示pa是一个指针
(int, int)表示pa存储的是一个函数的地址,函数有两个类型为整形的形参

(*pa)前面的int,表示函数的返回类型为int

需要注意的是,在函数指针中,&函数名 = 函数名 = 函数的地址

5.2 函数指针的使用

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pa)(int, int) = add;
	int ret = (*pa)(1, 2);
	printf("%d\n", ret);
}

6. 函数指针数组

6.1 函数指针数组的概念和写法

函数指针数组是存放函数地址的数组

写法如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int main()
{
	int(*ptr[2])(int,int) = { add,sub };
}

int(*ptr[2])(int,int) 如何理解?

ptr首先会与[2]结合,表示ptr是一个数组,数组有2个元素

int (* ptr[2] )(int,int) ---> int (*)(int,int)

(*) (int,int)表示ptr数组的每一个元素是函数地址,函数有两个整形类型形参

6.2 函数指针数组的使用

#include <stdio.h>
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("1.add	2.sub\n");
	printf("3.mul	4.div\n");
}
 
int main()
{
	int(*ptr[5])(int, int) = { 0,add,sub,mul,div }; //函数指针数组
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do 
	{
		menu();
		printf("输入: ");
		scanf("%d", &input);
		if (input > 4 || input <= 0)
		{
			printf("退出程序\n");
		}
		else
		{
			printf("输入x: ");
			scanf("%d", &x);
			printf("输入y: ");
			scanf("%d", &y);
			ret = (ptr[input])(x, y); //通过input,锁定函数地址,最后传参得到结果
			printf("%d\n", ret);
		}
	} while (input);
}

7. 回调函数

7.1 什么是回调函数

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

下面举例说明

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int Add(int x, int y)
{
	return x + y;
}

void calc(int(*pc)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入2个操作数:");
	scanf("%d %d", &x, &y);
	ret = pc(x, y);
	printf("%d\n", ret);
}
int main()
{
	calc(Add);
}

将Add函数地址作为参数传递给函数calc,当函数指针pc(Add函数地址)在calc函数内部使用调用Add函数时,这时候的Add函数就可以称为回调函数

7.2 回调函数的应用

#include <stdio.h>
 
int cmp_int(const void* e1, const void* e2)
{
	return * (int*)e1 - * (int*)e2;
}
struct Stu
{
	char name[20];
	int age;
};
// 以名字排序
int cmp_struct_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
// 以年龄排序
int cmp_struct_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
 
void swap(char* s1, char* s2, size_t width)
{
	size_t i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *s1;
		*s1 = *s2;
		*s2 = tmp;
		s1++;
		s2++;
	}
}
 
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
	size_t i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		size_t j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp( (char*)base + (j * width), (char*)base + (j+1)*width ) > 0)
			{
				swap((char*)base + (j * width), (char*)base + (j + 1) * width, width);
			}
		}
	}
}
 
// 排序整形
void sort_int()
{
	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);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
 
// 排序结构体
void sort_struct()
{
	struct Stu s[5] = { {"b",25},{"e",20},{"a",18},{"c",30},{"d",12} };
	int sz = sizeof(s) / sizeof(s[0]);
	//bubble_sort(s, sz, sizeof(s[0]), cmp_struct_name);
	bubble_sort(s, sz, sizeof(s[0]), cmp_struct_age);
 
}
 
int main()
{
 
	//sort_int();
	sort_struct();
 
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值