C enhance Day(一)

一. 如何看懂带算法的程序

  1. 流程
  2. 每个语句的功能
  3. 试数
  4. 调试
  5. 模仿改
  6. 不看代码写

1.1 选择排序

选择排序的原理:a[0]先和a[1]进行比较,如果a[0]大于a[1],就交换两个的位置,此时的a[0]是相对较小的那个,然后继续a[0]和a[2]进行比较,如果a[0]大于a[2]就交换两个的位置,依次类推,进行一轮比较之后,最小的数就到了a[0]的位置。然后进行第二轮,将a[1]与a[2]进行比较,如果a[1]大于a[2]就交换两个的位置,依次类推。

int main(void)
{
	int a[] = { 1,3,5,7,9,2,4,6,8,10 };
	int i = 0;
	int j = 0;
	int tmp = 0;
	int n = sizeof(a) / sizeof(a[0]);

	printf("排序前:");
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	//注意此处i的条件,一共10个数,但是只比较9次,所以这里是n-1,且不取等号,0-8为9次。
	for (i = 0; i < n - 1; i++)
	{
		for (j = i+1; j < n; j++)
		{
			if (a[i] > a[j])
			{
				tmp = a[i];
				a[i] = a[j];
				a[j] = tmp;
			}
		}
	}

	printf("排序后:");
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

1.2 冒泡排序

冒泡排序原理:在一个内循环里,a[0]和a[1]进行比较,如果a[0]小于a[1],就交换,然后比较a[1]和a[2],如果a[1]小于a[2],继续交换,依此循环下去,数组的最后一个元素就是最小值,在下一轮大循环中不用参加比较,下一轮循环中依旧是a[0]和a[1]比较,如果a[0]小于a[1],就交换两个位置,依此类推就完成降序排列。

int a[] = { 1,3,5,7,9,2,4,6,8,10 };
	int i,j;
	int temp;
	int n = sizeof(a) / sizeof(a[0]);

	printf("排序前:");
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	for(i = 0;i < n-1;i++)
		for (j = 0; j < n - i - 1; j++)
		{
			if (a[j] < a[j + 1])
			{
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}

	printf("排序后:");
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;

二. 函数封装和数组退化为指针

如果数组作为函数参数,数组形参退化为指针,如以下三种情况的定义时等价的:
void printf_array(int a[1], int n)
void printf_array(int a[],int n)
void printf_array(int *a, int n)
在此处调用printf_array时打印n的值为1,因为当a做指针用,是指针类型,32位,长度为4个字节,算出的sizeo(a)为4

void printf_array(int *a,int n)
{
	//当a做指针用,指针类型,32位,长度为4个字节
	n = sizeof(a) / sizeof(a[0]); 
	
	int i;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

三. 内存四区基础

3.1 数据类型的本质

  • 数据类型可以理解为创造变量的模具:是固定内存大小的别名
  • 数据类型的作用:编译器预算对象(变量)分配的内存空间大小
  • 注意:数据类型只是模具,编译器没有分配空间,只有根据类型(模具)创造变量(实物),编译器才会分配空间。

3.2 数据类型长度的测试

定义如下两个数据:

int a;
int b[10];

测试以下两种:

// 数组名字,数组首元素地址,数组首地址 相同
printf("b:%d, b:%d\n", b, &b);
printf("b+1:%d, b+1:%d\n", b+1, &b+1);

输出结果如下:

在这里插入图片描述
分析:

  • 对于第一行的打印结果,因为数组名字,数组首元素地址,数组首地址是相等的
  • 对于第二行的打印结果,验证了数据类型的本质,因为b和&b的数据类型不一样
    对于b,数组首元素地址,一个元素4字节,+1的时候等于+4
    对于&b,整个数组的首地址,一个数组4*10 = 40字节,+1的时候等于+40

3.3 void 无类型

在这里插入图片描述

3.4 变量的本质

  • 一段连续内存空间的别名
  • 变量相当于门牌号,内存相当于房间

四. 上午阶段总结

4.1 听课标准

  1. 会选择排序法
  2. 会简单封装函数,如上面的printf_array
  3. 数组做函数参数会退化为一级指针,思考一下为什么会退化为指针,主要是为了效率,因为每次调用函数时就把字符串的所有内容都拷贝一份放进去,这个消耗的空间是比较大的,所以退化为指针,每次只用传入数组的首地址即可
    在这里插入图片描述

4.2 数据类型

在这里插入图片描述

4.3 拓展部分

在这里插入图片描述

五. 内存四区实体

5.1 内存四区模型

在这里插入图片描述
1、堆区里面的内容手动分配,手动释放
2、栈区里面的内容,系统来分配空间,自动分配,自动回收
3、全局区包含全局变量,静态变量和文字常量区,细分为未初始化的变量,初始化的变量和文字常量区

在这里插入图片描述

5.2 全局区分析

char * get_str1()
{
	char *p = "abcdef"; //文字常量区
	
	return p;
}

char * get_str2()
{
	char *q = "abcdef"; //文字常量区
	
	return q;
}

int main(void)
{
	char *p = NULL;
	char *q = NULL;
	p = get_str1();
	//%s:指针指向内存区域的内容
	//%d:打印p本身的值
	printf("p = %s, p = %d",p,p);

	q = get_str1();	
	printf("q = %s, q = %d",q,q);
}

分析以上代码,首先明确:

  • %s 打印的是指针指向内存区域的内容
  • %d 打印p本身的值
  • 文字常量区的内存只有在程序结束后才释放

输出结果如下,因为字符串常量就是地址的值,所以对于打印字符串本身的值,他们如果是相同的字符串,地址就是一样的:

在这里插入图片描述
内存结构图如下

在这里插入图片描述

5.3 栈区分析

char * get_str()
{
	char str[] = "abcdedsgaas"; //栈区
	return str;
}

int main(void)
{
	char buf[128] = { 0 };
	
	strcpy(buf, get_str());
	printf("buf = %s\n", buf);  //乱码,不确定
}

上述代码是一个错误的代码,因为char str[] = "abcdedsgaas"的变量是分配在栈区,函数结束之后内存自动释放。具体操作是,操作系统先在栈区分配str变量的内存,"abcdedsgaas"首先实际是在文字常量区,但是因为定义的变量是str数组类型,操作系统会把文字常量区里的字符做一份拷贝,放在栈区的str数组变量里面。当get_str()运行完毕,str空间自动回收,str的空间内容未知,有可能还保留着之前的内容,有可能是乱码。

内存结构图如下:
在这里插入图片描述
上述代码打印出来的结果还是正确的字符串,是因为程序在完成strcpy拷贝后才释放栈区变量的内存,但是最好不要这么做,因为可能只有这一个编译器是先拷贝完再释放内存的,不能确保其他编译器也是这么操作的,这样可能使代码报出莫名其妙的错误,所以为了更好的验证栈区变量在调用完是会被释放的,采用以下代码验证:

char * get_str()
{
	char str[] = "abcdedsgaas"; //栈区
	printf("%s",str)
	return str;
}

int main(void)
{
	char buf[128] = { 0 };
	
	char *p = NULL;
	p = get_str();
	printf("p = %s\n",p);
}

结果如下:
在这里插入图片描述
由此见得,在调用函数时,它本身是含有内容的,但是函数调用结束后,内存被释放,打印里面的内容显示的是乱码。

这种情况下的结构图:

在这里插入图片描述

5.4 堆区分析

char * get_str2()
{
	char *tmp = (char *)malloc(100);
	if(tmp == NULL)
	{
		return NULL;
	}

	strcpy(tmp, "adafsagasgg");

	return tmp;
}

int main(void)
{
	char buf[128] = {0};
	
	char *p = NULL;
	p = get_str2();
	if(p != NULL)
	{
		printf("p = %s"\n, p);

		free(p);
		p = NULL;
	}
}

结构图如下:
在这里插入图片描述
分析过程:首先是在栈区定义一个变量p,然后调用函数时,在栈区定义一个tmp变量,在函数里面,字符串本来是在文字常量区,通过strcpy对字符串进行了一份拷贝,放在了堆区,然后将堆区字符串的地址传给了变量tmp,此时tmp是指向堆区的字符串的,然后函数将地址的值传给了p变量来接收,此时函数结束,给tmp分配的内存空间被回收,此时黑色箭头的指向消失,我们能够通过p的指向来打印地址,使用完堆区空间后free(p)的意思是消除红色这个箭头,让p不能去操作这个空间,但实际上p变量里面存的还是堆区字符串的地址,只是操作系统不允许p来使用堆区里的内容了,所以一般习惯会在free(p)之后给p赋值为NULL

5.5 栈的生长方向与内存的存放方向

在这里插入图片描述
对于栈的生长方向如图所示,上面是高地址,下面是低地址,然后最先定义的变量在高地址的位置,往下继续定义的变量的地址是减小的,比如先定义的a,然后定义的b,打印出b的地址是小于a的地址的,所以栈的生长方向向下,如果是对于数组的话,buf存的是数组的首地址,在数组内部从第一个元素往上走,地址是增加的,比如打印buf+1的地址是大于buf的地址的。这个自己理解一下即可。同时对于堆区的生长方向是向上的,与栈区相反,与数组内部的生长方向相同。

六. 指针强化

指针也是一种数据类型,数据类型的本质是固定内存块大小的别名,指针变量占4个字节
在这里插入图片描述

七. 思考

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

发布了22 篇原创文章 · 获赞 5 · 访问量 470

猜你喜欢

转载自blog.csdn.net/RedValkyrie/article/details/105301936
今日推荐