学习面向对象编程之前的准备工作(二)

        综述

本次学习的所有知识点如下: 

/*
 * C++ struct 使用
 */
 /*
 * Filename:    StructDemo.cpp
 * AuthorName:  xxx
 * Date:        2025.3.10
 * Email:       xxxxxxxxxxxxxx
 * Function:    Demonstration to how to use the structure of C++.
 */

#include 
#include 

using namespace std;

// 结构体的声明
struct Birthday
{
    int year;
    int month;
    int day;
}   ;//注意结尾的;必不可少

struct time1
{
    int hour;
    int minute;
    int second;
}t1,t2{0,0,0},t3={10,0,0};
// 声明了三个变量t1,t2,t3,其中t2、t3通过初始化列表进行了初始化


struct time2
{
    int hour;
    int minute;
    int second;
    //默认的构造函数

    //不用C中struct的地方,C++struct允许定义成员函数,默认访问属性和数据成员一样,均为public
    void print1()
    {
        cout << hour << ", " << minute << ", " << second << endl;
    }
    void print2(); // 成员函数的声明
}t21,t22{12,0,0},t23={20,0,0},t24{};
// 声明了两个变量t21,t22,t23,t24其中t22、t23,t24通过初始化列表进行了初始化

// 在外部定义结构体的成员函数,需要用到结构体类型+全局作用域运算符来限定print2属于结构体类型time2
void time2::print2()
{
    cout << hour << ": " << minute << ": " << second << endl;
}

//unsigned int age;
//typedef unsigned int uintAge;
//uintAge age1;

// 使用typedef为结构体声明别名
typedef struct student1{
      string name;
	  int number;
	  int age;
	  Birthday birth;
	  struct student1 *pnext; // 自引用指针
}Student1,*Ptrstudent1;
// Student1是类型,实际是struct student1的简写;
// Ptrstudent1也是类型,实际是struct student1 *的简写;
// 这种声明方式,特别重要,务必掌握,数据结构的基础
//typedef      struct student1 *         Ptrstudent1;
//typedef       struct student1        Student1;


// char *name = "name";
typedef struct student2{
      char name[20]; // 可以是char *name吗?
	  int number;
	  int age;
	  Birthday birth;
	  struct student2 *pnext;
}Student2,*Ptrstudent2;
// Student2是类型,实际是struct student2的简写;
// Ptrstudent2也是类型,实际是struct student2 *的简写;


typedef struct student3{
      char *name; // 可以是char *name,但需要动态分配内存
	  int number;
	  int age;
	  Birthday birth;
	  struct student2 *pnext;

	  student3()// 构造函数,结构体变量生成时自动调用
	  {
	      name = new char [20];
	  }

	  ~student3()// 析构函数,结构体变量销毁时自动调用
	  {
	      delete [] name;
	  }

	  void print()
	  {
        cout << "学生姓名" << name << endl;
        cout << "学生年龄" << age << endl;
        cout << "学生学号" << number << endl;
        cout << "学生生日年份" << birth.year << endl;
        cout << "学生生日月份" << birth.month << endl;
        cout << "学生生日日子" << birth.day << endl;
        cout << "\n\n";
	  }
}Student3,*Ptrstudent3;
// Student3是类型,实际是struct student3的简写;
// Ptrstudent3也是类型,实际是struct student3 *的简写;


// 结构中的位字段
struct test
{
    unsigned int SN : 4; // 4 bits for SN value
    unsigned int : 4; // 4 bits unused,通常使用没有名称的字段来提供间距
    bool goodIn : 1;
    bool goodTorgle : 1;
};

void stu1()
{
    /*三种定义方法*/
    struct student1 *p = new student1; // struct可以省略
    Student1 *p1 = new Student1;
    Ptrstudent1 p2 = new Student1;
    student1 *p3=p;
    student1 *p4=p;

//    int a,b;
//    int *a,*b;

    /*有关信息的初始化*/
    p->name = "Tom";
    p->age = 20;
    p->number = 21101;
    p->birth.day = 6;
    p->birth.month = 6;
    p->birth.year = 2002;
    p->pnext = NULL;

    p1->name = "Jerry";
    p1->age = 20;
    p1->number = 21102;
    p1->birth.day = 5;
    p1->birth.month = 5;
    p1->birth.year = 2002;
    p->pnext = p1; // 把第一个学生和第二个学生的信息链接在一起
    p1->pnext =	NULL;

    p2->name = "shirlly";
    p2->age = 20;
    p2->number = 21103;
    p2->birth.day = 1;
    p2->birth.month = 1;
    p2->birth.year = 2002;
    p1->pnext = p2;// 把第二个学生和第三个学生的信息链接在一起
    p2->pnext = NULL;

    /*信息的输出*/
    for(; p3 != NULL; p3 = p3->pnext)
    {
        cout << "学生姓名" << p3->name << endl;
        cout << "学生年龄" << p3->age << endl;
        cout << "学生学号" << p3->number << endl;
        cout << "学生生日年份" << p3->birth.year << endl;
        cout << "学生生日月份" << p3->birth.month << endl;
        cout << "学生生日日子" << p3->birth.day << endl;
        cout << "\n\n";
    }

    //结束时销毁链表
//    for(p3=p; p3!=NULL; )
//    {
//        p4=p3;
//        p3=p3->pnext;
//        delete p4;
//    }
    delete p;
    delete p1;
    delete p2;
    p=NULL;
    p1=NULL;
    p2=NULL;
}

void stu2()
{
    /*三种定义方法*/
    struct student2 *head = NULL;//头结点,用来存放首地址
    Student2 *p1, *p2 ;
    p1 = p2 = new Student2;
    int N = 0;  //用来记录学生的数量

    while(true)
    {
        cout<<"请输入学生的学号(输入学号以0结束信息输入):";
        cin>>p1->number;

        if(p1->number == 0)  //判断是否结束学生信息的输入
        {
            delete p1;
            p1=NULL;
            break;
        }

        //如果输入学号不为0,则继续输入学生信息
        N++;

        cout<<"请输入学生的姓名:";
        cin>>p1->name;
        cout<<"请输入学生的年龄:";
        cin>>p1->age;
        cout<<"请输入学生的生日年份:";
        cin>>p1->birth.year;
        cout<<"请输入学生的生日月份:";
        cin>>p1->birth.month;
        cout<<"请输入学生的生日日子:";
        cin>>p1->birth.day;

        if(N == 1)
            head = p1;
        else
            p2->pnext = p1;
        p2 = p1; // p2表示学生链表的最后一个学生的信息
        p1 = new student2;
    }

    p2->pnext = NULL;

    Student2 *p = head;

    //将学生信息输出
    cout << "\n\n一共有 " << N << " 个学生信息,";
    cout << "详细信息为:"<pnext)
    {
        cout << "学生姓名" << p->name << endl;
        cout << "学生年龄" << p->age << endl;
        cout << "学生学号" << p->number << endl;
        cout << "学生生日年份" << p->birth.year << endl;
        cout << "学生生日月份" << p->birth.month << endl;
        cout << "学生生日日子" << p->birth.day << endl;
        cout << "\n\n";
    }

    //结束时销毁链表
    for(p=head; p!=NULL; )
    {
        p1=p;
        p=p->pnext;
        delete p1;
    }
}

void stu3()
{
    // struct 数组
    student1 stuarray[3];
    stuarray[0].name = "Tom";
    stuarray[0].age = 20;
    stuarray[0].number = 21101;
    stuarray[0].birth.day = 6;
    stuarray[0].birth.month = 6;
    stuarray[0].birth.year = 2002;
    stuarray[0].pnext = NULL;

    stuarray[1].name = "Jerry";
    stuarray[1].age = 20;
    stuarray[1].number = 21102;
    stuarray[1].birth.day = 5;
    stuarray[1].birth.month = 5;
    stuarray[1].birth.year = 2002;
    stuarray[1].pnext =	NULL;

    stuarray[2].name = "shirlly";
    stuarray[2].age = 20;
    stuarray[2].number = 21103;
    stuarray[2].birth.day = 1;
    stuarray[2].birth.month = 1;
    stuarray[2].birth.year = 2002;
    stuarray[2].pnext = NULL;

    /*信息的输出*/
    cout << "Function: stu3() calling...\n";
    for(int i=0; i<3; i++)
    {
        cout << "学生姓名" << stuarray[i].name << endl;
        cout << "学生年龄" << stuarray[i].age << endl;
        cout << "学生学号" << stuarray[i].number << endl;
        cout << "学生生日年份" << stuarray[i].birth.year << endl;
        cout << "学生生日月份" << stuarray[i].birth.month << endl;
        cout << "学生生日日子" << stuarray[i].birth.day << endl;
        cout << "\n\n";
    }
}

int main()
{
    Birthday day1;
    day1.year=2002;
    day1.month=5;
    day1.day=17;

    Birthday day2={2003,2,14};

    cout << "Day1++++++++++++++++++++++++++++++++" << endl;
    cout << day1.year << " "; // 圆点运算符叫成员访问符号
    cout << day1.month << " ";
    cout << day1.day << endl;
    cout << "Day1++++++++++++++++++++++++++++++++" << endl;

    cout << "Day2++++++++++++++++++++++++++++++++" << endl;
    cout << day2.year << " ";
    cout << day2.month << " ";
    cout << day2.day << endl;
    cout << "Day2++++++++++++++++++++++++++++++++" << endl;

    t1.hour=19;
    t1.minute=52;
    t1.second=58;
    cout << "t1: " << t1.hour << " " << t1.minute << " " << t1.second << endl;
    cout << "t2: " << t2.hour << " " << t2.minute << " " << t2.second << endl;
    cout << "t3: " << t3.hour << " " << t3.minute << " " << t3.second << endl;

    t21.print1();
    t22.print1();
    t23.print1();
    t24.print1();

    t21.print2();
    t22.print2();
    t23.print2();
    t24.print2();

    cout << endl;

    student3 st3;
    strcpy(st3.name,"Belly");
    st3.number=21106;
    st3.age=19;
    st3.birth.year=2003;
    st3.birth.month=11;
    st3.birth.day=17;
    st3.print();


    int n;
    cout << "有两种演示学生信息存储的方式:" << endl;
    cout << "1直接输出内部存储的学生数据信息" << endl;
    cout << "2将必须先输入学生信息然后打印输出" << endl;
    cout << "0表示结束选择" << endl;
    cout << "请输入你要选择的序号:" << endl;

    while(cin>>n)
    {
        if(n == 0)
            break;

        switch(n)
        {
        case 1:
            stu1();
            cout << "有两种演示学生信息存储的方式:" << endl;
            cout << "1直接输出内部存储的学生数据信息" << endl;
            cout << "2将必须先输入学生信息然后打印输出" << endl;
            cout << "0表示结束选择" << endl;
            cout << "请输入你要选择的序号:" << endl;
            break;

        case 2:
            stu2();
            cout << "有两种演示学生信息存储的方式:" << endl;
            cout << "1直接输出内部存储的学生数据信息" << endl;
            cout << "2将必须先输入学生信息然后打印输出" << endl;
            cout << "0表示结束选择" << endl;
            cout << "请输入你要选择的序号:" << endl;
            break;

        default:
            cout<<"请输入错误"<

        结构体

        基本概念

        在学习面向对象编程之前的准备工作(一)中已经详细解释,例如基本声明原理、成员访问运算符的定义使用和变量以及成员变量之间的关系等。

        初始化

        

struct time1
{
    int hour;
    int minute;
    int second;
}t1,t2{0,0,0},t3={10,0,0};
//记住这种声明方式
// 声明了三个变量t1,t2,t3,其中t2、t3通过初始化列表进行了初始化
//当然他和下面这种声明是一样的


struct time1
{
    int hour;
    int minute;
    int second;
};
    time1 t1;
    time1 t2={0,0,0};
    time1 t3={10,0,0};

        上次学习了基本的结构体声明,这次有几种等价的快捷声明方式,程序员常用。在结构体{~}内部的定义完成后,在“;”之前就可以声明 数据类型为你刚刚定义的这个的 变量。可以进行初始化,也可以不进行,初始化使用的是花括号“{~~}”而不是成员访问运算符(点运算符),这样更加方便快捷,但要注意顺序和成员变量的类型,中间用逗号隔开。

        构造函数

struct time2
{
    int hour;
    int minute;
    int second;
    //默认的构造函数

    //不用C中struct的地方,C++struct允许定义成员函数,默认访问属性和数据成员一样,均为public
    void print1()
    {
        cout << hour << ", " << minute << ", " << second << endl;
    }
    void print2(); // 成员函数的声明
}t21,t22{12,0,0},t23={20,0,0},t24{};
// 声明了两个变量t21,t22,t23,t24其中t22、t23,t24通过初始化列表进行了初始化

// 在外部定义结构体的成员函数,需要用到结构体类型+全局作用域运算符来限定print2属于结构体类型time2
void time2::print2()
{
    cout << hour << ": " << minute << ": " << second << endl;
}

        C++struct允许定义成员函数,默认访问属性和数据成员一样,均为public,当然是在不是private的前提下。在结构体的内部声明或定义的函数和普通函数的形式没有什么区别。在初始化的时候,如上图所示,在分号前的初始化有没有“=”都可以。

        但是,在外部定义结构体的成员函数,需要用到结构体类型+全局作用域运算符(两个冒号)。通俗说就是,数据类型 类名 :: 函数名(参数)。(例如:    void time2::print2()    )。函数内部和普通函数一样。

        关于typedef的用法

//unsigned int age;
//typedef unsigned int uintAge;
//uintAge age1;

// 使用typedef为结构体声明别名
typedef struct student1{
      string name;
	  int number;
	  int age;
	  Birthday birth;
	  struct student1 *pnext; // 自引用指针
}Student1,*Ptrstudent1;
// Student1是类型,实际是struct student1的简写;
// Ptrstudent1也是类型,实际是struct student1 *的简写;
// 这种声明方式,特别重要,务必掌握,数据结构的基础

//typedef      struct student1 *         Ptrstudent1;
//typedef       struct student1        Student1;

        typedef是一个关键字,用于为已有的类型创建一个新的名称,最上面注释中的意思是:为“unsigned int”创建了一个新的名字“uintAge”。unitAge的使用和unsigned int完全一致。

        中间的代码主体的意思是:struct student1 是一个结构体类型的定义,它包含多个成员,不再一一列举,其中比较重要的是,struct student1 *pnext 这是一个自引用指针,指向 struct student1 类型的结构体,常用于构建链表等数据结构。typedef的作用是,定义Student1是 struct student1 的别名。此后,可以使用 Student1 来声明 struct student1 类型的变量,例如 Student1 s; 就等同于 struct student1 s;。另外一个就是,Ptrstudent1是 struct student1 * 的别名,也就是指向 struct student1 类型的指针的别名。可以使用 Ptrstudent1 来声明指针变量,例如 Ptrstudent1 p; 等同于 struct student1 *p;

        这里解释一下后者,Ptrstudent1通过 typedef 为 struct student1 * 定义的别名。当使用 typedef struct student1{...} Student1, *Ptrstudent1; 时,Ptrstudent1 就成了指向 struct student1 类型的指针类型的新名称。此后,可以用 Ptrstudent1 来声明指针变量,就像这样:Ptrstudent1 p;,这和 struct student1 *p; 是等效的。

        关于最后的注释部分,展示了另一种等价的类型别名声明方式,与前面的 typedef struct student1{...} Student1, *Ptrstudent1; 效果是一样的。在花括号后面不加“Student1和*Ptrstudent”,分开声明,先为 struct student1 * 定义别名 Ptrstudent1,再为 struct student1 定义别名 Student1

        结构体与动态数组的并用

        引入

// char *name = "name";
typedef struct student2{


      char name[20]; // 可以是char *name吗?


	  int number;
	  int age;
	  Birthday birth;
	  struct student2 *pnext;
}Student2,*Ptrstudent2;
// Student2是类型,实际是struct student2的简写;
// Ptrstudent2也是类型,实际是struct student2 *的简写;

        这段代码和上一段没什么大的区别,这里抛出一个问题,来承接下文,也是我写上一篇文章的用意,初学者可以思考一下。就是 char name[20]  能不能换成 char *name ? 这个问题一会儿解答,是可以的,但需要一些额外的操作。

typedef struct student3{
      char *name; // 可以是char *name,但需要动态分配内存!
	  int number;
	  int age;
	  Birthday birth;
	  struct student2 *pnext;

	  student3()// 构造函数,结构体变量生成时自动调用
	  {
	      name = new char [20];
	  }

	  ~student3()// 析构函数,结构体变量销毁时自动调用
	  {
	      delete [] name;
	  }

	  void print()
	  {
        cout << "学生姓名" << name << endl;
        cout << "学生年龄" << age << endl;
        cout << "学生学号" << number << endl;
        cout << "学生生日年份" << birth.year << endl;
        cout << "学生生日月份" << birth.month << endl;
        cout << "学生生日日子" << birth.day << endl;
        cout << "\n\n";
	  }
}Student3,*Ptrstudent3;
// Student3是类型,实际是struct student3的简写;
// Ptrstudent3也是类型,实际是struct student3 *的简写;

       定义结构体

         首先是结构体的定义,同上,typedef:用于为struct student3类型定义别名Student3struct student3的别名,之后可以直接使用Student3来声明变量,而无需每次都写struct student3Ptrstudent3struct student3 *的别名,也就是指向struct student3类型的指针的别名,方便定义指向该结构体的指针变量。char *name:这是一个字符指针,用于存储学生的姓名。由于使用指针需要动态分配内存来存储姓名信息。struct student2 *pnext:这是一个指向struct student2类型的指针,可以用于构建链表结构。

        构造函数

student3()// 构造函数,结构体变量生成时自动调用
{
    name = new char [20];
}

        构造函数的名称与结构体名称相同没有返回类型。当创建student3类型的结构体变量时构造函数会自动被调用。之后是, name = new char [20]; 在构造函数中,使用new运算符name指针动态分配了 20 个字符的内存空间,用于存储学生的姓名。

        析构函数

~student3()// 析构函数,结构体变量销毁时自动调用
{
    delete [] name;
}

        这是析构函数,在结构体变量销毁时自动调用。析构函数的名称是在结构体名称前加上“~符号同样没有返回类型。当结构体变量的生命周期结束时,析构函数会自动被调用。delete [] name; 在析构函数中,使用delete []运算符释放之前为name指针动态分配的内存,避免内存泄漏。

        成员函数

void print()
{
    cout << "学生姓名" << name << endl;
    cout << "学生年龄" << age << endl;
    cout << "学生学号" << number << endl;
    cout << "学生生日年份" << birth.year << endl;
    cout << "学生生日月份" << birth.month << endl;
    cout << "学生生日日子" << birth.day << endl;
    cout << "\n\n";
}

        这是一个成员函数,用于打印学生各项信息,唯一需要注意的是,最后的两个“\n”是“换行符”,使输出更加清晰。

        结构体中的位字段(Bit Fields)

// 结构中的位字段
struct test
{
    unsigned int SN : 4; // 4 bits for SN value
    unsigned int : 4; // 4 bits unused,通常使用没有名称的字段来提供间距
    bool goodIn : 1;
    bool goodTorgle : 1;
};

        这段代码定义了一个名为test的结构体,其中使用了位字段技术位字段允许在一个结构体中为各个成员指定占用的二进制位数,从而更紧凑地存储数据。  unsigned int SN : 4;  表明SN这个成员变量仅占用 4 个二进制位。由于是无符号整数类型,这 4 位可以表示的数值范围是从0(二进制0000)到15(二进制1111)。  unsigned int:4;这里没有给这个位字段命名,其作用是在内存中预留 4 位空间,不被任何变量使用,起到填充或对齐的作用,让后续的成员变量能按照特定的内存布局排列。  bool goodIn : 1; (另一个和这个完全一样) :说明每个布尔型成员变量仅占用 1 个二进制位。因为布尔型只有两种状态,所以 1 位足以表示。0可以表示false1可以表示true

        在不使用位字段时,一个unsigned int通常占用 4 个字节(32 位),一个bool类型变量通常也占用 1 个字节(8 位)。但使用位字段后,test结构体总共只占用 1 个字节(8 位),因为所有成员变量的位宽之和为 4 + 4 + 1 + 1 = 10 位,但在实际内存分配中,会按字节对齐,所以只占用 2个字节。因为test结构体的成员位宽总和是 10 位,但是由于内存按字节(8 位)进行分配和对齐,第一个字节(8 位)可以容纳前两个位字段SN的 4 位和未命名的 4 位),而剩下的两个布尔类型的位字段(共 2 位)无法再放入第一个字节,所以需要再占用一个新的字节来存储。因此,整个test结构体最终会占用 2 个字节的内存空间。

        需要注意的是,位字段的使用时,给定的位宽不能超过其对应数据类型的最大位数。例如,unsigned int通常是 32 位,若指定的位宽超过 32 位就会出错

       定义单向链表法一

void stu1()
{
    /*三种定义方法*/
    struct student1 *p = new student1; // struct可以省略
    Student1 *p1 = new Student1;
    Ptrstudent1 p2 = new Student1;
    student1 *p3=p;
    student1 *p4=p;

//    int a,b;
//    int *a,*b;

    /*有关信息的初始化*/
    p->name = "Tom";
    p->age = 20;
    p->number = 21101;
    p->birth.day = 6;
    p->birth.month = 6;
    p->birth.year = 2002;
    p->pnext = NULL;

    p1->name = "Jerry";
    p1->age = 20;
    p1->number = 21102;
    p1->birth.day = 5;
    p1->birth.month = 5;
    p1->birth.year = 2002;
    p->pnext = p1; // 把第一个学生和第二个学生的信息链接在一起
    p1->pnext =	NULL;

    p2->name = "shirlly";
    p2->age = 20;
    p2->number = 21103;
    p2->birth.day = 1;
    p2->birth.month = 1;
    p2->birth.year = 2002;
    p1->pnext = p2;// 把第二个学生和第三个学生的信息链接在一起
    p2->pnext = NULL;

    /*信息的输出*/
    for(; p3 != NULL; p3 = p3->pnext)
    {
        cout << "学生姓名" << p3->name << endl;
        cout << "学生年龄" << p3->age << endl;
        cout << "学生学号" << p3->number << endl;
        cout << "学生生日年份" << p3->birth.year << endl;
        cout << "学生生日月份" << p3->birth.month << endl;
        cout << "学生生日日子" << p3->birth.day << endl;
        cout << "\n\n";
    }

    //结束时销毁链表
//    for(p3=p; p3!=NULL; )
//    {
//        p4=p3;
//        p3=p3->pnext;
//        delete p4;
//    }
    delete p;
    delete p1;
    delete p2;
    p=NULL;
    p1=NULL;
    p2=NULL;
}

        链表的概念

        链表是由多个节点构成的线性数据结构,每个节点一般包含两部分:数据域(用于存储具体数据)和指针域(存放指向下一个指针的节点)。链表的最后一个节点的指针域不指向任何其他节点,一般会将其设置为 NULL(在 C++ 里也可以用 nullptr),以此来标志链表的结束

        链表状态变化

        在关于typedef的使用中,有一行代码: struct student1 *pnext; // 自引用指针   
这是一个自引用指针,指向 struct student1 类型的结构体,而后面有一个p1->pnext,他的含义是p1->pnext 访问的是该节点的 pnext 成员,这个成员是一个指向 student1 类型的指针,其作用是指向下一个节点。他不会单独出现,通常以p1->pnext = NULL;的方式出现,或者指向下一个链表,如p->pnext = p1;  。在执行 p->pnext = p1; 之后,链表状态:p ---> p1  。             而执行 p1->pnext = NULL; 之后,链表状态变为:p ---> p1 ---> NULL。这里的 NULL 表明链表结束,没有后续节点了。

        以上这段代码定义了一个名为 stu1 的函数,其主要功能是创建一个包含学生信息的单向链表,对链表中的每个节点(即每个学生的信息)进行初始化,然后遍历链表输出所有学生的信息,最后释放链表所占用的内存。

        变量定义与链表节点创建

struct student1 *p = new student1; // struct可以省略
Student1 *p1 = new Student1;
Ptrstudent1 p2 = new Student1;
student1 *p3=p;
student1 *p4=p;

        new 的使用方法见上一篇文章,有详细解释。
        struct student1 *p = new student1; 创建一个指向 student1 结构体类型指针 p,并使用 new 运算符在堆上动态分配一个 student1 类型的对象struct 关键字在 C++ 中对于自定义类型可以省略Student1 *p1 = new Student1; 这里 Student1 是 student1 结构体类型的别名,同样使用 new 运算符创建一个 student1 类型的对象,并让指针 p1 指向它。Ptrstudent1 p2 = new Student1;  Ptrstudent1 是 student1* 类型的别名,使用 new 运算符创建一个 student1 类型的对象,指针 p2 指向该对象。student1 *p3=p; 和 student1 *p4=p; 分别创建指针 p3 和 p4,并初始化为指向 p 所指向的对象,用于后续遍历链表和释放内存

        第一个学生节点信息初始化

p->name = "Tom";
p->age = 20;
p->number = 21101;
p->birth.day = 6;
p->birth.month = 6;
p->birth.year = 2002;
p->pnext = NULL;

        通过 -> 运算符访问 p 所指向的 student1 对象的成员变量,对学生的姓名、年龄、学号、生日等信息进行初始化。

         p->pnext = NULL; 表示第一个节点目前是链表的最后一个节点。其 pnext 指针指向 NULL。在链表这种数据结构里,每个节点除了存有数据之外,还会有一个指向下一个节点的指针,以此把各个节点串联起来。

        必要的知识补充

        ->运算符的使用

        讲解一下->运算符,这是一个成员访问运算符,主要用于通过指针访问对象的成员(成员变量或者成员函数)。当有一个指向结构体或者类对象的指针时,就可以使用 -> 运算符来访问该对象的成员,语法形式:   指针名->成员名    。“指针名” 是指向结构体或者类对象的指针,“成员名” 是该结构体或者类中定义的成员变量或者成员函数的名称。原理上来说,举个例子,         ptr->member  实际上等价于  (*ptr).member 。也就是说,-> 运算符会先对指针进行解引用操作,然后再访问对象的成员

        结构体嵌套和指针成员访问

        代码中 p->birth.month = 6; 这行代码涉及到结构体嵌套和指针成员访问,这行代码的主要功能是通过指针 p 访问其所指向结构体对象中的嵌套结构体成员 month,并将其值设置为 6。前提是,student1 结构体包含了一个 Birthday 类型的成员 birthBirthday 结构体则包含了表示年、月、日的三个整数成员(详见对student1和Birthday的定义,这两个必须都有)。我把有用部分的代码摘录下来:

struct Birthday
{
    int year;
    int month;
    int day;
}

typedef struct student1{
      string name;
	  int number;
	  int age;
	  Birthday birth;
	  struct student1 *pnext; // 自引用指针
}Student1,*Ptrstudent1;

         

   第二个学生节点信息初始化并连接到链表
 

p1->name = "Jerry";
p1->age = 20;
p1->number = 21102;
p1->birth.day = 5;
p1->birth.month = 5;
p1->birth.year = 2002;
p->pnext = p1; // 把第一个学生和第二个学生的信息链接在一起
p1->pnext = NULL;

        同样对 p1 所指向的学生节点进行信息初始化,和上面的一模一样,只有一点需要注意,但上面链表的状态变化已经提到了,就是这行代码:   
        p->pnext = p1; // 把第一个学生和第二个学生的信息链接在一起 

第一个节点的 pnext 指针(即 “p->pnext”)指向第二个节点(即  “= p1”),从而将两个节点连接起来,形成链表。p1->pnext = NULL; 表示第二个节点目前是链表的最后一个节点

        第三个学生节点信息初始化并连接到链表

p2->name = "shirlly";
p2->age = 20;
p2->number = 21103;
p2->birth.day = 1;
p2->birth.month = 1;
p2->birth.year = 2002;
p1->pnext = p2; // 把第二个学生和第三个学生的信息链接在一起
p2->pnext = NULL;

        同第二个完全一致,不在赘述。

        遍历链表并输出学生信息

       

for(; p3 != NULL; p3 = p3->pnext)
{
    cout << "学生姓名" << p3->name << endl;
    cout << "学生年龄" << p3->age << endl;
    cout << "学生学号" << p3->number << endl;
    cout << "学生生日年份" << p3->birth.year << endl;
    cout << "学生生日月份" << p3->birth.month << endl;
    cout << "学生生日日子" << p3->birth.day << endl;
    cout << "\n\n";
}

        使用 for 循环遍历链表,从 p3 所指向的第一个节点开始,只要 p3 不为 NULL,就继续遍历。在每次循环中,通过 -> 运算符访问当前节点的成员变量p3 = p3->pnext; 将 p3 指向下一个节点,继续遍历。

        指针的连接


        对于初学者来说,这里再讲一下为什么p3会指向下一个节点,首先,链表的指针域,包含一个指向下一个节点的指针,这个指针起到连接各个节点的作用,从而让链表中的节点能够依次串联起来。还是那行代码:

struct student1 *pnext; // 自引用指针    


        这里的 pnext 就是指针域,它存储着下一个 student1 节点的地址。别忘了P3是什么,p3 是一个指向 student1 结构体节点的指针,在最开始的初始化的时候:

struct student1 *p = new student1; // struct可以省略
student1 *p3=p;
student1 *p4=p;

  p3 = p3->pnext;中 p3->pnext 的含义p3 指向当前的节点p3->pnext 表示访问 p3 所指向节点的 pnext 成员。  由于 pnext 是一个指向 student1 类型的指针,它存储着下一个 student1 节点的地址,所以 p3->pnext 存储的就是下一个节点的地址。赋值操作p3 = p3->pnext; 把 p3->pnext 的值赋给 p3。这就意味着 p3 现在指向了原来 p3 所指向节点的下一个节点。
        易混淆的是:p3->pnext 的含义虽然是 p3 指向当前的节点,访问的是p3所指向的该节点的pnext成员,但是pnext存储的是下一个student1节点的地址,所有赋值操作p3 = p3->pnext,其实是把下一个节点的值赋值给了p3。而且p3最开始指向的是p节点,故从头开始遍历。

        释放链表所占内存

        

// 注释掉的代码是另一种释放内存的方式
// for(p3=p; p3!=NULL; )
// {
//     p4=p3;
//     p3=p3->pnext;
//     delete p4;
// }
delete p;
delete p1;
delete p2;
p=NULL;
p1=NULL;
p2=NULL;

        注释掉的代码是一种更通用的释放链表内存的方式,通过循环依次释放每个节点的内存。代码中实际使用的方式是分别使用 delete 运算符释放 pp1 和 p2 所指向的节点的内存。最后将指针 pp1 和 p2 置为 NULL,避免成为野指针。for循环中的逻辑关系自己看吧,p3从头开始,p3先赋值给p4,然后p3指向下一个节点,p4删除,一直重复,直到内存回收完。

        定义单向链表法二

        

void stu2()
{
    /*三种定义方法*/
    struct student2 *head = NULL;//头结点,用来存放首地址
    Student2 *p1, *p2 ;
    p1 = p2 = new Student2;
    int N = 0;  //用来记录学生的数量

    while(true)
    {
        cout<<"请输入学生的学号(输入学号以0结束信息输入):";
        cin>>p1->number;

        if(p1->number == 0)  //判断是否结束学生信息的输入
        {
            delete p1;
            p1=NULL;
            break;
        }

        //如果输入学号不为0,则继续输入学生信息
        N++;

        cout<<"请输入学生的姓名:";
        cin>>p1->name;
        cout<<"请输入学生的年龄:";
        cin>>p1->age;
        cout<<"请输入学生的生日年份:";
        cin>>p1->birth.year;
        cout<<"请输入学生的生日月份:";
        cin>>p1->birth.month;
        cout<<"请输入学生的生日日子:";
        cin>>p1->birth.day;

        if(N == 1)
            head = p1;
        else
            p2->pnext = p1;
        p2 = p1; // p2表示学生链表的最后一个学生的信息
        p1 = new student2;
    }

    p2->pnext = NULL;

    Student2 *p = head;

    //将学生信息输出
    cout << "\n\n一共有 " << N << " 个学生信息,";
    cout << "详细信息为:"<pnext)
    {
        cout << "学生姓名" << p->name << endl;
        cout << "学生年龄" << p->age << endl;
        cout << "学生学号" << p->number << endl;
        cout << "学生生日年份" << p->birth.year << endl;
        cout << "学生生日月份" << p->birth.month << endl;
        cout << "学生生日日子" << p->birth.day << endl;
        cout << "\n\n";
    }

    //结束时销毁链表
    for(p=head; p!=NULL; )
    {
        p1=p;
        p=p->pnext;
        delete p1;
    }
}

         初始化

struct student2 *head = NULL; // 头结点,用来存放首地址
Student2 *p1, *p2;
p1 = p2 = new Student2;
int N = 0;  // 用来记录学生的数量

        head:作为链表的头指针,将 head 指针初始化为 NULL。在链表操作中,NULL 表示该指针不指向任何有效的节点,这意味着当前链表为空head 指针的作用是保存链表的首地址,也就是链表中第一个节点的地址。p1 和 p2:定义了两个指针变量 p1 和 p2,它们都指向 Student2 类型的对象。  p1 = p2 = new Student2; :借助 new 运算符在堆上动态分配了一个 Student2 类型的对象,并且让 p1 和 p2 这两个指针都指向这个新分配的对象new 运算符返回的是新分配对象的地址。在后续的链表操作中,p1 和 p2 会用于创建新节点、插入节点以及遍历链表等操作。N是计数器。

        循环输入学生信息

while (true) {
    cout << "请输入学生的学号(输入学号以0结束信息输入):";
    cin >> p1->number;

    if (p1->number == 0) {  // 判断是否结束学生信息的输入
        delete p1;
        p1 = NULL;
        break;
    }

    // 如果输入学号不为0,则继续输入学生信息
    N++;

    cout << "请输入学生的姓名:";
    cin >> p1->name;
    cout << "请输入学生的年龄:";
    cin >> p1->age;
    cout << "请输入学生的生日年份:";
    cin >> p1->birth.year;
    cout << "请输入学生的生日月份:";
    cin >> p1->birth.month;
    cout << "请输入学生的生日日子:";
    cin >> p1->birth.day;

    if (N == 1)
        head = p1;
    else
        p2->pnext = p1;
    p2 = p1; // p2表示学生链表的最后一个学生的信息
    p1 = new student2;
}

        while cin之类的不再解释,直解释未曾学习过的。

        if (N == 1){head = p1;}   :如果是第一个学生信息,将 head 指向该节点。
        else  {p2->pnext = p1 ;} :如果不是第一个学生信息,将前一个节点 pnext 指针指向当前节点。
        p2 = p1:更新 p2 为当前链表的最后一个节点。
  p1 = new student2:为下一个学生信息创建新节点。

        链表结尾处理

p2->pnext = NULL;

        将链表的最后一个节点的 pnext 指针置为 NULL,表示链表结束

        输出学生信息

Student2 *p = head;

// 将学生信息输出
cout << "\n\n一共有 " << N << " 个学生信息,";
cout << "详细信息为:" << endl;

// 遍历链表
for (; p != NULL; p = p->pnext) {
    cout << "学生姓名" << p->name << endl;
    cout << "学生年龄" << p->age << endl;
    cout << "学生学号" << p->number << endl;
    cout << "学生生日年份" << p->birth.year << endl;
    cout << "学生生日月份" << p->birth.month << endl;
    cout << "学生生日日子" << p->birth.day << endl;
    cout << "\n\n";
}

        Student2 *p = head:创建一个临时指针 p 指向链表头节点
  for (; p != NULL; p = p->pnext):遍历链表,输出每个学生的详细信息。(方法同定义单向链表法一中提到的,一模一样,从头到尾, p->pnext 存储下一个节点的地址,“\n”是换行符,不再赘述)

        释放链表内存

for (p = head; p != NULL;) {
    p1 = p;
    p = p->pnext;
    delete p1;
}

        使用 for 循环遍历链表,依次释放每个节点占用的内存,避免内存泄漏。方法也是和法一一模一样,不再赘述。

        定义单向链表法三

void stu3()
{
    // struct 数组
    student1 stuarray[3];
    stuarray[0].name = "Tom";
    stuarray[0].age = 20;
    stuarray[0].number = 21101;
    stuarray[0].birth.day = 6;
    stuarray[0].birth.month = 6;
    stuarray[0].birth.year = 2002;
    stuarray[0].pnext = NULL;

    stuarray[1].name = "Jerry";
    stuarray[1].age = 20;
    stuarray[1].number = 21102;
    stuarray[1].birth.day = 5;
    stuarray[1].birth.month = 5;
    stuarray[1].birth.year = 2002;
    stuarray[1].pnext =	NULL;

    stuarray[2].name = "shirlly";
    stuarray[2].age = 20;
    stuarray[2].number = 21103;
    stuarray[2].birth.day = 1;
    stuarray[2].birth.month = 1;
    stuarray[2].birth.year = 2002;
    stuarray[2].pnext = NULL;

    /*信息的输出*/
    cout << "Function: stu3() calling...\n";
    for(int i=0; i<3; i++)
    {
        cout << "学生姓名" << stuarray[i].name << endl;
        cout << "学生年龄" << stuarray[i].age << endl;
        cout << "学生学号" << stuarray[i].number << endl;
        cout << "学生生日年份" << stuarray[i].birth.year << endl;
        cout << "学生生日月份" << stuarray[i].birth.month << endl;
        cout << "学生生日日子" << stuarray[i].birth.day << endl;
        cout << "\n\n";
    }
}

        结构体数组的定义

student1 stuarray[3];

        这里定义了一个名为 stuarray 的数组,该数组包含 3 个 student1 类型的元素。    

        结构体数组元素的初始化    

stuarray[0].name = "Tom";
stuarray[0].age = 20;
stuarray[0].number = 21101;
stuarray[0].birth.day = 6;
stuarray[0].birth.month = 6;
stuarray[0].birth.year = 2002;
stuarray[0].pnext = NULL;

stuarray[1].name = "Jerry";
stuarray[1].age = 20;
stuarray[1].number = 21102;
stuarray[1].birth.day = 5;
stuarray[1].birth.month = 5;
stuarray[1].birth.year = 2002;
stuarray[1].pnext = NULL;

stuarray[2].name = "shirlly";
stuarray[2].age = 20;
stuarray[2].number = 21103;
stuarray[2].birth.day = 1;
stuarray[2].birth.month = 1;
stuarray[2].birth.year = 2002;
stuarray[2].pnext = NULL;

        这里很简单,对数组中的每个元素进行初始化操作使用 “.”运算符访问结构体的成员变量。分别为每个学生的姓名、年龄、学号、生日信息进行赋值,并且把 pnext 指针设置为 NULL

        信息输出

cout << "Function: stu3() calling...\n";
for(int i = 0; i < 3; i++)
{
    cout << "学生姓名" << stuarray[i].name << endl;
    cout << "学生年龄" << stuarray[i].age << endl;
    cout << "学生学号" << stuarray[i].number << endl;
    cout << "学生生日年份" << stuarray[i].birth.year << endl;
    cout << "学生生日月份" << stuarray[i].birth.month << endl;
    cout << "学生生日日子" << stuarray[i].birth.day << endl;
    cout << "\n\n";
}

        首先输出提示信息 Function: stu3() calling...,表明 stu3 函数正在被调用。利用 for 循环遍历数组,依次输出每个学生的详细信息。通过 stuarray[i] 访问数组中的第 i 个元素,再使用 . 运算符访问该元素的成员变量并输出。

        此原码主函数main的使用

int main()
{
    Birthday day1;
    day1.year=2002;
    day1.month=5;
    day1.day=17;

    Birthday day2={2003,2,14};

    cout << "Day1++++++++++++++++++++++++++++++++" << endl;
    cout << day1.year << " "; // 圆点运算符叫成员访问符号
    cout << day1.month << " ";
    cout << day1.day << endl;
    cout << "Day1++++++++++++++++++++++++++++++++" << endl;

    cout << "Day2++++++++++++++++++++++++++++++++" << endl;
    cout << day2.year << " ";
    cout << day2.month << " ";
    cout << day2.day << endl;
    cout << "Day2++++++++++++++++++++++++++++++++" << endl;

    t1.hour=19;
    t1.minute=52;
    t1.second=58;
    cout << "t1: " << t1.hour << " " << t1.minute << " " << t1.second << endl;
    cout << "t2: " << t2.hour << " " << t2.minute << " " << t2.second << endl;
    cout << "t3: " << t3.hour << " " << t3.minute << " " << t3.second << endl;

    t21.print1();
    t22.print1();
    t23.print1();
    t24.print1();

    t21.print2();
    t22.print2();
    t23.print2();
    t24.print2();

    cout << endl;

    student3 st3;
    strcpy(st3.name,"Belly");
    st3.number=21106;
    st3.age=19;
    st3.birth.year=2003;
    st3.birth.month=11;
    st3.birth.day=17;
    st3.print();


    int n;
    cout << "有两种演示学生信息存储的方式:" << endl;
    cout << "1直接输出内部存储的学生数据信息" << endl;
    cout << "2将必须先输入学生信息然后打印输出" << endl;
    cout << "0表示结束选择" << endl;
    cout << "请输入你要选择的序号:" << endl;

    while(cin>>n)
    {
        if(n == 0)
            break;

        switch(n)
        {
        case 1:
            stu1();
            cout << "有两种演示学生信息存储的方式:" << endl;
            cout << "1直接输出内部存储的学生数据信息" << endl;
            cout << "2将必须先输入学生信息然后打印输出" << endl;
            cout << "0表示结束选择" << endl;
            cout << "请输入你要选择的序号:" << endl;
            break;

        case 2:
            stu2();
            cout << "有两种演示学生信息存储的方式:" << endl;
            cout << "1直接输出内部存储的学生数据信息" << endl;
            cout << "2将必须先输入学生信息然后打印输出" << endl;
            cout << "0表示结束选择" << endl;
            cout << "请输入你要选择的序号:" << endl;
            break;

        default:
            cout<<"请输入错误"<

        基础的跳过,只来讨论未系统学习过的。

        初始化方式

    Birthday day1;
    day1.year=2002;
    day1.month=5;
    day1.day=17;

    Birthday day2={2003,2,14};

        这两种初始化方式一样,只需要注意格式。

        ctrcpy函数的使用

    strcpy(st3.name,"Belly");

         在 C++ 里,strcpy 函数是一个常用的字符串操作函数,它来自  头文件,主要功能是把一个字符串复制到另一个字符数组中。这行代码调用了 strcpy 函数,其目的是把字符串 "Belly" 复制到 st3.name 所指向的内存空间中。

        函数原型
char* strcpy(char* dest, const char* src);

      dest指向目标字符数组的指针,也就是要把字符串复制到的地方。目标数组必须有足够的空间来容纳源字符串及其终止符 '\0'
  src指向源字符串的指针,该字符串会被复制到目标数组中。const 修饰表示函数不会修改源字符串。

        位字段的使用

struct test
{
    unsigned int SN : 4; // 4 bits for SN value
    unsigned int : 4; // 4 bits unused,通常使用没有名称的字段来提供间距
    bool goodIn : 1;
    bool goodTorgle : 1;
};
    test tt = {14, true, false};
    cout << "test: " << tt.SN << " " << tt.goodIn << " " << tt.goodTorgle << endl;

        跳过了无名称字段,提供了间距。

你可能感兴趣的:(C++,学习,c++,算法,开发语言)