820 likes | 946 Views
第 4 章 复杂数据类型. 为了解决比较复杂的问题,本章介绍 C 语言提供的复杂数据类型。 4.1 数组 4.2 指针 4.3 结构体 4.4 共用体 4.5 枚举类型 4.6 类型定义符 typedef. 4.1 数组. 4.1.1 数组的概念 4.1.2 一维数组 4.1.3 二维数组 4.1.4 字符维数. s[0]. s[1]. s[2]. ……. s[29]. 数组 s. 4.1.1 数组的概念.
E N D
第4章 复杂数据类型 为了解决比较复杂的问题,本章介绍C语言提供的复杂数据类型。 4.1 数组 4.2 指针 4.3 结构体 4.4 共用体 4.5 枚举类型 4.6 类型定义符typedef
4.1 数组 4.1.1 数组的概念 4.1.2 一维数组 4.1.3 二维数组 4.1.4 字符维数
s[0] s[1] s[2] …….. s[29] 数组s 4.1.1 数组的概念 如果有30个互不关联的数据,可以分别把它们存放在30个变量中。但是如果这些数据是有内在联系的,是具有相同属性的(如30个学生的成绩),则可以把这组数据看作一个有机的整体,称为“数组”,用一个统一的名字代表这组数据。例如可用s代表学生成绩这组数据,s就是数组名。一个数组在内存中占一片连续的存储单元,数组s在内存中的存放情况如右图所示。
数组名代表一组数据,那么用什么方法来区分这组数据中的个体数据呢?从图4-1可以看出,s[0]代表第1个学生的成绩、s[1]代表第2个学生的成绩、…、s[29]代表第30个学生的成绩。s右下脚的数字0、1、…、29用来表示数据在数组中的序号,称为下标。数组中的数据称为数组元素。概括地说,数组就是有序数据的集合。要寻找一个数组中的某一个元素必须给出两个要素,即数组名和下标。数组名和下标唯一地标识一个数组中的一个元素。数组名代表一组数据,那么用什么方法来区分这组数据中的个体数据呢?从图4-1可以看出,s[0]代表第1个学生的成绩、s[1]代表第2个学生的成绩、…、s[29]代表第30个学生的成绩。s右下脚的数字0、1、…、29用来表示数据在数组中的序号,称为下标。数组中的数据称为数组元素。概括地说,数组就是有序数据的集合。要寻找一个数组中的某一个元素必须给出两个要素,即数组名和下标。数组名和下标唯一地标识一个数组中的一个元素。
1. 定义一维数组 数组同变量一样,也必须先定义、后使用。 1维数组是只有1个下标的数组,定义形式如下: 数据类型 数组名[常量表达式] 比如 float s[30];int tp[31]; (1)“数据类型”是指数组元素的数据类型。 (2)数组名,与变量名一样,必须遵循标识符命名规则。 (3)“常量表达式”必须用方括号括起来,指的是数组的元素个数(又称数组长度),它是一个整型值,其中可以包含常数和符号常量,但不能包含变量。 比如 int n=30; int a[n] 是错误的。 (4)数组元素的下标从0开始。 4.1.2 一维数组定义、引用和初始化
2 数组元素的引用 引用数组中的任意一个元素的形式: 数组名[下标表达式] 1.“下标表达式”可以是任何非负整型数据,取值范围是0~(元素个数-1)。 特别强调:在运行C语言程序过程中,系统并不自动检验数组元素的下标是否越界。因此在编写程序时,保证数组下标不越界是十分重要的。 2.1个数组元素,实质上就是1个变量,它具有和相同类型单个变量一样的属性,可以对它进行赋值和参与各种运算。 3.在C语言中,数组作为1个整体,不能参加数据运算,只能对单个的元素进行处理。
例4.1 输入10个整数,按输入时相反的顺序输出这10个数。 分析:用一个一维数组来存储输入的10个整数,通过从后往前访问数组元素的方法即可实现10个数的逆序输出。 #include <stdio.h> void main() { int i,data[10]; for(i=0;i<10;i++) scanf("%d",&data[i]); for(i=9;i>=0;i--) printf("%d ",data[i]); } 运行结果: 1 2 3 4 5 6 7 8 9 10 10 9 8 7 6 5 4 3 2 1
例4.2 输入10名学生某门课程的成绩,要求把高于平均分的那些成绩打印出来。 • 分析:用一个一维数组来存储10名学生的成绩,通过访问所有数组元素可计算出总成绩进而求出平均分。将每个学生的成绩逐个和平均分相比较,即可把高于平均分的成绩打印出来。 #include <stdio.h> void main() { int i,score[10]; int sum=0,aver; for(i=0;i<10;i++) { scanf("%d",&score[i]); sum=sum+score[i]; }
aver=sum/10; printf("平均分为:%d\n",aver); printf("高于平均分的成绩为:\n"); for(i=0;i<10;i++) if(score[i]>aver) printf("%4d",score[i]); } 运行结果: 60 70 80 78 76 56 98 87 67 61 平均分为:73 高于平均分的成绩为:80 78 76 98 87
3. 一维数组元素的初始化 初始化格式: 数据类型 数组名[常量表达式]={初值表} (1)如果对数组的全部元素赋以初值,定义时可以不指定数组长度(系统根据初值个数自动确定)。如果被定义数组的长度,与初值个数不同,则数组长度不能省略。比如 int a[10]={0,1,2,3,4,5,6,7,8,9}; int a[]={1,2,3,4,5}; (2)“初值表”中的初值个数,可以少于元素个数,即允许只给部分元素赋初值。 int a[10]={0,1,2,3,4};
4.1.3 二维数组的定义、引用和初始化化 如果说一维数组在逻辑上可以想象成一行长表或矢量,那么二维数组在逻辑上可以想象成是由若干行、若干列组成的表格或矩阵。例如以下5名学生的成绩表格就形成了一个二维数组。
上表中的每一行表示了某个学生的三门课程的成绩。上表中的每一行表示了某个学生的三门课程的成绩。 如果用一个score数组来存放上面表格中的成绩, score数组各元素的名称和其所在位置如下所示: score[0][0] score[0][1] score[0][2] score[1][0] score[1][1] score[1][2] score[2][0] score[2][1] score[2][2] score[3][0] score[3][1] score[3][2] score[4][0] score[4][1] score[4][2] 这个数组应该有5行3列,每个元素都具有两个下标用来 表示该元素在数组中的位置。第一个下标表示元素的行号, 第二个下标表示元素的列号。因此,可以通过指定行号和 列号来对不同的数组元素进行存取和引用。
1. 二维数组的定义 二维数组定义的一般形式是: 类型说明符 数组名[常量表达式1][常量表达式2] 其中,“类型说明符”是指数组的数据类型,也就是每个数组元素的类型。“常量表达式1”指出数组的行数,“常量表达式2”指出数组的列数,它们必须都是正整数。 例如 int score[5][3]; 定义了一个5行3列的二维数组score,用于存储上面提到的5个人3门课程的成绩。 虽然在逻辑上可以把二维数组看作是一张表格,但在计算机内部,二维数组的所有元素都是一个接着一个排列的。C编译程序按行主顺序存放数组元素;也就是说先放第一行上的元素,接着放第二行上的元素,依次把各行的元素放入一串连续的存储单元中。数组score在内存中的排列顺序如下图所示。
score[0][0] score[0][1] score[0][2] score[1][0] score[1][1] score[1][2] … score[4][0] score[4][1] score[4][2] 二维数组score在内存存放情况
2. 二维数组元素的引用 引用二维数组元素的形式为: 数组名[行下标表达式][列下标表达式] 1.“行下标表达式”和“列下标表达式”,都应是整型表达式或符号常量。 2.“行下标表达式”和“列下标表达式”的值,都应在已定义数组大小的范围内。假设有数组score[5][3],则可用的行下标范围为0~4,列下标范围为0~2。 3.对基本数据类型的变量所能进行的操作,也都适合于相同数据类型的二维数组元素。
例4.3 参照上面的学生成绩表,输入5个学生3门课程的成绩并计算每个学生的平均分。 分析:可设一个二维数组score[5][3]存放五个人三门课的成绩,再设一个一维数组v[5]存放每个学生的平均分。 #include <stdio.h> void main() { int i,j,s=0,v[5],score[5][3]; for(i=0;i<5;i++) { for(j=0;j<3;j++) { scanf("%d",&score[i][j]); s=s+score[i][j]; }
v[i]=s/3; s=0; } printf("5个人的平均成绩分别为:"); for(i=0;i<5;i++) printf("%4d",v[i]); } 运行结果: 78 89 76↙ 90 78 67↙ 87 87 94↙ 66 77 65↙ 66 96 77↙ 5个人的平均成绩分别为: 81 78 89 69 80
3. 二维数组的初始化 (1)按行分段赋值可写为: int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; (2)按行连续赋值可写为: int a[3][4]={ 1,2,3,4,5,6,7,8,9,10,11,12}; 对于二维数组初始化赋值还有以下说明: (1)可以只对部分元素赋初值,未赋初值的元素自动取0值。 例如: int a[3][3]={{1},{2},{3}}; 是对每一行的第一列元素赋值,未赋值的元素取0值。 赋值后各元素的值为: 1 0 0 2 0 0 3 0 0 (2)如对全部元素赋初值,则第一维的长度可以不给出。 例如: int a[3][3]={1,2,3,4,5,6,7,8,9}; 可以写为: int a[][3]={1,2,3,4,5,6,7,8,9};
4.1.4 字符数组与字符串 1.字符数组的定义 定义格式 char 数组名[长度] 定义一维字符数组 char 数组名[行长][列长] 定义二维字符数组 例如 char c[10]; c[0]=‘I’;c[1]=‘’;c[2]=‘a’;c[3]=‘m’; c[4]=‘’;c[5]=‘a’;c[6]=‘’;c[7]=‘b’; c[8]=‘o’;c[9]=‘y’;
c[0] c[1] c[2] c[3] c[4] c[5] c[6] C h i n a \0 \0 数组c在内存中的存放情况 2.字符数组的初始化 字符数组也允许在定义时作初始化赋值。如: char c[5]={ ’C’,’h’,’i’,’n’,’a’}; 把5个字符分别赋给c[0]到c[4]5个元素。 如果花括号中提供的初值个数大于数组长度,则在编译时系统会提示语法错误。如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余元素由系统自动定义为空字符(即’\0’)。如: char c[7]={ ’C’,’h’,’i’,’n’,’a’}; 则数组在内存中的存放情况如下图所示。
3字符数组的引用 例4.4 字符数组的逐个引用 #include <stdio.h> void main() { char c[12]=“I am a boy”; int i; for(i=0;i<10;i++) printf(“%c”,c[i]); printf(“\n”); } 运行结果: I am a boy
4. 字符串和字符串结束标志 在C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串。前面介绍字符串常量时,已说明字符串总是以’\0’作为串的结束符。因此当把一个字符串存入一个数组时,也把结束符’\0’存入数组,并以此作为该字符串是否结束的标志。有了’\0’标志后,就不必再用字符数组的长度来判断字符串的长度了。 C语言允许用字符串的方式对数组作初始化赋值。 例如: char c[]={"C program"}; 或去掉{}写为: char c[]="C program"; 上面的数组c在内存中的实际存放情况如下图所示:
5.字符数组的输入与输出 • (1)、字符数组的输入 • 输入形式:char c[10]; • scanf(“%s”,c); • 说明: 输入项应使用数组名,不得以“&”开始。 空格、Tab、回车仍将作为字符串分隔符。 • 例如: char str[13]; • scanf(“%s”,str); • 若输入“How are you!”,则只存入“How”。 • 又如:scanf(“%s%s%s”,str1,str2,str3); • 若输入“How are you!”,则“How”、“are”、“you!”分别存入str1、str2、str3中。
(2)、字符数组的输出 • 输出形式 • char c[10]=“China”; • printf(“%s”,c); • 说明: 输出项也应使用数组名。 被输出的字符数组应以“\0”作为结束标记,否则输出错误。 仅输出“\0”前的字符。
6.字符串处理函数 • (1)、字符串输出函数 • 格式:puts(字符串) • 功能:将字符串送终端输出。 • 说明: 字符串可以使用字符串常量、字符数组或字符串函数。 字符串应以“\0”作为结束标志。 • 例如: puts(“Hello”); • puts(str); //其中str为字符数组名
(2)、字符串输入函数 格式:gets(字符数组) 功能:将键盘输入的字符串存入字符数组中。 例如: char str[30]; gets(str); puts(str); (3)、字符串连接函数 • 格式:strcat(字符数组,字符串) • 功能:将字符串连接在字符数组后,并将结果存入字符数组中。 • 例如: char str1[20]=“Good ”; • char str2[5]=“bye!”; • puts(strcat(str1,str2)); • 说明: 字符数组应足够大。 两个字符串均应以“\0”作为结束标志。
(4)、字符串复制函数 • 格式:strcpy(字符数组,字符串) • 功能:将字符串复制到字符数组中。 • 例如: char str1[10]; • strcpy(str1,“Hello”)); • 说明: 字符数组应足够大。 字符串尾部应有“\0”,该标志将一同复制。 本函数不可用赋值语句实现。例如:str1=“Hello”
(5)、字符串比较函数 格式:strcmp(字符串1,字符串2) 功能:将字符串1与字符串2中的字符自左向右按ASCII码值逐个进行比较,直到“\0”或遇不同字符为止。 返回值: 0 ——两串完全相同 正数 ——字符串1>字符串2 负数 ——字符串1<字符串2 例如: char str1[40],str2[40]; gets(str1); gets(str2); if(strcmp(str1,str2)==0) puts(“Yes”); else puts(“No”);
(6)、测试字符串长度函数 格式:strlen(字符串) 功能:返回字符串的有效字符个数。 例如: char str[10]=“China”; printf(“%d”,strlen(str)); 结果为:5
4.2 指 针 指针是C语言中的重要概念,也是C语言的重要特色。使用指针,可以使程序更加简洁、紧凑、高效。 4.2.1 指针和指针变量的概念4.2.2 指针变量的定义与应用 4.2.3 指针变量和 数组 4.2.4 字符串指针变量和字符串 4.2.5 指针数组和多级指针
4.2.1 指针和指针变量概念 C程序中每一个实体,如变量、数组都要在内存中占有一个可标识的存储区域。每一个存储区域由若干个字节组成,在内存中每一个字节都有一个“地址” ,一个存储区域的“地址”指的是该存储区域中第一个字节的地址(或称首地址)。 如执行以下说明后 int a=3; double b=4.9; int *a_pointer=&a; 内存分配情况如右图所示: … 2010 a 3 2014 4.9 b … 3000 a_pointer 2010 … 内存中的a、b、a_pointer
在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。比如,printf(”%d”,a);的执行是这样的:根据变量名与地址的对应关系(这个对应关系是在编译时确定的),找到变量a的地址2010,然后从2010开始的四个字节中取出数据即变量的值3,把它输出。这种通过变量地址存取变量值的方式称为“直接访问”方式。在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。比如,printf(”%d”,a);的执行是这样的:根据变量名与地址的对应关系(这个对应关系是在编译时确定的),找到变量a的地址2010,然后从2010开始的四个字节中取出数据即变量的值3,把它输出。这种通过变量地址存取变量值的方式称为“直接访问”方式。 还有一种“间接访问”方式,把变量a的地址存放在另一个变量a_pointer中, 如果想得到a的值,可以先访问a_pointer,得到a_pointer的值2010,再通过地址2010找到它所指向的存储单元单元的值即数值3。 指针:变量的地址。 指针变量: 用来存放另一个变量的地址(即指针)的特殊变量。
4.2.2 指针变量的定义及其运算 1. 指针变量的定义 其一般形式为: 类型说明符 *变量名; 比如 int *p1; double *p2; 例4.5 分析以下程序的运行结果。 #include <stdio.h> void main() { int *p1; float *p2; double *p3; char *p4; printf("%d,%d,%d,%d\n",sizeof(p1),sizeof(p2),sizeof(p3),sizeof(p4)); } 运行结果:4,4,4,4
2. 指针变量运算符 (1)取地址运算符(&) 在c中,“&”称为取地址运算符,是单目运算符,其功能是 取变量的地址。 例如 p=# 将num的地址存储到p中 (2)指针运算符(*) 在c中,“*”称为指针运算符或者间接访问运算符,是单目运算符,用来表示指针变量所指向的变量
例4.6 下面的程序说明指针变量是如何工作的。 #include <stdio.h> void main() { int a=10; int *p; p=&a; printf("a=%d\n",a); printf("p=%x\n",p); printf("&a=%x\n",&a); printf("*p=%d\n",*p); printf("&p=%x\n",&p); } 运行结果: a=10 p=12ff7c &a=12ff7c *p=15 &p=12ff78
例4.7 分析以下程序的运行结果。 #include <stdio.h> void main() { int a=3,b=5,*pa,*pb,*p; pa=&a;pb=&b; printf("%d,%d,%d,%d\n",a,b,*pa,*pb); p=pa;pa=pb;pb=p; printf("%d,%d,%d,%d\n",a,b,*pa,*pb); } 运行结果: 3,5,3,5 3,5,5,3
pa a pa a &a 3 p &b 3 p pb b &a pb b &b 5 &a 5 pa、pb指针变量指向情况
3. 指针变量的初始化 与其他变量一样,指针变量在定义的同时,也可以对其赋初值,称为指针变量的初始化。 指针变量初始化的一般格式如下: 基类型 *指针变量名=初始地址值; 例如: float x; float *px=&x; 4. 指针变量的运算 可以在指针变量上进行的运算有赋值 、关系和一些受限的算术运算。指针变量的值可赋给同类型的另一指针变量,同类型的指针可以比较是否相等。指针变量可以加上或减去某个整数值,一个指针变量的值也可以减去另一个指针变量的值。
数组a a[0] (&a[0]) a → 1 (&a[1]) a+1→ 21 a[1] (&a[2]) a+2→ 13 a[2] 18 a[3] (&a[3]) a+3→ … … 4.2.3 指针变量和数组 1、数组指针 一个数组通常包含若干个数组元素,在内存中占用连续若干个存储单元。每个元素均有其相应的地址(指针),第一个元素的地址(指针)即数组的起始地址,也称为该数组的指针。数组名可作为该数组的指针。如右图所示
数组a p → a[0] p+1→ a[1] p+2→ a[2] p+3→ a[3] … … 2、数组指针的定义与指向 定义方法与步骤: 定义数组→定义指针变量→指向 例如: ① int a[10]; int *p; p=a;(或p=&a[0];) ② int a[10]={0,1,2,3}; int *p=a;(或p=&a[0];) ③ int a[10],*p=a; 数组指针定义后如右图所示。
例4.8 多种方法访问数组元素 (1) 下标法。 #include <stdio.h> void main() { int a[3]={10,20,30}; int i; for(i=0;i<3;i++) printf("%3d",a[i]); printf("\n"); } 运行结果:10 20 30
(2) 通过数组名计算数组元素地址,找元素的值。 #include <stdio.h> void main() { int a[3]={10,20,30}; int i; for(i=0;i<3;i++) printf("%3d",*(a+i)); printf("\n"); } 运行结果:10 20 30
(3) 通过指针变量计算数组元素地址,找元素的值。 #include <stdio.h> void main() { int a[3]={10,20,30}; int *p=a,i; for(i=0;i<3;i++) printf("%3d",*(p+i)); /*(p+i)也可写成p[i]*/ printf("\n"); } 运行结果:10 20 30
(4) 用指针变量指向数组元素 #include <stdio.h> void main() { int a[3]={10,20,30}; int *p=a; for(p=a;p<a+3;p++) printf("%3d",*p); printf("\n"); } 运行结果:10 20 30
4.2.4 字符串指针变量和字符串 1. 字符串常量的表示 字符串常量是用双引号括起来的字符序列, 例如“Good bye”就是一个字符串常量。 它由8个字符序列组成。 字符串常量的长度是指该字符串中的字符个数。 但在实际存储区域中,C编译器还自动给字符串序列的末尾 加上一个空字符’\0’,以此来标志字符串的结束。 因此,一个字符串常量所占用的存储区域的字节数总比 它的字符个数多一个字节。
p G o o d b y e \0 在C语言中,操作一个字符串常量的方法有: (1)把字符串常量放在一个字符数组中,例如: char s[]="Good bye"; 数组s共有9个元素组成,其中,s[8]的内容是 ’\0’。 (2)用字符指针变量指向字符串, 然后通过字符指针变量来访问字符串存储区域 2. 字符串指针变量的定义和使用 char *p; p="Good bye"; 使p指向字符串常量中的首字符’G’,如下图所示。
例4.12 分析以下程序的运行结果。 #include <stdio.h> void main() { char *str="Good bye"; str=str+3; printf("%s",str); } 运行结果: d bye
例4.13 分析以下程序的运行结果。 #include <stdio.h> void main() { char a[]="Good bye",*str=a; str=str+3; *str='A'; printf("%s\n",str); } 运行结果: A bye
3 字符指针变量与字符数组的比较 虽然用字符串指针变量和字符数组都能实现字符串的存储和处理,但两者是有区别的,不能混为一谈。 (1)存储内容不同:字符指针变量中存储的是字符串的首地址,而字符数组中存储的是字符串本身。 例如,char *pointer,str[100]; 则编译系统给pointer分配4个字节存放它所指向的字符串首地址,给str分配则分配了100个字节来存放字符。 (2)赋值方式不同:对字符指针变量,可采用下面的赋值语句赋值 char *pointer; pointer="This is a example";
而字符数组,虽然可以在声明时初始化, 但不能用赋值语句整体赋值,只能逐个元素地赋值。 下面的用法是非法的: char array[20]; array="This is a example"; (3)指针变量的值是可以改变的,如: 例4.14 改变指针变量的值。 #include <stdio.h> #include <string.h> void main() { char *p="hello world"; p=p+6; puts(p); } 运行结果: world