C和指针—结构体和联合

首先在这一章我们要讲的是结构与联合。
在平时,我们往往需要对数据进行成组的形式进行存储和访问,所以,在这里C提供了两种可以存储多种数据的数据类型:数组和结构

1.关于结构体的基础知识

数组,这是一个同类型元素的集合,而结构体,可以存储不同类型的元素,并且这两个类型的访问方式也是不一样的,数组是通过下标的形式进行访问的,而结构体是通过名字来访问。所以,一个结构变量在表达式中使用时,他并不被替换成一个指针。

结构体的组成:

对于结构体,我们先要认清其中的各种组成

struct A
{
          int a ;
         char b ;
};

首先在这个里面
在这个里面,我们要清楚,A是这个结构体的标签,里面的a,b是这个结构体的成员,在这里我们没有给这个结构体提供变量,所以并没有创建变量。想要创建变量,就可以这样来写,struct A x;在这里,x是一个类型为struct A类型的变量。

另外,在写程序中,结构体我们会更多的和typedef进行搭配使用。
例:

typedef struct BiTNode
{
           datatype data ;
           struct BiTNode *lchild , * rchild;
} BiTNode, *BiTree;

在这里对这个结构体类型进行重定义,在以后BiTNode就相当于struct BiTNode,而BiTree就相当于struct BiTNode *。当初学习数据结构的时候,记得无数人都因为这个疑问产生过疑惑。所以在书写链表等数据结构时,推荐大家都使用这种方式书写代码,另外,在这提一个技巧,我们可以把结构体放到头文件中,然后源文件只需要使用预处理指令就可以把头文件包含进来了。

其实这个就typedef int datatype是一样的。用datatype把int给替代了。

结构体就是类的一个过渡。
访问结构体成员,需要结构体变量的名字,在对成员进行引用。

结构体的访问:

  • 1:直接访问

这种方式被称为是通过点操作符(.)进行访问,点操作符,接受两个操作数,它的左边是结构变量的名字,右边是需要访问的成员的名字。

struct A
{
                 int a ;
                 char b ;
};

int main()
{
                 struct A x;
                 x.a =5;
                 return 0 ;
}

在这里有一个有趣的((comp.sa)[4]).c说的就是comp这个变量的成员sa是一个数组,然后选择(comp.sa)[4],即这个数组的第四个元素,而刚好,这个歌元素又是一个结构体,所以再访问这个元素的成员.

  • 2.间接的访问

C语言的强大之处在于指针的存在,当然,我们的结构体也需要使用指针,所以产生了一种通过指针的间接访问的形式。

struct A
{
                 int a ;
                 char b ;
};

int main()
{
                 struct A x;
                 struct A *p ;
                 p = &x ;
                 p->a = 5;
                 return 0 ;
}

p->a等价于(*p).a,这个又等价于x.a
p所指向的结构体变量中的a成员。

->这个操作符我们常常称呼为箭头操作符,这个操作符左边必须是一个指向结构体类型的指针。而右边必须是一个结构体中的成员。

关于结构体的自引用

struct A
{
    int a;
    char b;
    struct A c;
};

在这里面对于c而言就相当于又是另一个完整的struct A的结构,这样就会重复进行下去,其实这个就想我们给了一个没有限制条件的递归的一个程序,你说是不是呢?

另外,看下面着一个例子:

typedef struct A
{
    int a;
    char b;
    NODE *c;
}NODE;

这个目的是为了创建类型名NODE,但是,失败了,类型名是在最后的末尾定义的,但是在结构体内部,你就引用了类型名,这样是肯定不符合逻辑的,相当于你把你没定义的东西拿出来用,用完才给它定义。

所以在链表等数据结构中,我们时常这样写:

typedef struct node
{
    int data;
    struct node *pnext;

}NODE,*PNODE;

不完整声明

对于一些一个结构中包含了另外一个结构的一个或者多个成员。那么那么我们应该怎么做呢,这时我们要使用一个不完整声明,它声明一个作为结构标签的标识符,然后,我们可以把这个标签用在不需要知道这个结构的长度的声明中,如果声明指向这个结构的指针,那么接下来的声明把这个标签与成员列表联系在一起。

struct B;

struct A
{
    int a;
    char b;
    struct B *ptail;
};
struct B
{
    int a;
    char b;
    struct A *ptail;
};


结构体的初始化

结构体的初始化,和数组十分类似。
例:

struct B
{
    int a;

    int arr[3];
}x = { 5, { 1, 2 } };
  • 结构体注意事项:

结构体变量不能加减乘除,但是可以相互赋值。
普通结构体变量和结构体指针变量作为函数传参的问题。

2.关于结构体的内存分配问题

对于这个问题,我在前期有一篇博客已经讲述过了。大家可以去看一下。

C语言之内存字节对齐

在这里,为了让结构体方便访问,采取了一种牺牲空间换取时间的方式,在这里首先我们要知道,32位一般是采取4字节进行操作读取的。所以:

struct arr
{
    int a;
    char c;
    int b;
    char d;
};

对这个结构体,
如果我们这样存储的话:
C和指针—结构体和联合_第1张图片
所以,内存为了方便偏移读取:采用下面这种方式进行存储!
C和指针—结构体和联合_第2张图片

在这里我们要知道linux和VS下的对齐数是不同的,linux下是4,VS下是8。

对齐遵循的规则:
1.第一个成员永远对齐0偏移处。
2.每个变量的对齐数是它的大小与默认对齐数的较小值。
3.结构体的大小时最大对齐数的整数倍。
当出现结构体嵌套式,看所有对齐数中对打对齐数的整数倍。

另外,在这里,我们可以使用宏offsetof确定结构体中某个成员的实际位置。

offsetof(type,member

它表示你的member存储的位置距离开始存储的位置多少个字节。

3.关于结构体作为参数的问题

在这其实我感觉和数组作为参数的时候非常像,当我们把数组作为参数的时候,我们传递的是地址,因为如果我们传递值的话,会重新开辟很大一块空间,所以为了效率更高,我们直接把地址传递进去,用指针来接收地址。

说到这,我想你也就大概有些了解了,当一个函数你把一个结构体传进去,如果你采用传值的方式,那么当然要开辟一个相同的结构体作为形式参量。这样效率很低,所以我们也采用传地址的方式,然后用一个结构体类型的指针进行接收地址就好了。

void add(struct A x)
{
    ;
}
void add(struct A *P)
{
    ;
}

如果你学习过数据结构,关于链表顺序表好多都是采用传地址的方式进行操作的。

4.位段

关于位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。

首先我们要知道,位段成员必须声明为int,signed int或者unsign int类型。然后,在成员名后面是一个冒号和一个整数,这个整数也就是说这个位段所占用的位的数目。
注意:当声明位段为int类型时,编译器会决定它是无符号还是有符号。另外,位段和平台会有很大的关系,所以我们在一些可移植性的程序中最好不要使用位段。

例:

#include<stdio.h>
#include<stdlib.h>
struct A
{
    unsigned int a : 2;
    unsigned int b : 20;
    unsigned int c : 10;
};

int main()
{
    struct A x;
    printf("%d", sizeof(x));
    system("pause");
    return 0;
}

在这,我们就相当于把4个字节32位中,2位给了a,20位给了b,10位给了c。
对于位段的结构体,他的大小永远是unsigned int大小的整数倍,也就是说它的位段和如果小于等于一个unsigned int所占位大小时,他就是一个unsigned int的大小,如果超了,就是两个unsigned int的大小。

4.联合

其实一提到联合,我第一下想到的是对于机器大小端的验证:


#include<stdio.h>
#include<stdlib.h>
union node
{
    int num;
    char ch;
};
int main()
{
    union node p;

      //方法一
    p.num = 0x12345678;
    if (p.ch == 0x78)
    {
        printf("Little endian\n");
    }
    else
    {
        printf("Big endian\n");
    }
    //方法二
    int num = 0x12345678;
    char *q = &num;
    if (*q == 0x78)
    {
        printf("Little endian\n");
    }
    else
    {
        printf("Big endian\n");
    }
    return 0;
}

在这里的第一种方法所运用的就是union的特点。union进行存储是开辟最大的成员作为存储空间。所以,当你给num进行赋值,你访问ch也可以得到值。

你可能感兴趣的:(C和指针—结构体和联合)