11构造类型

构造类型

一、结构体

结构体类型概念

结构体:一种构造类型,由若干成员组成,成员可以是基本数据类型,也可以是构造类型

struct  结构体名
{
             成员列表
};

结构体变量定义

方式一:先声明结构体类型,再定义变量

struct Product product1;
struct Product product2;
//Product是结构体类型
//product1、product2是结构体变量名

结构体变量的定义不仅要求指定变量为结构体类型,而且要求指定为某一特定的结构体类型。而基本变量的定义只需要指定类型即可。

定义结构体变量后,系统会为其分配内存单元,大小为定义成员列表的大小总和。

方式二:在声明结构类型的同时定义变量

struct Product{
    //成员列表
}product1,product2;

方式三:直接定义结构体类型变量

struct{
    //成员列表
}product1,product2;

类型变量不同,在编译时,计算机不会对类型进行分配内存,只对变量分配内存

结构体变量的引用

对结构体变量进行引用时,不能直接将结构体变量作为一个整体进行输入和输出,在进行 输出时需要对结构体变量进行赋值、存取或运算。赋值方法:结构变量名.成员名=值。

如果成员变量本身是结构体变量,则需要一级一级的赋值,即智能对最低一级进行赋值、运算、存取

#include <stdio.h>
#include <stdlib.h>
 
struct test{
    int num;
    char name;
 
}test01;
 
int main()
{
    scanf("%d",&test01.num);
    printf("%d",test01.num);
 
    return 0;
}

结构体类型的初始化

#include<stdio.h>
 
struct Student					/*学生结构*/
{
	char cName[20];				/*姓名*/
	char cSex;					/*性别*/
	int iGrade;					/*年级*/
} student1={"HanXue",'W',3};					/*定义变量并设置初始值*/

int main()
{
	struct Student student2={"WangJiasheng",'M',3};/*定义变量并设置初始值*/
	
	/*将第一个结构体中的数据输出*/
	printf("the student1's information:\n");
	printf("Name: %s\n",student1.cName);
	printf("Sex: %c\n",student1.cSex);
	printf("Grade: %d\n",student1.iGrade);
	/*将第二个结构体中的数据输出*/
	printf("the student2's information:\n");
	printf("Name: %s\n",student2.cName);
	printf("Sex: %c\n",student2.cSex);
	printf("Grade: %d\n",student2.iGrade);
	return 0;
}

二、结构体数组

定义结构体数组

struct  结构体名

{

       成员列表

}数组名[];

struct Student{
    char cName[20];
    int iNumber;
    char cSex;
    int iGrade;
}student[5];

初始化结构体数组

#include<stdio.h>
 
struct Student								/*学生结构*/
{
	char cName[20];						/*姓名*/
	int  iNumber;						/*学号*/
	char cSex;							/*性别*/
	int iGrade;								/*年级*/
} student[5]={{"WangJiasheng",12062212,'M',3},
				{"YuLongjiao",12062213,'W',3},
				{"JiangXuehuan",12062214,'W',3},
				{"ZhangMeng",12062215,'W',3},
				{"HanLiang",12062216,'M',3}};	/*定义数组并设置初始值*/
 
int main()
{
	int i;					/*循环控制变量*/
	for(i=0;i<5;i++)		/*使用for进行5次循环*/
	{
		printf("NO%d student:\n",i+1);		/*首先输出学生的名次*/
		printf("Name: %s, Number: %d\n",student[i].cName,student[i].iNumber);/*使用变量i做下标,输出数组中的元素数据*/
		printf("Sex: %c, Grade: %d\n",student[i].cSex,student[i]. iGrade);
		printf("\n");		/*空格行*/
	}
	return 0;
}

三、结构体指针

定义:结构体类型 *指针名

引用方式1:点运算符

#include<stdio.h>
 
int main()
{
	struct Student								/*学生结构*/
	{
		char cName[20];							/*姓名*/
		int  iNumber;							/*学号*/
		char cSex;								/*性别*/
		int iGrade;								/*年级*/
	}student={"SuYuQun",12061212,'W',2};		/*对结构变量进行初始化*/
 
	struct Student *pStruct;			/*定义结构体类型指针*/
	pStruct=&student;					/*指针指向结构体变量*/
	printf("-----the student's information-----\n");	/*消息提示*/
	printf("Name: %s\n",(*pStruct).cName);		/*使用指针引用变量中的成员*/
	printf("Number: %d\n",(*pStruct).iNumber);
	printf("Sex: %c\n",(*pStruct).cSex);
	printf("Grade: %d\n",(*pStruct).iGrade);
	return 0;
}

引用方式2:指向运算符

注意:

p->i;    //表示指向的结构体变量中成员 i 的值

p->i++;       //表示指向结构体成员变量中 i 值,在使用后该值+1;

++p->i;       //表示指向结构体成员变量 i 的值加1后进行使用。

(++p)->i;    //表示指向结构体成员变量 i 的下一个元素的地址,然后取得该元素的值

(p++)->i;    //表示先取得当前元素的成员值,在使得指针p指向下一个元素的地址
#include<stdio.h>
#include<string.h>
struct Student							/*学生结构*/
{
	char cName[20];					/*姓名*/
	int  iNumber;						/*学号*/
	char cSex;						/*性别*/
	int iGrade;							/*年级*/
}student;							/*定义变量*/
 
int main()
{
 
	struct Student *pStruct;					/*定义结构体类型指针*/
	pStruct=&student;						/*指针指向结构体变量*/
 
	strcpy(pStruct->cName,"SuYuQun");		/*将字符串常量复制到成员变量中*/
	pStruct->iNumber=12061212;				/*为成员变量赋值*/
	pStruct->cSex='W';
	pStruct->iGrade=2;
 
	printf("-----the student's information-----\n");	/*消息提示*/
	printf("Name: %s\n",student.cName);		/*使用变量直接输出*/
	printf("Number: %d\n",student.iNumber);
	printf("Sex: %c\n",student.cSex);
	printf("Grade: %d\n",student.iGrade);
	return 0;
}

四、结构体作为函数参数

1、使用结构体变量作为函数的参数

将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。

void  Display(struct  Student  stu);

在形参的位置使用结构体变量,但在函数调用期间,形参也要占用大量的内存,这种方式在时间和空间上的消耗都很大

#include<stdio.h>
 
struct Student								/*学生结构*/
{
	char cName[20];							/*姓名*/
	float fScore[3];						/*分数*/
}student={"SuYuQun",98.5f,89.0,93.5f};									/*定义变量*/
 
void Display(struct Student stu)	/*形参为结构体变量*/
{
	printf("-----Information-----\n");	/*提示信息*/
	printf("Name: %s\n",stu.cName);		/*引用结构成员*/
	printf("Chinese: %.2f\n",stu.fScore[0]);
	printf("Math: %.2f\n",stu.fScore[1]);
	printf("English: %.2f\n",stu.fScore[2]);
	/*计算平均分数*/
	printf("Average score:%.2f\n",(stu.fScore[0]+stu.fScore[1]+stu.fScore[2])/3);
	
}
 
int main()
{
	Display(student);	/*调用函数,结构变量作为实参进行传递*/
	return 0;
}

2、使用指向结构体变量的指针作为函数参数

#include<stdio.h>
 
struct Student								/*学生结构*/
{
	char cName[20];							/*姓名*/
	float fScore[3];						/*分数*/
}student={"SuYuQun",98.5f,89.0,93.5f};									/*定义变量*/
 
void Display(struct Student* stu)	/*形参为结构体变量的指针*/
{
	printf("-----Information-----\n");	/*提示信息*/
	printf("Name: %s\n",stu->cName);		/*使用指针引用结构体变量中的成员*/
	printf("English: %.2f\n",stu->fScore[2]);
	stu->fScore[2]=90.0f;			/*更改成员变量的值*/
}
 
int main()
{
	struct Student* pStruct=&student;  /*定义结构体变量指针*/
	Display(pStruct);	/*调用函数,结构变量作为实参进行传递*/
	printf("Changed English: %.2f\n",pStruct->fScore[2]);	/*输出成员的值*/
	return 0;
}

3、使用结构体变量的成员作为函数参数

void Display(student.fscore[0]);

包含结构体的结构

#include<stdio.h>
 
struct date								/*时间结构*/
{
	int year;								/*年*/
	int month;								/*月*/
	int day;								/*日*/
};
 
struct student								/*学生信息结构*/
{
	char name[30];							/*姓名*/
	int num;								/*学号*/
	char sex;								/*性别*/
	struct date birthday;						/*出生日期*/
}student={"SuYuQun",12061212,'W',{1986,12,6}};	/*为结构变量初始化*/
 
int main()
{
	printf("-----Information-----\n");
	printf("Name: %s\n",student.name);	/*输出结构成员*/
	printf("Number: %d\n",student.num);
	printf("Sex: %c\n",student.sex);
	printf("Birthday: %d,%d,%d\n",student.birthday.year,
			student.birthday.month,student.birthday.day);/*将成员结构体数据输出*/
 
	return 0;
}

因为date成员变量本身是结构体,则在使用的时候需要用大括号将date本身包含的成员变量扩起来
共用体

union  共用体名

{

    成员列表

}变量列表;

#include<stdio.h>
 
union DataUnion			/*声明共用体类型*/
{
	int iInt;			/*成员变量*/
	char cChar;			
};
 
int main()
{
	union DataUnion Union;		/*定义共用体变量*/
	Union.iInt=97;				/*为共用体变量中成员赋值*/
	printf("iInt: %d\n",Union.iInt);		/*输出成员变量数据*/
	printf("cChar: %c\n",Union.cChar);
	Union.cChar='A';			/*改变成员的数据*/
	printf("iInt: %d\n",Union.iInt);		/*输出成员变量数据*/
	printf("cChar: %c\n",Union.cChar);
	return 0;
}

在程序中改变共用体中一个成员的值,其他成员也会随之改变。当给某个特定的成员进行赋值时,其他成员的值也会有一致的含义,因为它们每个二进制位都被新值所覆盖。
共用体变量的初始化

对于共用体变量初始化时,只需要一个初始化值就够了,其类型必须与共用体的第一个成员的类型相一致

#include<stdio.h>
 
union DataUnion							/*声明共用体类型*/
{
	int iInt;								/*成员变量*/
	char cChar;			
};
 
int main()
{
	union DataUnion Union={97};					/*定义共用体变量,并进行初始化*/
	printf("iInt: %d\n",Union.iInt);				/*输出成员变量数据*/
	printf("cChar: %c\n",Union.cChar);
	return 0;
}

如果共用体中的第一个成员是结构体类型,则初始化值可以包含多个用于初始化该结构的表达式

①对于共用体同一内存中可以存放几种不同的数据类型,但是每次只能存放一种,而不能同时存放所有的类型,即只有一个成员变量起作用。

②共用体中起作用的是最后一个放进去的成员,存放入新的成员后,原成员就失去作用

③共用体变量的地址和其它各成员的地址是一样的

④不能对共用体变量名赋值,要求不能用 变量名来得到一个值。

五、 结构体字节对齐规则

三个概念:自身对齐值、指定对齐值、有效对齐值。(如果题目中没要求按照对齐分配出,则默认使用按需分配)

自身对齐值:数据类型本身的对齐值,例如char类型的自身对齐值是1,short类型是2;

指定对齐值:编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;

有效对齐值:自身对齐值和指定对齐值中较小的那个。

对齐有两个规则:

1、不但结构体的成员有有效对齐值,结构体本身也有对齐值,这主要是考虑结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。结构体的有效对齐值是其最大数据成员的自身对齐值;

2、存放成员的起始地址必须是该成员有效对齐值的整数倍。

举四个例子

输入图片说明

假如结构体起始地址是0x0000,

成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节;

成员b的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0001是1的整数倍,故b存放起始地址是0x0001,占一个字节;

成员c的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0002是1的整数倍,故c存放起始地址是0x0002,占一个字节;

成员d的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0003是1的整数倍,故d存放起始地址是0x0003,占一个字节;

此时结构体A的有效对齐值是其最大数据成员的自身对齐值,它的成员都是char类型,故结构体A的有效对齐值是1.

结构体A的存储结构如下,其中Y是根据规则1补齐的字节,x是规则2补齐的字节。

0x0000 0x00001 0x0002 0x0003
a b c d

根据以上规则可以知道其他结构体的存储结构:

结构体B占6个字节

0x0000 0x00001 0x0002 0x0003 0x0004 0x0005
a x b b c d

结构体C占12个字节

成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节;

成员b的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0004是4的整数倍,故b存放起始地址是0x0004,占四个字节;

成员c的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0008是1的整数倍,故c存放起始地址是0x0008,占一个字节;

成员d的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0009是1的整数倍,故d存放起始地址是0x0009,占一个字节;

结构体C的成员占据10个字节,而结构体C的有效对齐值是其成员b的自身对齐值4,10不是4的倍数,故还需补齐两个字节,此时结构体C占据12个字节,是4的倍数

如下:

0x0000 0x00001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 0x0009 0x000A 0x000B
a x x x b b b b c d Y Y

结构体D占16个字节

0x0000 0x00001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 0x0009 0x000A 0x000B 0x000C 0x000D 0x000E 0x000F
a x x x b b b b b b b b c d Y Y

代码验证如下:

输入图片说明

六、共用体

共用体的概念 :

使几个不同的变量共占同一段内存的结构称为 “共用体”类型的结构。

共用体定义

  union 共用体名

      {

        成员表列

      }变量表列;

//例如:

 union data             
{                  
    int i;               
    char ch;         
    float f;             
}a,b,c;                
                     
//或者
 union data             
{                  
    int i;               
    char ch;         
    float f;             
};
 union data a,b,c;

共用体引用

只有先定义了共用体变量才能引用它,而且不能引用共用体变量,而只能引用共用体变量中的成员。例如:前面定义了a、b、c为共用体变量:

  • a.i (引用共用体变量中的整型变量i)
  • a.ch(引用共用体变量中的字符变量ch)
  • a.f (引用共用体变量中的实型变量f)

共用体和结构体的比较:

  • 结构体变量所占内存长度是各成员占的内存长度之和。每个成员分别占有其自己的内存单元。
  • 共用体变量所占的内存长度等于最长的成员的长度。 例如: 上面定义的“共用体”变量a、b、c各占4个字节(因为一个实/整型变量占4个字节),而不是各占4+1+4=9个字节。

共用体类型数据的特点:

  1. 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同时存放几种
  2. 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。
  3. 共用体变量的地址和它的各成员的地址都是同一地址。
  4. 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值,又不能在定义共用体变量时对它初始化。
  5. 不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针 。
  6. 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

七、枚举类型

  在实际问题中,有些变量的取值被限定在一个有限的范围内。 例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。 如果把这些量说明为整型,字符型或其它类型显然是不妥当的。 为此,C语言提供了一种称为“枚举”的类型。设有变量a,b,c被说明为上述的weekday:

枚举定义与引用

//可采用下述任一种方式:

enum weekday{ sun,mon,tue,wed,thu,fri,sat };
enum weekday a, b, c;

//或者为:
enum weekday{ sun,mon,tue,wed,thu,fri,sat }a, b, c;

//或者为:
enum { sun,mon,tue,wed,thu,fri,sat }a, b, c;

  

枚举类型中需要注意的地方:

  • 在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。

  • 应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。

  • 在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。

  • 在C编译中,对枚举元素按常量处理,故称枚举常量。它们不是变量,不能对它们赋值。

  • 枚举元素作为常量,它们是有值的,C语言编译按定义时的顺序使它们的值为0,1,2…

  • 枚举值可以用来作判断比较。

  • 一个整数不能直接赋给一个枚举变量。

八、用typedef定义类型

用typedef声明新的类型名来代替已有的类型名:

  • 声明INTEGER为整型:typedef int INTEGER

    #include <stdio.h>
    
    typedef int INTEGER;
    
    void main()
    {
          INTEGER i = 1;
          int j = 2;
    
          printf("%d, %d\n\n", i, j);
    }
    
  • 声明结构类型:

    Typedef  struct{
     int month;
     int day;
     int year;}DATE;
    

    举例:

    #include <stdio.h>
    
    typedef struct
    {
          int month;
          int day;
          int year;
    }DATE;
    
    void main()
    {
          DATE date_one;
    
          date_one.month = 12;
          date_one.day = 31;
          date_one.year = 2012;
    
          printf("%d - %d - %d \n", date_one.year, date_one.month, date_one.day);
    }
    
  • 声明NUM为整型数组类型 :typedef int NUM[100];

    #include <stdio.h>
    
    typedef int NUM[100];
    
    void main()
    {
          NUM num = {0};
          printf("%d\n\n", sizeof(num));
    }
    
  • 声明STRING为字符指针类型 :typedef char* STRING;

    #include <stdio.h>
    
    typedef char* P;
    
    void main()
    {
          P p1;
    
          p1 = "I love Fishc.com";
          printf("%s\n", p1);
    }
    
  • 声明 POINTER 为指向函数的指针类型,该函数返回整型值: typedef int (*POINTER)();

    #include <stdio.h>
    
    typedef void (*P)();
    
    void fun();
    void main()
    {
          P p1; // void (*p1)();
          char a[10] = "Fishc.com!";
    
          printf("%d %d\n", a, &a);//数组地址和数组名地址相同
    
          p1 = fun;//等同于p1 = &fun; 函数名称和函数地址相同,与数组地址和数组名地址道理相同
          (p1)();
    }
    
    void fun()
    {
          printf("I love Fishc.com!\n");
    }
    

 用typedef定义类型的方法:先按定义变量的方法写出定义体(如:int i) ,将变量名换成新类型名(例如:将i换成COUNT)。即在最前面加typedef(例如:typedef int COUNT),然后可以用新类型名去定义变量 (例如:COUNT i, j;)

关于 typedef的说明:

  • 用typedef 可以声明各种类型名,但不能用来定义变量。
  • 用typedef 只是对已经存在的类型增加一个类型名,而没有创造新的类型。
  • 当不同源文件中用到同一类型数据时,常用typedef 声明一些数据类型,把它们单独放在一个文件中,然后在需要用到它们的文件中用#include命令把它们包含进来。
  • 使用typedef 有利于程序的通用与移植。
  • typedef与#define有相似之处,例如:typedef int COUNT;#define COUNT int 的作用都是用COUNT 代表 int。但是,它们二者是不同的。
  • #define是在预编译时处理的,它只能作简单的字符串替换,而typedef是在编译时处理的。实际上它并不是作简单的字符串替换,而是采用如同定义变量的方法那样来声明一个类型。

区别: typedef 和 define

    typedef (int*) p1;#define p2 int*

格外注意,一个有分号,一个没有分号!

posted @ 2022-02-25 21:59  Wmic  阅读(59)  评论(0)    收藏  举报