预先知识:(C语言)
1、指针
地址:内存单元的编号
指针:指针就是地址,地址就是指针
指针变量:就是一个变量,这个变量存储了一个非负整数,即存储了内存单元的编号的变量,所有指针变量只占4个字节(32位机器来说)
基本类型的指针
指针和数组
#include <stdio.h>
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
/* int *p; p = a
数组名也是一个变量,一个指向数组第一个元素的指针变量,存放了第一个元素的内存空间地址
将数组名给指针变量p,意味着p指向数组的第一个元素(真正指向第一个元素的第一个字节单元,因为这是个整型指针变量,所以可以理解为指向第一个元素)
a = p => 第一个元素的内存空间地址
所以 *p就是第一个元素的内存空间 *(p+1) 是第二个元素的内存空间,因为p = a,所以a[0] = p[0]
*/
printf("p = %p\n", p);
printf("*p = %d\n", *p);
printf("p[0] = %d\n", p[0]);
printf("*(p+1) = %d\n", *(p+1));
printf("*p+1 = %d\n", (*p + 1) );
return 0;
}
传递给被调函数的实参,在被调函数中修改后,在主调函数中能响应到 ,这里可以说是主调函数中的静态内存在被调函数中进行了修改,关键在于:在主调函数中的实参是否想要在被调函数中被修改,如果想被修改,就需要传实参地址给被调函数
#include <stdio.h>
void func(int *);
int main(void)
{
int p = 1;
func(&p);
/* 主调函数中要使用取地址符&来获取整型p(对4个字节的内存空间命名为p,没有其他实际意义)的第一个字节地址,传递到func中
*/
printf("p = %d\n",p);
}
void func(int *pt)
{
*pt = 10;
/* pt是一个整型指针变量,虽然它指向p的第一个字节地址 ,但对剩余的3个字节也有访问权限, 所以*pt应该是代表了4个字节单元空间 即*pt = p,这样对内存进行操作了,主调函数中的变量自然就会被改变
*/
}
2、结构体
1、定义的末位分号不能省
2、数据类型是 【struct 名称】整体是一个数据类型
3、‘.’成员操作符,结构体变量.成员;‘->’成员操作符,结构体指针变量->成员
4、与基本数据类型不同,不能算术运算和逻辑运算,允许赋值操作
#include <stdio.h>
struct ST1
{
char name[200] ;
int age;
}; //末位的;不能省略
int main(void)
{
struct ST1 st = {"zhangsan", 20};
struct ST1 st2 = {"wangwu", 22};
struct ST1 *sp ;
sp = &st;
//定义结构体变量,使用'.'成员操作符获取结构体的成员变量值
printf("name = %s\n", st.name);
printf("age = %d\n", st.age);
//定义结构体指针变量,使用'->'成员操作符获取结构体的成员变量值
printf("name = %s\n", sp->name);
printf("age = %d\n", sp->age);
/*
sp = &st,sp保存了结构体变量st的地址,这个地址是结构体的第一个字节的内存空间的地址
*sp 就是结构体的字节空间(准确来讲是结构体第一个字节空间,因为sp是这个结构体的指针,所以后面空间也能被方访问),所以 *sp = st ===> (*sp).name = st.name ,为了书写方便,
就把(*sp).name 替换成了 sp->name,这个是C语言开发人员人为约定的。
*/
// st + st2 这个操作是不允许的,同样减法,乘法,除法都不能操作,只允许赋值
printf("name = %s\n", st2.name);
printf("age = %d\n", st2.age);
st2 = st;
printf("name = %s\n", st2.name);
printf("age = %d\n", st2.age);
return 0;
}
3、动态内存分配和释放
静态内存分配:由申明好的数据类型,系统自动分配指定好的类型内存分配数量,这个可以在被调函数中修改它的内存值
malloc() free()
跨函数使用内存,只能通过动态分配内存才能保证内存被函数外使用,什么时候释放,取决于什么时候使用free函数
在被调用函数中国动态分配内存,供主调函数进行使用和修改
#include <stdio.h>
#include <malloc.h>
int declare_memory(int *p , int memory_size);
int main(void)
{
int * p; //这个整型指针用于指向申请的内存空间的地址
/*
使用&为了能够在主调函数中响应修改后实参的值
p是 int *类型
&p 就是 int **类型了,所以被调函数中形参的类型也要对应
如果使用declare_memory(p, 12),而被调函数的形参使用int *类型,这样和一般函数的实参修改 一样,不能将被调函数修改后的值传递到主调函数中了
*/
if(!declare_memory(&p, 12))
{
printf("内存申请失败");
return 0;
}
//在这里释放declare_memory函数申请的内存空间,如果一直不释放,要么等到main函数执行结束,要 么就会内存泄漏,
free(p);
return 0;
}
int declare_memory(int **p , int memory_size)
{
// 这里的*p == 主调函数中的p了
*p = (int *)malloc(sizeof(int)*memory_size);
if(*p == NULL)
{
return 0;
}
return 1;
}
数据结构:
将现实中大量而复杂的问题转换为数据,保存到内存中,这个就是数据结构,以及在此基础上实现某些功能而执行的相应的操作,这个操作就是算法
算法:
狭义的算法与数据的存取方式密切相关
广义的算法与数据的存储无关
泛型:利用某种技术达到这个效果(不同的数据存储方式,执行的操作是一样的)
时间复杂度和空间复杂度
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言
1、线性结构:第一个元素只有后继元素,最后一个元素只有前驱元素,其余元素都有前驱元素和后继元素
基本操作:
1、初始化 int init(&L)
2、销毁 int destroy(&L)
3、重置 int clear(&L)
4、判空 bool isEmpty(&L)
5、获取已存元素数量 void listLen(&L,int * len)
6、获取指定索引的元素 int getElem(&L, int index,ElemType *e)
7、获取给定值并满足给定关系的第一个元素(这里的一个形参是函数类型) int locateElem(&L, ElemType elem, (int)(*comp)(ElemType , ElemType) )
8、获取一个元素的前驱 int getPriorElem(&L, ElemType elem, ElemType *pre_)
9、获取一个元素的后继 int getNextElem(&L, ElemType elem , ElemType *next_)
10、插入一个元素 int insertElem(&L, int index, ElemType elem)
11、删除一个元素 int deleteElem(&L, int index, ElemType *del_elem)
12、根据给定关系改变元素的值(这里同样有一个形参是函数类型)int traverse(&L, (void)(*update)(ElemType *))
连续存储:数组 ,操作代码
首节点(第一个存有有效元素的结点),尾节点(最后一个存有有效元素的结点),头结点(首结点前的一个结点),头指针(指向头结点的指针),尾指针(指向尾结点的指针)
线性结构的常见应用:栈和队列
结构如下,
typedef int ElemType;
typedef struct Node
{
ElemType data;
struct Node *next;
}Node,*PNode;
typedef struct stack
{
PNode base; //栈基地址,栈底指针
PNode pop; //栈顶指针
int len; //栈长度,方便操作
}STACK;
typedef struct queue
{
PNode front;
PNode rear;
}QUEUE;
栈:只允许在栈顶插入元素和删除元素 ,链栈代码,顺序栈代码(待更新)
队列:只允许在队列头删除元素,在队列尾插入元素,静态队列代码(待更新),链式队列(待更新)
静态队列一般是循环队列,容易操作,入队就把队尾向上加,出队把队头向上加,如果队头或队尾到达空间上限,就置0,一是为了不浪费空间,二是不用将队列元素整体向上或向下移动
链式队列就和一般链表操作相同,只是只能在head结点加入,在tail结点删除
链式结构和顺序结构除了操作细节不一样之外,广义上说是完全一样的结构类型
2、非线性结构
树
二叉数:前序遍历(根,左,右),中序遍历(左,根,右),后序遍历(左,右,根),层次遍历(从根向下,一层一层的,每层从左到右)
图
3、查找和排序
折半查找
排序:
冒泡
插入
选择
归并
快速