C++初探 4-2(复合类型 - 结构)

目录

注:

结构简介

在程序中使用结构

C++11结构初始化

结构可以将string类作为成员

其他结构属性

结构的一些特点

更为特殊的声明与初始化

内存对齐

例子

内存对齐规则

内存对齐出现的理由

结构数组

结构中的位字段(位段)

共用体

枚举

设置枚举量的值

枚举的取值范围


注:

        本笔记参考:《C++ PRIMER PLUS(第6版)》


结构简介

        C++中,对于一些复杂数据的存储需求(如录入学生信息),可以使用 结构 这一数据格式进行处理。结构,是一种可以存储多种类型的数据格式,同时,它也是 C++ OOP(类)的基石。

        结构是用户定义的类型,用户通过结构声明定义这种类型的数据属性。创建结构包括两步:

  1. 定义结构描述:描述并标记存储在该结构中的各种数据类型;
  2. 按照描述创建结构变量(创建数据对象)。

例如:

struct student        //结构的声明
{
	char name[10];
	unsigned char address[10];
	int ID;
};

【分析】

  • 上述语句创建了一个名为 student 的新类型;
  • 其中的每个列表项都是一条声明语句
  • 列表的每一项都被成为结构成员,上述结构有3个成员。

        然后就是创建变量了,与C语言相比,C++在声明结构变量中允许省略了关键字struct

//外部声明
struct student xiaoMing;    //C语言的结构体声明方式
student xiaoHong;           //C++的结构声明方式

student xiaoLan =           //可以在声明变量的同时进行初始化
{
    "xiaoLan",        //name变量
    "xxxxxx",         //address变量
    1213,             //ID变量
};

student xiaoLv = { "xiaoLv", "acadad", 1234 };  

//内部声明
int main()
{
    student liMing;              //在结构声明放在主函数内也是可行的
    return 0;
}

  由于结构的声明会影响该变量的生命周期,所以在进行结构声明时需要考虑该变量的使用范围。(一般,外部声明被使用的场景更多)

        现在我们可以通过成员运算符[ . ]来访问结构的成员,例如:

        被访问的成员的使用方式与基本类型相同,譬如ID被声明为int类型,就可以将其看作int类型的变量。

  ps:访问类成员函数的方式是从访问结构成员变量的方式衍生出来的。

在程序中使用结构

例子

#include<iostream>
struct shopping
{
	char name[20];
	float weight;
	double price;
};

int main()
{
	using namespace std;
	shopping list_1 =
	{
		"香蕉",		//name成员
		1.2,			//weight成员
		12.55			//price成员
	};
	shopping list_2 =
	{
		"苹果",
		0.8,
		10.81
	};

	cout << "您将要购买这些 " << list_1.name << " 和 " << list_2.name << " 。\n";
	cout << "请支付 " << list_1.price + list_2.price << " 元。\n";

	return 0;
}

程序执行的结果是:

【分析】

        上述的 shopping类型 是在外部创建的,外部变量可以被所有的函数共享。这种进行外部结构声明的方法也被C++所提倡。

        可以将每一个结构成员单独看作相应类型的变量,比如:shopping.name ,并且使用访问该类型变量的方式访问结构成员,就比如 shopping.name[0] 就是用下标访问存储在 name成员 中的字符串。

C++11结构初始化

        C++11同样支持对结构进行列表初始化,等号(=)可省略:

student xiaoLv {"xiaoLv", "acadad", 1234};

        并且,如果在大括号( {} )内没有包含任何东西,则各个成员都将被设置为 0 ,例如:

student xiaoHei{};

使用调试可以看见:

结构可以将string类作为成员

        类似于下面的代码,将原本的数组成员name替换成string类成员。

#include<iostream>
struct student
{
	std::string name;        //需要访问名称空间std
	unsigned char address[10];
	int ID;
};

        需要注意的是这种写法要求编译器支持 对string对象作为成员的结构 进行初始化。

其他结构属性

结构的一些特点

        结构与内置类型很相似:

  1. 结构也可以被作为函数参数和返回值;
  2. 结构之间也可以使用赋值运算符(=)进行赋值(这样做会把结构中每一个成员的值设置为另一个结构成员的值),这就是 成员赋值 。

例子

#include<iostream>
struct student
{
	char name[10];
	unsigned char address[10];
	int ID;
};

int main()
{
	using namespace std;
	student std_1 =
	{
		"阳光少年",
		"xxxsss",
		1234567
	};
	student std_2;

	cout << "std_1:" << "std_1.name = " << std_1.name
		<< "  std_1.address = " << std_1.address
		<< "  std_1.ID = " << std_1.ID << endl;

	cout << "std_2 = std_1\n";
	std_2 = std_1;				//赋值

	cout << "std_2:" << "std_2.name = " << std_2.name
		<< "  std_2.address = " << std_2.address
		<< "  std_2.ID = " << std_2.ID << endl;

	return 0;
}

程序执行的结果是:

------

更为特殊的声明与初始化

1. 除上述初始化方法外,还可以同时完成定义结构和创建结构变量:

struct teacher
{
    char name[20];
    int bonus;
}tac_1, tac_2;

  甚至可以对上述创建的变量进行初始化:

struct teacher
{
    char name[20];
    int bonus;
}tac_1 =
{
    "Alice",
    5000
};

2. 匿名结构体

        声明没有名称的结构类型是被允许的,例如:

struct
{
	int x;
	int y;
}position;

上述的声明方法将会创建一个名为position的结构变量。不过这种类型创建的变量没有名称,因此之后无法在创建这种类型的变量。

  C++结构不仅包含了C结构的,并且具有更多特性。

  可以参考:笔记21-1

内存对齐

例子

        结构中存在名为内存对齐这种规则。先看例子:

#include<iostream>
struct S
{
	char c_1;	//1个字节
	int i;		//4个字节
	char c_2;	//1个字节
};

int main()
{
	using namespace std;
	struct S s_1 = { 0 };
	cout << sizeof s_1 << endl;

	return 0;
}

程序执行的结果是:

【分析】

        上述程序中的int类型和char类型已确认分别是 4个字节 和 1个字节 。也就是说,如果只计算结构成员,那么该结构的大小应该是 1 + 4 + 1 = 6 个字节,但实际上确实12个字节。这就是因为 内存对齐规则 。

------

内存对齐规则

  1. 第一个成员在 结构体变量的偏移量为0 的地址处;
  2. 其他成员变量要对齐到 对齐数的整数倍 的地址处(相当于偏移量为0的地址而言)
  3. 结构体总大小最大对齐数(每个成员变量都有一个对齐数)的整数倍 ;
  4. 如果是嵌套了结构体的情况:嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

注:

  • 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值 。譬如在VS系统中,一个不是第一个成员的int类型是4个字节,与默认对齐数8相比,4小,取4为对齐数。
  • VS中默认对齐数是8 (ps:Linux系统没有默认对齐数的概念。)

内存对齐出现的理由

  1. 方便跨平台移植;
  2. 方便了处理器对于结构这一数据类型的查找。

结构数组

        可以创建元素为结构的数组,例如:

struct teacher
{
	char name[20];
	int bonus;
};

int main()
{
	teacher group[100];    //创建以结构teacher为元素类型的数组
	return 0;
}

        上述代码中的 group 就是以结构teacher为元素的数组,在使用时,每个 teacher对象 都可以与成员运算符[ . ]一起使用:

cin >> group[0].bonus;
cout << group[99].name << endl;

  不过类似于 group.name 这种用法是不行的,因为 group 是数组。

        如果要初始化结构体数组,则可以如下所示:

teacher group[2] =
{
	{"小明", 4000},		//初始化第一个元素
	{"李明", 5000}		//初始化第二个元素
};

使用例

#include<iostream>
struct teacher
{
	char name[20];
	int bonus;
};

int main()
{
	using namespace std;

	teacher group[2] =
	{
		{"小明", 4000},		//初始化第一个元素
		{"李明", 5000}		//初始化第二个元素
	};

	cout << "第一位老师是 " << group[0].name << " ,第二位老师是 " << group[1].name
		<< "\n他们的工资一共是 "
		<< group[0].bonus + group[1].bonus << " 元。\n";

	return 0;
}

程序执行的结果是:

结构中的位字段(位段)

        类似于C语言,C++允许指定结构成员占用特定位数,这种结构方便程序员创建与某些寄存器配对的数据结构。

        位段与结构的不同在于:

  1. 位段的成员必须是 int、unsigned int 或 signed int 。(char类型也可以)
  2. 位段的成员名后边有一个冒号和一个数字

例子:

struct S_1
{
	unsigned int a_1 : 4;	//为 a_1 分配 4个字节 的空间
	unsigned int : 4;		//不被使用的4个字节的空间
	bool c_1 : 1;
	bool d_1 : 1;
};

        并且可以像访问普通结构一样访问字段:

S_1 s_1 = { 12, true, false };

共用体

        共用体(也称联合体)可以存储不同的数据类型,但这些数据的存储形式与普通的结构有所不同:存储在共用体内的数据可以共用内存单元。

先看例子:

#include<iostream>
//联合类型的声明
union Un
{
	char c;		//1个字节
	int i;		//4个字节
};

int main()
{
	using namespace std;
	//联合变量的定义
	union Un un_1;
	//计算这个变量的大小
	cout << "un_1所占空间的大小是:" << sizeof un_1 << endl;

	return 0;
}

程序执行的结果是:

        此处显示 un_1 的大小是 4个字节 ,但如果按照计算,un_1 的大小应该是 1(char类型)+ 4(int类型)= 5个字节,这就是共用体成员共用空间的实例。

------由上述例子可以得出以下结论:

        共用体的特点:共用体的成员是共用一块空间的,且该共用体变量的大小至少是 最大成员的大小

        通过观察共用体的成员所占空间的地址可以论证上述特点:

        注意此处共用体成员的地址都是相同的。如图:

        因为这种存储结构,共用体每次只能使用\存储一个值。为了防止空间不够,共用体的长度必须是最大成员的长度。

使用例:

#include<iostream>
struct widget
{
	char brand[20];
	int type;
	union id		//结构和联合体的嵌套使用
	{
		long id_num; 
		char id_ch[20];
	}id_val;		//该结构成员类型是 联合体 ,中间标识符是 id_val
};

int main()
{
	using namespace std;
	widget prize;

	cout << "请选泽要输入的商品信息:";
	cin >> prize.type;

	cout << "请输入对应商品的信息:";
	if (prize.type == 1)
		cin >> prize.id_val.id_num;
	else
		cin >> prize.id_val.id_ch;

	cout << "输入完毕。\n";

	return 0;
}

也可以使用 匿名共用体 ,其成员将成为位于相同地址处的变量:

#include<iostream>
struct widget
{
	char brand[20];
	int type;
	union		//此处没有联合体的名字
	{
		long id_num;
		char id_ch[20];
	};			//匿名联合体
};

int main()
{
	using namespace std;
	widget prize;
	//省略中间代码
	if (prize.type == 1)	
		cin >> prize.id_num;
	else
		cin >> prize.id_ch;

	return 0;
}

  注意:在使用联合体变量的成员时要分开使用,防止成员的内存互相侵占

枚举

        enum工具为我们提供了另一种创建符号常量的方式(可代替const)。enum的语法与结构类似,例如:

enum color
{
	red,		//对应值:0
	orange,		//对应值:1
	yellow,		//对应值:2
	green,		//对应值:3
	blue,		//对应值:4
	violet,		//对应值:5
	indigo,		//对应值:6
	untraviolet	//对应值:7
};

【分析】

  • color是类型的名称,这种类型被称为枚举;
  • red、orange 和 yellow 等是符号常量,被称为枚举量。

注:

  在默认情况下,第一个枚举量的值是整数0,第二个是1,以此类推。

枚举的声明与使用

类似于:

枚举变量的赋值会受到一些限制,如果将一个非法值赋给枚举变量将会导致编译器警告并报错。

  之所以会出现上述情况,是为了在进行程序移植时,保证枚举变量的使用安全。


        除上述所说情况外,枚举类型只有赋值运算符。这意味着,枚举是无法进行算术运算的:

        但要注意,枚举量也是整型,所以这种常量也可以被提升为int类型(不过int类型无法自动转换成枚举类型):

  不过我们可以通过强制类型转换将int值赋给枚举变量:

要注意,如果将一个不在该枚举范围内的值强制类型转换,其结果是未定义的:

  在上述例子中,存在这样一条语句:band = orange + red;  这条语句的报错并非未定义运算符,而是无法将int类型的变量赋给枚举类型

  之所以这样报错,是因为在算术表达式中,枚举类型被转换成了整型,即 orange + red = 1 + 0,表达式本身合法,但是从int到枚举的赋值是不合法的。

设置枚举量的值

枚举变量的赋值方式:

enum bits { one = 1, two = 2, four = 4, eight = 8 };    //指定的值必须是整数

------或者------

enum bigstep { first, second = 100, third };

其中:

  1. first在默认情况下是 0 ;
  2. second 被赋值为 100 ;
  3. third 接在second后面,是 101 。

------或者------

enum {zero, null = 0, one, numero_uno = 1};

其中:

  • zero 和 null 都是 0 ;
  • one 和 numero_uno 都是 1 。

枚举的取值范围

        原本,对于枚举而言,只有声明中指出的值是有效的。但是C++中的强制类型转换增加了枚举变量的合法值。每个枚举都有取值范围,通过强制类型转换转换,可以将取值范围中的任何整数值赋给枚举变量。例如:

enum bits { one = 1, two = 2, four = 4, eight = 8 };

int main()
{
	bits myFlag;
	myFlag = bits(6);		//赋值合法,6 位于该枚举的范围内

	return 0;
}

枚举取值范围的定义如下:

||| 上限

  • 设 x 是取值范围的上限,则

    举例:上述定义的枚举 bits ,最大枚举值是8(2³),则

    故枚举 bits 的取值范围的上限是15;

||| 下限

  • 设 y 为取值范围的下限,则

    例如:若最小的枚举值是-6,则

    故该枚举取值范围的下限是 -7。

  枚举类型的存储空间大小由编译器决定,如果枚举的取值范围较小,则使用空间更少;如果枚举内包含了long类型的值,则使用4个字节。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值