【C语言指针】 回调函数、冒泡函数模拟实现qsort、指针和数组笔试题解析

目录

    • 一、回调函数
        • 定义:
      • 用回调函数形式实现加法运算
    • 二、qsort
        • 函数参数:
        • void指针
      • 用qsort排序整型和结构体
      • 用冒泡函数模拟实现qsort,排序整型和结构体
    • 三、指针和数组笔试题解析
      • 字符数组
      • 字符串
      • 总结:
      • 指针
      • 二维数组
      • 指针加1
      • 结构体
      • char


一、回调函数

定义:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一
个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该
函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或
条件进行响应。

用回调函数形式实现加法运算

int Add(int x, int y)
{
	return x + y;
}
void Calc(int (*pf)(int, int))
{
	int ret = pf(3, 5);
	printf("%d\n", ret);
}
int main()
{
	Calc(Add);

	return 0;
}

二、qsort

函数参数:
void qsort(void* base, 
			size_t num, // 待排序的元素个数
			size_t width, // 一个元素的大小,单位是字节
			int(* cmp)(const void* e1, const void* e2) 
			// cmp指向的是:排序时,用来比较2个元素的函数
);
void指针

void* - 无具体类型的指针
能够接收任意类型的地址
缺点:不能进行运算,不能±整数,不能解引用


用qsort排序整型和结构体

#include 
#include 
#include 

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

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

void test1()
{
	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);
}


struct Stu
{
	char name[20];
	int age;
};

int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

int cmp_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

void printStuName(struct Stu* s, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s : %d\n", s->name, s->age);
		s++;
	}
}

void printStuAge(struct Stu* s, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s : %d\n", s->name, s->age);
		s++;
	}
}

void test2()
{
	struct Stu s[3] = { {"张三", 15}, {"李四", 30}, {"王五", 10} };
	int sz = sizeof(s) / sizeof(s[0]);

	// 按照名字排序
	qsort(s, sz, sizeof(s[0]), cmp_by_name);
	printStuName(s, sz);
	// 李四: 30
	// 王五 : 10
	// 张三 : 15

	printf("\n");

	// 按照年龄排序
	qsort(s, sz, sizeof(s[0]), cmp_by_age);
	printStuAge(s, sz);
	// 王五 : 10
	// 张三 : 15
	// 李四 : 30
}

int main()
{
	// 测试排序整型
	//test1();

	// 测试排序结构体数据
	test2();

	return 0;
}


用冒泡函数模拟实现qsort,排序整型和结构体

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

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

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 BubbleSort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))
{
	size_t i = 0;
	// 趟数
	for (i = 0; i < num - 1; i++)
	{
		size_t j = 0;
		for (j = 0; j < num - 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);
			}
		}
	}
}

// 测试自定义的BubbleSort();
void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	
	BubbleSort(arr, sz, sizeof(arr[0]), cmp_int);

	print_arr(arr, sz);
}


// 测试自定义的BubbleSort() 排序结构体
struct Stu
{
	char name[20];
	int age;
};

int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test4()
{
	struct Stu s[3] = { {"张三", 15}, {"李四", 30}, {"王五", 10} };
	int sz = sizeof(s) / sizeof(s[0]);

	// 按照名字排序
	qsort(s, sz, sizeof(s[0]), cmp_by_name);

	// 按照年龄排序
	//qsort(s, sz, sizeof(s[0]), cmp_by_age);
}

int main()
{
	//test3();

	test4();

	return 0;
}

【C语言指针】 回调函数、冒泡函数模拟实现qsort、指针和数组笔试题解析_第1张图片



三、指针和数组笔试题解析

Gitee 9_2

#include 

int main()
{
	int a[] = { 1,2,3,4 };

	printf("%d\n", sizeof(a)); // 数组名a单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小
	printf("%d\n", sizeof(a + 0)); // a表示首元素的地址,a+0还是首元素的地址,地址的大小就是4/8字节
	printf("%d\n", sizeof(*a)); // a表示首元素的地址,*a就是首元素 --> a[0],大小就是4
	// *a <==> *(a+0) <==> a[0]
	
	printf("%d\n", sizeof(a + 1)); // a表示首元素的地址,a+1是第二个元素的地址,大小就是4/8
	printf("%d\n", sizeof(a[1])); // a[1] 就是第二个元素,4
	printf("%d\n", sizeof(&a)); // &a - 数组的地址 - 4/8 - int(*)[4]

	printf("%d\n", sizeof(*&a)); // *&a - &a是数组的地址,对数组的地址解引用拿到的是的数组,所以大小是16
	// printf("%d\n", sizeof(&a)); // 16
	
	printf("%d\n", sizeof(&a + 1)); // &a是数组的地址,&a+1是数组的的地址+1,跳过整个数组,虽然跳过了数组,还是地址 4/8
	printf("%d\n", sizeof(&a[0])); // 首元素地址 4/8
	printf("%d\n", sizeof(&a[0] + 1)); // 第二个元素的地址 4/8

	return 0;
}

字符数组

int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	
	printf("%d\n", sizeof(arr)); // 6 单独放在sizeof 整个数组

	printf("%d\n", sizeof(arr + 0)); // 4/8 首元素地址

	printf("%d\n", sizeof(*arr)); // 1 char*解引用一字节

	printf("%d\n", sizeof(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 b的地址


	// strlen - 库函数
	// 求字符串长度

	// sizeof - 操作符,单位字节
	// 求变量所占空间的大小
	// 求类型创建的变量所占空间的大小

	printf("%d\n", strlen(arr)); // 随机值

	printf("%d\n", strlen(arr + 0)); // 随机值-1

	printf("%d\n", strlen(*arr)); // err
	// *arr - 'a' - 97    strlen以为传进来的'a'得ascii码值就是地址

	printf("%d\n", strlen(arr[1])); // err  'b' - 98

	printf("%d\n", strlen(&arr)); // 随机值
	// &arr类型是char(*)[6],strlen的类型是char*,类型不匹配,但还是指向首元素,值一样,类型转换a的地址往后找

	printf("%d\n", strlen(&arr + 1)); // 随机值-6

	printf("%d\n", strlen(&arr[0] + 1)); // 随机值-1

	return 0;
}

字符串

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

	printf("%d\n", sizeof(arr)); // 7

	printf("%d\n", sizeof(arr + 0)); // 4/8  arr是首元素地址

	printf("%d\n", sizeof(*arr)); // 1  arr是首元素的地址,*arr就是首元素

	printf("%d\n", sizeof(arr[1])); // 1  第二个元素

	printf("%d\n", sizeof(&arr)); // 4/8  数组的地址,数组的地址也是地址

	printf("%d\n", sizeof(&arr + 1)); // 4/8  &arr是数组的地址,&arr+1是跳过整个数组后的地址

	printf("%d\n", sizeof(&arr[0] + 1)); // 4/8  第二个元素的地址


	printf("%d\n", strlen(arr)); // 6
	
	printf("%d\n", strlen(arr + 0)); // 6  首元素地址+0还是首元素地址
	
	printf("%d\n", strlen(*arr)); // err
	
	printf("%d\n", strlen(arr[1])); // err
	
	printf("%d\n", strlen(&arr)); // 6  类型不匹配,但还是以char*找
	
	printf("%d\n", strlen(&arr + 1)); // 随机值
	
	printf("%d\n", strlen(&arr[0] + 1)); // 5
	
	return 0;
}

总结:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。(大小是字节)
  2. & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

【C语言指针】 回调函数、冒泡函数模拟实现qsort、指针和数组笔试题解析_第2张图片


指针

int main()
{
	const char* p = "abcdef";

	printf("%d\n", sizeof(p)); // 4/8  p是一个指针变量,存放地址
	
	printf("%d\n", sizeof(p + 1)); // p类型chat* 加1跳过1字节 指向字符b的地址
	
	printf("%d\n", sizeof(*p)); // 1  通过地址找到a
	
	printf("%d\n", sizeof(p[0])); // 1  p[0]--> *(p+0)--> *p
	
	printf("%d\n", sizeof(&p)); // 4/8  取出指针p的地址
	
	printf("%d\n", sizeof(&p + 1)); // 4/8
	// &p 一级指针&是二级指针 
	// char** ptr = &p
	// &p+1
	// ptr+1
	// 跳过p指向指针变量p后面,还是地址
	
	printf("%d\n", sizeof(&p[0] + 1)); // 4/8  b的地址


	printf("%d\n", strlen(p)); // 6
	
	printf("%d\n", strlen(p + 1)); // 5
	
	printf("%d\n", strlen(*p)); // err
	
	printf("%d\n", strlen(p[0])); // err
	
	printf("%d\n", strlen(&p)); // 随机值  
	// 指针变量的地址,不是字符串首元素的地址

	printf("%d\n", strlen(&p + 1)); // 随机值
	// 与&p没有关系,因为p里的4字节可能有\0 提前结束

	printf("%d\n", strlen(&p[0] + 1)); // 5

	return 0;
}

二维数组

int main()
{
	//二维数组
	int a[3][4] = { 0 };

	printf("%d\n", sizeof(a)); // 48
	
	printf("%d\n", sizeof(a[0][0])); // 4  第一行第一列元素 int
	
	printf("%d\n", sizeof(a[0])); // 16   a[0]第一行数组名,数组名单独放在sizeof
	
	printf("%d\n", sizeof(a[0] + 1)); // 4/8
	// a[0]是第一行的数组的数组名,数组名没有单独放在sizeof,也没有&,所以arr[0]是首元素的地址
	// 第一行数组第一个元素的地址
	// a[0] + 1 就是第一行,第二个元素的地址

	printf("%d\n", sizeof(*(a[0] + 1))); // 4 第一行第二个元素 int
	
	printf("%d\n", sizeof(a + 1)); // 4/8
	// 数组名a,没有单独放在sizeof内部,也没有&,所以a表示首元素(第一行)的地址
	// 二维数组的数组名,表示首元素(第一行)的地址
	// +1 跳过一行 指向第二行地址
	// a+1 --> &a[1]

	printf("%d\n", sizeof(*(a + 1))); // 16  a首元素地址,第一行地址+1第二行地址,第二行地址* 就是第二行
	// *(a + 1)) - 第二行数组名 单独放在sizeof 整个数组大小 16

	printf("%d\n", sizeof(&a[0] + 1)); // 4/8
	// a[0]第一行数组名
	// 数组名没有单独放在sizeof内部,但是&,表示首元素地址
	// &a[0] 拿到的是第一行的地址
	// &a[0]+1 就是第二行的地址
	
	printf("%d\n", sizeof(*(&a[0] + 1))); // 16  第二行的地址* 拿到第二行
	// *(&a[0] + 1) --> *(&a[1]) --> a[1]
	// 数组名单独放在sizeof内部求大小

	printf("%d\n", sizeof(*a)); // 16
	// a表示首元素(第一行)的地址
	// *a - 第一行 - 第一行的数组名
	// *a --> *(a+0) --> a[0]
	
	printf("%d\n", sizeof(a[3])); // 16
	// sizeof内部表达式不参与运算
	// a[3]假设存在,就是第四行的数组名,sizeof(a[3])就相当于把第四行
	//  的数组名单独放在sizeof内部

	return 0;
}

指针加1

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };

	int* ptr = (int*)(&a + 1);
	// int(*)[5] --强制类型转换 (int*)
	// ptr指向5后 减1减一个int 指向5 解引用取出5

	printf("%d,%d", *(a + 1), *(ptr - 1));
	// *(a + 1) -- 数组名表示首元素地址,+1第二个元素地址,*是第二个元素
	
	// 2 5
	return 0;
}

结构体

//由于还没学习结构体,这里告知结构体的大小是20个字节
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p; // 结构体指针
int main()
{
	// p的类型是struct Test*   -- 0x100000的类型是int    
	// 强制类型转换成结构体指针类型
	p = (struct Test*)0x100000;

	printf("%p\n", p + 0x1); //0x1 十六进制1就是1
	// 结构体指针+1 跳过一个结构体 大小20
	// 0x00100014
	// 1*16^1 + 4*16^0 = 20

	printf("%p\n", (unsigned long)p + 0x1);
	// p强制类型转化成整型  
	// 整型+1  
	// 0x100000 + 1
	// 转化成10进制计算  1*16^5 + 1

	printf("%p\n", (unsigned int*)p + 0x1);
	// 强制类型转化成整型指针,整型指针+1,跳过一个整型4字节
	// 0x00100004

	// 00100014  00100001  00100004
	// %#xp  默认打印0x

	return 0;
}

char

#include 

int main()
{
	// 0-255
	unsigned char a = 200;
	// 00000000000000000000000011001000
	// 11001000 - a  存到a、b中截断

	unsigned char b = 100;
	// 00000000000000000000000001100100
	// 01100100 - b
	
	unsigned char c = 0;

	c = a + b;
	// 计算:整型提升
	// 无符号数,直接补0

	// 00000000000000000000000011001000 - a
	// 00000000000000000000000001100100 - b
	// 00000000000000000000000100101100 - a+b
	// 00101100 - c 存进c截断

	printf("%d %d", a + b, c);
	// c按%d补码打印 整型提升
	// c是unsigned char 补0
	// 00000000000000000000000000101100 - %d打印c  正数原反补相同
	// 44
	
	// a+b的值没有存进c,没有发生截断,直接以%d打印
	// 00000000000000000000000100101100 - a+b
	// 正数原反补相同
	// 300

	return 0;
}

你可能感兴趣的:(C语言,c语言)