目录
4.1 基于链表实现原理、栈与队列的特性进行链式存储结构的函数抽象
第一章 结构体
1.1 结构体基本概念与类型定义方式
结构体是一种数据类型(扩展类型)
1.1.1 结构体定义
应用场景:单个信息是一组不同类型的信息的组合
#define MAN 'm'
#define WOMEN 'W'
typedef enum card {SFZ,SXZ,JSZ} CARD;
/*定义一个结构体
创建结构体变量*/
struct stu_info {
unsigned stu_num; //学号
CARD cardType; //证件类型
char cardNum[20]; //证件号
char name[21];
char sex; //性别
}s1,s2;
struct 和 enum 一样,都是关键字
- 标准结构体:标准情况“struct 结构体名 { 数据类型…… }结构体变量名;”
- 匿名结构体:锁定变量变量个数“struct { 数据类型…… }结构体变量名;”即没有结构体名
/*创建结构体变量并赋初值*/
struct stu_info s3 = {03,SFZ,12345,"abc",MAN}, s4;
/*存取结构体成员值 用.操作符*/
s1.stu_num = 01;
s1.cardType = SFZ;
//s1.cardNum = "123";
strcpy(s1.cardNum, "123");
strcpy(s1.name, "bca");
s1.sex = MAN;
常见错误:
数组名是地址常量,字符串是字符串常量(创建在常量区)
//键盘输入赋值
scanf("%d%d%s%s %c", &s2.stu_num, &s2.cardType, &s2.cardNum, &s2.name, &s2.sex);
枚举类型的底层还是Int
1.1.2 用typedef简化结构体命名
typedef struct book {
int year;
double price;
char author[20];
char bookName[50];
}BOOK;
1.2 结构变量·结构数组·结构指针的运用
1.2.1 结构体数组
/*创建结构体数组变量*/
BOOK books[4] = { {1968,5.2,"余华","活着"},
{1968,6.8,"孙子" ,"孙子兵法"},
{2010 ,53.8 ,"刘慈欣" ,"三体"},
{1968,19.8,"毛泽东","毛选"} };
int len = sizeof(books) / sizeof(BOOK);
for(int i=0;i<len-1;i++)
for(int j=i+1;j<len;j++)
if (books[i].year > books[j].year || books[i].year == books[j].year && books[i].price < books[j].price) {
BOOK temp = books[i];
books[i] = books[j];
books[j] = temp;
}
for (int i = 0; i < len; i++) {
printf("%-6d %7.2lf %10s %10s\n", books[i].year, books[i].price, books[i].author, books[i].bookName);
}
1.2.2 结构体指针
int * p; 基本类型指针
int (*p)[5]; 数组 指针
int * p[5]; 指针 数组
三种定义结构体指针变量的方式
typedef struct book {
int year;
double price;
char author[20];
char bookName[50];
}BOOK,*P_BOOK;
/*创建结构体指针变量*/
BOOK* p1 = books;
struct book* p2 = books + 1;
P_BOOK p3 = &books[2];
*P_BOOK是该结构体指针类型
使用指针操作结构体成员
方式一: (*指针).成员 // . 级别高,所以要先得到指针空间后再获取成员
方式二: 指针 -> 成员 //结构体指针专属(成员选择)
printf("%s %.2lf\n", (*p1).bookName, (*p1).price);
printf("%s %.2lf\n", p2->bookName, p2->price);
1.3 基于结构存储原理理解对齐·补齐·位域等存储特性
1.3.1 结构体对齐 补齐
对齐:假定从零地址开始,每成员的起始地址编号,必须是它本身字节数的整数倍。
补齐:结构的总字节数必须是它最大成员的整数倍。
特殊的:
指针的大小取决于计算机操作位数。x86为32位(4字节),x64为64位(8字节)。所以,在x86下sizeof(struct FF1)=4+4(char a补齐)+4(*e),在x64下sizeof(struct FF1)=8+8。
对齐补齐原则的意义:节省空间
1.3.2 结构体位域使用
//结构体位域 应用于嵌入式领域居多
typedef struct
{
int a:2;//a变量占两个位的存储空间 但int会分配4个字节。
int b:8;
int c:2;
int :0;//断开
int d:2;
} TT;
TT t ;
printf("%d\n",sizeof(TT));
t.a = 0b10; //00 01 10 11
//0 1 -2 -1
printf("%d\n", t.a);
1.4 结构体的嵌套使用
#include <stdio.h>
typedef struct book{
int price;
char* name;
} Book;
typedef struct{
int age;
char *name;
Book book;
} Student;
int main (){
Student s={ 20,"张三",{12,"如来神掌"} };
printf("书名:%s\n",s.book.name);//得到嵌套结构体内的成员,继续 . 运算
printf("价格:%d\n",s.book.price);
return 0;
}
第二章 联合体
分时 共享
2.1 联合体的基本概念与类型定义方式
//定义共用体
union aa
{
int a ;
char b;
} ;
2.2 联合体的存储原理
共用体:所有成员共享一段存储空间,但每个时间只有一个成员在使用
2.3 联合体创建变量与用法特性
union Data data;
data.i = 10;
data.f = 220.5;
strcpy( data.str, "C Programming");
printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);
第三章 线性表与链表
3.1 数据结构与线性表的基本概念
线性表(List):数组(Array),链表(Link),栈(Stack),队列(Queue)
顺序存储 链式存储
3.2 理解线性表的链式存储原理
单向链表
双向链表
循环链表
3.3 定义链表相关结构体数据类型
typedef struct node
{
int data;//数据域
struct node * next;//下一个节点
} Node, *P_NODE;
3.4 用静态链表的实现来理解链表结构
#include <stdio.h>
#include <malloc.h>
//声明节点结构
typedef struct node
{
int data;//数据域
struct node * next;//下一个节点
} Node, *P_NODE;
int main()
{
/*链表基本概念*/
//1 .声明一个头节点,一般头节点不存贮数据
Node header = { -1,NULL };
//2.创建几个节点变量: 栈区 或 堆区
Node n1 = { 1,NULL };
Node n2 = { 2,NULL };
Node n3 = { 3,NULL };
Node n4 = { 4,NULL };
Node n5 = { 5,NULL };
//3. 链接所有节点
header.next = &n1;
n1.next = &n2;
n2.next = &n3;
n3.next = &n4;
n4.next = &n5;
//4. 遍历链表
Node* p = header.next;
while (p != NULL) {
printf("%d\n", p->data);
p = p->next;
}
return 0;
}
3.5 动态链表的创建及增删改查等基本功能实现
3.5.1 动态链表的创建
如3.4中创建的节点在栈区,而当所需的节点是大量的且需在函数间共享时,需要我们将节点创建在堆区。
Node* p1 = malloc(sizeof(Node));
p1->data = 1;
p1->next = NULL;
P_NODE p2 = malloc(sizeof(Node));
p2->data = 2;
p2->next = NULL;
p1->next = p2;
P_NODE p = p1;
while (p != NULL) {
printf("%d\n", p->data);
p = p->next;
}
3.5.2 增删改查
3.5.2.1 增加节点
typedef struct node {
int data;
struct node* next;
}Node,*PNode;
PNode header=NULL; //指向头节点
PNode ender=NULL; //指向尾节点
/*数据 打包 进节点*/
PNode create(int data)
{
PNode newNode = malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
/*把节点连接成链表*/
void add(PNode node)
{
if (header == NULL) {
header = ender= node;
}
else {
ender->next = node;
ender = node; //指向新的尾节点
}
}
3.5.2.2 插入新节点
/*插入到index后*/
void insert_behind(int index, PNode node)
{
PNode p = header;
for (int i = 0; i < index; i++) {
p = p->next;
}
node->next = p->next;
p->next = node;
}
3.5.2.3 删除节点
/*按下标删除节点*/
void remove_index(int index)
{
PNode p = header;
for (int i = 0; i < index - 1; i++) {
p = p->next;
}
PNode q = p->next;
p->next = p->next->next;
free(q);
}
/*按数据删除节点*/
void remove_data(int data)
{
PNode p = header;
while (p!= NULL) {
if (p->next->data == data)
break;
}
PNode q = p->next;
p->next = p->next->next;
free(q);
}
在通过free()函数释放指针内存之后讲其指针置空,这样可以避免后面的程序对与该指针非法性的判断所造成的程序崩溃问题。释放空间,指针的值并没有改变,无法直接通过指针自身来进行判断空间是否已经被释放,将指针置空有助于判断一个指针所指向的空间已经被释放。
在用free()函数释放指针内存时为何要将其指针置空 - zou-ting-rong - 博客园 (cnblogs.com)
3.5.2.4 查找节点
/*某下标的数据节点*/
int get(int index){
PNode p = header;
for (int i = 0; i < index; i++) {
p = p->next;
}
return p->data;
}
/*某数据的下标*/
int indexOf(int data){
PNode p = header;
int i = 0;
while (p != NULL) {
if (p->data == data)
return i;
p = p->next;
i++;
}
return -1;
}
int size(){
int cnt = 0;
PNode p = header;
while (p != NULL) {
cnt++;
p = p->next;
}
return cnt;
}
第四章 组件化封装:《线性表组件》
经过前面的学习,我们发现之前的代码存在着许多问题,不能解决通用性等问题。
接下来将通过对链表组件化的同时完善其功能。
目的:该链表功能综合体应当能实现
- 创建多条列表
- 增删改查等基本功能
- 更加复杂多样的功能
4.1 基于链表实现原理、栈与队列的特性进行链式存储结构的函数抽象
4.1.1 数据结构类型定义
4.1.1.1 双向链表
/*链表内部的节点类型*/
typedef struct node //双向链表
{
struct node* prev;//前一个节点指针
void* data;//数据域指针
struct node* next;//下一个节点指针
} Node, * PNode;
/*创建节点*/
PNode createNode(void* data)
{
PNode newNode = (PNode)malloc(sizeof(Node));
newNode->prev = NULL;
newNode->next = NULL;
newNode->data = data;
return newNode;
}
怎样使数据多样化?
data数据类型采用:void* 不确定类型指针,节点的通用性
4.1.1.2 多条链表
/*链表管理类型*/
typedef struct //多条链表
{
PNode header;//头节点
PNode ender;//尾部节点
int size;//当前节点总数
PNode nextNode;//迭代器的下一个节点
}LinkedList, * PLinkedList;
/*创建链表*/
PLinkedList createLinkedList()
{
PLinkedList newList = (PLinkedList)malloc(sizeof(LinkedList));
newList->header = NULL;
newList->ender = NULL;
newList->size = 0;
newList->nextNode = NULL;
return newList;
}
4.1.2 链表实现
4.1.2.1 节点添加
若链表为空
若链表不为空
/*增加新的节点*/
void add(PLinkedList list, void* data)
{
PNode newNode = createNode(data);
if (list->size == 0) {
list->header = list->ender = newNode;
}
else {
list->ender->next = newNode;
newNode->prev = list->ender;
list->ender = newNode;
}
list->size++;
}
4.1.2.2 节点插入
/*在某节点的位置上插入新节点*/
void insert(PLinkedList list, int index, void* data)
{
//特殊处理
if (list->size == 0 || index>list->size-1) { //空链表 || 尾
add(list, data);
return;
}
PNode newNode = createNode(data);
if (index <= 0) { //取代头
newNode->next = list->header;
list->header->prev = newNode;
list->header = newNode;
}
else {
//中间插入
PNode p = findNode(list, index);
PNode q = p->prev;
newNode->next = p;
p->prev = newNode;
q->next = newNode;
newNode->prev = q;
}
list->size++;
}
/*找到节点*/
PNode findNode(PLinkedList list, int index)
{
PNode p = list->header;
for (int i = 0; i < index; i++) {
p = p->next;
}
return p;
}
4.1.2.3 节点的删除
/*删除 指定 下标的节点*/
void* removeIndex(PLinkedList list, int index)
{
//特殊情况
if (list->size == 0 ) //空链表
return NULL;
PNode p = NULL; //指向被删的节点
if (list->size == 1) { //链表只有一个节点
p = list->header;
list->header = NULL;
list->ender = NULL;
}
else if (index<=0) { //删头
p = list->header;
list->header = p->next;
list->header->prev = NULL;
}
else if (index >= list->size - 1) { //删尾
p = list->ender;
list->ender = p->prev;
list->ender->next = NULL;
}
else {
//中间删除
p = findNode(list, index);
PNode q = p->prev;
PNode m = p->next;
q->next = m;
m->prev = q;
}
void* temp = p->data;
free(p);
list->size--;
return temp;
}
4.1.2.4 遍历
4.1.2.4.1 使用下标进行遍历
/*节点数量*/
int size(PLinkedList list)
{
return list->size;
}
/*某下标对应的 节点*/
void* get(PLinkedList list, int index)
{
PNode p = findNode(list, index);
return p->data;
}
//下标方式 list->size
for (int i = 0; i < size(list); i++) {
const char* str=(const char*)get(list, i);
printf("%s\n", str);
}
4.1.2.4.2 使用迭代器进行遍历
/*迭代器*/
//产生新的迭代器
void iterator(PLinkedList list)
{
list->nextNode = list->header;
}
//是否有下一个节点
int hasNext(PLinkedList list)
{
return list->nextNode !=NULL ;
}
//取得下一个节点数据
void* next(PLinkedList list)
{
void* temp=list->nextNode->data;
list->nextNode = list->nextNode->next;
return temp;
}
iterator(list); //产生一个新的迭代
while (hasNext(list)) { //判断当前有数据吗
const char* str=(const char*)next(list); //只取一个
printf("+%s\n", str);
}
4.1.2.5 对节点值的修改
/*某下标对应的 节点数据进行更新 */
void set(PLinkedList list, int index, void* newdata)
{
PNode node = findNode(list, 2);
node->data = newdata;
}
4.1.2.6 清空链表
栈区的list指向堆区的node,node里存储的data来自常量区。
所以要清空链表其实是把所有的节点删掉。
/*清空链表*/
void clear(PLinkedList list)
{
PNode p = list->header;
PNode q = p;
while (p != NULL) {
q = p;
p = p->next;
free(q);
list->size--;
}
list->header = list->ender = NULL;
}
4.1.2.7 找到内容对应的下标
a == b 比较ab两个地址是否相同
如果我们在进行内容比较时遇到 char str[] = “”字符串 与 “字符串” 的比较时,会发现得到的结果是“-1”。原因是str创建在栈区,而“字符串”创建在静态区,二者地址不同,如果简单用用“==”来进行比较必然得不到想要的结果。
函数指针的应用
/*用于比较数据域大小的函数指针类型*/
typedef int(*Compare)(void*, void*);
//比较字符串内容
int comStr(void* s1, void* s2) {
const char* ps1 = (const char*)s1;
const char* ps2 = (const char*)s2;
return strcmp(ps1, ps2) == 0;
}
/*某数据对应的下标*/
int indexOf(PLinkedList list, void* data, Compare fun)
{
PNode p = list->header;
int xb = 0;
while (p != NULL) {
if (fun(p->data, data))
return xb;
xb++;
p = p->next;
}
return -1;
}
4.1.2.8 按照数据删节点
/*删除等于参数的首个节点*/
void removeData(PLinkedList list, void* data, Compare fun)
{
int xb = indexOf(list, data, fun);
if (xb > 0)
removeIndex(list, xb);
}
4.1.3 栈与队列的实现
4.1.3.1 栈的实现
LIFO(Last In Fisrt Out)后进先出原则
/*实现栈stack的功能:LIFO:last in first out */
/* 入栈*/
void push(PLinkedList list, void* data)
{
add(list, data);
}
/*出栈*/
void* pop(PLinkedList list)
{
return removeIndex(list, list->size - 1);
}
4.1.3.2 队列的实现
FIFO(First In First Out)先进后出
/*实现队列queue的功能:FIFO:first in first out */
void addFirst(PLinkedList list, void* data)
{
insert(list, 0, data);
}
void addLast(PLinkedList list, void* data)
{
add(list, data);
}
void* removeFirst(PLinkedList list)
{
return removeIndex(list,0);
}
void* removeLast(PLinkedList list)
{
return removeIndex(list, list->size - 1);
}
4.2 对链表进行功能性函数抽象
4.2.1 两条链表链接
/*将链表2 追加到链表1的后面*/
void addAll__(PLinkedList list1, PLinkedList list2)
{
iterator(list2);
while (hasNext(list2))
add(list1, next(list2));
}
这里为什么不直接将 list1->ender->next=list2->header->pre 呢?
因为这样就不能使list2独立,所以使用迭代器将list2的节点一个一个加在list1上。
4.2.2 返回最大/最小数据域
按自定义规则实现返回链表中最大/最小的那个数据。
int strCMP(void* s1, void* s2)
{
const char* p1 = (const char*)s1;
const char* p2 = (const char*)s2;
return strcmp(p1, p2);
}
int strLEN(void* s1, void* s2)
{
const char* p1 = (const char*)s1;
const char* p2 = (const char*)s2;
return strlen(p1)-strlen(p2);
}
/*返回最大数据域*/
void* max__(PLinkedList list, Compare fun)
{
iterator(list);
void* max_value=next(list);
while (hasNext(list)) {
void* tmp = next(list);
if (fun(max_value, tmp) < 0)
max_value = tmp;
}
return max_value;
}
/*返回最小数据域*/
void* min__(PLinkedList list, Compare fun)
{
iterator(list);
void* min_value = next(list);
while (hasNext(list)) {
void* tmp = next(list);
if (fun(min_value, tmp) > 0)
min_value = tmp;
}
return min_value;
}
4.2.3 交换数据域
/*交换 两个下标对应的数据域*/
void swap__(PLinkedList list, int i, int j)
{
PNode p_i = findNode(list, i);
PNode p_j = findNode(list, j);
void* tmp = p_i->data;
p_i->data = p_j->data;
p_j->data = tmp;
}
4.2.4 排序
/*对链表 按 Compare比较规则 进行排序 */
void sort__(PLinkedList list, Compare fun)
{
PNode p_i, p_j;
int i = 0, j = 0;
for (i = 0,p_i = list->header; i < size(list) - 1; i++, p_i = p_i->next) {
for (j = i + 1, p_j = p_i->next; j < size(list); j++, p_j = p_j->next) {
if (fun(p_i->data, p_j->data) > 0) {
void* tmp=p_i->data;
p_i->data = p_j->data;
p_j->data = tmp;
}
}
}
}
4.2.5 打乱
#include <stdlib.h>
#include <time.h>
/*打乱链表的顺序*/
void shuffle__(PLinkedList list)
{
srand((unsigned int)time(NULL));
int len = size(list);
for (int i = 0; i < len; i++) {
int a = rand() % len;
int b = rand() % len;
swap__(list, a, b);
}
}
4.2.6 首尾倒置
/*对链表成员进行首尾倒置*/
void reverse__(PLinkedList list)
{
/*for (int i = 0, j = size(list) - 1; i < j; i++, j--)
swap__(list, i, j);*/
PNode p = list->header, q = list->ender;
void* temp;
for (int i = 1; i <= size(list) / 2; i++) {
temp = p->data;
p->data = q->data;
q->data = temp;
p = p->next;
q = q->prev;
}
}
4.2.7 替换
/*把复合fun比较规则的数据域 替换成新的数据域*/
void replaceAll__(PLinkedList list, void* oldVal, void* newVal, Compare fun)
{
PNode p = list->header;
while (p) {
if (fun(p->data, oldVal))
p->data = newVal;
p = p->next;
}
}
4.2.8 二分查找
/*使用折半查找思路 查询某key数据的下标*/
int binarySearch__(PLinkedList list, void* key, Compare fun)
{
int left = 0, right = list->size-1;
while (left <= right) {
int mid = (left + right) / 2;
void* data = get(list, mid);
int bj = fun(data, key);
if (bj == 0)
return mid;
else if (bj < 0)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
4.2.9 qsort快排
4.3 用测试驱动的过程进行组件化封装的代码实现
4.3.1 静态库.lib
组件随项目一起生成了.exe文件
打包时应在头文件最前面添加 #include "pch.h"
使用时应当包含头并启动
#include "lib/collections.h"
#pragma comment(lib,".\\lib\\StaticLink.lib")
注意:\ 为转义字符,应当写为 \\
4.3.2 动态库.dll
组件与项目独立出来
4.3.2.1 实现
在头文件函数声明前加 _declspec(dllexport) 说明该函数可以导出到外部使用。
生成两个文件.lib和.dll文件(.exp文件暂不需要)