结构体与C++引用
- C语言
- 2024-03-28
- 16111热度
- 0评论
结构体与C++引用
结构体的定义、初始化、结构体数组
在程序编写的过程中,有时候需要将不同类型的数据组合为一个整体,以便于引用。例如:一名学生有学号、姓名、性别、年龄、地址等属性,如果针对学生的学号、姓名、年龄等都单独定义一个变量,那么在有多名学生时,变量就难以分清。为此C语言提供结构体来管理不用类型的数据组合。
声明一个结构体类型的一般形式为
struct 结构体名
{成员表列};
struct student
{
int num;char name[20];char sex;
int age;float score;char add[30];
};
结构体的scanf读取和输出
#include <stdio.h>
struct student{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}; //结构体类型声明,注意在最后要加分号
int main() {
//定义结构体
struct student s={1001,"lele",'M',20,85.4,"shenzhen"};
//定义结构体数组
struct student sarr[3];
int i;
printf("%d %s %c %d %5.2f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);
for(i=0;i<3;i++)
{
//%c之前需要用空格隔开,否则%c会将空格读取
scanf("%d%s %c%d%f%s",&sarr[i].num,&sarr[i].name,&sarr[i].sex,&sarr[i].age,&sarr[i].score,&sarr[i].addr);
}
for(i=0;i<3;i++)
{
printf("%d %s %c %d %5.2f %s\n",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score,sarr[i].addr);
}
return 0;
}
注意:
1、结构体类型声明要放在main函数之前,这样main函数才能使用这个结构体,工作中往往把结构体声明放在头文件中。
2、结构体类型声明最后一定要加分号,否则会编译不通。
3、定义结构体变量时,使用struct student进行定义,不能只单独出现struct或student。sarr是结构体数组变量。
4、结构体初始化只能在一开始定义,如果 struct student s={1001,"lele",'M',20,85.4,"Shenzhen"}已经执行,即 struct student s 已经定义,就不能再执行 s={1001,"lele",'M',20,85.4,"Shenzhen"}。
5、如果结构体变量已经定义,那么只能对它的每个成员单独赋值,如s.num=1003。
6、采用"结构体变量名.成员名"的形式来访问结构体成员,如s.num访问学号。在进行打印输出时,必须访问到成员,而且printf中的%类型要与定义的成员类型匹配。使用scanf读取标准输入时,也必须的各成员取地址,然后进行存储。
结构体对齐
C 语言中的结构体对齐是一种编译器优化手段,用于提高内存访问效率和节省内存空间。结构体对齐的主要意义和作用包括以下几个方面:
-
提高内存访问效率:结构体对齐可以使得结构体的成员按照合适的字节边界对齐,从而使得访问结构体成员的地址更加高效。例如,对齐的结构体成员可以直接使用 CPU 的原生数据类型进行访问,而不需要额外的对齐操作。
-
减少内存碎片:结构体对齐可以使得结构体的大小变得更加整齐,从而减少内存碎片。例如,如果结构体中的某个成员是 4 字节对齐的,但其后紧跟着一个 1 字节的成员,那么编译器可能会在 4 字节对齐的位置插入 3 字节的填充,以确保下一个成员按照合适的字节边界对齐,这样可以减少内存碎片,提高内存利用率。
-
提高缓存效率:结构体对齐可以使得结构体成员在缓存中更加紧凑地排列,从而提高缓存的命中率。例如,对齐的结构体成员可以更好地利用 CPU 缓存行的特性,减少缓存行的浪费,提高缓存效率。
-
与外部数据交互:在与外部数据进行交互时,结构体对齐可以确保结构体的布局与外部数据的格式一致,从而简化数据的转换和传输过程。
总的来说,结构体对齐是一种重要的编译器优化手段,它可以提高内存访问效率、减少内存碎片、提高缓存效率,并与外部数据交互等。在实际编程中,可以通过编译器选项或者 #pragma pack
等指令来控制结构体对齐的行为,以满足程序的性能和内存需求。
提示
1、结构体大小必须是其最大成员的整数倍。
2、如果两个相邻的结构体成员初始大小之和比最大的结构体成员小,那么两个相邻的成员会存储到同一片内存区域里,而不是直接与最大的结构体大小对齐。
#include <stdio.h>
//两个成员,short类型加上6字节与double类型对齐
struct type_1{
double score; //double是一种浮点类型,8个字节,浮点分为float和double
short age; //short是一种短整型,2个字节
};
//三个成员,int和short相邻,并且相加比double类型字节小,所以short类型加2字节,与int一起组成成8字节,与double类型对齐
struct type_2{
double score;
int height;
short age;
};
//与type_2的区别是double和int成员交换位置
struct type_3{
int height;
double score;
short age;
};
struct type_4{
int height;
char sex;
short age;
};
int main() {
struct type_1 s1;
struct type_2 s2;
struct type_3 s3;
struct type_4 s4;
//struct type_2 s5={1, 2, 3}; //调试
printf("s1 size=%d\n",sizeof(s1));
printf("s2 size=%d\n",sizeof(s2));
printf("s3 size=%d\n",sizeof(s3));
printf("s4 size=%d\n",sizeof(s4));
return 0;
}
代码的运行结果:
s1 size=16
s2 size=16
s3 size=24
s4 size=8
type_2和type_3的成员位置改变导致了结构体大小不一样,对应提示第2点。注释部分代码供读者自行调试,在Clion的内存视图对s5取地址,可查看到int和short类型的成员并不是直接增加字节与double类型成员对齐,而且short成员增加2字节,合并称为8字节,与double类型成员进行对齐。附上结果图。
结构体指针
一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设置一个指针变量,用它指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素。从而能够通过结构体指针快速访问结构体内的每个成员。
#include <stdio.h>
struct student{
int num;
char name[20];
char sex;
};
int main() {
struct student s={1001,"xiaocai",'M'};
struct student sarr[3]={1001,"zhangsan",'M',1002,"lisi",'M',1003,"wangwu",'F'};
struct student *p; //定义结构体指针
int num;
p=&s;
printf("%d %s %c\n",p->num,p->name,p->sex);
p=sarr; //不用取地址符是因为数组名对应的就是首地址
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex); //方式1获取成员
printf("%d %s %c\n",p->num,p->name,p->sex); //方式2获取成员
printf("------------------------------\n");
p=p+1;
printf("%d %s %c\n",p->num,p->name,p->sex);
return 0;
}
使用(p).num访问成员为什么要加括号?原因是"."成员选择的优先级高于"*"取值运算符,所以必须加括号,通过\p得到sarr[0],然后获取对应的成员。
typedef的使用
前面定义结构体变量时使用的语句是struct student s,以这种方式来定义结构体变量有些麻烦,即每次都需要写struct student。为了简化操作,可以使用typedef声明新的类型名来替代已有的类型名。
#include <stdio.h>
typedef struct student{
int num;
char name[20];
char sex;
}stu,*pstu;
typedef int INTEGER;
int main() {
stu s={1001,"xiaocai",'M'};
pstu p;
INTEGER i=10; //其实本质还是整型
p=&s;
printf("i=%d,p->num=%d",i,p->num);
return 0;
}
使用 stu 定义结构体变量和使用 struct student 定义结构体变量是等价的;使用 INTEGER 定义变量 i 和使用 int 定义变量 i 是等价的;pstu 等价于 struct student*,所以 p 是结构体指针 变量。
C++引用讲解
在 C++ 中,引用是一种别名,允许我们使用原始变量的别名来访问相同的内存位置。在函数中,我们可以将引用作为参数传递给函数,这样函数可以直接操作原始变量,而不是其副本。这种通过引用传递参数的方式称为引用传递。
当我们在函数参数中使用引用时,我们可以改变传递给函数的实际参数的值。这样做有几个好处:
-
避免复制:使用引用传递参数可以避免不必要的复制操作,因为函数直接操作原始变量而不是副本。
-
改变实际参数:函数可以修改传递给它的变量的值,这在某些情况下可能很有用。
-
传递大对象:通过引用传递参数比通过值传递参数更高效,尤其是当对象很大时。
下面是一个示例,演示了如何在 C++ 函数中使用引用:
//#include <iostream> //C++的头文件,但是C++兼容C,读者可以使用stdio.h的头文件
#include <stdio.h>
// 函数参数为引用
void increment(int &x) {
x++; // 修改实际参数的值
}
int main() {
int num = 5;
//std::cout << "Before increment: " << num << std::endl;
printf("Before increment:%d\n",num);
// 传递变量的引用给函数
increment(num);
//std::cout << "After increment: " << num << std::endl;
printf("After increment:%d\n",num);
return 0;
}
可以观察到在使用C++的引用后,在子函数内操作和函数外操作一致,不像前面学习的需要用指针去操作,这样编程效率会更高。
下一个例子是子函数内修改主函数的一级指针变量(重点)
#include <stdio.h>
void modify_pointer(int *&p,int *q)
{
p=q;
}
int main() {
int *p=NULL;
int i=10;
int *q=&i;
modify_pointer(p,q);
printf("after modify_pointer *p=%d\n",*p);
return 0;
}
如果要使用C的代码,那就需要使用二级指针,下面给出示例。
#include <stdio.h>
void modify_pointer(int **p, int *q)
{
*p = q;
}
int main() {
int *p = NULL;
int i = 10;
int *q = &i;
modify_pointer(&p, q);
printf("after modify_pointer *p=%d\n", *p);
return 0;
}
C++的布尔类型
布尔类型在 C 语言没有,是 C++的,在 C++ 中,布尔类型用于表示逻辑值,它有两个可能的值:true 和 false。布尔类型的关键字是 bool。通过下面代码进行理解。
#include <stdio.h>
//设置布尔值的好处是提升了代码的可阅读性
int main() {
bool a = true;
bool b = false;
printf("a=%d,b=%d\n", a, b);
return 0;
}