目录
回调函数
用冒泡排序模拟实现qsort函数
qsort函数
回忆冒泡排序
给冒泡排序函数增加新成员
比较函数传参的注意事项
比较函数
完整代码
指针和数组相关习题讲解
指针笔试题
官方定义:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
代码解释:
#include
void test()
{
printf("HaHaHa");
}
void print(void (*pr)())//创建一个函数指针接受test函数地址
{
pr();//间接调用test函数打印 因此 test函数便被称为回调函数
}
int main()
{
print(test);//不直接调用test函数打印 而是将test函数的地址作为参数传给print函数
return 0;
}
在模拟实现之前 ,我们要先了解一下qsort函数可以实现什么功能,以及相关参数是什么?
qsort函数实现了一个快速排序算法,其可以对任何数据进行升序排序,但需要传入相关参数。
void qsort(
void *base, //待排序数据的起始位置
size_t num, //数组的元素个数
size_t width, //一个元素的字节大小
int (__cdecl *compare )(const void *elem1, const void *elem2 )
//比较函数的函数指针 用来调用比较函数
);//elem1和elem2代表待比较的两个数据的地址
其中比较函数需要自行创建,返回值为:
< 0 | elem1 less than elem2 |
0 | elem1 equivalent to elem2 |
> 0 | elem1 greater than elem2 |
数组按照比较函数定义的递增顺序排序。要按降序对数组排序,请颠倒比较函数中“大于”和“小于”的含义。
了解完qsort函数的相关参数,那么让我们着手用冒泡排序来实现一样的功能吧!
#include
void bubble_sort(int* arr, int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)//sz-1趟 一趟排一个数字 最后一个数字不用排 因此为sz-1
{
for (j = 0; j < sz - 1 - i; j++) //比较的对数
{
if (arr[j] > arr[j + 1]) //若大于就交换 排序完为升序
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr[] = { 1,3,5,2,4,7,11,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
我们可以看到,qsort函数定义时,是用 void*类型的指针来接收地址的,其意义是什么?
因为qsort函数的作者不知道我们即将要排序的数据是什么类型的,int?字符串?又或者是结构体?,他无从而知,所以他将用void*这种具有无限包容性的指针类型来接收,也因此,qsort函数可以对任何数据进行排序。所以,我们在更改冒泡函数时,接收地址的指针类型也要为void*类型.
void bubble_sort(void* base, int num, int width, int (*comp)(void* e1, void* e2))
我们比较函数需要传入待比较数据的地址,但是我们只知道数据的起始地址base,且他还是void*类型的,那么我们如何用这个参数得到待比较数据的地址呢???又如何对其进行交换呢??
我们都知道内存中是以字节为单位存放数据的,而char*类型的指针每+1,可前进一个字节,正好访问到下一个内存单元,这样,不管任何类型的数据,都可精准访问到,而我们接下来要知道的,就是控制访问的宽度,即 我们拿到的数据存放在几个单元格内,比如int类型的是四个字节,即四个单元格,那么我们如何知道呢?
对咯,我们传入的参数中有个width,即数据的字节大小,在交换的时候,我们一个单元格一个单元格的交换
交换的问题我们解决了,那么如何传参呢?
其实和交换的道理类似,将首地址转换为char*类型后,再加上width就可以得到第二个数据的地址了。
void bubble_sort(void* base, int num, int width, int (*comp)(void* e1, void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (comp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
//swap为交换函数,width是告诉其交换几次,有几个字节(我们所说的单元格)交换几次
我们在写比较函数的代码时,注意形参类型也要为void*,因为我们的函数指针中形参的类型为void*要一一对应,在使用时,将其强制类型转为换对应的类型便可,比如比较的是整型,将其转换为int*型,再解引用进行比较。
//test1为整型数组的比较
//test2为结构体数据的比较
#include
#include
int compare(void* e1, void* e2)
{
return (*((int*)e1) - *((int*)e2));
}
struct stu
{
char name[10];
int age;
};
int cmp1(void* e1, void* e2)
{
strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void swap(char* e1, char* e2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char temp = *e1;
*e1 = *e2;
*e2 = temp;
e1++;
e2++;
}
}
void bubble_sort(void* base, int num, int width, int (*comp)(void* e1, void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (comp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test2()
{
struct stu arr[3] = { {"ziu",20},{"wang",23},{"ihang",25} };
bubble_sort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]), cmp1);
}
void test1()
{
int arr[] = { 10,1,6,7,3,4,5,2,9,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), compare);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test2();
return 0;
}
好,那么先让我们复习一下知识点
数组名一般情况下代表首元素的地址,但是有两个例外
1.sizeof(数组名) - 这里的数组名代表整个数组
2.&数组名 这里取出的是整个数组的地址 要用数组指针来接收
(编译器平台为32位)
//一维数组
1.int a[] = {1,2,3,4};
2.printf("%d\n",sizeof(a));
3.printf("%d\n",sizeof(a+0));
4.printf("%d\n",sizeof(*a));
5.printf("%d\n",sizeof(a+1));
6.printf("%d\n",sizeof(a[1]));
7.printf("%d\n",sizeof(&a));
8.printf("%d\n",sizeof(*&a));
9.printf("%d\n",sizeof(&a+1));
10.printf("%d\n",sizeof(&a[0]));
11.printf("%d\n",sizeof(&a[0]+1));
1.数组名单独放在了sizeof内部,这里代表整个数组,即求整个数组的字节大小,又因为元素为int类型,有四个元素,即 16
2.
2、3、5、10、11为一个类型,以第二题为例讲解
这里数组名没有单独放在sizeof内部,所以代表首元素地址,+0后依然是首元素地址,即&a[0]
,因为是地址,地址的大小为 4/8
4.这里的数组名依然表示数组首元素地址,解引用后得到第一个元素,类型为int,即 4
6. *(a+i)相当于a[ i ],即拿到的是元素本身 元素类型为int,所以这里为4
7.&a拿到的是整个数组的地址,但是它依然是地址,是地址大小就为4/8
8.我们从后往前看,首先&a拿到整个数组的地址,再解引用得到整个数组,所以结果是16
还可以理解为 *与&抵消了,剩下了数组名,而数组名单独放在sizeof内部表示整个数组
9.&a表示拿到整个数组的地址,+1表示跳过了整个数组,但还是地址 4/8
接下来我将对必要的进行讲解
//字符数组
char arr[] = {'a','b','c','d','e','f'};
1.printf("%d\n", sizeof(arr));
2.printf("%d\n", sizeof(arr+0));
3.printf("%d\n", sizeof(*arr));
4.printf("%d\n", sizeof(arr[1]));
5.printf("%d\n", sizeof(&arr));
6.printf("%d\n", sizeof(&arr+1));
7.printf("%d\n", sizeof(&arr[0]+1));
1.arr单独放在sizeof内部表示整个数组,此数组用字符abcdef来初始化,大小为6
2.数组名没有单独放在sizeof内部,表示地址 4/8
3.*arr -> 字符a char型 大小为1
4. 1 // 其表示字符b
5.4/8
6.4/8
7.4/8
strlen是传入一个地址,从该地址向后计算\0之前的字符数目
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
1. printf("%d\n", strlen(arr));
2. printf("%d\n", strlen(arr + 0));
3. printf("%d\n", strlen(*arr));
4. printf("%d\n", strlen(arr[1]));
5. printf("%d\n", strlen(&arr));
6. printf("%d\n", strlen(&arr + 1));
7. printf("%d\n", strlen(&arr[0] + 1));
1.因arr数组初始化时没有放入\0,所以在 ' f ' 后,我们不知道什么时候遇到\0,所以是随机值
2. 与1同理 随机值
3.*arr表示字符a,字符a为97,只要传入strlen,他就会认为这是个地址,但是这里的地址是非法的,可以理解为我们无法访问,因此这是个错误代码(运行时将此代码屏蔽掉)
4.与3同理
5.随机值
&arr得到的是整个数组的地址,从数值方面来说,整个数组地址与首元素地址一样,所以在strlen这里,它就是从首元素的地址向后计算字符个数
6.此时&arr+1跳到了字符f后面的地址,但是我们依然不知道什么时候遇到\0 所以为随机值
7.从第二个元素后开始计算 结果依然为随机值,但是与1相比结果差1,因为跳过了一个元素
char arr[] = "abcdef"; //这种初始化方式 f后隐藏了 \0
1 printf("%d\n", sizeof(arr));
2 printf("%d\n", sizeof(arr + 0));
3 printf("%d\n", sizeof(*arr));
4 printf("%d\n", sizeof(arr[1]));
5 printf("%d\n", sizeof(&arr));
6 printf("%d\n", sizeof(&arr + 1));
7 printf("%d\n", sizeof(&arr[0] + 1));
1.7 &arr为整个数组,因为隐藏了\0,所以为7
2.4/8
3.1 *arr得到字符a
4.1 arr[1] -> 字符b
5.4/8
6.4/8 地址的大小
7.4/8
char arr[] = "abcdef";
1 printf("%d\n", strlen(arr));
2 printf("%d\n", strlen(arr + 0));
3 printf("%d\n", strlen(*arr));
4 printf("%d\n", strlen(arr[1]));
5 printf("%d\n", strlen(&arr));
6 printf("%d\n", strlen(&arr + 1));
7 printf("%d\n", strlen(&arr[0] + 1));
1.6 计算的是 \0 之前的字符个数
2.7
3.错误代码 *arr -> 'a' -> 97
4.错误代码
5.6
&arr是整个数组的地址 但依然从首元素地址处开始计算,从数值方面来说,整个数组地址与首元素地址一样
6.随机值 f后的空间中存的是什么我们无从得知,不知道什么时候遇到\0,所以为随机值
7.5 从b处开始计算
char* p = "abcdef";
1 printf("%d\n", sizeof(p));
2 printf("%d\n", sizeof(p + 1));
3 printf("%d\n", sizeof(*p));
4 printf("%d\n", sizeof(p[0]));
5 printf("%d\n", sizeof(&p));
6 printf("%d\n", sizeof(&p + 1));
7 printf("%d\n", sizeof(&p[0] + 1));
1.4/8 p为指针,存放首元素地址
2.4/8 p+1为第二个元素的地址
3. 1 *p->'a'->char型->大小为1
4. 1 p[0] 等价于 *(p+0) 为'a'
5.4/8 &p表示地址
6.4/8
7.4/8 &p[0]+1 -> &(*(p+0) ) +1 ->表示字符b的地址
char* p = "abcdef";
1 printf("%d\n", strlen(p));
2 printf("%d\n", strlen(p + 1));
3 printf("%d\n", strlen(*p));
4 printf("%d\n", strlen(p[0]));
5 printf("%d\n", strlen(&p));
6 printf("%d\n", strlen(&p + 1));
7 printf("%d\n", strlen(&p[0] + 1));
1.6
2.5 从字符b开始计算
3.错误代码 *p为字符a (运行时记得屏蔽)
4.错误代码
5.随机值
6.随机值
7.5 从b处开始计算
二维数组薄弱的同学这道题建议大家反复食用!!!!(当然也包括作者本人!!!)
//二维数组
int a[3][4] = { 0 };
1 printf("%d\n", sizeof(a));
2 printf("%d\n", sizeof(a[0][0]));
3 printf("%d\n", sizeof(a[0]));
4 printf("%d\n", sizeof(a[0] + 1));
5 printf("%d\n", sizeof(*(a[0] + 1)));
6 printf("%d\n", sizeof(a + 1));
7 printf("%d\n", sizeof(*(a + 1)));
8 printf("%d\n", sizeof(&a[0] + 1));
9 printf("%d\n", sizeof(*(&a[0] + 1)));
10 printf("%d\n", sizeof(*a));
11 printf("%d\n", sizeof(a[3]));
注:二维数组数组名表示首元素地址,为第一行的地址。
1.48 数组名单独放在sizeof内部,表示整个数组
2.4 a[0][0]表示第一个元素,又因为类型为int
3.16 a[0]表示第一行的数组名,数组名单独放在sizeof内部,代表这一整行
4.4/8 数组名没有单独放在sizeof内部,所以这里代表数组首元素地址,
5.4 a[0]为第一行的数组名没有单独放在sizeof内部,所以这里就代表首元素地址,+1后为第二个元素的地址,解引用访问,拿到第一行第二个元素,类型为int,因而为4
6.4/8 a为二维数组数组名,没有单独放在sizeof内部,所以表示数组首元素地址,因为是二维数组,首元素的地址为第一行的地址,+1后为第二行的地址,是地址就是4/8
7.16 (a+1)为第二行的地址,解引用拿到第二行;或者可以这么理解:*(a+1) -> a[1] -> 第二行的数组名,单独放在sizeof内部,求的是第二行的大小
8.4/8 a[0]是第一行的数组名,&数组名就是求整个第一行的地址,+1跳过一整行,表示第二行的地址
9.16 对第二行的地址解引用拿到第二行
10.16 a没有单独存在,所以表示数组首元素地址,对其解引用拿到第一行
还可理解为:*a -> *(a+0) -> a[0]
11.16 sizeof不会访问数组内部,只看类型,a[3]我们知道是不存在的,但是a[2],a[1],a[0]是存在的,其类型一样为int [4] ,所以大小为16
我们总结一下:我们还是要多多注意数组名是否&和单独放在sizeof内部。
1. 2,5
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
解析:
&a+1指向5后面的地址,此时指针类型为int (*) [5],所以需要强制类型转换成int*。
ptr-1,就指向了5的地址,解引用拿到数字5
*(a+1) -> a[1] -> 2
2.
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
我们注意:p为结构体指针,0x表示十六进制数字
p + 0x1加的是一个结构体,而结构体大小为20个字节,所以为:100014
将p转换为整数类型,此时+0x1就是加1,所以为100001
将p转换为unsigned int*型,此时再+1就是跳过一个int,即+4,所以为100004
3. %x是以十六进制进行打印
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
&a+1指向4后面的地址, 转换成int*类型赋给ptr,ptr[-1]表示ptr的地址向前走1然后访问,即数字4
然后将其转换成十六进制,进行打印
结果为:4 2000000
4.
#include
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
这里是逗号表达式,结果为最后一个表达式
5.
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
两地址相减表示地址之间的元素个数,又因为这道题中是低地址减高地址所以为-4,%d打印出来肯定是-4,但是%p就不一定了,我们首先要看补码,
原码:10000000000000000000000000000100
反码:11111111111111111111111111111011
补码:11111111111111111111111111111100
对于%p来说,认为内存中存的是地址,直接把地址打印出来,所以打印的是补码,转换为十六进制为:FFFFFFFC
6. 10,5
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));
return 0;
}
7. at
#include
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
8.
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
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;
}