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 <stdio.h>
 
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);
}

 执行结果:

3.6.3访问结构成员

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

#include <stdio.h>
#include <string.h>
 
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);
   .............
}

3.6.4结构作为函数参数

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

#include <stdio.h>
#include <string.h>
 
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);
}

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占用空间:"<<sizeof(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的大小"<<sizeof(test1);
    qDebug()<<"test2的大小"<<sizeof(test2);//大小是一样的,sizeof只关注类型,

    test1 = test2;
    qDebug()<<"test1.name:"<<test1.name;
    qDebug()<<"test2.name:"<<test2.name;
    qDebug()<<"test1.name的地址:"<<&test1.name;
    qDebug()<<"test2.name的地址:"<<&test2.name;


    if(test1.name != nullptr)
    {
        free(test1.name);
        test1.name = nullptr;
    }

    if(test2.name != nullptr)
    {
        free(test2.name);
        test2.name = nullptr;

    }

输出: 

 

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

3.6.8结构体嵌套一级指针

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

     

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

#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"<<i<<":"<<temp[i]->T_a<<endl<<temp[i]->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()<<off1<<off2;
    printf("b.c.c2 = %f\n",((struct C *)( (char *)&b + off1))->c2);

其他:

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

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);    /* 用指针方式输出了这三个域的值 */
}

 

发布了85 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41605114/article/details/104497112