第 4 章 复杂数据类型

82
为为为为为为为为为为为 为为为为 C 为为为为为为为为为为为4.1 数数 4.2 数数 4.3 数数4.4 数数4.5 数数数数 4.6 数数数数数 typedef 数4数 数数数数数数

Upload: yorick

Post on 13-Jan-2016

177 views

Category:

Documents


0 download

DESCRIPTION

第 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 数组的概念. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 4 章    复杂数据类型

为了解决比较复杂的问题,本章介绍 C 语言提供的复杂数据类型。

4.1 数组4.2 指针4.3 结构体4.4 共用体4.5 枚举类型4.6 类型定义符 typedef

第 4 章 复杂数据类型

Page 2: 第 4 章    复杂数据类型

4.1 数组

4.1.1 数组的概念4.1.2 一维数组4.1.3 二维数组4.1.4 字符维数

Page 3: 第 4 章    复杂数据类型

4.1.1 数组的概念

如果有 30 个互不关联的数据,可以分别把它们存放在 30 个变量中。但是如果这些数据是有内在联系的,是具有相同属性的(如 30个学生的成绩),则可以把这组数据看作一个有机的整体,称为“数组”,用一个统一的名字代表这组数据。例如可用 s 代表学生成绩这组数据, s 就是数组名。一个数组在内存中占一片连续的存储单元,数组 s 在内存中的存放情况如右图所示。

s[0]

s[1]

s[2]

……..

s[29]

数组 s

Page 4: 第 4 章    复杂数据类型

数组名代表一组数据,那么用什么方法来区分这组数据中的个体数据呢?从图 4-1 可以看出, s[0]代表第 1 个学生的成绩、 s[1] 代表第 2 个学生的成绩、…、 s[29] 代表第 30 个学生的成绩。 s 右下脚的数字 0 、1 、…、 29 用来表示数据在数组中的序号,称为下标。数组中的数据称为数组元素。概括地说,数组就是有序数据的集合。要寻找一个数组中的某一个元素必须给出两个要素,即数组名和下标。数组名和下标唯一地标识一个数组中的一个元素。

Page 5: 第 4 章    复杂数据类型

1. 定义一维数组数组同变量一样,也必须先定义、后使用。1 维数组是只有 1 个下标的数组,定义形式如下:数据类型 数组名 [ 常量表达式 ]

比如 float s[30];int tp[31];

( 1 )“数据类型”是指数组元素的数据类型。( 2 )数组名,与变量名一样,必须遵循标识符命名规则。( 3 )“常量表达式”必须用方括号括起来,指的是数组的元素个数(又称数组长度),它是一个整型值,其中可以包含常数和符号常量,但不能包含变量。比如 int n=30; int a[n] 是错误的。( 4 )数组元素的下标从 0 开始。

4.1.2 一维数组定义、引用和初始化

Page 6: 第 4 章    复杂数据类型

2 数组元素的引用

引用数组中的任意一个元素的形式: 数组名 [ 下标表达式 ] 1 .“下标表达式”可以是任何非负整型数据,取值

范围是 0~ (元素个数 -1 )。 特别强调:在运行 C 语言程序过程中,系统并不自

动检验数组元素的下标是否越界。因此在编写程序时,保证数组下标不越界是十分重要的。

2 . 1 个数组元素,实质上就是 1 个变量,它具有和相同类型单个变量一样的属性,可以对它进行赋值和参与各种运算。

3 .在 C 语言中,数组作为 1 个整体,不能参加数据运算,只能对单个的元素进行处理。

Page 7: 第 4 章    复杂数据类型

例 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

Page 8: 第 4 章    复杂数据类型

例 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]; }

Page 9: 第 4 章    复杂数据类型

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 6

1平均分为: 73高于平均分的成绩为 :80 78 76 98

87

Page 10: 第 4 章    复杂数据类型

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};

Page 11: 第 4 章    复杂数据类型

如果说一维数组在逻辑上可以想象成一行长表或矢量,那么二维数组在逻辑上可以想象成是由若干行、若干列组成的表格或矩阵。例如以下 5 名学生的成绩表格就形成了一个二维数组。

数学 语文 英语

王海 78 89 76

张刚 90 78 67

李玉 87 87 94

丁一 66 77 65

赵兵 68 96 77

4.1.3 二维数组的定义、引用和初始化化

Page 12: 第 4 章    复杂数据类型

上表中的每一行表示了某个学生的三门课程的成绩。如果用一个 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列,每个元素都具有两个下标用来表示该元素在数组中的位置。第一个下标表示元素的行号,第二个下标表示元素的列号。因此,可以通过指定行号和列号来对不同的数组元素进行存取和引用。

Page 13: 第 4 章    复杂数据类型

1. 二维数组的定义 二维数组定义的一般形式是: 类型说明符 数组名 [ 常量表达式 1][ 常量表达式 2] 其中,“类型说明符”是指数组的数据类型,也就是每个数

组元素的类型。“常量表达式 1” 指出数组的行数,“常量表达式 2” 指出数组的列数,它们必须都是正整数。

例如 int score[5][3]; 定义了一个 5 行 3列的二维数组 score, 用于存储上面提到的 5

个人 3 门课程的成绩。 虽然在逻辑上可以把二维数组看作是一张表格,但在计算

机内部,二维数组的所有元素都是一个接着一个排列的。 C编译程序按行主顺序存放数组元素;也就是说先放第一行上的元素,接着放第二行上的元素,依次把各行的元素放入一串连续的存储单元中。数组 score 在内存中的排列顺序如下图所示。

Page 14: 第 4 章    复杂数据类型

score[0][0]

二维数组 score 在内存存放情况

score[0][1]

score[0][2]

score[1][0]

score[1][1]

score[1][2]

score[4][0]

score[4][1]

score[4][2]

Page 15: 第 4 章    复杂数据类型

2. 二维数组元素的引用

引用二维数组元素的形式为:

数组名 [ 行下标表达式 ][列下标表达式 ]

1 .“行下标表达式”和“列下标表达式”,都应是整型表达式或符号常量。

2 .“行下标表达式”和“列下标表达式”的值,都应在已定义数组大小的范围内。假设有数组 score[5][3] ,则可用的行下标范围为 0~4 ,列下标范围为 0~2 。

3 .对基本数据类型的变量所能进行的操作,也都适合于相同数据类型的二维数组元素。

Page 16: 第 4 章    复杂数据类型

例 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];

}

Page 17: 第 4 章    复杂数据类型

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

Page 18: 第 4 章    复杂数据类型

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};

Page 19: 第 4 章    复杂数据类型

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’;

Page 20: 第 4 章    复杂数据类型

字符数组也允许在定义时作初始化赋值。如 :char c[5]={ ’C’,’h’,’i’,’n’,’a’};把 5 个字符分别赋给 c[0]到 c[4]5 个元素。如果花括号中提供的初值个数大于数组长度,则在编译时系统会提示语法错误。如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余元素由系统自动定义为空字符 ( 即’ \0’) 。如:char c[7]={ ’C’,’h’,’i’,’n’,’a’};则数组在内存中的存放情况如下图所示。

2.字符数组的初始化

\0C h i n a

c[0] c[1] c[2] c[3] c[4]

数组 c 在内存中的存放情况

\0

c[5] c[6]

Page 21: 第 4 章    复杂数据类型

例 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”);

}

3字符数组的引用

运行结果:I am a boy

Page 22: 第 4 章    复杂数据类型

4. 字符串和字符串结束标志在C语言中没有专门的字符串变量,通常用一个字符数

组来存放一个字符串。前面介绍字符串常量时,已说明字符串总是以’ \0’ 作为串的结束符。因此当把一个字符串存入一个数组时,也把结束符’ \0’ 存入数组,并以此作为该字符串是否结束的标志。有了’ \0’ 标志后,就不必再用字符数组的长度来判断字符串的长度了。C语言允许用字符串的方式对数组作初始化赋值。例如: char c[]={"C program"}; 或去掉 {} 写为: char c[]="C program";

上面的数组 c 在内存中的实际存放情况如下图所示:

C p r o g r a m \0

Page 23: 第 4 章    复杂数据类型

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中。

Page 24: 第 4 章    复杂数据类型

( 2)、字符数组的输出

输出形式char c[10]=“China”;printf(“%s”,c);

说明 :输出项也应使用数组名。被输出的字符数组应以“ \0” 作为结束标记,否则

输出错误。仅输出“ \0” 前的字符。

Page 25: 第 4 章    复杂数据类型

6.字符串处理函数

( 1)、字符串输出函数格式: puts(字符串 )功能:将字符串送终端输出。说明 :

字符串可以使用字符串常量、字符数组或字符串函数。字符串应以“ \0” 作为结束标志。

例如: puts(“Hello”);puts(str); //其中 str为字符数组

Page 26: 第 4 章    复杂数据类型

( 2)、字符串输入函数

格式: gets(字符数组 )功能:将键盘输入的字符串存入字符数组中。例如 :

char str[30];gets(str);puts(str);

(3)、字符串连接函数

格式: strcat(字符数组 ,字符串 )功能:将字符串连接在字符数组后,并将结果存入字符数组中。

例如: char str1[20]=“Good ”;char str2[5]=“bye!”;puts(strcat(str1,str2));

说明 :字符数组应足够大。两个字符串均应以“ \0” 作为结束标志。

Page 27: 第 4 章    复杂数据类型

(4)、字符串复制函数

格式: strcpy(字符数组 ,字符串 )功能:将字符串复制到字符数组中。

例如: char str1[10];strcpy(str1,“Hello”));

说明 :字符数组应足够大。字符串尾部应有“ \0” ,该标志将一同复制。本函数不可用赋值语句实现。例如: str1=“Hello”

Page 28: 第 4 章    复杂数据类型

(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”);

Page 29: 第 4 章    复杂数据类型

(6)、测试字符串长度函数

格式: strlen(字符串 )功能:返回字符串的有效字符个数。例如 :

char str[10]=“China”;printf(“%d”,strlen(str));

结果为: 5

Page 30: 第 4 章    复杂数据类型

4.2 指 针指针是C语言中的重要概念,也是C语言的重要特色。使

用指针,可以使程序更加简洁、紧凑、高效。 4.2.1 指针和指针变量的概念

4.2.2 指针变量的定义与应用 4.2.3 指针变量和 数组4.2.4 字符串指针变量和字符串4.2.5 指针数组和多级指针

Page 31: 第 4 章    复杂数据类型

4.2.1 指针和指针变量概念

C 程序中每一个实体,如变量、数组都要在内存中占有一个可标识的存储区域。每一个存储区域由若干个字节组成,在内存中每一个字节都有一个“地址” ,一个存储区域的“地址”指的是该存储区域中第一个字节的地址(或称首地址)。

如执行以下说明后int a=3;double b=4.9;int *a_pointer=&a;内存分配情况如右图所示:

a

b

a_pointer

32010

20144.9

3000 2010

内存中的 a 、 b 、 a_pointer

……

Page 32: 第 4 章    复杂数据类型

在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。比如, printf(”%d”,a); 的执行是这样的:根据变量名与地址的对应关系(这个对应关系是在编译时确定的),找到变量 a 的地址 2010 ,然后从 2010 开始的四个字节中取出数据即变量的值 3 ,把它输出。这种通过变量地址存取变量值的方式称为“直接访问”方式。 还有一种“间接访问”方式,把变量 a 的地址存放在另一个变量 a_pointer 中 , 如果想得到 a 的值,可以先访问 a_pointer,得到 a_pointer 的值 2010 ,再通过地址 2010 找到它所指向的存储单元单元的值即数值 3 。

指针:变量的地址。

指针变量:用来存放另一个变量的地址(即指针)的特殊变量。

Page 33: 第 4 章    复杂数据类型

4.2.2 指针变量的定义及其运算

其一般形式为:类型说明符 *变量名;比如 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(p

4));}运行结果: 4,4,4,4

1. 指针变量的定义

Page 34: 第 4 章    复杂数据类型

2. 指针变量运算符( 1 )取地址运算符

( & )在 c 中,“ &” 称为取地址运算符,是单目运算符,其功能是取变量的地址。

例如 p=&num;将 num 的地址存储到 p 中

( 2 )指针运算符( * )在 c 中,“ *” 称为指针运算符或者间接访问运算符,是单目运算符,用来表示指针变量所指向的变量

Page 35: 第 4 章    复杂数据类型

例 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=10p=12ff7c&a=12ff7c*p=15&p=12ff78

Page 36: 第 4 章    复杂数据类型

例 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,*p

b);p=pa;pa=pb;pb=p;printf("%d,%d,%d,%d\n",a,b,*pa,*p

b);}运行结果:3, 5, 3, 53, 5, 5, 3

Page 37: 第 4 章    复杂数据类型

p&a

pa

3

a

&b 5

pb b &a

p&b

pa

3

a

&a 5

pb b

pa 、 pb 指针变量指向情况

Page 38: 第 4 章    复杂数据类型

3. 指针变量的初始化与其他变量一样,指针变量在定义的同时,也可以对其赋初值,称为指针变量的初始化。指针变量初始化的一般格式如下:基类型 * 指针变量名 = 初始地址值;例如:float x;float *px=&x;

4. 指针变量的运算可以在指针变量上进行的运算有赋值 、关系和一些受限的

算术运算。指针变量的值可赋给同类型的另一指针变量,同类型的指针可以比较是否相等。指针变量可以加上或减去某个整数值,一个指针变量的值也可以减去另一个指针变量的值。

Page 39: 第 4 章    复杂数据类型

4.2.3 指针变量和数组

1、数组指针

一个数组通常包含若干个数组元素,在内存中占用连续若干个存储单元。每个元素均有其相应的地址(指针),第一个元素的地址(指针)即数组的起始地址,也称为该数组的指针。数组名可作为该数组的指针。如右图所示

数组 a

a[0]

a[1]

a[2]

a[3]

1

21

13

18

(&a[0]) a →

(&a[1]) a+1→(&a[2]) a+2→

(&a[3]) a+3→

Page 40: 第 4 章    复杂数据类型

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;数组指针定义后如右图所示。

p+1→p+2→

数组 a

a[0]

a[1]

a[2]

a[3]

…p →

p+3→

Page 41: 第 4 章    复杂数据类型

例 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

Page 42: 第 4 章    复杂数据类型

#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");

}

( 2 ) 通过数组名计算数组元素地址,找元素的值。

运行结果: 10 20 30

Page 43: 第 4 章    复杂数据类型

( 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

Page 44: 第 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");

}

( 4 ) 用指针变量指向数组元素

运行结果: 10 20 30

Page 45: 第 4 章    复杂数据类型

4.2.4 字符串指针变量和字符串 1. 字符串常量的表示字符串常量是用双引号括起来的字符序列,例如“ Good bye” 就是一个字符串常量。它由 8 个字符序列组成。字符串常量的长度是指该字符串中的字符个数。但在实际存储区域中, C 编译器还自动给字符串序列的末尾

加上一个空字符’ \0’, 以此来标志字符串的结束。因此,一个字符串常量所占用的存储区域的字节数总比它的字符个数多一个字节。

Page 46: 第 4 章    复杂数据类型

在 C 语言中,操作一个字符串常量的方法有:( 1 )把字符串常量放在一个字符数组中,例如: char s[]="Good bye";数组 s共有 9 个元素组成,其中, s[8] 的内容是 ’ \0’ 。( 2 )用字符指针变量指向字符串,然后通过字符指针变量来访问字符串存储区域

2. 字符串指针变量的定义和使用char *p;p="Good bye";使 p 指向字符串常量中的首字符’ G’, 如下图所示。

p

bG o o d y e \0

Page 47: 第 4 章    复杂数据类型

例 4.12 分析以下程序的运行结果。#include <stdio.h>void main(){

char *str="Good bye";str=str+3;printf("%s",str);

}

运行结果:d bye

Page 48: 第 4 章    复杂数据类型

例 4.13 分析以下程序的运行结果。#include <stdio.h>void main(){

char a[]="Good bye",*str=a;str=str+3;

*str='A'; printf("%s\n",str);

}运行结果:A bye

Page 49: 第 4 章    复杂数据类型

3 字符指针变量与字符数组的比较

虽然用字符串指针变量和字符数组都能实现字符串的存储和处理,但两者是有区别的,不能混为一谈。

( 1 )存储内容不同:字符指针变量中存储的是字符串的首地址,而字符数组中存储的是字符串本身。

例如, char *pointer,str[100];

则编译系统给 pointer 分配 4 个字节存放它所指向的字符串首地址,给 str 分配则分配了 100 个字节来存放字符。

( 2 )赋值方式不同:对字符指针变量,可采用下面的赋值语句赋值

char *pointer;

pointer="This is a example";

Page 50: 第 4 章    复杂数据类型

而字符数组,虽然可以在声明时初始化,但不能用赋值语句整体赋值,只能逐个元素地赋值。下面的用法是非法的: 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

Page 51: 第 4 章    复杂数据类型

说明:指针变量 p 可以变化,输出字符串时从 p当时指向的

单元开始输出各个字符,直到遇’ \0’ 为止。而数组名则代表

数组的起始地址,是一个常量,常量是不能被改变的。下面是错误的:char p[]="hello world";p=p+6;puts(p);

Page 52: 第 4 章    复杂数据类型

4.2.5 指针数组与多级指针

1 .指针数组 一个数组的每个元素都是指针,则称该数组为指针数组。 例如

int *p[3];

该语句中 p 与“ [ ]” 结合,说明 p 是一个由 3 个元素组成的一维数组, p 前面的“ *” 则说明 p 数组中的每个元素均为一个指向整型数据的指针。

Page 53: 第 4 章    复杂数据类型

指针数组的最常见应用是对字符串的处理,即利用字符型指针数组完成一系列的有关字符串的处理。例如下面的语句定义了一个字符型指针数组,且同时对其进行了初始化:

char *p[3]={ "Basic","Pascal","C++"}; 此时, p 数组中的元素 p[0] 、 p[1] 、 p[2] 分别指向了三个字符串,它们之间的关系如下图所示。

B a s i c \0

P a s c a l \0

C + + \0

p[0]

p[1]

p[2]

Page 54: 第 4 章    复杂数据类型

2.多级指针变量在 C 中,除了允许指针变量指向普通变量外,还允许指针变量

指向另外的指针变量,这种指向指针变量的指针变量称为多级指针变量。如二级指针变量的定义格式如下:基类型 ** 指针变量名;

地址 值

指针变量 变量

地址 1 地址 2

指针变量 1 指针变量 2

变量

二级指针变量

Page 55: 第 4 章    复杂数据类型

例 4.15 二级指针基本应用。#include <stdio.h>void main(){ int **k,*a,b=100; a=&b;k=&a; printf("%d\n",**k);}运行结果: 100

Page 56: 第 4 章    复杂数据类型

4.3 结构体前面我们介绍了用户自己声明的构造类型 ----- 数组,

数组中的各元素是属于同一个类型的。但是在处理任务时只使用数组是不够的。有时需要将不同类型的数据组合成一个有机的整体,以供用户方便使用。例如,一个员工的姓名、性别、年龄、工资等项,都是这个员工的属性,见下图。

name sex age salary

mary F 30 1800

员工信息

Page 57: 第 4 章    复杂数据类型

4.3.1 结构体定义 4.3.2 结构体变量定义

4.3.3 结构体变量引用4.3.4 结构体变量赋值和初始化4.3.5 结构体数组

4.3.6 结构体数指针变量

Page 58: 第 4 章    复杂数据类型

4.3.1 结构体类型定义struct 结构类型名 { 数据类型 数据项 1; 数据类型 数据项 2; …… …… 数据类型 数据项n ; };

例如,下面的语句定义了一个反映员工基本情况的结构体类型struct employee{ char name[15]; /*姓名 */ char sex; /* 性别 */ int age; /*年龄 */ float salary; /*工资 */};

Page 59: 第 4 章    复杂数据类型

4.3.2 结构变量定义

定义结构变量有以下三种方法。1. 先定义结构体,再定义结构体变量在已经定义好结构体类型后,再定义结构体变量的一般格式如

下 :struct 结构体名 结构体变量名表;如: struct employee emp1,emp2;

上面的语句定义了两个 employee 结构体变量 emp1 和 emp2 。在定义了结构体变量后,系统会为之分配内存单元。例如 emp1 和 emp2 在内存中各占 24 字节( 15+1+4+4 )。为这两个变量分配的存储空间情况参见下图:

Page 60: 第 4 章    复杂数据类型

name

sex

age

salary

emp1

name

sex

age

salary

emp2

emp1 和 emp2 在内存存放情况

Page 61: 第 4 章    复杂数据类型

2. 在定义结构类型的同时定义结构体变量这种形式的说明的一般形式为:struct 结构体名{成员表列} 变量名表列 ;例如,以下语句在定义结构体类型 employee 的同

时定义了结构体变量 emp1 和 emp2 。struct employee{ char name[15]; /*姓名 */ char sex; /* 性别 */ int age; /*年龄 */ float salary; /*工资 */}emp1,emp2;

Page 62: 第 4 章    复杂数据类型

3.直接说明结构体变量这种形式的说明的一般形式为:struct { 成员表列 } 变量名表列 ;例如:struct { char name[15]; /*姓名 */ char sex; /* 性别 */ int age; /*年龄 */ float salary; /*工资 */}emp1,emp2;第三种方法与第二种方法的区别在于第三种方法中省去

了结构体名,而直接给出结构体变量。

Page 63: 第 4 章    复杂数据类型

在上述 employee 结构体定义中,所有的成员都是基本数据

类型或数组类型。成员也可以又是一个结构体,即构成了嵌套的结构体。例如 :struct Date{ int year,month,day; /*年,月,日 */}struct Teacher{ char name[8]; /*姓名 */ struct Date birthday; /* 出生日期 */ char depart[20l /*工作部门 */}teacher1,teacher2;

Page 64: 第 4 章    复杂数据类型

4.3.3 结构体变量的引用在定义了结构体变量以后,当然可以引用这个变量。1. 可以将一个结构体变量的值赋给另一个具有相同结构的结构体变

量。如上面的 emp1 和 emp2都是 employee 型的变量,可以这样赋值:

emp2=emp1; 此赋值语句把 emp1 的所有成员的值整体赋予 emp2 。2. 一般对结构体变量的使用,都是通过结构体变量的成员来实现的。引用结构体变量成员的一般形式是: 结构体变量名 . 成员名例如: emp1.salary 即第一个人的工资emp2.salary 即第二个人的工资3. 如果成员本身又是一个结构体则必须逐级找到最低级的成员才能

使用。例如对上面定义的结构体变量 teacher1, 可以这样访问其 month 成员:

teacher1.birthday.month

Page 65: 第 4 章    复杂数据类型

4.3.4 结构体变量的赋值和初始化1. 赋值例 4.16 给结构体变量赋值并输出其值。#include <stdio.h>void main(){struct employee{

char name[15]; char sex; int age; float salary; }emp1;

emp1.age=35; emp1.salary=1200; printf("input name and sex:\n"); scanf("%s %c",&emp1.name,&emp1.sex); printf("name=%s\nsex=%c\n",emp1.name,emp1.sex); printf("age=%d\nsalary=%7.2f\n",emp1.age,emp1.salary);}

Page 66: 第 4 章    复杂数据类型

运行结果:input name and sex:

Zhang gang M name=Zhang gang

sex=M

age=35

salary=1200.00

Page 67: 第 4 章    复杂数据类型

2. 结构体变量的初始化和其他类型变量一样,对结构体变量可以在定义时进行初始化赋值。例 4.17 对结构体变量初始化。#include <stdio.h>void main(){

struct employee { char name[15]; char sex; int age;

float salary; }emp1={"Zhang ping",'M',35,2000};

printf("name=%s\nsex=%c\n",emp1.name,emp1.sex); printf("age=%d\nsalary=%7.2f\n",emp1.age,emp1.salary);}

Page 68: 第 4 章    复杂数据类型

运行结果:name=Zhang ping

sex=M

age=35

salary=2000.00

Page 69: 第 4 章    复杂数据类型

数组的元素也可以是结构体类型的。因此可以构成结构体数组。结构体数组的每一个元素都是具有相同结构体类型的下标结构体变量。在实际应用中,经常用结构体数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。方法和结构体变量相似,只需说明它为数组类型即可。

例如:struct employee emp[3];定义了一个结构体数组 emp ,共有 3 个元素, em

p[0]~ emp[2] 。每个数组元素都具有 struct employee 的结构形式。

4.3.5 结构体数组的定义

Page 70: 第 4 章    复杂数据类型

例 4.18 计算员工的工资总和。#include <stdio.h>void main(){

struct employee { char name[15]; char sex; int age; float salary;

}emp[3]={ {"Li ping",'M',45,1000}, {"Zhang ping",'M',50,3000}, {"He fang",'F',35,1800},

}; int i; float s=0; for(i=0;i<3;i++) s+=emp[i].salary; printf("s=%7.2f\n",s);}运行结果: s=5800.00

Page 71: 第 4 章    复杂数据类型

4.3.6 结构体指针变量的说明和使用

结构体指针变量说明的一般形式为: struct 结构体名 * 结构体指针变量名例如, struct employee *pemp;

pemp=&emp1

使 pemp 指向 emp1.

其访问的一般形式为: (* 结构体指针变量 ). 成员名或为: 结构体指针变量 -> 成员名例如:(*pemp).salary

或者: pemp->salary

Page 72: 第 4 章    复杂数据类型

例 4.19 通过指针变量访问结构体。#include <stdio.h>

void main()

{

struct employee

{

char name[15];

char sex;

int age;

float salary;

}emp1={"Zhang ping",'M',35,2000};

struct employee *pemp;

pemp=&emp1;

printf("Name=%s,salary=%7.2f\n",(*pemp).name,(*pemp).salary);

printf("Name=%s,salary=%7.2f\n",pemp->name,pemp->salary);

}

Page 73: 第 4 章    复杂数据类型

运行结果:Name=Zhang ping,salary=2000.

00Name=Zhang ping,salary=2000.

00

Page 74: 第 4 章    复杂数据类型

4.4 共用体

4.4.1 共用体概念 4.4.2共用体变量的访问方式

4.4.3共用体结类型数据的特点

Page 75: 第 4 章    复杂数据类型

有时需要使几种不同类型的变量存放到同一段内存单元中。例如,可以把一个整型变量和一个双精度变量放在同一个地址开始的内存单元中(见下图 )

整型变量i双精度 变 量d

i 和 d共用一段内存

2000 地址

以上 2 个变量在内存中占用的字节数不同,但都从同一地址开始存放。也就是使用覆盖技术,几个变量互相覆盖。这种使几个不同的变量共占同一段内存的结构,称为共用体类型的结构。

4.4.1 共用体的概念

Page 76: 第 4 章    复杂数据类型

定义一个共用体类型的一般形式为: union 共用体名 { 数据类型 成员名 1;数据类型 成员名 2;……数据类型 成员名 n;};定义共用体变量的一般形式为共用体类型名 共用体变量名;当然也可以在定义共用体类型的同时定义共用体变量,也可以没有共用体类

型名而直接定义共用体变量。

union data{ int i; double d;}union data a,b,c;(先定义再说明)

union data{ int i; double d;} a,b,c;

(定义同时说明)

union { int i; double d;} a,b,c;

(直接说明)

Page 77: 第 4 章    复杂数据类型

不能引用共用体变量,而只能引用共用体变量的成员。例如,下面的引用是正确的:

a.i( 引用成员 i)

a.d( 引用成员 d)

4.4.3 共用体类型数据的特点1. 使用共用体变量的目的是希望用同一个内存段存放几种不

同类型的数据。但请注意:在每一瞬间只能存放其中一种,而不能同时存放几种。换句话说,每一瞬间只有一个成员起作用,其它成员不起作用。

2. 能够访问的是共用体变量中最后一次被赋值的成员,在对一个新的成员赋值后原有的成员就失去作用。

3. 共用体变量的地址和它的成员的地址都是同一地址。例如:&a,&a.i,&a.d都具有同一地址值,其原因是显然的。

4.4.2 对共用体变量的访问方式

Page 78: 第 4 章    复杂数据类型

4.5 枚举类型

4.5.1 枚举类型的定义和枚举变量的说明 4.5.2 枚举变量的赋值和使用

在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,表示一年四季的 spring,summer,autumn,winter 。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。

Page 79: 第 4 章    复杂数据类型

枚举的定义枚举类型定义的一般形式为: enum 枚举名 { 枚举值表 };

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

例如: enum season{ spring,summer,autumn,winter};

该枚举名为 season ,枚举值共有 4 个,即一年的四季。

4.5.1 枚举类型的定义和枚举变量的说明

Page 80: 第 4 章    复杂数据类型

枚举类型在使用中有以下规定:枚举元素本身由系统定义了一个表示序号的数值,从 0开始顺序

定义为 0, 1, 2… 。如在 season中, spring值为 0, summer值为 1, autun值为 2,winter值为 3。

例 4.21 枚举变量基本应用。 #include <stdio.h>void main(){enum season{ spring,summer,autumn,winter} a,b,c;

a=spring; b=summer; c=winter; printf("%d,%d,%d",a,b,c);} 运行结果: 0,1,3

4.5.2 枚举类型变量的赋值和使用

Page 81: 第 4 章    复杂数据类型

4.6 类型定义符 typedef

C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符 typedef即可用来完成此功能。例如,有整型量 a,b, 其说明如下:

int a,b;

其中 int 是整型变量的类型说明符。 int 的完整写法为 integer ,为了增加程序的可读性,可把整型说明符用 typedef 定义为:

typedef int INTEGER

这以后就可用 INTEGER 来代替 int 作整型变量的类型说明了。 例如: INTEGER a,b;

它等效于: int a,b;

用 typedef 定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。

Page 82: 第 4 章    复杂数据类型

例如: typedef char NAME[20]; 表示 NAME 是字符数组类型,数组长度为 20 。然后可用 NAME 说明变量,如:

NAME a1,a2,s1,s2;

完全等效于: char a1[20],a2[20],s1[20],s2[20]

又如: typedef struct stu

{ char name[20];

int age;

char sex;

} STU;

定义 STU 表示 stu 的结构类型,然后可用 STU 来说明结构变量:STU body1,body2;

typedef 定义的一般形式为:typedef 原类型名 新类型名 其中原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。