14.结构与其他数据形式

一、结构基础

1.结构声明与变量

结构声明(structure  declaration):描述一个结构的组织布局;

struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
};

在结构声明中,用一对花括号括起来的是结构成员列表。每个成员都用自己的声明来描述;

右花括号后面的分号是声明所必需的,表示结构布局定义结束;

创建结构变量:编译器使用book模板为一个结构变量library分配空间;

struct book library;

在结构变量的声明中,struct book所起的作用相当于一般声明中的int或 float;

以下为声明简化形式:

//第一种类型:
struct book {
char title[MAXTITL];
char author[AXAUTL];
float value;
} library; /* 声明的右右花括号后跟变量名*/

//第二种类型
struct { /* 无结构标记 */
char title[MAXTITL];
char author[MAXAUTL];
float value;
} library;/* 声明的右右花括号后跟变量名*/

2.结构初始化

1)一般初始化:

使用在一对花括号中括起来的初始化列表进行初始化, 各初始化项用逗号分隔;

tips:初始化与类别存储期:如果初始化一个静态存储期的结构,初始化列表中的值必须是常量表达式。如果是自动存储期,初始化列表中的值可以不是常量。

2)指定初始化器

C99和C11提供指定初始化器(designated initializer),使用点运算符和成员名标识特定元素

只初始化book结构某成员:

struct book surprise = { .value = 10.99};

任意顺序使用指定初始化器;

注意:对特定成员最后一次赋值才是它实际获得的值,此时value的值是0.25.

struct book gift= {.value = 18.90,
.author = "Philionna Pestle",
0.25};

3)其他结构特性

A.允许把一个结构赋值给另一个结构(内含数组成员可以完成赋值),但是数组不能这样做;

B.把一个结构初始化为相同类型的另一 个结构:

如果n_data和o_data都是相同类型的结构,可以这样做:
o_data = n_data; // 把一个结构赋值给另一个结构

struct names right_field = {"Ruthie", "George"};
struct names captain = right_field; // 把一个结构初始化为另一个结构

3.访问结构成员

使用结构成员运算 符——点(.)访问结构中的成员。

注意,虽然library是一个结构,但是library.value是一个float类型的变 量,可以像使用其他float类型变量那样使用它。

例如使用,scanf(“%f”,.....),需要一个float的地址,即,&library.value,

注意,&比.的优先级低,所以上述表达式和&(library.value)一样。

二、结构与数组

1.结构数组声明

把library声明为一个内含MAXBKS个元素的数组,数组的每个元素都是一个book类型的数组;

struct book library[MAXBKS];

其中数组名library本身不是结构名,它是一个数组名,该数组中的每个元素都是struct book类型的结构变量;

2.标识结构数组的成员

采用访问单独结构的规则:在结构名后面加一个点运算符,再在点运算符后面写上成员名。

library[0].value /* 第1个数组元素与value 相关联 */
library[4].title /* 第5个数组元素与title 相关联 */

注意,点运算符右侧的下标作用于各个成员,点运算符左侧的下标作用与结构数组。

library // 一个book 结构的数组
library[2] // 一个数组元素,该元素是book结构
library[2].title // 一个char数组(library[2]的title成员)
library[2].title[4] // 数组中library[2]元素的title 成员的一个字符

即,library[2].title[4] 是指数组;library[2]元素title成员中的第5个字符

3.嵌套结构

如何在结构声明中创建嵌套结构:

struct names { // 第1个结构
char first[LEN];
char last[LEN];
1027
};
struct guy { // 第2个结构
struct names handle; // 嵌套结构
char favfood[LEN];
char job[LEN];
float income;
};

注意,访问嵌套结构的成员,需要使用两次点运算符

printf("Hello, %s!\n", fellow.handle.first);

三、结构与指针

1.声明和初始化结构指针

首先是关键字 struct,其次是结构标记 guy,然后是一个星号(*),其 后跟着指针名。

struct guy * him;

现在指针him现在可以指向任意现有的 guy类型的结构;

假设barney是一个guy类型的结构,就有

him = &barney;

注意,和数组不同的是,结构名并不是结构的地址,因此要在结构名前面加上 &运算符。

上例中,library是一个结构数组,这意味library[0]是一个结构,当she指向library[0],就有

struct book  *she;

she = &library[0];

注意,she指向library[0],she+ 1指向library[1];

2.指针访问成员

1)->运算符

如果him == &barney,那么him->income 即是 barney.income

如果she == &library[0],那么she->value 即是 library[0].ivalue

tips:->运算符后面的结构指针和.运算符后面的结构名工作方式相同,但是(不能写成him.incone,因为him不是结构名)

2)*与&运算符

 如果she == &library[0], 那么*she == library[0]

就有,library[0].value == (*she).value,必须要使用圆括号,因为.运算符比*运算符的优先级高。

四、函数与结构体之间传递

1.传递结构成员

结构成员是一个具有单个值的数据类型

即,int及其相关类型、 char、float、double或指针,便可把它作为参数传递给接受该特定类型的函数。

struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
1036
};

主函数使用sum()函数:

double sum(double, double);
int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
4032.27,
"Lucky's Savings and Loan",
8543.94
};
printf("Stan has a total of $%.2f.\n",
sum(stan.bankfund, stan.savefund));
return 0;
}

2.传递结构的地址

如果需要在被调函数中修改主调函数中成员的值,就要传递成员的地址:

注意,sum函数参数的变化

double sum(const struct funds *); /* 参数是一个指针 */
int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
4032.27,
"Lucky's Savings and Loan",
8543.94
};
printf("Stan has a total of $%.2f.\n", sum(&stan));
return 0;
}

3.传递结构

传递结构作为函数的参数

double sum(struct funds moolah); /* 参数是一个结构 */
int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
4032.27,
"Lucky's Savings and Loan",
8543.94
};
printf("Stan has a total of $%.2f.\n", sum(stan));
return 0;
}

结构也可以作为函数返回参数

//返回参数是结构
struct funds getinfo(void);

//结构的双向通信
struct funds makeinfo(struct funds);

4.结构指针的双向通信

使用指向结构的指针

struct namect {
char fname[NLEN];
char lname[NLEN];
int letters;
};

makeinfo()函数使用双向传输方式传送信息。

void makeinfo(struct namect *);
int main(void)
{
struct namect person;
makeinfo(&person);
return 0;
}

void makeinfo(struct namect * pst)
{
pst->letters = strlen(pst->fname) +strlen(pst->lname);
}

注意:指针作为参数有两个优点:无论是以前还是现在的C实现都能使用这 种方法,而且执行起来很快,只需要传递一个地址。缺点是无法保护数据。

把结构作为参数传递的优点是,函数处理的是原始数据的副本,这保护 了原始数据。

5.结构数组与函数

由于数组名就是该数组的地址,所以可以把它传递给函数。

#define FUNDLEN 50
#define N 2
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};

数组名jones是该数组的地址,即该数组首元素(jones[0])的地址,所以有money = &jones[0];

double sum(const struct funds money [], int n);
int main(void)
{
struct funds jones[N] = {
{
"Garlic-Melon Bank",
4032.27,
"Lucky's Savings and Loan",
1069
8543.94
},
{
"Honest Jack's Bank",
3620.88,
"Party Time Savings",
3802.91
}
};
printf("The Joneses have a total of $%.2f.\n",sum(jones, N));
return 0;
}

下面是几个要点:

可以把数组名作为数组中第1个结构的地址传递给可以用数组表示法访问数组中的其他结构函数;

可以用数组表示法访问数组中的其他结构;

注意,下面的函数调用与 使用数组名效果相同: sum(&jones[0], N)

6.结构、指针和malloc()

使用malloc()分配内存并使用指针储存该地址

struct namect {
char * fname; // 用指针代替数组
char * lname;
int letters;
};

void getinfo (struct namect * pst)
{
char temp[SLEN];
// 分配内存储存名
pst->fname = (char *) malloc(strlen(temp) + 1);
}

应该成对使用malloc()和free()。

free(pst->fname);

五、复合字面量、伸缩型数组、匿名结构

1.复合字面量

C99 的复合字面量特性可用于结构和数组。语法是把类型名放在圆括号中,后面紧跟一个用花括号括起来的初始化列表。

例如,下面是struct book类型的复合字面量:

(struct book) {"The Idiot", "Fyodor Dostoyevsky", 6.99}

复合字面量作为函数的参数:

struct rect {double x; double y;};
double rect_area(struct rect r){return r.x * r.y;}
......
double area;
area = rect_area( (struct rect) {10.5, 20.0});

复合字面量传递函数地址:

struct rect {double x; double y;};
double rect_areap(struct rect * rp){return rp->x * rp->y;}
...
double area;

area = rect_areap( &(struct rect) {10.5, 20.0});

2.伸缩型数组成员

C99新增了一个特性:伸缩型数组成员(flexible array member),利用这项特性声明的结构,其最后一个数组成员具有一些特性;

第1个特性是, 该数组不会立即存在;第2个特性是,使用这个伸缩型数组成员可以编写合适的代码

声明一个伸缩型数组成员有如下规则:

伸缩型数组成员必须是结构的最后一个成员;

结构中必须至少有一个成员;

伸缩数组的声明类似于普通数组,只是它的方括号中是空的;

struct flex
{
int count;
double average;
double scores[]; // 伸缩型数组成员
}

声明一个struct flex类型的结构变量时,不能用scores做任何事,因为没 有给这个数组预留存储空间;

C99的意图并不是让你声明struct flex 类型的变量,而是希望你声明一个指向struct flex类型的指针,然后用 malloc()来分配足够的空间;

例如,假设用scores表示一个内含5个double类型值的数组,可以这样做:

pf = malloc(sizeof(struct flex) + 5 * sizeof(double));

带伸缩型数组成员的结构确实有一些特殊的处理要求;

不能用结构进行赋值或拷贝:确实要进行拷贝, 应使用memcpy()函数;

不要以按值方式把这种结构传递给结构:要把结构的地址传递给函数。

不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员。

3.匿名结构

匿名结构是一个没有名称的结构成员;

可以用嵌套的匿名成员结构定义:

struct person
{
int id;
struct {char first[20]; char last[20];}; // 匿名结构
};

六、联合简介

联合(union)是一种数据类型,它能在同一个内存空间中储存不同的数据类型(不是同时储存)

创建联合和创建结构的方式相同,需要一个联合模板和联合变量。

下面是一个带标记的联合模板声明:

union hold {
int digit;
double bigfl;
char letter;
};

声明的联合只能储存一个int类型的值或一个double类型的 值或char类型的值;

注意,联合只能储存一个值,这与结构不 同;

有 3 种初始化的方法:

把一个联合初始化为另一个同类型的联合;

初始化联合的第1个元素;

或者根据C99标准,使用指定初始化器:

fit.digit = 23; //把 23 储存在 fit,占2字节
fit.bigfl = 2.0; // 清除23,储存 2.0,占8字节
fit.letter = 'h'; // 清除2.0,储存h,占1字节

常见用法:

用一个成员把值储存在一个联合中,然后用另一个成员查看内 容,这种做法有时很有用;

在结构中储存与其成员有从属关系的信息。

七、枚举类型

枚举类型(enumerated type):声明符号名称来表示整型常量;

关键字:enum

可以创建一个新“类型”并指定它可具有的值(实际上,enum 常量是int类型,因此,只要能使用int类型的地方就可以使用枚举类型

有如下使用方法:

第1个声明:创建了spetrum作为标记名,允许把enum spetrum作为一个类型名使用

第2个声明使color作为该类型的变量。第1个声明中花括号内的标识符枚举了spectrum变量可能有的值。因此, color 可能的值是 red、 orange、yellow 等;

这些符号常量被称为枚举符(enumerator);

enum spectrum {red, orange, yellow, green, blue, violet};
enum spectrum color;

枚举符(如red和blue)是int类型;

枚举变量可以是任意整数类型,前提是该整数类型可以储存枚举常量

注意:C允许枚举变量 使用++运算符,但是C++标准不允许

默认情况下,枚举列表中的常量都被赋予0、1、2等(枚举类型只能在内部使用。如果要输入color中orange的值,只能输入1,而不是单词orange。)

在枚举声明中,可以为枚举常量指定整数值:

enum levels {low = 100, medium = 500, high = 2000};

枚举类型的目的是为了提高程序的可读性和可维护性;

八、typedef

用typedef可以为某一类型自定义名称;

typedef unsigned char BYTE;
BYTE x, y[10], * z;

typedef创建的符号名只受限于类型,不能用于值;

typedef由编译器解释,不是预处理器, 在其受限范围内,typedef比#define更灵活;

定义的作用域取决于typedef定义所在的位置;

用typedef来命名一个结构类型时,可以省略该结构的标签:

typedef struct {double x; double y;} rect;

九、其他复杂声明

较复杂的声明示例:

int board[8][8]; // 声明一个内含int数组的数组

int ** ptr; // 声明一个指向指针的指针,被指向的指针指向int

int * risks[10]; // 声明一个内含10个元素的数组,每个元素都是一
个指向int的指针

int (* rusks)[10]; // 声明一个指向数组的指针,该数组内含10个int类
型的值

int * oof[3][4]; // 声明一个3×4 的二维数组,每个元素都是指向int
的指针

int (* uuf)[3][4]; // 声明一个指向3×4二维数组的指针,该数组中内含
int类型值

int (* uof[3])[4]; // 声明一个内含3个指针元素的数组,其中每个指针
都指向一个内含4个int类型元素的数组

数组名后面的[]和函数名后面的()具有相同的优先级。它们比*(解引 用运算符)的优先级高。

char * fump(int); // 返回字符指针的函数

char (* frump)(int); // 指向函数的指针,该函数的返回类型为char

char (* flump[3])(int); // 内含3个指针的数组,每个指针都指向返回
类型为char的函数

这3个函数都接受int类型的参数。 可以使用typedef建立一系列相关类型:

typedef int arr5[5];

typedef arr5 * p_arr5;

typedef p_arr5 arrp10[10];

arr5 togs; // togs 是一个内含5个int类型值的数组

p_arr5 p2; // p2 是一个指向数组的指针,该数组内含5个int类型的值

arrp10 ap; // ap 是一个内含10个指针的数组,每个指针都指向一个
内含5个int类型值的数组

十、函数与指针(进阶)

声明一个函数指针时,必须声明指针指向的函数类型;

通常,函数指针常用作另一个函数的参数,告诉该函数要使用哪一个函数;

指向函数的指针中储存着函数代码的起始处的地址;

1.函数指针指向函数

例如有, 下面声明了一个指针pf指向该函数类型:

ToUpper()函数的类型是“带char * 类型参数、返回类型是void的函数”。

void ToUpper(char *); // 把字符串中的字符转换成大写字符

void (*pf)(char *); // pf 是一个指向函数的指

(*pf)是一个参数列表为(char *)、返回类型为void的函数。

把函数名ToUpper替换为表达式(*pf)是创建指向函数指针最简单的方式。

声明了函数指针后,可以把类型匹配的函数地址赋给它;

在这种上下文中,函数名可以用于表示函数的地址

void ToUpper(char *);

void (*pf)(char *);

pf = ToUpper; // 有效,ToUpper是该类型函数的地址

特别的是,既然可以用数据指针访问数据,也可以用函数指针访问函数

char mis[] = "Nina Metier";

(*pf)(mis); // 把ToUpper 作用于(语法1)

pf(mis); // 把ToLower 作用于(语法2)

由于pf 指向ToUpper 函数,那么*pf就相当于ToUpper函数,

所以表达式(*pf)(mis)和ToUpper(mis) 相同;

由于函数名是指针,那么指针和函数名可以互换使用,

所以pf(mis) 和ToUpper(mis)相同。

2.作为函数参数的函数指针

声明了两个形参:fp和str。fp形参是一个函数指针,str是一个数据指针;

void show(void (* fp)(char *), char * str);

show(ToLower, mis); /* show()使用ToLower()函数:fp = ToLower */
show(pf, mis); /* show()使用pf指向的函数: fp = pf */

值得注意,show()如何使用传入的函数指针?是用fp()语法还是(*fp)()语法调用函 数:

void show(void (* fp)(char *), char * str)
{
(*fp)(str); /* 把所选函数作用于str */
puts(str); /* 显示结果 */
}

3.带返回值的函数作为参数传递给另一个函数

function1(sqrt); /* 传递sqrt()函数的地址 */

function2(sqrt(4.0)); /* 传递sqrt()函数的返回值 */

第1条语句传递的是sqrt()函数的地址,假设function1()在其代码中会使用该函数。第2条语句先调用sqrt()函数,然后求值,并把返回值(该例中是 2.0)传递给function2()。

以上介绍了使用函数名的4种方法:定义函数、声明函数、调用函数和作为指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值