CacheLab(附Excellent优化思路)

CacheLab(附Excellent优化思路)

前言

因为这部分我感觉我学起来确实困难想不清楚,遂决定做个笔记整理一下。写着写着想到“干脆就发个博客和同学们交流一下吧”,因此这篇博客就产生了!没有放出所有的源代码,仅仅是提供一些思路上的梳理和帮助😀!

作者才疏学浅,若内容有误,还请多多批评指正!

基础概念

主存

考虑主存地址为n位的计算机系统,主存大小即2n字节。

以B = 2b作为一个块的大小来和cache进行数据传输。

此时任一地址可以表示为:

Block Number(n-b位)Offset(b位)

举个例子,若主存(64位)中存放了两个int类型(4字节)的变量a,b,那么:

a:0x0000000000000000 -> 0x0000000000000003

b:0x0000000000000004 -> 0x0000000000000007

这里为了方便,我们把a,b十六进制的最后一位展开为二进制,其余位均为0,暂时忽略。

a:0000 -> 0011

b:0100 -> 0111

不难想到,为了正确的储存和传输a,b,这里的块的大小应该为4个字节(b=2),下同。

值得注意的是b的大小是可以自己定的,因为取数就是地址+偏移,怎么都能取到!也可以让b=3把a,b放在一个块里传输,但是这样就避开了我想讲的东西了!

此时,剩下的62位自然成为a,b的Block Number,a的Block Number为0,b的为1。

Cache

这时,我们引入一个Cache结构,包含S=2s个高速缓存组(set),每个组包含E个高速缓存行(line)。

特别地,这里我们考虑一个s=1,e=1的cache结构,包含2个组,每组一行。

我们希望达到两个效果:

1.主存中同组的那些块仅能映射到Cache中对应组的那些行中

2.相邻的块能被分到不同的组

因此,我们这样划分地址:

Tag(n-s-b位)Set(s位)Offset(b位)

引入int变量c:0x0000000000000008 -> 0x000000000000000b

最后四位:1000 -> 1011

我们利用a,b,c来观察这种划分有什么好处:

nameTag(61位)Set(1位)Offset(2位)
a000…00000 -> 11
b000…00100 -> 11
c000…01000 -> 11

可以看到,相邻的a,b不在同一组,而a,c在同一组,利用Tag来区分同一组内的不同块。

实际上,对于任意的一对(s,b)的地址划分都具备这样的性质。这样划分非常巧妙,我们不用引入任何新的变量来储存信息,只需要单纯地根据地址就可以完成信息的分组和编号。

而E代表cache内每一组能存放多少个block。主存中每两个block就有一个block属于第0组,这显然很多!属于同一组的block最多在cache中同时存在e个,否则就需要替换。

替换策略

策略有很多,这里实现一个简单但是有效的。

LRU策略的实现:

  • 缓存的每一块都设置一个计数器,初始时均为0。
  • 访问命中时,所有块的计数值与命中块的计数值进行比较,如果某块计数值小于命中块的计数值, 则该块的计数值加 1;如果该块的计数值大于命中块的计数值,则数值不变;最后将命中块的计数器清为0。
  • 访问未命中,需要替换/装入时,则选择计数值最大的块被替换/装入,其计数器清为0,而其它的计数器则加1(除了初始装入之外,计数值是不会出现相等情况的,可以思考一下为什么)。
  • (照抄的指导书)

Part1-Cache模拟器

实现一个Cache模拟器

这应该是一个较简单的部分,只要理解Cache的原理和构成就不难模拟。

结构

-S=2s个组

​ -每个组包含e列

​ -每一列能够储存valid,Tag和Block信息

方法

1.初始化

2.根据传入地址解析出必要信息,存取block

3.实现LRU替换策略

解析

此外,解析指令也非常重要,推荐使用getopt来解析命令行参数,使用fscanf(reader, "%llx,%d", &address, &size)来解析文件内指令!

Part2-Cache友好的矩阵转置

例子

给定Cache结构s=5,E=1,b=5,编写一个实现32x32矩阵转置的C语言程序,使得miss数尽可能小。

1.首先理解矩阵在内存中怎么储存

在b等于5的情况下,矩阵A[32][32]:

Set1Set2Set3Set4
Block0Block1Block2Block3
A[0][0]-A[0][7]A[0][8]-A[0][15]A[0][16]-A[0][23]A[0][24]-A[0][31]
Set4Set5Set6Set7
Block4Block5Block6Block7
A[1][0]-A[1][7]A[1][8]-A[1][15]A[1][16]-A[1][23]A[1][24]-A[1][31]
Set8Set9Set10Set11
Block8Block9Block10Block11
A[2][0]-A[2][7]A[2][8]-A[2][15]A[2][16]-A[2][23]A[2][24]-A[2][31]
Set12Set13Set14Set15
Block12Block13Block14Block15
A[3][0]-A[3][7]A[3][8]-A[3][15]A[3][16]-A[3][23]A[3][24]-A[3][31]
Set16Set17Set18Set19
Block16Block17Block18Block19
A[4][0]-A[4][7]A[4][8]-A[4][15]A[4][16]-A[4][23]A[4][24]-A[4][31]
Set20Set21Set22Set23
Block20Block21Block22Block23
A[5][0]-A[5][7]A[5][8]-A[5][15]A[5][16]-A[5][23]A[5][24]-A[5][31]
Set24Set25Set26Set27
Block24Block25Block26Block27
A[6][0]-A[6][7]A[6][8]-A[6][15]A[6][16]-A[6][23]A[6][24]-A[6][31]
Set28Set29Set30Set31
Block28Block29Block30Block31
A[7][0]-A[7][7]A[7][8]-A[7][15]A[7][16]-A[7][23]A[7][24]-A[7][31]

这里我只列出来了A[0][0] - A[7][31]这8*32个矩阵元素,之后还有3组这样的元素没有列出。s=5,cache中一共只有32个set,因此以后的3组元素的Set编号将会与以上相同,即Set0-31,因此,例如说,A[0][0]和A[8][0]两个数在Cache中将会因为相同的组编号而发生冲突(e=1). 此外,如果要访问B的元素B[i][j],因为题目中强调A和B的起始地址间隔保证了是Cache容量的整倍数,B[i][j]和A[i][j]也会发生冲突,即属于同一个组(Set)。

2.优化原理

下面给出一个简单分析:

实现一个32x32的矩阵转置至少要两步:

1.将所有数从矩阵A里读出来

2.将所有取出来的数写进矩阵B

读和写都记一次内存访问,一个块可以存8个数,要获取一个块至少要miss一次(miss或者miss+eviction)

因此,最少的miss次数也要有 32x32x2/8 = 256次

为了实现这个miss次数,我们必须保证对于每个块都只访问一次!换言之,我们优化的任务也就是尽量减少对同一个块的反复访问!

3.分块优化

考虑一个普通的矩阵转置函数:

//M=32 N=32
void trans(int M, int N, int A[N][M], int B[M][N])
{
    int i, j;
    for (i = 0; i < N; i++) {
        for (j = 0; j < M; j++) {
            B[j][i] = A[i][j];
        }
    }
}

这样的访问不具有良好的空间局部性。

为什么?就只看程序开始的一小部分:

     i = 0: {
        for (j = 0; j < 8; j++) //loop1
            B[j][i] = A[i][j];
        for (j = 8; j < 16; j++) //loop2
            B[j][i] = A[i][j];
	 }  

loop1中B[0-7][0]分别存放在8个块里,因此取这8个块分别访问,产生8个miss,紧接着loop2中的B[8-15][0]和B[0-7][0]组号相同,会把B[0-7][0]全部替换掉!loop1将这八个块都访问了,但是却都只利用了1/8!

不难想到一种分块方法,将32x32的矩阵分成16个8x8的子矩阵,把loop1访问的8个块充分利用了再替换成下8个块,从而减少miss数。下面实战一下!

16X16矩阵

1.题目分析

题目中给的Cache结构是s=4,E=1,b=5,这和上面例子是不一样的!请注意。

同理,分析一下矩阵在内存中的排布。

Set0Set1
Block0Block1
A[0][0]-A[0][7]A[0][8]-A[0][15]
Set2Set3
Block2Block3
A[1][0]-A[1][7]A[1][8]-A[1][15]
Set4Set5
Block4Block5
A[2][0]-A[2][7]A[2][8]-A[2][15]
Set6Set7
Block6Block7
A[3][0]-A[3][7]A[3][8]-A[3][15]
Set8Set9
Block8Block9
A[4][0]-A[4][7]A[4][8]-A[4][15]
Set10Set11
Block10Block11
A[5][0]-A[5][7]A[5][8]-A[5][15]
Set12Set13
Block12Block13
A[6][0]-A[6][7]A[6][8]-A[6][15]
Set14Set15
Block14Block15
A[7][0]-A[7][7]A[7][8]-A[7][15]

同样的,我只列出来了A[0-7][0-15]的这部分,剩下一半的组编号与这一半完全相同。

对于16x16矩阵我们该怎么分块?32x32是8x8,那么16x16是不是该分成4x4?什么是我们选择分块策略的决定性因素?

如果你不知道这个问题的答案,那么我也没办法,因为我也不知道,但是我们可以从下面的做法中得到启发。

(如果有人研究出了公式请务必教教我,我感觉是可以推导的,但是我不会。。。)

2.解法

首先确定目标: 16x16x2/8 = 64次,外加额外的3次miss(教程有说明),我们优化的极限是67次。

我们选取8x8的分块策略。

下面展示4个8x8块中的一块:i:0-7;j:0-7 。

Set(所在组)A[i][j]
000-7
210-7
420-7
630-7
840-7
1050-7
1260-7
1470-7

对这一块进行充分利用:

for (k = 0; k < 8; k++)//8x8的分块,k代表一个块的开始位置
{
    //取A[k][0-7]会占用Cache的第 2k-2 组
    tmp0 = A[k][0];
    tmp1 = A[k][1];
    tmp2 = A[k][2];
    tmp3 = A[k][3];
    tmp4 = A[k][4];
    tmp5 = A[k][5];
    tmp6 = A[k][6];
    tmp7 = A[k][7];
    //取B[0-7][k]占用Cache全部偶数组
    B[0][k] = tmp0;
    B[1][k] = tmp1;
    B[2][k] = tmp2;
    B[3][k] = tmp3;
    B[4][k] = tmp4;
    B[5][k] = tmp5;
    B[6][k] = tmp6;
    B[7][k] = tmp7;
}

分析:对于每一个k,读取A造成一次miss。k=0时,读取B造成8次miss,k>0时,由于读取A会挤占掉一个B的block,因此每个k会导致B重读一次,造成1次miss。

如果不算A,B相互替换而造成的miss数,A,B的block利用率均为100%,而相互排挤导致了7次额外的miss。这个块一共产生了8+7+8=23次miss。

那我们这样优化的结果是不是 23*4+3 = 95次呢?

还真不是,考虑以下情景:

for (k = 8; k < 15; k++)//8x8的分块,k代表一个块的开始位置
{
    //取A[k][0-7]会占用Cache的第 2k-2 组
    tmp0 = A[k][0];
    tmp1 = A[k][1];
    tmp2 = A[k][2];
    tmp3 = A[k][3];
    tmp4 = A[k][4];
    tmp5 = A[k][5];
    tmp6 = A[k][6];
    tmp7 = A[k][7];
    //取B[0-7][k]占用Cache全部奇数组
    B[0][k] = tmp0;
    B[1][k] = tmp1;
    B[2][k] = tmp2;
    B[3][k] = tmp3;
    B[4][k] = tmp4;
    B[5][k] = tmp5;
    B[6][k] = tmp6;
    B[7][k] = tmp7;
}

我们发现,这一块居然没有冲突!这一块只产生16个miss。

事实上,只有对角线上的块才会产生冲突,而别的块都没有这样的冲突!

因此,最后的miss数是: 23*2 + 16*2 +3 = 81!

这不就过了吗!

3.Excellent优化思路

聪明如你一定已经知道怎么优化了!

不知道的话还是要自己多思考一下哦~

没错,对角线上的块会产生14次额外的miss,这是我们不能忍受的。

怎么才能避免A和B的冲突?一个解决方案是开64个临时变量,将A的数据全部读取暂存,再全部放入B中。

很显然,上面只是我说着玩的,因为限制了只能有12个临时变量,那该怎么办呢?

没错,用B来暂存一下。寄存器有限,但是B我们可以随意更改呀~

核心思想是先暂存在B里,等A的下一行的元素访问完了再转置。

愿意写的自己写写吧,可以达到理论最优67个。

(我写的有点丑。。。完全可以写得更好!)

代码参考:

if (i == j)
                {
                    k = i;
                    tmp0 = A[k][j];
                    tmp1 = A[k][j + 1];
                    tmp2 = A[k][j + 2];
                    tmp3 = A[k][j + 3];
                    tmp4 = A[k][j + 4];
                    tmp5 = A[k][j + 5];
                    tmp6 = A[k][j + 6];
                    tmp7 = A[k][j + 7];

                    B[k][j] = tmp0;
                    B[k][j + 1] = tmp1;
                    B[k][j + 2] = tmp2;
                    B[k][j + 3] = tmp3;
                    B[k][j + 4] = tmp4;
                    B[k][j + 5] = tmp5;
                    B[k][j + 6] = tmp6;
                    B[k][j + 7] = tmp7;

                    tmp0 = A[k + 1][j];
                    tmp1 = A[k + 1][j + 1];
                    tmp2 = A[k + 1][j + 2];
                    tmp3 = A[k + 1][j + 3];
                    tmp4 = A[k + 1][j + 4];
                    tmp5 = A[k + 1][j + 5];
                    tmp6 = A[k + 1][j + 6];
                    tmp7 = A[k + 1][j + 7];

                    B[k + 1][j] = B[k][j + 1];
                    B[k][j + 1] = tmp0;

                    B[k + 1][j + 1] = tmp1;
                    B[k + 1][j + 2] = tmp2;
                    B[k + 1][j + 3] = tmp3;
                    B[k + 1][j + 4] = tmp4;
                    B[k + 1][j + 5] = tmp5;
                    B[k + 1][j + 6] = tmp6;
                    B[k + 1][j + 7] = tmp7;

                    tmp0 = A[k + 2][j];
                    tmp1 = A[k + 2][j + 1];
                    tmp2 = A[k + 2][j + 2];
                    tmp3 = A[k + 2][j + 3];
                    tmp4 = A[k + 2][j + 4];
                    tmp5 = A[k + 2][j + 5];
                    tmp6 = A[k + 2][j + 6];
                    tmp7 = A[k + 2][j + 7];

                    B[k + 2][j] = B[k][j + 2];
                    B[k + 2][j + 1] = B[k + 1][j + 2];
                    B[k][j + 2] = tmp0;

                    B[k + 1][j + 2] = tmp1;
                    B[k + 2][j + 2] = tmp2;
                    B[k + 2][j + 3] = tmp3;
                    B[k + 2][j + 4] = tmp4;
                    B[k + 2][j + 5] = tmp5;
                    B[k + 2][j + 6] = tmp6;
                    B[k + 2][j + 7] = tmp7;

                    tmp0 = A[k + 3][j];
                    tmp1 = A[k + 3][j + 1];
                    tmp2 = A[k + 3][j + 2];
                    tmp3 = A[k + 3][j + 3];
                    tmp4 = A[k + 3][j + 4];
                    tmp5 = A[k + 3][j + 5];
                    tmp6 = A[k + 3][j + 6];
                    tmp7 = A[k + 3][j + 7];

                    B[k + 3][j] = B[k][j + 3];
                    B[k + 3][j + 1] = B[k + 1][j + 3];
                    B[k + 3][j + 2] = B[k + 2][j + 3];
                    B[k][j + 3] = tmp0;

                    B[k + 1][j + 3] = tmp1;
                    B[k + 2][j + 3] = tmp2;
                    B[k + 3][j + 3] = tmp3;
                    B[k + 3][j + 4] = tmp4;
                    B[k + 3][j + 5] = tmp5;
                    B[k + 3][j + 6] = tmp6;
                    B[k + 3][j + 7] = tmp7;

                    tmp0 = A[k + 4][j];
                    tmp1 = A[k + 4][j + 1];
                    tmp2 = A[k + 4][j + 2];
                    tmp3 = A[k + 4][j + 3];
                    tmp4 = A[k + 4][j + 4];
                    tmp5 = A[k + 4][j + 5];
                    tmp6 = A[k + 4][j + 6];
                    tmp7 = A[k + 4][j + 7];

                    B[k + 4][j] = B[k][j + 4];
                    B[k + 4][j + 1] = B[k + 1][j + 4];
                    B[k + 4][j + 2] = B[k + 2][j + 4];
                    B[k + 4][j + 3] = B[k + 3][j + 4];
                    B[k][j + 4] = tmp0;

                    B[k + 1][j + 4] = tmp1;
                    B[k + 2][j + 4] = tmp2;
                    B[k + 3][j + 4] = tmp3;
                    B[k + 4][j + 4] = tmp4;
                    B[k + 4][j + 5] = tmp5;
                    B[k + 4][j + 6] = tmp6;
                    B[k + 4][j + 7] = tmp7;

                    tmp0 = A[k + 5][j];
                    tmp1 = A[k + 5][j + 1];
                    tmp2 = A[k + 5][j + 2];
                    tmp3 = A[k + 5][j + 3];
                    tmp4 = A[k + 5][j + 4];
                    tmp5 = A[k + 5][j + 5];
                    tmp6 = A[k + 5][j + 6];
                    tmp7 = A[k + 5][j + 7];

                    B[k + 5][j] = B[k][j + 5];
                    B[k + 5][j + 1] = B[k + 1][j + 5];
                    B[k + 5][j + 2] = B[k + 2][j + 5];
                    B[k + 5][j + 3] = B[k + 3][j + 5];
                    B[k + 5][j + 4] = B[k + 4][j + 5];
                    B[k][j + 5] = tmp0;

                    B[k + 1][j + 5] = tmp1;
                    B[k + 2][j + 5] = tmp2;
                    B[k + 3][j + 5] = tmp3;
                    B[k + 4][j + 5] = tmp4;
                    B[k + 5][j + 5] = tmp5;
                    B[k + 5][j + 6] = tmp6;
                    B[k + 5][j + 7] = tmp7;

                    tmp0 = A[k + 6][j];
                    tmp1 = A[k + 6][j + 1];
                    tmp2 = A[k + 6][j + 2];
                    tmp3 = A[k + 6][j + 3];
                    tmp4 = A[k + 6][j + 4];
                    tmp5 = A[k + 6][j + 5];
                    tmp6 = A[k + 6][j + 6];
                    tmp7 = A[k + 6][j + 7];

                    B[k + 6][j] = B[k][j + 6];
                    B[k + 6][j + 1] = B[k + 1][j + 6];
                    B[k + 6][j + 2] = B[k + 2][j + 6];
                    B[k + 6][j + 3] = B[k + 3][j + 6];
                    B[k + 6][j + 4] = B[k + 4][j + 6];
                    B[k + 6][j + 5] = B[k + 5][j + 6];
                    B[k][j + 6] = tmp0;

                    B[k + 1][j + 6] = tmp1;
                    B[k + 2][j + 6] = tmp2;
                    B[k + 3][j + 6] = tmp3;
                    B[k + 4][j + 6] = tmp4;
                    B[k + 5][j + 6] = tmp5;
                    B[k + 6][j + 6] = tmp6;
                    B[k + 6][j + 7] = tmp7;

                    tmp0 = A[k + 7][j];
                    tmp1 = A[k + 7][j + 1];
                    tmp2 = A[k + 7][j + 2];
                    tmp3 = A[k + 7][j + 3];
                    tmp4 = A[k + 7][j + 4];
                    tmp5 = A[k + 7][j + 5];
                    tmp6 = A[k + 7][j + 6];
                    tmp7 = A[k + 7][j + 7];

                    B[k + 7][j] = B[k][j + 7];
                    B[k + 7][j + 1] = B[k + 1][j + 7];
                    B[k + 7][j + 2] = B[k + 2][j + 7];
                    B[k + 7][j + 3] = B[k + 3][j + 7];
                    B[k + 7][j + 4] = B[k + 4][j + 7];
                    B[k + 7][j + 5] = B[k + 5][j + 7];
                    B[k + 7][j + 6] = B[k + 6][j + 7];
                    B[k][j + 7] = tmp0;

                    B[k + 1][j + 7] = tmp1;
                    B[k + 2][j + 7] = tmp2;
                    B[k + 3][j + 7] = tmp3;
                    B[k + 4][j + 7] = tmp4;
                    B[k + 5][j + 7] = tmp5;
                    B[k + 6][j + 7] = tmp6;
                    B[k + 7][j + 7] = tmp7;
                }

32X32矩阵

1.解法

都写到这里了,我也懒得再画一遍内存图了,大家自己画一画就好了。基本的思想和之前还是一模一样的!

唯一不同于16x16的是,我们发现如果8x8分块的话,不仅A,B之间有冲突,甚至就连A,B内部上四行和下四行都有冲突!

难道要用4x4?不行,4x4引入新问题:一个block有8个数,你都只用4个,那miss率不得上天去了!

因此我们想要结合两者的优点:使用8x8的分块,但是在内部以4x4为单位操作,先完全操作上半4x8,再操作下半。

提示:在考虑能不能AC而不是Excellent时,应先不要考虑A1A2 和B1B2的冲突,而是着眼于A1A2 和A3A4的冲突

因为只有对角线块会产生A,B之间的冲突,而每个块A1A2 和 A3A4都是冲突的!

如果把一个8x8的块分为4份:

A1A2
A3A4

块内的操作可以分成以下步骤:

  • 步骤一
    • 将A1, A2数据复制给B1, B2
  • 步骤二
    • 将A3的数据传给B2,同时,B2的数据传给B3(这里注意如何利用8个临时变量来保证不会产生额外的miss,这个比较tricky)
  • 步骤三
    • 将A4复制给B4

代码就自己写啦~

2.miss分析

非对角线块只有上下冲突,我们已经解决了,因此只有16个miss!还是很牛的!

但是还有4大个对角线块,我们没有做任何优化,因此每个对角线块都会额外产生19个miss!恁多!

幸运的是,即便如此,总miss仍然小于400!过了!!那要不然就不优化了吧。

3.Excellent优化思路

不行,就是要优化!

同样的,优化的思路就是去尝试解决对角线块的冲突问题。

我的想法很简单,块内有3个操作步骤,每一步都可以当做独立的一部分进行优化(就像16x16矩阵中的那样)

但是我没办法优化优化到理论最优解,作业太多,遂放弃。

具体就不细讲了,感兴趣的自己可以研究一下。

补:

我找到一篇文章,里面的思路可以把这道题优化到理论最优解259次(只能膜了,非常巧妙)

https://zhuanlan.zhihu.com/p/387662272

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
学生成绩管理系统可以使用链表法来进行设计,链表是一种常用的数据结构,可以方便地实现插入、删除、查找等操作。下面是使用链表法实现学生成绩管理系统的思路: 1. 定义链表节点结构体,包括学生信息和成绩等数据成员,同时定义指向下一个节点的指针。 ``` struct Node { char no[10]; // 学号 char name[20]; // 姓名 char sex[10]; // 性别 char class[20]; // 班级 float chinese; // 语文成绩 float math; // 数学成绩 float english; // 英语成绩 float physics; // 物理成绩 float chemistry; // 化学成绩 float biology; // 生物成绩 float total; // 总分 float average; // 平均分 int rank; // 排名 struct Node *next; // 指向下一个节点的指针 }; ``` 2. 创建链表,包括初始化链表头节点、添加节点等操作。 ``` struct Node *head = NULL; // 链表头节点 // 初始化链表头节点 head = (struct Node*)malloc(sizeof(struct Node)); strcpy(head->no, ""); strcpy(head->name, ""); strcpy(head->sex, ""); strcpy(head->class, ""); head->chinese = 0; head->math = 0; head->english = 0; head->physics = 0; head->chemistry = 0; head->biology = 0; head->total = 0; head->average = 0; head->rank = 0; head->next = NULL; // 添加节点 struct Node *p = NULL; // 新节点 p = (struct Node*)malloc(sizeof(struct Node)); strcpy(p->no, "20210101"); strcpy(p->name, "张三"); strcpy(p->sex, "男"); strcpy(p->class, "2021"); p->chinese = 90; p->math = 80; p->english = 85; p->physics = 75; p->chemistry = 80; p->biology = 70; p->total = p->chinese + p->math + p->english + p->physics + p->chemistry + p->biology; p->average = p->total / 6.0; p->rank = 1; p->next = head->next; head->next = p; ``` 3. 实现学生成绩管理系统的各个功能模块,包括录入学生信息和成绩、计算学生成绩、查询学生成绩、修改学生成绩、删除学生成绩、统计学生成绩等。 ``` // 录入学生信息和成绩 void add_student() { struct Node *p = NULL; // 新节点 p = (struct Node*)malloc(sizeof(struct Node)); printf("请输入学号:"); scanf("%s", p->no); printf("请输入姓名:"); scanf("%s", p->name); printf("请输入性别:"); scanf("%s", p->sex); printf("请输入班级:"); scanf("%s", p->class); printf("请输入语文成绩:"); scanf("%f", &p->chinese); printf("请输入数学成绩:"); scanf("%f", &p->math); printf("请输入英语成绩:"); scanf("%f", &p->english); printf("请输入物理成绩:"); scanf("%f", &p->physics); printf("请输入化学成绩:"); scanf("%f", &p->chemistry); printf("请输入生物成绩:"); scanf("%f", &p->biology); p->total = p->chinese + p->math + p->english + p->physics + p->chemistry + p->biology; p->average = p->total / 6.0; p->rank = 1; p->next = head->next; head->next = p; } // 计算学生成绩 void calculate_score() { struct Node *p = head->next; while(p != NULL) { p->total = p->chinese + p->math + p->english + p->physics + p->chemistry + p->biology; p->average = p->total / 6.0; p = p->next; } } // 查询学生成绩 void search_score() { char no[10]; printf("请输入要查询的学生学号:"); scanf("%s", no); struct Node *p = head->next; while(p != NULL) { if(strcmp(p->no, no) == 0) { printf("学号:%s\t姓名:%s\t性别:%s\t班级:%s\n", p->no, p->name, p->sex, p->class); printf("语文:%5.1f\t数学:%5.1f\t英语:%5.1f\t物理:%5.1f\t化学:%5.1f\t生物:%5.1f\n", p->chinese, p->math, p->english, p->physics, p->chemistry, p->biology); printf("总分:%5.1f\t平均分:%5.1f\t排名:%d\n", p->total, p->average, p->rank); return; } p = p->next; } printf("未找到该学生!\n"); } // 修改学生成绩 void modify_score() { char no[10]; printf("请输入要修改的学生学号:"); scanf("%s", no); struct Node *p = head->next; while(p != NULL) { if(strcmp(p->no, no) == 0) { printf("请输入修改后的语文成绩:"); scanf("%f", &p->chinese); printf("请输入修改后的数学成绩:"); scanf("%f", &p->math); printf("请输入修改后的英语成绩:"); scanf("%f", &p->english); printf("请输入修改后的物理成绩:"); scanf("%f", &p->physics); printf("请输入修改后的化学成绩:"); scanf("%f", &p->chemistry); printf("请输入修改后的生物成绩:"); scanf("%f", &p->biology); p->total = p->chinese + p->math + p->english + p->physics + p->chemistry + p->biology; p->average = p->total / 6.0; return; } p = p->next; } printf("未找到该学生!\n"); } // 删除学生成绩 void delete_score() { char no[10]; printf("请输入要删除的学生学号:"); scanf("%s", no); struct Node *p1 = head->next; struct Node *p2 = head; while(p1 != NULL) { if(strcmp(p1->no, no) == 0) { p2->next = p1->next; free(p1); printf("删除成功!\n"); return; } p2 = p1; p1 = p1->next; } printf("未找到该学生!\n"); } // 统计学生成绩 void statistics_score() { float chinese_sum = 0, math_sum = 0, english_sum = 0, physics_sum = 0, chemistry_sum = 0, biology_sum = 0; int count = 0, pass_count = 0, excellent_count = 0; struct Node *p = head->next; while(p != NULL) { chinese_sum += p->chinese; math_sum += p->math; english_sum += p->english; physics_sum += p->physics; chemistry_sum += p->chemistry; biology_sum += p->biology; count++; if(p->total >= 60) { pass_count++; } if(p->total >= 90) { excellent_count++; } p = p->next; } printf("班级语文平均分:%5.1f\t数学平均分:%5.1f\t英语平均分:%5.1f\t物理平均分:%5.1f\t化学平均分:%5.1f\t生物平均分:%5.1f\n", chinese_sum/count, math_sum/count, english_sum/count, physics_sum/count, chemistry_sum/count, biology_sum/count); printf("班级及格率:%5.1f%%\t班级优秀率:%5.1f%%\n", pass_count*100.0/count, excellent_count*100.0/count); } ``` 以上是使用链表法实现学生成绩管理系统的思路
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值