详解C++中的指针结构体数组以及指向结构体变量的指针
C++结构体数组
一个结构体变量中可以存放一组数据(如一个学生的学号、姓名、成绩等数据)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与以前介绍过的数值型数组的不同之处在于:每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。
定义结构体数组和定义结构体变量的方法相仿,定义结构体数组时只需声明其为数组即可。如:
struct Student //声明结构体类型Student { int num; char name[20]; char sex; int age; float score; char addr[30]; }; Student stu[3]; //定义Student类型的数组stu
也可以直接定义一个结构体数组,如:
struct Student { int num; char name[20]; char sex; int age; float score; char addr[30]; }stu[3];
或
struct { int num; char name[20]; char sex; int age; float score; char addr[30]; }stu[3];
结构体数组的初始化与其他类型的数组一样,对结构体数组可以初始化。如:
struct Student { int num; char name[20]; char sex; int age; float score; char addr[30]; }stu[3]={ {10101,″Li Lin″, ′M′, 18,87.5, ″103 Beijing Road″}, {10102,″Zhang Fun″,′M′,19,99, ″130 Shanghai Road″}, {10104,″Wang Min″,′F′, 20,78.5, ″1010 Zhongshan Road″} };
定义数组stu时,也可以不指定元素个数,即写成以下形式:
stu[ ]={{…},{…},{…}};
编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数。一个结构体常量应包括结构体中全部成员的值。
当然,数组的初始化也可以用以下形式:
Student stu[ ]={{…},{…},{…}}; //已事先声明了结构体类型Student
由上可以看到,结构体数组初始化的一般形式是在所定义的数组名的后面加上 ={初值表列};
结构体数组应用举例
下面举一个简单的例子来说明结构体数组的定义和引用。
【例】对候选人得票的统计程序。设有3个候选人,最终只能有1人当选为领导。今有10个人参加投票,从键盘先后输入这10个人所投的候选人的名字,要求最后输出这3个候选人的得票结果。
可以定义一个候选人结构体数组,包括3个元素,在每个元素中存放有关的数据。程序如下:
#include <iostream> using namespace std; struct Person //声明结构体类型Person { char name[20]; int count; }; int main( ) { //定义Person类型的数组,内容为3个候选人的姓名和当前的得票数 Person leader[3]={"Li",0,"Zhang",0,"Fun",0}; int i,j; char leader_name[20]; //leader_name为投票人所选的人的姓名 for(i=0;i<10;i++) { cin>>leader_name; //先后输入10张票上所写的姓名 for(j=0;j<3;j++) //将票上姓名与3个候选人的姓名比较 //如果与某一候选人的姓名相同,就给他加一票 if(strcmp(leader_name,leader[j].name)==0) leader[j].count++; } cout<<endl; for(i=0;i<3;i++) //输出3个候选人的姓名与最后得票数 { cout<<leader[i].name<<":"<<leader[i].count<<endl; } return 0; }
运行情况如下:
Zhang (每次输入一个候选人的姓名) Li Fun Li Zhang Li Zhang Li Fun Wang Li:4 (输出3个候选人的姓名与最后得票数) Zhang:3 Fun:2
程序定义一个全局的结构体数组leader,它有3个元素,每一元素包含两个成员,即name(姓名)和count(得票数)。在定义数组时使之初始化,使3位候选人的票数都先置零。
在这个例子中,也可以不用字符数组而用string方法的字符串变量来存放姓名数据,程序可修改如下:
#include <iostream> #include <string> using namespace std; struct Person { string name;//成员name为字符串变量 int count; }; int main( ) { Person leader[3]={"Li",0,"Zhang",0,"Fun",0}; int i,j; string leader_name;// leader_name为字符串变量 for(i=0;i<10;i++) { cin>>leader_name; for(j=0;j<3;j++) if(leader_name==leader[j].name) leader[j].count++//用“==”进行比较 } cout<<endl; for(i=0;i<3;i++) { cout<<leader[i].name<<":"<<leader[i].count<<endl; } return 0; }
运行情况与前相同。显然后一个程序节省内存空间,使用更方便,易读性更好。但是 有些C++系统不能对包含string成员的结构体变量初始化,需要作一些修改才能运行, 读者可上机试一下。
C++指向结构体变量的指针
一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素。
通过指向结构体变量的指针引用结构体变量中的成员
下面通过一个简单例子来说明指向结构体变量的指针变量的应用。
【例】指向结构体变量的指针的应用。
#include <iostream> #include <string> using namespace std; int main( ) { struct Student//声明结构体类型student { int num; string name; char sex; float score; }; Student stu;//定义Student类型的变量stu Student *p=&stu;//定义p为指向Student类型数据的指针变量并指向stu stu.num=10301;//对stu中的成员赋值 stu.name="Wang Fun";//对string变量可以直接赋值 stu.sex='f'; stu.score=89.5; cout<<stu. num<<" "<<stu.name<<" "<<stu.sex<<" "<< stu.score<<endl; cout<<p -> num<<" "<<(*p).name<<" "<<(*p).sex<<" "<<(*p).score<<endl; return 0; }
程序运行结果如下:
10301 Wang Fun f 89.5 (通过结构体变量名引用成员)
10301 Wang Fun f 89.5 (通过指针引用结构体变量中的成员)
两个cout语句输出的结果是相同的。
为了使用方便和使之直观,C++提供了指向结构体变量的运算符->,例如p->num表示指针p当前指向的结构体变量中的成员num。
p->num 和(*p).num等价。
同样
p->name等价于(*p).name。
也就是说,以下3种形式等价:
结构体变量.成员名。如stu.num。
(*p).成员名。如(*p).num。
p->成员名。如p->num。
“->”称为指向运算符。
请分析以下几种运算:
- p->n 得到p指向的结构体变量中的成员n的值。
- p->n++ 得到p指向的结构体变量中的成员n的值,用完该值后使它加1。
- ++p->n 得到p指向的结构体变量中的成员n的值,并使之加1,然后再使用它。
用结构体变量和指向结构体变量的指针构成链表
链表是一种常见的重要的数据结构。下图表示最简单的一种链表(单向链表)的结构。
链表有一个“头指针”变量,图中以head表示,它存放一个地址。该地址指向一个元素。链表中的每一个元素称为“结点”,每个结点都应包括两个部分:
一是用户需要用的实际数据,
二是下一个结点的地址。
可以看到链表中各元素在内存中的存储单元可以是不连续的。要找某一元素,可以先找到上一个元素,根据它提供的下一元素地址找到下一个元素。
可以看到,这种链表的数据结构,必须利用结构体变量和指针才能实现。
可以声明一个结构体类型,包含两种成员,一种是用户需要用的实际数据,另一种是用来存放下一结点地址的指针变量。
例如,可以设计这样一个结构体类型:
struct Student { int num; float score; Student *next; //next指向Student结构体变量 };
其中成员num和score是用户需要用到的数据,相当于图7.8结点中的A, B, C, D。next是指针类型的成员,它指向Student类型数据(就是next所在的结构体类型)。用这种方法就可以建立链表。见图。
图中每一个结点都属于Student类型,在它的成员next中存放下一个结点的地址,程序设计者不必知道各结点的具体地址,只要保证能将下一个结点的地址放到前一结点的成员next中即可。
下面通过一个例子来说明如何建立和输出一个简单链表。
【例】建立一个如图所示的简单链表,它由3个学生数据的结点组成。输出各结点中的数据。
#define NULL 0 #include <iostream> using namespace std; struct Student { long num; float score; struct Student *next; }; int main( ) { Student a,b,c,*head,*p; a. num=31001; a.score=89.5; //对结点a的num和score成员赋值 b. num=31003; b.score=90; //对结点b的num和score成员赋值 c. num=31007; c.score=85; //对结点c的num和score成员赋值 head=&a; //将结点a的起始地址赋给头指针head a.next=&b; //将结点b的起始地址赋给a结点的next成员 b.next=&c; //将结点c的起始地址赋给b结点的next成员 c.next=NULL; //结点的next成员不存放其他结点地址 p=head; //使p指针指向a结点 do { cout<<p->num<<" "<<p->score<<endl; //输出p指向的结点的数据 p=p->next; //使p指向下一个结点 } while (p!=NULL); //输出完c结点后p的值为NULL return 0; }
本例是比较简单的,所有结点(结构体变量)都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为静态链表。对各结点既可以通过上一个结点的next指针去访问,也可以直接通过结构体变量名a, b, c去访问。
动态链表则是指各结点是可以随时插入和删除的,这些结点并没有变量名,只能先找到上一个结点,才能根据它提供的下一结点的地址找到下一个结点。只有提供第一个结点的地址,即头指针head,才能访问整个链表。如同一条铁链一样,一环扣一环,中间是不能断开的。
建立动态链表,要用到后面介绍的动态分配内存的运算符new和动态撤销内存的运算符delete。