一. 如何看懂带算法的程序
- 流程
- 每个语句的功能
- 试数
- 调试
- 模仿改
- 不看代码写
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 听课标准
- 会选择排序法
- 会简单封装函数,如上面的printf_array
- 数组做函数参数会退化为一级指针,思考一下为什么会退化为指针,主要是为了效率,因为每次调用函数时就把字符串的所有内容都拷贝一份放进去,这个消耗的空间是比较大的,所以退化为指针,每次只用传入数组的首地址即可
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个字节
七. 思考