C语言进阶(3)——指针(3)

目录

1.回调函数

2.qsort函数

3.sizeof和strlen的比较

4.数组和指针练习


1.回调函数

概念:

如果把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。

代码实例:

//使用回调函数前:
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 x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
	return 0;
}


//使用回调函数后:
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;
}

//calc功能强大了
void calc(int (*pf)(int,int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);//完成计算	
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
	return 0;
}
2.qsort函数

(1)qsort函数可以实现排序任意类型的数据;

(2)qsort函数在使用时需要使用者传递一个函数的地址,这个函数用于比较排序数组中的2个元素,按照参数与返回值的要求实现即可;

(3)学会如何使用qsort:

void qsort(
    void* base,//base 指向了要排序的数组的第一个元素
    size_t num, //base指向的数组中的元素个数(待排序的数组的元素的个数)
    size_t size,//base指向的数组中元素的大小(单位是字节)
    int (*compar)(const void*p1, const void*p2)//函数指针 - 指针指向的函数是用来比较数组中的2个元素的
    );
#include <stdlib.h>
#include <string.h>

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

//1.qsort排序整型数据
void test1()
{
	int arr[10] = { 3,1,5,2,4,6,8,7,0,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

//2.qsort函数排序结构体数据
struct Stu
{
	char name[20];//名字
	int age;
};

//
//3.qsort比较2个结构体数据
//- 不能直接使用> < == 比较
//1> 可以按照名字比较
//2> 可以按照年龄比较
//
//按照年龄比较
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age;
}

void test2()
{
	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 38}, {"wangwu", 18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}

//按照名字比较
//两个字符串比较不能使用> < ==
//而是使用库函数strcmp - string compare
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

void test3()
{
	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 38}, {"wangwu", 18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

(4)回调函数通过冒泡排序实现qsort函数:

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
#include <stdlib.h>
#include<string.h>
//改造的前提,还是使用冒泡排序 
//void bubble_sort(int arr[], int sz)//参数也要改变
//{
//	//趟数
//	int i = 0;
//	for (i = 0; i < sz - 1; i++)
//	{
//		//一趟冒泡排序的过程
//		//两两相邻元素的比较
//		int j = 0;
//		for (j = 0; j < sz - 1 - i; j++)
//		{
//			if (arr[j] > arr[j + 1])//比较的地方要改造:回调函数
//			{
//				//交换的代码也要变
//				int tmp = arr[j];
//				arr[j] = arr[j + 1];
//				arr[j + 1] = tmp;
//			}
//		}
//	}
//}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//void test1()
//{
//	//将一组整数排序为升序
//	int arr[10] = { 3,1,5,2,4,6,8,7,0,9 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	bubble_sort2(arr, sz);
//	print_arr(arr, sz);//排序后打印一次
//}

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

//void test2()
//{
//	//将一组整数排序为升序
//	int arr[10] = { 3,1,5,2,4,6,8,7,0,9 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	qsort(arr,sz,sizeof(arr[0]), cmp_int);
//	print_arr(arr, sz);//排序后打印一次
//}

void Swap(char* buf1, char* buf2, size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;  
	}
}

void bubble_sort2(void* base,size_t sz,size_t width,int(*cmp)(const void *p1,const void *p2))//参数也要改变
//因为排序的趟数不可能为负,故传参size_t无符号整型
//使用cmp函数对两个元素进行比较,由于只希望比较而不改变故使用const修饰两个传入的参数
{
	//趟数
	int i = 0;

	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		//两两相邻元素的比较
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//if (arr[j] > arr[j + 1])//比较的地方要改造:回调函数
			if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
			//base指的是首元素
			//j*width:下标*字符宽度(4个字节)/结构体宽度(24个字节)
			//将base强制类型转换为char类型(比较两个字符大小)
			//使用char强制类型转换而不是int强制类型转换:
            //1.int整型类型只有4个字节
            //2.而char字符类型取决于字符具体的字节大小
            //(例如本题中排序数组数据字符宽度为4个字节大小,
            //而排序结构体数据时字符宽度为24个字节大小)
			{
				//交换的代码也要变
				/*int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;*/
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}

void test3()
{
	//设计和实现bubble_sort(),这个函数能够排序任意类型的数据
	int arr[10] = { 3,1,5,2,4,6,8,7,0,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort2(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

//使用结构体bubble_sort2来排序结构体的数据
struct Stu
{
	char name[20];
	int age;
};

int cmp_stu_by_name(const void* p1,const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

void test4()
{
	struct Stu arr[] = { {"zhangsan",18},{"lisi",20},{"wangwu",19}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bubble_sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_name);//按名字排序 
	bubble_sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age);//按年龄排序

	//打印arr数组的内容
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n",arr[i].name,arr[i].age);
	}
}


int main()
{
	//test1();
	//test2();
	//test3();
	test4();

	return 0;
}

思路梳理:

对排序数组数据的思路梳理(排序结构体数据思路与其类似,只不过字符宽度由4个字节变为了24个字节(可自主调试验证));

3.sizeof和strlen的比较

(1)sizeof:

1.sizeof是操作符;

2.sizeof计算操作数所占内存的⼤⼩, 单位是字节

3. 不关注内存中存放什么数据,可以存放任意类型数据

(2)strlen:

1. strlen是库函数,使⽤需要包含头⽂件 string.h ;

2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数;

3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界;

4.只能针对字符串类型; 

#inculde <stdio.h>
int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));//4
	printf("%d\n", sizeof a);//4
	printf("%d\n", sizeof(int));//4

	return 0;
}

int main()
{
	char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";
	printf("%d\n", strlen(arr1));//随机的,strlen必须要找到\0
	printf("%d\n", strlen(arr2));//3

	printf("%d\n", sizeof(arr1));//3
	printf("%d\n", sizeof(arr1));//4——3个元素+1个\0
	return 0;
}
4.数组和指针练习
//数组名的理解
//数组名一般表示数组首元素的地址
//但是有2个例外:
//1. sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
//2. &数组名,数组名表示整个数组,取出的数组的地址
//除此之外,所有遇到的数组名都是数组首元素的地址

int main()
{
	int a[] = { 1,2,3,4 };//a数组有4个元素,每个元素是int类型的数据

	printf("%zd\n", sizeof(a));//16 - sizeof(数组名)的情况,计算的是整个数组的大小,单位是字节 - 16
	printf("%zd\n", sizeof(a + 0));//a表示的就是数组首元素的地址,a+0还是首元素的地址 - 4/8
	//int*
	printf("%zd\n", sizeof(*a));//a表示的就是数组首元素的地址,*a 就是首元素(解引用),大小就是4个字节
	printf("%zd\n", sizeof(a + 1));//a表示的就是数组首元素的地址,a+1就是第二个元素的地址,这里的计算的是第二个元素的地址的大小-4/8

	printf("%zd\n", sizeof(a[1]));//a[1]是数组的第二个元素(一个整型数据的大小),大小是4个字节
	printf("%zd\n", sizeof(&a));//&a - 取出的是数组的地址,但是数组的地址也是地址,是地址,大小就是4/8个字节
	//int (*pa)[4] = &a
	//int(*)[4]
	printf("%zd\n", sizeof(*&a));//等价于sizeof(a) -16
	//1. & 和 * 抵消
	//2.&a 的类型是数组指针,int(*)[4],*&a就是对数组指针解引用访问一个数组的大小,是16个字节

	printf("%zd\n", sizeof(&a + 1));//&a+1是跳过整个数组后到达的一个地址,是地址,大小就是4/8个字节

	printf("%zd\n", sizeof(&a[0]));//&a[0]是数组第一个元素的地址,大小就是4/8个字节
	printf("%zd\n", sizeof(&a[0] + 1));//&a[0] + 1 是第二个元素的地址,大小就是4/8个字节
	                      //int*
	return 0;
}
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };//arr数组中有6个元素

	printf("%d\n", sizeof(arr));//计算的是整个数组的大小,6个字节
	printf("%d\n", sizeof(arr + 0));//arr+0 是数组第一个元素的地址 4/8
	printf("%d\n", sizeof(*arr));//*arr是首元素,计算的是首元素的大小,就是1个字节
	printf("%d\n", sizeof(arr[1]));//arr[1]:第二个元素的大小  - 1
	printf("%d\n", sizeof(&arr));//整个数组的地址 - 4/8
	printf("%d\n", sizeof(&arr + 1));//跳过整个数组到达的地址,是地址就是4/8
	printf("%d\n", sizeof(&arr[0] + 1));//第二个元素的地址 - 4/8

	return 0;
}
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//不知道什么时候遇到\0,随机值
	printf("%d\n", strlen(arr + 0));//随机值,且与上一行的随机值相同
	//'a'-97(ASCII码值)
//printf("%d\n", strlen(*arr));//strlen需要的是地址,然而此时传入的是字符a的ASCII码值97,只是个编号,故报错 - err
//                   //'b'-98
//printf("%d\n", strlen(arr[1]));//传入第二个元素b,同上 - err
	printf("%d\n", strlen(&arr));//找不到\0,随机值
	printf("%d\n", strlen(&arr + 1));//跳过整个数组到达的地址,随机值(与上一行不同)
	printf("%d\n", strlen(&arr[0] + 1));//从第二个元素开始跳过整个数组到达的地址,随机值

	return 0;
}
int main()
{
	char arr[] = "abcdef";
    //sizeof返回值为size_t类型值,应用%zd打印
	printf("%zd\n", sizeof(arr));//7
	printf("%zd\n", sizeof(arr + 0));//arr+0是数组首元素的地址,地址的大小是4/8个字节
	printf("%zd\n", sizeof(*arr));//*arr是数组的首元素,这里计算的是首元素的大小 1
	printf("%zd\n", sizeof(arr[1]));//1
	printf("%zd\n", sizeof(&arr));//&arr - 是数组的地址,数组的地址也是地址,是地址就是4/8个字节
	printf("%zd\n", sizeof(&arr + 1));//&arr+1,跳过整个数组,指向了数组的后边,4/8
	printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 4/8

	return 0;
}
int main()
{
	char arr[] = "abcdef";

	printf("%zd\n", strlen(arr));//arr也是数组首元素的地址 6
	printf("%zd\n", strlen(arr + 0));//arr + 0是数组首元素的地址,6
	//printf("%zd\n", strlen(*arr));//?传递是'a'-97,//err
	//printf("%zd\n", strlen(arr[1]));//?'b'-98//err
	printf("%zd\n", strlen(&arr));//6, &arr虽然是数组的地址,但是也是指向数组的起始位置
	printf("%zd\n", strlen(&arr + 1));//随机值
	printf("%zd\n", strlen(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 - 5

	return 0;
}
int main()
{
	char* p = "abcdef";
	printf("%zd\n", sizeof(p));//4/8 计算的指针变量的大小
	printf("%zd\n", sizeof(p + 1));//p + 1是'b'的地址,是地址大小就是4/8个字节
	printf("%zd\n", sizeof(*p));//*p就是'a',大小是1个字节
	printf("%zd\n", sizeof(p[0]));//p[0]--> *(p+0) - *p //1字节
	printf("%zd\n", sizeof(&p));//&p也是地址,是指针变量p的地址,大小也是4/8个字节
	printf("%zd\n", sizeof(&p + 1));//&p + 1是指向p指针变量后面的空间,也是地址,是4/8个字节
	printf("%zd\n", sizeof(&p[0] + 1));//&p[0]+1是'b'的地址,是地址就是4/8个字节

	return 0;
}
​int main()
{
	char* p = "abcdef";

	printf("%zd\n", strlen(p));//6
	printf("%zd\n", strlen(p + 1));//从第二个元素开始 5
	//printf("%zd\n", strlen(*p));//'a'- 97//err
	//printf("%zd\n", strlen(p[0]));//p[0]--*(p+0)-->*p //err
	printf("%zd\n", strlen(&p));//随机值
	printf("%zd\n", strlen(&p + 1));//随机值
	printf("%zd\n", strlen(&p[0] + 1));//从第二个元素地址往后数 5

	return 0;
}
int main()
{
	//二维数组也是数组,之前对数组名理解也是适合
	int a[3][4] = { 0 };
	printf("%zd\n", sizeof(a));//12*4 = 48个字节,数组名单独放在sizeof内部
	printf("%zd\n", sizeof(a[0][0]));//4
	printf("%zd\n", sizeof(a[0]));//a[0]是第一行这个一维数组的数组名,数组名单独放在sizeof内部了
	//计算的是第一行的大小,单位是字节,16个字节

	printf("%zd\n", sizeof(a[0] + 1));//a[0]第一行这个一维数组的数组名,这里表示数组首元素
	//也就是a[0][0]的地址,a[0] + 1是a[0][1]的地址 4/8

	printf("%zd\n", sizeof(*(a[0] + 1)));//a[0][1] - 4个字节
	printf("%zd\n", sizeof(a + 1));//a是二维数组的数组名,但是没有&,也没有单独放在sizeof内部
	//所以这里的a是数组收元素的地址,应该是第一行的地址,a+1是第二行的地址
	//大小也是4/8 个字节
	printf("%zd\n", sizeof(*(a + 1)));//*(a + 1) ==> a[1] - 第二行的数组名,单独放在sizeof内部,计算的是第二行的大小
	//16个字节
	printf("%zd\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,&a[0]+1就是第二行的地址,4/8
	printf("%zd\n", sizeof(*(&a[0] + 1)));//访问的是第二行,计算的是第二行的大小,16个字节
	//int(*p)[4] = &a[0] + 1;
	//
	printf("%zd\n", sizeof(*a));//数组名没有单独放入sizeof中也没有取地址,表示的就是数组首元素地址
//这里的a是第一行(二维数组首元素)的地址,*a就是第一行,sizeof(*a)计算的是第一行的大小-16
	//*a --> *(a+0) --> a[0]
	printf("%zd\n", sizeof(a[3]));//这里不存在越界
	//因为sizeof内部的表达式不会真实计算的
	//a[3]就是第四行的数组名,数组名单独放入sizeof内部,计算的是第四行的大小-16

	return 0;
}
5.指针运算练习
#include <stdio.h>

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));//2 5
    return 0;
}
//在X86环境下
//假设结构体的大小是20个字节
//程序输出的结果是啥?

struct Test
{
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}* p = (struct Test*)0x100000;
//
//指针+整数
//
int main()
{
    printf("%#x\n", p + 0x1); //结构体指针+1跳过20个字节,0x100000+20 == 0x100014(16进制:1*16^1+4*16^0)
    printf("%#x\n", (unsigned long)p + 0x1);//(强制类型转换)整型+1:0x100000+1 == 0x100001
    printf("%#x\n", (unsigned int*)p + 0x1);//(强制类型转换)无符号整型指针+1(跳过4个字节):0x100000+1 == 0x100004
    return 0;
}
#include <stdio.h>

int main()
{
    //注意逗号表达式:整个表达式结果为最后一个表达式结果
    //实际上为int a[3][2] = {1, 3, 5};
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int* p;
    p = a[0];
    printf("%d", p[0]);//1
    return 0;
}

//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>

int main()
{
    int a[5][5];
    int(* p)[4];//p是数组指针,指向的元素是4个int类型元素(将4个元素当一行)
    p = a;
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//指针-指针=两指针间的元素个数(小地址-大地址得到负数;大地址-小地址得到正数)
    //%d打印原码,%p将其翻译为地址(用补码翻译)
    return 0;//
}
#include <stdio.h>
int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    int* ptr1 = (int*)(&aa + 1);//&aa是首元素的地址,就是第一行地址;&aa+1为第二行地址
    int* ptr2 = (int*)(*(aa + 1));//aa+1等价于aa[1],为第二行的数组名;由于既未单独放于sizeof又未取地址,故就是第二行首元素的地址
    printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
    return 0;
}

 

 

#include <stdio.h>
int main()
{
    char* a[] = { "work","at","alibaba" };//三个元素均为char*类型
    char** pa = a;
    pa++;
    printf("%s\n", *pa);//at
    return 0;
}

#include <stdio.h>
int main()
{
    char* c[] = { "ENTER","NEW","POINT","FIRST" };//char*类型指针数组存储的是各字符串的首字符
    //              'E'     'N'    'P'    'F'
    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;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值