C 程序设计教程(04)—— C 语言的数据类型(二):构造数据类型
该专栏主要介绍 C 语言的基本语法,作为《程序设计语言》课程的课件与参考资料,用于《程序设计语言》课程的教学,供入门级用户阅读。
目录
- C 程序设计教程(04)—— C 语言的数据类型(二):构造数据类型
- 一、数组类型
- 1、数组的定义
- 2、数组的初始化
- 3、数组元素的访问
- 4、数组的存储
- 5、关于数组名
- 6、字符串的表示
- 7、字符数组的赋值
- 二、结构体类型
- 1、结构体类型
- 2、结构体类型变量的定义
- 3、结构体变量的初始化
- 4、结构体成员的访问
- 三、共用体(联合体)
- 1、共用体的定义
- 2、共用体变量的引用
- 四、枚举类型
- 五、用 typedef 定义类型
- 1、为基本数据类型定义新的类型名
- 2、为自定义数据类型(结构体、共用体和枚举类型)定义简洁的类型名称
- 3、为数组定义简洁的类型名称
- 4、为指针定义简洁的名称
我们已经学过 int、char、float、double 等基本数据类型,这些数据类型只能表示某些特定的数据,不能完全表示现实中所有事物。例如:表示一个人的特征,则这个人的姓名、年龄、性别等信息就无法用基本数据类型来完成。因此,C 语言提供了构造类型。
C 语言中的构造数据类型包括:数组类型、结构体类型、共用体类型和枚举类型。
一、数组类型
数组是一个由若干相同类型的变量组成的集合,引用这些变量时可以使用同一个名称。数组由连续的存储单元组成,最低地址对应于数组的第一个元素,最高地址对应于数组的最后一个元素,数组可以是一维、二维和多维数组。
1、数组的定义
数组的定义格式如下:
type array_name[size]; //一维数组
type array_name[size][size]; //二维数组
说明:
(1)在定义数组时,数组的元素个数([size])必须是常数(C99标准支持变长数组,但大多编译器是不支持的)。
(2)对于 m 行 n 列的二维数组,可以理解为该二维数组有 m 个元素,每个元素是一个有 n 个元素的一维数组。
2、数组的初始化
数组的初始化就是给数组元素赋初始值,数组使用 { } 初始化,在大小给定的情况下,可以完全初始化,也可以不完全初始化(其余元素默认为 0)。在大小没有给定的情况下根据初始化内容确定数组的大小。例如:
#include<stdio.h>
int main()
{int i,j;int arr1[10]={1,2,3,4,5,6,7,8,9,0}; //完全初始化,定义包含10个整型元素的数组,10个数组元素都已赋初值int arr2[10]={1,2,3}; //不完全初始化,定义的数组有10个元素,但只给定了3个值,其余7个值默认为0int arr3[]={1,2,3}; //数组大小未给定,根据初始化内容确定数组大小(含有三个整型元素)int arr4[2][3]={{0,1,2},{3,4,5}}; //完全初始化,二维数组可以看作若干个一维数组,分别初始化int arr5[][3]={{0,1,2},{3,4,5}}; //省略行数// int arr6[2][]={{0,1,2},{3,4,5}}; //错误!列数不可省略,编译器编不过去printf("arr1: ");for(i=0;i<10;i++){printf("%d ",arr1[i]);}printf("\n\n"); printf("arr2: ");for(i=0;i<10;i++){printf("%d ",arr2[i]);}printf("\n\n"); printf("arr3: ");for(i=0;i<3;i++) {printf("%d ",arr3[i]);}printf("\n\n"); printf("arr4: ");for(i=0;i<2;i++) {for(j=0;j<3;j++) printf("%d ",arr4[i][j]);printf("\n");}printf("\n"); printf("arr5: ");for(i=0;i<2;i++) {for(j=0;j<3;j++) printf("%d ",arr5[i][j]);printf("\n");}return 0;
}
说明:二维数组可以省略行数,但不可省略列数。
上述程序的运行结果如下:
3、数组元素的访问
数组通过下标引用操作符([ ])实现对数组元素的访问。比如:arr[1] 代表数组 arr 中下标为 1 的元素,arr[i] 代表数组中下标为 i 的元素。数组元素的下标是从 0 开始的。例如:
#include<stdio.h>
int main()
{int i;int arr[10];for(i=0;i<10;i++){arr[i]=i*2+1;}printf("arr: ");for(i=0;i<10;i++){printf("%d ",arr[i]);}return 0;
}
以上程序的输出结果如下:
4、数组的存储
无论是一维数组还是二维数组,数组内的元素在内存中是连续存储的,相邻的两个元素之间的地址都相差一个固定的值(值的大小和数组元素的类型有关。如果数组元素类型是 int,则相差 4 个字节)。例如:
#include<stdio.h>
int main()
{int i,j;int arr1[]={1,2,3,4,5,6};int arr2[3][5]={{1,2,3,4,5},{11,22,33,44,55},{111,222,333,444,555}};printf("输出arr1的地址:\n");for(i=0;i<6;i++){printf("arr1[%d]:%p\n",i,&arr1[i]);}printf("输出arr2的地址:\n");for(i=0;i<3;i++){for(j=0;j<5;j++){printf("arr2[%d][%d]:%p\n",i,j,&arr2[i][j]);} } return 0;
}
以上程序的输出结果如下:
5、关于数组名
数组名是第一个数组元素的地址。当求 sizeof(数组名) 时,得到的是整个数组所占空间的大小;&数组名,是整个数组的地址。例如:
#include<stdio.h>
int main()
{int arr1[5]={1,2,3,4,5};int arr2[2][3]={{1,2,3},{4,5,6}};//结果:20(5个元素,每个元素4个字节)printf("arr1的大小:%d\n",sizeof(arr1)); //结果:24(整个数组大小)printf("arr2的大小:%d\n",sizeof(arr2)); //结果:12(arr2[0]也是数组名,是二维数组第一行元素的数组名)printf("arr2[0]的大小:%d\n",sizeof(arr2[0]));//以下三个地址一样,但是第三个含义与前两个不一样printf("arr1的地址:%p\n",arr1);printf("第一个元素的地址:%p\n",&arr1[0]);printf("整个数组的地址:%p\n",&arr1);//前两个地址一样(都是arr1[1]的地址)printf("arr1+1的地址:%p\n",arr1+1);printf("arr1[0]+1的地址:%p\n",&arr1[0]+1);//&arr是整个数组的地址,+1跳过整个数组printf("&arr1+1的地址:%p\n",&arr1+1);return 0;
}
以上程序的输出结果如下:
6、字符串的表示
C 语言并没有字符串类型,但允许使用字符串常量。字符串常量是用一对双引号括起来的一串字符。例如:
“China”、“OS”、“yes”、"0373-3048111"等。
字符串常量在存储时,系统自动在字符串的末尾加一个“字符串结束标志”(即:ASCII 码为 0 的字符 NULL),常用“\0”表示。因此,程序中长度为 n 个字符的字符串常量,在内存中占用 n+1 个字节的存储空间。
在定义字符型数组时,必须比要存放的最大字符数多一个字符。例如:
#include<stdio.h>
int main()
{char str[20]="I am a student.";printf("%s\n",str);return 0;
}
以上程序的输出结果如下:
7、字符数组的赋值
定义字符数组时可以同时对数组进行初始化,但定义完成后,就不能按初始化的形式对其赋值了。例如:
char a[10]="abcdefgh"; //正确
char b[10];
b="abcdefgh"; //错误
(1)单字符赋值:通过数组下标方式或指针方式,引用数组元素,进行赋值。
(2)字符串赋值:使用 string.h 头文件中的字符串操作函数(strcpy)进行赋值。
#include<stdio.h>
#include<string.h>
int main()
{//单字符赋值 char a[10];a[0]='C';a[1]='h';a[2]='i';a[3]='n';a[4]='a';//字符串赋值char b[10];strcpy(b,"abcdefgh");printf("数组a的内容: %s\n",a);printf("数组b的内容: %s\n",b);return 0;
}
以上程序的输出结果如下:
二、结构体类型
结构体是由一组称为成员(成员变量)的不同数据所组成的,其中每个成员可以具有不同的类型。结构体通常用来表示类型不同但是又相关的若干数据。
结构体的成员可以是数组、指针变量、甚至是其他结构体,一般描述复杂对象时用到结构体。
结构体采用关键字 struct 来进行构建。
1、结构体类型
例如:成绩表的结构如下:
班级 | 学号 | 姓名 | 操作系统 | 数据结构 | 计算机网络 |
---|---|---|---|---|---|
字符串 | 长整型 | 字符串 | 实型 | 实型 | 实型 |
通讯录的结构如下:
姓名 | 工作单位 | 家庭住址 | 邮编 | 电话号码 | |
---|---|---|---|---|---|
字符串 | 字符串 | 字符串 | 字符串 | 字符串 | 字符串 |
把成绩和通讯录定义为结构体类型:
//结构体类型的定义格式:
struct 结构体名称
{成员列表
};//定义“成绩”结构体
struct score
{char grade[20]; //班级long number; //学号char name[20]; //姓名float os; //操作系统float datastru; //数据结构float compnet; //计算机网络
};//定义“通讯录”结构体
struct addr
{char name[20]; //姓名char department[30]; //工作单位char address[30]; //家庭住址char box[6]; //邮编char phone[20]; //电话char email[50]; //E-mail
};
2、结构体类型变量的定义
结构体类型变量的定义与其他类型的变量的定义相同,但结构体类型需要预先定义。结构体变量的定义有三种形式:
(1)先定义结构体类型,再定义结构体类型变量
//定义“成绩”结构体
struct score
{char grade[20]; //班级long number; //学号char name[20]; //姓名float os; //操作系统float datastru; //数据结构float compnet; //计算机网络
};
struct score student1,student2; //定义结构体类型变量
(2)定义结构体类型同时定义结构体变量
struct score
{char grade[20]; //班级long number; //学号char name[20]; //姓名float os; //操作系统float datastru; //数据结构float compnet; //计算机网络
} score student1,student2; //定义结构体类型变量
struct score student3; //定义其他变量
(3)直接定义结构体类型变量
这种定义方式由于没有为结构体类型指定名称,则只能使用一次。不能再次定义该结构体类型的其他变量。
struct
{char grade[20]; //班级long number; //学号char name[20]; //姓名float os; //操作系统float datastru; //数据结构float compnet; //计算机网络
} student1,student2; //定义结构体类型变量
3、结构体变量的初始化
结构体变量的初始化采用 { },成员变量间用逗号隔开便可。例如:
#include<stdio.h>
int main()
{struct score{char grade[20]; //班级long number; //学号char name[20]; //姓名float os; //操作系统float datastru; //数据结构float compnet; //计算机网络};struct score s1={"xinguan211",2021021401,"tom",87.5,90.0,87.0}; //定义结构体类型变量printf("%s\n%d\n%s\n%lf\n%lf\n%lf\n",s1.grade,s1.number,s1.name,s1.os,s1.datastru,s1.compnet);return 0;
}
以上程序的输出结果如下:
4、结构体成员的访问
(1)采用点操作符(.)访问结构体成员
相同结构体类型的结构体能够相互赋值:如有 struct stu s1,s2; 就可以有s1=s2,不同结构体类型的结构体不能相互赋值。结构体成员引用的一般格式:结构体变量名.成员名。例如:
#include<stdio.h>
#include<string.h>
int main()
{struct stu{char name[20];int age;char id[20];};struct stu s1={"tom",20,"20210224111"};struct stu s2;s2=s1;strcpy(s2.name,"jerry");s1.age=22;strcpy(s2.id,"20210224345");printf("学生s1的信息如下:\n");printf("name: %s\nage: %d\nid: %s\n\n",s1.name,s1.age,s1.id);printf("学生s2的信息如下:\n");printf("name: %s\nage: %d\nid: %s\n",s2.name,s2.age,s2.id);return 0;
}
以上程序的输出结果如下:
(2)采用(->)操作符来访问结构体成员
当我们访问的不是一个结构体变量,而是指向一个结构体变量的指针,这时我们可以使用(->)操作符来访问结构体成员。例如:
#include<stdio.h>
struct stu
{char name[20];int age;char id[20];
};
void print(struct stu *s2)
{printf("%s %d %s\n",s2->name,s2->age,s2->id);
}int main()
{struct stu s1={"tom",20,"20210224111"};print(&s1);return 0;
}
以上程序的输出结果如下:
三、共用体(联合体)
一般情况下,系统会为每一个变量开辟独立的存储空间。例如:
char a;
int b;
float c;
系统将为这三个变量分别开辟1个字节、2个字节和4个字节的存储空间。使用共用体类型(union)可以让这三个变量共用一块内存空间。
1、共用体的定义
共用体的定义格式如下:
union 共用体名
{成员列表};
以下代码定义共用体类型的为data,该共用体中有三个成员,分别为a、b、c,它们占用同一个起始地址的存储空间,内存长度等于最长的成员的长度。
//定义共用体类型data
union data
{char a;int b;float c;
};
和定义结构体变量一样,可以用以下三种方式定义共用体变量:
// 格式1
union data
{char a;int b;float c;
};
union data x,y;// 格式2
union data
{char a;int b;float c;
} x,y;// 格式3
union
{char a;int b;float c;
} x,y;
2、共用体变量的引用
定义了共用体变量之后,可以采用如下方式引用共用体变量的成员:
变量名.成员名
对共用体变量的说明:
(1)同一个内存段可以用来存放几种不同类型的成员,但每一时刻只能存放其中一种。也就是说,每一时刻只有一个成员起作用。
(2)共用体变量中起作用的是最后一次被赋值的成员。例如:
#include<stdio.h>
#include<string.h>
int main()
{union data{char a;int b;float c;};union data x;x.a='a';x.b=10;x.c=15.3;printf("%c\n",x.a);printf("%d\n",x.b);printf("%f\n",x.c);return 0;
}
完成以上三个赋值运算之后,只有 x.c 是有效的,x.a 和 x.b 已经无意义了。
以上程序的运行结果如下:
(3)共用体变量的地址和它的各成员的地址相同。例如:
#include<stdio.h>
#include<string.h>
int main()
{union data{char a;int b;float c;};union data x;x.a='a';x.b=10;x.c=15.3;printf("%p\n",&x);printf("%p\n",&x.a);printf("%p\n",&x.b);printf("%p\n",&x.c);return 0;
}
以上程序的运行结果如下:
(4)不能对共用体变量赋值,也不在定义共用体变量时对它进行初始化。例如:
union data
{char a;int b;float c;
} x={'a',10,15.3}; //错误,不能对共用体变量初始化
a=1; //错误,不能对共用体变量赋值
union data m=a; //错误
(5)不能把共用体变量作为函数参数,也不能使函数返回共用体类型的值,但可以使用指向共用体变量的指针。
(6)共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型的定义中,数组也可以作为共用体的成员。
四、枚举类型
枚举就是把变量可能取到的值一一列举出来,变量的取值只局限于列举出来的值的范围。声明枚举类型使用 enum 保留字,例如:
//定义枚举类型
enum weekday{sun,mon,tus,wed,thu,fri,sat};
//定义枚举类型的变量
enum weekday workday,weekend;
//也可以直接定义枚举类型的变量
enum {sun,mon,tus,wed,thu,fri,sat} workday,weekend;
//为枚举类型的变量赋值
workday = mon;
weekend = sun;
说明:
(1)枚举元素按常量处理,称为枚举常量,不能给枚举元素赋值。例如以下赋值是错误的:
sun = 0;
mon = 1;
(2)枚举元素作为常量,C 语言按枚举元素定义的顺序将他们的值定义为0、1、2、…。在前面的定义中,sun 的值为 0,mon 的值为 1,…,sat 的值为 6。可以改变枚举元素的值,如:
#include<stdio.h>
int main()
{enum weekday{sun=7,mon=1,tus,wed,thu,fri,sat};enum weekday workday,weekend;workday = mon;weekend = sun;printf("monday:%d\nsunday:%d",workday,weekend);return 0;
}
以上程序的输出结果如下:
(3)枚举值可用于构造条件,例如:
if(workday == mon) {...}
if(workday > sun) {...}
(4)整数不能直接赋给枚举变量,因为他们属于不同的类型。需要进行强制类型转换才能赋值。例如:
workday = 2; //错误
workday = (enum weekday)2; //相当于将序号为2的枚举元素赋给workday,等价于下面的语句:
workday = tue;
(5)不能对枚举类型的变量直接读写,只能输出枚举变量的序号。
五、用 typedef 定义类型
除了可以直接使用标准类型名(如:int、char、float、double、long 等)和结构体、共用体、指针、枚举类型外,还可以用 typedef 定义新的类型名来代替已有的类型名。
例如:
#include<stdio.h>
#include<string.h>
int main()
{typedef struct{int month;int day;int year;} DATE; //定义新的类型名为DATEtypedef int ARR[10]; DATE birthday={10,5,2000};ARR a={0,1,2,3,4,5,6,7,8,9};int i;printf("birthday: %d-%d-%d\n\n",birthday.year,birthday.month,birthday.day);for(i=0;i<10;i++){printf("数组元素a[%d]的值:%d\n",i,a[i]);}return 0;
以上程序的输出结果如下:
typedef 主要用于以下几种情形:
1、为基本数据类型定义新的类型名
比如:用 typedef 来定义与平台无关的类型。
可以定义一个名称为 REAL 的浮点类型:typedef long double REAL;
在不支持 long double 的机器上,可以修改为:typedef double REAL;
如果连 double 都不支持,可以修改为:typedef float REAL;
当跨平台时,只要修改 typedef 本身就行,不用对其他源码做任何修改。
#include<stdio.h>
#include<string.h>
int main()
{typedef double REAL;REAL a=100.2;printf("%f",a);return 0;
}
以上程序的输出结果如下:
2、为自定义数据类型(结构体、共用体和枚举类型)定义简洁的类型名称
例1:
typedef struct tagPoint
{double x;double y;double z;
} Point;//上面的代码实际上完成了两个操作:
//1、定义一个新的结构类型
//其中:struct 关键字和 tagPoint 一起构成了这个结构类型
struct tagPoint
{double x;double y;double z;
} ;//2、使用 typedef 为这个新的结构起了一个别名,叫 Point,即:
typedef struct tagPoint Point;//然后就可以像 int 和 double 那样直接使用 Point 定义变量,如下面的代码所示:
Point oPoint1={100,100,0};
Point oPoint2;
例2:
#include <stdio.h>
//定义两个类型,一个整型的别名 INTEGER,一个整型指针 PINT,这两个变量没有联系,是独立的。
int main()
{typedef int INTEGER, *PINT;INTEGER a,c;PINT b;a=10; c=20; b=&c;printf("&a=%p\n",&a);printf("b=%p\n",b);printf("a=%d\n",a);printf("*b=%d\n",*b);return 0;
}
以上程序的输出结果如下:
例3:
typedef int ElemType; //定义顺序表的数据元素为整数。
typedef struct
{ElemType data[MAXSIZE]; //用数组存储顺序表中的元素unsigned int length; //顺序表中元素的个数
} SeqList,*PSeqList; //定义两个类型,一个是结构体别名SeqList和一个结构体指针PSeqList。
3、为数组定义简洁的类型名称
例如:
typedef int INT_ARRAY_100[100];
INT_ARRAY_100 arr;
4、为指针定义简洁的名称
例如:
typedef char* PCHAR;
PCHAR pa;