BUAA程设第三周上机总结

写在前面

上周的上机题有些太难了,然后这周的题经过了超级削弱之后同学们做起来可能感觉十分轻松,然后暴露出来的问题也比较少。所以今天除了知识点外再稍微提提做题的技巧吧。

左移、右移与位运算

有、无符号位运算

在C语言中,位运算分为两类,分别是有符号位运算和无符号位运算。这两种位运算的区别主要体现在右移过程中。

因为位运算是直接针对数据的补码进行按位操作,那么在处理有符号负数的补码时会出现一些歧义。这里拿 − 1 -1 1 做例子:

− 1 -1 1 的补码是 1111 1111 1111 1111 1111 1111 1111 1111,对它进行左移操作毫无疑问是是在右侧补零的,这没有任何异议。即对 − 1 -1 1 左移一位后他的补码会变成 1111 1111 1111 1111 1111 1111 1111 1110,对应的数字是 − 2 -2 2

但是在右移操作时,左侧补零与否就是一个问题:补零的话就会改变这个数字的符号(由负数变成正数),但是如果只看补码的话左侧补零也是有着道理的。这就引出了有符号右移和无符号右移。

先说结论,在C语言中对有符号类型数据(如intlong longchar)进行右移操作是有符号右移;对无符号类型数据(如unsigned intunsigned long longunsigned char)进行右移操作时无符号右移。

这两者的区别就在于左侧是否补零:有符号数字在右移时左侧会默认补一(不改变符号位),无符号数字在右移时左侧会补零(无符号数字没有符号位)。

举例来说,1111 1111 1111 1111 1111 1111 1111 1111 这个补码既可以表示 int 类型的 − 1 -1 1,也可以表示 unsigned int 4 , 294 , 967 , 295 4,294,967,295 4,294,967,295 。如果是 int 类型,那么对他右移的结果是左侧会补一,补码仍是 1111 1111 1111 1111 1111 1111 1111 1111 ,依然表示 − 1 -1 1 。如果是 unsigned int 类型,那么对他右移的结果是左侧补零,补码会变成 0111 1111 1111 1111 1111 1111 1111 1111,表示 2 , 147 , 483 , 647 2,147,483,647 2,147,483,647

左移、右移的位数不要超过类型本身的位数

这句话的意思是,举例来说 int 的位数是 32 位,那么在左移 / 右移的时候移位的位数不要超过32。如果移位的位数超过了32,那么结果是未知的。(进入了混沌领域)

同学们记住不要这么做就可以了。

数组的二三事

数组初始化

在声明数组的时候,可以直接对数组进行初始化。常用的是直接在数组中存入我们可能需要用到的数字,或者是全部初始化为零。

int month[13] = {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //在声明数组的时候直接进行初始化
int num[1000] = {0}; //将数组中的1000个元素全部置零

//对于字符串数组,可以直接用字符串进行初始化,如:
char str[100] = "Hello world!";

其中全部置零的写法比较常见,希望同学们记住并掌握。(其实还有一些其他的规则,但是防止同学们学习后写出一些奇怪的代码所以就不多加介绍了,同学们目前只需要掌握这两种初始化的方法就可以了)

char类型数组

关于 char 数组的问题主要在于,一般而言我们都是将 char 类型的数组视为字符串。而字符串需要一个结尾标识符 \0 。即你的 char 类型数组的容量应该至少比其存储的字符串长度多 1 1 1。这个错误同学们常常在使用字符数组读取字符串时出错,数组开的不够大导致 REG 错误。由于同学们目前还没有系统地接触字符串及字符串处理函数,所以这里不进行过多拓展。

数组容量在合理范围内稍微开大一点

比如题目给的数据范围是一千以内,在开数组的时候就不要正好开一个一千大小的数组去存数据,要稍微开的大一点,比如一千一百。

这样做的好处是,防止你在写出这样的代码时出错:

int num[1000];
for(int i = 1; i <= 1000; i++)
	scanf("%d", &num[i]);    //ERROR!

这个代码错在只给 num 数组定义了 1000 1000 1000 容量,那么可用的下标范围是 0 ~ 999 0~999 0999 ,访问到 num[1000] 时就会出错。

防止此类问题的最好方式就是把数组在合理范围内稍微开的大一点。(球球不要卡着数据范围开数组了)

0到1000有1001个数

我发现很多同学在使用循环时不是很理解 0 0 0 n n n 总共有 n + 1 n+1 n+1 个数字这件事。

但是我又不知道怎么解释这个事情。。。

0 0 0 n − 1 n-1 n1 或从 1 1 1 n n n 都有 n n n 个数字,但是从 0 0 0 n n n 就会有 n + 1 n+1 n+1 个。。。希望同学们能记住这个错误。

总之就给出两个遍历读取方式:

for(int i = 0; i < n; i++)
	scanf("%d", &num[i]);
//数字将存储在num[0]到num[n-1],总计n个数

for(int i = 1; i <= n; i++)
	scanf("%d", &num[i]);
//数字将存储在num[1]到num[n],总计n个数

遍历数组也是使用相同的方式。

数据类型要和格式符相对应

不要出现这样的代码:

long long a;
scanf("%d", &a); //Wrong!

对于一种类型的数据,就要使用相对应的格式符读取、输出,不要使用不匹配的格式符,很容易出bug。(在以后同学们水平高了之后可能会接触相关的知识,但是现在千万不要这么尝试)

养成良好的编程习惯

唔,有同学询问如何养成良好的编程习惯,这里依据我自身的经验给出一点建议吧。

保持良好的代码风格

缩进和空格和换行

虽然之前已经说过一次,但是同学们还是很难做到。打空格这个事情我只能说是打习惯之后就会成为一个习惯。打了空格的代码的可读性真的会强很多。

ans = (a - b) > 0 ? (a - b) : (b - a);

ans=(a-b)>0?(a-b):(b-a);

明明是两行相同的代码,但是为什么美观性就差了这么多呢,这是怎么肥事呢.jpg

此外在代码块直接之间加上空行,会让代码的分块更加明显,便于阅读。

还是希望同学们能养成打空格、打缩进的习惯,这真的很重要!

保持良好的命名习惯与声明变量习惯

不要在程序开头的一行里一下子声明一堆变量。错误示范:

int a,b,c,i,j,k,sum,ans1,ans2;//这是令人死亡的定义方式

即使真的要一下声明很多变量,也要分开一行一行声明,并且要对每个变量的命名赋予意义,不要使用 i , j i,j i,j 这样读起来毫无意义的变量存储有意义的数据。 i , j i,j i,j 这样的变量通常用于控制循环,即定义在 f o r for for 循环内。而如果使用 a , b a,b a,b 这样的变量用作临时变量存取临时数也是可以理解的。

一个变量尽量在用到他的时候再进行定义。同时也要注意变量的作用域和生命周期。这个同学们之后便会学到。

此外在声明的时候,若有必要,尽量给变量赋上初值。这样可以杜绝许多bug。

多写注释

在代码中多添加注释,并且尽量是简短的英文,防止不同IDE对中文字符的编码不同导致不能阅读注释。特别是在声明变量时以及在一些程序的关键地方。多写注释不仅可以帮助别人理解自己的代码,也可以帮助自己理解自己的代码。

在写代码之前先整理好思路

在开始码代码之前务必先对代码有整体规划和思路,不要打一点想一点。可以先在草稿纸上写下大体思路,构思代码结构,大体规划好需要的变量。这样在码代码的时候能保证思路连贯。打一点想一点的弊端很大,常见的比如打到后面忘了前面的某些代码是干什么的等等,来回检查代码、回想思路就会平白浪费许多时间。

保持全局观再码代码是很重要的。这样做的好处包括但不仅限于:保持连贯的思路,bug更容易发现,更容易一口气打出代码…同学们尝试几次就会发现这样的好处了。

就结论而言,这样码出的代码出现bug的可能性更小,一次性 A C AC AC 的概率更大。一次性 A C AC AC 真的既酷又爽的好不好!

确保在读懂题之后再码代码

在做题前要先读懂题意,认认真真读题干,认认真真理解样例输入和输出,认认真真阅读给出的 h i n t hint hint ,在任何一步没有完全理解之前都不要妄然动手打代码。确保自己在完全理解了题目,脑海中已经想好了题目的解法后,再开始动手码代码。

切忌在没读懂题的情况下就开始码代码!

示范代码

下面给一个代码风格良好(大概)的代码,是我自己写的E2卡牌数数那题的代码。

#include <stdio.h>

int main(){
    int num[10];                //store num of cards
    long long ans;
    for(int i = 0; i < 10; i++)
        scanf("%d", &num[i]);
    scanf("%lld", &ans);
    
    while(1){
        long long tem = ans;
        int flag = 0;
        
        if(tem == 0)            //special judge
            num[0]--;
        while(tem != 0){
            num[tem % 10]--;
            if(num[tem % 10] < 0){
                flag = 1;
                break;
            }
            tem /= 10;
        }
        
        if(flag == 1)                //if exist num < 0
            break;
        ans++;
    }
    
    printf("%lld\n", ans - 1);  //cant count to ans
    return 0;
}

仅做参考。

代码调试的简单方法

一种常见的方法是在代码中使用 printf() 输出中间变量,观察变量的变化是否如自己所愿。比如我上面的代码,如果我想要调试:

#include <stdio.h>

int main(){
    int num[10];                //store num of cards
    long long ans;
    for(int i = 0; i < 10; i++)
        scanf("%d", &num[i]);
    scanf("%lld", &ans);
    
    while(1){
        long long tem = ans;
        int flag = 0;
        printf("%d ", ans);//添加printf以观察ans值的变化
        if(tem == 0)            //special judge
            num[0]--;
        while(tem != 0){
            num[tem % 10]--;
            if(num[tem % 10] < 0){
                flag = 1;
                break;
            }
            tem /= 10;
        }
        
        if(flag == 1)                //if exist num < 0
            break;
        ans++;
    }
    
    printf("%lld\n", ans - 1);  //cant count to ans
    return 0;
}

观察其他的变量也是同理。这是新手最容易掌握的一种调试代码的方法,希望同学们都可以掌握。(掌握起来没有任何难度)

记得换行

在输出多组数据的时候记得在该换行的时候添加换行符。如果不添加换行符,评测机显示的结果大概率是 WA 而不是 PE

不要抄袭代码

在上次上机的时候对同学们提交的代码进行了查重,最后发现了一部分同学存在抄袭的现象。令我欣慰的是咱班同学的抄袭现象很少,而且应该不是有意的。这里再次强调不要直接抄袭代码!上机的时候讨论乃至搜索都是允许的(开卷),但是对于抄袭代码的行为是绝对零容忍的。之后再出现抄袭代码的情况将会做出严厉的惩罚,希望同学们都能够对自己负责。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值