【C++】User-Defined Data Type

《C++程序设计基础教程》------刘厚泉,李政伟,二零一三年九月版,学习笔记


文章目录


C++ 提供了构造用户自定义数据类型的机制,如结构体、共用体、枚举和类等。

1、结构体类型

结构体(structure) 专门用于描述类型不同、但逻辑意义相关的数据构成的聚集

结构体的成员可以是任何类型,既可以是 C++ 基本数据类型(整型、浮点型、字符型等),也可以是复杂的数据类型(如数组、指针以及其它结构体等)

结构体在C语言中就已经存在,并且在C++中得到了继承和发展。结构体在C++中的用法与C语言相似,但有一些关键的差异,特别是在C++引入了类(class)之后。

结构体在C++中非常有用,尤其是在需要组织和管理相关数据时。它们可以用于创建复杂的数据结构,如链表、树和图等。此外,结构体还可以用于在函数之间传递多个值,或者作为容器类(如 std::vectorstd::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 语言利用 mallocfree 来分配和撤销内存空间

C++ 提供了运算符 newdelete 来取代上述函数。

C++ 也兼容 mallocfree

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_ptrstd::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
相关推荐
一水鉴天几秒前
为AI聊天工具添加一个知识系统 之135 详细设计之76 通用编程语言 之6
开发语言·人工智能·架构
m0_7482475513 分钟前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
环能jvav大师30 分钟前
Electron桌面应用开发:自定义菜单
开发语言·前端·javascript·windows·electron
一水鉴天41 分钟前
为AI聊天工具添加一个知识系统 之136 详细设计之77 通用编程语言 之7
开发语言·人工智能·架构
一只小小汤圆41 分钟前
c++ std::tuple用法
开发语言·c++
Jelena1577958579243 分钟前
爬虫与翻译API接口的完美结合:开启跨语言数据处理新纪元
开发语言·数据库·爬虫
柠石榴1 小时前
【练习】【二叉树】力扣热题100 102. 二叉树的层序遍历
c++·算法·leetcode·二叉树
gyc27271 小时前
快速熟悉JavaScript
开发语言·前端·javascript
越甲八千1 小时前
C++海康相机DEMO
开发语言·c++·数码相机
天若有情6732 小时前
用 C++ 实现选择题答案随机生成器:从生活灵感走向代码实践
c++·算法·生活