PSET-2:加解密

目标

  • 更好的熟悉和运用库
  • 涉及一些加密编码

推荐阅读

  • 基本的C 11-14, 39页
  • Chapters 6, 7, 10, 17, 19, 21, 22, 30, and 32 of Absolute Beginner’s Guide to C.
  • Chapters 7, 8, and 10 of Programming in C.

基本的C(有基础可无视)

数组

在这一部分,我们将用新的变量类型创建一个c程序,用来产生10个随机数并且排序它们,它就是数组

数组能声明几个同一类型的值在一个集合中,举个例子,你要创建五个整数的集合,有一个方法是直接声明5个整数:

int a, b, c, d, e;

这样是可以,但是当你需要创建一千个整数呢?因此有个更简单的方法:声明五个整数组成的数组

int a[5]

也可以这么声明:

int *x = malloc(sizeof(int) *N);

还可以快捷声明:

int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

这五个独立的整数通过索引进行读访问,所有的数组索引都是从0到n-1,因此int a[5]包含了五个元素,如下:

int a[5]

a[0] = 12;
a[1] = 9;   
a[2] = 14;
a[3] = 5;
a[4] = 1;

你可以使用循环来遍历操控索引,举个例子,下列的代码把数组中所有元素初始化为0:

int a[5];
int i;

for (i=0; i<5; i++)
    a[i] = 0;

下面代码按照排序初始化并且输出:

#include <stdio.h>
int main(){
    int a[5];

    for(int i=0;i<5;i++){
        a[i]=i;
        printf("a[%d] is %d\n", i, a[i]);
    }
}

数组是C中使用频繁的类型,下面是常见用法

#include <stdio.h>

#define MAX 10

int a[MAX];
int rand_seed=10;

int rand(){
    rand_seed = rand_seed * 1103515245+12345;
    return (unsigned int)(rand_seed / 65536) % 32768;
}

int main(){
    int i;

    for (i=0; i<MAX; i++)
    {
        a[i]=rand();
        printf("%d ", a[i]);
    /* more stuff will go here in a minute*/
    }
    printf("\n");
}

这个代码中出现了几个新的概念,#define声明一个叫MAX的常量并设为10,常量的名称一般使用全大写,注意声明在程序中的位置,它是全局范围的

rand_seed也是全局变量,该变量在每次调用时都初始化为10,这个数是开始随机数的种子(seed),在真正调用生成时,这个种子应赋值为随机数,在这个程序中,rand函数每次会产生相同的随机值

int rand()是函数的声明,这个函数不接受参数并且返回一个整数型,在main函数中声明了4个局部变量,并使用循环把10个随机数填入数组中,现在数组a里包含了10个独立的整数,当你需要指向数组中特定位置的整数,可以使用a下标, 由/**/包起来的地方是注释,编译器会忽略它

接下来加入以下代码到之前的注释位置

    /*bubble sort the array */
    for (x=0;x<MAX-1;x++)
        for (y=0;y<MAX-x-1;y++)
            if (a[y]>a[y+1])
            {
                t = a[y];
                a[y] = a[y+1];
                a[y+1] = t;
            }
    /* print sorted array */
    printf("-----------------------------------\n");
    printf("after:\n");
    for (i=0;i<MAX;i++)
    printf("%d ", a[i]);
    printf("\n ");

这段代码把10个随机数进行排序,由小到大,每次运行都是相同的值,如果你想改变,那么就改变rand_seed的值在每次你运行程序的时候

理解这段代码的最简单方法是亲手执行,把自己想象成计算机,找一张纸进行手写代码,执行每一行代码写在纸上,你会发现,每次执行一次内循环,最大的值就会沉底(个人比喻),外循环执行的次数越少,说明约接近顶部

变量类型

在C中,有三种基本的变量类型:
- 整型:int(integer) 4byte
- 浮点型:float(Floating point) 4byte
- 字符型:char(character) 1bpye 字符串(string)是字符型数组

还有几种派生的类型:
- double(双精度浮点型) 8byte
- short(短整型) 2byte
- unsigned short/int (无符号位短整型/整型)

类型转换

C允许你动态的进行类型转换,尤其是当使用指针时,在某些类型的赋值操作中也会发生,想要转换一个值的类型需要在前面加上(type),比如你想把10转成浮点数进行除法运算,就可以这样:
a=10/3 -> a=(float)10/3

类型定义

你可以给数据类型起一个自定义类型(别名,绰号),用 typedef,比如我给int起个别名:

int main(){
    typedef int bigbig;
    bigbig a;
    a = 10/3;
    printf("%d", a); }

这行代码允许你使用bigbig来声明int类型的值

结构体

结构体(struct)可以存放一组不同类型的数据,它是一个集合,里面包含了多个变量和数组,里面的每个元素称为成员(Member)定义形式为:

struct 结构体名{
    结构体所包含的变量或数组};

结构体也可定义变量:在结构体名后定义:

struct stu stu1, stu2

也可在定义结构体时放在尾部:

    struct stu{
        char *name;  //姓名
        int num;  //学号
        int age;  //年龄
        char group;  //所在学习小组
        float score;  //成绩
    } stu1, stu2;

如果只要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名,但是因为没有结构体名,后面就没法用该结构体定义新的变量

    struct{  //没有写 stu
        char *name;  //姓名
        int num;  //学号
        int age;  //年龄
        char group;  //所在学习小组
        float score;  //成绩
    } stu1, stu2;

结构体使用.(点号)来获取成员:

结构体变量名.成员名

结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。

函数

许多语言允许你去创建某种函数,函数可以让你把又长又大的程序分割成代码块,也便于在其他程序中复用这个代码块,函数接受参数并返回结果,c能够接受无限量的参数,一般来说,c关心你的函数在程序中的排列顺序,只要在调用之前进行声明,就可以

命令行参数

c提供一个相当简单的机制用于检索用户输入的命令行参数,通过一个argv参数:

#include <stdio.h>

int main(int argc, char *argv[]){
    int x;

    printf("%d\n", argc);
    for (x=0;x<argc;x++)
        printf("%s\n", argv[x]);
    return 0;
}

在这段代码中,主程序接受两个参数,argc和argv,argv是一个指向字符串的指针数组argc是包含参数数目的计数

char argv[]是一个指向字符串的指针的数组,换句话说,每个数组中的元素是一个指针,每个指针指向一个字符串,因此argv[0]指向的是包含在命令行中的第一个参数(程序的名字),argv[1]指向下一个参数,以此类推,atgc变量告诉你有多少个指针

因为argv的存在,可以让你的程序很容易对用户在命令行中输入的参数进行反应,例如你想让你的程序检测单词help做为首参数(跟在程序名后,一般是argv[1]),然后输出一个帮助文件,文件名(argv[0])也可以在你的打开文件中使用

缩写

让我们更加的了解字符串(string)

实现

写一个initials.c程序用来获得用户输入的名字(使用GetString),并且输出他们名字的大写字母(没有空格间隔,最后有换行符),假设用户只会输入大/小写字母和空格,不会出现像Joseph Gordon-Levitt, Conan O’Brien, and David J. Malan的名字,下面是例子:

username@ide50:~/workspace/pset2 $ ./initials
Zamyla Chan
ZC

代码

#include <stdio.h>
#include <cs50.h>
#include <string.h> //strlen
#include <ctype.h> //toupper
int main(){
    char *name;
    name = GetString();
    int length = strlen(name);  //strlen() get length
    // printf("len is:%d\n", length);
    printf("%c", toupper(name[0])); //first letter
    for (int i=0;i<length;i++)
        if (name[i]==' ')
            printf("%c", toupper(name[i+1]));
    printf("\n");
}

Hail,Caesar!

回想一下在short中提到过的凯撒密码,讯息通过转动每个 k的位置,从Z到A进行环绕,详见wiki, 换句话说,如果p是明文,对p进行加密,那就是s,为了方便,把字母变成数字(0-25),生成精确的公式:
这里写图片描述

n是转动次数,x是需要加/解密的值

这个公式可能使密码看起来更复杂,但很简洁明了,假设给你提供了关键码k=13(也就是n),和明文"Be sure to drink your Ovaltine!"让我们用k来加密p,通过把每个在p中的字符旋转13次:

加密前:

Be sure to drink your Ovaltine!

加密后:

Or fher gb qevax lbhe Binygvar!

顺便一说,关键key为13的凯撒加密通常被称作ROT13,然而在现实生活中最好使用ROT26

实现

现在你需要做的是写一个能使信息进行凯撒加密的程序,命名为caesar.c, 你的程序必须接受一个非负整型的命令行参数,为了方便讨论我们称它为k,如果你的命令行参数为多个或零个,程序会发出错误信息,并返回值1

除了以上,你的程序还要提示用户输入一个纯文本的字符串,输出中的每个字符串中的字符都是进行了rot k次后的字符,非字母不变,输出完毕后你的程序应该返回值0并退出

如果你不从main显式的返回一个整型,系统将自动返回0(事实上,每个返回类型,main都需要返回一个int),现在如果你返回的1,就说明有错误,0是正常,虽然0通常表示成功,任何非零的数通常表示一个错误,这样你可以表示40亿个错误(因为一个int型通常是32位的)

尽管在英文字母里只存在26个字母,你可以不局限k值小于等于26,在程序中的k值将适用于所有小于2^31 - 26的非负整型(也就是说,你不必担心最终你的程序会因为k值过大而崩溃),现在就算k值大于26,你也要保证你输出的字符是正常的字母字符

举个例子,如果k=27,A在ASCII码中是65,rot27个位置,会得到[,做为cs可能会说,我们想得到的是B,因为27%26=1,换句话说,k=1实际上等同于k=27

设计程序时要遵守:大写字母进行rot后还是大写字母,小写字母进行rot后也还是小写,这个程序需要接受命令行参数,k,你可以在main函数中声明:

int main(int argc, string argv[])

调用argv时字符串是由”数组”组成,你可以把数组看作是健身房中储物柜的行,一行有多格,在每格里面都有值,在这种情景下可以把每个字符串都看作是一个储物柜,打开第一行里的第一格,可以这么访问:argv[0](数组是从零开始计数的),后面的以此类推,如果你有n个格子,你最多可以访问argv[n-1],因为argv[n]是不存在的

所以你可以这样访问命令行参数k并赋值:

string k = argv[1]

在打开”储物格”之前最好先用argc检查一下 argv的数量,防止不存在的情况, 理论上argc的值应该是2,原因如下:默认的argv[0]的值是程序的名字,所以argc至少是1,加上我们需要用户提供一个命令行参数k,所以为2,当然如果用户输入过多的命令行参数,argc就会>2,如果这种情况发生,我们应该做提示一些警告信息

有时候用户的输入不见得是int类型,可能会是长的像int的string类型,这时候就要转换类型了,辛运的是,有个叫atoi()的函数,可以把命令行参数to成int型:

int k = atoi(argv[1]);

上面的例子中,atoi()会return0,我们在声明了k是一个int后,才进行的操作,顺便说句,你可以假设用户只会输入整型,不用担心他们所输入的类型,就像是foo,这样会变得很困难

atoi()函数需要头文件stdlib.h,不过我们在cs50.h中已经包含了atoi,所以不用声明也可运行,接受int型值已经完成了,接下来向用户要一段要加密的文本p,这里cs50里的GetString可以帮助你,一旦你有了字符串和k值,就可以对其进行加密了,回忆一下之前用过的迭代字符串方法:

for (int i=0, n=strlen(p);i<n;i++)
printf("%c", p[i]);

可以这么说,正如argv是一个字符串数组,同理,一个字符串是char型数组组成,你可以用方括号[]来对字符串中的每个字符进行访问,通过上面的提示正常都可以自己做出符合要求的caesar.c,顺便一说,使用strlen的话需要声明某个头文件!

代码

# include <stdio.h>
# include <cs50.h>
# include <string.h>
# include <stdlib.h>

int main (int argc, char *argv[]){
    int k;
    char * text;

    if (argc>2||argv[1]==NULL)
    {
        printf("ohh!!!!\n");
        return 1;
    }
    k = atoi(argv[1])%26;
    text = GetString();

    for (int i=0, n=strlen(text);i<n;i++)
    {
        if ((96<text[i]+k&&text[i]+k<123)||(64<text[i]+k&&text[i]+k<91)) // lower 96<x<123
        {

            printf("%c", text[i]+k);
            continue;
        }
        else if ((96<text[i]&&text[i]<123)||(64<text[i]&&text[i]<91)) // lower 96<x<123
        {
            printf("%c", text[i]+k-26);
        }else{
            printf("%c", text[i]);
            }
    }
    printf("\n");
    // return 0;
}

Vigenère

实现

维吉尼亚加密在凯撒加密的基础上进行改进,使用连续并分离的key,如果p代表待加密的文本,k表示密钥(密钥是由字符串组成,因此a=0,b=1,c=2…),每个加密后的字符(Ci)计算方式如下:

这里写图片描述
–其中Kj代表密钥的第j个字符

要求:
设计和执行一个程序,它使用Vigenère的密码加密消息,从命令行中获得一个参数,这个参数要求由字母组成(如果多于一个或者不是字符串会报错并return 1退出),然后用plaintext:提示用户输入字符串,如果输入的是非字符串,那么不进行加密,接收后使用密钥中的字符串依次加密(到尾循环)直至结束,最终用ciphertext:提示打印结果并返回(return 0)

代码

# include <stdio.h>
# include <cs50.h>
# include <ctype.h>
# include <string.h>


int main(int argc, char *argv[]){
    int len;
    char *key, *plaintext;

    if (argc>2||argv[1]==NULL){    //这个if循环用来检查输入个数
        printf("Error!\n");
        return 1;
    }
    else { //检查输入是否满足条件
        key = argv[1];
        len = strlen(key);
        for (int i=0;i<len;i++)
        { 
            if (key[i]<'A'||key[i]>'z'||(key[i]>'Z'&&key[i]<'a')) //使用ASCII码值进行检查非数字
            {   printf("invalid input:%c\n", key[i]);
                return 1;
            }
        }
    }
    printf("plaintext:\n");
    plaintext = GetString();
    printf("ciphertext:\n");
    for(int i=0, j=0, n=strlen(plaintext);i<n;i++){
        if ((plaintext[i]>='A'&&plaintext[i]<='Z')||(plaintext[i]>='a'&&plaintext[i]<='z')){  //检查plaintext是否为大小写字母
            if (j==len&&i!=0) //key长度不足时回到开头
                j = 0;
            if (key[j]>='A'&&key[j]<='Z') //使A(a)表示0,B(b)表示1...
                key[j] -= 'A';
            if (key[j]>='a'&&key[j]<='z') 
                key[j] -= 'a';
            if (plaintext[i]>='A'&&plaintext[i]<='Z'){ //大写部分
                if (plaintext[i]+key[j]>'Z'){
                        while (plaintext[i]+key[j]>'Z') //控制ASC码值
                            plaintext[i]-= 26;
                }
                plaintext[i] = plaintext[i]+key[j];
                printf("%c", plaintext[i]);
            }   

            if (plaintext[i]>='a'&&plaintext[i]<='z'){ //小写部分
                if (plaintext[i]+key[j]>'z'){
                        while (plaintext[i]+key[j]>'z') //控制ASC码值
                            plaintext[i]-=26;
                }
                plaintext[i] = plaintext[i]+key[j];
                printf("%c", plaintext[i]);
            }
            j+=1;    
        }else{ //输出非字符
                printf("%c",  plaintext[i]);
         }
    }
    printf("\n");
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值