从零开始的数据结构
由于笔者c语言没有学好,于是选择复习一遍指针,结构体,链表等,本期复习动态内存分配和结构体。
本篇文章提取改编自参考视频
参考视频 浙江大学翁恺老师c语言
动态内存分配复习
由于C99之后可以用变量做数组定义的大小,不再讨论
本篇讨论的是C99之前的操作,重在原理理解
int *a = (int*)malloc(n*sizeof(int));
malloc()括号中要的参数不是多少个单元,而是多少个空间,是以字节为单位的。因此是n*sizeof(int)
//#include <stdlib.h>部分底层代码
void *
malloc(size_t size);//这里只需要知道malloc返回的是void *
由于malloc()返回的结果是void*
因此一般要在前面进行一个类型转化
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int number;
int *a;
int i;
printf("输入数量");
scanf("%d",&number);
a = (int*)malloc(number*sizeof(int));
for(i=0;i<number;i++){ //指针其实就是数组,所以拿a当数组用
scanf("%d",&a[i]);
}
free(a); //malloc要了空间以后,注意“还”空间
return 0;
}
注意malloc要了内存后,只是一整块内存。计算机自己不能分辨出是什么类型的内存,是什么类型的内存是我们自己决定的(类型转化决定的)
malloc如果申请失败则返回0或者NULL
能分配多少空间是系统决定的
free()
把申请来的空间“还”给系统
申请过的空间,最终都应该要还
只能还申请来的空间的首地址,即原始的地址
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
void *p;
p = malloc(100);
p++;
free(p);
return 0;
}
这里有个疑问,在看翁恺老师的课时,他说这段代码会提示报错“pointer being was not allocted”
但用我使用CodeBlocks编译运行并无此报错,于是问了我的专业老师,解读为:目前的系统都放宽了这个限制,不提示错误。但是如果p后移了,应该是只释放该地址之后的空间。释放不完全会造成泄露。所以程序员要自己注意保存好空间起始地址,完整释放。
free(NULL) 或者说 free(0) 是没有问题的,这里有个好习惯,任何指针在定义时赋初值为0,原因是这样如果malloc失败后,free§是没有问题的。
结构体复习
声明结构的形式
//形式1
struct point{
int x;
int y;
};
struct point p1,p2;
p1 和 p2都是point,里面有x和y的值
//形式2————无名结构
struct{
int x;
int y;
}p1,p2
p1和p2是一种无名结构,里面有x和y
//形式3
struct point{
int x;
int y;
}p1,p2;
p1和p2都是point,里面有x和y的值
对与第一和第三种形式,都声明了结构point
第二种没有声明point,只是定义了两个变量
#include<stdio.h>
struct date{
int month;
int day;
int year;
}; //这里注意末尾的;
int main(int argc,char const *argv[])
{
struct date today; //名字是today,类型是struck date
today.month = 9;
today.day = 28;
today.year = 2023;
printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day);
return 0;
}
和本地变量一致,在函数内部声明的结构类型只能在函数内部使用,所以通常在函数外部声明结构类型,这样就可以被多个函数所使用
结构的初始化
结构用.运算符和名字访问其成员
today.day;
p1.x;
p1.y
初始化
struct date today = {9,28,2023}; //对应定义结构时的顺序赋值
struct date thismonth = {.month=9, .year=2023}; //不给初值,会根据默认规则填入,这里int类型是0(跟数组的规则类似)
结构运算
要访问整个结构,直接用结构变量的名字
对于整个结构,可以做赋值,取地址,也可以传递给函数参数
p1 = (struct point){5,10};
/*相当于 p1.x = 5;
p1.y = 10; */
p1 = p2;
/*相当于p1.x = p2.x;
p1.y = p2.y; */
//数组无法做这两种运算
结构指针
和数组不同,结构变量的名字并不是结构变量的地址,必须用&运算符
struct date *pDate = &today;
结构作为函数参数
int numberofDays(struct date d);
整个结构可以作为参数的值传入函数
这时候是在函数内新建一个结构变量,并复制调用者的结构的值
也可以返回一个结构
这与数组完全不同
输入结构
没有直接的方法可以一次scanf一个结构
如果想写一个函数来读入结构,如下
#include<stdio.h>
struct point{
int x;
int y;
};
void getStruct(struct point);
void output(struct point);
void main()
{
struct point y = {0,0};
getStruct(y);
output(y);
}
void getStruct(struct point p)
{
scanf("%d",&p.x);
scanf("%d",&p.y);
printf("%d,%d",p.x,p.y);
}//结果为自己scanf的两个数
void output(struct point p)
{
printf("%d,%d",p.x,p.y);
} //结果为0,0
C在函数调用时是传值的
所以函数中的p和main中的y是不同的
在函数读入了p的数值后,没有任何东西返回到main,所以y还是{0,0}
解决方案
之前的方案问题在于传入函数的是外面那个结构的克隆体,而不是指针(传入结构和传入数组是不同的)
可以在这个输入函数中,创建一个临时的结构变量,然后把这个结构返回给调用者
#include<stdio.h>
struct point{
int x;
int y;
};
struct point getStruct(void);
void output(struct point);
int main(int argc,char const*argv[])
{
struct point y = {0,0};
y = getStruct();
output(y);
}
struct point getStruct(void)
{
struct point p;
scanf("%d",&p.x);
scanf("%d",&p.y);
printf("%d,%d",p.x,p.y);
return p;
}
void output(struct point p)
{
printf("%d,%d",p.x,p.y);
}
但通常,结构指针作为参数(传指针)是更好的方式
指向结构的指针
用 -> 表示指针所指的结构变量中的成员
struct date{
int month;
int day;
int year;
}myday;
struct date *p=&myday;
(*P).month = 12;
p->month = 12; //这两种写法表示的含义一致
结构数组
struct date dates[100]; //这个数组的每一个元素是一个date的结构变量
struct date dates[]={{4,5,2005},{2,4,2005}} //这是dates[0]和dates[1]的初值
结构中的结构
结构里的变量也可以是结构
struct dateAndTime{
struct date sdate; //第一个成员变量是结构date
struct time stime; //第二个成员变量是结构time
};
struct point{
int x;
int y;
};
struct rectangle{
struct point pt1;
struct point pt2;
};
/*如果有变量
struct rectangle r;
就可以有:
r.pt1.x r.pt1.y 对于第一个 . 和第二个 . 它们的左边都是结构
r.pt2.x r.pt.y */
//如果有变量定义
struct rectangle r,*rp;
rp = &r; // 定义了一个指针指向r
//那么下面的四种形式是等价的:
r.pt1.x;
rp->pt1.x;
(r.pt1).x;
(rp->pt1).x;
//但是没有rp->pt1->x(因为pt1不是指针)