C语言复习笔记--自定义类型
今天我们来复习一下自定义类型.自定义类型大概分为结构体,枚举,联合体,数组这几种.数组在之前就介绍过.今天我们来看下其他三种.
结构体
首先来看结构体.
结构体类型的声明
之前在操作符的地方简单认识过结构体.下面我们回顾一下.
结构体回顾
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构的特殊声明--匿名结构体
上⾯的两个结构在声明的时候省略掉了结构体标签(tag).
#include <stdio.h>struct
{int a;int b;
}x;
struct
{int a;int b;
}*p;
int main()
{x.a = 10;printf("%d", x.a);//x=*p;//err//不可以这样进行赋值,虽然两个结构体内部的结构相同,//但是匿名结构体只能用一次,所以编译器会把这两个看成不同类型//struct s;//err//并且无法用匿名结构体在之后创建变量return 0;
}
如果想使用多次可以进行typedef重命名.具体见下.
typedef struct
{int a;int b;
}ST;
int main()
{ST st;st.a = 10;printf("%d", st.a);return 0;
}
结构的自引用
其实是不可以的,如果这样可以的 话,那么这个结构体的sizeof应该是多少呢?如果可以就没法解答这个问题了,因为会出现套娃现象,结构体大小会变为无穷大.那么链表的节点是如何实现的呢?其实我们可以用结构体指针来代替直接在结构体中创建一个一样的结构体.如下
如果要用typedef使结构体的名字更加简单方便调用的话,在结构体内自引用使用指针时不要用typedef过的简单名字.
结构体内存对齐
我们已经了解了结构体的使用下面我们来了解一下结构体的大小.也就是结构体内存对齐.
虽然是浪费空间去换取时间,但是空间也不能大肆浪费,所以如何既满⾜对⻬,⼜节省空间是需要学习的.
S1 和 S2 类型的成员⼀模⼀样,但是 S1 所占空间要大于 S2.
修改默认对齐数
对于上述问题也可以通过修改默认对齐数来解决(一般修改的都是2的n次方的数).
#pragma pack(1)
struct S
{char a;int b;char c;
};
int main()
{struct S s;printf("%zd", sizeof(s));return 0;
}
//如果想还原
#pragma pack()
struct S
{char a;int b;char c;
};
int main()
{struct S s;printf("%zd", sizeof(s));return 0;
}
结构体传参
typedef struct S
{char a;int b;char c;
}S;void test1(S s1)
{printf("%c %d %c\n", s1.a, s1.b, s1.c);
}
void test2(S* s2)
{printf("%c %d %c\n", s2->a, s2->b, s2->c);
}
int main()
{S s = {'y',10,'x'};test1(s);test2(&s);return 0;
}
上面两个函数中test2要更好一点.
结构体实现位段
什么是位段
位段的声明和结构是类似的,有两个不同
1. 位段的成员必须是 int 、 unsigned int 或 signed int ,在C99中位段成员的类型也可以选择其他类型(char,一定要是整形大家庭中的).
2. 位段的成员名后边有⼀个冒号和⼀个数字.(这个数字代表这个成员所占的比特位).
如下,一个简单的例子
struct S
{
int a : 2;
int b : 3;
char c : 5;
};
既然知道了位段的大体结构,下面我们来看下大小
struct S1
{char a : 2;char b : 3;char c : 5;
};
struct S2
{int a : 2;int b : 3;char c : 5;
};
int main()
{printf("%zd\n", sizeof(struct S1));printf("%zd\n", sizeof(struct S2));return 0;
}
输出结果见下:
位段的内存分配
1. 位段的成员可以是 int unsigned int( signed int 或者是 char 等类型 int )
2. 位段的空间上是按照需要以4个字节或者1个字节( char )的⽅式来开辟的。并且在计算大小时也要遵循内存对齐.(如上求大小例子中的S2所示).
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
位段的跨平台问题
位段的应用
位段使用的注意事项
位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地的。 所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员.
//struct S1
//{
// char a : 2;
// char b : 6;
// char c : 5;
//};
//int main()
//{
// struct S1 s;
// char ch;
// scanf("%c", &ch);
// s.b = ch;
// printf("%c", s.b);//在存进位段中时会发生截断,不要用char类型实验,这里打出来是截断之后的
// return 0;
//}
//
//换为int实验
struct S1
{int a : 2;int b : 18;int c : 5;
};
int main()
{struct S1 s;int ch = 0;scanf("%d%d",&s, &ch);s.b = ch;printf("%d %d\n", s.a, s.b);return 0;
}
联合体
联合体类型的声明
联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型.但是编译器只为最⼤的成员分配⾜够的内存空间.联合体的特点是所有成员共⽤同⼀块内存空间。所以联合体也叫:共用体.给联合体其中⼀个成员赋值,其他成员的值也跟着变化.
看一个简答的例子
union U
{char a;int b;
};
int main()
{union U u;u.b =0x11223348;printf("%c\n", u.a);//48对应的ASCII码表是Hreturn 0;
}
输出结果见下
联合体的特点
联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩(因为联合 ⾄少得有能⼒保存最⼤的那个成员).
更加深入的理解一下
所以联合体中所有元素的起始位置是相同的.
联合体大小的计算
联合的⼤⼩⾄少是最⼤成员的⼤⼩
当最大成员大小不是最大对⻬数的整数倍的时候,就要对⻬到最大对齐数的整数倍.
在计算之前看好是联合体还是结构体,千万不要搞混了.
union Un1
{char c[5];//数组的对齐数按照数组的类型的对齐数算int i;
};
//因为有int所以最大对齐数为4
union Un2
{short c[7];int i;
};
int main()
{//下⾯输出的结果是什么?printf("%d\n", sizeof(union Un1));printf("%d\n", sizeof(union Un2));return 0;
}
输出结果见下
使⽤联合体是可以节省空间的.
既然了解了联合体那么我们来看下一个小练习吧.
写⼀个程序,判断当前机器是⼤端?还是⼩端(用到联合体的知识)
union U
{int a;char b;
};
int main()
{union U u;u.a = 1;if (1 == (int)(u.b)){printf("小端\n");}else{printf("大端\n");}return 0;
}
枚举类型
枚举类型的声明
举个例子:
enum Colour
{
RED,
GREEN,
BLUE,
};
验证一下
enum Colour
{RED,GREEN,BLUE,
};
int main()
{printf("%d %d %d", RED, GREEN, BLUE);return 0;
}
enum Colour
{RED=2,GREEN,BLUE=5,
};
int main()
{printf("%d %d %d", RED, GREEN, BLUE);return 0;
}
如果一个给初始值后下面的没有给则下面的值为上面值+1.
枚举类型的优点
简单的注意一下第五点.
枚举类型的使用
这里主要注意的是枚举常量的赋值.
enum Colour
{RED=2,GREEN,BLUE=5,
};
int main()
{printf("%d %d %d", RED, GREEN, BLUE);// enum Colour c = 0;//在VS中不报错,但是最好不要这样做,枚举类型就要用枚举类型赋值enum Colour c = RED;//要用枚举常量赋值return 0;
}
那是否可以拿整数给枚举变量赋值呢?在C语⾔中是可以的,但是在C++是不⾏的,C++的类型检查⽐ 较严格
以上就是自定义类型的复习了,我们下次复习见.