结构 struct
结构:
结构布局:告知编译器结构内容,不分配空间
结构变量:根据结构布局给变量分配空间
处理具有多种属性的实体、不同类型信息放在一个单元内
设计结构时、开发配套的函数包更高效:以结构或结构地址为参数的函数、一个参数可以调用结构中所有成员、增删成员只需要修改函数内容、不需要改函数调用
成员运算符 . 结构或联合名指定成员
间接成员运算符 -> 指向结构或联合的指针标记成员
结构声明、定义、初始化、访问
结构声明
结构变量声明,初始化
结构变量访问
C99和C11提供初始化器、使用 .成员名 标识特定元素
#include <stdio.h>
#include <string.h>
#define MAXTITL 41 /*书名的最大长度+1*/
#define MAXAUTL 31 /*作者姓名的最大长度+1*/
char *s_gets(char *st, int n);
// 结构声明
struct book
{ /*结构模版:标记是book,标记可引用该结构*/
char title[MAXTITL]; // 书名
char author[MAXAUTL]; // 作者名
float value; // 价格,不是文本、使用文本模式查看时此部分不可读,会乱码
}; /*结构模版结束
声明2个字符数组、1个float类型变量组成的结构
只描述结构组成、不创造实际对象
花括号内是结构成员列表、每个成员单独描述,均是C的任意一种数据类型或其他结构
结构声明可以放在函数外(所有函数都可使用标记)或函数内(当前函数适用)
结构大小为40*sizeof(char)+40*sizeof(char)+sizeof(float)
不是每次输入都需要完整的空间,为检索方便*/
// 结构变量声明
struct book library;
/*把library声明为一个book类型的变量,使用book模板给变量分配空间
结构变量library包含title、author、value部分
struct book 相当于int
可以声明指向struct book 类型结构的指针struct book * ptbook;
指针ptbook可以指向任何book类型的结构变量
简化声明
struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
}library;声明的右花括号后跟变量名
struct {无结构标记
char title [ MAXTITL];
char author [MAXAUTL];
float value;
}library;变量名
需要多次使用模板时需要带标记或者typedef */
// 结构变量声明&初始化
struct book doyle = {
"The Pious Pirate and the Devious Damsel",
/*各初始化项用逗号分隔、换行增加可读性
静态存储期的变量初始化必须用常量值、静态存储期的结构初始化成员必须用常量*/
"Renee vivotte",
1.95}; // ANSI 之前不能用自动变量初始化结构,之后可以用任意存储类别
printf("Please enter the book title.\n");
s_gets(library.title, MAXTITL);
/*访问title部分
结构内char title[MAXTITL]、像使用字符数组一样使用library.title
结构成员运算符 . 结构变量.结构成员
其属性与结构成员相同、可以自行决定如何使用结构成员*/
printf("Now enter the author.\n");
s_gets(library.author, MAXAUTL);
printf("Now enter the value. \n");
scanf("%f", &library.value); //.比&优先级高,相当于&(library.value)
printf("%s by %s:$%.2f\n", library.title, library.author, library.value);
printf("%s:\"%s\"($%.2f)\n", library.author, library.title, library.value);
printf("Done.\n");
// 初始化器
// struct book surprise = {.value = 10.99}; // 初始化器 .成员名、只初始化book结构的value成员
// 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
// 结构数组 初始化器
// typedef struct lens
// { /* lens描述*/
// float foclen; /*焦距长度,单位:mm*/
// float fstop; /*孔径*/
// char brand[30]; /*品牌*/
// } LENS;
// LENS bigEye[10];
// bigEye[2].foclen = 500;
// bigEye[2].fstop = 2.0;
// strcpy(bigEye[2].brand, "Remarkatar");
// LENS bigEye[10] = {[2] = {500, 2, "Remarkatar"}};
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;
}
结构数组
创建1个包含100个结构变量的数组
自动存储类别、存在栈中、数组需要很大内存,可能会出现栈溢出
编译器可设置栈大小为1000、或者创建静态或外部数组、减少数组大小
#define MAXBKS 100 /*书籍的最大数量*/
struct book library[MAXBKS];
/*声明一个包括MAXBKS个book类型元素的数组
library是数组名 数组每个元素都是struct book类型的结构变量
library[1]是book结构变量名(数组元素)
library[1].value 访问结构内成员
library[1].title[3] 访问library数组的第2个结构变量中title成员的第4个字符*/
int count = 0;
int index;
printf("Please enter the book title.\n");
printf("Press [enter] at the start of a line to stop.\n");
while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL && library[count].title[0] != '\0')
{ /*一个while循环读取多个项 读取成功且不为空时继续读取其他内容
一行开始处Enter键相当于输入空字符串循环结束*/
printf("Now enter the author. \n");
s_gets(library[count].author, MAXAUTL);
printf("Now enter the value.\n");
scanf("%f", &library[count++].value);
while (getchar() != '\n')
continue; /*清理输入行剩下的内容
避免输入value之后按下的enter键留在缓冲区影响下一次输入*/
if (count < MAXBKS)
printf("Enter the next title.\n");
}
if (count > 0)
{
printf("Here is the list of your books : \n");
for (index = 0; index < count; index++)
printf("%s by %s:$%.2f\n", library[index].title, library[index].author, library[index].value);
}
else
printf("No books? Too bad.\n");
嵌套结构
#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
{ // 第1个结构
char first[LEN]; // 20字节
char last[LEN]; // 20字节
};
struct guy
{ // 第2个结构
struct names handle; // 40 嵌套结构,声明handle是一个struct names类型的变量
char favfood[LEN]; // 20
char job[LEN]; // 20
float income; // 4
}; // 84字节 54(十六进制) 自动对齐另外讲
struct guy fellow = {// 初始化一个结构变量
{"Ewen", "Villard"},
"grilled salmon",
"personality coach",
68112.00};
printf("Dear %s,\n\n", fellow.handle.first);
/*从左往右解释(fellow.handle).first
找到fellow,然后找fellow的handle成员,再找handle的first成员*/
printf("%s%s.\n", msgs[0], fellow.handle.first);
printf("%s%s\n", msgs[1], fellow.job);
printf("%s \n", msgs[2]);
printf("%s%s%s", msgs[3], fellow.favfood, msgs[4]);
if (fellow.income > 150000.0)
puts("!!");
else if (fellow.income > 75000.0)
puts("!");
else
puts(".");
printf("\n%40s%s\n", " ", "see you soon, ");
printf("%40s%s\n", " ", "Shalala");
指向结构的指针
优势:
指向结构的指针更容易操控,效率更高
早期的C实现中结构不可以作为参数,可以传递结构指针
表示数据的结构中包含指向其他结构的指针
struct guy fellow[2] = {
{{"Ewen", "Villard"},"grilled salmon","personality coach",68112.00},
{{"Rodney", "Swillbelly"},"tripe","tabloid editor",432400.00} };
struct guy *him;
/*类型 * 指针名 struct guy 相当于int
一个指向结构guy的指针 不声明新的结构
struct guy barney;
him=&barney;结构名不是结构的地址、需要&取地址
*/
printf("address #1: %p #2: %p\n", &fellow[0], &fellow[1]);
him = &fellow[0];
/*告诉编译器该指针指向何处
fellow结构数组名 fellow[0]第一个结构 &fellow[0]第一个结构的地址*/
printf("pointer #1:%p #2: %p\n", him, him + 1);
printf("him->income is $%.2f: (*him).income is $%.2f : fellow[0].income is $%.2f\n", him->income, (*him).income, fellow[0].income);
/* him->income (him=&barney)相当于barney.income fellow[0].income
(*him=barney *him=fellow[0])相当于(*him).income 必须使用圆括号,运算符优先级
him->income 指针指向结构的一个成员,是一个float类型的变量 */
him++; /*指向下一个结构*/
printf("him->favfood is %s:him->handle.last is %s\n", him->favfood, him->handle.last);
结构传递
传递结构成员
结构成员是单个值的数据类型时,直接作为参数传值或者传地址给接受该特定类型的函数
传递指向结构的指针
结构地址作为参数
传递结构本身(ANSI之后)
#define FUNDLEN 50
struct funds
{
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum1(double, double);//只传值
double sum2(const struct funds *); /*参数是一个指针*/
double sum3(struct funds moolah); /*参数是一个结构*/
void modify(double *x);
struct funds stan = {"Garlic-Melon Bank",4032.27,"Lucky's Savings and Loan",8543.94};
printf("stan has a total of $%.2f.\n", sum1(stan.bankfund, stan.savefund)); // 不需要修改,只需要传值
printf("Stan has a total of $%.2f.\n", sum2(&stan)); /*stan结构 &stan结构地址*/
printf("stan has a total of $%.2f.\n", sum3(stan));
modify(&stan.bankfund); // 需要修改主调函数成员的值,传成员地址
printf("stan has a total of $%.2f.\n", sum1(stan.bankfund, stan.savefund));
double sum1(double x, double y)
{ /*两个double类型的数相加
sum函数不在乎实参是否为结构成员,只要求传入double类型*/
return (x + y);
}
double sum2(const struct funds * money)
{/*使用指向funds 的指针作为参数 传地址&stan给函数
money指向结构stan 相当于 money=&stan
避免函数修改指针指向的内容const
可以通过->访问所有成员*/
return (money->bankfund + money->savefund);
}
double sum3(struct funds moolah)
{ /*直接传结构 编译器根据模板创建一个名为moolah的自动结构变量,各成员被初始化为stan的成员的副本
moolah是结构 故moolah.bankfund*/
return (moolah.bankfund + moolah.savefund);
}
void modify(double *x)
{ /*修改成员值、传递成员地址*/
*x = 3.14;
}
其他结构特性
结构可以直接赋值给结构、数组不可以
结构传参,做返回值,被调函数和主调函数之间双向传递结构信息
结构指针传参,做返回值
指针作为参数:
兼容所有版本的C
执行快,只需要传地址
无法保护数据,容易被被调函数修改数据,const修饰
结构作为参数:
函数处理原始数据的副本,保护原始数据
结构传参+返回值的形式、代码更清楚
ANSI之前不支持
大型结构,拷贝副本、浪费时间和空间,尤其在只是用很少成员时
值传递一般小型结构常用
#define NLEN 30
struct namect
{
char fname[NLEN];
char lname[NLEN];
int letters;
};
void getinfo1(struct namect *);
void makeinfo1(struct namect *);
void showinfo1(const struct namect *);
struct namect getinfo2(void);
struct namect makeinfo2(struct namect);
void showinfo2(struct namect);
// o_data = n_data;
// /*把一个结构赋值给另一个结构
// 把n_data的每个成员的值都赋给o_data的相应成员。
// 即使成员是数组,也能完成赋值*/
// struct names right_field = {"Ruthie", "George"};
// struct names captain = right_field; // 把一个结构初始化为相同类型的另一个结构
struct namect person;
getinfo1(&person); // 传指针 传参时&person 形参为struct namect * pst
makeinfo1(&person);
showinfo1(&person);
person = getinfo2();
person = makeinfo2(person); // 传结构 传参时person 形参为struct namect pst,把返回值再拷贝给person
showinfo2(person);
void getinfo1(struct namect *pst)
{ // 通过指针修改结构体内容,把结构的信息从自己传给main
printf("Please enter your first name. \n");
s_gets(pst->fname, NLEN);
/* pst=&person 故给person结构的成员fname赋值
fname为char数组,可以作为s_gets的参数*/
printf("Please enter your last name. \n");
s_gets(pst->lname, NLEN);
}
void makeinfo1(struct namect *pst)
{ // 双向传输,获取到person.fname,改变person.letters
pst->letters = strlen(pst->fname) + strlen(pst->lname);
}
void showinfo1(const struct namect *pst)
{ // 从主调函数获取信息并打印
printf("%s %s,your name contains %d letters.\n", pst->fname, pst->lname, pst->letters);
}
struct namect getinfo2(void)
{
struct namect temp; // 创建自己的person备份
printf("Please enter your first name .\n");
s_gets(temp.fname, NLEN);
printf("Please enter your last name . \n");
s_gets(temp.lname, NLEN);
return temp; // 副本赋值之后再返回
}
struct namect makeinfo2(struct namect info)
{ // 创建新结构info,拷贝person的值到info
info.letters = strlen(info.fname) + strlen(info.lname);
return info; // 修改完info的值,再返回
}
void showinfo2(struct namect info)
{
printf("%s %s,your name contains %d letters.\n", info.fname, info.lname, info.letters);
}
结构中的字符串
字符指针:涉及到字符串的存储问题,误用会导致程序异常
struct names
{
char first[LEN];
char last[LEN];
// 字符串存储在结构内部,结构会分配40字节存储姓、名
};
struct pnames
{
char *first;
char *last;
/*结构只存地址2*8=16字节,只能管理在其他地方已分配的字符串
不为字符串分配空间,使用的是存储在别处的字符串(字符串常量或数组)*/
};
struct names veep = {"Talia", "Summers"}; // 字符串存在成员的char数组中
struct pnames treas = {"Brad", "Fallingjaw"}; // 字符串放在常量区,指针指向字符串
printf("%s and %s\n", veep.first, treas.first);
struct names accountant;
struct pnames attorney;
puts("Enter the last name of your accountant : ");
scanf("%s", accountant.last); // 获取输入直接放入结构内
puts("Enter the last name of your attorney: ");
scanf("%s", attorney.last);
/*这里有一个潜在的危险,输入之后把字符串放到指针指向的地址,但地址成员、输入的字符串变量均未经初始化,地址可以是任意值*/
结构、指针、malloc
malloc可以为字符串分配合适的存储空间
分配空间之后即可使用指针处理字符串
#include <stdlib.h>//提供malloc()、 free ()的原型
#define SLEN 81
struct namead
{
char *fname; // 使用指针
char *lname;
int letters;
};
void getinfo3(struct namead *); // 分配内存
void makeinfo3(struct namead *);
void showinfo3(const struct namead *);
void cleanup(struct namead *); // 调用该函数时释放内存
struct namead person;
getinfo3(&person);
makeinfo3(&person);
showinfo3(&person);
cleanup(&person);
void getinfo3(struct namead *pst)
{ // 两个字符串均存在与malloc分配的动态内存块中、结构内指针指向动态内存块
char temp[SLEN];
printf("Please enter your first name . \n");
s_gets(temp, SLEN); // 从输入读入临时数组
pst->fname = (char *)malloc(strlen(temp) + 1); // 分配内存以储存名,malloc需要与free成对出现
strcpy(pst->fname, temp); // 把名拷贝到动态分配的内存中
printf("Please enter your last name. \n");
s_gets(temp, SLEN);
pst->lname = (char *)malloc(strlen(temp) + 1);
strcpy(pst->lname, temp);
}
void makeinfo3(struct namead *pst)
{
pst->letters = strlen(pst->fname) + strlen(pst->lname);
}
void showinfo3(const struct namead *pst)
{
printf("%s %s,your name contains %d letters.\n", pst->fname,pst->lname,pst->letters);
}
void cleanup(struct namead *pst)
{
free(pst->fname);
free(pst->lname);
}
复合字面量
复合字面量特性用于结构和数组、临时结构值
用复合字面量创建一个数组作为函数参数或赋给另一个结构
复合字面量特性:
在所有函数外部,具有静态存储期
在块中,自动存储期
可使用指定初始化器
struct rect
{
double x;
double y;
};
double rect_area(struct rect r) { return r.x * r.y; }
/*函数接受结构,复合字面量作为结构实参传递*/
double rect_areap(struct rect *rp) { return rp->x * rp->y; }
/*函数接受地址,传递复合字面量的地址*/
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 Nice Hat",
"Fred winsome",
5.99};
printf("Your assigned reading : \n");
printf("%s by %s:$%.2f\n", readfirst.title, readfirst.author, readfirst.value);
double area = rect_area((struct rect){10.5, 20.0}); // 值210被赋给area
double area = rect_areap(&(struct rect){10.5, 20.0}); // 值210被赋给area
伸缩性数组成员C99
特点:
数组不会立即存在
使用时存在并具有所需数目的元素
规则:
伸缩性数组必须是结构的最后一个成员
结构中至少有一个成员
伸缩数组的声明类似普通数组,方括号中是空的
带伸缩数组的结构要求:
不可以用结构直接赋值或拷贝,memcpy
不可以按值方式把结构传给结构,相当于赋值,只可以传地址
不可以作为数组成员和另一个结构的成员
struct flex
{
size_t count;
double average;
double scores[]; // 伸缩型数组成员
};
void showFlex(const struct flex *p);
struct flex *pf1, *pf2; // 结构指针
int n = 5;
int i;
int tot = 0;
pf1 = (struct flex *)malloc(sizeof(struct flex) + n * sizeof(double));
/*为结构和数组分配存储空间、没有分配之前不能操作scores
存储flex结构的常规内容sizeof(struct flex)和伸缩数组成员所需的额外空间n*sizeof(double)
此后有足够的空间存count,average和一个内含5个double的数组*/
pf1->count = n;
for (i = 0; i < n; i++)
{
pf1->scores[i] = 20.0 - i;
tot += pf1->scores[i];
/* pf1->scores[i] 访问pf1结构的scores数组成员的第i+1个元素*/
}
pf1->average = tot / n;
showFlex(pf1);
n = 9;
tot = 0;
pf2 = (struct flex *)malloc(sizeof(struct flex) + n * sizeof(double)); // 此后有足够的空间存count,average和一个内含9个double的数组
pf2->count = n;
for (i = 0; i < n; i++)
{
pf2->scores[i] = 20.0 - i / 2.0;
tot += pf2->scores[i];
}
pf2->average = tot / n;
showFlex(pf2);
//*pf2=*pf1;不可以
free(pf1);
free(pf2);
void showFlex(const struct flex *p)
{
int i;
printf("Scores : ");
for (i = 0; i < p->count; i++)
printf("%g ", p->scores[i]);
printf("\nAverage : %g\n", p->average);
}
匿名结构 C11
没有名称的结构成员
// struct names
// {
// char first[20];
// char last[20];
// };
// struct person
// {
// int id;
// struct names name; // 嵌套结构成员
// };
// struct person ted = {8483, {"Ted", "Grass"}};
// puts(ted.name.first); // ted.name.first访问first
// 嵌套的匿名成员结构定义
struct person
{
int id;
struct
{
char first[20];
char last[20];
}; // 匿名结构
};
struct person ted = {8483, {"Ted", "Grass"}};
puts(ted.first); // first看作是person的成员
结构数组传递
#define N 2
double sum4(const struct funds money[], int n);
struct funds jones[N] = {{"Garlic-Melon Bank", 4032.27, "Lucky's savings and Loan", 8543.94},
{"Honest Jack's Bank", 3620.88, "Party Time Savings", 3802.91} };
printf("The Joneses have a total of $%.2f.\n", sum4(jones, N));
/*数组名是数组首元素地址,直接传参
数组表示法访问数组中其他结构 sum4(&jones[0], N)*/
getchar();
double sum4(const struct funds money[], int n)
{ /*指针money=&jones[0],指向数组的首元素,money[0]相当数组首元素
每个元素都是funds类型的结构
money[i].savefund 相当于jones[i].savefund
避免改变原始数据,const*/
double total;
int i;
for (i = 0, total = 0; i < n; i++)
total += money[i].bankfund + money[i].savefund;
return (total);
}
结构保存
结构存储不同类型的信息,用于构建数据库
数据库文件可以包含任意数量的此类数据对象
存储在一个结构中的整套信息称为记录、单独的项称为字段
/*fprintf(pbooks,"%s %s %.2f\n", primer.title,primer.author,primer.value);
用fprintf把信息存储在struct book类型的结构变量primer内
不适用于成员多的情况、需要确认每一个字段的结束和开始位置
fwrite(&primer, sizeof(struct book), 1,pbooks) ;
fread和fwrite读写结构大小的单元、二进制模式
从primer结构地址、读取结构大小的单元、1块、存到pbooks中
结构变量整块放入文件中,不具体到某一字段
不同的系统使用不同的二进制表示法时,数据文件不可移植
*/
struct book library[MAXBKS]; /*结构数组*/
int count = 0;
int index, filecount;
FILE *pbooks;
int size = sizeof(struct book); // 结构大小为40*sizeof(char)+40*sizeof(char)+sizeof(float)
if ((pbooks = fopen("book.dat", "a+b")) == NULL)
{ /*以a+b模式打开文件、
a+读取整个文件、在文件末尾添加内容、b二进制文件格式
对不接受b模式或只有b模式的系统可以省略b
fread 和 fwrite 使用二进制文件
float value不是文本、使用文本模式查看时此部分不可读,会乱码*/
fputs("Can't open book.dat file\n", stderr);
exit(1);
}
rewind(pbooks); /*定位到文件开始*/
while (count < MAXBKS && fread(&library[count], size, 1, pbooks) == 1)
{ /*每次读1个结构(size结构大小的数据)到结构数组、数组已满或读完文件停止*/
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.", stderr);
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')
{ /*数组满、行开始处为Enter时退出循环*/
puts("Now enter the author. ");
s_gets(library[count].author, MAXAUTL);
puts("Now enter the value. ");
scanf("%f", &library[count++].value); // 使用完count之后++,在上一个循环结束数组处增加结构
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);
/*把用户输入的数据添加到文件末尾
添加count - filecount个size结构大小的内容到pbooks*/
}
else
puts("No books ? Too bad.\n");
puts("Bye .\n");
fclose(pbooks);
getchar();
链式结构
结构可创建新数据形式
链式结构:
数据项+指向同类型结构的指针、指针链接结构、有可遍历所有结构的路径
可组成队列、二叉树、堆、哈希表、图、表
C-study(十七)
联合 union
不同时间在同一空间存储不同的数据类型、成员共享空间
一种表、存储既无规律、不知道顺序的混合类型
用联合的数组、联合大小相等、每个联合可以存储各种数据类型
/*模板声明*/
union hold
{ // 带标记的联合模板
int digit;
double bigfl;
char letter;
}; /*声明union、只能存储1个int或1个double或1个char
1个联合占用的空间取决于占用最大字节的类型
此联合占用空间最大是double 8字节*/
/*变量声明*/
union hold fit; // hold类型的联合变量、分配8字节
union hold save[10]; // 内含10个联合变量的数组、10*8字节
union hold *pu; // 指向hold类型联合变量的指针、
/*初始化*/
union hold valA;
valA.letter = 'R';
union hold valB = valA; // 用另一个同类型联合来初始化
union hold valC = {88}; // 初始化联合的第一个元素
union hold valD = {.bigfl = 118.2}; // C99指定初始化器
/*使用*/
fit.digit = 23; // 把 23 储存在 fit,占2字节
fit.bigfl - 2.0; // 清除23,储存2.0,占8字节
fit.letter = 'h'; // 清除2.0,储存h,占1字节
/*点运算符表示正在使用哪个数据类型
一次存储一个值、即使空间够也不能同时存储2个类型*/
pu = &fit; // 联合取地址之后赋值给指针
int x = pu->digit; // 相当于x = fit.digit
/*
fit.letter = 'A';此时存储的是char
flnum = 3.02 * fit.bigfl; 用的是double、出错
*/
/*常用场景*/
struct owner
{ // 驾驶者
char socsecurity[12];
};
struct leasecompany
{ // 租赁公司
char name[40];
char headquarters[40];
};
union data
{
struct owner owncar;
struct leasecompany leasecar;
};
struct car_data
{
char make[15];
int status; /*私有为0,租赁为1 */
union data ownerinfo;
} flits;
/*flits.status为0时使用flits.ownerinfo.owncar.socsecurity
flits.status为1时使用flits.ownerinfo.leasecar.name*/
// 匿名联合
struct car_data
{
char make[15];
int status; /*私有为0,租赁为1 */
union
{
struct owner owncar;
struct leasecompany leasecar;
};
} flits;
/*flits.status为0时使用flits.owncar.socsecurity
flits.status为1时使用flits.leasecar.name*/
枚举 enum
声明符号名称表示整型常量
提高程序可读性
/*enum.c --使用枚举类型的值*/
#include <stdbool.h> // c99特性
enum spectrum
{ // spectrum作为enum的标记名
red,orange,yellow,green,blue,violet
/*枚举spectrum变量可能有的值:red,orange,yellow...、分别代表整数0-5
默认从0开始、每次累加
可以指定整数值 enum levels{low=100,medium=500,high=2000};
指定一个整数值,后面的自动累加 enum feline {cat,lynx = 10,puma,tiger}; 分别为0,10,11,12
只能在程序内部使用*/
};
const char *colors[] = {"red", "orange", "yellow", "green", "blue", "violet"};
#define LEN 30
char choice[LEN];
enum spectrum color; // enum spectrum作为类型名使用、声明enum变量、可以是任意整数类型 eg unsigned char color
bool color_is_found = false;
printf("red = %d,orange = %d\n ", red, orange); // 符号常量/枚举符、有名称的常量、可作为int类型常量使用、
puts("Enter a color (empty line to quit): ");
while (s_gets(choice, LEN) != NULL && choice[0] != '\0')
{ // 想要输入yellow时,输入对应的整型值2、或者读入字符串再进行比对转换
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: // 枚举符、int类型、可作为标签
puts("Roses are red. ");
break;
case orange:
puts("Poppies are orange . ");
break;
case yellow:
puts("sunflowers are yellow . ");
break;
case green:
puts("Grass is green. ");
case blue:
puts("Bluebells are blue. ");
break;
case violet:
puts("violets are violet . ");
break;
}
else
printf("I don't know about the color %s.\n", choice);
color_is_found = false;
puts("Next color, please (empty line to quit) : ");
}
puts("Goodbye! ");
getchar();
typedef
给类型自定义名称
1、为经常出现的类型创建一个方便、已识别的类型名
2、给复杂的类型命名
定义在函数中、函数作用域
定义在函数外面、文件作用域
define 定义常量、变量、编译开关、类型别名 typedef 定义类型的别名
define 预处理器直接替换字符串、不检查对错 typedef 编译器类型检查
define没有作用域限制 typedef 有自己的作用域
typedef unsigned char BYTE;
/*给unsigned char起一个类型别名BYTE、没有创建新的类型
BYTE别名遵循变量命名规则、通常全大写
别名利于程序可读性、BYTE便于理解此处需要数字
相当于#define BYTE unsigned char
typedef提高程序可移植性、sizeof和time()需要返回整数类型、根据平台选择最优整数类型
size_t和time_t被定义为适合平台的整数类型的别名、可以是unsigned long 或者unsigned long long
通用原型time_t time(time_t *); */
BYTE x, y[10], *Z; // 需要用到unsigned char替换BYTE
// char *STRING; // STRING:指向char的指针
typedef char *STRING; // STRING:char *类型的标识符
STRING name, sign; // 相当于: char * name,char * sign;
/*
#define STRING char * 只做替换
STRING name,sign;
char * name,sign;只有name是指针
*/
typedef struct complex
{ // 使用typedef时、标签可有可无
float real;
float imag;
} COMPLEX; // COMPLEX:struct complex类型标识
typedef struct
{
double x;
double y;
} rect;
rect r1 = {3.0,6.0}; // 相当于struct {double x; double y;) r1= {3.0,6.0};
rect r2; // 相当于struct {double x; double y;} r2;
r2 = r1; // 成员(名和类型)都匹配、结构类型相同可以赋值
typedef char(*FRPTC())[5]; // FRPTC 函数类型:返回(内含5个char元素的数组的首)指针
其他声明
1、[]和()优先级相同、从左往右结合
2、[]和()优先级高于*
int board[8][8]; // 声明一个内含int数组的数组
int goods[12][50]; /*从左往右结合、12个(内含50个int类型值的)数组组成的二维数组*/
int **ptr; // 声明一个指向指针的指针,被指向的指针指向int
int *risks[10];
/* 声明一个内含10个元素的数组,每个元素都是一个指向int的指针
[]优先级高 相当于int *(risks[10]) 指针作元素的数组*/
int(*rusks)[10];
/* 声明一个指针、指向数组,数组内含10个int类型的值
()和[]优先级相同、从左往右、指向数组的指针*/
int *oof[3][4];
/* 声明一个3×4的二维数组,每个元素都是指向int的指针
根据优先级:oof[3] 内含3个元素的数组
oof[3][4] 内含3个元素的数组、每个元素都是内含4个元素的数组
int * oof[3][4] 内含3个元素的数组、每个元素都是内含4个元素的数组、数组内元素都是int指针
预留12个指针空间*/
int(*uuf)[3][4]; // 声明一个指针、指向3×4二维数组,该数组中内含int类型值、预留一个指针空间
int(*uof[3])[4]; // 声明一个内含3个指针元素的数组,其中每个指针都指向一个内含4个int类型元素的数组
char *fump(int); // 函数、int参数、返回字符指针
char (*frump)(int); // 指向函数的指针,该函数:int参数、返回char
char (*flump[3])(int); // 内含3个指针的数组,每个指针都指向函数:int参数、返回char
typedef int arr5[5];
typedef arr5 *p_arr5;
typedef p_arr5 arrp10[10];
arr5 togs; // int togs[5]; togs 是一个内含5个int类型值的数组
p_arr5 p2; // int (*p2)[5]; p2是一个指向数组的指针,该数组内含5个int类型的值
arrp10 ap; // int (*ap[10])[5] ap 是一个内含10个指针的数组,每个指针都指向一个内含5个int类型值的数组
函数指针
函数的机器语言实现由载入内存的代码组成
函数指针存储函数代码起始地址
作为另一个函数的参数、告诉该函数要使用哪一个函数
eg qsort可对任何类型的数据排序、针对不同类型使用不同的比较函数、接受指向比较函数的指针、使用该函数提供的方案排序
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 *); // 不更改字符串
// typedef 函数指针
typedef void (*V_FP_CHARP)(char *);
void show(V_FP_CHARP fp, char *);
// int cmp(const void *a, const void *b)
// {
// return *(int *)a - *(int *)b;
// }
// qsort(num, n, sizeof(int), cmp);
// function1(sqrt); /*传递sqrt()函数的地址*/
// function2(sqrt(4.0)); /*传递sqrt()函数的返回值*/
char line[LEN];
char copy[LEN];
char choice;
void (*pfun)(char *);
/* (*pfun)声明一个指针、指向形参为char *的函数、返回值为void
需要声明函数类型、即确认函数返回类型和形参类型
void ToUpper(char *);将函数名ToUpper替换为(*pfun)、必须有括号
即可声明一个指向ToUpper函数的指针
void *pf(char *); //pf是一个返回字符指针的函数
*/
puts("Enter a string (empty line to quit) : ");
while (s_gets(line, LEN) != NULL && line[0] != '\0')
{
while ((choice = showmenu()) != 'n')
{
switch (choice) // switch语句设置指针
{
case 'u':
pfun = ToUpper;
/*声明之后可赋值类型匹配的函数
pfun = ToUpper();无效,ToLower()不是地址、返回值为void、不可赋值
pf =showmenu; 无效,showmenu与指针类型不匹配
*pf相当于ToUpper 可以(*pf)(line);
函数名是指针 pf=ToUpper 可以pf(line);*/
break;
case 'l':
pfun = ToLower; // 声明之后可赋值类型匹配的函数
break;
case 't':
pfun = Transpose;
break;
case 'o':
pfun = Dummy;
break;
}
strcpy(copy, line); // 为show() 函数拷贝一份
show(pfun, copy);
/* 根据用户的选择,使用选定的函数、fp=pfun
show(ToLower, copy) fp=ToLower*/
}
puts("Enter a string (empty line to quit) : ");
}
puts("Bye ! ");
#if 0
V_FP_CHARP pfun;
V_FP_CHARP arpf[4] = {ToUpper, ToLower, Transpose, Dummy}; // 声明并初始化一个函数指针的数组
int index = showmenu(); // 返回类型改为int,如果用户输入u,则返回0;如果用户输入t,则返回2,以此类推
while (index >= 0 & &index <= 3)
{
strcpy(copy, line); /*为show()拷贝一份*/
show(arpf[index], copy); /*使用选定的函数*/
index = showmenu();
}
#endif
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); // 转换为小写、或者tolower(getchar())
eatline(); // 清理输入行:只需要每一行的第一个字符、其他字符丢弃、避免影响下一次输入
while (strchr("ulton", ans) == NULL)
{ // 在字符串中找ans字符首次出现的位置、相当于while (ans != 'u' && ans != 'l' && ans != 't' && ans != 'o' && ans != 'n')
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)
{ /*2个形参、函数指针、char指针*/
(*fp)(str);
/*把用户选定的函数作用于字符串
void (*fp)(char *);void ToUpper(char *);fp=ToUpper ;
即*fp相当于ToUpper 函数、(*fp)(str)相当于ToUpper(str)
ToUpper函数名是指针、fp(str)相当于ToUpper(str)、待确认
*/
puts(str); // 显示结果
}
共享名称空间
名称空间:通过名称识别程序中的各部分
作用域是名称空间概念的一部分:
不同作用域的同名变量不冲突
相同作用域的同名变量冲突
在特定作用域中的结构标记、联合标记、枚举标记共享名称空间、与普通变量的空间不同
即相同作用域:
变量标记名称可以相同(但不建议、容易造成混乱、C++不支持)、
标记和标记、变量和变量不可以相同