C入门系列:第七章 结构和其他数据形式

C入门系列:第一章 基本类型

C入门系列:第二章 字符串

C入门系列:第三章 函数

C入门系列:第四章 数组和指针

C入门系列:第五章 数据存储类别和内存管理

C入门系列:第六章 文件输入/输出

C入门系列:第七章 结构和其他数据形式

C入门系列:第八章 C预处理器和C库

1 结构体

#include<stdio.h>
#include<string.h>
char * s_gets(char * st, int n);
#define MAXTITL 41 
#define MAXAUTL 31

// 结构声明
// 定义变量时使用struc book library
struct book {
	char title[MAXTITL];
	char author[MAXAUTL];
	float value;	
};

// 简化写法
// 定义变量时使用book library;
struct book {
	char title[MAXTITL];
	char author[MAXAUTL;
	float value;
} library;

// 结构变量初始化
struct book library = {
	"The Pious Pirate and the Devious Damsel",
	"REnee Vivotte",
	1.95
};

// 初始化部分结构变量,.成员名=xxx
struct book surprise = {.value = 10.99};
// 可以任意顺序初始化
struct book gift = {
	.value = 25.99,
	.author = "James Broadfool",
	.title = "Rue for the Toad"
};

// 初始化最后的赋值才是实际获得的值
struct book gift = {
	.value = 18.90,
	.author = "Philionna Pestle",
	0.25  // 最终赋值.value=0.25
};

int main(void) {
	struct book library;
	
	printf("Please enter the book title\n");
	s_gets(library.title, MAXTITL);
	
	printf("Please enter the author\n");
	s_gets(library.author, MAXAUTL);

	printf("Enter the value\n");
	scanf("%f", &library.value);

	return 0;
}

char * s_gets(char * st, int n) {
	char * ret_val;
	char * find;

	ret_val = fgets(st, n, stdin);
	if (ret_val) {
		find = strchr(st, '\n');
		if (find) 
			*fid = '\0';
		else
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

2 结构数组

#include<stdio.h>
#include<string.h>
char * s_gets(char * st, int n);
#define MAXTITL 40
#define MAXAUTL 40
#define MAXBKS 100

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

int main(void) {
	struct book library[MAXBKS]; // 结构数组
	int count = 0;
	int index;

	printf("Please enter the book title.\n");
	while (count < MAXBKS && s_gets(library[cout].title, MAXTITL) != NULL && library[cout].title[0] != '\0') {
		printf("enter the author\n");
		s_gets(library[cout].author, MAXAUTL);
		printf("enter the value\n");
		scanf("%f", &library[count++].value);
		while(getchar() != '\n')
			continue;
		if (count < MAXBKS)
			printf("enter the next title\n");
	}
}

3 嵌套结构

#include<stdio.h>
#define LEN 20
const char * msgs[5] = {
	"Thank you for the wonderful evening",
	"You certainly prove that a",
	"is a special kind of guy.We must get together",
	"over a delicious",
	"and have a few laughs"
};

struct names {
	char first[LEN];
	char last[LEN];
};

struct guy {
	struct names handle; // 嵌套names结构
	char favfood[LEN];
	char job[LEN];
	float income;
};

int main(void) {
	struct guy fellow = {
		{"Ewen", "Villard"},
		"grilled salmon",
		"personality coach",
		68112.00
	};

	
	printf("Dear %s\n", fellow.handle.first); // 调用嵌套结构成员变量
	....
}

4 指向结构的指针

#include<stdio.h>
#define LEN 20

struct names {
	char first[LEN];
	char last[LEN];
};

struct guy {
	struct names handle;
	char favfood[LEN];
	char job[LEN];
	float income;
};

int main(void) {
	struct guy fellow[2] = {
		{
			{"Ewen", "Villard"},
			"grilled salmon",
			"personality coach",
			68112.00
		},
		{
			{"Rodney", "Swillbelly"},
			"tripe",
			"tabloid editor",
			432400.00
		}
	};

	struct guy * him; // 指向结构的指针

	printf("address #1: %p, #2:%p", &fellow[0], &fellow[1]);

	// 指向结构的指针与普通的指针不同,指向结构的指针不能使用数组名作为元素首地址,要使用&运算符

	// 假设barney是一个guy类型的结构,指向guy结构的指针:
	// him = &barney

	him = &fellow[0]; // 指向第一个结构数组元素
	
	printf("pointer #1:%p, #2:%p", him, him + 1);

	// 指向结构的指针访问结构成员变量,要使用->运算符访问
	// him->income等价于(*him).income
	printf("him->income is %.2f, (*him).income is %.2f\n", him->income, (*him).income);

	him++; // 指向下一个结构
	
	printf("him->favfood is %s: him->handle.last is %s\n", him->favfood, him->handle.last);

	return 0;
}

5 传递结构地址

#include<stdio.h>
#define FUNDLEN 50

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

double sum(const struct funds *);

int main(void) {
	struct funds stan = {
		"Grrlic-Melon Bank",
		4032.27,
		"Lucky's Saving and Loan",
		8543.94
	};

	// 传递结构地址
	printf("Stan has a total of %.2f", sum(&stan));
}

double sum(const struct funds * money) {
	return (money->bankfund + money->savefund);
}

6 传递结构

#include<stdio.h>
#define FUNDLEN 50

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

double sum(struct funds moolah); // 传递结构

int main(void) {
	struct funds stan = {
		"Garlic-Melon Bank",
		4032.27.
		"Lucky's Saving and Loan",
		8543.94
	};

	printf("Stan has a total of %.2f", sum(stan));
}

double sum(struct funds moolah) {
	return (moolah.bankfund + moolah.savefund);
}

7 结构特性

结构可以把一个结构赋值给另一个结构,每个成员的值都赋值,即使成员是数组。

struct names o_data;
struct names n_data; // 假设这个结构以初始化
o_data = n_data; // 把一个结构赋值给另一个结构

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

#include<stdio.h>
#include<string.h>
#define NLEN 30
struct namect {
	char fname[NLEN];
	char lname[NLEN];
	int letters;
};

// 返回结构
struct namect getinfo(void);
struct namect makeinfo(struct namect);
void showinfo(struct namect);
char * s_gets(char * st, int n);

int main(void) {
	struct namect person;
	person = getinfo();
	person = makeinfo(person);
	showinfo(person);

	return 0;
}

struct namect getinfo() {
	struct namect temp;
	s_gets(temp.fname. NLEN);
	s_gets(temp.lname, NLEN);
	return temp;
}

struct namect makeinfo(struct namect info) {
	info.letters = strlen(info.fname) + strlen(info.lname);
	return info;
}	

void showinfo(struct namect info) {
	printf("%s %s, your name contains %s letters", info.fname, info.lname, info.letters);
}

函数中传递结构形参和传递结构指针的优缺点:

  • 传递指针访问速度快,但无法保护数据,该问题可以在形参使用const关键字防止修改数据

  • 传递结构的优点是结构清晰,但浪费时间和存储空间,老版本的代码可能无法处理这样的代码

通常,程序员为了追求效率会使用结构指针作为函数参数

8 结构中的字符数组和字符指针

#define LEN 20
struct names {
	char first[LEN];
	char last[LEN];
};

struct pnames {
	char * first;
	char * last;
};

// 都能够正常运行

// 字符串存储在结构内部,总共分配40个字节
struct names veep = {"Talia", "Summers"};

// 结构只存储了两个地址占16个字节,不用为字符串分配任何存储空间
// 结构变量中的指针应该只用来在程序中管理那些已分配和在别处分配的字符串
struct pnames treas = {"Brad", "Fallingjaw"};

struct names accountant;
struct pnames attorney;
scanf("%s", accountant.last);
// 有潜在危险,因为attorney.last是未初始化的变量,地址可以是任何值,因此程序可能有时候能正常运行,但也有可能导致程序崩溃
scanf("%s", attorney.last); 

在结构中存储字符串,应该使用字符数组;如果要使用字符指针,应该要给字符指针先初始化 malloc()calloc() 分配内存空间。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define SLEN 81

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

void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
void cleanup(struct namect *);

int main(void) {
	struct namect person;
	
	getinfo(&person);
	makeinfo(&person);
	showinfo(&person);
	cleanup(&person);

	return 0;
}

void getinfo(struct namect * pst) {
	char temp[SLEN];

	s_gets(temp, SLEN);
	// 给结构的字符指针分配内存空间
	pst->fname = (char *)malloc(strlen(temp) + 1);
	// 拷贝到已分配的内存
	strcpy(pst->fname, temp);
	s_gets(temp, SLEN);
	pst->lname = (char *)malloc(stelen(temp) + 1);
	strcpy(pst->lname, temp);
}

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

void cleanup(struct namect * pst) {
	free(pst->fname);
	free(pst->lname);
}

char * s_gets(char *st, int n) {
	char * ret_val;
	char * find;

	ret_val = fgets(st, n, stdin);
	if (ret_val) {
		find = strchr(st, '\n'); // 查找换行符
		if (find) 				// 如果地址不是NULL
			*find = '\0';		// 在此放置一个空字符
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

9 复合字面量

C99的复合字面量特性可用于结构和数组。如果只需要一个临时结构值,复合字面量很好用。例如,可以使用复合字面量创建一个数组作为函数的参数或赋给另一个结构。

语法是把类型名放在括号中,后面紧跟一个用花括号括起来的初始化列表:

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

#include<stdio.h>
#define MAXTITL 41
#define MAXAUTL 31

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

int main(void) {
	struct book readfirst;
	int score;

	printf("Enter test score");
	scanf("%d", &score);

	if (score >= 84) {
		readfirst = (struct book) {
			"Crime and Punishment",
			"Fyodor Dostoyevsky",
			11.25
		};
	} else {
		readfirst = (struct book) {
			"Mr.Bouncy's Nic Hat",
			"Fred Winsome",
			5.99
		};
	}

	return 0;
}

// 复合字面量作为函数参数传递
struct rect {
	double x;
	double y;
};

double area;
area = rect_area((struct rect){10.5, 20.0i});

double rect_area(struct rect r) {
	return r.x * r.y;
}

struct rect {
	double x;
	double y;
};

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

double rect_area(struct rect * rp) {
	return rp->x * rp->y;
}

10 伸缩型数组成员

C99新增了一个特性:伸缩型数组成员。伸缩型数组成员有两个特性:

  • 该数组不会立即存在

  • 使用这个伸缩型数组成员可以编写合适的代码,好像它确实存在并具有所需数目的元素一样

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

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

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

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

声明了伸缩型数组成员的结构在使用上有一定要求:

  • 相同的两个结构不能进行赋值:
//只能拷贝除伸缩型数组成员外的其他成员
// 确实要进行拷贝,应使用memcpy()函数
struct flex *pf1, *pf2;
*pf2 = *pf1; 
  • 不要以按值方式把这种结构传递给结构

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

#include<stdio.h>
#include<stdlib.h>

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

void showFlex(const struct flex * p);

int main(void) {
	struct flex *pf1, *pf2;
	int n = 5;
	int i;
	int tot = 0;

	pf1 = malloc(sizeof(struct flex) + n * sizeof(double));
	pf1->count = n;
	for (i = 0; i < n; i++) {
		pf1->scores[i] = 20.0 - i; // 伸缩型数组成员可以像普通数组一样使用
		tot += pf1->socres[i];
	}
	pf1->average = tot / n;
	showFlex(pf1);

	n = 9;
	tot = 0;
	pf2 = malloc(sizeof(struct flex) + n * sizeof(double));
	pf2->count = n;
	for (i = 0; i < n; i++) {
		pf2->scores[i] = 20.0 - i / 2.0;
		tot += pf2->socres[i];	
	}
	pf2->average = tot / n;
	showFlex(pf2);
	free(pf1);
	free(pf2);

	return 0;
}

void showFlex(const struct flex * p) {
	int i;
	for (i = 0; i < p->count; i++)
		printf("%g", p->scores[i]);
	printf("\nAverage:%g\n", p->average);
}

11 匿名结构

C11才可以使用匿名结构

struct names {
	char first[20];
	char last[20];
};
	
struct person {
	int id;
	struct names name;
};	

struct person ted = {
	8483,
	{"Ted", "Grass"}
};

访问:ted.names.first

// 匿名结构	
struct person {
	int id;

	struct {
		char first[20];
		char last[20];
	};
};

访问:ted.first

12 文件保存结构内容

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAXTITL 40	
#define MAXAUTL 40
#define MAXBKS 10
char * s_gets(char * st, int n);
struct book {
	char title[MAXTITL];
	char author[MAXAUTL];
	float value;
};

int main(void) {
	struct book library[MAXBKS];
	int count = 0;
	int index, filecount;
	FILE * pbooks;
	int size = sizeof(struct book);

	if ((pbooks = fopen("book.dat", "a+b")) == NULL) {
		fputs("Can't open book.dat");
		exit(1);
	}
	rewind(pbooks); // 定位到文件开始
	
	while (count < MAXBKS && fread(&library[count], size, 1, pbooks) == 1) {
		if (count == 0)
			puts("Current contents of book.dat");
		printf("%s by %s:%.2f\n", library[count].title, library[count].author, library[count].value);
		count++;
	}
	filecount = count;
	if (count == MAXBKS) {
		fputs("The book.dat file is full");
		exit(2);
	}

	puts("Please add new book titles");
	puts("Press [enter] at the start of a line to stop");
	while (count < MAXBKS && s_gets(library[count).title, MAXTITL) != NULL && library[count].title[0] != '\0') {
		puts("Now enter the author");
		s_gets(library[count].author, MAXAUTL);
		puts("Now enter the value");
		scanf("%f", &library[count++].value);
		while (getchar() != '\n')
			continue;
		if (count < MAXBKS)
			puts("Enter the next title");
	}

	if (count > 0) {
		puts("Here is the list of your books");
		for (index = 0; index < count; index++)
			printf("%s by %s:%.2f\n", library[index].title, library[index].author, library[index].value);
		fwrite(&library[filecount], size, count - filecount, pbooks);
	} else {
		puts("No books? Too bad");
	}
	fclose(pbooks);

	return 0;
}

13 联合

联合是一种数据类型,它能在同一个内存空间中存储不同的数据类型(不是同时存储)。其典型用法是,设计一种表以存储既无规律、事先也不知道顺序的混合类型。使用联合类型的数组,其中的联合都大小相等,每个联合可以存储各种数据类型。

联合的使用与结构相同,但要注意联合当前使用的类型

// 声明了三个成员,但最终只能存储其中一个
// 编译器分配空间以最大的为主,例如double最大有8个字节
union hold {
	int digit;
	double bigf1;
	char letter;
}

union hold fit; // 联合变量,分配8个字节内存空间
union hold save[10]; // 10个元素,每个元素8个字节
union hold * pu; 

// 联合初始化
union hold valA;
valA.letter = 'R';
union hold valB = valA; // 用另一个联合初始化
union hold valC = {88}; // 初始化联合digit成员
union hold valD = {.bigf1 = 118.2}; // 指定初始化成员

fit.letter = 'A';
num = 3.02 * fit.digit; // 错误,因为当前存储的是char类型

联合的另一种用法是,在结构中存储与成员有从属关系的信息。

例如,假设用一个结构表示一辆汽车,如果汽车属于驾驶者,就要用一个结构成员来描述这个所有者;如果汽车被租赁,那么需要一个成员来描述其租赁公司。

struct ower {
	char socsecurity[12];
};
struct leasecompay {
	char name[40];
	char headquarters[40];
};

union data {
	struct ower owncar;
	struct leasecompany leasecar;
};
struct car_data {
	char make[15];
	int status; // 私有为0,租赁为1
	union data ownerinfo;
};

假设 flitscar_data 类型,如果 flits.status=0,程序将使用 flits.ownerinfo.owncar.socsecurity,如果 flits.status=1,使用flits.ownerinfo.leasecar.name

14 匿名联合

C11提供了匿名联合

struct ower {
	char socsecurity[12];
};
struct leasecompay {
	char name[40];
	char headquarters[40];
};

struct car_data {
	char make[15];
	int status;
	union {
		struct owner owncar;
		struct leasecompany leasecar;
	};
};

访问:
struct car_data flits;
flits.owncar.socsecurity;
flits.leasecar.name;

15 枚举类型

可以用枚举类型声明符号名称来表示整型常量。使用 enum 关键字(实际上,enum常量是int类型)。

// 枚举spectrum
// 默认情况下,枚举列表中的常量都被赋予0、1、2等值
// red=0,orange=1,yellow=3,....
enum spectrum {
	red,
	orange,
	yellow,
	green,
	blue,
	violet
};
enum spectrum color; // 声明枚举变量color,color的值是spectrum定义的值

// 可以直接使用枚举值判断和赋值
int c;
color = blue;
if (color == yellow)
	...;
for (color = red; color <= violet; color++)
	...;

// 为枚举定义具体值
// 如果枚举的值有部分被赋值,那么后面的常量了会被赋予后续的值
enum levels {
	low = 100,
	medium = 500,
	high = 2000
};

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
#define LEN 30
char * s_gets(char * st, int n);

enum spectrum {
	red, orange, yellow, green, blue, violet
};
const char * colors[] = {"red", "orange", "yellow", "green", "blue", "violet"};

	int main(void) {
	char choice[LEN];
	enum spectrum color;
	bool color_is_found = false;
	
	puts("Enter a color(empty line to quit)");
	while (s_gets(choice, LEN) != NULL && choice[0] != '\0') {
		for (color = red; color <= violet; color++) {
			if (strcmp(choice, colors[color] == 0) {
				color_is_found = true;
				break;
			}
		}
		if (color_is_found) {
			switch(color) {
				case red:
					puts("Roase are red");
				break;
				case orange:
					puts("Poppies are orange");
				break;
				....
			}
		}
	}

	return 0;
}

16 typedef

利用 typedef 可以为某一类型自定义名称,与 #define 有3处不同:

  • 与#define不同,typedef创建的符号名只受限于类型,不能用于值

  • typedef由编译器解释,不是预处理器

  • 在其受限范围内,typedef比#define更灵活

// BYTE定义为unsigned char类型
typedef unsinged char BYTE;
// 变量都是unsigned char类型
BYTE x, y[10], *z; 

// 字符指针使用STRING表示
typedef char * STRING;
STRING name, sign;

// 结构struct complex使用COMPLEX表示
typedef struct complex {
	float real;
	float imag;
} COMPLEX;

17 函数指针

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#define LEN 81
char * s_gets(char * st, int n);
char showmenu(void);
void eatline(void);	// 读取至行末尾
void show(void(*fp)(char *), char * str);
void ToUpper(char *); // 字符串转大写
void ToLower(char *); // 字符串转小写
void Transpose(char *); // 大小写转置
void Dummy(char *); // 不更改字符串

int main(void) {
	char line[LEN];
	char copy[LEN];
	char choice;
	void(*pfun)(char *);

	puts("Enter a string (empty line to quit)");
	while (s_gets(line, LEN) != NULL && line[0] != '\0') {
		while ((choice = showmenu()) != '\n') {
			switch(choice) {
				case 'u':
					pfun = ToUpper;
					break;
				case 'l':
					pfun = ToLower;
					break;
				case 't':
					pfun = Transpose;
					break;
				case 'o':
					pfun = Dummy;
					break;
			}
			strcpy(copy, line); // 为show()函数拷贝一份
			show(pfun, copy); // 根据用户选择,使用选定函数
		}
		puts("Enter a string (empty line to quit)");
	}

	return 0;
}

char showmenu(void) {
	char ans;
	puts("Enter menu choice");
	puts("u) uppercase l) lowercase");
	puts("t) transposed case o) original case");
	puts("n) next string");
	ans = getchar(); // 获取用户的输入
	ans = tolower(ans); // 转为小写
	eatline(); // 清理输入行
	while (strchr("ulton", ans) == NULL) {
		puts("Please enter a u, l, t, o, or n:");
		ans = tolower(getchar());
		eatline();
	}
	return ans;
}

void eatline(void) {
	while (getchar() != '\n')
		continue;
}

void ToUpper(char * str) {
	while (*str) {
		*str = toupper(*str);
		str++;
	}
}

void ToLower(char * str) {
	while (*str) {
		*str = tolower(*str);
		str++;
	}
}

void Transpose(char * str) {
	while (*str) {
		if (islower(*str))
			*str = toupper(*str);
		else if (isupper(*str))
			*str = tolower(*str);
		str++;
	}
}

void Dummy(char * str) {

}

void show(void(*fp)(char *), char * str) {
	(*fp)(str); // 把用户选定的函数作用于str
	puts(str);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值