C++ 关于结构体struct的一些总结
文章目录
- 一、 结构体(struct)是什么?
- (1)概念
- (2)struct 与 calss 的区别
- 二、定义、声明与初始化
- (1)三种定义结构体的方法:
- (2)结构体变量初始化
- 三、结构体嵌套
- 四、结构体数组
- 五、结构体指针
- 六、结构体指针成员
- (1)指向文字常量区:
- (2)指向堆区:
- 七、结构体的拷贝
- (1)浅拷贝
- (2)深拷贝
- 八、返回值是结构体的函数
- 九、结构体的对齐规则
- (1) 为什么要对齐
- (2)强制对齐规则
- (3)有趣的问答
- 九、共用体 union
- 十、枚举 enum
一、 结构体(struct)是什么?
(1)概念
- 结构体是一种自定义的数据类型,它可以包含多个不同的数据类型的成员。结构体允许用户将相关的数据项组合在一起形成一个单独的实体,并可以对该实体进行操作。
- C++中结构体和类基本完全类似!C++中结构体能继承、能实现多态!结构体中也可以包含构造函数和析构函数和其他内部成员函数,因此结构体和类基本雷同!唯一的区别是,类中的成员变量默认为私有,而结构体中则为公有。
struct
更适合作为数据结构的实现体,class
更适合作为对象的实现体。
(2)struct 与 calss 的区别
概念:class和struct的语法基本相同,从声明到使用,都很相似,但是struct的约束要比class多,理论上,struct能做到的class都能做到,但class能做到的stuct却不一定做的到
类型:struct是值类型,class是引用类型,因此它们具有所有值类型和引用类型之间的差异
效率:由于堆栈的执行效率要比堆的执行效率高,但是堆栈资源却很有限,不适合处理逻辑复杂的大对象,因此struct常用来处理作为基类型对待的小对象,而class来处理某个商业逻辑
关系:struct不仅能继承也能被继承 ,而且可以实现接口,不过Class可以完全扩展。内部结构有区别,struct只能添加带参的构造函数,不能使用abstract和protected等修饰符,不能初始化实例字段
应用场景:
(1) 在表示诸如点、矩形等主要用来存储数据的轻量级对象时,首选struct
(2) 在表示数据量大、逻辑复杂的大对象时,首选class
(3) 在表现抽象和多级别的对象层次时,class是最佳选择
二、定义、声明与初始化
- 系统不会为结构体类型开辟空间,只会为结构体类型定义的变量开辟空间。
- 不能在结构体声明中初始化结构体成员,因为结构体声明只是创建一个新的数据类型,还不存在这种类型的变量。
- 默认情况下,所有结构体成员都是公开的,所以不需要使用关键字
public
。
(1)三种定义结构体的方法:
1.先定义结构体类型,再定义结构体变量。
struct Student
{
int Code;
string Name;
};
Student tom;
2.定义结构体类型的同时定义变量。
struct Student{
int Code;
string Name;
} tom;
3。定义一次性结构体类型。
struct{
int Code;
string Name;
} tom;
(2)结构体变量初始化
1.默认构造函数
struct Student
{
int Code;
string Name;
};
Student tom = {50, "tom"};
2.自定义构造函数
struct Student
{
int Code;
string Name;
Student (int code, string name){
Code = code;
Name = name;
}
};
Student tom = {50, "tom"};
三、结构体嵌套
一个类的对象可以嵌套在另一个类中一样,一个结构体的实例也可以嵌套在另一个结构体中。
#include <iostream>
#include <string>
using namespace std;
struct Address
{
string street;
string city;
string state;
};
struct Person {
string name;
int age;
Address address; // 结构体嵌套
};
int main() {
Person person1 = { "John Doe", 30, { "123 Main St", "Anytown", "CA" } }; // 嵌套结构体的初始化赋值
cout << "Name: " << person1.name << endl;
cout << "Age: " << person1.age << endl;
cout << "Address: " << person1.address.street << ", ";
cout << person1.address.city << ", " << person1.address.state << endl;
system("PAUSE");
return 0;
}
四、结构体数组
结构体数组本质是数组,只是数组的每个元素是结构体变量。
struct student {
int id;
char name[20];
float score;
};
int main() {
// 定义一个有3个元素的结构体数组
student stu[3];
// 给每个学生赋值
stu[0].id = 1;
strcpy(stu[0].name, "Tom");
stu[0].score = 90;
stu[1].id = 2;
strcpy(stu[1].name, "Jerry");
stu[1].score = 80;
stu[2].id = 3;
strcpy(stu[2].name, "Bob");
stu[2].score = 85;
// 输出每个学生的信息
for (int i = 0; i < 3; i++) {
cout << "ID: " << stu[i].id << endl;
cout << "Name: " << stu[i].name << endl;
cout << "Score: " << stu[i].score << endl;
cout << endl;
}
return 0;
}
五、结构体指针
- 结构体指针变量本质是变量,只是该变量保存的是结构体变量的地址。
- 在使用结构体指针时,要使用箭头运算符(
->
)来访问成员变量。因为指针本身只存储了地址信息,而不是结构体本身,所以需要使用箭头运算符来指示指针所指向的结构体中的成员变量。
struct student {
int id;
char name[20];
float score;
};
int main() {
// 定义一个名为s的结构体变量
student s;
// 定义一个名为p的结构体指针变量,并将其指向s
student *p = &s;
// 通过指针访问结构体中的成员变量
p->id = 1;
strcpy(p->name, "Tom");
p->score = 90;
return 0;
}
六、结构体指针成员
(1)指向文字常量区:
struct Stu{
int num;
char *name;
};
struct Stu lucy={101,"hello world"};
(2)指向堆区:
struct Stu{
int num;
char *name;
};
struct Stu lucy;
lucy.name=new char[32]; // 指向堆区
lucy.num=101;
strcpy(lucy.name,"lucy");
delete[] lucy.name;
七、结构体的拷贝
(1)浅拷贝
- 相同类型的结构体变量可以整体赋值,默认方式是浅拷贝
- 如果结构体中没有指针成员,浅拷贝不会带来任何问题。如果结构体中有指针成员,浅拷贝会带来多次释放堆区空间的问题。
#include <iostream>
#include <string>
using namespace std;
struct Student {
char* name;
int age;
};
int main() {
Student s1 = { "Tom", 18 };
Student s2 = s1; // 浅拷贝
cout << "Befor: s1.name: " << s1.name << endl;
cout << "Befor: s2.name: " << s2.name << endl;
s1.name = "Jerry";
cout << "After: s1.name: " << s1.name << endl;
cout << "After: s2.name: " << s2.name << endl;
system("PAUSE");
return 0;
}
(2)深拷贝
- 深拷贝是指复制一个新的对象,其中包含原始对象中所有成员变量的副本,而不是只简单地复制指向成员变量数据的指针。
- 如果结构体中有指针成员,尽量使用深拷贝。
八、返回值是结构体的函数
可以从函数返回结构体变量。在这种情况下,函数的返回类型是结构体的名称。
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
};
Student getStudent()
{
Student st;
cout << "Enter the name:";
cin.get();
getline(cin, st.name);
cout << "Enter the age ";
cin >> st.age;
return st;
}
int main()
{
Student stu = getStudent();
cout << "name : " << stu.name << endl;
cout << "age : " << stu.age << endl;
system("PAUSE");
return 0;
}
九、结构体的对齐规则
(1) 为什么要对齐
假设有这样一个结构体:
struct Data{
char a;
int b;
};
CPU一次读取4字节,当没有字节对齐时,内存状态:
- 访问a只需要一个周期,读取四个字节,只用第一个字节,其他字节丢弃。
- 访问b需要两个周期:第一个周期读取4字节,只要后三个字节;第二个周期读取4字节,只要第一个字节;然后将它们拼接成一个4字节的数据得到b。
CPU一次读取4字节,字节对齐时,内存状态:
- 访问a只需要一个周期,读取四个字节,只用第一个字节,其他字节丢弃。
- 访问b只需要一个周期,读取四个字节。
可以看到,字节对齐之后访问速度就变快了,而且不用进行字节拼接;只是需要浪费三个字节的空间(用空间换时间)。但是带来的好处是:提取快、方便、效率高。
(2)强制对齐规则
#pragma pack(value)
- 基本类型的对齐值就是其sizeof值;
- 结构体的对齐值是其成员的最大对齐值;
- 指定对齐值value,value值一般为1、2、4、8、16等(2的n次幂)。
- 编译器可以设置一个最大对齐值,怎么类型的实际对齐值是该类型的对齐值与默认对齐值取最小值得来。
(3)有趣的问答
问:现代Intel的CPU,不对齐并不会影响访问速度?
答:虽说如此,但是很多场景下我们对性能十分讲究,用的可能也不是Intel的CPU,比如一些嵌入式设备,又或者说游戏引擎开发,可谓是极尽性能之能事,这时候必须要小心翼翼,避免任何有可能影响存取速度的地方,以“榨干”机器的性能。
又比如,没有考虑内存对齐的时候,有些内存是“空着”的,也无法利用,这时候对于一些内存极小的设备来说,就必须要争取利用每一块可能的内存,避免空间浪费。
九、共用体 union
共用体(union
)是一种特殊的数据类型,它允许在同一个内存区域存储不同的数据类型。这些成员并不是随意放置,而是用有相同的首地址。因此,我们在任意时刻只可以按照一个数据类型对共用体进行赋值,共用体中这些成员的关系是“或”,即“不是你死就是我活”。
其实我们可以这样说“共用体其实可以在任意时刻被当作其任意一个数据成员来赋值使用”。共用体的出现基本上实现了我们追求的“万能数据类型”。
union my_union {
int i;
float f;
char str[10];
};
简单应用:
共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。例如,假设管理一个小商品目录,其中有一些商品ID为整数,而另一些的ID为字符串。在这种情况下,可以这样做。
struct widget
{
char brand[20];
int type;
union id
{
long id_num;
char id_char[20];
}id_val;
};
...
widget prize;
...
if(prize.type == 1)
cin>>prize.id_val.id_num;
else
cin>>prize.id_val.id_char;
十、枚举 enum
枚举(enum)是 C++ 中的一种数据类型,它允许将一组常量值定义为一个命名集合。枚举中的每个元素都有一个关联的整数值,默认情况下从 0 开始递增。枚举元素也可以显式地指定整数值。
enum EnumName {
Element1,
Element2 = 10,
Element3
};
简单应用:
enum Color {
Red,
Green,
Blue
};
int main() {
Color c = Red;
if (c == Green) {
std::cout << "Green\n";
} else if (c == Blue) {
std::cout << "Blue\n";
} else if (c == Red) {
std::cout << "Red\n";
}
}