Notes

C

  • 今日は新しい言語であるCを学びます。CはScratchのすべての機能を備えたプログラミング言語ですが、純粋にテキストで記述されているため、少し使いにくいかもしれません。
#include <stdio.h>

int main(void)
{
    printf("hello, world");
}
  • 最初のうちは、MITの言葉を借りると、これらの新しい概念をすべて吸収しようとすると、消火ホースから水を飲むような気分になるかもしれませんが、学期の終わりまでには、私たちはこれらの概念を学び、応用する能力を身につけ、経験を積むことになるでしょう。
  • Cのプログラミング機能の多くを、Scratchですでに見て使ったブロックと比較することができます。構文の詳細は、すでに紹介した概念ほど重要ではありません。
  • 上記の例は、それぞれの単語は新規ですが、アイデアはScratchの「when green flag clicked」「say (hello, world)」というブロックとまったく同じです。
  • コードを書くときには、次のような特性を考慮します。
    • 正確性、つまりコードが意図したとおりに正しく動作するかどうか。
    • デザイン、つまり、コードがどれだけ効率的であるか、どれだけエレガントか、論理的に読みやすいかに基づいて、不必要な繰り返しなしに、コードがどれだけ良く書かれているかを主観的に測定します。
    • スタイル、つまり一貫したインデントや記号の配置がされており、また美しく書式が設定されているか。スタイルの違いは、コードの正確さや意味には影響しませんが、視覚的な読みやすさには影響します。

CS50 IDE

このコースのツールであるCS50 IDEは、コードを書くためのプログラムや機能を含む統合開発環境です。CS50 IDEは、一般的なプログラマが使用する一般的なクラウドベースのIDEの上に構築されていますが、追加の教育用機能とカスタマイズがされています。

  • IDEを開き、ログインすると、次のような画面が表示されます。
  • 上部の空白のパネルには、コードを記述できるテキストファイルが含まれます。
    • 一番下のパネルはターミナルウィンドウで、上のコードのプログラムを含め、さまざまなコマンドを入力して実行できます。
  • 私たちのIDEはクラウドで動作し、標準的なツールセットが付属していますが、デスクトップベースのIDEも多くあり、さまざまなプログラミング目的に合わせてより多くのカスタマイズと制御機能が提供されています。
  • IDEで、 「ファイル」 > 「新規ファイル」 を選択し、 「ファイル」 > 「保存」 を選択してファイルをhello.cとして保存します。これは、ファイルがC言語で記述されたコードであることを示します。タブの名前が実際にhello.cに変更されたことがわかります。次に、このコードを貼り付けます。
#include <stdio.h>

int main(void)
{
    printf("hello, world");
}
  • プログラムを実行するには、CLI (コマンド・ライン・インタフェース) を使用し、プロンプトにテキストコマンドを入力します。これは、テキストに加えてイメージ、アイコン、ボタンがあるScratchのようなGUI (グラフィカル・ユーザ・インタフェース) とは対照的です。

コンパイル

  • IDEの下部区画にあるターミナルで、コードをコンパイルしてから実行します。コンピュータが理解できるのはバイナリだけで、これは画面に何かを印刷するといった命令を表すのにも使われます。私たちのソースコードは私たちが読むことのできる文字で書かれていますが、コンパイルする必要があります。マシンコード、つまり0と1のパターンとして、コンピュータが直接理解できるものに変換する必要があります。
  • コンパイラと呼ばれるプログラムは、入力としてソースコードを受け取り、出力としてマシンコードを生成します。CS50 IDEでは、makeというコマンドを使ってコンパイラにアクセスできます。ターミナルでmake helloと入力すると、ソースコードを含むhello.cファイルが自動的に検索され、helloというプログラムにコンパイルされます。出力が表示され、黄色または赤のエラーメッセージが表示されていないため、プログラムは正常にコンパイルされました。
  • プログラムを実行するには./helloという別のコマンドを入力します。. は現在のフォルダを検索します。これを呼び出して、helloというプログラムを実行します。
  • ターミナルの$は、どこにプロンプトがあるか、どこにコマンドを入力できるかを示します。

関数と引数

  • Scratchで検討したのと同じアイデアを使用します。
  • 関数は、プログラムで何かを行うために使用できる小さなアクションまたは動詞であり、関数への入力は引数と呼ばれます。
    • 例えば、Scratchの 「say」 ブロックは「hello, world」のようなものを引数に取っているかもしれません。Cでは、画面に何かを印刷する関数をprintf  (fは 「フォーマット (formatted) された」 テキストを表します) と呼びます。Cでは、printf("hello, world");のように引数を括弧で囲んで渡します。二重引用符は、文字hello, worldを文字どおりに出力することを示し、末尾のセミコロンはコード行の終わりを示します。
  • 関数には、次の2種類の出力もあります。
    • 画面に印刷されるものなどの副作用
    • 戻り値は、プログラムに返される値で、後で使用したり保存したりすることができます。
      • 例えばScratchの 「ask」 ブロックは、「answer」 ブロックを作成します。
  • 「ask」 ブロックと同じ機能を使用するには、ライブラリ (すでに作成されているコードの集合) を使用します。CS50 Libraryには、すぐに使用できる基本的で簡単な関数が含まれています。例えば、get_stringはユーザに文字列あるいはテキストのシーケンスを問い合わせ、それをプログラムに返します。get_stringは、 What's your name?などの入力をユーザーのプロンプトとして取り込みます。次のように変数に保存する必要があります。
string answer = get_string("What's your name? ");
  • Cでは、単一の=代入、つまり右側の値を左側の変数に設定することを示します。Cはget_string関数を呼び出して、最初にその出力を取得します。
    • また、answer という名前の変数が文字列であることを示す必要があります。すると、プログラムは0と1をテキストとして解釈します。
    • 最後に、コード行の末尾にセミコロンを忘れずに追加する必要があります。
  • Scratchでは、「join」ブロックと「say」ブロック内で「answer」ブロックも使用しました。Cでは次のようにします。
printf("hello, %s", answer);
  • %s書式コード(変換指定子)と呼ばれます。これは単に、プレースホルダ%sがある変数をprintf関数に代入するという意味です。上記の場合使用する変数はanswerで、printfに別の引数として与え、最初の引数とコンマで区切ります (printf("hello, answer")は文字通りhello, answerを毎回出力します) 。
  • CS 50 IDEに戻り、今まで学んだ内容を追加します。
#include <cs50.h>
#include <stdio.h>

int main(void)
{
    string answer = get_string("What's your name? ");
    printf("hello, %s", answer);
}
  • get_string関数を使用できるように、コンパイラに#include <cs50.h>を指定してCS 50ライブラリをインクルードするように指示する必要があります。
    • 変数answerには何でも名前を付けることができますが、ここではより良いスタイルを使用します。axのような短い名前よりも、よりわかりやすい名前の方が目的を理解するのに役立ちます。
  • ファイルを保存したら、make helloを使用してプログラムを再コンパイルする必要があります。ソースコードを変更しただけで、コンパイルされたマシンコードは変更していないからです。他の言語やIDEでは、コードを変更した後に手動で再コンパイルする必要はありませんが、ここでは、内部で何が起こっているのかをより理解する機会としましょう。
  • さて、./helloはプログラムを実行し、意図したとおりに名前を入力するように促します。hello, Brian~/ $のように、プログラムの出力の直後に次のプロンプトが表示されることに気づくでしょう。\nでプログラムの出力の後に新しい行を追加すると、次のプロンプトが単独の行に表示されます。:
printf("hello, %s\n", answer);
  • \nエスケープシーケンス、または他の特定のテキストを表すテキストの例です。

メイン、ヘッダファイル

  • Scratchの「when green flag clicked」ブロックは、メインプログラムと考えられるものを開始しています。C言語では、同じ役割に当たる最初の行はint main(void)です。これについては、今後のレッスンで詳しく説明します。次に、プログラムに必要なものをすべて囲んでいる開いた中括弧{、閉じた中括弧}が続きます。
int main(void)
{

}
  • この行を変更する方法については、次回以降のレッスンで詳しく説明しますが、今のところは、この行を使用してプログラムを開始します。
  • .hで終わるヘッダファイルは、ライブラリなど、プログラムで使用できる他のコードの集合を参照します。たとえば、printf関数を含む標準入出力ライブラリの場合は、#include <stdio.h>のような行を含めます

ツール

  • 新しい構文を使うと、間違いをしたり、何かを忘れたりするというのはよくあることです。スタッフが作成したツールがいくつか用意されています。
  • プログラムをコンパイルしようとすると、コンパイラはより技術的なプログラマ向けに設計されているため、理解しにくいエラーメッセージの行がたくさん表示されます。help50は、コード内の問題をよりわかりやすく説明するために実行できるコマンドです。help50 make helloのように、試しているコマンドの先頭にhelp50を追加することで、より理解しやすいアドバイスを得ることができます。
  • C言語では、新しい行やインデントは通常、コードの実行方法に影響しません。例えば、main関数をint main(void){printf("hello, world");}という1行の関数に変更することはできますが、読みにくいため、スタイルが悪いと考えます。style50 hello.cとして、ソースコードのファイル名を指定してstyle50を実行すると、改行とインデントの候補が表示されます。
  • また自分自身や他の人間に向けて、コードの実行方法に影響を与えないように、ソースコードにコメントを追加することもできます。たとえば、// Greet userのような行を追加し、その行がコメントであることを示すために//という2つのスラッシュを付けてから、後で分かりやすいようにコードまたはプログラムの目的を記述します。
  • check50 は、いくつかの自動テストをして、コードの正確性をチェックします。スタッフは、このコースで作成する一部のプログラム専用のテストを作成し、必要に応じて各問題セットまたは演習にcheck50の使用手順を含めています。check50を実行すると、コードが関連するテストに合格したかどうかを示す出力が表示されます。
  • また、CS50 IDEは、インターネット上のどこかにある、クラウド上の私たちのコンピュータに相当するものを、私たち自身のファイルとフォルダとともに提供します。左上のフォルダアイコンをクリックすると、IDEにファイルのGUIであるファイルツリーが表示されます。
  • ファイルを開くにはダブルクリックします。hello.cは先ほど作成したソースコードです。hello自体にはたくさんの赤い点があり、それぞれがコンピュータのバイナリ命令を表しているので印刷できません。

コマンド

  • CS50 IDEはクラウド上の仮想コンピュータであるため、macOSやWindowsのようなオペレーティングシステムであるLinuxで利用可能なコマンドを実行することもできます。
  • ターミナルでlsと入力すると、現在のフォルダ内のファイルとフォルダのリストが表示されます。
~/ $ ls
 hello*  hello.c
  • hello は緑で表示され、アスタリスクが付いています。これは、プログラムとして実行できることを示します。
  • rm helloのようなコマンドでrmを使ってファイルを削除することもできます。確認のプロンプトが表示され、yまたはn (yesまたはno) で応答できます。
  • mv (move) を使用すると、ファイルの名前を変更できます。mv hello.c goodbye.cとすると、hello.cファイルの名前をgoodbye.cに変更されます。
  • mkdir (make directory) を使用すると、フォルダまたはディレクトリを作成できます。mkdir lectureを実行すると、lectureというフォルダが作成されます。mv hello.c lecture/のようなコマンドを使用してファイルをディレクトリに移動できます。
  • ターミナルでカレントディレクトリを変更するには、cd lecture/のようにcdを使います。プロンプトが~/から~/lecture/に変わり、~内のlecture ディレクトリにいることが示されます。~は、ホームディレクトリまたはアカウントのデフォルトの最上位フォルダを表します。
  • ..を親フォルダ (格納されているフォルダ) の省略として使用できます。~/lecture/内でmv hello.c ..を実行すると、ファイルが~に移動します。これは、~lecture/の親フォルダであるためです。同様に、cd ..はカレントディレクトリを現在の親ディレクトリに変更します。1つのドット.は、./helloのように現在のディレクトリを参照します。
  • lecture/フォルダが空になったので、rmdir lecture/を使用して削除することもできます。

型、フォーマット

  • 変数には多くのデータ型を使用できます。データ型は、コンピュータにどの種類の文字を扱うかを示します。
    • bool: true またはfalseのブール式
    • char: a や2のような単一のASCII文字
    • double: floatよりも多くの桁を持つ浮動小数点値
    • float: 浮動小数点値、または10進数値を持つ実数
    • int: 特定のサイズまでの整数またはビット
    • long: intより多くのビットを持つ (より大きな数を表せる) 整数
    • string: 文字を文字列として扱う
  • また、CS50ライブラリには、さまざまなタイプの入力を取得するための対応する関数があります。
    • get_char
    • get_double
    • get_float
    • get_int
    • get_long
    • get_string
  • printfにも、タイプごとに異なるプレースホルダがあります。
    • %c でcharを格納
    • %f で float, doubleを格納
    • %i で intを格納
    • %li で longを格納
    • %s で stringを格納

演算子、制限、切り捨て

  • 使用できる算術演算子もいくつかあります。
    • +: 足し算
    • -: 引き算
    • *: 掛け算
    • /: 割り算
    • %: 剰余
  • addition.cという新しいプログラムを作ります。
#include <cs50.h> 
#include <stdio.h>

int main(void)
{
    int x = get_int("x: ");
    int y = get_int("y: ");

    printf("%i\n", x + y);
}
  • 使用するライブラリのヘッダファイルをインクルードし、get_intを呼び出してユーザから整数を取得し、x およびyという名前の変数に格納します。
    • printfで、整数%iのプレースホルダの後に改行を出力します。xyの合計を出力したいので、printfで文字列に代入するためにx + yを渡します。
    • 保存し、ターミナルでmake additionを実行してから、./additionでプログラムが動作することを確認します。整数以外の値を入力すると、get_intが再び整数を要求します。4000000000のような大きな数字を入力しても、get_intがプロンプトへの入力を促します。これは、多くのコンピュータシステムと同様に、CS50 IDEのintは32ビットであり、約40億の異なる値しか含むことができないためです。整数には正と負があるので、intの正の最大値は約20億、負の最小値は約20億となり、合計で約40億となります。
  • long 型を使用するようにプログラムを変更できます。
#include <cs50.h>
#include <stdio.h>

int main(void)
{
    long x = get_long("x: ");
    long y = get_long("y: ");

    printf("%li\n", x + y);
}
  • これで大きな整数を入力することができ、期待通りの正しい結果が得られます。
  • コンパイル中にエラーが発生した場合は、一番上までスクロールして最初のエラーを確認し、最初のエラーを修正することをお勧めします。プログラムの初期段階でエラーが発生すると、プログラムの残りの部分もエラーとして解釈されることがあるためです。
  • 別の例として、truncation.cを見てみましょう。
#include <cs50.h>
#include <stdio.h>

int main(void)
{
    // Get numbers from user
    int x = get_int("x: ");
    int y = get_int("y: ");

    // Divide x by y
    float z = x / y;
    printf("%f\n", z);
}
  • xyで割った結果をzとして浮動小数点値 (実数) に格納し、それも浮動小数点として出力します。
    • しかし、プログラムをコンパイルして実行すると、zは0.0000001.000000のような整数として出力されます。このコードでは、x/yは最初に二つの整数として分割されるので、割り算によって返される結果も整数になります。結果は切り捨てられ、小数点以下の値は失われます。zfloat (浮動小数点) ですが、格納される値はすでに整数です。
    • これを修正するには、キャスト (変換) してから除算します。
float z = (float) x / (float) y;
  • 結果は期待通りのfloatになります。実際には、xyのどちらか一方だけをキャストし、floatを得ることができます。

変数、糖衣構文

  • Scratchでは、 「set [counter] to (0) 」 のようなブロックを使って変数に値を設定していました。Cでは、int counter = 0;と記述します。同じ効果を得ることができます。
  • counter = counter + 1;で変数の値を増やすことができます。ここでは、最初に右辺を見て、counterの元の値を取得し、1を加え、それを左辺に格納します (counterに変数が戻ります) 。
  • Cは、同じ機能のためのシンタックスシュガー(糖衣構文)、つまり短縮表現もサポートしています。この場合、counter += 1;と等価になります (counterに1を加え再度保存します)。counter++;と書くこともできます。オンラインのドキュメントやその他の参考資料を見ることで、この例 (およびその他の例) を学ぶことができます。

条件

  • 条件または 「if」 ブロックは、次のように変換できます。
if (x < y)
{
    printf("x is less than y\n");
}
  • Cでは、コード行をネストする方法を示すために { } (およびインデント) を使用します。
  • 「if」 条件と 「else」 条件を使用できます。
if (x < y)
{
    printf("x is less than y\n");
}
else
{
    printf("x is not less than y\n");
}
  • また「else if」も利用できます。
if (x < y)
{
    printf("x is less than y\n"); 
}
else if (x > y)
{
    printf("x is greater than y\n");
}
else if (x == y)
{
    printf("x is equal to y\n");
}
  • C言語で2つの値を比較するには、== という2つの等号を使用します。
    • 論理的には、if (x == y) はこれが唯一残っているケースですから必要ありません。ですので、最終的な条件としてelseを指定します。
if (x < y)
{
     printf("x is less than y\n"); 
}
else if (x > y)
{
    printf("x is greater than y\n");
}
else
{
    printf("x is equal to y\n");
}
  • 別の例としてconditions.cを見てみましょう。
#include <cs50.h>
#include <stdio.h>
 
int main(void)
{
    // Prompt user for x
    int x = get_int("x: ");

    // Prompt user for y
    int y = get_int("y: ");
   
    // Compare x and y
    if (x < y)
    {
       printf("x is less than y\n");
    }
    else if (x > y)
    {
       printf("x is greater than y\n");
    }
    else
    {
       printf("x is equal to y\n");
    }
}
  • 先ほどの条件に加えて、ユーザからxyを取得するためのget_intの2つの呼び出し (使用) を含めました。
    • プログラムをコンパイルして実行し、意図したとおりに動作することを確認します。
  • agree.cでは、ユーザーに確認または拒否を求めることができます。
#include <cs50.h>
#include <stdio.h>

int main(void)
{
    char c = get_char("Do you agree? ");

    // Check whether agreed
    if (c == 'Y' || c == 'y')
    {
        printf("Agreed.\n");
    }
    else if (c == 'N' || c == 'n')
    {
        printf("Not agreed.\n");
    }
}

ブール式、ループ

  • Scratchの“forever”ブロックを翻訳するには、次のようにすればよいです。
while (true)
{
    printf("hello, world\n");
}
  • whileキーワードには条件が必要なので、ループが永久に実行されるようにブール式としてtrue を使用します。while は、式がtrueと評価されるかどうかをチェックするようにコンピュータに指示し、中括弧内の行を実行します。そして、その表現が真でなくなるまで、それを繰り返します。この場合、true は常に真であるため、ループは無限ループ、つまり永久に実行されるループになります。
  • whileで何かを決められた回数だけ行うことができます。
int i = 0;
while (i < 50)
{
    printf("hello, world\n");
    i++;
}
  • 変数iを作成し、それを0に設定します。次に、iが50未満である間に、コードを何行か実行します。その中に、毎回iに1を追加するコードが含まれます。このようにして、iの値が50に達すると、最終的にループが終了します。
    • この例では、変数iをカウンタとして使用していますが、追加の目的がないため、単にiと名前を付けることができます。
  • 以下のようにして1から数え始めてもよいですが、慣習的には0から始めるべきです。
int i = 1;
while (i <= 50)
{
    printf("hello, world\n");
    i++;
}
  • もうひとつの正しいが、恐らくあまりうまく設計されていない解法は、50から始まって逆算していくものです。
int i = 50;
while (i > 0)
{
    printf("hello, world\n");
    i--;
}
  • この場合、ループのロジックは、追加の目的がないため推論するのが難しく、コードの読み手を混乱させることがあります。
  • 最後に、より一般的には、forキーワードを使用できます。
for (int i = 0; i < 50; i++)
{
    printf("hello, world\n");
}
  • ここでも、最初にiという名前の変数を作成し、それを0に設定します。次に、内部のコードを実行する前に、ループの先頭に達するたびにi < 50であることを確認します。その式が真の場合、内部のコードを実行します。最後に、内部のコードを実行した後、i++を使用してiに1を追加し、ループを繰り返します。
    • この場合、for ループはwhile ループよりも洗練されています。ループに関連するものはすべて同じ行にあり、実際に複数回実行したいコードだけがループ内にあるからです。
  • if条件やforループなど、これらのコード行の多くでは、末尾にセミコロンを付けていないことに注意してください。これは、何年も前にC言語が設計された方法であり、一般的なルールとして、セミコロンはアクションまたは動詞の行の末尾にしかありません。

抽象化

  • meowを3回出力するプログラムを書くことができます。
#include <stdio.h>
 
int main(void)
{
    printf("meow\n");
    printf("meow\n");
    printf("meow\n");
}
  • forループを使用すると、多くの行をコピーして貼り付ける必要がなくなります。
#include <stdio.h>
 
int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        printf("meow\n");
    }
}
  • printf 行を独自の関数に移動することができます。
#include <stdio.h>
 
void meow(void) {
  printf("meow\n");
}

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        meow();
    }
}
  • main 関数の上に関数meowを定義しました。
  • しかし、通常、main関数はプログラムの最初の関数でなければならないので、さらに数行必要になります。
#include <stdio.h>
 
void meow(void);
 
int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        meow();
    }
}

void meow(void)
{
    printf("meow\n");
}
  • mainで使用する前に、まずプロトタイプ宣言meow 関数を宣言し、その後実際に定義する必要があることがわかりました。コンパイラーはソースコードを上から下へ読むので、meow がファイルの後の方に存在することを知る必要があります。
  • さらに、meow 関数を変更して、入力n回、meow n回を取り込むこともできます。
#include <stdio.h>
 
void meow(int n);
 
int main(void)
{
    meow(3);
}
 
void meow(int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("meow\n");
    }
}
  • meow 関数の前のvoid は、値を返さないことを意味します。同様に、main もmeowの結果に対しては何もしておらず、単にmeowを呼び出します。
  • ここでの抽象化は、将来的に複数の場所でmeow関数を再利用できる柔軟性を持っているため、より良い設計につながります。
  • 抽象化の別の例として、get_positive_int.cを見てみましょう。
#include <cs50.h>
#include <stdio.h>

int get_positive_int(void);

int main(void)
{
    int i = get_positive_int();
    printf("%i\n", i);
}

// Prompt user for positive integer
int get_positive_int(void)
{
    int n;
    do
    {
        n = get_int("Positive Integer: ");
    }
    while (n < 1);
    return n;
}
  • 1以上の整数が得られるまでget_intを繰り返し呼び出す独自の関数があります。do-whileループを使用すると、プログラムはまず何かを実行してから条件をチェックし、条件が真である限り繰り返します。一方、whileループでは、まず条件がチェックされます。
    • 整数nはdo-whileループの外側で宣言する必要があります。これはループの終了後に使用する必要があるからです。Cの変数のスコープは、その変数が存在するコンテキストまたはコード行を参照します。多くの場合、これは変数を囲む中括弧になります。
    • 関数get_positive_intintで始まるようになりました。これはint型の戻り値を持つことを示しており、実際にはget_positive_int()を呼び出した後にiに格納しています。get_positive_intには、関数が呼び出された場所に値nを返すための新しいキーワードreturnがあります。

マリオ

  • スーパーマリオブラザーズのようなビデオゲームの画面の一部を印刷するプログラムを作ってみたいかもしれません。mario.cでは、ブロックをシミュレートして4つのクエスチョンマークを印刷できます。
#include <stdio.h>
 
int main(void)
{
    printf("????\n");
}
  • ループを使用すると、複数のクエスチョンマークを印刷し、そのあとにループの後に新しい行を追加できます。
#include <stdio.h>
 
int main(void)
{
    for (int i = 0; i < 4; i++)
    {
        printf("?");
    }
    printf("\n");
}
  • ユーザから正の整数を取得し、その数のクエスチョンマークを出力するには、ループにnを使用します。
#include <cs50.h>
#include <stdio.h>
 
int main(void)
{
    // Get positive integer from user
    int n;
    do
    {
        n = get_int("Width: ");
    }
    while (n < 1);
 
    // Print out that many question marks
    for (int i = 0; i < n; i++)
    {
        printf("?");
    }
    printf("\n");
}
  • また、ネストされたループを持つブロックの2次元セットを印刷し、一方を他方の内側に配置することもできます。
#include <cs50.h>
#include <stdio.h>
 
int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("#");
        }
        printf("\n");
    }
}
  • 2つのネストされたループがあり、外側のループはiを使用して内側のすべてを3回実行し、内側のループはj (異なる変数) を使用してそれぞれの回数で何かを3回実行します。つまり、外側のループは3つの 「行」 (行) を出力し、それぞれの行の最後に新しい行を追加し、内側のループは3つの 「列」 (文字#) を出力し、新しい行は出力しません

メモリ、精度不足、オーバーフロー

  • 私たちのコンピュータはRAM (ランダムアクセスメモリ) と呼ばれるハードウェアチップを搭載しています。私たちのプログラムは、実行中のデータを保存するためにそのRAMを使用しますが、そのメモリは有限です。
  • imprecision.cを使用すると、floatを使用した場合に何が起こるかがわかります。
#include <cs50.h>
#include <stdio.h>
 
int main(void)
{
    float x = get_float("x: ");
    float y = get_float("y: ");
 
    printf("%.50f\n", x / y);
}
  • %.50fでは、表示する小数点以下の桁数を指定できます。
    • すると、以下の結果を得ることができます。
x: 1
y: 10
0.10000000149011611938476562500000000000000000000000
  • これは浮動小数点精度と呼ばれ、すべての可能な値を格納するのに十分なビットがないことがわかります。浮動小数点のビット数は有限なため、可能なすべての実数 (無限数) を表すことはできないため、コンピュータは可能な限り近い値を格納する必要があります。これは、プログラマが必要に応じて正確に小数の値を表すために別の方法を使用しない限り、値のわずかな違いでも、それらを合計した場合は問題につながる可能性があります。
  • 前回は、3つのビットがあって、7 (111) よりも大きく数える必要があったとき、8 (1000) を得るためにもう1つのビットを加えました。しかし、もし3つのビットしか使えなければ、余分な1を入れる場所はありません。そのビットは消えて、000に戻ります。この問題は整数オーバーフローと呼ばれ、整数はビット切れにならないという前提でしか大きくなり得ません。
  • Y2K問題が発生したのは、1998年の98、1999年の99のように、多くのプログラムが暦年を2桁だけで格納していたためですが、2000年が近づくと00しか格納しなくなり、1900年と2000年の間で混乱が生じました。
  • 2038年には、時間を追跡するためのビットもなくなります。何年も前に、1970年1月1日以降の秒数を数えるために32ビットを標準のビット数として使用することを決めた人がいたためです。しかし、32ビットで正の数を表すとすると、40億までしか数えることができません。2038年には、ソフトウェアをアップグレードしない限り、その制限に達するでしょう。