1.动态内存分配
1.1什么是动态分配内存
c语言要求在编译时指针数组元素的个数。但是我们往往做不到,可能导致程序的失败或空间的浪费。很多编程语言允许程序员在运行时指定数组的大小,即能够在运行时计算并分配程序中变量所需的内存空间。在运行时分配内存空间的过程就称为动态内存分配。尽管c语言本身不具有这种能力,但是它有4个名为“内存管理函数”的库例程,可以用来在程序运行时分配和释放内存。
以下是与c语言有关的内存分配过程。(c程序的存储)。
程序指令,全局变量,静态变量存储在永久内存区域中。局部变量储存在栈(stack)中。位于这两个区之间的内存空间可以用于程序运行时的动态分配,这些内存区称为堆(heap)。当程序运行时,堆的大小是不断变化的,因为会发生函数或代码块的局部变量的创建和销毁。因此在动态分配过程中,有可能会遇到内存的溢出。这种情况下,上面介绍的函数将返回空指针(当它们不能分配所请求的足够空间时)。
1.2用malloc函数分配一块内存
利用malloc函数可以分配一块内存。malloc函数将保留指定大小的内存块,并返回void类型的指针。(这意味着可以把它赋给任意类型指针)
一般形式:ptr=(cast-type *)malloc(byte-size);
ptr为cast-type类型的指针。malloc函数返回一个指向大小为byte-size的内存区的指针(类型为cast-type)。
malloc函数分配的是连续的字节块。如果堆空间不能满足要求,分配失败,返回NULL。因此应检查内存是否分配成功。
1.3malloc演示
/**
*malloc函数使用
*使用一个整数表,其大小在运行时交互地指定。
*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p, *table;
int size;
printf("表的长度是多少?\n");
scanf("%d", &size);
printf("\n");
/*内存分配*/
if ((table = (int *)malloc(sizeof(int))) == NULL)
{
printf("没有可用空间\n");
exit(1);
}
printf("\n第一个字节的地址是%u\n", table);
printf("\n输入表的数据\n");
for (p = table; p < table + size; p++)
{
scanf("%d", p);
}
for (p = table + size - 1; p >= table; p--)
{
printf("%d储存在地址%u中\n", *p, p);
}
system("pause");
return 0;
}
1.4用calloc函数分配多个内存块
calloc函数也是一个内存分配函数,通常用于在运行时为了存储派生数据类型(入数组和结构体)而分配所需的内存空间。malloc函数分配的是单个内存块,calloc函数分配的是多个内存块,每个块大小相等,并把所有字节都设为零。
calloc的一半形式:ptr=(cast type *)calloc(n,elem-size);
上述语句分配n块连续的内存空间,每块的大小为elem-size个字节。所有字节初始化为零,并返回一个指针,指向所分配区域的第一个字节。如果没有足够的空间,就返回一个空指针。
#include <stdio.h>
#include <stdlib.h>
struct student
{
char name[30];
float age;
long int id_num;
};
typedef struct student record;
record *st_ptr;
int class_size = 30;
// calloc函数分配了用于保存30条记录数据的存储空间
st_ptr = (record *)calloc(class_size, sizeof(record));
// 检验内存是否已经分配
if (st_ptr == NULL)
{
printf("可用内存不足");
exit(1);
}
1.5用free函数释放已用的空间
当不在需要保存在内存块中的数据,且不打算再使用这块内存,可用free函数来释放掉该内存块。
一般形式:free(ptr);
ptr为指向内存块的指针,所释放的不是指针本身,而是指向的内容。
1.6用realloc函数改变内存块的大小
在分配内存后,有可能发现分配的内存不够。或者,已分配的比需要的大,想要减少内存。都可以利用realloc函数来改变已分配内存的大小。如下:
ptr=malloc(size);
ptr=realloc(ptr,newsize);
该函数把大小为newsize的新内存空间分配给指针ptr,并返回一个指向新内存块的第一个字节的指针。newsize可以比size更大或更小。记住!新内存块的开始位置可以与旧的相同。如果在相同的区域找不到其他的内存空间,就将在全新的区域中创建,旧内存块的内容将移到新块中。该函数保证旧数据的完整性。
1.7realloc演示
/**
* 把一个字符串保存在由malloc创建的内存块中,然后修改它以存储更大的字符串
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *buffer;
if ((buffer = (char *)malloc(10)) == NULL)
{
printf("失败");
exit(1);
}
printf("创建大小为:%d的缓冲区\n", _msize(buffer)); //_msize 函数可以用于获取之前使用 malloc 函数分配的堆空间的大小
strcpy(buffer, "CHINABYDP");
printf("\nBuffer:%s\n", buffer);
if ((buffer = (char *)realloc(buffer, 15)) == NULL)
{
printf("失败");
exit(1);
}
printf("修改缓冲区大小\n");
printf("缓冲区仍然包含:%s\n", buffer);
strcpy(buffer, "HELLOWORLD");
printf("\n缓冲区现在包含:%s\n", buffer);
free(buffer);
system("pause");
return 0;
}
2.链表
2.1链表的概念
大家都知道,列表是按序组成的项集。数组就是一种列表。在数组中,元素的顺序是由索引隐式地给定的。可以使用索引来访问和操作数组元素。但是数组的大小必须在开始时就给定。这在很多实际应用中是件困难的事情。
一种完全不同的列表表示方法是用结构体表示一个列表成员,它含有指向下一个结构体成员的“链接”。称为链表(linked list)。
链表的每个结构体称为节点(node),它由两个字段组成:一个包含数据项,一个包含指向链表中下一个数据项的地址(也就是指向下一个数据项的指针)。因此链表是结构体的集合。
通常如下表示:
struct node{
int item;
struct node *next;
};
结构体可以包含不同数据类型的多项,但是必须有一项是结构体指针。
/**
* 简单示例:链表的链接
*/
#include <stdio.h>
struct link_list
{
float age;
struct link_list *next;
};
int main()
{
struct link_list node1, node2;
node1.next = &node2; // 建立链接
node1.age = 35.0;
node2.age = 49.0;
node2.next = 0; // 链表的结尾
printf("%f", node1.next->age); // 可以利用node1的next成员来访问node2的age成员。
return 0;
}
2.2链表的优点
主要优点:其大小可以在程序运行时增大或缩小,长度可以按需求决定。其次链表不会浪费内存空间,任何时候链表所使用的内存就是它所需要的。更为重要的是,链表的灵活性允许高效地重排数据项,可以很容易的插入,删除数据项。
主要局限:访问任意数据时有些费时,无论何时只要列表的长度固定,最好使用数组而不是链表。对于相同数量的数据项,链表所使用的内存空间比数组更多,因为在链表中,每个数据项还包含额外的链接字段(指针)。
链表也分几种,如下图所示:
2.3创建链表
可以把链表看成一种抽象数据类型,可以执行以下基本操作:
创建链表,遍历链表,计算链表中数据项的数目,显示链表,查找某项数据(用于编辑或显示),插入数据项,删除数据项,连接两个链表。
我们写好了结构体只是描述了结点格式,没有分配存储空间,并且上面我们手动的创建结点node1,node2等。尽管我们可以继续添加,但是这种方法十分愚蠢。使用递归与迭代技术可以很容易实现。如下语句就可以将指针从当前结点移到下一个结点:
head=head->next;
2.4创建链表演示
/**
* 创建完整的链表,并利用递归显示其内容
*/
#include <stdio.h>
#include <stdlib.h>
struct linked_list
{
int number;
struct linked_list *next;
};
typedef struct linked_list node;
int main()
{
node *head;
void create(node * p);
int count(node * p);
void print(node * p);
head = (node *)malloc(sizeof(node));//此时才为结点创建了储存空间
create(head);
printf("\n");
print(head);
printf("\n");
printf("\n数据个数: %d\n", count(head));
system("pause");
return 0;
}
void create(node *list)
{
printf("\n输入一个数据项数据(输入-1停止):");
scanf("%d", &list->number);
if (list->number == -1)
{
list->next = NULL;
}
else
{
list->next = (node *)malloc(sizeof(node));
create(list->next);
}
return;
}
void print(node *list)
{
if (list->next != NULL)
{
printf("%d-->", list->number);
if (list->next->next == NULL)
{
printf("%d", list->next->number);
}
print(list->next);
}
return;
}
int count(node *list)
{
if (list->next == NULL)
{
return 0;
}
else
{
return (1 + count(list->next));
}
}
创建了一个链表,利用递归来显示内容。
2.5插入一个数据项
插入之前要先检查插入的位置。一般要定位数据项将插入哪个结点后面。插入的一般算法如下:
开始
if 链表为空,或
新结点位于头节点之前,then
插入新节点作为头节点
else
if新节点位于尾节点之后,then
插入新节点作为尾节点
else
把新节点插入链表中
结束
把新数据项置于链表开头的算法如下:
1.为新节点分配空间
2.把数据赋给新节点的数据项字段
3.把新节点的next字段指向链表的开头
4.把head指针修改为指向新节点
把新节点X插入当两个已有节点N1,N2之间的算法如下:
1.为新节点分配空间
2.把值赋给x的数据项字段
3.把x的next指向N2
4.把N1的next字段指向节点X
插入到尾部算法类似,只不过next字段被置为NULL。