C-study(十四)

结构 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++不支持)、
标记和标记、变量和变量不可以相同

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值