C语言---自定义类型:结构体

结构体回顾

结构体

自定义的类型:结构体、联合体、枚举

结构是一些值的集合,这些值成为成员变量,结构的每个成员可以是不同类型的变量

//描述一本书:书名、作者、定价、书号

//结构体类型---类似于整型、浮点型
struct Book
{
    char book_name[20];//书名
    char author[20];//作者
    float prince;//价格
    char id[18];//书号

}; b4, b5, b6;//结构体变量’
//这里的结构体变量个下面的本质是一样的
//但是这里的是全局变量,而下面的是局部变量
int main()
{
    //结构体需要用大括号进行初始化,因为结构体里面不只一个值
    //初始化的时候我们是根据成员进行初始化的
    //下面的初始化是按照成员顺序进行初始化的
    struct Book b1 = {"鹏哥c语言","鹏哥",38.8f,"PG20240520"};
    //如果不按照成员顺序进行初始化可以这么写
    struct Book b2 = {.id="DG20240520",.book_name="蛋哥Linux",.author="蛋哥",.prince=55.5f};
    //两个操作符.  ->
    printf("%s %s %f %s\n", b1.book_name, b1.author, b1.prince, b1.id);
    printf("%s %s %f %s\n", b2.book_name, b2.author, b2.prince, b2.id);
    return 0;
}

//鹏哥c语言 鹏哥 38.799999 PG20240520
//蛋哥Linux 蛋哥 55.500000 DG20240520

//为什么这个数据会和我们设置的有差异呢?
//
//因为浮点数在内存中 有可能是不能进行精确保存的

/*
判断浮点数是否相等的方法
if(fabs(f-3.45)<0.00000001)
{
    printf("相等");
}
else
{
    printf("不相等");
}

*/

结构体的声明

在声明结构体的时候,可以不完全的声明---把名字省略掉

struct //直接把结构体名字去掉
{
    char c;
    int i;
    double d;
}s1;//直接在这里进行结构体变量的创建
//匿名结构类型创建变量
//创建变量只能用一次
int main()
{
    //struct S s2;这种创建的方式就不行了


    return 0;
}



struct //直接把结构体名字去掉
{
    char c;
    int i;
    double d;
}* ps;//匿名结构类型的指针类型
//匿名结构体类型+ *就是匿名结构体类型指针
//这里的ps就是指针变量
int main()
{
    ps = &s1;

    return 0;
}
//两个类型虽然成员是一样的,但是编译器会认为这两个匿名结构体的类型是不一样的
//所以结构体指针也是不一样的

//编译器会认为一种匿名结构体类型是一种类型,而另一种就是另一种类型
//反正是没有相同的匿名结构体类型的


//我们只有在仅仅只使用一次的情况下才会使用匿名结构体类型

//编译器会把两个匿名结构体类型当成两个不同类型的匿名结构体类型的

数据结构---数据结构其实是数据在内存中的组织结构

//struct Node
//{
//    int data;//存放数据
//    struct Node next;//访问下一个节点---类似递归
//    //但这中写法是错误的
//};
//结构体能自己找到同类型的下个节点,实现自引用,但是上面的方式是不对的


//那么在一个结构体中寻找下一个同类型的结构体的变量的方法
struct Node
{
    int data;//存放数据----数据域
    struct Node* next;//---指针域

    //next存放的是下个节点的地址
    //这样我们不仅能存储这个节点的数据,还能存储下个节点的地址
    //这样我们就能使结构体能自己找到同类型的下个节点,实现自引用


    //存放这个节点的数据,还能找到下个节点的数据,那么我们添加一个结构体指针就行了
};
int main()
{

    return 0;
}
/*这里的是一个结构体类型的
struct Node
{
    int data;//存放数据----数据域
    struct Node* next;//---指针域


};*/
//那么我们对这个结构体类型进行重命名Node
typedef struct Node
{
    int data;//存放数据----数据域
    struct Node* next;//---指针域
    //错误写法:Node* next;//---指针域
    //我们是先创建这个结构体类型,再进行重名名的,所以在顺序上面,这个代码就是错的

    //重命名是后来的,所以不能将里面的代码进行改变
}Node;


int main()
{

    return 0;
}

2.结构体内存对齐

我们在涉及计算结构体的大小的问题的时候,就会面临结构体内存对其的问题了

offsetof-----宏--头文件stddef.h

计算结构体成员相较于结构体变量起始位置的偏移量

offsetof(type,member)

offsetof计算的是每个成员在内存中相较于起始位置的偏移量

//struct S1
//{
//    char c1;
//    char c2;
//    int n;
//};
//struct S2
//{
//    //顺序不一样
//    char c1;    
//    int n;
//    char c2;
//};
//int main()
//{
//    printf("%zd\n", sizeof(struct S1));//占8个字节
//    printf("%zd\n", sizeof(struct S2));//占12个字节
//    return 0;
//}
//为什么输出的大小一个是8一个是12呢?
//那么这里就涉及到了结构体的对齐问题了

//结构体的成员在内存中是存在对齐现象的
//结构体内对齐

//offsetof-----宏

//计算结构体成员相较于结构体变量起始位置的偏移量

struct S1
{
    char c1;
    char c2;
    int n;
};
struct S2
{
    //顺序不一样
    char c1;
    int n;
    char c2;
};
int main()
{
    //printf("%zd\n", sizeof(struct S1));//占8个字节
    //printf("%zd\n", sizeof(struct S2));//占12个字节

    struct S1 s1 = { 0 };
   //计算偏移量
    /*printf("%zd\n", offsetof(struct S1, c1));//0
    printf("%zd\n", offsetof(struct S1, c2));//1
    printf("%zd\n", offsetof(struct S1, n));*///4



    printf("%zd\n", offsetof(struct S2, c1));//0
    printf("%zd\n", offsetof(struct S2, n));//4
    printf("%zd\n", offsetof(struct S2, c2));//8




    return 0;
}

//第一个字节给c1,第二个字节给c2,后面的两个字节空着了,
// 因为n是从第4个字节开始的,占了4个字节


//offsetof计算的是每个成员在内存中相较于起始位置的偏移量
//画图:
/*
第一行是c1占的字节
第二行是c2占的字节
第二三行是空的
n是从第四行到第七行的
总共算下来就是8个字节


*/


//printf("%zd\n", offsetof(struct S2, c1));//0
//printf("%zd\n", offsetof(struct S2, n));//4
//printf("%zd\n", offsetof(struct S2, c2));//8

//对s2进行计算得到的就是0  4  8

/*
第0行是c1
1 2 3行是空的

4 5 6 7放的是n,因为n占4个字节,并且李起始点有4个字节

第8行方的是c2

*/

//但是为什么之前计算的是S2占12个字节呢?

//不管是S1还是S2,他们的成员在内存中不是一个放完就放另一个的
//其实是存在对齐现象的

结构体对其的规则:

1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处

2.其他的成员变量要对齐到某个数字(对齐数)的整数倍的地址处

对齐数=编译器默认的一个对齐数与该成员变量大小的较小值

--vs中,默认对其的值是8

--Linux中qcc没有默认对齐数,对齐数就是成员自身的大小



3.结构体总大小为最大对齐数。(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)整数倍

4.如果嵌套了结构体的情况。嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

//那么结构体是如何对齐的呢?
//结构体成员在内存中到底是怎么存放对齐的?
//那么我们就了解一下结构体对其的规则



//struct S1
//{
//    char c1;
//    char c2;
//    int n;
//};
//
//int main()
//{
//    struct S1 s1;
//
//    return 0;
//}
//1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处

/*
2.对齐数=编译器默认的一个对齐数与该成员变量大小的较小值

--vs中,默认对其的值是8

*/
//在这个代码中,c1就是第一个成员,占一个字节,那么c1就占了内存中第0行的空间位置
//c2是1个字节,小于vs规定的对齐数8,所以c2要对齐在1的倍数的位置
//那么刚好1是1的倍数,那么c2就占了第1行的空间位置

//因为除了第一个成员,剩下的都是其他成员

//因为n的大小是4个字节,vs默认对齐数是8,4<8,那么c2的对齐数就是4

//所以n此时此刻要对齐在4的倍数上面,就是偏移量是4的倍数就行了
//所以我们是从第4行开始占的,占4个字节

//中间的2 3行是被空着的,因为这两个字节我们用不上了


/*
现在我们将所有成员都放进内存去了,占了8个字节

但是结构体的大小还不能因此判断
我们还要根据第三条进行判断


3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,
所有对齐数中最大的)的整数倍


这三个成员的默对齐数是最大对齐数是4,
那么在这里结构体的大小就是4的倍数,因为这里刚好8是4的倍数,所以我们直接进行判断结构体的大小是8个字节

*/




//对s2进行讲解
struct S2
{

    char c1;
    int n;
    char c2;
};

int main()
{
    struct S2 s2;

    return 0;
}

/*
根据规则
结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
因为c1占一个字节,那么c1就放在第一行的位置

对于n,n的大小是4个字节,vs上默认对齐数是8,因为4<8
那么取这两个数的较小数就是4作为n的对齐数,所以n要对齐4的倍数
那么我们就只能从第四行开始,因为是4个字节大小,所以从第4行到第7行都是n的范围

在这里我们又发现中间的1 2 3行是空的,说明我们又浪费三个字节


对于c2,因为c2的字节大小是1,vs默认对齐数是8,1<8,所以c2的对齐数是1
所以c2找到1的倍数就行了

刚好第8行是1的倍数,那么c2就将第8行占了

到目前为止S2占了9个字节了
但是真的是9个字节吗?

我们还得根据后面的规则进行判断
在S2中,最大的对齐数是4,那么结构体的总大小是4的倍数
但是9 10 11行都不是4的倍数,所以我们只能取第12行,
所以我们中间又浪费了3个字节的大小
所以最终S2结构体的大小是12

*/
struct S3
{
    double d;//0-7
    char c;//8
    int i;//10-12
    //最大对齐数是8,那么最后的位置固定在8的倍数上面,就是16
};
int main()
{
    printf("%d\n", sizeof(struct S3));//16
    return 0;
}

//因为double类型是8个字节,根据结构体对其的规则,构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
//那么0-就是d占了

/*
对于c来说,c大小1个字节,vs默认对齐数是8,因为1<8,所以c的对齐数是1
那么只要是1的倍数就行了
所以c就将第8行占了


对于i,i是4个字节,vs默认对齐数是8,4<8所以i的对齐数是4的倍数
所以 9 10 11这三块字节浪费了,直接从12开始,因为是4个字节大小,所以i的空间就是12-15行

因为这三个数据的最大对齐数是8,所以最后的结构体的大小要取8的倍数,,那么我们只能取16了

所以最后这个结构体大小就是16

*/
//练习4-结构体嵌套问题
struct S3
    {
        double d;
        char c;
        int i;

    };


struct S4
{
 char c1;
 struct S3 s3;
 double d;
};
int main()
{
    struct S4 s4;
    printf("%d\n", sizeof(struct S4));//32
    return 0;
}

/*
这种就是结构体嵌套了一种其他类型的结构体变量

我们在之前就将S3的大小算出来了,是16个字节的大小
S4成员c1因为是第一个成员,肯定是放在偏移量为0的地址处
因为c1类型是char类型,一个字节大小,所以第0行被c1占了

c1放完我们放s3
因为我们计算出的s3是16个字节,但是这个16个字节从哪里开始放呢?


根据规则4
如果嵌套了结构体的情况。嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,
结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

所以s3要对齐到字节的结构体内最大对齐数8的整数倍处

所以s3对齐到8的倍数就行了,因为第一个8的倍数是8
所以s3是从第8行开始对齐的,因为大小是16个字节

所以s3占了8-23行

所以中间的1-7行的7个字节被浪费了 

对于d来说,因为类型是double,大小是8个字节,vs默认对齐数是8,
那么d的对齐数是8,所以d要对齐8的倍数位
因为24是8的对齐数,所以d从24行开始对齐,d的大小是8个字节
所以24-31行是d占了

现在我们已经占了32个字节了,从0-31行
那么32是不是我们最终的大小呢?



在规则4中我们还说到
结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

因为这哥结构体所有成员中对齐数里面最大对齐数是8
并且我们结构的大小最终还是要取决最大对齐数的倍数,
因为最大对齐数的倍数是8
所以最终这个结构体的大小我们取32

*/

那么为什么会存在内存对齐的问题呢

  1. 平台原因 (移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定

类型的数据,否则抛出硬件异常。

  1. 性能原因:

数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。

总体来说:结构体的内存对⻬是拿空间来换取时间的做法。

内存对齐可以提升读取的效率

所以内存对齐还是很有必要的

那么在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到?

让占用空间小的成员尽量集中在一起

//那我们只能眼睁睁的看着字节被浪费吗?
/*
我们是否有办法使内存少浪费一些

那么在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到?

让占用空间小的成员尽量集中在一起


*/
/*这个时候我们就可以拿出之前的S1和S2了
struct S1
 {
    char c1;
    int i;
    char c2;
 };

 struct S2
 {
    char c1;//对齐数是1
    char c2;//对齐数是1
    int i;
 };
 这两个类型成员是一样的,只是内部成员顺序不同
 S1占12个字节
 S2占8个字节

 不难发现,我们将占用空间较小的成员集中放在一起,
 原本要浪费的空间就被利用起来了


*/

修改默认对齐数

pragma这个预处理指令,可以改变编译器的默认对齐数

//vs上默认对齐数是8

#pragma pack(1)//将默认对齐数设置为1
struct S
{
    char c1;
    int n;
    char c2;

};
#pragma pack()//恢复默认对齐数,恢复默认对齐数为8
//如果想恢复默认对齐数,我们pack()括号内可以直接不写
int main()
{
    //printf("%zd\n", sizeof(struct S));//12个字节---默认对齐数为8的时候
    printf("%zd\n", sizeof(struct S));//6个字节-=--默认对齐数是1的时候
    return 0;
}

3.结构体传参

//结构体传参
struct S//我们必须将这个结构体类型的创建放在前面,不然会报错的
{
    int arr[1000];
    int n;
    char ch;
};

void print1(struct S tmp)//传过来的是一个结构体变量,那么我们就创建一个结构体变量进行接收
{
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", tmp.arr[i]);
    }
    printf("\n");
    printf("n=%d\n", tmp.n);
    printf("ch=%c\n", tmp.ch);

}

void print2(const struct S* ps)//传过来的是结构体变量的地址,那么我们就创建一个结构体指针进行接收
{//那么ps就指向了s

    for (int i = 0; i < 10; i++)
    {
        printf("%d ", ps->arr[i]);
    }
    printf("\n");
    printf("n=%d\n", ps->n);
    printf("ch=%c\n",ps->ch );

}
int main()
{
    struct S s = { {1,2,3,4,5,6,7,8,9,10},10,'w' };//创建结构体变量s并进行初始化
    //利用函数将结构体内数据打印出来
    print1(s);//直接将结构体变量s传过去-----传值调用
    print2(&s);//直接将结构体变量s的地址传过去
    return 0;
}


//分析:那种更好一些
/*
在print1中,我们传过去的是结构体变量,
相当与传值调用
在调用的时候,我们还要创建一个结构体变量tmp用来接收传过来的数据
在这个调用的时候,我们还要额外的空间进行结构体变量的创建

假如传过来的变量占用空间很大,我们又要创建一个一模一样的空间进行存储

我认为这么很浪费空间,还很消耗时间

*/

/*
但是我们利用print2传的仅仅只是地址,地址的大小是4个字节或者8个字节

我们在创建形参的时候仅仅只需要创建一个指针ps进行接收,
指针ps就能找到这个结构体变量内的数据
这样也不需要创建结构体指针变量进行数据的拷贝,我们直接利用地址进行数据的调用和访问
*/

//传值能做到的,传地址一定能做到
//传地址能做到的,传值不一定能做到

//所以我们在结构体传参的时候,要传结构体的地址

//如果不想因为地址传过去的原因,地址指向的数据被改变了,我们直接加上const

在我们进行结构体传参的时候,我们传地址就行了

4.结构体实现位段

结构体讲完就得讲讲结构体实现位段的能力

位段的声明和结构是类似的,有两个不同:


1.位段的成员必须是int 、unsigned int 或signed int,在C99中位段成员的类型也可以选择其他类型

2.位段的成员名后边有一个冒号和一个数字


位段中的位指的是二进制中的位

//二进制中一位就是一个比特位
//一个字节是8个比特位
struct S
{
    /*int _a:2;*///_a占2个比特位
    /*
    假设我们在设定a的值的时候,我们只想要a是0 1 2 3 这4个数字
    那么二进制位表达出来就是
    0---00
    1---01
    2---10
    3---11

    这四个数刚好满足两个比特位的要求
    如果我们给a32个比特位的话就有点浪费空间了
    */




    int _a : 2;
    int _b:5;//_b占5个比特位
    int _c:10;//_c占10个比特位
    int _d : 30;//_d占30个比特位
    //int _e : 50;//这种写法是错的,对于一个整型来说,最大也就32个比特位,没有50
};

int main()
{
    printf("%zd\n", sizeof(struct S));//8
    /*
    如果不进行位段的调整,将是16个字节

    */
    return 0;
}
//这种设计相当于限制了大小,可以减小空间,避免空间浪费
//8个比特位等于1个字节

通过位段的使用,可以减少空间的占有,减少空间浪费

限制数据的长度、大小

节省空间

位段的内存分配

那么位段时如何改变内存的分配的呢?

  1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型

  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。

  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。

//struct S
//{
//    char a : 3;
//    char b : 4;
//    char c : 5;
//    char d : 4;
//};
//int main()
//{
//    struct S s;
//
//    return 0;
//}
/*
因为这里的数据是char类型的,1个字节,8个比特位
那么我们先开辟8个比特位,我们先用,不够的话再开辟

1.给定了空间后,在空间内部是从左向右使用还是从右向左使用呢?这个是不确定的
c语言并没与规定这个方向


那么我们假设从右到左
 1  2  3  4  5  6  7  8---这里表示的是比特位的位置
    b  b  b  b  a  a  a


    放完a和b还只有一个比特位了,不够的话我们再开辟一个字节


    2.当剩下的空间不足以存放喜爱一个成员的时候,空间是浪费还是使用,这个是不确定的
    那么我们就假设:是浪费
    //那么1号位就浪费了
1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16
   b  b  b  b  a  a  a              c   c   c   c   c


   放完c之后,就放d,d要4个比特位,但是现在只剩下3个比特位了
   那我们就再次开辟一个字节

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24 
   b  b  b  b  a  a  a              c   c   c   c   c              d   d    d   d   d

   此时s b c d都已经放好了,我们用了3个字节
   假如我们不浪费的话只要2个字节就够了
   那么我们来测试一下
*/
//struct S
//{
//    char a : 3;
//    char b : 4;
//    char c : 5;
//    char d : 4;
//};
//int main()
//{
//    struct S s;
//    printf("%zd\n", sizeof(struct S));//3
//    return 0;
//}


/*
这个结果和我们判断的是一样的,占了3个字节大小的空间

可事实真的是这样的么?
那么我们就再举个例子进行判断
*/
struct S
{
    char a : 3;
    char b : 4;
    char c : 5;
    char d : 4;
};
int main()
{
    struct S s = {0};//将每个比特位都设置为0
    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;

    printf("%zd\n", sizeof(struct S));
    return 0;
}

/*
//初始化的情况如下
1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24-----比特位
0  0  0  0  0  0  0  0  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0

0  b  b  b  b  a  a  a  0   0   0   c   c   c   c   c   0   0   0   0   d   d   d   d
*/
/*
s.a = 10;
往a里面放10,,10的二进制序列就是1010,可是a只有3个比特位,这里的10是4个比特位,3个比特位放不下1010
那么我们只能从地位到高位进行存放,存的是010

s.b = 12;
往b里面放12,12的二进制位是1100,b是4个比特位,那么刚好放得下1100

s.c = 3;
往c里面放3,c占5个比特位,3的二进制是11,那么我们就将00011放进去
前三个比特位还是0

s.d = 4;
往d里面放4,4的二进制是100,d占4个比特位,那么我们就将0100放进去,第一个

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24-----比特位
0  0  0  0  0  0  0  0  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0

0  b  b  b  b  a  a  a  0   0   0   c   c   c   c   c   0   0   0   0   d   d   d   d

0  1  1  0  0  0  1  0  0   0   0   0   0   0   1   1   0   0   0   0   0   1   0   0
放置结果如上:

因为4个二进制位写一个16进制,那么我们将16进制表示出来

6     2     0     3       0       4

我们进行调试,和我们分析的是一模一样的
*/
/*总结:
在当前的vs环境下,开辟空间是从右向左使用,如果剩下的空间不够,那我们就浪费这些空间,再开辟一个字节
*/




/*
我们再将一开始的例子拿过来进行判断下
struct A
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

因为类型是int
那么我们开辟4个字节,23个比特位,但是a只要2个比特位,剩下30个比特位,
b用5个,还有25个比特位
c用10个,还有15个比特位

d说要用30个比特位,剩下的15个不够了
那么我们再开辟4个字节,就是32个比特位
那么我们给d用30个比特位,剩下2个

算下来我们总共开辟了8个字节,浪费了17个比特位


这就是我们之前为什么算出来是8个比特位了


*/

1.给定了空间后,在空间内部是从右向左使用

2.当剩下的空间不足以存放喜爱一个成员的时候,空间是浪费的

上面两种是不确定的,需要我们实时进行探究

位段的跨平台问题

  1. int 位段被当成有符号数还是⽆符号数是不确定的。

  2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会

出问题。

  1. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。

  2. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃

剩余的位还是利⽤,这是不确定的。

总结:

跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

位段的注意事项

位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位

置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。

所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊

放在⼀个变量中,然后赋值给位段的成员。

struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};
int main()
{
    struct A sa = { 0 };
    //scanf("%d", &sa._b);//这是错误的

    //正确的⽰范
    int b = 0;
    scanf("%d", &b);
    sa._b = b;//直接进行赋值
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/722770.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

照度计仪器校准检测需要注意哪些因素?通常选择什么校准机构?

照度计是计量中光学领域常见的一类计量器具&#xff0c;一般是用于测量光照影响的微量变化&#xff0c;在实验室和机构中&#xff0c;都有广泛运用。常规的照度计在仪器校准检测中&#xff0c;误差主要因素是外界光线干扰&#xff0c;以及温湿度变化和稳压直流电源的电压变化差…

FPGA早鸟课程第二弹 | Vivado 设计静态时序分析和实际约束

在FPGA设计领域&#xff0c;时序约束和静态时序分析是提升系统性能和稳定性的关键。社区推出的「Vivado 设计静态时序分析和实际约束」课程&#xff0c;旨在帮助工程师们掌握先进的设计技术&#xff0c;优化设计流程&#xff0c;提高开发效率。 课程介绍 关于课程 权威认证&…

MyBatis系列四: 动态SQL

动态SQL语句-更复杂的查询业务需求 官方文档基本介绍案例演示if标签应用实例where标签应用实例choose/when/otherwise应用实例foreach标签应用实例trim标签应用实例[使用较少]set标签应用实例[重点]课后练习 上一讲, 我们学习的是 MyBatis系列三: 原生的API与配置文件详解 现在…

据APO Research(阿谱尔)统计,2023年全球乳酸企业产能约119.3万吨

乳酸又称 2-羟基丙酸&#xff0c;一种天然有机酸&#xff0c;分子式是 C3H6O3。是自然界中最为广泛存在的羟基酸&#xff0c;于 1780 年被瑞典科学家 Scheele 首次发现。乳酸是自然界最小的手性分子&#xff0c;以两种立体异构体的形式存在于自然界中&#xff0c;即左旋型 L-乳…

定制化物联网设备:开启智能生活新篇章

随着科技的进步&#xff0c;物联网&#xff08;IoT&#xff09;已成为我们日常生活和工作中不可或缺的一部分。从智能家居到工业自动化&#xff0c;物联网设备以其独特的功能和特性&#xff0c;极大地提高了我们的生活质量和工作效率。然而&#xff0c;在众多的物联网设备中&am…

思科配置路由器,四台主机互相ping通

一、如图配置 PC4和PC5用来配置路由器&#xff0c;各ip、接口如图所示。 二、配置各主机ip、子网掩码SNM、默认网关DGW (一)、PC0 (二)、PC1 (三)、PC2 (四)、PC3 三、 配置路由器Router0 (期间报错是打错了字母) Router>en Router#configure terminal Enter configurat…

使用 Vue CLI 脚手架生成 Vue 项目

最近我参与了一个前端Vue2的项目。尽管之前也有过参与Vue2项目的经验&#xff0c;但对一些前端Web技术并不十分熟悉。这次在项目中遇到了很多问题&#xff0c;所以我决定借此机会深入学习Vue相关的技术栈。然而&#xff0c;直接开始深入钻研这些技术可能会显得枯燥&#xff0c;…

[图解]建模相关的基础知识-12

1 00:00:00,650 --> 00:00:06,200 我们看&#xff0c;下面这个&#xff0c;你看f里面定义域是编号 2 00:00:06,410 --> 00:00:09,040 值域是工号&#xff0c;各只有一个元素 3 00:00:11,850 --> 00:00:14,340 所以这些就没有了 4 00:00:14,610 --> 00:00:19,640…

vue+echarts实现tooltip轮播

效果图如下&#xff1a; 实现步骤如下&#xff1a; 定义一个定时器 timer:null, len: 0,页面一加载就清空定时器&#xff0c;此操作是为了防止重复加载时会设置多个定时器在setOption后设置定时器 this.myChart.clear() this.myChart.setOption(option); this.autoShowTool…

vue.js有哪几种甘特图库?Vue.js的5大甘特图库分享!

vue.js有哪几种甘特图库?Vue.js的5大甘特图库分享&#xff01; 如今&#xff0c;软件市场为任何复杂程度的项目提供了各种现成的计划和调度工具&#xff0c;但这些解决方案可能包含过多的功能或缺乏一些必要的功能。这就是为什么许多公司更愿意投资开发基于网络的定制解决方案…

【C++】拷贝构造函数、拷贝赋值函数与析构函数

C中的拷贝构造函数、拷贝赋值函数与析构函数详解 一、拷贝构造函数&#xff08;Copy Constructor&#xff09;二、拷贝赋值函数&#xff08;Copy Assignment Operator&#xff09;三、析构函数&#xff08;Destructor&#xff09;四、总结 在C中&#xff0c;拷贝构造函数、拷贝…

Docker私有化仓库Harbor安装流程

1.搭建Docker私有仓库主要有以下几种方式 使用Docker官方提供的Registry镜像&#xff1a;Docker官方提供了一个用于构建私有镜像仓库的Registry镜像&#xff0c;只需将镜像下载并运行容器&#xff0c;然后暴露5000端口即可使用。可以通过修改Docker的配置文件daemon.json&#…

具备人工智能标记的书签应用Hoarder

什么是 Hoarder &#xff1f; Hoarder 是一款可自托管的书签应用程序&#xff08;链接、笔记和图像&#xff09;&#xff0c;具有基于人工智能的自动标记和全文搜索功能。适合数据囤积者使用。 软件特点&#xff1a; &#x1f517; 为链接添加书签、做简单的笔记并存储图像。⬇…

简单介绍vim

文章目录 前言一、Vim的特点二、安装Vim三、设置Vim配置文件的位置&#xff1a;编辑配置文件&#xff1a;添加配置选项&#xff1a;保存并退出编辑器&#xff1a;快速配置验证设置&#xff1a; 总结 前言 Vim是一款强大的文本编辑器&#xff0c;被广泛用于各种编程和文本编辑任…

大咖专栏 | AI 时代下,我们可以拥有怎样的数据库?

Hi&#xff0c;各位朋友们&#xff0c;我是 KaiwuDB 高级架构师赵衎衎。 KaiwuDB 始于万物互联时代下千万条数据洪流中&#xff0c;我们持续打磨构造了更加灵活兼容的分布式多模架构&#xff0c;实现了海量异构数据高性能、低成本的集中管理… …这些底层特性都在为后续提供更…

年轻人膳食营养补充剂小程序,营养看得见

随着经济与科技的不断发展&#xff0c;人们对于大众健康、全民养生的意识也在不断增强。越来越多的年轻人加入进来&#xff0c;逐渐成为保健品行业的主力军&#xff0c;传统的保健品行业抓住这一波时代潮流&#xff0c;也采取了新的变革。 一&#xff0e; 膳食营养补充剂的定义…

nodejs爬取小红书图片

昨天的文章已经描述了可以抓取评论区内容&#xff0c; 抓取图片内容和抓取评论区的内容基本一致 我们可以看到接口信息中含有图片链接&#xff0c;我们要做的就是爬取图片链接然后下载 这边要用到的模块为const downloadrequire(download) 将爬到的图片链接存放到images数组…

OpenAI新模型发布,免费开放GPT-4o!但只开放一点点...

GPT-4o 中的“o”代表“omni”——指的是 GPT-4o 的多模态。 该模型将向免费客户开放&#xff0c;这意味着任何人都可以通过 ChatGPT 访问 OpenAI 最先进的技术。 GPT-4o 是 OpenAI 昨天晚上发布的新旗舰模型&#xff0c;可以实时推理音频、视觉和文本。 据官方介绍&#xff0…

LCL滤波器并网逆变器双闭环控制系统仿真

并网逆变器通常采用L滤波器&#xff0c;虽然结构和控制简单&#xff0c;但是随着功率级别的增加&#xff0c;体积重量增大等问题也日益突出。为了解决这个问题&#xff0c;人们开始使用LCL滤波器&#xff0c;这种滤波器在功率较大的场合表现出色。 无源滤波器&#xff0c;又称…

人工智能在音乐创作中的崛起与未来展望

目录 前言1. 国内外音乐大模型现状1.1 国内音乐大模型概览1.2 国外音乐大模型概览1.3 市场份额与竞争格局 2. 音乐大模型的商业模式2.1 订阅制服务2.2 定制化服务2.3 授权与版权合作 3. 人工智能与音乐人的合作模式3.1 AI辅助创作3.2 共同创作平台3.3 AI乐器与音乐人表演 4. AI…