结构体与C++引用

结构体与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 语言中的结构体对齐是一种编译器优化手段,用于提高内存访问效率和节省内存空间。结构体对齐的主要意义和作用包括以下几个方面:

  1. 提高内存访问效率:结构体对齐可以使得结构体的成员按照合适的字节边界对齐,从而使得访问结构体成员的地址更加高效。例如,对齐的结构体成员可以直接使用 CPU 的原生数据类型进行访问,而不需要额外的对齐操作。

  2. 减少内存碎片:结构体对齐可以使得结构体的大小变得更加整齐,从而减少内存碎片。例如,如果结构体中的某个成员是 4 字节对齐的,但其后紧跟着一个 1 字节的成员,那么编译器可能会在 4 字节对齐的位置插入 3 字节的填充,以确保下一个成员按照合适的字节边界对齐,这样可以减少内存碎片,提高内存利用率。

  3. 提高缓存效率:结构体对齐可以使得结构体成员在缓存中更加紧凑地排列,从而提高缓存的命中率。例如,对齐的结构体成员可以更好地利用 CPU 缓存行的特性,减少缓存行的浪费,提高缓存效率。

  4. 与外部数据交互:在与外部数据进行交互时,结构体对齐可以确保结构体的布局与外部数据的格式一致,从而简化数据的转换和传输过程。

总的来说,结构体对齐是一种重要的编译器优化手段,它可以提高内存访问效率、减少内存碎片、提高缓存效率,并与外部数据交互等。在实际编程中,可以通过编译器选项或者 #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类型成员进行对齐。附上结果图。

4095316ce33fb18932f71a4eea827f4

结构体指针

一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设置一个指针变量,用它指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素。从而能够通过结构体指针快速访问结构体内的每个成员。

#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++ 中,引用是一种别名,允许我们使用原始变量的别名来访问相同的内存位置。在函数中,我们可以将引用作为参数传递给函数,这样函数可以直接操作原始变量,而不是其副本。这种通过引用传递参数的方式称为引用传递。

当我们在函数参数中使用引用时,我们可以改变传递给函数的实际参数的值。这样做有几个好处:

  1. 避免复制:使用引用传递参数可以避免不必要的复制操作,因为函数直接操作原始变量而不是副本。

  2. 改变实际参数:函数可以修改传递给它的变量的值,这在某些情况下可能很有用。

  3. 传递大对象:通过引用传递参数比通过值传递参数更高效,尤其是当对象很大时。

下面是一个示例,演示了如何在 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;
}