今天这节课分为两个部分,第一个是利用冒泡排序的思想,仿照qsort函数来实现bubble_sort版的可以排序任意类型数据的函数。另一个部分是对指针的应用(一些面试题)。
bubble_sort实现
先捋一下冒泡排序的基本思想:
- 排序趟数由元素个数决定;
- 每趟内只能将一个元素排到正确的位置;
然后看一下最基本的代码(假设排序的是int
类型的数据):
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*
,那么函数的第一个参数就可以设计为void* base
; - 排序的过程中也一定需要元素个数,这里用一个
size_t sz
来接收; - 知道了元素个数,那也一定需要元素的大小,否则是没法进行排序的,这里就用一个
size_t width
来接收; - 有了这些,参考qsort,就需要一个函数来比较数据中的两个元素了,同样,可以设计为:
int (*cmp)(const void* e1, const void* e2)
那么,函数就可以设计成:
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
依据冒泡排序的思想,参考上面的代码,可以开始实现这个函数:
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
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() > 0)
{
//交换
//int tmp = arr[j];
//arr[j] = arr[j + 1];
//arr[j + 1] = tmp;
Swap();
}
}
}
}
判断部分交给cmp函数去处理,而交换部分就不能写成之前int型的了,需要独立设计一个函数来交换两个元素。
到这里,继续思考如何实现cmp
和Swap
。可以继续参考qsort
。首先思考cmp
:
- cmp的返回值如何设计?参考一下qsort里函数指针的写法,那么可以写出来:
int cmp(const void* e1, const void* e2)
- 传进来的元素可能是任意的类型,那么怎么去利用
cmp
函数呢?类型不同,那么指针的运算就没有办法写死,再思考一步,就是如何让cmp实现类似arr[j]>arr[j+1]
这种模式呢?传进来的元素有首地址,还有宽度,那就思考一下该怎么样利用这些信息。数据的首地址是base,想找到数据中的第j个元素,那么就可以这样做:(char*)base + j*width
。由于char
类型的数据只占1个字节,将base
强制转换为char*
之后,再+j*width
就是第j
个元素的首地址了。所以,对于任意类型的数据,给cmp传参为:cmp((char*)base+j*width,(char*)base+(j+1)*width)
,就可以利用外部实现的cmp来计算了。
然后看一下Swap
:
- 交换的是任意类型的数据,那么也可以一个字节一个字节的交换;
- 交换的多少字节还是
width
决定,那么Swap
可以这设计:
void Swap(char* buf1, char* buf2,size_t width)
实现就比较简单了:
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
*buf1 ^= *buf2;
*buf2 ^= *buf1;
*buf1 ^= *buf2;
buf1++;
buf2++;
}
}
有了这些,完整的写一个冒泡排序:
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
*buf1 ^= *buf2;
*buf2 ^= *buf1;
*buf1 ^= *buf2;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 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);
}
}
}
}
sizeof和strlen的区别
sizeof
是看目标所占空间的大小(字节计),不关心目标在内存里存了什么;sizeof
是操作符strlen
是计算字符串中‘\0’之前元素的个数;strlen
是库函数。
后面的一些面试题会和这两个功能有关。
面试题
一维数组
先看一组代码:
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
一个一个分析一下,但把握下面的原则:
- sizeof(数组名)中的数组名表示整个数组;
- &(数组名)也表示数组的地址;
- 其余情况数组名均作为首元素地址处理。
printf("%d\n",sizeof(a));
:这里a表示整个数组,因此结果是16;
printf("%d\n",sizeof(a+0));
:这里表示的是数组中的第一个元素的地址,所以结果是4/8;
printf("%d\n",sizeof(*a));
:这里对数组名解引用,而a此时是首元素地址,相当于对首元素解引用之后判断大小,数组是int
,所以结果是4;
printf("%d\n",sizeof(a+1));
:这里a还表示首元素地址,首元素地址+1到第二个元素的地址,所以这里表示的是第二个元素地址的大小,还是4/8;
printf("%d\n",sizeof(a[1]));
:这里就是第二个元素的大小,int
型,所以是4;
printf("%d\n",sizeof(&a));
:取地址表示整个数组的地址,但本质还是地址,那么4/8;
printf("%d\n",sizeof(*&a));
:这里取地址和解引用抵消,16;
printf("%d\n",sizeof(&a+1));
:这里是跳过了整个数组,指向了4之后的地址,但还是地址,4/8;
printf("%d\n",sizeof(&a[0]));
:第一个元素的地址,4/8
printf("%d\n",sizeof(&a[0]+1));
:第一个元素的地址+1,第二个元素的地址,还是地址,4/8。
再来一组:
char arr[] = { 'a', 'b', 'c', 'd', 'e','f' };
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr + 0));//4/8 地址
printf("%d\n", sizeof(*arr));//1 首元素a
printf("%d\n", sizeof(arr[1]));//1 第二个元素b
printf("%d\n", sizeof(&arr));//4/8 地址
printf("%d\n", sizeof(&arr + 1));//4/8 地址,f之后的第一个地址
printf("%d\n", sizeof(&arr[0] + 1));//4/8 地址,b的地址
简单做个总结:
- sizeof主要是看操作数的类型,如果能确定是地址,那么结果就是4/8;
- 不是地址的话就是看类型,记住各个类型都占几个字节即可;