
《C++程序设计基础教程》------刘厚泉,李政伟,二零一三年九月版,学习笔记
文章目录
- 1、结构体类型
- 2、结构体的使用
- 3、单向链表
-
- [3.1、 动态内存分配与回收的运算符 new 和 delete](#3.1、 动态内存分配与回收的运算符 new 和 delete)
- 3.2、单向链表的定义
- 3.3、单向链表的操作
- 4、共用体类型
- 5、枚举类型
- [6、类型定义 typedef](#6、类型定义 typedef)
C++ 提供了构造用户自定义数据类型的机制,如结构体、共用体、枚举和类等。
1、结构体类型
结构体(structure) 专门用于描述类型不同、但逻辑意义相关的数据构成的聚集
结构体的成员可以是任何类型,既可以是 C++ 基本数据类型(整型、浮点型、字符型等),也可以是复杂的数据类型(如数组、指针以及其它结构体等)
结构体在C语言中就已经存在,并且在C++中得到了继承和发展。结构体在C++中的用法与C语言相似,但有一些关键的差异,特别是在C++引入了类(class)之后。
结构体在C++中非常有用,尤其是在需要组织和管理相关数据时。它们可以用于创建复杂的数据结构,如链表、树和图等。此外,结构体还可以用于在函数之间传递多个值,或者作为容器类(如 std::vector
或 std::map
)的元素类型。
1.1、结构体类型的定义
cpp
struct 结构体类型名
{
成员列表
}; // 注意,本行的分号不能省略
别忘了分号,这是因为结构体定义本身就是条语句
eg
cpp
struct Person {
char name[50];
int age;
float height;
};
在这个例子中,Person 是一个结构体类型,它包含三个成员:一个字符数组 name 用于存储名字,一个整数 age 用于存储年龄,以及一个浮点数 height 用于存储身高。
上述结构体定义未在内存分配任何空间(并未声明变量),而是定义了一个新的数据类型
eg,结构体嵌套
cpp
#include <iostream>
using namespace std;
int main()
{
struct Date // 定义结构体 Date
{
int year;
int month;
int day;
};
struct // 定义无名结构体
{
int num;
char name[20];
char gender;
Date birthday; // 结构体嵌套
float score;
}stu3, stu4;
return 0;
}
结构体内部的成员名可以与程序中的其它变量同名,因为 C++ 把它们处理成不同的作用域
1.2、结构体变量的初始化
cpp
#include<iostream>
#include<string.h>
using namespace std;
struct Person {
char name[50];
int age;
float height;
};
int main() {
struct Person person;
cout << "初始化前:" << endl;
cout << person.name << endl;
cout << person.age << endl;
cout << person.height << endl;
strcpy(person.name, "John Doe"); // 使用strcpy来复制字符串到字符数组
person.age = 30;
person.height = 5.9f; // 注意浮点数后的'f'来表示这是一个float类型的字面量
// ...
cout << "初始化后:" << endl;
cout << person.name << endl;
cout << person.age << endl;
cout << person.height;
return 0;
}
output
cpp
初始化前:
633
1.12104e-44
初始化后:
John Doe
30
5.9
也可以用括号的形式初始化
cpp
#include<iostream>
#include<string.h>
using namespace std;
struct Person {
char name[50];
int age;
float height;
};
int main() {
struct Person person;
cout << "初始化前:" << endl;
cout << person.name << endl;
cout << person.age << endl;
cout << person.height << endl;
person = {"John Doe", 30, 5.9f};
cout << "初始化后:" << endl;
cout << person.name << endl;
cout << person.age << endl;
cout << person.height;
return 0;
}
output
cpp
初始化前:
529
1.12104e-44
初始化后:
John Doe
30
5.9
需要注意的是,C 语言中定义一个结构体变量时必须使用关键字 struct
,而 C++ 仅仅使用结构体名就可以直接定义结构体变量
c
cpp
struct Student stu1, stu2;
C++
cpp
Student stu1, stu2;
不过 C++ 编译器仍然能接受 C 语言的语法,称为向后兼容 backward compatibility,这给程序员带来了很大的方便
eg
cpp
#include<iostream>
#include<string.h>
using namespace std;
struct Person {
char name[50];
int age;
float height;
};
int main() {
Person person;
cout << "初始化前:" << endl;
cout << person.name << endl;
cout << person.age << endl;
cout << person.height << endl;
person = {"Kobe Doe", 30, 5.9f};
cout << "初始化后:" << endl;
cout << person.name << endl;
cout << person.age << endl;
cout << person.height;
return 0;
}
output
cpp
初始化前:
463
1.12104e-44
初始化后:
Kobe Doe
30
5.9
1.3、结构体变量成员的引用
圆点运算符 . 又称成员运算符,用于通过结构体变量名访问结构体成员
cpp
结构体变量名.结构体成员
如果成员本身也是结构体,可以逐级使用圆点运算符找到各级成员
eg
cpp
stud3.birthday.month
1.4、结构体与指针
当一个指针变量指向一个结构体变量时,称为结构体指针变量(简称结构体指针)
cpp
结构体名 * 结构体指针变量名
cpp
Student *pStud, stud;
pStud = &stud;
上面例子定义了结构体指针 pStud
和一个结构体变量 stud
,将结构体变量 stud
的地址赋给 pStud
,因此指针 pStud
就指向了 stud
结构体指针访问结构体变量的各个成员的形式为
cpp
结构体指针变量->成员名
等价于
cpp
(*结构体指针变量).成员名
括号不能少,去掉括号就相当于* (结构体指针变量.成员名)
eg:
cpp
#include<iostream>
#include<string.h>
using namespace std;
struct Person {
char name[50];
int age;
float height;
float weight;
};
int main() {
Person *p, person;
person = {"Kobe Doe", 30, 5.9f};
person.weight = 61.5;
p = &person;
cout << "初始化后:" << endl;
cout << person.name << endl;
cout << p->age << endl;
cout << (*p).height << endl;
cout << p->weight << endl;
return 0;
}
output
cpp
初始化后:
Kobe Doe
30
5.9
61.5
1.5、附录------结构体和类的区别
在 C++ 中,结构体和类在语法上非常相似,但它们有一些默认的差异:
-
默认情况下,结构体的成员是 public 的,而类的成员是 private 的。这意味着在结构体中定义的成员可以直接从外部访问,而在类中定义的成员则不能(除非它们被显式地声明为 public)。
-
结构体通常用于表示简单的数据结构,而类则用于表示具有更复杂行为和属性的对象。
然而,这些差异并不是绝对的。在 C++ 中,你可以使用 class 关键字来定义一个与结构体行为相同的类型,只需将所有成员声明为 public 即可。同样地,你也可以在结构体中声明 private 或 protected 成员,并为其添加成员函数,从而使其表现得像类一样。
2、结构体的使用
2.1、结构体与函数
(1)结构体变量成员作函数的参数
(2)结构体变量作函数的参数或返回值
结构体变量作为实参传给函数,结构体变量的成员较多,占用内存空间大,传送花费的代价较高,所以不提倡这种作法
在函数调用时如果要传递结构体型参数,常用的方法是采取传引用方式或者传递变量地址方式。
eg
cpp
#include<iostream>
using namespace std;
struct Student
{
int num;
char name[20];
char sex;
float score;
};
void display(Student &stu) //形参为引用
{
cout << stu.num << "\t" << stu.name << "\t" << stu.sex << "\t" << stu.score << endl;
}
void Value(Student stu) // 形参为结构体变量
{
stu.score++;
}
void Reference(Student &stu)
{
stu.score++;
}
void Point(Student *stu)
{
stu->score++;
}
int main() {
cout<<"初始化结构体:"<< endl;
Student stu = {102, "Kobe", 'M', 97.5};
display(stu);
cout<<"传值后:"<<endl;
Value(stu); // 实参为结构体变量,改变不了成员的值
display(stu);
cout<<"传引用后:"<<endl;
Reference(stu);
display(stu);
cout<<"传指针后:"<<endl;
Point(&stu);
display(stu);
return 0;
}
output
cpp
初始化结构体:
102 Kobe M 97.5
传值后:
102 Kobe M 97.5
传引用后:
102 Kobe M 98.5
传指针后:
102 Kobe M 99.5
采用传值方式时,void Value(Student stu)
,对形参成员的修改不会影响实参的值
而采用传引用和指针方式时,被调函数都可以修改实参的值。
2.2、结构体与数组
有时候需要将结构体与数组结合起来,形成一个结构体数组,用于存储多个结构体实例。
cpp
#include<iostream>
using namespace std;
struct Student
{
int num;
char name[20];
char sex;
float score;
};
void display(Student &stu) //形参为引用
{
cout << stu.num << "\t" << stu.name << "\t" << stu.sex << "\t" << stu.score << endl;
}
int main() {
Student stu[5] = {
{101, "Zhang San", 'M', 86},
{102, "Li Si", 'W', 98},
{103, "Wang Wu", 'M', 77},
{104, "Zhao Liu", 'W', 92},
{105, "Sun Qi", 'M', 66},
};
for(int i=0; i<5; i++)
display(stu[i]);
return 0;
}
output
cpp
101 Zhang San M 86
102 Li Si W 98
103 Wang Wu M 77
104 Zhao Liu W 92
105 Sun Qi M 66
eg 7-2,将学生档案记录按照学号从小到大进行排序
cpp
#include<iostream>
using namespace std;
struct Student
{
int num;
char name[20];
char sex;
float score;
};
void display(Student stu[], int len) //形参为引用
{
for(int i=0; i<len; i++)
cout << stu[i].num << "\t" << stu[i].name << "\t" << stu[i].sex << "\t" << stu[i].score << endl;
}
void sort(Student stu[], int len)
{
for(int i=0; i<len-1; i++)
{
for(int j=i+1; j<len; j++)
{
if(stu[j].num<stu[i].num)
{
int tmp;
tmp = stu[j].num;
stu[j].num = stu[i].num;
stu[i].num = tmp;
}
}
}
}
int main() {
Student stu[5] =
{
{111, "Zhang San", 'M', 86},
{102, "Li Si", 'W', 98},
{153, "Wang Wu", 'M', 77},
{134, "Zhao Liu", 'W', 92},
{105, "Sun Qi", 'M', 66},
};
cout << "初始化学生信息:"<<endl;
display(stu, 5);
cout << "按学号排序后学生的信息:"<<endl;
sort(stu, 5);
display(stu, 5);
return 0;
}
output
cpp
初始化学生信息:
111 Zhang San M 86
102 Li Si W 98
153 Wang Wu M 77
134 Zhao Liu W 92
105 Sun Qi M 66
按学号排序后学生的信息:
102 Zhang San M 86
105 Li Si W 98
111 Wang Wu M 77
134 Zhao Liu W 92
153 Sun Qi M 66
3、单向链表
链表是通过指针链接在一起的一组数据(称为"结点",Node)。
链表的每个结点都是同类型的结构体变量,其中有的成员用于存储结点的数据信息(Data),即用户实际需要的数据;有的成员是指向同类型结构体的指针变量(Next),指向链表中的其它结点。
结点中只有一个指针域的链表称为单向链表(Singly Linked List)。
头节点(Head Node):通常为了操作方便,会引入一个头节点,它本身不存储实际数据,而是指向链表的第一个实际数据节点。
单向链表是一种基础且重要的数据结构,适用于需要频繁进行插入和删除操作的场景。尽管其访问速度不如数组快(因为需要从头节点开始遍历),但其动态性和灵活性在很多应用中非常有用。
3.1、 动态内存分配与回收的运算符 new 和 delete
C 语言利用 malloc
和 free
来分配和撤销内存空间
C++ 提供了运算符 new
和 delete
来取代上述函数。
C++ 也兼容 malloc
和 free
cpp
float *pf = new float(3.14); // 开辟一个存放单精度数的空间且赋初始值3.14,并将返回地址赋给指针变量 p
char *pc = new char[10]; // 开辟一个存放字符数组(含10个元素)的空间,并返回首元素地址赋给指针 pc
new int[5][4]; //开辟一个存放二维整型数组的空间,返回首元素的地址
在C++编程中,new 和 delete 操作符被用来在堆(heap)上动态地分配和释放内存。这是与在栈(stack)上自动分配内存(例如,通过局部变量)相对的一种内存管理方式。
new 操作符用于在堆上分配内存并初始化一个对象。它实际上做了两件事:
- 分配足够的内存来存储指定类型的对象。
- 调用该类型的构造函数(如果有的话)来初始化这块内存中的对象。
使用 new 分配内存时,如果内存分配失败,它将抛出一个 std::bad_alloc
异常(除非你使用了 nothrow 版本的 new,它会返回一个空指针而不是抛出异常,用户可以根据返回指针的值判断分配空间是否成功)。
要访问 new 所开辟的空间,无法直接通过变量名进行访问,只能通过返回指针进行间接访问。
delete 操作符
delete 操作符用于释放之前用 new 分配的内存,并调用对象的析构函数(如果有的话)来清理资源。对于用 new[] 分配的数组,应该使用 delete[] 来释放。
使用delete(或delete[])时,如果传递给它们的指针是无效的(例如,空指针或未通过 new 分配的指针),结果是未定义的。因此,务必确保只释放你确实分配过的内存。
cpp
delete pf; // 对变量,释放内存并调用析构函数
delete[] pc; // 对数组,释放数组内存(不调用析构函数对于基本类型,但对于类类型会调用)
常见的错误
- 释放同一块内存多次。
- 释放未分配的内存或空指针(虽然对于空指针是安全的,但释放未分配的内存是未定义行为)。
- 忘记调用析构函数(当你手动管理内存时,这通常不是问题,因为 delete 会为你调用它;但在实现自己的内存管理器或进行底层操作时可能会遇到)。
智能指针
为了简化内存管理并减少内存泄漏的风险,C++11 引入了智能指针,如 std::unique_ptr
和 std::shared_ptr
。这些智能指针类自动管理它们所指向的对象的生命周期,当智能指针被销毁或超出作用域时,它们会自动释放所管理的内存。
3.2、单向链表的定义
cpp
struct Node
{
int data;
Node * next;
};

实际上,链表中的结点可以根据需要保存各种类型的数据。
cpp
struct Student
{
int num;
char name[20];
char sex;
float score;
Student *next;
};
3.3、单向链表的操作
建立链表、遍历链表、查找链表,以及在链表中插入或者删除结点
(1)建立链表
eg 7-3 建立链表
cpp
#include<iostream>
using namespace std;
struct Node
{
int data;
Node * next;
};
Node *CreateList(int n)
{
Node *tmp, *head, *tail;
int num;
cin >> num;
head = new Node; // 动态创建新结点,并将结点首地址返回给指针
if(head==NULL) // 内存分配检测
{
cout << "No memory available";
return NULL;
}
else // 设置第一个结点成员
{
head->data = num;
head->next = NULL;
tail = head; // 尾部指针后移
}
for(int i=1; i<n; i++)
{
cin >> num;
tmp = new Node; //为新结点动态分配内存
if(tmp==NULL)
{
cout << "No memory available";
return head;
}
else
{
tmp->data = num;
tmp->next = NULL;
tail->next = tmp; // 将新创建的第 i 个新节点链入当前链表的结尾
tail = tmp; // 修改尾指针,确保其指向当前链表的末尾
}
}
return head;
}
int main() {
int n;
Node * listhead = NULL;
cout << "please enter the number of Node:";
cin >> n;
if(n>0)
listhead = CreateList(n);
for(int i=0; i<n; i++)
{
cout << listhead->data << " ";
listhead = listhead->next;
}
return 0;
}
output
cpp
please enter the number of Node:10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10

建立链表涉及到结点结构体,head 指针,tail 指针和新增的 tmp 结点
在链表建立的过程中,先读入的数据更靠近链表的头部,后读入的数据更靠近链表的尾部,因此称这种方法为"尾插法"
与之对应,还可以使用头插法建立链表
(2)遍历链表
cpp
Node *pCurNode = head;
while(pCurNode)
pCurNode = pCurNode->next;
eg 7-4 遍历链表
cpp
#include<iostream>
using namespace std;
struct Node
{
int data;
Node * next;
};
Node *CreateList(int n)
{
Node *tmp, *head, *tail;
int num;
cin >> num;
head = new Node; // 动态创建新结点,并将结点首地址返回给指针
if(head==NULL) // 内存分配检测
{
cout << "No memory available";
return NULL;
}
else // 设置第一个结点成员
{
head->data = num;
head->next = NULL;
tail = head; // 尾部指针后移
}
for(int i=1; i<n; i++)
{
cin >> num;
tmp = new Node; //为新结点动态分配内存
if(tmp==NULL)
{
cout << "No memory available";
return head;
}
else
{
tmp->data = num;
tmp->next = NULL;
tail->next = tmp; // 将新创建的第 i 个新节点链入当前链表的结尾
tail = tmp; // 修改尾指针,确保其指向当前链表的末尾
}
}
return head;
}
int main() {
int n;
Node * listhead = NULL;
cout << "please enter the number of Node:";
cin >> n;
if(n>0)
listhead = CreateList(n);
Node *pCur=listhead;
while(pCur)
{
cout << pCur->data << " ";
pCur = pCur->next;
}
return 0;
}
output
cpp
please enter the number of Node:10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
建立链表还是 eg 7-3 的方法,遍历的时候从 for 改成了 while
(3)链表的查找
eg 7-5,基于 eg 7-3 和 eg 7-4,实现链表的查找
cpp
#include<iostream>
using namespace std;
struct Node
{
int data;
Node * next;
};
Node *CreateList(int n)
{
Node *tmp, *head, *tail;
int num;
cin >> num;
head = new Node; // 动态创建新结点,并将结点首地址返回给指针
if(head==NULL) // 内存分配检测
{
cout << "No memory available";
return NULL;
}
else // 设置第一个结点成员
{
head->data = num;
head->next = NULL;
tail = head; // 尾部指针后移
}
for(int i=1; i<n; i++)
{
cin >> num;
tmp = new Node; //为新结点动态分配内存
if(tmp==NULL)
{
cout << "No memory available";
return head;
}
else
{
tmp->data = num;
tmp->next = NULL;
tail->next = tmp; // 将新创建的第 i 个新节点链入当前链表的结尾
tail = tmp; // 修改尾指针,确保其指向当前链表的末尾
}
}
return head;
}
Node *FindList(Node *head, int num)
{
while(head)
{
if(head->data == num)
{
cout << head->data << endl;
return head;
}
else
head = head->next;
}
cout << "链表中没有对应的元素" << endl;
return NULL;
}
int main() {
int n;
Node * listhead = NULL;
cout << "please enter the number of Node:";
cin >> n;
if(n>0)
listhead = CreateList(n);
// 遍历
Node *pCur=listhead;
while(pCur)
{
cout << pCur->data << " ";
pCur = pCur->next;
}
// 查找
Node *pfind = FindList(listhead, 5);
cout << pfind->data << endl;
Node *pfind2 = FindList(listhead, 11);
return 0;
}
我们新定义了 Node *FindList(Node *head, int num)
查找函数,查找到返回指针,否则输出未查找到
output
cpp
please enter the number of Node:10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
5
链表中没有对应的元素
(4)链表中插入结点
链表的优点之一就是灵活性好,非常适合数据的动态变化
在链表中插入和删除结点比数组中增加或减少一个元素要容易的多
单向链表不能直接引用前驱结点(仅指向后继结点),需要在遍历链表定位插入点或者删除点的同时,注意记录前驱结点的位置
eg 7-6 编写一个插入函数,将输入整数按照从小到大的顺序插入到链表中合适位置(假设链表中的数据已经按照从小到大的顺序排列)
cpp
#include<iostream>
using namespace std;
struct Node
{
int data;
Node * next;
};
Node *CreateList(int n)
{
Node *tmp, *head, *tail;
int num;
cin >> num;
head = new Node; // 动态创建新结点,并将结点首地址返回给指针
if(head==NULL) // 内存分配检测
{
cout << "No memory available";
return NULL;
}
else // 设置第一个结点成员
{
head->data = num;
head->next = NULL;
tail = head; // 尾部指针后移
}
for(int i=1; i<n; i++)
{
cin >> num;
tmp = new Node; //为新结点动态分配内存
if(tmp==NULL)
{
cout << "No memory available";
return head;
}
else
{
tmp->data = num;
tmp->next = NULL;
tail->next = tmp; // 将新创建的第 i 个新节点链入当前链表的结尾
tail = tmp; // 修改尾指针,确保其指向当前链表的末尾
}
}
return head;
}
Node *FindList(Node *head, int num)
{
while(head)
{
if(head->data == num)
{
// cout << head->data << endl;
return head;
}
else
head = head->next;
}
cout << "链表中没有对应的元素:" << num << endl;
return NULL;
}
Node * InsertNode(Node *head, int num)
{
Node *pCur = head; //当前结点
Node *pre = NULL; // 前驱结点
Node *newNode = new Node; // 新结点
if(newNode == NULL)
{
cout << "No memory available!";
return head;
}
newNode->data = num;
// 遍历链表,寻找插入位置
while((pCur !=NULL) && (pCur->data<num))
{
pre = pCur;
pCur = pCur->next;
}
// 插在当前结点前面
if(pre==NULL) // head 结点
{
newNode->next = pCur;
return newNode;
}
else // 核心代码
{
pre->next = newNode;
newNode->next = pCur; // 先头后尾
return head;
}
}
//1 2 4 7 8 9
int main() {
int n;
Node * listhead = NULL;
cout << "please enter the number of Node:";
cin >> n;
if(n>0)
listhead = CreateList(n);
// 遍历
Node *pCur=listhead;
while(pCur)
{
cout << pCur->data << " ";
pCur = pCur->next;
}
cout << endl;
// 查找
Node *pfind = FindList(listhead, 5);
//插入
cout << "插入元素 5:" << endl;
Node *pInsert = InsertNode(listhead, 5);
// 遍历
while(pInsert)
{
cout << pInsert->data << " ";
pInsert = pInsert->next;
}
cout << endl;
return 0;
}
引入了 Node * InsertNode(Node *head, int num)
,注意插入头节点的时候和插入中间的区别
新结点先插入头,再插入尾
注意遍历的时候记录前驱结点
output
cpp
please enter the number of Node:10
1 2 3 4 6 7 8 9 10 11
1 2 3 4 6 7 8 9 10 11
链表中没有对应的元素:5
插入元素 5:
1 2 3 4 5 6 7 8 9 10 11
插入 0 试试
cpp
cout << "插入元素 0:" << endl;
Node *pInsert = InsertNode(listhead, 0);
output
cpp
please enter the number of Node:10
1 2 3 4 6 7 8 9 10 11
1 2 3 4 6 7 8 9 10 11
链表中没有对应的元素:5
插入元素 0:
0 1 2 3 4 6 7 8 9 10 11
插入 12
cpp
cout << "插入元素 12:" << endl;
Node *pInsert = InsertNode(listhead, 12);
output
cpp
1 2 3 4 6 7 8 9 10 11
1 2 3 4 6 7 8 9 10 11
链表中没有对应的元素:5
插入元素 12:
1 2 3 4 6 7 8 9 10 11 12
(5)链表中删除节点
eg 7-7 删除单链表结点
cpp
#include<iostream>
using namespace std;
struct Node
{
int data;
Node * next;
};
Node *CreateList(int n)
{
Node *tmp, *head, *tail;
int num;
cin >> num;
head = new Node; // 动态创建新结点,并将结点首地址返回给指针
if(head==NULL) // 内存分配检测
{
cout << "No memory available";
return NULL;
}
else // 设置第一个结点成员
{
head->data = num;
head->next = NULL;
tail = head; // 尾部指针后移
}
for(int i=1; i<n; i++)
{
cin >> num;
tmp = new Node; //为新结点动态分配内存
if(tmp==NULL)
{
cout << "No memory available";
return head;
}
else
{
tmp->data = num;
tmp->next = NULL;
tail->next = tmp; // 将新创建的第 i 个新节点链入当前链表的结尾
tail = tmp; // 修改尾指针,确保其指向当前链表的末尾
}
}
return head;
}
Node *FindList(Node *head, int num)
{
while(head)
{
if(head->data == num)
{
// cout << head->data << endl;
return head;
}
else
head = head->next;
}
cout << "链表中没有对应的元素:" << num << endl;
return NULL;
}
Node * InsertNode(Node *head, int num)
{
Node *pCur = head; //当前结点
Node *pre = NULL; // 前驱结点
Node *newNode = new Node; // 新结点
if(newNode == NULL)
{
cout << "No memory available!";
return head;
}
newNode->data = num;
// 遍历链表,寻找插入位置
while((pCur !=NULL) && (pCur->data<num))
{
pre = pCur;
pCur = pCur->next;
}
// 插在当前结点前面
if(pre==NULL) // head 结点
{
newNode->next = pCur;
return newNode;
}
else
{
pre->next = newNode;
newNode->next = pCur; // 先头后尾
return head;
}
}
Node *DeleteNode(Node *head, int num)
{
Node *pCur = head;
Node *pre = NULL;
if(head==NULL) // 空链表,直接返回
return NULL;
while((pCur!=NULL) && (pCur->data != num))
{
pre = pCur;
pCur = pCur->next;
}
if(pCur == NULL)
{
cout << "can't find" << num << "in the list." << endl;
return head;
}
if(pCur == head) // 被删除的是首结点
head = head->next;
else
pre->next = pCur->next;
delete pCur;
return head;
}
void PrintNode(Node *pCur)
{
while(pCur)
{
cout << pCur->data << " ";
pCur = pCur->next;
}
cout << endl;
}
int main() {
int n;
Node * listhead = NULL;
cout << "please enter the number of Node:";
cin >> n;
if(n>0)
listhead = CreateList(n);
cout<< "遍历链表:"<<endl;
// 遍历链表
PrintNode(listhead);
// 查找
Node *pfind = FindList(listhead, 5);
//插入
cout << "插入元素 5" << endl;
Node *pInsert = InsertNode(listhead, 5);
// 遍历
while(pInsert)
{
cout << pInsert->data << " ";
pInsert = pInsert->next;
}
cout << endl;
cout << "删除 5:"<< endl;
//删除
Node * delNode = DeleteNode(listhead, 5);
// 遍历
while(delNode)
{
cout << delNode->data << " ";
delNode = delNode->next;
}
cout << endl;
return 0;
}
删除元素5
output
cpp
please enter the number of Node:10
1 2 3 4 6 7 8 9 10 11
遍历链表:
1 2 3 4 6 7 8 9 10 11
链表中没有对应的元素:5
插入元素 5
1 2 3 4 5 6 7 8 9 10 11
删除 5:
1 2 3 4 6 7 8 9 10 11
删除元素 1,5,11
cpp
//删除
Node * delNode = DeleteNode(listhead, 5);
Node * delNode1 = DeleteNode(delNode, 1);
Node * delNode2 = DeleteNode(delNode1, 11);
// 遍历
while(delNode2)
{
cout << delNode2->data << " ";
delNode2 = delNode2->next;
}
cout << endl;
output
cpp
please enter the number of Node:10
1 2 3 4 6 7 8 9 10 11
遍历链表:
1 2 3 4 6 7 8 9 10 11
链表中没有对应的元素:5
插入元素 5
1 2 3 4 5 6 7 8 9 10 11
删除 1,5,11:
2 3 4 6 7 8 9 10
4、共用体类型
共用体(union)类型,又称为联合
cpp
union Data
{
int i;
char ch;
double d;
}a,b;

内存共享:共用体的所有成员共享同一块内存区域,因此,一次只能存储一个成员的值。如果存储了一个新的成员值,之前存储的成员值将被覆盖。
大小:共用体的大小至少是其最大成员的大小。编译器会确保共用体有足够的空间来存储其最大的成员。
类型安全:由于共用体允许不同类型的成员共享内存,因此在使用时需要特别小心,以避免类型不匹配的问题。通常,需要手动管理当前存储的成员类型。
cpp
#include <iostream>
#include <string.h> // 用于 strcpy
using namespace std;
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
// 存储整数
data.i = 42;
cout << "Integer: " << data.i << endl;
// 存储浮点数(覆盖之前的整数)
data.f = 3.14;
cout << "Float: " << data.f << endl;
// 存储字符串(覆盖之前的浮点数)
strcpy(data.str, "Hello, World!");
cout << "String: " << data.str << endl;
return 0;
}
output
cpp
Integer: 42
Float: 3.14
String: Hello, World!
共用体通常用于以下场景:
- 节省内存:当需要在同一内存位置存储不同类型的数据时,可以使用共用体来节省内存。
- 硬件编程:在嵌入式系统和硬件编程中,共用体常用于处理硬件寄存器的不同表示(例如,将寄存器值解释为整数或位字段)。
- 协议解析:在网络协议解析中,共用体可以用于解析具有不同字段但共享相同内存区域的数据包。
共用体和结构体的区别
(1)内存使用方式
- 结构体:结构体的各个成员占用不同的内存空间,它们之间互不影响。结构体占用的内存空间是其所有成员占用的内存空间的总和(但成员之间可能会存在内存对齐导致的空隙)。
- 共用体:共用体的所有成员共享同一块内存空间,这意味着在任何给定时间,共用体只能存储其成员中的一个值。如果存储了一个新的成员值,之前存储的成员值将被覆盖。共用体占用的内存空间等于其最大成员占用的内存空间。
(2)数据存储特性
- 结构体:由于结构体的成员各自占用独立的内存空间,因此可以同时存储所有成员的值。
- 共用体:由于共用体的成员共享内存,因此一次只能存储一个成员的值。如果尝试访问未存储的成员,将导致未定义行为。
(3)使用场景
- 结构体:结构体通常用于将多个相关的数据项组合在一起,以便作为一个整体进行管理和操作。例如,可以定义一个结构体来表示一个人的姓名、年龄和地址等信息。
- 共用体:共用体通常用于需要在同一内存位置存储不同类型数据的场景,例如硬件编程中的寄存器访问、网络协议解析等。在这些场景中,使用共用体可以节省内存空间并提高代码的可读性和可维护性。
(4)安全性与类型检查
- 结构体:结构体提供了更好的类型安全性和类型检查。由于结构体的成员各自占用独立的内存空间,因此访问成员时不会发生类型冲突。
- 共用体:共用体不提供类型安全性。在访问共用体的成员之前,需要手动确保当前存储的成员类型与要访问的成员类型相匹配。否则,可能会导致数据损坏或程序崩溃。
(5)构造函数与析构函数
-
结构体:结构体可以包含构造函数和析构函数(尽管在实际编程中很少这样做)。这使得结构体在初始化和销毁时可以执行特定的操作。
-
共用体:共用体没有构造函数和析构函数。这意味着在初始化共用体时不能执行任何特定的操作,并且在销毁共用体时也不会执行任何清理操作。
5、枚举类型
在C++中,枚举类型(enum)是一种用户定义的数据类型,它允许程序员为整型值指定更易读的名字。
枚举类型的主要目的是增加代码的可读性和可维护性。
cpp
enum 枚举名{标识符1, 标识符2, ..., 标识符n};
eg
cpp
enum weekday{Sun, Mon, Tue, Wed, Thu, Fri, Sat};
每个枚举常量对应一个整数,在默认情况下,整数从 0 开始,也可以在声明枚举类型时另行指定枚举元素的值
eg
cpp
enum Color {
RED = 1,
GREEN = 3,
BLUE = 5,
YELLOW = 7
};
eg
cpp
#include<iostream>
using namespace std;
int main()
{
enum weekday{ Sun, Mon, Tue, Wed = 4, Thu, Fri = 9, Sat };
weekday workday = Mon;
cout << workday << endl;
cout << Tue << endl;
cout << Wed << endl;
cout << Thu << endl;
cout << Sat << endl;
return 0;
}
我们声明了一个 weekday
类型的变量 workday
并将其初始化为 Mon
output
cpp
1
2
4
5
10
eg
cpp
#include<iostream>
using namespace std;
int main()
{
enum weekday{ Sun, Mon, Tue, Wed = 4, Thu, Fri = 9, Sat };
weekday workday = Mon;
cout << workday << endl;
workday = Fri;
cout << workday << endl;
return 0;
}
output
cpp
1
9
6、类型定义 typedef
在C++中,typedef 关键字用于为现有的数据类型定义一个新的名称(别名)。
这对于简化复杂的数据类型声明、提高代码的可读性和可维护性非常有用。
cpp
typedef existing_type new_type_name;
eg:
cpp
typedef int INTEGER; // 用 INTEGER 代表 int
typedef float REAL; // 用 REAL 代替 float
eg 为复杂类型定义别名
cpp
typedef unsigned long long int ulonglong;
typedef const char* cstring;
以下是等价的
cpp
int i,j;
float a,b;
等价于
cpp
INTEGER i,j;
REAL a,b;
必须牢记,typedef 并未创建任何新的数据类型,仅仅为已存在类型定义了一个别名
eg
cpp
typedef struct {
int x;
int y;
} Point;
在这个例子中,我们定义了一个匿名结构体,并使用 typedef
为其创建了一个名为 Point 的别名。之后,我们可以使用 Point 来声明这种类型的变量。
cpp
#include<iostream>
using namespace std;
int main()
{
typedef struct {
int x;
int y;
} Point;
Point xy;
xy.x = 1;
xy.y = 2;
cout << xy.x << " " << xy.y << endl;
return 0;
}
output
cpp
1 2
eg
cpp
#include<iostream>
using namespace std;
int main()
{
typedef struct {
int month;
int day;
int year;
} DATE, *PT;
DATE myBirthday;
PT p = &myBirthday;
p->month = 6;
p->day = 8;
p->year = 2008;
cout << myBirthday.year << " " << myBirthday.month << " " << myBirthday.day << endl;
return 0;
}
DATA
是结构体类型名(而非结构体变量名),PT
是结构体指针类型名
output
cpp
2008 6 8