「云顶书院」C语言复习笔记

MoyiTech
2022-11-05 / 1 评论 / 114 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年11月08日,已超过535天没有更新,若内容或图片失效,请留言反馈。

la3qh6zv.png

前言

说真的,在云顶书院的四周适应期简直太快太快了。
就感觉好像还没开始学啥呢,但转头一看,之前对于C语言还是萌新的我已经学会了好多好多了......
C语言的基本语法网上都有,在这就不再整理了,那就写点儿不一样的吧!
作为强迫症 在这几周的编码过程中时时刻刻都想着优化自己的代码:怎么写更快、更美、更易读、更可续。甚至有的代码只写了1个小时,但优化它的时间甚至都接近了10个小时。
在这儿主要是想梳理一些近期写过/遇到的比较有趣的代码

Amazing code

Let's enjoy it!

通过数组调用函数

在完成本周作业时,需要一个随机生成+-*/的功能,但是对生成的数字都有一定的范围要求(1~100),那么在生成随机数后 首先想到的是先把四个运算生成器封装成函数,再返回是否答对,但是到了这里,使用连续的if-else就显得不够那么“优雅”。于是就想到了将四个函数放到一个数组里,由于函数名并不能通过整数型和字符型这些来表示,于是就想到了指针:将函数的地址储存到一个长度为4的数组中。调用时就可以先使用随机数作为数组索引进行调用。
具体实现方式如下:

int (*exam_container[4]) (int i) = {add_exam, sub_exam, mul_exam, div_exam};

其中第一个int表示函数的返回值是int类型,*表示该数组储存的为指针,int i表示这四个函数的形参都是int i,后面大括号里面则是定义的四个函数的名称(注意不加括号)


接着又发现了一个弊端,以加法生成器add_exam()为例

//加法
int add_exam (int i) {
    //加法题目生成采用答案倒推法,避免使用while过多消耗系统资源
    int input;
    int res = random (1, 100);
    int add2 = random (1, res - 1); //保证add1 > 0
    int add1 = res - add2;
    printf ("%d + %d =", add1, add2);
    scanf ("%d", &input);

    exam_log[i][0] = add1;
    exam_log[i][1] = '+';//自动转化为ASCII码
    exam_log[i][2] = add2;
    exam_log[i][3] = res;
    exam_log[i][4] = input;

    if (input == res) {
        return 10;
    } else {
        return 0;
    }
}

其中int类型exam_log负责储存本次考试每道题的详情,这段代码显然在四个函数里是重复的,scanf和input==res同样也是。
这里就需要用函数返回一个数组,交由上层函数统一进行调用,于是就用到了指针函数: C 从函数返回数组

再经过一番修改,函数就变成这样了

//加法
int *add_exam () {
    static int outcome[4];
    //加法题目生成采用答案倒推法,避免使用while过多消耗系统资源
    int res = random (1, 100);
    int add2 = random (1, res - 1); //保证add1 > 0
    int add1 = res - add2;
    printf ("%d + %d =", add1, add2);
    outcome[0] = add1;
    outcome[1] = '+';//相当于返回的ASCII码
    outcome[2] = add2;
    outcome[3] = res;
    return outcome;
}

由于函数变为了指针函数,且无需参数了,那么定义储存函数指针的数组就变成这样了

int * (*exam_container[4]) () = {add_exam, sub_exam, mul_exam, div_exam};

很恐怖对不对?

完整代码待作业提交结束后奉上~

//written by moyi
//2022-11-04
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>

char data[100][3][20];
int count = 0;
int exam_log[10][5];
char user_id[50];

int login();
void initialize();
void read_file();
void write_file (char *user_id, int mark, int time);
int random (int start, int end);
int random_except (int start, int end, int except_num);
int print_info (char *user_id);
void exam (char *user_id);
void menu();

int *add_exam ();
int *sub_exam ();
int *mul_exam ();
int *div_exam ();

//把四种运算生成器放到数组里
//前面的*代表里面函数返回指针(这个试了好几分钟才发现的问题)
//后面的星号代表函数的地址
int * (*exam_container[4]) () = {add_exam, sub_exam, mul_exam, div_exam};

int main() {
    initialize();
    menu();
    return 0;
}

void menu(){
    while (1) {
        char input[50];
        printf ("\nMenu:\n1.开始测试\n2.检查分数\n3.退出\n\n请输入指令(1/2/3):");
        scanf ("%s", input);
        if (!strcmp (input, "1") ) {
            exam (user_id);
        } else if (!strcmp (input, "2") ) {
            if(!print_info (user_id)){
                printf("你没考过试!\n");
            }
        } else if (!strcmp (input, "3") ) {
            return;
        } else {
            system ("cls");
            printf ("看看你写的啥!\n\n");
        }
    }
}


//初始化
void initialize(){
    srand ( (unsigned int) time (NULL) );
    while(!login()){
        printf("\n格式错误!请重新输入!\n");
    }
}


//登录
int login(){
    printf ("What is your ID:");
    scanf ("%s", user_id);
    
    if (strlen (user_id) != 6) {
        return 0;
    }
    
    int i;
    for (i = 0; i < 6; i++) {
        if (i < 2) {
            if (user_id[i] > 'Z' || user_id[i] < 'A') {
                return 0;
            }
        } else {
            if (user_id[i] > '9' || user_id[i] < '0') {
                return 0;
            }
        }
    }
    
    system("cls");
    printf ("Login success!\nHello:%s\n", user_id);
    return 1;
}

//读文件
void read_file() {
    //r:文件不存在会报错
    //w+:文件被清空
    //a:只写在最后面
    //a+:读写,不会覆盖文件,没有文件也可以自动创建
    FILE *fp = fopen ("record.txt", "a+");

    char row[80];
    char *token;

    int i = 0;
    count = 0;//读取前重置计数器

    while (fgets (row, 80, fp) != NULL) {
        count++;
        token = strtok (row, ",");
        int j = 0;

        while (token != NULL) {
            if (j == 2) {
                token = strtok (token, "\n");
            }

            strcpy (data[i][j], token);
            token = strtok (NULL, ",");
            j++;
        }

        i++;
    }

    fclose (fp);
}

//写入文件
void write_file (char *user_id, int mark, int time) {
    FILE *fp = fopen ("record.txt", "a"); //a:追加到文件结尾,节省IO资源

    if (fp == NULL) {
        fprintf (stderr, "fopen() failed.\n");
        exit (EXIT_FAILURE);
    }

    fprintf (fp, "%s,%d,%d\n", user_id, mark, time);
    fclose (fp);
    return;
}

int random (int start, int end) {
    return rand() % (end - start + 1) + start;
}

//生成随机数时不包含某数
int random_except (int start, int end, int except_num) {
    int tmp;

    do {
        tmp = random(start, end);
    } while (tmp == except_num); //注意是取等才循环

    return tmp;
}

//加法
int *add_exam () {
    static int outcome[4];
    //加法题目生成采用答案倒推法,避免使用while过多消耗系统资源
    int res = random (1, 100);
    int add2 = random (1, res - 1); //保证add1 > 0
    int add1 = res - add2;
    printf ("%d + %d =", add1, add2);
    outcome[0] = add1;
    outcome[1] = '+';//相当于返回的ASCII码
    outcome[2] = add2;
    outcome[3] = res;
    return outcome;
}

//减法
int *sub_exam () {
    static int outcome[4];
    int sum = random (1, 100);
    int x = random (1, sum - 1); //保证res > 0
    int res = sum - x;
    printf ("%d - %d =", sum, x);
    outcome[0] = sum;
    outcome[1] = '-';//相当于返回的ASCII码
    outcome[2] = x;
    outcome[3] = res;
    return outcome;
}

//乘法
int *mul_exam () {
    static int outcome[4];
    //下面同样利用了100/a <= 100.0/a的限制,避免了使用while
    int res, a, b;
    a = random (1, 51);
    b = random (1, 100 / a);
    res = a * b;
    printf ("%d * %d =", a, b);
    outcome[0] = a;
    outcome[1] = '*';//相当于返回的ASCII码
    outcome[2] = b;
    outcome[3] = res;
    return outcome;
}

//除法
int *div_exam () {
    static int outcome[4];
    //除法跟乘法生成方式一样,换个参数就行
    int res, Divisor, Dividend;
    res = random (1, 51);
    Divisor = random (1, 100 / res);
    Dividend = res * Divisor;
    printf ("%d / %d =", Dividend, Divisor);

    outcome[0] = Dividend;
    outcome[1] = '/';//相当于返回的ASCII码
    outcome[2] = Divisor;
    outcome[3] = res;
    return outcome;
}

//考试函数
void exam (char *user_id) {
    int random_log[4];//已经出过的赋值1
    int input;
    int *p;
    int i, tmp;
    int mark = 0;//初始分数
    int start_time = time (0);//初始时间
    tmp = random (0, 3);//确保random_expect函数正确执行

    for (i = 0; i < 10; i++) {
        //用了个不太妙的方法
        //对后3个题目进行操作,保证四中运算均出现
        int flag=0;
        if(i > 6){
            int j;
            for(j=0;j<4;j++){
                if(!random_log[j]){
                    tmp = j;
                    flag = 1;
                }
            }
        }
        
        if(!flag){
            tmp = random_except (0, 3, tmp);//保证每次题目不同
        }
        random_log[tmp] = 1;
        
        p = exam_container[tmp] ();//调用出题函数获取数组
        scanf ("%d", &input);
        
        //判断答案是否正确
        if (input == * (p + 3) ) {
            mark += 10;
        }
        
        //赋值到exam_log,便于考试结束后输出
        exam_log[i][0] = *p;
        exam_log[i][1] = *++p;
        exam_log[i][2] = *++p;
        exam_log[i][3] = *++p;
        exam_log[i][4] = input;
    }

    int end_time = time (0);
    
    //写入数据
    write_file (user_id, mark, end_time - start_time);
    
    //输出考试详情
    printf ("\n\n    问题   | 正确答案 | 你的答案");
    for (i = 0; i < 10; i++) {
        printf ("\n%3d%3c%3d=?|%10d|%d", exam_log[i][0], exam_log[i][1], exam_log[i][2], exam_log[i][3], exam_log[i][4]);
    }
    printf ("\n\n本次得分:%d\n时间:%d秒\n\n\n", mark, end_time - start_time);
}

//输出该ID的考试记录
int print_info (char *user_id) {
    system("cls");
    printf ("你以前的记录是:");
    read_file();
    int res = 0;
    int i;

    for (i = 0; i < count; i++) {
        if (!strcmp (user_id, data[i][0]) ) {
            printf ("\n%s %3s %s秒", data[i][0], data[i][1], data[i][2]);
            res = 1;
        }
    }

    return res;
}

C语言随机数生成:rand和srand函数

原文链接

关于杨辉三角的一些思考

原文链接

超市老板那道题

原文链接

结语

保持学习,保持热爱!

0

评论 (1)

取消
  1. 头像
    小小黑
    Windows 10 · Google Chrome

    画图

    回复