C语言指针(延伸)

在这里插入图片描述
🎉welcome to my blog
请留下你宝贵的足迹吧(点赞👍评论📝收藏⭐)
💓期待你的一键三连,你的鼓励是我创作的动力之源💓

1.回调函数

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

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

#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 Calc(int (*pf)(int, int))//函数的参数类型是int (*)(int,int)函数指针类型
//参数名是pf
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入2个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);//利用函数指针调用函数
	printf("%d\n", ret);

}
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 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使用举例

回忆一下冒泡排序

在这里插入图片描述

//写一个冒泡排序的函数,对一组整型数据进行升序排序
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 tem = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tem;
			}
		}
	}
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print_arr(arr, sz);
	return 0;
}

以上代码只能排序整形类型的数组,存在局限性,可使用qsort函数排序任意类型的数据(qsort是库函数,其头文件是stdlib.h用来快速排序数据)

//qsort返回类型是void,有四个参数
void qsort(void* base,//指针,指向的是待排序的数组的第一个元素	
           size_t num, //是base指向的待排序数组的元素个数 
           size_t size, //base指向的待排序数组的元素的大小
           int (*compar)(const void*, const void*)//函数指针-指向的是两个元素的比较函数
           );
//qsort的使用者要明确知道排序什么数据,并且要提供比较两个元素的函数
//利用qsort函数实现对一组整型数据进行排序
#include<stdio.h>
#include<stdlib.h>
//void* 类型指针是无具体类型的指针,这种类型的指针不能直接解引用,也不能进行加减整数的操作
//需要将其强制类型转化为已知类型的指针,再进行相关操作
int com_int(const void* p1, const void* p2)
{
	if (*(int*)p1 > *(int*)p2)
		return 1;//返回大于0的数即可
	else if (*(int*)p1==*(int*)p2)
		return 0;//返回0
	else
		return -1;//返回小于0的数即可
}
//以上代码可以替换成
//int com_int(const void* p1, const void* p2)
//{
//      return *(int*)p1 > *(int*)p2;//此时为升序(即有*(int*)p1 > *(int*)p2时交换)
//若将其改为return *(int*)p2 > *(int*)p1;则为降序(即有*(int*)p2 > *(int*)p1时交换)
//}
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]), com_int);
	print_arr(arr, sz);
}
int main()
{
	test1();
	return 0;
}
//补充:结构体指针->成员名(间接访问)
#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
};
void print(struct Stu* ps)
{
	printf("%s %d\n", (*ps).name, (*ps).age);//可将此方法改为以下代码,更方便
	printf("%s %d\n", ps->name, ps->age);//间接访问
}
void main()
{
	struct Stu s = { "zhangsan",18 };//s为局部的结构体变量
	printf("%s %d\n", s.name, s.age);//直接访问
	print(&s);
	return 0;
}
//注意👀
struct Stu
{
	char name[20];
	int age;
}s1,s2,s3;//s1,s2,s3均为全局的结构体变量
typedef struct Stu
{
	char name[20];
	int age;
}stu;//stu是类型名(等价于struct Stu)

在这里插入图片描述


在这里插入图片描述

3.qsort函数的模拟实现

在这里插入图片描述

//模仿qsort来实现一个冒泡排序的函数(这个函数可以排序任意类型的数据)
#include<stdio.h>
#include<stdlib.h>
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++;
	}
	//将大块空间分成小块进行交换
}
int com_int(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//每趟相邻两两元素相互比较,随着比较趟数的进行,每趟参与比较的元素逐渐减少
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{   //比较arr[j]和arr[j + 1]
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{    //交换两个元素
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
test3()
{
	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]), com_int);
	print_arr(arr, sz);
}
int main()
{
	test3();
	return 0;
}

在这里插入图片描述

//模仿qsort来实现一个冒泡排序的函数(这个函数可以排序任意类型的数据)
//结构体排序
//按照年龄排序(升序)
#include<stdio.h>
#include<stdlib.h>
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++;
	}
	//将大块空间分成小块进行交换
}
struct Stu
{
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//每趟相邻两两元素相互比较,随着比较趟数的进行,每趟参与比较的元素逐渐减少
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{   //比较arr[j]和arr[j + 1]
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{    //交换两个元素
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
test4()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main()
{
	test4();
	return 0;
}
//模仿qsort来实现一个冒泡排序的函数(这个函数可以排序任意类型的数据)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
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++;
	}
	//将大块空间分成小块进行交换
}
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);
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	//趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//每趟相邻两两元素相互比较,随着比较趟数的进行,每趟参与比较的元素逐渐减少
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{   //比较arr[j]和arr[j + 1]
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{    //交换两个元素
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
test5()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
	test5();
	return 0;
}

4.sizeof和strlen

4.1.sizeof

sizeof (单目操作符)计算变量所占内存内存空间大小的,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据。
在这里插入图片描述

4.2 strlen

strlen 是求字符串长度的库函数,其头文件为string.h。

函数原型如下:

size_t strlen ( const char * str );

从 strlen 函数参数 str 的首地址开始统计字符的个数, 遇到\0就停止统计(不记\0)

在这里插入图片描述

#include<stdio.h>
int main()
{
    char arr[]={'a','b','c'};
    printf("%d\n",strlen(arr));//由于没有\0,此处会输出随机值
    return 0;
}

4.3 sizeof和strlen的对比

在这里插入图片描述
sizeof后括号中有表达式的话,表达式不参与计算

#include<stdio.h>
int main()
{
    int a=8;
    short s=4;
    printf("%d\n",sizeof(s=a+2));//a+2得到的结果是int类型的,占4个字节,而s是short类型的,占2个字节,4个字节的数存到2个字节的数中会发生截断,故打印出2
    printf("%d\n",s);//sizeof后括号中的表达式不计算,故打印出4
    return 0;
}

在这里插入图片描述

5.数组和指针相关试题解析

数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

  3. 除此之外所有的数组名都表示首元素的地址。

5.1 ⼀维数组

在这里插入图片描述

在这里插入图片描述

5.2 字符数组

在这里插入图片描述

#include<stdio.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//arr是首元素地址,数组中没有\0,就会导致越界访问,结果会是随机值x
	printf("%d\n", strlen(arr + 0));//arr+0还是数组首元素地址,数组中没有\0,就会导致越界访问,结果会是随机值
	printf("%d\n", strlen(*arr));//arr是首元素的地址,*arr是首元素‘a’,a的ASCII码值是97,strlen会把97当成地址,
	//但97的地址不能被访问(strlen得到的就是野指针),故程序走到这会崩掉,此代码是有问题的
	printf("%d\n", strlen(arr[1]));//arr[1]指向数组第二个元素‘b’,b的ASCII码值是98,
	//但98的地址不能被访问(strlen得到的就是野指针),故程序走到这会崩掉,此代码是有问题
	printf("%d\n", strlen(&arr));//&arr是数组地址,也是从首地址开始,strlen(&arr)相当于strlen(arr),结果会是随机值x
	printf("%d\n", strlen(&arr + 1));//&arr + 1跳过了一个数组,也遇不到\0,会得到随机值x-6
	printf("%d\n", strlen(&arr[0] + 1));//从第二个元素开始向后统计,得到的也是随机值x-1
	return 0;
}
#include<stdio.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr)); //arr是数组名,单独放在sizeof内部,计算的是数组总大小,是7个字节
	printf("%d\n", sizeof(arr + 0));//arr表示数组首元素的地址,arr+0还是首元素的地址,是地址就是4/8
	printf("%d\n", sizeof(*arr));//arr表示数组首元素的地址,*arr就是首元素,大小是1字节
	printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1个字节
	printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8
	printf("%d\n", sizeof(&arr + 1));//&arr是数组的地址,+1跳过整个数组,还是地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] +1是第二个元素的地址,大小是4/8个字节
	return 0;
}

在这里插入图片描述

#include<stdio.h>
int main()
{
	const char* p = "abcdef";//常量字符串,不可被修改
	printf("%d\n", sizeof(p));//p是指针变量,存放的是地址,地址的大小是4(x86环境下)/8(x64环境下)
	printf("%d\n", sizeof(p + 1));//p + 1会指向第二个元素的地址,地址的大小是4(x86环境下)/8(x64环境下)
	printf("%d\n", sizeof(*p));//p的类型是const char*,*p的类型是char,即为1个字节
	printf("%d\n", sizeof(p[0]));//两种理解思路;
	//1.p[0] -->*(p+0) -->*p -->'a',p[0]即‘a’,为一个字节
	//2.把常量字符串想象成数组,p可以理解为数组名,p[0]即为首元素'a'
	printf("%d\n", sizeof(&p));// &p取出指针变量p的地址,地址的大小是4(x86环境下)/8(x64环境下)
	printf("%d\n", sizeof(&p + 1));//&p + 1即为跳过p指针变量后的地址,地址的大小是4(x86环境下)/8(x64环境下)
	printf("%d\n", sizeof(&p[0] + 1));//首元素的地址加一会跳过一个元素,&p[0] + 1即为第二个元素的地址,地址的大小是4(x86环境下)/8(x64环境下)
	return 0;
}

在这里插入图片描述

5.3 ⼆维数组

#include<stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//a是数组名,单独放在sizeof内部,计算的是数组的大小,单位是字节,3*4*sizeof(int)=48
	printf("%d\n", sizeof(a[0][0]));//a[0][0]是第一行第一个元素,为4个字节
	printf("%d\n", sizeof(a[0]));//a[0]是第一行的数组名,数组名单独放在了sizeof内部,计算的是数组的总大小,4*sizeof(int)=16
	printf("%d\n", sizeof(a[0] + 1));//a[0]是第一行的数组名,但此处a[0]并未单独放在sizeof内部
	//所以这里的a[0]即为数组首元素的地址(&arr[0][0]),+1后是arr[0][1]的地址,大小是4/8个字节
	printf("%d\n", sizeof(*(a[0] + 1)));//a[0] + 1表示第一行第二个元素的地址,*(a[0] + 1)表示第一行第二个元素,4个字节
	printf("%d\n", sizeof(a + 1));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址
	//即第一行的地址,a+1会跳过一行,指向第二行,故a+1是第二行的地址,是数组指针,地址的大小是4(x86环境下)/8(x64环境下)
	printf("%d\n", sizeof(*(a + 1)));//两种理解思路:
	//1.a+1是第二行的地址,*(a + 1)就是第二行,计算的是第二行的大小,为16个字符
	//2.*(a + 1)等价于a[1],即第二行的数组名,sizeof(*(a + 1))计算的是第二行的大小
	printf("%d\n", sizeof(&a[0] + 1));//a[0]是第一行的数组名,&a[0]取出的就是第一行的地址
	//&a[0]+1即为第二行的地址,地址的大小是4(x86环境下)/8(x64环境下)
	printf("%d\n", sizeof(*(&a[0] + 1))); //*(&a[0] + 1)意思是对第二行解引用,访问的是第二行,大小是16个字节
	printf("%d\n", sizeof(*a));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址
	//即第一行的地址,*a就是第一行,计算的是第一行的大小,16个字节
	//*a==*(a+0)==a[0]
	printf("%d\n", sizeof(a[3]));//a[3]无需真实存在,仅仅通过类型推断就能算出长度
	//a[3]是第四行的数组名,单独放在sizeof内部,计算的是第四行的大小,16个字节
	return 0;
}

6.指针运算相关试题解析

在这里插入图片描述

//在X86(32位)环境下 
//假设结构体的⼤⼩是20个字节 
//程序输出的结果是?
#include<stdio.h> 
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;//编译器会将16进制地址0x100000认为是int类型
//故需将其强制类型转化为结构体指针类型
//结构体指针加一会跳过一个结构体,此结构体加一的效果相当于跳过20个字节
int main()
{
	printf("%p\n", p + 0x1);//0x1为16进制形式的一
	//0x100000+20---->0x100014(1*16+4*1=20)
    //以16进制%p的形式打印时由于要补够一个地址的长度故会打印00100014
	printf("%p\n", (unsigned long)p + 0x1);//指针变量p被强制类型转化为unsigned long类型
	//整型值+1就是+1,故0x100000+1---->0x100001,打印00100001
	printf("%p\n", (unsigned int*)p + 0x1);//指针变量p被强制类型转化为unsigned int*类型
	//整型指针加一会跳过一个整型(4个字节),故0x100000+1---->0x100004,打印00100004
	return 0;
}
#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };//逗号表达式从左向右计算,最后一个表达式为整个表达式的结果
	//故此结构体可表示为int a[3][2] = { 1,3,5 };
	int* p;
	p = a[0];//a[0]为第一行的数组名,数组名表示首元素的地址,相当于&a[0][0]的地址
	printf("%d", p[0]);//p[0]相当于*(p+0)即*(p),实际上就是&a[0][0],为1
	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);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//打印出10,5
	return 0;
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
💓期待你的一键三连,你的鼓励是我创作的动力之源💓
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

engrave行而不辍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值