学习笔记

结构体

1.结构体例子

1. 例1

设计一个学生类型的结构体,该结构体包含student1和student2;学生信息包括学号、姓名、分数,要求输出分数高的学生的所有信息。

#include<iostream>
using namespace std;
#include<string>


//定义学生结构体
struct Student
{
	string num;
	string name;
	int score;
};

int main()
{
	Student student1, student2;
	cout << "请输入学生1的学号、姓名、成绩:" << endl;
	cin >> student1.num >> student1.name >> student1.score;

	cout << "请输入学生2的学号、姓名、成绩:" << endl;
	cin >> student2.num >> student2.name >> student2.score;

	cout << "最高分是:" << endl;
	if (student1.score > student2.score)
	{
		cout << student1.num << "  " << student1.name << "  " << student1.score << endl;

	}
	else if (student1.score < student2.score)
	{
		cout << student2.num << "  " << student2.name << "  " << student2.score << endl;
	}
	else
	{
		cout << student1.num << "  " << student1.name << "  " << student1.score << endl;
		cout << student2.num << "  " << student2.name << "  " << student2.score << endl;
	}

	system("pause");
	return 0;
}

2.结构体数组

给定学生成绩登记表如下所示。利用结构体数组计算表中给定的两门课程成绩的平均成绩,最后输出该学生成绩登记表。

学号姓名性别年龄成绩1成绩2平均成绩
101ZhangM1995.564.0
102WangF1892.097.0
103ZhaoM1985.078.0
104LiM2096.088.0
105GouM1991.096.0
106LinM1893.078.0
107MaF1898.097.0
108ZhenM2189.093.0
109XuM1988.090.0
110MaoF1894.090.0

程序如下:

#include<iostream>
using namespace std;
#include<string>

struct Student
{
	string id;  //学号
	string name;  //姓名
	char sex;  //性别
	int age;  //年龄
	float score[3];  //成绩(成绩1,成绩2,平均成绩)
};

//计算平均成绩
float average(float score1, float score2)
{
	return (score1 + score2) / 2;
}

int main()
{
	Student s[10]=
	{
		{"101","Zhang",'M',19,{95.5,64.0}},
		{"102","Wang",'F',18,{92.0,97.0}},
		{"103","Zhao",'M',19,{85.0,78.0}},
		{"104","Li",'M',20,{96.0,88.0}},
		{"105","Gou",'M',19,{91.0,96.0}},
		{"106","Lin",'M',18,{93.0,78.0}},
		{"107","Ma",'F',18,{98.0,97.0}},
		{"108","Zhen",'M',21,{89.0,93.0}},
		{"109","Xu",'M',19,{88.0,90.0}},
		{"110","Mao",'F',18,{94.0,90.0}}
	};

	for (int i = 0; i < 10; i++)
	{
		s[i].score[2] = average(s[i].score[0], s[i].score[1]);
	}

	for (int i = 0; i < 10; i++)
	{
		cout << "学号:" << s[i].id << "  姓名:" << s[i].name << "  性别:" << s[i].sex
			<< "  年龄:" << s[i].age << "  成绩1:" << s[i].score[0]
            << "  成绩2:" << 	s[i].score[1]<< "  平均成绩:" << s[i].score[2] << endl;
	}


	system("pause");
	return 0;
}

3.结构体与指针

3.1结构体指针类型作为函数参数

#include<iostream>
using namespace std;
#include<string>

struct student
{
	int num;
	char name[10];
	char gender;
	int age;
	float score;
};

void change(student *t)
{
	cout << "t = " << t->num <<"  "<< t->name << "  " << t->gender << "  " 
         << t->age << "  " << t->score << endl;

	strcpy_s(t->name, "Wang");   //将字符串“string”复制到 t->name 中
	t->score = 95.0;

	cout << "t = " << t->num << "  " << t->name << "  " << t->gender << "  "
         << t->age << "  " << t->score << endl;
}

int main()
{
	student st = { 101,"Zhang",'M',19,89.0 };
	cout << "st = " << st.num << "  " << st.name << "  " << st.gender << "  " << st.age 		 << "  " << st.score << endl;

	change(&st);
	cout << "st = " << st.num << "  " << st.name << "  " << st.gender << "  " << st.age 		 << "  " << st.score << endl;

	system("pause");
	return 0;
}

将结构体数组小节中的例题用结构体类型指针实现,程序如下:

#include<iostream>
using namespace std;
#define STUDENT struct student

STUDENT
{
	int num;
	char name[10];
	char gender;
	int age;
	float score[3];
};

//求平均成绩
void average(STUDENT *t,int n) //形参使用结构体类型指针变量,(实参使用结构体数组名)
{
	for (int i = 0; i < n; i++)
	{
        //注意:t是指针访问属性时用 ‘ t->属性名’ ,
        // 但是 t[i]是对应的元素,所以访问时依然用的是't[i].属性名'
		t[i].score[2] = (t[i].score[0] + t[i].score[1]) / 2;
	}
}

int main()
{
	STUDENT stu[10]=
	{
		{101,"Zhang",'M',19,{95.5,64.0}},
		{102,"Wang",'F',18,{92.0,97.0}},
		{103,"Zhao",'M',19,{85.0,78.0}},
		{104,"Li",'M',20,{96.0,88.0}},
		{105,"Gou",'M',19,{91.0,96.0}},
		{106,"Lin",'M',18,{93.0,78.0}},
		{107,"Ma",'F',18,{98.0,97.0}},
		{108,"Zhen",'M',21,{89.0,93.0}},
		{109,"Xu",'M',19,{88.0,90.0}},
		{110,"Mao",'F',18,{94.0,90.0}}
	};

	for (int i = 0; i < 10; i++)
	{
		cout  << stu[i].num << "  " << stu[i].name << "  " << stu[i].gender << "  "
			<< stu[i].age << "  " <<  stu[i].score[0] << "  " << stu[i].score[1] << "  " 			 << stu[i].score[2] << endl;
	}

	cout << "-------------------------------------------------------" << endl;
    
	average(stu, 10);  //(形参使用结构体类型指针变量)实参使用结构体数组名
	for (int i = 0; i < 10; i++)
	{
		cout << stu[i].num << "  " << stu[i].name << "  " << stu[i].gender << "  "
			<< stu[i].age << "  " << stu[i].score[0] << "  " << stu[i].score[1] << "  " << stu[i].score[2] << endl;
	}

	system("pause");
	return 0;
}

设有学生登记表如下所示,用选择排序法对该表按成绩从小到大进行排序。

学号姓名性别年龄成绩
101ZhangM1995.6
102WangF1892.4
103ZhaoM1985.7
104LiM2096.3
105GouM1990.2
106LinM1891.5
107MaF1898.7
108ZhenM2190.1
109XuM1989.8
110MaoF1894.9

程序如下(自己写的):

#include<iostream>
using namespace std;

//学生结构体
struct student
{
	int num;
	char name[8];
	char gender;
	int age;
	float score;
};

//选择排序
void selectionSort(student *stu, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int minIndex = i;
		for (int j = i+1; j < n; j++)
		{
            //注意:stu 是指针; stu[j] 是对应的元素
			if (stu[j].score < stu[minIndex].score)
			{
				minIndex = j;
			}
		}
		if (i != minIndex)
		{
            //交换学生结构体类型对应的位置
			student temp = stu[i];
			stu[i] = stu[minIndex];
			stu[minIndex] = temp;
		}
	}
}


int main()
{
	student stu[10]=
	{
		{101,"Zhang",'M',19,95.5},
		{102,"Wang",'F',18,92.4},
		{103,"Zhao",'M',19,85.7},
		{104,"Li",'M',20,96.3},
		{105,"Gou",'M',19,90.2},
		{106,"Lin",'M',18,91.5},
		{107,"Ma",'F',18,98.7},
		{108,"Zhen",'M',21,90.1},
		{109,"Xu",'M',19,89.8},
		{110,"Mao",'F',18,94.9}
	};

	selectionSort(stu, 10);

	for (int i = 0; i < 10; i++)
	{

		cout << stu[i].num << "  " << stu[i].name << "  " << stu[i].gender << "  "
			<< stu[i].age << "  " << stu[i].score<< "  " << endl;
	}

	system("pause");
	return 0;
}
指针数组与结构体

(书上给的答案)

书上用的是指针数组,不太好理解

#include<iostream>
using namespace std;
#include<iomanip>

//学生结构体
struct student
{
	int num;
	char name[8];
	char gender;
	int age;
	double score;
};

//选择排序
void selectionSort(student *p[], int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int minIndex = i;
		for (int j = i + 1; j < n; j++)
		{
			if (p[j]->score < p[minIndex]->score)
			{
				minIndex = j;
			}
		}
		if (minIndex != i)
		{

			// 交换指针p[i]和p[minIndex]的值
			student * temp = p[i];
			p[i] = p[minIndex];
			p[minIndex] = temp;
		}
	}
}

int main()
{
	student stu[10]=
	{
		{101,"Zhang",'M',19,95.5},
		{102,"Wang",'F',18,92.4},
		{103,"Zhao",'M',19,85.7},
		{104,"Li",'M',20,96.3},
		{105,"Gou",'M',19,90.2},
		{106,"Lin",'M',18,91.5},
		{107,"Ma",'F',18,98.7},
		{108,"Zhen",'M',21,90.1},
		{109,"Xu",'M',19,89.8},
		{110,"Mao",'F',18,94.9}
	};
	
    //定义了一个指针数组,其中每一个指针元素指向一个学生的结构体信息
	student *p[10];
	for (int i = 0; i < 10; i++)
	{
		p[i] = &stu[i];
	}

	cout << "原数组:" << endl;
	for (int i = 0; i < 10; i++)
	{
        // setw(8)是包含在<iomanip.h>头文件中的,->意思是 总共输出8位
		cout << p[i]->num << setw(8) << p[i]->name << setw(8) << p[i]->gender
			<< setw(8) << p[i]->age << setw(8) << p[i]->score << endl;
	}

	selectionSort(p, 10);
	cout << "排序后数组" << endl;
	for (int i = 0; i < 10; i++)
	{
		cout << p[i]->num << setw(8) << p[i]->name << setw(8) << p[i]->gender
			<< setw(8) << p[i]->age << setw(8) << p[i]->score << endl;
	}

	system("pause");
	return 0;
}

程序分析:在上述程序的排序过程中,并没有改变原信息在结构体数组中的存储位置,而是用了一个指针数组,其中每一个指针元素指向一个学生的结构体信息。排序过程中,交换的是指针而不是结构体,最后得到的结果是按指针数组中各元素所指向的学生成绩依次有序的。

最后的运行结果为:

原数组:
101   Zhang       M      19    95.5
102    Wang       F      18    92.4
103    Zhao       M      19    85.7
104      Li       M      20    96.3
105     Gou       M      19    90.2
106     Lin       M      18    91.5
107      Ma       F      18    98.7
108    Zhen       M      21    90.1
109      Xu       M      19    89.8
110     Mao       F      18    94.9
排序后数组
103    Zhao       M      19    85.7
109      Xu       M      19    89.8
108    Zhen       M      21    90.1
105     Gou       M      19    90.2
106     Lin       M      18    91.5
102    Wang       F      18    92.4
110     Mao       F      18    94.9
101   Zhang       M      19    95.5
104      Li       M      20    96.3
107      Ma       F      18    98.7

​ 其中的 p[i]->num ,p[i]->name ,p[i]->gender,p[i]->age ,p[i]->score 如果不用 -> 运算符,不得不写为

( *p[i] ).num ,( *p[i] ).name,( *p[i] ).gender,( *p[i] ).age,( *p[i] ).score,可读性差很多。如果结构体中

还有嵌套的结构体指针 next,不得不写为( *( *p[i]).next).num,可读性就更差了,而用->运算符重写为

p[i]->next->num,结构变得很清晰,可读性较好。(*p[i]表示对应的结构体)

思考:将排序后数组后的 for 循环里面换成以下内容,结果会如何?

for (int i = 0; i < 10; i++)
	{
		cout << stu[i].num << setw(8) << stu[i].name << setw(8) << stu[i].gender
			<< setw(8) << stu[i].age << setw(8) << stu[i].score << endl;
	}

答案:结果出来的是没有排好序的,和原数组一模一样,因为交换的是指针而不是结构体

4.结构体的大小与 # pragma 中 pack 的关系

可以用sizeof 求结构体所占内存单元的大小,例如有结构体:

struct student
{
    char name[10];
    long sno;
    char gender;
    float score[4];
}a,b;

其中 sizeof(struct student)和 sizeof(a) 按照 student 结构体中成员的构成,大小应该是:10+4+1+16=31,也就是说 sizeof(struct student) 的结果应该是31.实际情况如何

struct student
{
    char name[10];
    long sno;
    char gender;
    float score[4];
};

int main()
{
    struct student stu={"Zhang",101,'M',98,95,86,90};
    cout << "sizeof(struct student)=" << sizeof(struct student) << endl;
}

程序的运行结果为:

sizeof(struct student) =36

运行结果不是31而是36的原因是因为字节对齐问题。对于32位编译器,结构体 student 中最长成员是4字节,为了不出现某个结构体变量跨4字节倍数单元地址存放,默认以结构体student中最长成员的4字节对齐。不足4字节倍数的name后补2字节对齐,不足4字节的 gender 后补3字节对齐,因此结构体 student 的大小位12+4+4+16=36.

​ 如果在程序的开头加上# pragma pack(4),程序改为:

#include<iostream>
using namespace std;
#pragma  pack(4)

struct student
{
    char name[10];
    long sno;
    char gender;
    float score[4];
};

int main()
{
    struct student stu={"Zhang",101,'M',98,95,86,90};
    cout << "sizeof(struct student)=" << sizeof(struct student) << endl;
    system("pause");
    return 0;
}

程序的运行结果为:

sizeof(struct student) =36

程序没有改变,仍是36,说明原来的程序默认以4字节对齐。如果程序改为:

#include<iostream>
using namespace std;
#pragma  pack(2)   //让编译器以2字节对齐

struct student
{
    char name[10];
    long sno;
    char gender;
    float score[4];
};

int main()
{
    struct student stu={"Zhang",101,'M',98,95,86,90};
    cout << "sizeof(struct student)=" << sizeof(struct student) << endl;
    system("pause");
    return 0;
}

程序的运行结果为:

sizeof(struct student) =32

结构体中成员以2字节对齐,不足2字节的gender后补1字节对齐,因此结构体 student 的大小为 10+4+2+16=32.

如果程序改为:

#include<iostream>
using namespace std;
#pragma  pack(1)   //让编译器以1字节对齐

struct student
{
    char name[10];
    long sno;
    char gender;
    float score[4];
};

int main()
{
    struct student stu={"Zhang",101,'M',98,95,86,90};
    cout << "sizeof(struct student)=" << sizeof(struct student) << endl;
    system("pause");
    return 0;
}

程序的运行结果为:

sizeof(struct student) =31

结构体中成员按照以1字节对齐,才得到了期望的结构体大小是31字节。利用 pack 紧缩方式可以更节省存储空间。但如果程序改为 #pragma pack(3) ,会是什么结果呢?如下面的程序:

#include<iostream>
using namespace std;
#pragma  pack(1)   //让编译器以1字节对齐

struct student
{
    char name[10];
    long sno;
    char gender;
    float score[4];
};

int main()
{
    struct student stu={"Zhang",101,'M',98,95,86,90};
    cout << "sizeof(struct student)=" << sizeof(struct student) << endl;
    system("pause");
    return 0;
}

程序的编译结果为:

warning C4086: 杂注参数应为“1”、“2”、“4”、“8”或者“16”

程序的运行结果为:

sizeof(struct student) =31

也就是说,编译系统先给出了错误警告信息,警告 pack 括号中的参数只能是 1、2、4、8或者16,因为3不是其中合法的对齐方式,因此自动忽略而按缺省方式4字节对齐。

#pragma pack( ) 的对齐方式应该是1,2,4,8或16,也就是按照 2^k(k>=0)字节对齐。

如果结构体中出现 double 型成员,32为编译器将默认以结构体中最长成员的8字节对齐。除非你用#pragma pack指定对齐方式。例如有下面程序:

#include<iostream>
using namespace std;

struct student
{
    char name[10];
    long sno;
    char gender;
    double score[4];  //将float 改为 double
};

int main()
{
    struct student stu={"Zhang",101,'M',98,95,86,90};
    cout << "sizeof(struct student)=" << sizeof(struct student) << endl;
    system("pause");
    return 0;
}

程序的运行结果为:

sizeof(struct student) =56

​ 32 位编译器默认以结构体中最长成员 double 的8字节对齐,name[9]后空2字节,sno在第二个8字节内存单元的后4字节中存放,gender 后空7字节。简单地讲,数据摆放时,总是以数据单元大小的整数倍作为起始位置。因此结构体中成员的定义顺序会影响对齐后的结构体大小。例如上面的程序改为:

#include<iostream>
using namespace std;

struct student
{
    char name[10];
    char gender;  //把 sno 和 gender 的定义交换位置
    long sno;
    double score[4];
};

int main()
{
    struct student stu={"Zhang",101,'M',98,95,86,90};
    cout << "sizeof(struct student)=" << sizeof(struct student) << endl;
    system("pause");
    return 0;
}

程序的运行结果为:

sizeof(struct student) =48

gender紧跟在第2个8字节中 name[9]后存放,占 1 字节,gender 后空1字节,sno 在第2个8字节内存单元的后4字节。结构体中成员这样存放就自动省出了一个8字节空间。所以在定义结构体的时候,可以适当考虑各成员的摆放顺序,以节省内存和存放空间。

5.单链表

5.1单链表的基本概念

1.链表的一般结构

链表由结点元素组成。为了适应链表的存储结构,计算机存储空间被划分为一个一个小块,每一小块占若干字节,通常称这些小块为存储结点。

在计算机中,为了存储链表中的一个元素,一方面要存储该数据元素的值;另一方面要存储该数据元素与下一元素之间的链接关系。为此,将存储空间中的每一个存储结点分为两部分:一部分用于存放数据元素的值,称为数据域;另一部分用于存放下一个数据元素的存储序号(即下一个结点的存储地址),称为指针域。

单链表结构如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R4Fus37b-1602035909048)(F:\MyData\C++学习笔记\图片\单链表.png)]

在链表中,用一个专门的指针HEAD指向链表中第一个数据元素的结点(即存放第一个数据元素的存储结点的序号),通常称为头指针。链表中最后一个元素后面已没有结点元素,因此,链表中最后一个结点的指针域为空(用NULL或0表示),表示链表终止。

当HEAD==NULL(或0)时,称为空表;

对于链表,可以从头指针开始,沿各节点的指针扫描到链表中的所有节点

定义链表结点结构如下:

struct node
{
    char name[10];  //数据域
    char gender;  //数据域
    node *next;   //指针域
};

在上述定义的节点类型中,数据域包含两个数据项成员:一是包含10个字符的数组name[10](即字符串)用于存放姓名;二是字符类型数据gender,用于存放性别。其中成员next是一个结构体 node 类型的指针,用于指向下一个节点。

2.节点的动态分配

可以利用 malloc 函数向系统申请分配链表节点的存储空间。例如:

struct node
{
    int d;
    node *next;
};

node *p;
p=(node *)malloc(sizeof(node));

申请了一个结构体node类数据的动态存储空间,并将申请得到的存储空间首地址赋给结构体 node 类型的指针变量 p。

可以用函数free(p)释放由 p 指向的节点存储空间。

例如下面的程序段定义了一种节点类型node;并定义了该类型的指针变量p(用于指向该种节点类型的存储空间的首地址);然后申请分配该节点类型的一个存储空间,并用指针变量p指向这个申请得到的存储空间,存储空间用完后,最后释放该存储空间。

#include<iostream>
using namespace std;

//定义节点类型
struct node
{
    int d;				//数据域
    struct node *next;	//指针域
};

int main()
{
    node *p; 		//定义该类型的指针变量
    ...
    
    p=(node *)malloc(sizeof(node));  //申请分配节点存储空间
    ...
        
    free(p);		//释放节点存储空间
    
    system("pause");
    return 0;
}

例5.1 下列程序的功能是:建立一个链表,其元素值依次为从键盘输入的正整数(以输入一个非正整数结束输入),然后依次输出链表中的各元素值。

#include<iostream>
using namespace std;
#include<iomanip>  //setw函数需包含头文件iomanip


//节点
struct node
{
	int d;
	node *next;
};

int main()
{
	int x;
    
    //*head, *p, *q:分别为 头节点指针  中间节点指针  尾节点指针
	node *head, *p, *q;
	head = NULL;   //置链表为空
	q = NULL;
	cin >> x;
	while (x > 0)
	{
		p = (node *)malloc(sizeof(node)); //申请一个节点(p指向申请到的节点存储空间)
		p->d = x;        //置当前结点的数据域为输入的正整数x
		p->next = NULL;		//置当前节点的指针域为空(即让当前节点指向NULL)
		if (head == NULL)
		{
			head = p;  //若链表为空,则将头指针指向当前节点p(头指针head和p指向同一块内存)
		}
		else
		{
			q->next = p;  //将当前节点链接在链表最后
		}
		q = p;   //置当前节点为链表最后一个节点
		cin >> x;
	}

	p = head;  //输出前先让p指向头节点指向的节点
	//从链表的第一个结点开始,打印各节点的元素值,并删除
	while (p!=NULL)
	{
		cout << setw(5) << p->d;  //打印当前结点的数据

		//删除当前节点
		q = p;  
		p = p->next; 

		//释放删除的节点空间
		free(q);
	}
	cout << endl;
	system("pause");
	return 0;
}

5.2单链表的基本运算

1.在链表中查找节点

下面是在非空链表中寻找包含指定元素值的前一个节点 的C++语言描述

//节点
struct node
{
	ET d;  //ET为数据元素类型名
	node *next;
};

//在头指针为head的非空链表中寻找包含元素x的前一个节点p(节点p作为函数值返回)
node *lookst(node *head, ET  x)
{
	node *p;   //注意调用本函数时,head不能为NULL
    p = head;
	while ((p->next != NULL) && (p->next->d != x))
	{
		p = p->next;
	}
	return p;
}

​ 在这个算法中,从头指针指向的结点开始往后沿指针链进行扫描,直到后面已没有结点或下一个结点的数据域为x为止。因此,由这个算法返回的结点值p有两种可能:当链表中存在包含元素x的结点时,则返回的p指向第一次遇到的包含元素x的前一个结点;当链表中不存在包含元素x的结点时,则返回的p指向链表中的最后一个结点。

2.链表的插人

链表的插人是指在原链表中的指定元素之前插人一个新元素。

为了要在链表中插人一个新元素,首先要给该元素分配个新结点p,以便用于存储该元素的值。新结点P可以用malloc()函数申请,然后将存放新元索值的结点链接到链表中指定的位置。

要在链表中包含元素x的结点之前插人一个新元素b,其插人过程如下。

(1)用malloc函数申请取地新节点p,并置该节点的数据域为b,即令p->d=b;

(2)在链表中寻找包含元素x的前一个结点。设该结点的存储地址 为q。

(3)最后将结点P插人到结点q之后。为了实现这一步,只要改变以下两个结点的指针域内容。

  • 使节点p指向包含元素x 的结点(即节点q 的后件节点),即令

    p->next = q-> next;

  • 使节点q的指针域内容改为指向节点p,即令

    q->next=p;

    ​ 由链表的插入过程可以看出,链表在插入过程中不发生数据元素移动的现象,只需改变有关节点的指针指向即可,这比起在数组中插入元素,大大提高了执行效率。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4btEpn2b-1602035909056)(F:\MyData\C++学习笔记\图片\链表的插入.png)]

    ​ 下面给出在链表中包含元素 x 的节点之前插入新元素 b 的C++程序:

    #include<iostream>
    using namespace std;
    #include<iomanip>
    
    struct node
    {
    	int d;
    	node *next;
    };
    
    /* 在头指针为 head 的链表中包含元素 x 的节点之前插入新元素b
       注意:因为函数inslst 中要修改head的值,因此给函数传来的是 head 的内存地址,
       所以出现了形参 struct node **head,后面的删除函数也是如此*/
    
    void inslst(node **head, int x, int b)
    {
    	node *p, *q;
    	p = (node *)malloc(sizeof(node));  //申请一个新节点p
    	p->d = b;   //给新节点的数据域赋值
        
        //链表为空的情况
    	if (*head == NULL)
    	{
    		*head = p;
    		p->next = NULL;
    		return;
    	}
        
        //情况2:在第一个节点前插入
    	if ((*head)->d == x)
    	{
    		p->next = *head;
    		*head = p;
    		return;
    	}
        
        //其他情况
        //寻找包含x的前一个节点q
    	q = lookst(*head, x);  //函数lookst的返回值为指定元素x的前一个节点
        //节点p插入到节点q之后
    	q->next = q->next;
    	q->next = p;
    	return;
    }
    
    

    关于二级指针node **head的理解:

    node **head:定义了一个二级指针 head,它指向单链表的头节点的指针,即如下图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnHPS9gY-1602035909061)(F:\MyData\C++学习笔记\图片\链表的插入2.png)]

至于为什么 要传入 ** head, 而不传入 * head ?

这是二级指针作为函数参数的一个作用,详见文章:二级指针的作用详解

这篇文章没有看太懂,但大致意思就是:传入二级指针后改变形参的指向时,实参的指向也会发生改变,一般用于修改指针的值。

3.链表的删除

​ 链表的删除是指在链表中删除包含指定元素的节点。

​ 为了在链表中删除包含指定元素的节点,首先要在链表中找到这个节点,然后将要删除节点释放回系统的内存堆中。

​ 要在链表中删除包含元素 x 的节点,其删除过程如下:

(1)在链表中寻找包含元素 x 的前一个节点,设该节点地址为 q , 则包含元素 x 的节点地址 p = q->next。

(2)将节点q后的节点 p 从链表中删除,即让节点q的指针指向包含元素 x 的节点 p 的指针指向的节点,即令

q->next = p->next;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gw42FjQS-1602035909071)(F:\MyData\C++学习笔记\图片\链表的删除.PNG)]

(3)将包含元素 x 的节点 p 释放。至此,链表的删除运算完成。

​ 从链表的删除过程可以看出,在链表中删除一个元素后,不需要移动表的数据元素,只需改变被删除元素所在节点的前一个节点的指针域即可,这比在数组中删除元素,也大大提高了执行效率。另外,当从链表中删除一个元素后,该元素的存储节点就变为空闲,应将该空闲节点释放。

下面给出在链表中删除包含元素 x 的节点的C++程序:

#include<iostream>
using namespace std;
#include<iomanip>

struct node
{
	int d;
	node *next;
};

// 在头指针为 head 的链表中删除包含元素 x 的节点
void delst(node **head, int x)
{
	node *p, *q;
 
    //链表为空的情况
	if (*head == NULL)
	{
		cout<<"This is a empty list!"<<endl;
		return;
	}
    
    //情况2:删除第一个节点
	if ((*head)->d == x)
	{
		p=(*head)->next;
        free(*head);	//释放要删除的节点
        *head = p;
		return;
	}
    
   
    //寻找包含x的前一个节点q
	q = lookst(*head, x); 
    
    //情况3:没有找到要删除的节点
    if(q->next == NULL)  //链表中没有包含元素 x 的节点
    {
        cout<<"No this node in the list!"<< endl;
        return;
    }
    
    //情况4:删除中间节点
    //删除节点p
	p=q->next;
    q->next=p->next;
    
    free(p);  //释放要删除的节点p
	return;
}

4.链表的打印

链表的打印是指在将链表中各节点的元素值顺序输出。c++程序为:

void printlst(node *head)
{
    node *p=head;
    
    //从链表的第一个结点开始,打印各节点的元素值
    while(p!=NULL)
    {
        cout<<setw(5) << p->data;
        p=p->next;
    }
    cout<<endl;
}
5.链表的逆转

链表的逆转是指让链表的头指针 head 指向链表的最后一个节点,所有节点的指针域都指向前一个节点,而链表的头节点指针置为NULL,变成逆转后的链尾表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sA75XfZ1-1602035909079)(F:\MyData\C++学习笔记\图片\链表的逆转.jpg)]

C++程序为:

void reverselst(node **head)
{
	node *p, *q, *r;
	p = *head;
	if (p == NULL)
		return;
	q = p->next;  //q指向p的下一个节点
	p->next = NULL;  //设置原链表头节点指针为NULL(这样后面虽然指针指向没变,但是链表从这断了)
	while (q != NULL)
	{
		r = q->next; //r指向q的下一个节点
		q->next = p; //q的指针域指向前一个结点p

		//3个指针依次递推,为下一次循环做好准备
		p = q;
		q = r;
	}
	*head = p; //head指向原链表的尾节点,完成逆转
	return;
}

5.3 多项式 的表示与运算

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xOnjupIM-1602035909084)(F:\MyData\C++学习笔记\图片\多项式的表示与运算.jpg)]

补充:结构体中默认以最长字节(double 8字节)对齐,调换顺序后 (int 4字节),所以结构体(struct node类型占4字节)默认+4补齐8字节。

多项式的运算主要有以下4种。

(1)多项式链表的生成

(2)多项式链表的释放

(3)多项式的输出

(4)多项式的相加

下面分别简单介绍一下:

1. 多项式链表的生成

多项式链表的生成过程如下:

按降幂顺序以数对的形式依次输入多项式中非零系数项的指数 e(k)和系数a(k)(k=m,m-1, … ,1),最后以输入指数值 -1为结束。对于每一次输入,申请一个节点,填入输入的指数值和系数值后,将该节点链接到链表的末尾。算法的C++语言描述为:

#include<iostream>
using namespace std;

//定义节点类型
struct node
{
	double coef;	//系数为双精度型
	int exp;	   //指数为正整数
	node *next;		//指针域
};

node *inpoly()		//函数返回多项式链表头指针
{
	node *head = NULL, *p, *k = NULL;
	int e;
	double a;
	cout << "please input exp and coef:";
	cin >> e >> a;   //输入指数与系数

	while (e > 0)
	{
		p = (node *)malloc(sizeof(node));	//申请一个新节点p
		p->exp = e;
		p->coef = a;
		p->next = NULL;

		//链表为空
		if (head == NULL)
		{
			head = p;	//头指针指向新节点
		}
		else
		{
			k->next = p;	//原链尾结点指针指向新节点
		}

		k = p;		//记住新的链尾

		cout << "please input exp and coef:";
		cin >> e >> a;		//输入下一对指数与系数
	}

	return head;
}
2.多项式链表的释放

从表头开始,逐步释放链表中的各节点。算法的C++语言描述为:

#include<iostream>
using namespace std;

//定义节点类型
struct node
{
	double coef;	//系数为双精度型
	int exp;	   //指数为正整数
	node *next;		//指针域
};

//多项式链表的释放
void delpoly(node *head)
{
	node *p, *k;
	k = head;
	while (k != NULL)
	{
		p = k->next;
		free(k);
		k = p;
	}
	return;
}
3.多项式的输出

从表头开始,以数对的形式顺链输出各节点中的指数域与系数域的内容。算法的C++语言描述为:

#include<iostream>
using namespace std;
#include<iomanip>

//定义节点类型
struct node
{
	double coef;	//系数为双精度型
	int exp;	   //指数为正整数
	node *next;		//指针域
};

//多项式的输出
void outpoly(node *head)
{
	node *p;
	p = head;
	while (p!=NULL)
	{
		cout << setw(5) << p->exp << setw(5) << p->coef << endl;
		p = p->next;
	}
	return;
}
4.多项式的相加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2seCvKA-1602035909089)(F:\MyData\C++学习笔记\图片\多项式的相加.jpg)]

(2)若两个多项式中对应节点的指数值不相等,则复抄指数值大的那个节点中的指数值与系数值,形成一个新节点后链入头指针为 ch 的链表末尾,然后再检测指数值小的链表中的当前节点与指数值大的链表中的下一个节点。

上述过程一直循环做到两个链表中的一个链表为空,则复抄另一个链表中所有剩余节点到 ch 的链表末尾。算法的C++语言描述为:

#include<iostream>
using namespace std;

//定义节点类型
struct node
{
	double coef;	//系数为双精度型
	int exp;	   //指数为正整数
	node *next;		//指针域
};

//多项式的相加
node *addpoly(node *ah, node *bh)  //函数返回和多项式链表的头指针(ah,bh分别为a,b多项式的指针)
{
	node *k = NULL, *p, *m, *n, *ch = NULL; //ch:和多项式的指针
	int e;
	double d;	//d:和多项式的系数
	m = ah;
	n = bh;

	while (m != NULL && n != NULL)
	{
		if (m->exp == n->exp)  //两个链表当前节点的指数值相等
		{
			d = m->coef + n->coef;	//系数相加
			e = m->exp;			//复抄指数
			m = m->next;
			n = n->next;
		}
		else if (m->exp > n->exp)
		{
			//复抄链表 a 中节点的系数值与指数值
			d = m->coef;
			e = m->exp;
			m = m->next;
		}
		else
		{
			//复抄链表 b 中节点的系数值与指数值
			d = n->coef;
			e = n->exp;
			n = n->next;
		}

		if (d != 0)
		{
			p = (node*)malloc(sizeof(node));  //为链表 c 申请一个新节点
			p->exp = e;
			p->coef = d;
			p->next = NULL;

			if (ch = NULL)		//和多项式链表为空
			{
				ch = p;			//和多项式链表头指针指向新节点
			}
			else
			{
				k->next = p;		//将新节点链接到和多项式链表末尾
			}

			k = p;		//记住和多项式链表的末尾
		}
	}

	//复抄链表 a 中剩余节点
	while (m != NULL)
	{
		d = m->coef;
		e = m->exp;
		m = m->next;
		if (d != 0)
		{
			p = (node*)malloc(sizeof(node));  //为链表 c 申请一个新节点
			p->exp = e;
			p->coef = d;
			p->next = NULL;

			if (ch = NULL)		//和多项式链表为空
			{
				ch = p;			//和多项式链表头指针指向新节点
			}
			else
			{
				k->next = p;		//将新节点链接到和多项式链表末尾
			}

			k = p;		//记住和多项式链表的末尾
		}
	}

	//复抄链表 b 中剩余节点
	while (n != NULL)
	{
		d = n->coef;
		e = n->exp;
		n = n->next;
		if (d != 0)
		{
			p = (node*)malloc(sizeof(node));  //为链表 c 申请一个新节点
			p->exp = e;
			p->coef = d;
			p->next = NULL;

			if (ch = NULL)
			{
				ch = p;
			}
			else
			{
				k->next = p;
			}

			k = p;
		}
	}

	return ch;  //返回和多项式链表的头指针
}

编写一个main函数调用上述各个函数,程序如下:

int main()
{
	node *ah, *bh, *ch;
	ah = inpoly();
	bh = inpoly();
	ch = addpoly(ah, bh);

	cout << "A is:" << endl;
	outpoly(ah);

	cout << "B is:" << endl;
	outpoly(bh);

	cout << "C is:" << endl;
	outpoly(ch);

	delpoly(ch);
	delpoly(bh);
	delpoly(ah);

	system("pause");
	return 0;
}

程序的运行结果为:

please input exp and coef:3 5
please input exp and coef:1 3
please input exp and coef:0 2
please input exp and coef:-1 -1
please input exp and coef:3 -5
please input exp and coef:2 4
please input exp and coef:1 2
please input exp and coef:-1 -1
A is:
    3    5
    1    3
    0    2
B is:
    3   -5
    2    4
    1    2
C is:
    2    4
    1    5
    0    2

程序的完整代码如下:

#include<iostream>
using namespace std;
#include<iomanip>

//定义节点类型
struct node
{
	double coef;	//系数为双精度型
	int exp;	   //指数为正整数
	node *next;		//指针域
};

node *inpoly()		//函数返回多项式链表头指针
{
	node *head = NULL, *p, *k = NULL;
	int e;
	double a;
	cout << "please input exp and coef:";
	cin >> e >> a;   //输入指数与系数

	while (e >= 0)
	{
		p = (node *)malloc(sizeof(node));	//申请一个新节点p
		p->exp = e;
		p->coef = a;
		p->next = NULL;

		//链表为空
		if (head == NULL)
		{
			head = p;	//头指针指向新节点
		}
		else
		{
			k->next = p;	//原链尾结点指针指向新节点
		}

		k = p;		//记住新的链尾

		cout << "please input exp and coef:";
		cin >> e >> a;		//输入下一对指数与系数
	}

	return head;
}

//多项式链表的释放
void delpoly(node *head)
{
	node *p, *k;
	k = head;
	while (k != NULL)
	{
		p = k->next;
		free(k);
		k = p;
	}
	return;
}

//多项式的输出
void outpoly(node *head)
{
	node *p;
	p = head;
	while (p!=NULL)
	{
		cout << setw(5) << p->exp << setw(5) << p->coef << endl;
		p = p->next;
	}
	return;
}

//多项式的相加
node *addpoly(node *ah, node *bh)  //函数返回和多项式链表的头指针(ah,bh分别为a,b多项式的指针)
{
	node *k = NULL, *p, *m, *n, *ch = NULL; //ch:和多项式的指针
	int e;
	double d;	//d:和多项式的系数
	m = ah;
	n = bh;

	while (m != NULL && n != NULL)
	{
		if (m->exp == n->exp)  //两个链表当前节点的指数值相等
		{
			d = m->coef + n->coef;	//系数相加
			e = m->exp;			//复抄指数
			m = m->next;
			n = n->next;
		}
		else if (m->exp > n->exp)
		{
			//复抄链表 a 中节点的系数值与指数值
			d = m->coef;
			e = m->exp;
			m = m->next;
		}
		else
		{
			//复抄链表 b 中节点的系数值与指数值
			d = n->coef;
			e = n->exp;
			n = n->next;
		}

		if (d != 0)
		{
			p = (node*)malloc(sizeof(node));  //为链表 c 申请一个新节点
			p->exp = e;
			p->coef = d;
			p->next = NULL;

			if (ch == NULL)
			{
				ch = p;
			}
			else
			{
				k->next = p;
			}

			k = p;
		}
	}

	//复抄链表 a 中剩余节点
	while (m != NULL)
	{
		d = m->coef;
		e = m->exp;
		m = m->next;
		if (d != 0)
		{
			p = (node*)malloc(sizeof(node));  //为链表 c 申请一个新节点
			p->exp = e;
			p->coef = d;
			p->next = NULL;

			if (ch == NULL)		//和多项式链表为空
			{
				ch = p;			//和多项式链表头指针指向新节点
			}
			else
			{
				k->next = p;		//将新节点链接到和多项式链表末尾
			}

			k = p;		//记住和多项式链表的末尾
		}
	}

	//复抄链表 b 中剩余节点
	while (n != NULL)
	{
		d = n->coef;
		e = n->exp;
		n = n->next;
		if (d != 0)
		{
			p = (node*)malloc(sizeof(node));  //为链表 c 申请一个新节点
			p->exp = e;
			p->coef = d;
			p->next = NULL;

			if (ch == NULL)
			{
				ch = p;
			}
			else
			{
				k->next = p;
			}

			k = p;
		}
	}

	return ch;  //返回和多项式链表的头指针
}

int main()
{
	node *ah, *bh, *ch;
	ah = inpoly();
	bh = inpoly();
	ch = addpoly(ah, bh);

	cout << "A is:" << endl;
	outpoly(ah);

	cout << "B is:" << endl;
	outpoly(bh);

	cout << "C is:" << endl;
	outpoly(ch);

	delpoly(ch);
	delpoly(bh);
	delpoly(ah);

	system("pause");
	return 0;
}
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*Heygirl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值