Android NDK 5 C语言结构体

前言

结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。与 Java 中的类类似,可以有成员,包括变量和函数(通过函数指针实现)。

一、结构体 struct

关键字 struct 能定义各种类型的变量集合,称为结构(structure),并把它们视为一个单元。以下是一个简单的结构声明例子:

struct Person {
    int age;
    int height;
} serah;

该例子声明了一个结构类型 Person,它是一个新的类型,类型名称通常称为结构标记符(structure tag)或标记符名称(tag name)。其内部的变量 age 和 height 称为成员(members)或字段。

在结构体中的成员可以使任何类型的变量,要注意的是初始化不能放在这里,因为现在是定义类型的成员,而不是在声明变量。结构类型是一种说明或一种蓝图,可以用于定义该类型的变量

1.1、定义结构类型和结构变量

可以将结构的声明和结构变量的声明分开,如下所示:

struct Person {
    int age;
    int height;
    char name[20];
    char mate[20];
};

struct Person lilith = {17, 172, "Lilith", "Adam"};

定义存储结构的新变量时,需要 struct 关键字,但没有关键字,代码看起来更加简单、更容易理解。使用 typedef 定义,声明变量时就可以删除 struct 关键字。例如:

typedef struct Person Person;

这个语句把 Person 定义为 struct Person。如果把这个定义放在源文件开头,就可以定义 Person 类型的变量:

 Person lilith = {17, 172, "Lilith", "Adam"};

不需要在使用关键字 struct,使代码更加简洁,看起来像是普通的类型。

1.2、访问结构成员

结构变量的名称不是一个指针,所以需要特殊的语法访问这些成员。成员的引用方式为:在结构体变量名称后面加“.”再加上成员变量名称。例如:

lilith.age  = 17;

结构变量名称混合成员变量名称之间的句点称为“成员选择运算符”。在上一小结中结构变量的初始化需要以正确的顺序获得字段的初始值,这对于 Person struct 不成问题,但结构若有很多成员,就有问题了。

其实在初始化列表中可以指定成员名,如下:

Person lilith = {
                    .height = 172, .age = 17, .name = "Lilith",
                    .mate = "Adam"
                };

1.3、未命名的结构(匿名结构)

用一条语句声明结构和该结构的实例时,可以省略标记符的名字,以之前的例子为例:

struct
{
    int age;
    int height;
    char name[20];
    char mate[20];
} lilith;

使用这种方式的最大缺点是不能在其他语句中定义这个结构的其他实例。这个结构类型的所有变量必须在一行语句中定义。

1.4、结构指针

要获取结构的地址,就需要使用结构的指针。由于需要的是结构的地址,因此就需要声明结构的指针。结构指针的声明方式和声明其他类型的指针变量相同,例如:

Person *adam = NULL;

以上语句声明了一个 Person 类型指针,它可以存储 Person 类型的结构地址。现在可以将 adam 设置为一个特定结构的地址值,使用的方法和其他类型的指针完全相同。

示例代码:

Person lilith = {17, 12, "Lilith","Adam"};
lilith.age = 18;
h1.height = 173;

Person *eve;
eve = &lilith;

printf("lilith: name is %s, age is %d, height is %d.\n", lilith.name, lilith.age, lilith.height);
printf("eve: name is %s, age is %d, height is %d.\n", eve->name, eve->age, eve->height);

-> 等价于 (*XXX),执行结果:

lilith: name is Lilith, age is 18, height is 173.
eve: name is Lilith, age is 18, height is 173.

1.5、为结构动态分配内存

要为结构动态分配内存,可以使用结构指针数组,其声名如下:

Person* angels[7];

以上代码声明了 7 个指向 Person 结构的指针数组。该语句只给指针分配了内存。还需要分配一些内存来存储每个结构的成员。

示例代码:

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

typedef struct Person Person;

struct Person
{
    int age;
    int height;
    char name[20];
    char mate[20];
};

int main(void) {
    Person* angels[7];
    int pcount = 0;
    char test = '\0';

    for(pcount = 0; pcount < sizeof(pcount)/sizeof(Person*); ++pcount) {
       printf("Do you want to enter details of a%s angel (Y or N)?", pcount?"nother" : "");
       scanf(" %c", &test);
       if(tolower(test) == 'n') break;

       // 动态分配内存
       angels[pcount] = (Person*)malloc(sizeof(Person));

       printf("Enter the name of the angel:");
       scanf("%s", angels[pcount]->name);
       printf("How old of the %s? ", angel[pcount]->name);
       scanf("%d", &angels[pcount]->age); // -> 优先级大于 &
       printf("How height of the %s? ", angels[pcount]->name);
       scanf("%d", &angels[pcount]->height);
    }

    printf("\n");
    for(int i = 0; i < pcount; ++i) {
        printf("%s is %d years old, %d hands high, ", angels[i]->name, angels[i]->age, angels[i]->height);
        free(angels[i]); // 注意释放动态分配的内存
    }
    return 0;
}

该示例代码与之前的类似,但是其运行并不相同。一开始并没有为任何结构分配内存。仅仅是声明了 7 个 Person 类型的指针,还要井结构放在指针指向的地址中,以下语句会为每个结构体分配内存空间:

angels[pcount] = (Person*)malloc(sizeof(Person));

这个例子给 Person 类型使用 sizeof 运算符来提供变元。使用 sizeof 运算符可以计算出结构体所占的字节数,其结构不一定对应于结构中各个成员所占的字节总数和。除了 char 类型变量外, 2 字节变量的启始地址常常是 2 的整数被,四字节变量的启始地址常常是 4 的整数倍,以此类推。这称为边界调整(boundary alignment),它和 C 语言无关,而是硬件要求。以这种方式在内存中存储变量,可
以更快地在处理器和内存之间传递数据,但不同类型的成员变量之间会有未使用的字节。详情请参考《深入理解计算机操作系统第》三章和第九章相关内容。

注意:C 的 _Alignof 运算符可以用于确定变量的边界调整量。在变量的声明中使用 _Alignof(type),可以强制根据特定的类型进行边界调整。

二、结构成员

所有基本数据类型(含数组)都可以成为结构的成员。除此之外,还可以把一个结构作为为另一个结构的成员,不仅指针可以是结构的成员,结构指针也可以是结构的成员。而这也增加了潜在的危险。

2.1、将结构作为另一个结构的成员

看以下示例代码:

结构体:

typedef struct Date Date;

struct Date
{
    int day;
    int month;
    int year;
};

struct Person
{
    Date dob;
    int height;
    char name[20];
    char mate[20];;
}

使用:

Person adam;

adam.height = 175;

adam.dob.day = 24;
adam.dob.month = 12;
adam.dob.year = 0;

Person eve;

eve.dob = adam.dob;

2.2、声明结构中的结构

可以在 Person 结构的定义中声明 Date 结构,如下:

struct Person
{
    struct Date
    {
        int day;
        int month;
        int year;
    } dob;
    int height;
    char name[20];
    char mate[20];
};

这个声明将 Date 结构声明放在结构 Person 的定义中,因此不能在 Person 结构的外部声明 Date 变量。当然,每个 Person 类型的变量都包含 Date 类型的成员 dob。但是以下语句会导致编译错误:

struct Date date;

如果需要在 Person 结构的外部使用 Date,就必须将它定义在 Person 结构之外。

2.3、将结构指针用于结构成员

任何指针都可以是结构的成员,包含结构指针在内。结构指针可以指向相同类型的结构(例如链表)。示例代码:

struct Person
{
    int age;
    int height;
    Person* next;
};

将以上代码中结构 Person 添加前向 Person 指针就可以构成双向链表,这样就可以进行双向遍历。

struct Person
{
    int age;
    int height;
    char name[20];
    Person* next;
    Person* previous;
};

三、结构与函数

3.1、结构作为函数变元

将结构作为变元传给函数和传递一般变量并没用什么不同,声明结构:

struct Family
{
    char name[20];
    int age;
    char father[20];
    char monther[20];
};

定义函数变元类型为结构,代码如下:

bool siblings(Family member1, Family member2) {
    if(strcmp(member1.monther, member2.monther) == 0) return true;
    else return false;
}

3.2、结构指针作为函数变量

在调用函数时,传送给变元的是变元的副本。如果变元是一个非常大的结构,就需要消耗大量的时间和内存空间。在这种情况下可以使用结构指针作为变元。重写以上示例:

bool siblings(Family* member1, Family* member2) {
    if(strcmp(member1->monther, member2->monther) == 0) return true;
    else return false;
}

但是这又一个缺点,按值传递机制禁止在被调用的函数中意外地改变变元值。如果使用指针,就丧失了这个优点。如果不需要改变指针变元的值(只是访问使用),把指针传送函数还是可以获得某种程度的保护,此时应使用
const 修饰符。重写以上函数:

bool siblings(Family const *member1, Family const *member2) {
    if(strcmp(member1->monther, member2->monther) == 0) return true;
    else return false;
}

注意:

  • Family const member1:指向 Family 结构类型的常量指针*;
  • Family const *member1:指向常量结构的指针;

3.3、作为函数返回值的结构

函数返回结构和返回一般数值一样,可以从函数中返回一个结构,但是比较方便的做法是返回结构指针。当然,在返回结构指针时,结构应在堆上创建。下面通过实例讨论其细节。

示例代码:

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

typedef struct Family Family;
typedef struct Date Date;

Family *get_person(void);
void show_people(bool forwards, Family *pf, Family *pl);
void release_memory(Family *pf);

struct Date
{
    int day;
    int month;
    int year;
};

struct Family
{
    Date dob;
    char name[20];
    char father[20];
    char monther[20];
    Family* next;
    Family* previous;
};

int main(void) {
    Family* first = NULL;
    Family* current = NULL;
    Family* last = NULL;
    char more = '\0';
    while(true) {
        printf("\nDo you want to enter details of a%s person (Y or N)?", first != NULL?"nother" : "");
        scanf(" %c", &more);
        if(tolower(more) == 'n') break;

        current = get_person();

        if(first == NULL) first = current;
        else {
            last->next = current;
            current->previous = last;
        }
        last = current;
    }
    show_people(true, first, last);
    release_memory(first);
    first = last = NULL;
    return 0;
}

Family *get_person(void) {
    Family *temp = (Family*)malloc(sizeof(Family));
    printf("Enter the name of the person:");
    scanf("%s", temp->name);
    printf("Enter %s's date of birth (day month year); ", temp->name);
    scanf("%d %d %d", &temp->dob.day, &temp->dob.month, &temp->dob.year);
    printf("\nWho is %s's father? ", temp->name);
    scanf("%s", temp->father);
    printf("\nWho is %s's monther? ", temp->name);
    scanf("%s", temp->monther);
    temp->next = temp->previous = NULL;
    return temp;
}

void show_people(bool forwards, Family *pf, Family *pl) {

    printf("\n");
    for(Family *p = forwards ? pf : pl; p != NULL; p = forwards ? p->next : p->previous) {
        printf("%s was born %d%d%d and has %s and %s as parents.\n",
                p->name, p->dob.day, p->dob.month, p->dob.year, p->father, p->monther);
    }
}

void release_memory(Family *pfirst) {
    Family *pcurrent = pfirst;
    Family *temp = NULL;
    while(pcurrent) {
        temp = pcurrent;
        pcurrent = pcurrent->next;
        free(temp);
    }
}

重点看 get_person() 函数,它首先是执行动态分配堆内存

Family *temp = (Family*)malloc(sizeof(Family));

temp 是 “Family 类型结构的指针”,并且是本地对象,只存在于函数体内。以上语句给 Family 类型分配内存空间,并将返回地址保存在了指针变量 temp 中。temp 是本地变量,在 get_person() 函数结束时,temp 就不存在了,但是 malloc() 函数分配的内存是永久的,可以在程序的某个地方释放该内存,或在退出程序时释放它。

get_person() 的最后一条语句为:

return temp;

这行语句返回结构指针的副本。

参考

Beginning C

深入理解计算机操作系统

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容

  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,745评论 2 9
  • “去超市,要不要去吃饭?” “好啊” “要不要来采购,顺便吃饭?” “好啊” “那家餐厅太远,就吃上次那家吧?” ...
    妮可奥特曼阅读 304评论 0 0
  • 本篇是深入的Node的读书笔记,相关知识点的归纳以及自己的简介《深入Node》 Node架构一览 libuv架构 ...
    Mr_Treasure阅读 537评论 0 1
  • 今天是农历腊月二十三,传统意义上的“辞灶节”又称“小年”!过了小年,以后这每一天就进入春节倒计时了,人们开始杀鸡宰...
    五颜六色的圆圈小宿阅读 224评论 0 1
  • 外资企业面试问题及答案: 1.请你自我介绍一下? 我是你爹。 2.谈谈你的家庭情况? 父母双亡,有车有房,现在的性...
    大友检阅读 1,241评论 3 6