目录
注:
本笔记参考:《C++ PRIMER PLUS(第6版)》
结构简介
C++中,对于一些复杂数据的存储需求(如录入学生信息),可以使用 结构 这一数据格式进行处理。结构,是一种可以存储多种类型的数据格式,同时,它也是 C++ OOP(类)的基石。
结构是用户定义的类型,用户通过结构声明定义这种类型的数据属性。创建结构包括两步:
- 定义结构描述:描述并标记存储在该结构中的各种数据类型;
- 按照描述创建结构变量(创建数据对象)。
例如:
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对象作为成员的结构 进行初始化。
其他结构属性
结构的一些特点
结构与内置类型很相似:
- 结构也可以被作为函数参数和返回值;
- 结构之间也可以使用赋值运算符(=)进行赋值(这样做会把结构中每一个成员的值设置为另一个结构成员的值),这就是 成员赋值 。
例子
#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个字节。这就是因为 内存对齐规则 。
------
内存对齐规则
- 第一个成员在 结构体变量的偏移量为0 的地址处;
- 其他成员变量要对齐到 对齐数的整数倍 的地址处(相当于偏移量为0的地址而言);
- 结构体总大小为 最大对齐数(每个成员变量都有一个对齐数)的整数倍 ;
- 如果是嵌套了结构体的情况:嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
注:
- 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值 。譬如在VS系统中,一个不是第一个成员的int类型是4个字节,与默认对齐数8相比,4小,取4为对齐数。
- VS中默认对齐数是8 (ps:Linux系统没有默认对齐数的概念。)
内存对齐出现的理由
- 方便跨平台移植;
- 方便了处理器对于结构这一数据类型的查找。
结构数组
可以创建元素为结构的数组,例如:
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++允许指定结构成员占用特定位数,这种结构方便程序员创建与某些寄存器配对的数据结构。
位段与结构的不同在于:
- 位段的成员必须是 int、unsigned int 或 signed int 。(char类型也可以)
- 位段的成员名后边有一个冒号和一个数字
例子:
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 };
其中:
- first在默认情况下是 0 ;
- second 被赋值为 100 ;
- 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个字节。