结构体
1结构体的定义和基本使用
引例1
下面的表格记录了多个联系人的相关信息: 每个联系人的信息:ID, 名称, 手机号
ID | 名称 | 手机号 |
---|---|---|
201501 | John | 18601011223 |
201502 | Jennifer | 13810022334 |
201503 | AnXin | 18600100100 |
201504 | Unnamed | 13111011011 |
引例1:如何表示结构化信息?
使用之前学到的知识,可以表示为: (注意选择合适的类型)
int id[4] = { 201501, 201502, 201503, 201504 };
char name[4][16] =
{
“John”, “Jennifer”, “AnXin”, “Unnamed”
};
char phone[4][16] =
{
“18601011223”, “13810022334”,
“18600100100”, “13111011011”
};
引例1:如何表示结构化信息?
缺点:
(1)信息比较松散,不直观
(2)容易重名
(3) 数据存取不方便
比如,现在要按ID来查找一个联系人,将联系人的信息返回。这 个函数很难写出来,因为字段太多了。
int find (int id, char* name, char* phone)
{
}
貌似可以使用输出参数name, phone来返回联系人的信息。。
但是,如果字段变多,有100个字段呢?难道要用100个输出参数?
引例1:如何表示结构化信息?
最好有一种新的数据类型,直接表示联系人所有属性。
Contact persons[4]; // Contact: 一种新的类型
Contact find(int id); // 直接返回一个Contact对象 或
int find(int id, Contact* p); // 使用一个输出参数
这样的语法才是简洁直观的!
struct: 结构体 structure
C/C++的几个主要的基本类型,char/short/int, float/double, 以及数组,指针。但它们还不够用。。。
将基本类型组合起来,新形成了新的数据类型,称为·“自定义类 型”
。
例:
struct Contact
{
int id;
char name[16];
char phone[16];
};
这里定义一个新的类型Contact,它的地位和int,double之类相 同。
struct: 结构体
基本形式为
struct TypeName
{
// Members
};
其中,struct是关键字, TypeName为新类型的名称, Members则 是一系列成员变量。
① TypeName的命名规则:和变量名的规则一样,字母、 数字和下划线的组合。
② 成员变量可以普通的变量/数组定义
struct: 结构体
例如,
struct Contact
{
int id;
char name[16];
char phone[16];
};
定义一个新类型Contact,成员变量有id, name, phone
注意:成员变量的定义和普通普通的定义方式是一样的。
struct: 结构体
新类型的使用和基本类型差不多。。。
(1)定义一个变量
Contact c;
(2)定义数组
Contact cs[4];
(3)指针
Contact* pc = &c;
(4)作为函数参数
void test(Contact c);
(5)作为返回值类型
Contact make(int id);
(1) 变量定义和初始化
定义一个变量 Contact a; // 定义一个变量,不给初始值
定义时初始化:用大括号将各个成员的初始值列出,按顺序列表 。 (对比二维数组的初始化)
Contact a =
{
201501, // int的初始化
“Jennifer”, // char数组的初始化
“13810022334”
};
初始化时: ①注意使用大括号,末尾要加分号结束 ②各字段的初始值要与该字段的类型匹配 ③各初始值以逗号分开 按顺序初始化
(1) 变量定义和初始化
和一维维数一样,可以只初始化部分成员变量。
Contact a = { 201501, “John”}; //只初始化前2个成员
也可以直接清零
Contact a = {0}; // 则该变量的每一位都是0
(1) 变量定义和初始化
在内存视图里,一目了然:各个成员紧密排列,相当于被组 合在了一起。
Contact a =
{
201501, // int的初始化
“Jennifer”, // char数组的初始化
“13810022334”
};
观察a在内存窗口中的数据。。。
(2) 定义数组
可以和基本类型一样,定义数组,表示并排的多个对象。
Contact cs[4];
或定义时初始化,最后一个元素/字段后面不需要加逗号
Contact cs[4] =
{
{201501, “John”, “18601011223”},
{201502, “Jennifer”, “13810022334”},
{201503, “AnXi”, “18600100100”},
{201504, “Unnamed”, “18601011223”}
};
(3) 对成员的访问
使用点号访问其成员:
Contact a;
a.id = 201501;
strcpy(a.name, “John”);
strcpy(a.phone, “18601011223”);
注:strcpy函数用于字符串的拷贝,需要<string.h>
(3) 对成员的访问
数组方式
Contact cs[4];
cs[0].id = 201501;
strcpy(cs[0].name, “John”);
strcpy(cs[0].phone, “18601011223”);
注:strcpy函数用于字符串的拷贝,需要<string.h>
(3) 对成员的访问
注意:
访问成员指的是访问“结构变量”的成员
Contact a;
printf(“id: %d \n”, a.id);
千万不要写成
printf(“id: %d \n”, Contact.id); //错!!
Contact只是一个类型的名字而已。只要变量才对应内存。
小结
(1)初步学习用struct定义
新类型
(2)新类型的变量定义方式和基本类型差不多相同(注意其初始化
方式)
(3)成员的访问
:使用点号加上成员的名称
2结构体的更多使用方法
(1) 赋值
结构体变量可以互相赋值
Contact a = { 20141003, “AnXin”, “18600100100”};
Contact b = a;
赋值的结果:原理很复杂(到19章后以,才能完全解释)
目前来说,我们只需要知道,结构体赋值的结果是:每个字 节都相等。
(观察内存。。。)
(2) 指针
Contact a = { 20141003, “AnXin”, “18600100100”};
Contact* p = &a;
使用箭头->够p访问各成员变量。。。
printf(“id: %d, name: %s \n”, p->id, p->name);
p->id//p指向的结构体的id
也可以写作: (*p).id
,但一般大家不会这么写。
(3) 作为函数参数
和基本类型一样,可以作为函数参数 // 传值
void test(Contact a)
{
printf(“id: %d , name: %s \n”, `a.id`, a.name);
} // 传地址
void test(Contact* p)
{
printf(“id: %d , name: %s \n”, `p->id`, p->name);
}
(4) 作为函数的返回值
可以作为函数的返回值
Contact make (int id)
{
Contact a;
a.id = id;
return a; // 像基本类型一样,直接 return
};
但通常我们写成
void make(int id, Contact* a)
{
a->id = id;
}
具体原因后面再说。。。
(5) 匿名struct
只定义变量,不定义其类型。(只想定义一个变量,因而不需要类型)
// 该struct没有命名,编译器会内部分配一个名字
struct
{
char guid[128];
int user_id;
}info;
void main()
{
info.user_id = 98780987;
strcpy(info.guid, "
{09f140d5-af72-44ba-a763-c861304b46f8}");
}
注意事项1: 不支持其他运算
结构体默认只支持=,不支持其他运算:加减乘除。。。
例如,
Contact a, b;
Contact c = a + b; // 编译错误!不支持加法
(第24章我们讲如何让结构体支持各种运算)
注意事项2: 杜绝非正规写法
可以把结构体的类型定义写在函数内部,语法上允许。。。
int main()
{
struct Contact
{
int id;
char name[16];
char phone[16];
};
Contact a = { 20141003, “AnXin”, “18601011223”};
return 0;
}
但作者不建议这么写:之所以要自定义一个类型,最终目的是要重用, 这样定义的类型无法重用。
注意事项3: 杜绝非正规写法
另一种非正规写法,
struct Contact
{
int id;
char name[16];
char phone[16];
}a, b;
直接把变量名写在后面。。。这是为了省纸吗。。。
第18章会说,类型的定义一般是放在头文件里的,没这么干 的。
注意事项4: C++与C的差异
在struct语法上,C++和C略有差别。简要介绍C的用法:
struct Contact
{
int id;
char name[16];
char phone[16];
};
struct Contact a; // 定义变量的时候, // 也要加关键词struct (纯C)
具体另外开辟一讲。另行介绍。
3结构体的深层次认识
内容提要
- 结构体的大小:对齐问题
- 结构体变量作为成员
- 结构体作为参数:再论“传值与传地址”的效率问题
结构体的大小:对齐问题
结构体的大小跟成员有关,但有时会比各成员之和要大
struct Object
{
char a;
int b;
};
printf(“%d \n”, sizeof(Object)); // ??? 不是1+4
结构体的大小:对齐问题
为了实现对齐,编译器会对结构体进行填充Padding
。。。
(注:C/C++标准没有规定一定要对齐,这是编译器自己的 行为)
例如,通常的对齐规则为:
short: 其内存地址
必须是2字节对齐
(地址能被2整除)
int : 4字节对齐
为什么要对齐:因为cpu的指令要求,只能在对齐的地址上 存取,属于指令集的语法要求。不对齐就没法存取。 (并不是所有的cpu都有此限制)
结构体的大小:对齐问题
如何取消对齐规则?使其没有填充?
:不同的编译器都不同的方法,这不在C++标准中定义
:但是:永远不要这么做!!
结构体对齐是十分必要的机制,没有任何理由要取消对齐。
结构体作为成员
struct的成员的类型可以是另外一种struct类型
struct Score
{
float chinese;
float english;
float math;
};
struct Student
{
int id;
char name[16];
Score score; // 此成员变量的类型是 Score
};
注意前后顺序:把struct类型的定义放在Student的前面。
结构体作为成员
在内存视图上,仍然很简单,各成员在内存里依次排列 (可能有少许padding)
访问成员的子成员:仍然是使用点号
Student s;
s.score.english = 89.5;
结构体作为成员
另外一种形式: (这种应用场景相对复杂)
struct Student
{
int id;
char name[16];
Score* ps; // 使用指针,而不是直接包含
};
Score _score = { 88.0, 90.0, 98.0};
Student _stu; _
stu.ps = &score;
结构体作为参数:再论“传值与传地址”
结构体的体积较大,占用的内存空间较多,往往不使用传值 方式。
void test(Contact who)
{
printf(“id:%d, name:%s \n”, who.id, who.name);
}
两大问题:
(1)空间因素:使用了更多的内存空间
(2)时间因素:将值拷贝到参变量who,要花费较多的cpu
结构体作为参数:再论“传值与传引用”
结构体作为函数的参数时,总是用“传地址”方式。如果只 是输入参数,则加上const修饰。
void test(const Contact* who)
{
printf(“id:%d, name:%s \n”, who->id, who->name);
}
4 结构体的项目应用示例
项目需求Requirement
需求:实现一个学生成绩的录入与查询程序。每个学生的信 息有:学号(ID)、名字、成绩(语数外三门课)。要求实 现以下功能:
① 可以在控制台录入学生的信息;
② 可以查询已经录入的学生的个数;
③ 可以通过名字查询某个学生的信息;
④ 可以以列表方式显示所有学生的成绩。
总人数在100以内。
一、需求分析Requirement Specification
阶段目标:分析需求到底什么意思
(本项目意在展示struct的用法,所以此步简单带过。。。)
二、设计Design
阶段目标:计划一下如何实现目标需求。
(1)定义一个结构体Student
(2)各字段的表示:id(int), name(char[16]), score(int[3]),其中依次为语数外成绩
(3)数据保存:数组 Student[100]
(4)个数未知,需要记录总个数
(5)功能实现分析:略过。。。
三、编码实现Coding : (1) 定义数据
struct Student
{
int id;
char name[16];
int scores[3];
};
Student data[100]; // 存储所有数据
int count = 0; // 总个数
三、编码实现Coding : (2) 输入信息
定义一个用入信息输入的函数:
// 将输入的信息保存到输出参数s
// 返回0,表示成功录入。返回-1,表示录入失败。
int input(Student* s)
{
}
三、编码实现Coding : (3) 添加一个对象
函数: 将新录入的学生信息保存到数据源 // 返回值:0,成功;-1,失败
int add(Student* s)
{
return 0; // 总是成功
}
三、编码实现Coding : (4) 打印所有信息
函数: 将数据源里所有记录全部打印显示。
void list_all()
{
}
三、编码实现Coding : (5) 菜单
设计一个最简单的菜单系统。
add – 添加
list – 打印所有
find – 查找
count – 显示总个数
int main()
{
char cmdline[128];
while(1)
{
printf(“> “);
gets(cmdline); …
}
return 0;
}
四、单元测试与集成测试
单元测试:Unit Test单独对每一个函数/模块进行测试
集成测试:System Test / Integrated Test各函数/模块联 在一起测试(联调)
五、发布程序Release
将VC设置为静态编译,所得*.exe可以交给用户运行(不需 要对方电脑上安装VC)
小结
(1)为什么只添加、不删除?(12章)
(2)数组长度为100,但是只输入了4个对象,这太浪费空间了。 有什么更好的办法来存储数据? (12章)
(3)程序一退出,数据就没了,下次还得重来,怎么办? (17章)
(4)一个cpp里写这么多代码,是不是太拥挤了?有没有好的代 码组织方式? (18章)
(5)查找的时候,不能在菜单内直接带上参数吗?(15章)
#include <stdio.h>
#include <string.h>
定义数据
struct Student
{
int id;
char name[16];
int scores[3];
};
Student data[100]; // 数据源 :存储所有数据
int count = 0; // 总个数
// 定义一个用入信息输入的函数:
// 将输入的信息保存到输出参数s
// 返回0,表示成功录入。返回-1,表示录入失败。
int input(Student* s)
{
printf("ID: ");
scanf("%d", &s->id);
printf("Name: ");
//gets(s->name); // char*
scanf("%s", s->name);
printf("Score: ");
int a, b, c;
scanf("%d,%d,%d",&a, &b, &c);
s->scores[0] = a;
s->scores[1] = b;
s->scores[2] = c;
//scanf("%d,%d,%d",
// &s->scores[0], &s->scores[1], &s->scores[2]);
return 0;
}
//函数: 将新录入的学生信息保存到数据源
// 返回值:0,成功;-1,失败
int add(const Student* s)
{
data[count] = *s;
count ++;
return 0; // 总是成功
}
//函数: 将数据源里所有记录全部打印显示。
void list_all()
{
printf("ID \tName \tScore \n");
printf("------------------------\n");
for(int i=0; i<count; i++)
{
Student* s = &data[i];
printf("%d \t%s \t%d,%d,%d \n",
s->id,
s->name,
s->scores[0],
s->scores[1],
s->scores[2]);
}
printf("------------------------\n");
}
// 按照名称查找
Student* find(const char* name)
{
for(int i=0; i<count; i++)
{
Student* s = &data[i];
if( strcmp(name, s->name) == 0)
{
return s;
}
}
return NULL;
}
int main()
{
char cmdline[128];
while(1)
{
printf("> ");
scanf("%s", cmdline);
//printf("cmd: %s \n", cmdline);
// 退出
if( strcmp(cmdline, "exit") == 0)
{
printf("now exit...\n");
break;
}
// 菜单处理
if( strcmp(cmdline, "add") == 0)
{
Student s;
input(&s);
add(&s);
continue;
}
// 菜单处理
if( strcmp(cmdline, "list") == 0)
{
list_all();
continue;
}
// 菜单处理
if( strcmp(cmdline, "count") == 0)
{
printf("total: %d \n", count);
continue;
}
// 菜单处理
if( strcmp(cmdline, "find") == 0)
{
printf("enter Name: ");
char name[16];
scanf("%s", name);
Student* s = find(name);
if(s)
{
printf("Found: ID:%d, Name:%s,Score:%d,%d,%d\n",
s->id, s->name,
s->scores[0], s->scores[1],
s->scores[2]);
}
else
{
printf("Not Found\n");
}
continue;
}
printf("Bad Input\n");
}
return 0;
}
int test ()
{
Student s;
//input(&s);
s.id = 1024;
strcpy(s.name, "shaofa");
s.scores[0] = 70;
s.scores[1] = 80;
s.scores[2] = 90;
add(&s);
list_all();
return 0;
}