C语言入门——第十一课

一、第十课作业复盘

题目:定义大小为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函数释放的时候,会自动调用内存控制块,释放对应字节个数的内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值