一、第十课作业复盘
题目:定义大小为100的整型数组,使用随机函数给数组元素赋值,数值范围为1…100,并且排序,使用冒泡排序实现。
复盘中需要注意的点:
数组作为形参的时候,我们一般使用指针类写法,并且一定要判空、判空、判空!!(三遍,希望下次不要忘记了)。
判空的方法有两种,如下所示:
#include <assert.h>
int Init_Array(int *br,int n)
{
//法一:if判断,若为空,返回-1
if(br == NULL)
{
return -1;
}
//法二:断言,如果断言括号里条件满足,继续执行
//如果不满足,终止程序
assert(br!=NULL);
}
所以初始化数组的代码如下:
int Init_Array(int* br, const int n)
{
assert(br != 0 || n > 0);
srand(time(NULL));
for (int i = 0; i < n; i++)
{
br[i] = rand() % 100 + 1;
}
return br[n];
}
另外,如果形参不可以被改变我们一般要给形参加上const,保证程序的安全性。
如下:打印数组的功能代码。
void Show_Array(const int *br,const int n)
{
for (int i = 0; i < n; i++)
{
printf("%5d", br[i]);
if ((i+1) % 10 == 0)
{
printf("\n");
}
}
printf("\n");
}
冒泡排序函数:
int Bubble_Sort(int *br, const int n)
{
assert(br != 0|| n > 0);
for (int i = 0; i < n-1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (br[j] > br[j+1])
{
int temp = br[j];
br[j] = br[j + 1];
br[j + 1] = temp;
}
}
}
return br[n];
}
主函数:
int main()
{
const int n = 100;
int br[100] = { 0 };
Init_Array(br, n);
Show_Array(br, n);
Bubble_Sort(br, n);
Show_Array(br, n);
}
完整代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<assert.h>
int Init_Array(int* br, const int n)
{
assert(br != 0 || n > 0);
srand(time(NULL));
for (int i = 0; i < n; i++)
{
br[i] = rand() % 100 + 1;
}
return br[n];
}
void Show_Array(const int *br,const int n)
{
assert(br != 0 || n > 0);
for (int i = 0; i < n; i++)
{
printf("%5d", br[i]);
if ((i+1) % 10 == 0)
{
printf("\n");
}
}
printf("\n");
}
int Bubble_Sort(int *br, const int n)
{
assert(br != 0|| n > 0);
for (int i = 0; i < n-1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (br[j] > br[j+1])
{
int temp = br[j];
br[j] = br[j + 1];
br[j + 1] = temp;
}
}
}
return br[n];
}
int main()
{
const int n = 100;
int br[100] = { 0 };
Init_Array(br, n);
Show_Array(br, n);
Bubble_Sort(br, n);
Show_Array(br, n);
}
结果如下所示:
优化
1.冒泡排序的两数交换可以使用交换函数。
void Swap(int* ap, int *bp)
{
int temp = *ap;
*ap = *bp;
*bp = temp;
}
更新冒泡函数Bubble_Sort()
int Bubble_Sort(int *br, const int n)
{
assert(br != 0|| n > 0);
for (int i = 0; i < n-1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (br[j] > br[j+1])
{
Swap(&br[j], &br[j + 1]);
}
}
}
return br[n];
}
2.已经排好序之后跳出循环
我们将随机生成的数限制在10个数。每次跑一趟排序,我们将它打印出来,需要更新的代码如下:
主函数:
int main()
{
const int n = 10;//更新
int br[10] = { 0 };//更新
Init_Array(br, n);
Show_Array(br, n);
Bubble_Sort(br, n);
Show_Array(br, n);
}
冒泡函数:
int Bubble_Sort(int *br, const int n)
{
assert(br != 0|| n > 0);
for (int i = 0; i < n-1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (br[j] > br[j+1])
{
Swap(&br[j], &br[j + 1]);
}
}
Show_Array(br, n);//更新
}
return br[n];
}
运行结果如下:
我们可以看到从倒数第三次开始已经完成排序了,序列已经整体有序,我们还是将它继续循环,继续打印,这样显然没有必要,怎么处理这样的情况:
做法:添加标志位,代码如下:
int Bubble_Sort(int *br, const int n)
{
assert(br != 0|| n > 0);
for (int i = 0; i < n-1; i++)
{
int flag = 0;
for (int j = 0; j < n - i - 1; j++)
{
if (br[j] > br[j+1])
{
Swap(&br[j], &br[j + 1]);
flag = 1;
}
}
if (flag == 0)
{
break;
}
Show_Array(br, n);
}
return br[n];
}
我们在循环开始时初始化一个标志flag,令它等于0,如果在循环过程中比较发现有大的在小的前面,我们就进行交换,并将标志设置成1,表示我此次循环是做了改动的,变换了顺序。
但是,如果我循环一遍我的元素,发现前面都是小的,后面都是大的,前一个都比后一个小,那么说明我不需要交换,说明我的排序结束了,这组数已经有序了,所以我们不改变标志,如果没有进行交换,没有改变标志,那么说明我们排序结束,所以我们使用break退出循环就可以啦!
break可以从当前循环退出。
冒泡排序第一个循环,代表一趟,如果一趟从上到下,再由下至上
更新代码如下:
void Bubble_Sort(int *br, const int n)
{
assert(br != 0|| n > 0);
for (int i = 0; i < n-1; i++)
{
int flag = 0;
for (int j=i ; j <n-1-i;j++ )
{
if (br[j] > br[j+1])
{
Swap(&br[j], &br[j + 1]);
flag = 1;
}
}
if (flag == 0)
{
break;
}
for (int j = n - 2 - i; j > i; j--)
{
if (br[j + 1] < br[j])
{
Swap(&br[j + 1], &br[j]);
}
}
Show_Array(br, n);
}
}
添加反向遍历,实现双向冒泡,可以在每轮排序中更快地将最大/最小元素移到两端,这样的作用是提高运行的效率。使用双冒泡的方法,可以反向检查排序的正确性,优化了代码。
我的问题是为什么int j = n - 2 - i为什么写成n-1-i会内存溢出呀,因为i不能大于n-1,所以最大值是n-2,j=n-2-i,在i取n-2的时候是0,如果你写j=n-1-i那么,n-1-n-2结果为-1,br[-1],会读取数组的前一个地址,造成内存溢出。
3.避免出现重复的数
写一个避免重复的函数
int FindValues(const int* br, const int n, const int value)
{
int pos = -1;
if (*br == NULL || n < 1) return pos;
for (int i = 0; i < n; i++)
{
if (br[i] == value)
{
pos = i;
break;
}
}
return pos;
}
更新初始化的代码:
int Init_Array(int* br, const int n)
{
int i = 0;
assert(br != 0 || n > 0);
srand(time(NULL));
while (i < n)
{
int value = rand() % 100 + 1;
if (FindValues(br, i, value) == -1)
{
br[i] = value;
i+=1;
}
}
return 0;
}
刚才我们FindValues函数是从前往后遍历,现在我们从后往前遍历,简化一下算法:
int FindValues(const int *br, const int n, const int value) {
int pos = n - 1;
while (value != br[pos] && pos >= 0) {
--pos;
}
return pos;
}
此时我们打印一下代码的循环次数,我们可以看到,代码的循环次数远远大于我们想象的100次,越往后,随机值重复的概率越大,所以需要再次循环生成新的数。
int Init_Array(int *br, const int n) {
int i = 0;
int sum = 0;
assert(br != 0 || n > 0);
srand(time(NULL));
while (i < n) {
int value = rand() % 100 + 1;
if (FindValues(br, i, value) == -1) {
br[i] = value;
i += 1;
}
sum += 1;
}
printf("sum = %d\n",sum);
return 0;
}
sum = 408循环408次,效率太低。越往后重复的概率越大,需要循环的次数更多。 想要优化来减少循环次数,推荐查表法。我们定义一个数组,数组下边是从0-10,我们给数组的每个元素存储0元素。
#define _CRT_SECURE_NO_WARNINGS
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void Swap(int *ap, int *bp)
{
int temp = *ap;
*ap = *bp;
*bp = temp;
}
int FindValues(const int *br, const int n, const int value)
{
int pos = n - 1;
while (value != br[pos] && pos >= 0) {
--pos;
}
return pos;
}
int Init_Array(int *br, const int n) {
int i = 0;
int sum = 0;
int *ip;
assert(br != 0 || n > 0);
srand(time(NULL));
ip = (int *)malloc(sizeof(int) * (n + 1));
if(ip == NULL)
return -3;
for (int j = 0; j <= n;++j)
{
ip[j] = 0;
}
while (i < n)
{
int value = rand() % 100 + 1;
if (ip[value]==0)
{
br[i] = value;
ip[value] = 1;
i += 1;
}
sum += 1;
}
printf("sum = %d\n", sum);
return 0;
}
void Show_Array(const int *br, const int n) {
assert(br != 0 || n > 0);
for (int i = 0; i < n; i++) {
printf("%5d", br[i]);
if ((i + 1) % 10 == 0) {
printf("\n");
}
}
printf("\n");
}
void Bubble_Sort(int *br, const int n) {
assert(br != 0 || n > 0);
for (int i = 0; i < n - 1; i++) {
int flag = 0;
for (int j = i; j < n - 1 - i; j++) {
if (br[j] > br[j + 1]) {
Swap(&br[j], &br[j + 1]);
flag = 1;
}
}
for (int j = n - 2 - i; j >= i; j--) {
if (br[j + 1] < br[j]) {
Swap(&br[j + 1], &br[j]);
flag = 1;
}
}
if (flag == 0) {
break;
}
// Show_Array(br, n);
}
}
int main() {
const int n = 100;
int br[n];
Init_Array(br, n);
Show_Array(br, n);
Bubble_Sort(br, n);
Show_Array(br, n);
}
查表法代码有一点提升,但是总体不是很大,后续这个题目会继续被优化。
连续空间的两种获取方式
动态和静态
泛型指针void*
void*:泛型指针,是任何类型的地址都可以存放。
示例如下:
int main()
{
int a = 10;
char ch = 'a';
double dx = 12.23;
float ft = 12.33f;
void *vp = NULL;
vp = &a;
vp = &ch;
vp = &dx;
vp = &ft;
}
需要注意的是,泛型指针可以直接获取其他任何类型的地址;
但是其他类型的地址获取泛型指针的地址需要加上地址转换,示例代码如下:
int main()
{
void *vp;
int *ip;
ip = (int *)vp;
}
malloc动态生成数组
在VS2019中,不能够动态的定义数组,定义数组的时候不能够使用变量,使用常变量也不可以呢,因为常变量是在链接过程中才将变量值替换,所以我们需要动态生成数组。动态生成数组的方法是:malloc(动态内存管理)。
malloc和calloc的区别
malloc:分配内存
calloc:分配并清零内存
malloc申请内存的时候要释放内存,free只可以释放堆区自己申请的内存空间。
int main()
{
int *ip = NULL;
ip = (int *)malloc(sizeof(int));
if(NULL == ip)
exit(1);
*ip = 100;
printf("%08x =>%d\n", ip,*ip);
free(ip);
ip = NULL;
return 0;
}
结果代码如下:
上述代码是给ip指针变量在堆空间生成了一个4个字节大小的空间。
注意:malloc使用结束后一定要free,另外还需要将指针置为空,要不然还是有各种风险。
野指针:定义指针没有初始值:int *p;
空指针,int* ip = NULL
失效指针,free(ip)
失效指针之后一定要置为空ip=NULL
作业
free怎么知道要释放多少空间?
malloc在申请空间的时候会将申请的空间大小和地址存储在运行时库里,被称作内存控制块,当你使用free函数释放的时候,会自动调用内存控制块,释放对应字节个数的内存。