由基本数据类型组成的复合数据类型
即先自定义想要的类型,再用该类型去申请空间
struct student//声明一个名称为student的数据类型,并没有分配空间
{
char name[16];
int age;
float score;
}
结构体内各个成员需要“对齐”地分配内存空间
为什么要内存对齐?
假设我们同时声明两个变量:
char a;
short b;
如果a地址取为0x0000 0000,那么b的地址为0x0000 0002,而不是取为0x0000 0001
这就是内存对齐。
那么,为什么b不取00000 0001地址呢?
因为如果b取0x0000 0001,那么,那么CPU就需要先从0x0000中读取一个short,取它的高8位放入b的低8位,然后再从0x0002中读取下一个short,取它的低8位放入b的高8位中,这样的话,为了获得b的值,CPU需要进行了两次读操作。
但是如果b的地址为0x0002,那么CPU只需一次读操作就可以获得b的值了。
所以编译器为了优化代码,往往会根据变量的大小,将其指定到合适的位置,即称为内存对齐(对变量b做内存对齐,a、b之间的内存被浪费,a并未多占内存)。
8bit字节对齐 | 任意一个8bit地址 |
16bit半字对齐: | 地址的最低位为0 |
32bit字对齐 | 地址的最低两位为0 |
64ibit多字对齐 |
各种数据类型 | 对齐方式 |
---|---|
char | 1字节对齐 |
short | 2字节对齐 |
int | 4字节对齐 |
long | 4字节对齐 |
float | 4字节对齐 |
double | 8字节对齐 |
示例
#include
void shouw();
//结构体一般分配在堆里
/*typedef即把struct新定义的数据类型加入到类型列表内
此时直接输bookinfo就相当于int,无需重复输入 struct */
typedef struct bookinfo
{
char name[16];
int num;
int id;
double cost;
}book,book1, *pbookinfo;//前两个为该结构体类型的两个定义名,后面为结构体指针
void show()
{
book sys ={
"kitty", 18, 50, 18.99};
pbookinfo *p = &sys;//结构体指针
printf("name: %s\nname_addr: %p\n\n", sys.name, &sys.name);
printf("num: %d\nnum_addr: %p\n\n", sys.num, &sys.num);
printf("id: %d\id_addr: %p\n\n", sys.id, &sys.id);
printf("cost:%.2lf\ncost_addr: %p\n\n", sys.cost, &sys.cost);
printf("book_sys_addr: %p\n", p);
printf("size_book = %lu\n", sizeof(book));
}
int main()
{
show();
return 0;
}
struct student_ info
{
char name[20];
char number[20];
char sex;
int age;
float height;
float weight;
};
struct student_ info list;//定义了一个结构体变量list
struct student_ info //复合数据类型
{
char name[20];
char number[20];
char sex;
int age;
float height;
float weight;
}head,ist; //定义了两个结构体变量head,list
typedef struct student
{
char name[16]
int age;
float score;
}student;/*此处为typedef定义的类型名
struct student(上面) = student(下面) ————都与int同属数据类型定义*/
//typedef为student这个类型取一个student的别名用于标识
//并将student变成一个默认类型,即加入到传统类型列表
int main()
{
/*stryct student stu;未加typedef关键字时的写法
存在struct显得臃肿,但是即便有typedef也可用这种方法定义变量
由此可以看出上面的student只是一个程序员为了便于管理代码的符号
而下面的student则通过typrdef获得了与int相同的地位*/
student stu = {
"kitty", 18, 100};//这里用的是下面的student
}
方式1:
struct student _info jason = {
"jason","007";M',20,180.5,70.0};
注意:
1)个数可以少于成员数量,但总是从第一个成员开始初始化
2)列表中的参数与成员类型号一定对上
3)这种初始化方式必须要跟定义变量同时进行
方式2:
struct student_ _info jason;
jason.name = "jason";
jason.number = "007",
jason.sex = :'M';
jason.age=20;
jason.height =180.5; .
jason.weight= 70.0;
表示结构体成员: 变量名.成员名
如果要表示张三的学号: zhangsan.number
方式3:
struct student_ info jason = {
.name = "jasoni",
.number = "007",
.sex = 'M',
.age = 20,
.height = 180.5,
.weight = 70.0};
这里要注意一点
当student结构体中存在类似
char name[16];
的内容
并且先定义后赋值时,格式为
student stu2;//定义stu2
strcpy(stu2.name, "putty");//采用strcpy赋值
//因为name在这里是一个地址而不是一个变量
C 语言中,「.」与「->」有什么区别?
scanf("%s' ,jason .name);//zhangsan.name变量zhangsan成员name的地址
Scanf(^%s" ,jason->name);
scanf("%s' ,jason .number);//输入学号
while(getchar() != "\n');//清空输入缓冲区
scanf("%c' ',&jason .sex);//输入性别.
scanf("%d" ,&jason .age);//&变量成员age的地址
scanf("%f" ,&jason .height);//输入身高
scanf("%of",&jason .weight);//输入体重
printf("jason.number = %s\n' ,jason.number);
printf("jason.sex = %c\n" ,jason.sex);
print("jason.age = %d\n' ,jason.age);
结构体定义中,结构体的成员又是另一个结构体的变量
对数据进行分类包装后,便于快速定位 / 操作与一次性读写嵌套结构体的全部信息
在第一个结构体当中,发现它的成员比较多,我们总会将其中具有相同某一种属性的数据进行包装成二级结构体,便于灵活高效的管理,使其框架更清晰(指针)
方式1
声明结构体
struct param
{
char name[20];
int flag;
}
struct time
{
int hour;
int minute;
int second;
struct param p;
}
struct dateTime
{
int year;
int month;
int day;
struct time t;
定义初始化
struct dateTime dt={
.year = 2019,
.month= 3,
.day=5,
.t={
.hour= 22,
.minute= 3,
.second = 50,
.p={
.name = "datetime",
.flag= 100,
},
},
};
方式2
struct date Time
{
int year,
int month;
int day;
struct time{
int hour,
int minute;
int second;
struct param {
char name[128];
int flag;
}p;
}t;
}dt={
2017,7,18,22,39,10};
或者写成另外一种更简洁的形式3
struct dateTime
{
int year;
int month;
int day;
struct {
//嵌套无名结构体
int hour;
int minute;
int second;
struct {
char name[128];
int flag;
}p;
}t;
}dt={
2017,7.18.,22,39,10}; //定义时须使用括号括起来
又或者4
先声明新的类型
struct dateTime
{
int year,
int month;
int day;
struct {
//嵌套无名结构体
char name[20];
int hour,
int minute;
int second;
}t;
};
再定义一个结构体变量:
struct dateTime dt =
{
.year = 2017,
.month= 7,
.day= 19,
.t={
.name = time",
.hour= 14,
.minute = 56,
.second = 0,
};
};
int main(int argc, char *argvQ)
{
printf("%d-%d-%d %d:%d:%d \n",dt.year, dt.month, dt.day, dt.t.hour,dt.t.minute, dt.t.second);
dt.t.name
return 0;
}
嵌套的结构体定义时就初始化容易占用空间
如果想使用时再分配内存,此时需要先到堆里分配一个指针指向嵌套的结构体空间
typedef struct student
{
struct base bs
struct scores psc;
}student;
int main()
{
struct scores *psc = &stu.sc;//
printf("peng: %f\n", psc->eng);//
}
注意
结构体定义中可以嵌套其他结构体类型的变量,不可以嵌套自己这个类型的变量
详见数组
如果知道结构体变量,就可以知道他本身的首地址,也可以算出其各个成员所在的内存地址
但是
当别人提前架构了一个结构体空间,并且只告诉它的内部成员的地址,而你需要去取同一个结构体内的其他成员内容,此时就需要应用该功能
由于在c中结构体是连续存储的,所有可以通过成员来计算结构地址
比如:
struct Data
{
int a;
char b;
int C;
}
struct Data data={
10, 'a', 20};
下面通过data.b的地址来计算data的地址
(这种情况下两个地址当然都是已知的,但是当你调用别人写的代码,时,会出现只知道某个大结构体中一个成员的情况,此时就需要求该结构体首地址)
先计算data.b与data的地址差值(当一个结构体设计好了后那么它的成员排列顺序就已经固定),所以差值就是固定的,可以通过一个已只得结构体来计算差值x
比如这里x = (char*)&data. b- (char*)&data;
//4字节
后续要算data的地址就可以通过成员地址减去差值x
dx = ((char *)&data.b - (char *)&data) = 4B
(char *)&data = (char *) (&data. b - dx)
(struct Data *) (char *)&data = ((struct Data *)) (char *) (&data.b一dx)
即所求的结构体地址为:
(struct Data*) (char*) (&data.b - x )
在linux内核中可以通过 container_of 宏去实现“知道结构体成员地址,求结构体入口地址”
#include
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
struct data
{
int a;
char b;
short c;
int d;
};
int main()
{
struct data dt = {
.a = 10,
.b = 20,
.c = 30,
.d = 40,
};
printf("dt_addr = %p, c_addr = %p\n", &dt, &dt.c);
printf("dt_addr2 = %p\n", container_of(&dt.c, struct data, c));
//知道一个结构体的成员地址,求这个结构体的总进口的地址(当然也就可以算出所有成员的地址)
//基本形式:container_of(参数1,参数2,参数3)
return 0;
}
ptr表示己知的结构体成员的地址
type表示这个结构体类型
member表示己知地址的成员在结构体中的名称
示例1:
#include
char add(int a, int b)
{
int c;
c = (a + b);
return c;
}
int main()
{
int ret;
int d1 = 1;
int d2 = 2;
ret = add(d1,d2);
printf("ret: %d\n", ret);
return 0;
}
结论:
由于ret = add(d1,d2)调用过程中,是先执行add(d1,d2)得到临时变量c=3结构,之后
退出函数,c会被free内存,再执行ret = add,由于先free,就不可能再将c=3给到ret
实验证明可以得到传递回来的数据,所以分析应该是有一个用户看不到的缓冲区,实现
了数据的传递。那到底有多大?
示例2:
#include
struct student
{
char name[128];
int age;
};
struct student getInfo()
{
printf("-----> getInfo <-----\n");
struct student s = {
"kitty", 10};
return s;
}
int main()
{
printf("size: %d\n", sizeof(struct student));
struct student stu;
stu = getInfo();
printf("name: %s\n", stu.name);
printf("age: %d\n", stu.age);
return 0;
}
结论:
证明也是可以正常传递一个较大的结构体内存空间,说明这个缓冲区是比较大的,那到底有多大?
示例3:
将结构体空间申请更多
struct student
{
char name[2*1024*1024];
int age;
};
是可以正常输出,再调大
struct student
{
char name[3*1024*1024];
int age;
};
结论:
说明这个缓冲区是一个月大小限定的区域。
思考题: 这个空间是谁分配的? 到哪里分配?(地址在哪里?)
应用情景:
如果操作数据较大,就不要在栈里面分配空间,建议在堆里面分配
如果操作数据想要分享给更多的函数
如果数据较小,可以采用返回值非指针类型
如查数据较大,要采用返回值为指针类型传递
C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。
它可以用于声明一组常数。当一个变量有几个固定的可能取值时,可以将这个变量定义为枚举类型。
枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。其在栈中申请的内存空间固定为4byte(32位)/由计算机平台决定。
枚举内声明一般都用大写声明,因为枚举成员的用法可以理解为宏,调用时直接输名称或对应的数字是一样的,但是预处理阶段编译器并不会展开名称,而对宏则会展开。并且enum中声明的枚举类型的值在编译完后,执行时才动态展开,这里类似于变量。
大部分的enum声明在.h文件中,定义变量在.c中使用,由于其内限定了某些数值,也可将其理解为可变的宏。
枚举可以被包含在结构体中使用。
#include
enum weekday
{
sun = 0,//其实就是标记一个常量
mou = 1,
tue = 2,
wed = 3,
thu = 4,
fri = 5,
sat = 6
};
int main()
{
enum weekday wday;//取值范围固定是无符号int型-32位
wday = sun;
printf("%d\n", wday);
return 0;
}
一般形式为:
enum枚举名字//可以自定义
{
枚举成员1,//枚举成员 1默认0
枚举成员2,//在默认枚举成员 1的值上加+1 = 1
枚举成员3,//值 2
枚举成员4,
};
说明:
枚举成员的值是无符号类型: %u
前面只是定义了枚举类型,接下来就可以利用定义好的枚举类型定义变量。
跟结构体一样,有3种方式定义枚举变量:
先定义枚举类型,再定义枚举变量
enum Season { spring, summer, autumn, winter} ;
enum Season S;
定义枚举类型的同时定义枚举变量
enum Season { spring, summer, autumn, winter} s;
省略枚举名称,直接定义枚举变量
enum {spring, summer, autumn, winter} s;
说明:
例如:
enum season { spring,summer=3, autumn, winter} ;
没有指定值的枚举元素,其值为前一元素加1。也就说spring的值默认为0,sunmer 的值为3,autumn 的值为4,winter的值为5
枚举变量=枚举成员(枚举常量)
示例:
int main(int argc, char *argv[])
{
enum Season {
spring, summer, autumn, winter} s;
s = spring;
printf("%u \n", s); //默认值为0
if(xxx)
s = winter;
return 0;
}
以上例子,为什么不用普通变量来描述?
说明:只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如:
S= spring;
是正确的。而:
s = 0;
是错误的。如一定要把数值赋予枚举变量,则必须用强制类型转换。如:
s = (enum weekday)2;
//声明枚举类型:
enum s5p6818_led_action
{
S5P6818_LED_ON, //0
S5P6818_LED_OFF, //1
};
//利用枚举来设计逻辑代码
int s5p6818_hwled_action(enum s5p6818_led_action action)
{
switch(action)
{
case S5P6818_LED_ON;
//{具体的点灯程序}
break;
case S5P6818_LED_OFF;
//{具体的灭灯程序}
break;
}
}
马仔(你)调用前辈程序(.h 或.c)
enum s5p6818_led_action myops;
myops = S5P6818_LED_IN;
s5p6818_hwled_action(myops);
int main()
{
enum body{
a,b,c,d } month[31],j;
int i;
j=a;
for(i= 1;i<=30;i++){
month[i]=j;
j++;
if (j>d) j=a;
}
for(i= 1;i<=30;i++)
{
switch(month[i])
{
case a:printf(" %2d %c\t",i,'a'); break;
case b:printf(" %2d %c\t" ,i,'b'); break;
case c:printf(" %2d %c\t" ,i,'c'); break;
case d:printf(" %2d %c\t" ,i,'d'); break;
default:break;
}
}
printf("\n");
return 0;
}
通过Source Insight在lib.c库中查找枚举类型并研究其用法(声明、定义、使用)
总结:当你需要使用一个变量,而这个变量的可能值需要限定在罗列出的范围内,把这个变量定义为枚举变量,并根据要解决的具体情况包装成枚举成员
联合体(共用体) :所有的变量共用一块内存空间(以成员最大类型决定的)
主要应用在:
一般形式为:
union 数据类型名字
{
成员1;
成员2;
成员3;
...
};
例如:
union perdata
{
int class;
char office[10];
};
//定义了一个名为perdata的联合体类型,它有两个成员,分别为class与office
说明:
联合体在内存中的存储方式,需要符合:
方式一: 先定义联合体类型,再定义联合体变量
union perdata
{
int class;
char office[10];
};
union perdata a,b;
方式二: 定义联合类型的同定义联合体变量
union perdata
int class;
char office[10];
}a,b;
方式三: 直接定义联合体
union
{
int class;
char office[10];
}a,b;
对联合体变量的赋值只能是对变的成员进行,一般表示方式为:
联合变量名.成员名=值
例如:
union
{
int class;
char office[10];
}a;
a.class = 102;
a.office[0] = 'a';
说明:
示例:设有一个教师与学生通用的表格,教师数据有姓名、年龄、职业与教研室四项。
学生数据有姓名、年龄、职业与班级四项。
设计代码,用于输入人员数据,再以表格输出。
struct
{
char name[10];
int age;
char job;
union {
//用到联合体结构
int class;
char ffie[10];
}depa;
}body[2];
int main()
{
int n,i;
//输入学生或者教师信息
for(i=0; i<2; i++)
printf("input name age job and department.\n");
scanf("%s %d 9%c",body[i].name, &body[i].age, &body[i]job);
if(body[i]job =='s') //说明是学生
scanf("%d", &body[i].depa.class);
else
scanf("%s", body[i]depa.office);
//输出学生或者教师信息
for(i=0; i<2; i++)
{
if(boy[].job=='s")
printf("%s\t%3d %3c %d \n", body[i].name, body[i].age, body[i].job, body[i].depa.class);
else
printf("%s\t%3d %3C %s \n", body[i].name, body[i].age, body[i].job, body[i].depa.office);
}
}
概念提出
由于数据可能是由多个字节组成,而存储器中的存储空间也是由多个字节组成,那么如何让多个字节的数据在存到存储器中能依然保存组合顺序将会会直接决定数据是否会丢失,所以为了能让写与读时数据的顺序不改变,计算机中就约定了两种组合方式:
小端存储结构:数据的高位存在高地址,低位存在低地址
大端存储结构:数据的高位存在低地址,低位存在高地址
如果在写操作时选择的是小端,那么读的时候也应该是小端读,同样,如果是设置为大端,那么读写都是大端。默认情况下,处理器设置的是小端模式,但是用户可以到协处理器中更改成大端模式。
#include
enum clnt_stat {
RPC_SUCCESS=0, /* call succeeded */
/*
* local errors
*/
RPC_CANTENCODEARGS=1, /* can't encode arguments */
RPC_CANTDECODERES=2, /* can't decode results */
RPC_CANTSEND=3, /* failure in sending call */
RPC_CANTRECV=4, /* failure in receiving result */
RPC_TIMEDOUT=5, /* call timed out */
/*
* remote errors
*/
RPC_VERSMISMATCH=6, /* rpc versions not compatible */
RPC_AUTHERROR=7, /* authentication error */
RPC_PROGUNAVAIL=8, /* program not available */
RPC_PROGVERSMISMATCH=9, /* program version mismatched */
RPC_PROCUNAVAIL=10, /* procedure unavailable */
RPC_CANTDECODEARGS=11, /* decode arguments error */
RPC_SYSTEMERROR=12, /* generic "other problem" */
RPC_NOBROADCAST = 21, /* Broadcasting not supported */
/*
* callrpc & clnt_create errors
*/
RPC_UNKNOWNHOST=13, /* unknown host name */
RPC_UNKNOWNPROTO=17, /* unknown protocol */
RPC_UNKNOWNADDR = 19, /* Remote address unknown */
/*
* rpcbind errors
*/
RPC_RPCBFAILURE=14, /* portmapper failed in its call */
#define RPC_PMAPFAILURE RPC_RPCBFAILURE
RPC_PROGNOTREGISTERED=15, /* remote program is not registered */
RPC_N2AXLATEFAILURE = 22, /* Name to addr translation failed */
/*
* unspecified error
*/
RPC_FAILED=16,
RPC_INTR=18,
RPC_TLIERROR=20,
RPC_UDERROR=23,
/*
* asynchronous errors
*/
RPC_INPROGRESS = 24,
RPC_STALERACHANDLE = 25
};
enum auth_stat
{
AUTH_OK=0,
/*
* failed at remote end
*/
AUTH_BADCRED=1, /* bogus credentials (seal broken) */
AUTH_REJECTEDCRED=2, /* client should begin new session */
AUTH_BADVERF=3, /* bogus verifier (seal broken) */
AUTH_REJECTEDVERF=4, /* verifier expired or was replayed */
AUTH_TOOWEAK=5, /* rejected due to security reasons */
/*
* failed locally
*/
AUTH_INVALIDRESP=6, /* bogus response verifier */
AUTH_FAILED=7 /* some unknown reason */
};
#define u_long unsigned long
struct rpc_err {
enum clnt_stat re_status;
union
{
int RE_errno; /* related system error */
enum auth_stat RE_why; /* why the auth error occurred */
struct {
u_long low;
u_long high;
} RE_vers;
struct {
long s1;
long s2;
} RE_lb;
} ru;
#define re_errno ru.RE_errno
#define re_why ru.RE_why
#define re_vers ru.RE_vers
#define re_lb ru.RE_lb
};
int main()
{
printf("u_long: %d\n", (int)sizeof(long long));
struct rpc_err data;
printf("size: %d\n", (int)sizeof(data));
return 0;
}