目录
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;
}