🐤

CS50 Lecture2 Array

2022/01/16に公開

ハーバード大学のコンピューターサイエンスの授業CS50のlecture2自分用メモ

Compile

ソースコードからマシンコードにコンパイルするにはいくつかのステップが必要になる

https://cs50.jp/x/2021/week2/

  • processing
  • compiling
  • assembling
  • linking

processing

まず初めに、下記のようなソースコードを受け取り、上からしたえ、左から右へ前処理(プリプロセス)する。

前処理とは基本的に、ハッシュ記号で始まる行を探すこと。

この処理は検索と置換のようなものを行う。

ハッシュ記号を見つけたら、CS50.hというコードを、みなさんのコードにコピーする。

compiling

コンパイルとはここにあるソースコードから別の種類のコードに変換することを意味する

コンパイラはアセンブリコードにコンパイルする

以下はアセンブリ

CPUはある種のコマンドを理解する。

その命令はアセンブリと呼ばれる言語で表現される傾向がある

assembling

アセンブリとは、上記のコードを0、1に変換すること

linkng

一番最後のリンキングで、includeしていたCS50.hとincludeしていた studio.h 、自分で書いたmain関数の0,1を一つにまとめる

デバッグ

step over (くるっとした矢印)でデバッグする

call stack

プログラムが実行したがまだそこから戻ってない関数を全てさす

10行目でブレークポイント打ってるが定義上main関数の内部

step intoでデバッグしてみる

get_nagative_intという関数にジャンプし最初の興味あるコードにジャンプする

get_nagative_intに飛ぶと、call stackにも入った

RAM

プログラムが実行されてる間は、メモリつまり RAMに格納されてる。

プログラムを保存したりインストールしたり保存したりする場合、一般的にはハードディスク、ハードディスク、ソリッドステートディスクなどの物理的媒体に保存される。保存するときに電気を使わないのが特長。

RAMは違って揮発性のもの。

プログラムをダブルクリックして保存したり表示したりするときは一時的にRAMに保存される

パソコンのバッテリーが切れたり、未保存の部分が消えてしまうのがRAM(メモリー)が揮発性のものだから。

電力を供給し続けるためには電気が必要だから。

整数を整数で割り、商が浮動小数になりうる場合は、エラーが出るので、割る数または割られる数を浮動小数にすればおk

int score1 = 72;
int score2 = 73;
int score3 = 33;

printf("Average* %f\n",(score1 + score2 + score3) / 3.0 );

Array

score1,score2,score3はそれぞれ下記のようにメモリに格納されてる

#include <stdio.h>

int main(void)
{
    char c = '#';
    
    printf("%i\n", (int) c);
}

charでなくintで表示すると35と表示される(ASCIIのハッシュコード)

C言語では、これが文字であればintに変換できるコンピューターにとってわかりやすい場合、明示的にキャストする必要はない

#include <stdio.h>

int main(void)
{
    char c = '#';
    
    printf("%i\n",  c);
}

これでも35になる

#はこの1byteに格納される(charは1byteなので1マスしか必要ない)

#include <stdio.h>

int main(void)
{
    char c1 = 'H';
    char c2 = 'I';
    char c3 = '!';
    
    
    printf("%c%c%c \n",  c1,c2,c3);
}

HI!

メモリの中ではこんな感じに格納されてる

#include <stdio.h>

int main(void)
{
    char c1 = 'H';
    char c2 = 'I';
    char c3 = '!';
    
    
    printf("%i%i%i \n",  c1,c2,c3);
}

727333

※C言語にはstring型はないがcs50はstring型定義できるようにされてる

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

int main(void)
{
    string s = "HI!";
    
    printf("%s\n", s);
}

3つの箱を用意して文字列sと呼ぶ

cs50.hはどのようにして文字列を実装したのか?

→ charの配列と考えれば良い

技術的にはHI!はsという配列

s = [H,I,!]

なので、s[1] = I というように個々の文字にアクセスできる 

スコアの例では配列自体を確保しただけでなく、その配列の中にいくつの整数があるのかを2つの変数で管理していた(わからん

printfmで文字列を出力するとき、論理的には文字列の配列しか渡していなかったが、配列の長さを割り出していた。

printfは配列が与えられれば、その長さがどれくらいかを知れるほど賢い。

printfは指定された単語しか印刷しないから。

文字列が文字の羅列でしかないなら、コンピュータはどのようにしてメモリ内の文字列の終わりを知ることができるのか?

文字の長さがHI!のように3文字の場合、4byte使う。

4byte目を使って\0で初期化している。

別名null文字とも呼ばれる

HI!がどこで終わり、次の文字がとこから始まるかおコンピューターが知る必要があるから。

この文字列はこれで終わりですというストップサインが必要。

0番目はNUL

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

int main(void)
{
    string s = "HI!";
    
    printf("%i %i %i %i\n", s[0], s[1], s[2], s[3]);
}
//0
#include <stdio.h>
#include <cs50.h>

int main(void)
{
    string s = "HI!";
    
    printf("%i %i %i %i %i\n", s[0], s[1], s[2], s[3], s[4]);
}
//72 73 33 0 37

37は%🤔

40番目(s[40])は24🤔

400番目(s[400])は0🤔

C言語は自分のものではないメモリにも触れるので注意が必要!

※メモリの先に格納されてるものにアクセスできてしまう。

string s = "HI!";
string t = "BYE!";

上記のようにメモリに格納されてるので、少し先の番号にアクセスすることで、そこに格納されてるものを見ることができる

文字列は最後が\0となってるので、最後の文字列が\0となるまで出力を続ける、とすると最後の文字列まで出力できる

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

int main(void)
{
    string s = get_string("Input: ");
    printf("Output: ");
    for(int i = 0; s[i] != '\0'; i++)
    {
        printf("%c", s[i]);
    }
    
    printf("\n");
}

1箇所だけ修正するとしたら...

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

int main(void)
{
    string s = get_string("Input: ");
    printf("Output: ");
//ここが毎度文字の長さを計算してる
    for(int i = 0; i < strlen(s); i++)
    {
        printf("%c", s[i]);
    }
    
    printf("\n");
}

//Input: hello
//Output: hello
#include <stdio.h>
#include <cs50.h>
#include <string.h>

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");
}

//Input: hello
//Output: hello
string word[2];
word[0] = "HI!";
word[1] = "BYE!";

メモリにはこんな感じになる

しかしそもそも文字列とは配列

つまり、上の図は配列の配列ということになるので

下記のように、一文字ずつアクセスできる

上記の特徴を使って、小文字を大文字に変換する関数を書いてみる

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

int main(void)
{
    //現在の文字が小文字であれば大文字に変換する
    string s = get_string("Before: ");
    printf("After: ");
    for(int i = 0; n = strlen(s); i < n; i++)
    {
        //ASCII表は大文字の次に小文字が定義されてる
        if(s[i] >= 'a' && s[i] <='z')
        {
						//大文字だった場合、はてなの部分で何をすべきか???
            printf("%c", s[i] ???);
        }else
        {
            printf("%c", s[i]);
        }
    }
    
    printf("\n");
}

ASCIIチャートを見てみると

A:65

a:97

B:66

b:98

...

大文字と小文字の間には常に32の差がある

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

int main(void)
{
    //現在の文字が小文字であれば大文字に変換する
    string s = get_string("Before: ");
    printf("After: ");
    for(int i = 0; n = strlen(s); i < n; i++)
    {
        //ASCII表は大文字の次に小文字が定義されてる
        if(s[i] >= 'a' && s[i] <='z')
        {
						//小文字だった場合、32を引けば大文字になる 
            printf("%c", s[i] - 32);
        }else
        {
            printf("%c", s[i]);
        }
    }
    
    printf("\n");
}

ctype.hをincludeすることで上記と同義のコードが下記のようにスッキリとかける

#include <stdio.h>
#include <cs50.h>
#include <string.h>
//c言語の文字の種類に関する関数がたくさん入ってる
#include <ctype.h>

int main(void)
{
    //現在の文字が小文字であれば大文字に変換する
    string s = get_string("Before: ");
    printf("After: ");
    for(int i = 0; n = strlen(s); i < n; i++)
    {
       printf("%c", touppers[i]);
    }
    
    printf("\n");
}

下記は実行コマンドと名前を入力すると、hello (名前)と出力される

c言語で0個以上の引数を受け付けるにはvoidだったものをint argc, string argv[]に変更する

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

//入力として整数を受け取り、有力として文字列ではなく配列を受け取ることを示している
//【argc】
//引数の数を表す略記法
//引数の数は整数でユーザーがプロンプトで入力した単語の数
//【argv】
//argument ventor(vectorはlintの洒落た言い方)
//プログラムの名前の後に人間がプロンプトで入力したすべての文字列を配列に格納する変数
int main(int argc, string argv[])
{
    //入力されたのが2文字だったら
    if(argc == 2)
    {
      printf("hello, %c\n", argv[1]);
    }else
    {
        printf("Hello World!");
    }
}
./

コンピューターが自動的にやってくれることは人間が入力した単語の総数

つまり引数だけでなく技術的には自分のプログラム名を含む全ての単語をargcに格納すること。

そして、argvには、プログラム名と自分の名前の両方が格納されている

Discussion