结构体

结构体


1结构体的定义和基本使用

引例1
下面的表格记录了多个联系人的相关信息: 每个联系人的信息:ID, 名称, 手机号

ID名称手机号
201501John18601011223
201502Jennifer13810022334
201503AnXin18600100100
201504Unnamed13111011011

引例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结构体的深层次认识

内容提要

  1. 结构体的大小:对齐问题
  2. 结构体变量作为成员
  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;
}

在这里插入图片描述

学习资源 《C语言/C++学习指南》语法篇(从入门到精通)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值