C语言数据类型非常丰富,其中结构体的使用非常广泛,也有一点复杂,这一讲我们主要学习结构体的使用方法,同时也会学习到联合、枚举以及typedef的使用,因为结构体最为复杂,使用最广,所以我们主要学习结构体。
struct结构体的定义和初始化
//例: struct student{ char name[100]; int age; }; int main(){ struct student st; //定义了一个student类型的结构体,名字叫做st,存放在栈里边 st.age =20; strcpy(st.name,”liudehua”); struct student st1 ={“zhzdu”,40}; //定义结构体变量,同时初始化结构体变量 struct student st1 ={.age =40,.name=”zxjiifuhg”}; //定义结构体变量,同时初始化结构体变量,使用这种方式可以改变初始化顺序 struct student st1 ={“zhzdu”}; //定义结构体变量,同时初始化结构体变量,不初始化的结构体变量则默认为0 };
结构体的对齐说明
结构体在内存中总是对齐的,一个结构体成员变量总是以最大的那个元素作为对齐单位。
struct A{//8字节,a1后面空着3个字节,a2占4个字节 char a1; int a2; } struct A1{//8字节,a1后面空着一个字节,然后a3占两个字节,a2占4个字节 char a1; short a3; int a2;
} struct A2{ char a[10]; int b; }
如果结构体出现数组,则以数组中的具体每个成员大小为对齐标准,如果变量结构体中的所有成员都是一种类型,那么结构体在内存中就基本和一个数组类似。结构体变量的地址就是它首元素的地址。
结构体元素的位字段
为了节省内存空间,结构体变量允许使用为声明,例子如下:
struct A2{//一共占1字节 unsigned char a:2;//a只有两个bit unsigned char b:4; //b只有4个bit }
结构体数组
struct A2{ char name[20]; unsigned char age; unsigned char sex; } void main(){ struct A2 st[3];//定义一个结构体数组,有3个成员,每个成员都是struct结构体变量 }
CPU处理int相比其它基本数据类型效率是最高的,但是int比char要多占内存.
冒泡排序结构体数组
首先要学会使用冒泡排序,然后根据结构体某个成员大小之间作比较,再根据冒泡排序交换结构体中的各个成员来排序,具体的实现方法可自行实现。
结构体嵌套
结构体内部可以有其它结构体,其本质和结构体没有太多区别。
struct A{ char a1; short a2; }; struct B{ struct A a;//这里是一个结构体的嵌套 char a3;//上面结构体变量作为一个整体存在,a3 不可能补到结构体A a2的后面去,它一定是一个单独的对齐单位。 int b; }; struct D{};//D结构体不含有任何结构体变量,这个语法在C语言是不合法的,在C++里是合法的
结构体的赋值其实就是内存的拷贝
struct A{ char a1; short a2; }; struct A st1={s ,s}; struct A st2=st1; //通过指针访问结构体成员 struct A * p=&st1; //(*p).a1 =12;这种写法与下面写法作用相同,但下面写法更直观 p->a1 = 12;
通过指针访问结构体数组
这个和通过指针去访问数组是类似的,这儿就不详细介绍了
void main(){ struct A2 st[3]={0}; struct A2 *p=st; p[1].name = “zcxc” }
结构体中的数组成员和指针成员
struct man{ char * name; int age; };
结构体拷贝的时候存在浅拷贝与深拷贝;浅拷贝之间只是成员之间的粗暴赋值,解决不了结构体中存在指针时,两个指针成员之间只是简单的地址赋值,当一个结构体变量指针成员释放空间时,另一个结构体变量指针成员访问的空间也就消失了,两结构体变量之间相互影响很大。深拷贝则是存在指针变量时,首先为指针变量各自分配空间,然后再进行拷贝,每个结构体指针变量指向的的空间时相互独立的。
堆中创建结构体变量
struct man{ char name[20]; int age; }; struct student{ char * name; int age; } int main(){ struct man st;//name在栈里边 struct man *st1 = malloc(sizeof(struct man));//name在堆里边 struct man *p = malloc(sizeof(struct student));//name在堆里边 p->name = malloc(20); strcpy(p->name,”lfsdi”);//申请一个堆空间,st1->name在堆里,但是一个野指针 st1->age = 20; free(p->name); free(p);//如果结构体变量里含有指针,注意free的先后顺序,如果先free p,则p堆已经释放了,就找不到p->name的首地址 }
函数的参数为结构体变量
struct man{ char name[20]; int age; }; printf_student(struct student st){//st是形参,函数调用的时候,在栈里面有一个浅拷贝的过程,如果里边某个成员为数组较大,会出现一个数组拷贝的过程,会消耗大量时间,不利于优化程序 printf(“%s,%d\n”,st.name,st.age); } printf_student(const struct student *st) {//st =&st //只是一个简单的结构体地址赋值,效率远远高于上面的,形参很少直接用一个结构体变量,一般放结构体指针 printf(“%s,%d\n”,st->name,st->age); }
联合体
联合union是一个能在同一个存储空间存储不同类型的数据,联合体所占的内存长度等于其最长成员的长度,所以代码效率很高。联合体虽然有多个成员,但同一时间只能存放其中一种。
union A{ int a1; short a2; char a3; char *p; };//只占4个字节 int main(){ union A a; a1 =1; a3 =10; a1 =0; //之后a.a3的值为0 a.p = malloc(10);//假设这块堆的内存编号为0x12345C a.a1 = 0;//p的值也成了0 free(a.p); return 0; }
枚举类型
可以使用枚举声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型,实际上enum常量是int类型的,可以增加代码的可读性。
struct A2{ char name[20]; unsigned char age; unsigned char sex; } enum color{red,blue,yellow,green,black}; enum sex {man,woman}; void main(){ struct A2 st; enum sex s; s =man; strcpy(st.name,”znfysry”); st.sex = man;//man就是一个整形的常量,不能做左值,常量也不能取地址 st.age = 0; }
每一个枚举都有默认值0,1, 2,3,4,5,6……… 可以自己设置每个成员的值,enum color{red = 100,blue =12,red = 58,yellow,black,white};100,12,58,59,60,61.....,100 在系统内是由CPU产生的一个立即数,不能取地址, “hello”在内存的常量区里,可以取地址,int a =100;//CPU生成一个立即数,在栈中分配一个4个字节的空间,然后把这个空间的值设置为100,enum在编译完成后只是一个不存在于内存中的立即数,不能取地址。
typdef数据类型
typdef数据类型是一种高级数据类型,它能使某种类型创建自己的名字。仅限于数据类型,不能是表达式或具体的值。
struct student{ char name[20]; unsigned char age; unsigned char sex; } typedef struct student M;//M就类似于int,就是一种数据类型 typedef unsigned char BYTE;//多了一种数据类型叫byte.可以提高代码的维护性 int main(){ M m; BYTE a; return 0; }
typdef数据类型不是一种必须使用的数据类型,但是使用typedef主要目的是为了让程序的可读性更高,方便代码的维护,在代码十分庞大的时候这种数据类型就显得十分必要。
#ifndef UNICODE //方便维护代码 typedef wchar_t TCHAR #else typedef char TCHAR #endif void main(){ TCHAR a1; }