IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言结构体中的柔性数组成员 -> 正文阅读

[C++知识库]C语言结构体中的柔性数组成员

考虑如下问题,我们试图定义一个名为Student的结构,这个结构应包括学生的姓名,学生已修课程的数量以及已修课程各科的分数。实践中,每个学生已修课程的数目是不一样的,这使得我们在定义用于存储分数的结构成员时面临两难的局面:

  • 如果将该数组定义得比较小,会存在某学生所修课程数量较多,存不下的情况。
  • 如果将该数组定义得很大,比如10000,则对于大多数学生而言,内存空间浪费严重。而且,无论将该数组定义得再大,理论上都存在实际数据超量,存不下的可能。

本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频

解决方案之一是把分数数组成员定义为一个指向float的指针,如下述C语言代码所示:

//Project - StudentScores
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    char sName[20]; //学生姓名
    int  n;         //已修课程数量
    float* scores;  //指针作为结构成员,分数数组
} Student;

int main() {
    Student s = {"Dorothy Henry", 4, NULL};
    printf("sizeof(s) = %lld, sizeof(s.sName) = %lld, "
           "sizeof(s.n) = %lld, sizeof(s.scores) = %lld\n",
           sizeof(s),sizeof(s.sName),sizeof(s.n),sizeof(s.scores));

    s.scores = calloc(s.n,sizeof(float));

    s.scores[0] = 80;  s.scores[1] = 90; s.scores[2] = 90; s.scores[3] = 80;
    float fSum = 0;
    for (int i=0;i<s.n;i++)
        fSum += s.scores[i];
    printf("Average score of %s: %f",s.sName,fSum/s.n);

    free(s.scores);
    return 0;
}

上述程序的执行结果为:

sizeof(s) = 32, sizeof(s.sName) = 20, sizeof(s.n) = 4, sizeof(s.scores) = 8
Average score of Dorothy Henry: 85.000000

第5 ~ 9行:定义了Student结构,其包含3个数据成员,分别是20个字节的学生姓名sName,4个字节的已修课程数量n,8个字节的分数”数组“指针scores。其3个成员的字节数相加,等于一个Student对象的尺寸32个字节。

对于Student结构而言,scores跟其它成员一样,只是数据成员,只不过类型特殊,是float*。

Student s = {"Dorothy Henry", 4, NULL};

第12行:s对象的初始化中,将s.n初始化为4,s.scores初始化为空指针。

s.scores = calloc(s.n,sizeof(float));

第17行:s.scores只是一个指针,要往s.scores”数组“里存分数前,需要手动申请需要的内存空间。这行代码为其申请了s.n,即4个float的空间。必要时,如果希望往s.scores“数组”中存入超过4个的分数,可以通过realloc()函数重新调整其动态内存的大小。

s.scores[0] = 80;  s.scores[1] = 90; s.scores[2] = 90; s.scores[3] = 80;
float fSum = 0;
for (int i=0;i<s.n;i++)
    fSum += s.scores[i];
printf("Average score of %s: %f",s.sName,fSum/s.n);

第19 ~ 23行:在分配了内存空间之后,s.scores指针便可以当成”数组“来使用。使用过程中,程序员会注意避免下标越界。这几行代码先把4个分数填入s.scores”数组”,然后再计算平均分并打印出来。

free(s.scores);

第25行:释放calloc()申请的动态内存。

这种使用指针成员来管理不定尺寸空间的方法需要程序员手动申请及释放内存,程序会变得比较零散。另外一个解决方案是使用结构的柔性数组成员(flexible array member)。请阅读下述C语言程序:

//Project - FlexMember
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    char sName[20];  //学生姓名
    int  n;          //已修课程数量
    float scores[];  //柔性数组成员
} Student;

int main() {
    unsigned int nBytes = sizeof(Student) + 4*sizeof(float);
    Student* s = malloc(nBytes);
    printf("sizeof(*s) = %lld, sizeof(s->sName) = %lld, "
           "sizeof(s->n) = %lld, nBytes = %lld\n",
           sizeof(*s),sizeof(s->sName),sizeof(s->n), nBytes);

    printf("s = %p, s->scores = %p\n", s, s->scores);

    s->n = 4;
    s->scores[0] = 80;  s->scores[1] = 90; s->scores[2] = 90; s->scores[3] = 80;
    float fSum = 0;
    for (int i=0;i<s->n;i++)
        fSum += s->scores[i];
    printf("Average score: %f",fSum/s->n);

    free(s);
    return 0;
}

上述程序的执行结果为:

sizeof(*s) = 24, sizeof(s->sName) = 20, sizeof(s->n) = 4, nBytes = 40
s = 0000000000711480, s->scores = 0000000000711498
Average score: 85.000000

说明:在读者的计算机上,执行结果中的地址很可能与本书不同。

typedef struct {
    char sName[20];  //学生姓名
    int  n;          //已修课程数量
    float scores[];  //柔性数组成员
} Student;

第5 ~ 9行:scores数组成员即为Student结构的柔性数组成员。柔性数组成员的定义要满足如下要求。

  • 该成员必须是结构的最后一个成员;
  • 该成员在语法上定义了一个不指定元素数量的“空”数组。
    事实上,对于一个Student类型的对象而言, 只有sName及n成员会被分配空间,scores成员是不占空间的。
unsigned int nBytes = sizeof(Student) + 4*sizeof(float);
Student* s = malloc(nBytes);

第12 ~ 13行:现假设我们要存4门课程的分数,通过一个Student的对象大小加上4个float的对象大小得到需要的内存字节数nBytes。然后,通过malloc()函数分配nBytes的堆空间,并把地址传给指针s。

printf("sizeof(*s) = %lld, sizeof(s->sName) = %lld, "
       "sizeof(s->n) = %lld, nBytes = %lld\n",
       sizeof(*s),sizeof(s->sName),sizeof(s->n), nBytes);

第14 ~ 16行:通过执行结果可以看到,sName成员占20个字节,n成员占4个字节。虽然我们事实上给s所指向的Student对象申请了nBytes = 40个字节的空间,但在编译器看来,*s,即s所指向的Student对象的大小只有24个字节。

printf("s = %p, s->scores = %p\n", s, s->scores);

第18行:把s,s->scores按地址格式输出。根据执行结果,我们可以画出该Student对象的内存结构图。
在这里插入图片描述
如果把s->scores的地址值减去s的地址值,差为24 = sizeof(Student)。这说明,结构的柔性数组成员事实上是一个指针,它指向紧随该对象的内存地址,其值恒等于对象地址+sizeof(类型)。换句话说:如果我们实际分配给结构对象的空间大于sizeof(Student),那么多出来的内存可以通过其柔性数组成员来访问。

Student s1;
printf("\n%p - %p",&s1,s1.scores);

如果我们直接定义类型为Student的变量s1,编译器会为s1分配sizeof(Student) = 24个字节的空间。但即便如此,s1.scores仍然会等于s1的地址+24。如果我们强行通过s1.scores进行数据访问,事实上访问的是不属于s1对象的空间,这是程序员需要小心避免的。

s->n = 4;
s->scores[0] = 80;  s->scores[1] = 90; s->scores[2] = 90; s->scores[3] = 80;
float fSum = 0;
for (int i=0;i<s->n;i++)
    fSum += s->scores[i];
printf("Average score: %f",fSum/s->n);

第20 ~ 25行:给s的柔性数组成员赋值,然后计算平均分并打印。由于我们确信s->scores所对应的内存空间属于s指向的结构对象,上述操作是安全的。

free(s);

第27行:一定不要忘了释放动态分配的内存空间。

请读者注意,将带有柔性数组成员的结构对象赋值给另外一个同类型对象是危险的:

Student s1;
s1 = *s;      //*s是存有4个分数的占40个字节空间的结构对象

对于编译器而言,s1和s都只有sizeof(Student) = 24字节的空间。从s到s1的赋值,只会拷贝前24个字节。同样的危险也会发生在函数传值时,函数的传值,可以认为是从实际参数到形式参数的赋值。

为了帮助更多的年轻朋友们学好编程,作者在B站上开了两门免费的网课,一门零基础讲Python,一门零基础C和C++一起学,拿走不谢!

简洁的C及C++
由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造
Python编程基础及应用
由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造

如果你觉得纸质书看起来更顺手,目前Python有两本,C和C++在出版过程中。

Python编程基础及应用

Python编程基础及应用实验教程
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 01:47:57  更:2022-09-15 01:51:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 14:55:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码