〇、从基本数据类型到抽象数据类型
1、用户自己构造数据类型——复合数据类型
由基本数据类型迭代派生而来,表示复杂的数据对象
典型代表就是“结构体”
2、抽象数据类型
在符合数据类型基础上增加了对数据的操作
3、抽象数据类型进而进化为“类”
Class是Object-Oriented的一个重要概念
一、结构体
C++: C++的struct成员包含成员函数
C: C语言的struct的成员只能是数据,不支持函数,因此若要定义结构体的函数需要使用函数指针实现
结构体将不同类型的数据成员组织到统一的名字之下,适合于对关系紧密、逻辑相关、具有相同或者不同属性的数据进行处理,占用相邻的内存单元。尤其在数据库管理中得到了广泛应用。
(一)结构体定义和结构体变量
1.1 (C++).定义结构体,同时定义变量
struct 结构体名{ //struct是关键字
成员表 //可以有多个成员
成员函数 //可没有,也可由多个
} 结构体变量表; //可以同时定义多个结构体变量。注意分号位置
//结构体变量之间用逗号隔开
例如:
struct student{
long studentID;
char studentname[10];
char studentsex;
int score[4];
int yearofbirthday;
}stu1,stu2;
1.2 先定义结构体再定义结构体变量
struct 结构体名{
成员表
成员函数
} ;
结构体名 结构体变量表;//也可以同时定义多个结构体变量,注意两个分号。
例如:
struct student{
long studentID;
char studentname[10];
char studentsex;
int score[4];
int yearofbirthday;
};
student stu1,stu2;
2. (C语言)
第一步:声明一个结构体模板
struct 结构体名{
数据类型 成员1的名字;
数据类型 成员2的名字;
…
数据类型 成员n的名字;
}
//以下为结构体模板
struct student{//student是结构体标签
long studentID;
char studentname[10];
char studentsex;
int score[4];
int yearofbirthday;
}
结构体标签:结构体的名字,作为用户自定义的结构体类型的标志,用于与其他结构体类型相区别。
结构体成员:构成结构体的变量
注意:结构体模板只是声明了一种数据类型,定义了数据的组织形式,并未声明结构体类型的变量,因而编译器不为其分配内存,正如编译器不为int型分配内存一样。
第二步:利用已经定义好的结构体数据类型来定义结构体变量。C语言允许一下两种形式:
(1)先声明结构体模板,再定义结构体变量
struct student stu1;//一个具有struct student类型的结构体变量
(2)在声明结构体模板的同时定义结构体变量
同C++
struct student{
long studentID;
char studentname[10];
char studentsex;
int score[4];
int yearofbirthday;
}stu1,stu2;
结构体模板和结构体变量一起定义时,结构体标记是可选的,也就是可以不出现结构体名。
struct {
long studentID;
char studentname[10];
char studentsex;
int score[4];
int yearofbirthday;
}stu1,stu2;
但该方法因为未指定结构体标签,不能再在程序的其他处定义结构体变量,因而并不常用。
(二)用typedef定义数据类型
typedef用于为系统固有的/ 程序员自定义的数据类型定义一个别名,别名常用大写字母以区分原名
注意:是为已存在的数据类型起别名
typedef int INTENGER;
这同样可用于结构体。下面两组代码等价:
typedef struct student STU;
typedef struct student{
long studentID;
char studentname[10];
char studentsex;
int score[4];
int yearofbirthday;
}STU;
下面两条语句等价:
STU stu1,stu2;
struct student stu1,stu2;
(三)、初始化
方法:将成员的初始值至于花括号内
STU stu1={120212, "Jessica", 'M', (72,83,78,78},2000};
(四)、嵌套的结构体
在一个结构体内包含了另一个结构体作为其成员。
如将前文代码中 int yearofbirthday; 修改为包含具体年月日信息的日期
先声明一个日期结构体模板如下
typedef struct data{
int year;
int month;
int dasy;
} DATE;
再根据这个DATE结构体模板来声明STU结构体模板
typedef struct student{
long studentID;
char studentname[10];
char studentsex;
int score[4];
DATE birthday;
}STU;
最后,定义STU类型的结构体变量stu1,初始化
STU stu1={120212, "Jessica", 'M', {72,83,78,78},{2000,10,19}};
(五)成员调用
不能将一个结构体作为一个整体进行输入输出操作,必须对其成员进行。
- 成员选择运算符"." (圆点运算符)
在存取成员数值时使用,优先级最高,具有左结合性。
stu1.studentID= 120212;
结构体成员的使用方法与其他类型变量相同。
出现结构体嵌套时,必须以级联方式访问结构体成员,即通过成员选择运算符并逐级找到最底层的成员再引用。
stu1.birthday.year=2001;
stu1.birthday.month=10;
stu1.birthday.day=19;
scanf("%d", &stu1.score[1]);
scanf("%s", stu1.studentname);//no &
scanf("%c", &stu1.studentsex);
scanf("%d", &stu1.birthday.year);
prinf("%d", stu1.score[1]);
cin>>stu1.score[1];
cout<<stu1.score[1];
可以对具有相同结构体类型的变量进行整体赋值。
stu2=stu1;
与把这两个结构体中的元素拿出来一一对应赋值,是等价的。
对于字符数组类型的结构体,赋值需要字符串处理函数strcpy()函数,其他结构体成员可以使用赋值号。
(六)成员函数调用(C++)
形式:
结构体变量名.成员函数
(七)结构体变量的地址
结构体变量所占内存空间的首地址,结构体成员的地址值与结构体成员在结构体中所处的位置及该成员所占内存的字节数相关。
//地址的不同
scanf("%ld",&stu1.studentID);//元素的地址
printf("&stu2=%p\n",&stu2);//打印结构体变量stu2的地址
(八)结构体内存所占的字节数
并非所有成员的总和,与定义的结构体类型和计算机系统都有关。由于结构体变量的成员的内存对齐方式和数据类型所占内存的大小都是与机器相关的,因此结构体在内存中所占的字节数也是与及其有关的。
所以,计算时一定要用sizeof()。
/*
事实上,所有数据类型在内存中都是从偶数地址开始存放的
且结构所占的实际空间一般是按照机器字长对齐的
不同的编译器、平台,对齐方式会有变化
结构体变量的成员的存储对齐规则是与机器相关的
具有特定数据类型的数据项大小也是与机器相关的
所以一个结构体在内存中的存储格式也是与机器相关的
*/
(九)结构体数组的定义和初始化
1. 定义
STU stud[30];
2. 初始化
STU stud[30]={{100310121,"王刚",'M',{72,83,90,82},{1991,5,19}},
{100310122,"李小明",'M',{88,92,78,78},{1992,8,20}}
{100310123,"王丽红",'F',{98,72,89,66},{1991,9,19}}
{100310124,"陈莉莉",'F',{87,95,78,90},{1992,3,22}}
};
(十)结构体指针的定义和初始化
1.指向结构体变量的指针
STU *pt;//定义指向STU结构体的指针变量
pt=&stu1;//结构体指针变量pt指向结构体变量stu1
STU *pt=&stu1;
2.访问结构体指针变量指向的成员
(1)成员选择运算符(圆点运算符) .
(2)指向运算符(箭头运算符) ->
下面两行语句等价
pt->studentID=100310121;
pt->birthday.year =1999;//结构体嵌套时
(*pt).studentID=100310121;//不常用
3.指向结构体数组的指针
STUDENT stu[30];
STUDENT *pt;
pt=stu;
等价于
STUDENT *pt=stu;
等价于
STUDENT *pt=&stu[0];
//使用pt++,使pt指向stu[1]
//访问
pt->studentID;
stu[1].studentID;
(十一)、向函数传递结构体
- 用结构体的单个成员作为函数参数,向函数传递结构体的单个成员
- 用结构体变量做函数参数,向函数传递结构体的完整结构
- 用结构体指针或结构体数组做函数参数,向函数传递结构体的地址
1:传递单个内容时,复制单个成员的内容,函数内对结构体内容的修改不影响原结构。
2:复制整个结构体成员的内容,多个值;函数内对结构内容的修改不影响原结构;内容传递更直观,但开销大
3:用结构体数组/结构体指针作函数参数;仅复制结构体的首地址,一个值;修改结构体指针所指向的结构体的内容;指针传递效率高
向函数传递结构体变量时,实际传递给函数的是该结构体变量成员值的副本,这就意味着结构体变量的成员值是不能在被调函数中修改的。和其他变量一样,仅当将结构体地址传递给函数时,结构体变量的成员值才可以在被调函数中修改。
(十二 )、实例
#include<stdio.h>
struct date{
int year;
int month;
int day;
};
void Func(struct date p){//结构体变量做函数形参
p.year=2000;
p.month=5;
p.day=22;
}
int main(){
struct date d;
d.year=1999;
d.month=4;
d.day=23;
printf("Before function call:%d/%02d/%02d\n",d.year,d.month,d.day);
Func(d);//d=Func(d)则为语法错误
printf("After function call:%d/%02d/%02d\n",d.year,d.month,d.day);
return 0;
}
/*
Output:
Before function call:1999/04/23
After function call:1999/04/23
*/
#include<stdio.h>
struct date{
int year;
int month;
int day;
};
void Func(struct date *pt){//结构体指针变量做函数形参。实参必须为地址值
pt->year=2000;
pt->month=5;
pt->day=22;
}
int main(){
struct date d;
d.year=1999;
d.month=4;
d.day=23;
printf("Before function call:%d/%02d/%02d\n",d.year,d.month,d.day);
Func(&d);
printf("After function call:%d/%02d/%02d\n",d.year,d.month,d.day);
return 0;
}
/*
Output:
Before function call:1999/04/23
After function call:2000/05/22
*/
Luogu P1093 奖学金
某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前5名学生发奖学金。期末,每个学生都有3门课的成绩:语文、数学、英语。先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学 排在前面,这样,每个学生的排序是唯一确定的。
任务:先根据输入的3门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名名学生的学号和总分。注意,在前5名同学中,每个人的奖学金都不相同,因此,你必须严格按上述规则排序。例如,在某个正确答案中,如果前两行的输出数据(每行输出两个数:学号、总分) 是:
7 279
5 279
这两行数据的含义是:总分最高的两个同学的学号依次是7号、5号。这两名同学的总分都是 279 (总分等于输入的语文、数学、英语三科成绩之和) ,但学号为7的学生语文成绩更高一些。如果你的前两名的输出数据是:
5 279
7 279
则按输出错误处理,不能得分。
输入格式
共n+1行。
第1行为一个正整数n(≤300),表示该校参加评选的学生人数。
第2到n+1行,每行有3个用空格隔开的数字,每个数字都在0到100之间。第j行的3个数字依次表示学号为j−1的学生的语文、数学、英语的成绩。每个学生的学号按照输入顺序编号为1~n(恰好是输入数据的行号减1)。
所给的数据都是正确的,不必检验。
//感谢 黄小U饮品 修正输入格式
输出格式
共5行,每行是两个用空格隔开的正整数,依次表示前55名学生的学号和总分。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<iomanip>
using namespace std;
int n;
struct student{//结构体记录每个学生的学号,总分,语数英成绩
int num;
int sum;
int chi;
int mat;
int eng;
}pui[305];//每个数组存储每个学生的信息
bool cmp(student a,student b){//bool函数用于成绩比较,注意函数格式
if(a.sum>b.sum)//注意数据调用的格式
return 1;//bool中,1为真,0为假
else if(a.sum<b.sum)
return 0;
if(a.chi>b.chi)
return 1;
else if(a.chi<b.chi)
return 0;
if(a.num<b.num)
return 1;
else if(a.num>b.num)
return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
pui[i].num=i;//存储学号
cin>>pui[i].chi>>pui[i].mat>>pui[i].eng;//输入每个学生单科成绩
}
for(int i=1;i<=n;i++)
pui[i].sum=pui[i].chi+pui[i].mat+pui[i].eng;//求每个学生的总成绩
sort(pui+1,pui+1+n,cmp);//排序函数
for(int i=1;i<=5;i++){
cout<<pui[i].num<<' '<<pui[i].sum<<endl;
}
return 0;
}
二、共用体(联合)
(一)、
与结构体相比,共用体虽然也能表示逻辑相关不同类型的数据集合,但其数据成员是情形互斥的,每一时刻只有一个数据成员起作用。
如“婚姻状况”:未婚、已婚、离婚。
共用体中,不同类型的成员共用同一段内存单元。
共用体类型所占内存空间的大小取决于其成员中占内存空间最多的那个成员变量。
#include<stdio.h>
union sample{
short i;
char ch;
float f;
};
typedef union sample SAMPLE;
int main(){
printf("bytes=%d\n",sizesof(SAMPLE));
return 0;
}
/*Output:
bytes=4
*/
//union--> struct ?
/*Output:
bytes=8
*/
同一内存单元每一瞬只能存放其中一种类型的成员,起作用的成员就是最后一次被赋值(存放)的成员。正因如此,不能为共用体的所有成员同时进行初始化,C89规定只能对共用体的第一个成员进行初始化,但C99没有这个限制,允许按名设置成员的处置。
SAMPLE u={.ch='a'};
SAMPLE num;
num.i=20;
优点:节省内存空间、避免操作失误引起逻辑上的冲突
主要用于有效使用存储空间、构造混的合数据结构
(二)、实例
1.记录职工个人信息,包括姓名、性别、年龄、婚姻状况(未婚、已婚(结婚日期、配偶姓名、子女数量)、离婚(离婚日期、子女数量)、婚姻状况标记
struct date{
int year;
int month;
int day;
};
struct marriedState{
struct date marryday;
char spousename[20];
int child;
};
struct divorceState{
struct date divorceday;
int child;
};
union maritalState{
int single;
struct marriedState married;
struct divorceState divorce;
};
struct person{
char name[20];
char sex;
int age;
union maritalState marital;
int marryflag;//biaoji
}
为什么要有婚姻状况标记?
共用体变量union的三个成员,我们无法知道每一瞬时具体起作用的究竟是哪个成员,就需要增加一个标志想,知名当前是哪个成员起作用,或是说明当前的婚姻状况是哪一种。如marryflag为1表未婚,single成员起作用,内存中的数据会解释为单身相关的数据,以此类推。
2.需要的数组元素是Int型和float型的混合
typedef union{
int i;
float f;
}NUMBER;
NUMER a[100];