CS50_3

CS50

lesson_3

回顾

从scratch图形化编程 ->等价的c语言程序

代码就是纯文本,计算机无法理解这个,它只能看懂二进制。

从源代码到二进制程序,有一个必要的步骤,是编译。比如昨天看到的clang对c语言的编译。(.out文件就是它的输出)而make并不是编译,它是一个构建工具(虽然在外人看来,它帮我们编译了程序,但它就包含一些命令,有-o之类的)

编译

编译好像是一个善意的谎言,你在编译,你把源代码转换为计算机认识的代码,但其实有很多其他的步骤会在你背后发生(clang隐藏了他们),而今天,我们会给它们取名字,探究一下编译,看看编译是怎么回事,希望这能让我们以后编译的代码更容易理解。我们分为四部

preprocessing(预处理)

在文件的开始,有很多的include指令,这些被称为预处理指令。这些行的起始,有一个#标记,这可以告诉编译器(clang),这些东西应该先被处理掉。

例如,再cs50.h里面包含着一些函数的声明/原型(如get_string)。而在预处理当中,这就可以告诉电脑这函数是什么,返回类型和参数是什么。

如,因为cs50.h里有队get_string函数的声明,所以第一行的#include cs50.h就能代替
string  get_string (string prompt)这行代码,来告诉电脑,grt_string是什么
compiling(编译)

预处理过的代码,(如翻译了各种头文件后),你的代码就会发生一些改变,变成这样

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhRFcPNQ-1639728300996)(image/15.png)]

这就是64位 x86汇编指令。

这语言之前有提过,a.out。它是很低级的语言,只有cpu能看的懂(他只能看懂这个)。c语言这些高级语言,只不过是对cpu可理解事物的高层抽象。

assembling(汇编)

之后,进一步转换汇编语言,变成0和1.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWUOnf0i-1639728300998)(image/16.png)]

linking

但我们如何和其他人的0和1融合在一起呢?毕竟,像printf这些函数,cs50.h,这些都是别人写的。

这样,就要使用link,把几块的0和1,融合成1片。

数组(array)

​ 我们的电脑里不仅仅只有cpu(中央处理器),它还有ram or access memory(随机存取储存器)。是笔记本,台式机,服务器都会用到的储存器。你每运行一个程序,打开一个文件都会用到。另一种储存器叫硬盘,你电脑所有的文件都永久储存在那里。(就算电池没电,它们也不会消失)。但ram不同,它是暂时的,但把文件和程序存在ram里,会更高效,你双击,就能打开他们。如打开word,你双击,他就会从硬盘拷贝到ram存储器里,ram存储器虽然小,但他很快。(你打开程序,网页,文档,它们的内容就都会存在里面)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xH30xLzv-1639728300998)(image/17.png)]

这个芯片(ram)可以看作一堆字节的集合,我们将它抽象成1格格小方块。如果你在写c语言,要创建一个字符,它占一字节,那么电脑就会把他存在这里面。如果你要存一个整数,四个字节,电脑就会分配给你连续的四个格子。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KBDdWERj-1639728300998)(image/18.png)]

但当字节一个个的连续在一起的时候,他就会有一个专门的术语,连续储存(数组)。

数组可以让我们在电脑连续输入我们想要的东西,它有利于我们改进代码,也可以提高代码运行速度。

如:
我们想用c语言写一个程序。连续输入score1,score2,score3的分数,然后连续输出

第一个方法,就是直接写。但这种方法很烦琐
int main(void)
{
    // Scores
    int score1 = 72;
    int score2 = 73;
    int score3 = 33;
}

第二个就是使用数组。但在c语言里,你使用数组,就必须先告诉电脑 ,数组的长度。
int main(void)
{
    // Get scores
    int scores[N];
    for (int i = 0; i < N; i++)
    {
        scores[i] = get_int("Score: ");
    }

    // Print average
    printf("Average: %f\n", average(N, scores));
}

而之前我们也讲过类似的东西,字符串。他其实也是数组,只不过他是字符的集合,字符是另一种数据结构。所以,我们也可以用s[i]来进行描述字符串,对他的每个字符进行索引。

int main(void)
{
    string s = get_string("Input:  ");
    printf("Output: ");
    for (int i = 0, n = strlen(s); i < n; i++)
    {
        printf("%c", s[i]);
    }
    printf("\n");
}
//strlen就是字符串长度的函数

但是,当你储存了一个字符串,转头又写另一个字符串的时候,电脑还会记得住之前那个字符串从哪开始,从哪结束吗,还能找到它么?

当你存入zamyla的时候,其实你也同时的存入了起始和结束标记。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbJpstki-1639728300999)(image/19.png)]

前面那个是它的变量名,后面结束符号是一个全0字符(空字符),8个位都由0构成,(也可以写作/0)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h07tgvYx-1639728301000)(image/20.png)]

这里的while语句,实现了strlen函数的功能。

//这一段程序能够实现输出字符串单个字符,后面还跟着他的ascll码
#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    string s = get_string("String: ");
    for (int i = 0; i < strlen(s); i++)
    {
        int c = (int) s[i];
        printf("%c %i\n", s[i], c);
    }
}

这个程序 int c = (int) s[i];直接把一种数据类型转换成了另一种。这是显示类型转换(explicit casting),而其实也可以进行隐式类型转换,直接把c改成s[i],前面的%i(%d)会告诉电脑,利用不同的方式对待相同的位码。

这个程序在进行小写字母转大写。(但以下源码在c语言里行不通)

int main(void)
{
    string s = get_string("Before: ");
    printf("After:  ");
    for (int i = 0, n = strlen(s); i < n; i++)
    {
        if (s[i] >= 'a' && s[i] <= 'z')
        {
            printf("%c", s[i] - 32);
        }
        else
        {
            printf("%c", s[i]);
        }
    }

它可以优化成以下程序。但要在前面加这个头文件ctype.h

int main(void)
{
    string s = get_string("Before: ");
    printf("After:  ");
    for (int i = 0, n = strlen(s); i < n; i++)
    {
        if (islower(s[i]))  //islower存在ctype.h里面
        {
            printf("%c", toupper(s[i]));  //toupper存在ctype.h里面
        }
        else
        {
            printf("%c", s[i]);
        }
    }
    printf("\n");
}

而当你不懂这个函数存储于哪个头文件,有什么用的时候,可以在终端输入man,后面接着你想了解的函数,它会提示你(好像是只有cs50有)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-conInZ2H-1639728301000)(image/21.png)]

我们一般默认对main的写法是int main(void),main函数不接受任何输入也可以运行。而当你给里面增加参数(main (int argc,string ardv[])),实际上你在告诉clang,我想这个程序接受一个或更多的单词或数字。这样直接可以写./hello david而不是在getstring函数运行的时候写david。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jZtVmGec-1639728301001)(image/22.png)]


所以如果你在编程里改变了main函数,使他接受了两个参数,如果他输入了两个单词,则会运行if语句。(两个单词是.\argv0 和 Zamla)

而因为argv是一个字符串数组,所以还可以这么写>


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

int main(int argc, string argv[])
{
    for (int i = 0; i < argc; i++)
    {
        for (int j = 0, n = strlen(argv[i]); j < n; j++)
        {
            printf("%c\n", argv[i][j]);
        }
        printf("\n");
    }
}
//外面的循环迭代字符串
//里面的循环迭代字符串的每个字符(每行输出一个字符)

这种方法可以对一些明文进行加密,(打乱每个字符排序)。利用算法加密文件,并不少见。main函数要是你不返回任何值,他会自动默认返回0

排序

数组可以用于解决很多算法问题。他可以被看成一串储物柜,里面可以是整数,字符,字符串。但他一次只能看到一扇门,他需要一个个的去看。

手机里的通话列表,按字母进行排序,他其实也是有某种类型的数组(数据结构)来储存。但如何为数组中的元素排序?

  • 冒泡排序:像冒泡一样一点一点的排好序。(Bobble Sort).

  • 选择排序:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。(Selection Sort)

  • 归并排序,这个排序要利用到数组。

    将所有数组拆分成一个个的状态,然后进行第一轮的两两合并(这个经过是从一个大数组变成n个小数组再到2/n的数组的过程,只不过此时每个数组里都是有序的。然后不断合并。

算法复杂度

选择排序的运行次数是:(n-1)+(n-2)+(n-3)+…+1。等于n(n-1)/2。算法复杂度是O(n^2)。因为平方这个量级太大(这个式子里最大的了)到了最后完全可以忽视其它因数。而冒泡排序的算法复杂度也是这个。

当然还有其他的类型(量级):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RTeCrmV-1639728301001)(image/23.png)]

如:查找某人电话,一次翻一页,是O(n)。一半一半的查找,是log级。而归并排序的算法复杂度是n*log(n)。有n个数字,每个数字排列log(n)次

算法复杂度

选择排序的运行次数是:(n-1)+(n-2)+(n-3)+…+1。等于n(n-1)/2。算法复杂度是O(n^2)。因为平方这个量级太大(这个式子里最大的了)到了最后完全可以忽视其它因数。而冒泡排序的算法复杂度也是这个。

当然还有其他的类型(量级):

[外链图片转存中…(img-7RTeCrmV-1639728301001)]

如:查找某人电话,一次翻一页,是O(n)。一半一半的查找,是log级。而归并排序的算法复杂度是n*log(n)。有n个数字,每个数字排列log(n)次
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值