C语言要点总结-复合结构和位域

目录

3.6复合结构(自定义类型)—结构体

3.6.1结构体的定义

①无tag

②variable-list分离 

③使用关键字typedef

3.6.2结构体变量的初始化

3.6.3访问结构成员

3.6.4结构作为函数参数

3.6.5指向结构的指针

3.6.6结构体的对齐方式

3.6.7结构体赋值

3.6.8结构体嵌套一级指针

3.7位域

3.7.1位域的定义和位域变量的说明

3.7.2位域的使用


 

3.6复合结构(自定义类型)—结构体

参考:https://www.runoob.com/cprogramming/c-structures.html

3.6.1结构体的定义

结构体最基本的形式:

struct students
{
  //成员列表
  char name[11];
  int number;
  char tel[16];
  float scores[3];
  char sex;

};

初始化:

struct students
{
  //成员列表
  char name[11];
  int number;
  char tel[16];
  float scores[3];
  char sex;

};
...........
int main()
{
   .........
   struct stduents stu = {"SAW",123,"数学",85,"female"};
   .........
}

结构体声明:

 

struct tag { 
    member-list
    member-list 
    member-list  
    ...
} variable-list ;

 其中:

tag 是结构体标签。

member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。

variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。

在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。以下为实例:

①无tag

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;

variable-list分离 

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
    int a;
    char b;
    double c;
};

//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;

③使用关键字typedef

//也可以用typedef创建新类型
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表等。

//此结构体的声明包含了其他的结构体
struct COMPLEX
{
    char string[100];
    struct SIMPLE a;
};
 
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
    char string[100];
    struct NODE *next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:

struct B;    //对结构体B进行不完整声明
 
//结构体A中包含指向结构体B的指针
struct A
{
    struct B *partner;
    //other members;
};
 
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
    struct A *partner;
    //other members;
};

3.6.2结构体变量的初始化

和其它类型变量一样,对结构体变量可以在定义时指定初始值。

#include 
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
 
int main()
{
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}

 执行结果:

C语言要点总结-复合结构和位域_第1张图片

3.6.3访问结构成员

为了访问结构的成员,我们使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。您可以使用 struct 关键字来定义结构类型的变量。下面的实例演示了结构的用法:

#include 
#include 
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main( )
{
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
   ............
 
   /* 输出 Book1 信息 */
   printf( "Book 1 title : %s\n", Book1.title);
   printf( "Book 1 author : %s\n", Book1.author);
   printf( "Book 1 subject : %s\n", Book1.subject);
   printf( "Book 1 book_id : %d\n", Book1.book_id);
   .............
}

C语言要点总结-复合结构和位域_第2张图片

3.6.4结构作为函数参数

您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。您可以使用上面实例中的方式来访问结构变量:

#include 
#include 
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books book );
int main( )
{
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 输出 Book1 信息 */
   printBook( Book1 );
 
   /* 输出 Book2 信息 */
   printBook( Book2 );
 
   return 0;
}
void printBook( struct Books book )
{
   printf( "Book title : %s\n", book.title);
   printf( "Book author : %s\n", book.author);
   printf( "Book subject : %s\n", book.subject);
   printf( "Book book_id : %d\n", book.book_id);
}

C语言要点总结-复合结构和位域_第3张图片

3.6.5指向结构的指针

struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;

3.6.6结构体的对齐方式

在使用sizeof计算结构体所占用空间大小时,大多数情况下需要考虑对齐模式的问题。....

#pragma pack()
typedef struct TestForCPP_1
{
    int T_a;
    double T_d;

}T;

就结构体定义而言,int占用4个字节,double占用8个字节,定义一个结构体,应占用12个字节。 

    T student;
    qDebug()<<"student占用空间:"<

 

但是实际的输出情况如上:如果要解决这个问题,需要设置结构体的对齐方式。

 设置对齐方式后:

#pragma pack(1)
typedef struct TestForCPP_1
{
    int T_a;
    double T_d;

}T;

  

3.6.7结构体赋值

 C++中

typedef struct TestForstrcut
{
    int size;
    char * name;

}Tstruct;
    Tstruct test1,test2;

    test1.size = 20;
    test1.name = (char *)malloc(sizeof(char)*64);
    //一种分配长度为num_bytes字节的内存块的函数,可以向系统申请分配指定size个字节的内存空间
    memset(test1.name,0,sizeof(char)*64);
    //作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
    strcpy(test1.name,"SAW");

    test2.size = 25;
    test2.name = (char *)malloc(sizeof(char)*128);//分配空间
    //一种分配长度为num_bytes字节的内存块的函数,可以向系统申请分配指定size个字节的内存空间
    memset(test2.name,0,sizeof(char)*128);//初始化
    //作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
    strcpy(test2.name,"SAWSAWSAW");

    //结构体赋值:
    qDebug()<<"test1的大小"<

输出: 

 C语言要点总结-复合结构和位域_第4张图片

在C++中进行结构体的赋值是安全的,但是在C中就不是这么简单了。 

3.6.8结构体嵌套一级指针

现在需要一个这样的结构体:

     C语言要点总结-复合结构和位域_第5张图片

指针指向的是变量,保存在指针内的是地址,下面操作,是主动申请一段内存,将内存地址给指针,之后将变量保存到这段内存中

#pragma pack(1)
typedef struct TestForCPP_1
{
    char * T_a;
    double T_d;

}T;
于构造函数中:
T ** test = allocataSpace();
Show(test);
Free(test);
........

T ** MainWindow::allocataSpace()
{
    T **temp = (T**)malloc(sizeof(T) * 3);
    for(int i = 0;i<3;++i)
    {
        temp[i] = (T*)malloc(sizeof(T));
        temp[i]->T_a = (char*)malloc(sizeof(char) * 64);
        sprintf(temp[i]->T_a,"Name_%d",i+1);
        temp[i]->T_d = 100+i;
    }
    return temp;
}
void MainWindow::Show(T ** temp)
{
    for(int i = 0;i<3;++i)
    {
        qDebug()<<"Number"<T_a<T_d;
    }
}
void MainWindow::Free(T ** temp)
{
    if(nullptr == temp)
        return;

    for(int p = 0;p<3;++p)
    {
        if(temp[p]->T_a == nullptr)
        {
            continue;
        }
        for(int i =0;i<3;++i)
        {
            free(temp[i]->T_a);
            temp[i]->T_a = nullptr;
        }
    }

    for(int p = 0;p<3;++p)
    {
        if(temp[p] == nullptr)
        {
            continue;
        }
        for(int i =0;i<3;++i)
        {
            free(temp[i]);
            temp[i] = nullptr;
        }

    }
    free(temp);
    temp =nullptr;

}

输出:

再次理解指针和结构体指针 

struct A
{
    char a1;//1
    int a2;//4

};
struct C
{
    int c1;//4
    double c2;//8

};
struct B
{
    char a;//1
    int b;//4
    struct C c;

};
    struct A a ={'a',11};
    printf("A.a2 = %d\n",*(int *)( (char *)&a + offsetof(A,a2) ));

    struct  B b = {'b',2,3,3.14};
    int off1 = offsetof(B,c);
    int off2 = offsetof(C,c2);
    qDebug()<c2);

其他:

结构体的嵌套 自身嵌套 相互嵌套:https://blog.csdn.net/weixin_42167759/article/details/80330953

柔性数组:

struct Node
{
   int size;
   char data[0];
};

看不懂char data[0];请去百度  柔性数组,它只能放在结构体末尾,

申明一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量, 数组名这个符号本身代 表了一个不可修改的地址常量 (注意:数组名永远都不会是指针! ),但对于这个数组的大小,我们可以进行动态分配 请仔细理解后半部分,对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量

 对于0长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:

注意:构造缓冲区就是方便管理内存缓冲区,减少内存碎片化,它的作用不是标志结构体结束,而是扩展

柔性数组是C99的扩展,简而言之就是一个在struct结构里的标识占位符(不占结构struct的空间

(牛客网@SunburstRun)

对于变长数组和变长结构体,这是在C99才加入标准的。

对于变长数组,举个例子就能解释了:

int main() {

    int n = 10;

    int arr[n];

}

对于变长结构体就比较复杂一点(也不算很复杂:))。很多人其实会有这种疑惑,就是为什么不用指针去代替变长结构体,比如:

structNode

{

   intsize;

   char*data;

};

就这个问题,我总结了一下用指针和用变长结构体的区别:

1.在位置方面:指针可以放在任何地方,但是变长结构体的变长部分一定要放在结构体的最后

2.在内存占用方面:指针会占一个指针的大小的内存空间,但是变长数组是不占内存的,它只是一个占位符

3.在内存布局方面:指针指向的内存和结构体的内存可以是不连续的,但是变长部分和结构体的内存必须是连续

4.在内存释放方面:使用指针,就要先释放指针所指的内存在释放整个结构体的内存,否则会照成内存泄露。

但是使用变长结构体直接释放整个结构体的空间就可以了

5.一个限制:指针可以用在C++的类中,但是变长结构体就不可以了。因为有些编译器会将一些额外的信息放在类的最后,

比如vptr或者虚基类的内容,使用了变长的类,就会把这部分的值改变,这种行为是未定义的,谁也不知道会发生什么。

(牛客网@waponx)

3.7位域

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。

所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

典型的实例:

  • 用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
  • 读取外部文件格式——可以读取非标准的文件格式。例如:9 位的整数。

3.7.1位域的定义和位域变量的说明

位域定义与结构定义相仿,其形式为:

struct 位域结构名 
{

 位域列表

};

其中位域列表的形式为:

类型说明符 位域名: 位域长度 
struct bs{
    int a:8;
    int b:2;
    int c:6;
}data;

说明 data 为 bs 变量,共占两个字节(2Byte = 16Bit)。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。

让我们再来看一个实例:

struct packed_struct {
  unsigned int f1:1;
  unsigned int f2:1;
  unsigned int f3:1;
  unsigned int f4:1;
  unsigned int type:4;
  unsigned int my_int:9;
} pack;

packed_struct 包含了 6 个成员:四个 1 位的标识符 f1..f4、一个 4 位的 type 和一个 9 位的 my_int。

对于位域的定义尚有以下几点说明:

  • 一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

struct bs{
    unsigned a:4;
    unsigned  :4;    /* 空域 */
    unsigned b:4;    /* 从下一单元开始存放 */
    unsigned c:4
}

在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位。

由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。

位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如

struct k{
    int a:1;
    int  :2;    /* 该 2 位不能使用 */
    int b:3;
    int c:2;
};

 

3.7.2位域的使用

位域的使用和结构成员的使用相同,其一般形式为:

位域变量名.位域名
位域变量名->位域名

位域允许用各种格式输出。

请看下面的实例:

main(){
    struct bs{
        unsigned a:1;
        unsigned b:3;
        unsigned c:4;
    } bit,*pbit;
    bit.a=1;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.b=7;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.c=15;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    printf("%d,%d,%d\n",bit.a,bit.b,bit.c);    /* 以整型量格式输出三个域的内容 */
    pbit=&bit;    /* 把位域变量 bit 的地址送给指针变量 pbit */
    pbit->a=0;    /* 用指针方式给位域 a 重新赋值,赋为 0 */
    pbit->b&=3;    /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
    pbit->c|=1;    /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
    printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);    /* 用指针方式输出了这三个域的值 */
}

 

 

你可能感兴趣的:(C/C++/数据结构,C)