本次学习的所有知识点如下:
/*
* 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() )。函数内部和普通函数一样。
//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
类型定义别名。Student3
是struct student3
的别名,之后可以直接使用Student3
来声明变量,而无需每次都写struct student3
。Ptrstudent3
是struct 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”是“换行符”,使输出更加清晰。
// 结构中的位字段
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
可以表示false
,1
可以表示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
类型的成员 birth
,Birthday
结构体则包含了表示年、月、日的三个整数成员(详见对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
运算符释放 p
、p1
和 p2
所指向的节点的内存。最后将指针 p
、p1
和 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
个元素,再使用 .
运算符访问该元素的成员变量并输出。
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};
这两种初始化方式一样,只需要注意格式。
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;
跳过了无名称字段,提供了间距。