C语言结构体、共用体与位运算
形式:struct 结构体名 {成员表列};其中成员表列形式为:类型名 成员名,结构体名第一个字母最好大写struct是关键字,不可省略,而且末尾必须加上分号接下来以学生的学号姓名成绩组成结构体为例int id;char n[20];}s1,s2;int main()
结构体
在各种业务上,现有的数据类型不足以解决问题,所以有时会将不同的数据类型组合成一个整体,称为结构体,而结构体类型需要由编写者自己声明,拿数组相比,可以理解为,数组是同类型元素的集合,而结构体是不同类元素的集合
结构体定义
形式:struct 结构体名 {成员表列}; 其中成员表列形式为:类型名 成员名,结构体名第一个字母最好大写
struct是关键字,不可省略,而且末尾必须加上分号。接下来以学生的学号姓名成绩组成结构体为例
struct Student
{
int id;
char n[20];
float sorce;
}s1,s2;
int main()
{
struct Student s = {1, zhangsan,93.5};
}
如此便定义了一个新的数据类型Student,有两种定义方式s1、s2(全局变量)和s(局部变量),而s进行了初始化,初始化时,花括号中内容的顺序必须和声明的顺序一致,如果只初始化一部分,那么其他部分都会变成0,而部分初始化时需要通过运算符具体列出,该用法只在GNU体系下,Windows下不行
,需要注意的是,成员与成员之间用逗号间隔
结构体之间是可以嵌套的,如下,在struct S中,s属于成员名,struct Date属于类型名,而对于嵌套结构体的初始化,无非就是在花括号中再添加花括号
struct Date
{
int year;
int month;
};
struct S
{
int i;
struct Date day;
}S1;
那么如何确定出具体的成员呢,这需要用到几个运算符
结构体运算符
(1)“.”结构体成员运算符,优先级一级,方向自左向右 例:s.id,表示在s这个结构体中id这个成员;S1.day.month,表示在S1结构体中的day结构体成员中的month这个成员
(2)“->”指向结构体成员运算符,优先级一级,方向自左向右,这个运算符需要用到指针,还是以学生结构体为例
#include <stdio.h>
struct Date
{
int year;
int month;
int day;
};
struct Student
{
int id;
char n[20];
float sorce;
struct Date birthday;
};
void printfstruct(struct Student *p)
{
printf("%d %s %f %d-%d-%d", p -> id, p -> n, p -> sorce, p -> birthday . year, p -> birthday . month, p -> birthday . day);
}
int main()
{
struct Student s = {1, "zhangsan", 96.5, {2000, 6, 21}};
struct Student *p = &s;
printfstruct(p);
}
对于函数printfstruct(),为了防止值传递,所以采用了指针传递的形式 ,所以对于“->”运算符,应该用于指向结构体的指针,所以“.”和“->”的区别在于一个左侧是变量一个左侧是指针
结构体数组
由于结构体属于一种数据类型,所以自然也会有数组,同样可以进行数组的操作
遍历

与数组不同的是,结构体(不是结构体数组)是可以被整体赋值的,如下

逆序

按照分数高低排序(更改if()可以更换排序依据,所以可用回调函数)

结构体内存
结构体所占内存的字节数并不是简单的成员类型相加,例如以下结构体所占内存空间为8个字节、12个字节、24个字节

由于结构体中变量的成员在内存中的顺序,与结构体声明成员时的顺序一致,即有序性,而且变量在内存存放时,会有这么一种现象,被称为内存对齐
内存对齐:变量存在内存空间中的地址,必须能够整除sizeof(变量类型),例:int t ,t的内存空间地址可能是0x10004000,而绝对不可能是0x10004001,0x10004005,也就是说地址数值一定能够整除以4,同理short型能整除以2。这种方式会提高CPU的读写速率
于是我们便可以知道上述结构体的储存方式,可以根据内存空间位置是否能够整除来存放
第一种
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| c | s | s | i | i | i | i |
1不能被2整除,所以空过去
第二种
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|
c |
i | i | i | i | s | s |
最大数据类型int是4个字节,所以10和11需要空出
第三种
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| c | d | d | d | d | d | d | d | d | d | i | i | i | i |
规则:(1)默认按照cpu位数对齐:最终大小必须是8的整数倍
(2)在结构体成员中找到最长成员,最终按该成员长度对齐(不与1矛盾,例如第二种)
(3)按照结构体声明的顺序,依次将成员保存到结构体内存中,保存的偏移量/sizeof(成员)== 0
(4)数组的话是根据数组元素的基类型,而并非一整个数组占了多少个字节
共用体(联合体)
将不同类型变量存到同一段内存空间中,所占字节数不同,但是从同一地址开始存放,该类结构被称为共用体。共用体中所有元素共用同一段内存
形式:union 共用体名 {成员表列} 变量表列; 其中分号必须加,变量表列可以单独拿出来重新定义,可以参考结构体
例:
#include <stdio.h>
union Demo
{
int i;
short s;
char c;
}a,b;
int main()
{
union Demo d;
d.i = 10;
d.s = 100;
d.c = 1;
printf("%d\n", d.i);
}
会输出1,因为三个变量共用一段内存空间,三者首字节地址完全相同,相当于后者将前者覆盖了。内存也是只是最大的类型所占的空间,所以Demo占用4个字节
通过这种性质可以来判断计算机是大端存储还是小端存储
#include <stdio.h>
union Demo
{
int i;
char c;
};
int main()
{
union Demo d;
d.i = 0x11223344;
d.c = 1;
printf("%x", d.i);
}
如果是小端存储,则是0x11223301,如果是大端存储,则是0x01223344
共用体可以作为函数参数传递,而且大部分也是通过指针传参
枚举类型
如果一个变量只有几种可能的值,便可定义为枚举类型。“枚举”就是把变量的值一一列出来,变量的值仅限于列出来的值
形式:enum 变量名{变量值,变量值.....};
例:

该类型与整型相兼容,默认情况下,常量在花括号中位置是几,对应的整型数字就是几,当然也可以进行如下赋值操作。一般来说,枚举类型和switch-case类型相运用

输出为1,如果w = Wes的话,输出则为9,它会根据上一个值的默认值加一
typedef
将已有数据类型的类型名更换
形式:typedef 数据类型 类型名
例:

INT就是int改了名,所以完全可以定义一个INT类型,但是int也可以照常使用。在没加typedef之前,类型名就是变量名,而去掉变量名就是该变量的数据类型,加上typedef之后,变量名改为类型名,所以a的类型是一个长度为40的整型数组,输出40
typedef可以用于结构体改名,如下,在这里数据类型和类型名并不冲突,因为该类型是struct Demo类型,却不是Demo类型,而且经过typedef后,在主函数定义该结构体时,便可直接Demo,而不用加上struct

typedef不止可以改一个名字,以下图为例,要判断p的类型,可以从没有typedef时后PDEMO是什么类型,是指针类型,但是p之前不用加*,是因为改名后,PDEMO就表示指针类型,而改名前需要*来表示PDEMQO为指针类型

typedef更改的名字可以再次进行更改,以下图为例,会输出80。从头来看,第一行将指向函数的指针改名为pfn,第二行将pfn的数组改为ARRAY,所以此时ARRAY表示的是这样的数据类型,即含有十个函数指针的数组,由于指针数据类型占8个字节,所以a作为数组占了80个字节

位运算

除了~之外,均为双目运算符,运算只能是整型或与整型兼容的数据类型,运算方式是将参数化为二进制,然后进行运算
&:指定位清零,如果两个对应的二进制位都为1,则结果为1,否则为0
|:指定为置一,如果两个对应的二进制位都为0,则结果为0,否则为1
^:指定位翻转,如果两个对应的二进制位相同,则结果为0,不同则为1
~:每个二进制位取反,1变为0,0变为1
<<:删去最左侧的n个数,在最右边补0(与1使用可以确定第几位为1的二进制数)
该运算符与其他运算符使用会有简便效果,例如取第二位和第五位为1的数,便可以写为(1 << 5) | (1 << 2)
>>:对于无符号数,删去右侧n个数,在左边补0,对于有符号数,若符号位为0,则同样,若为1,则根据计算机系统,左边补0为“逻辑右移”,补1为“算数右移”
例:
| 运算数 | 1111 0010 | 1110 0010 | 1101 1111 | 1101 0010 | 1100 1111 | 1101 1001 |
| 运算符 | & | | | ^ | ~ | << | >> |
| 运算数 | 1001 1011 | 1001 0111 | 1001 0001 | 2 | 2 | |
| 结果 | 1001 0010 | 1111 0111 | 0100 1110 | 0010 1101 | 0011 1100 | 0011 0110 |
运算符可以与赋值运算符=一起使用,营造出类似于自增自减的效果,如果长度不同,系统会按右侧对齐,正数和无符号数左侧添0,负数则添1
更多推荐



所有评论(0)