コンテンツにスキップ

宿題:命令型言語2#

命令型言語#宿題2

Homework 約30分 答え

次のC言語プログラムと等価なPythonプログラム,およびJavaプログラムを答えよ.

int factorial(int n) {
    if (n == 1) { 
        return 1;
    }
    return n * factorial(n - 1);
}

void main() {
    printf("%d", factorial(5));
}

以下の書式をコピーして回答せよ.様々な回答がありうるが機能的に等価であれば良い.必ず再帰呼び出しを用いること.

# python
???

# java
???

回答フォーム

Answer

Python
def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)

print(factorial(5))
Java
public class Homework {
    public static int factorial(int n) {
        if (n == 1) {
            return 1;
        }
        return n * factorial(n - 1);
    }
    public static void main(String[] args) {
        System.out.printf("%d", factorial(5));
    }
}

以下のような差異が確認できる.

  • Pythonは動的型付け言語なのでfactorial()の引数と返り値の型宣言がない
  • Pythonは文末のセミコロンが不要(インデントと改行が構文的意味を持つため)
  • JavaはCと同じ静的型付け言語なのでint factorial(int)という宣言が同じ
  • JavaはC言語から派生しているためprintf()の引数ルールは同じ
  • Javaはオブジェクト指向言語なので;
    • プログラム実行のためにクラス宣言が必要(この場合class Homework
    • factorial()関数には可視性(public private)の宣言が必須

ただし,どちらの言語も手続き言語であるため本質的な違いは少ない.

Q&A#

:口頭での解説はスキップ

誤字#

  • スタックは4ビットバイト,ヒープは32ビットバイト
  • 先入れ先出し後出しのデータ構造のこと.
  • SegmnetationSegmentation fault
  • ほとんどの場合,以下の図ように
  • コンピュータサイエンス分野おける

宿題のfactorial()関数はバグってるのでは?#

0!1であるべき.

そのとおりです.来年直します.

int factorial(int n) {
    if (n == 0) {  // if (n == 1) だと0!が計算できない
        return 1;
    }
    return n * factorial(n - 1);
}

なお,負の値は考慮していません.

どちらのソースコードが良いか?#

int factorial(int n) {
    if (n == 0) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}
int factorial(int n) {
    if (n == 0) {
        return 1;
    }
    return n * factorial(n - 1);
}
答え

明らかに右側.非負整数(0, 1, 2, ...)を考えたときに,右側が「0のみ特殊」という性質をうまく表現している.

early returnパターンと呼ばれるテクニックです.特殊な状況(nullや0等)のみを先にチェックして早いうちにリターンする.関数の本体を後ろに書く.

void doSomething() {
  if (/* 異常 */) {  // 異常を見つけたら
    return;        // 即終了
  }
  // 正常時の処理
  ...

これはよくないパターン.インデントが深くなりがち.

void doSomething() {
  if (/* 正常 */) {  // 正常なら
    ...            // 正常時の処理
  }

scanf()&をつける理由が納得できた#

great 👍👍👍

宿題の回答をメールで返信するようにしてほしい#

メールの自動返信のためには,メールアドレスを必須入力項目にしないといけない.あまり学生の入力項目を増やしたくない.

学籍番号の代わりにメールアドレスにすると良いかも.

無限に再帰呼び出ししたら15,400回でスタックオーバーフローが発生した#

おもろい

スタックに積まれる変数以外の情報は戻りアドレスか?#

まさにそう.授業後半でやります.

再帰関数の必要性がわからない#

これはデータ構造とアルゴリズムを勉強すべき.

全ての再帰処理はループで表現できます.ただ再帰で記述したほうが簡潔に記述できる問題もたくさんあります.空間の探索や木の走査は再帰が向いています.

ヒープの方が1変数の割当サイズが大きいのはなぜか?#

割り当て行為そのもののメタ情報が必要だからです.

  • スタック:値だけを積み上げれば良い(値のサイズは固定なので管理が楽)
  • ヒープ:値と値に関する情報(値のサイズ情報等)を管理する必要がある

グローバル変数と静的変数にメモリ管理の違いはあるか?#

同じなはず.グローバル変数と静的変数はスコープは違うが生存期間は全く同じ.メモリ上の扱いも同じ(だと思う).

free()を忘れてプログラムを終了したらどうなるのか?#

普通にメモリ解放されます.ヒープもスタックもプログラム終了時にすべて解放されます.

なので演習のような,短時間で終了するプログラムではfree()忘れの問題はほぼ起きません.常駐するプログラムで問題になりがちです.

ヒープとスタック以外のメモリ領域構成を採用する言語は存在するか?#

まつ本の知る限りない.命令型言語のほとんどはヒープ+スタックが基本のはず.

Rustは主に何に使われているのか?#

C言語の代替を目指している.主にバックエンド.

「Rustではより先進的なメモリ管理の仕組みが~」とあるが具体的には?#

自分で調べよう.

大学院の倍率は?#

自分で調べよう.


関数や変数の名前付けが難しい#

名前付けはプログラミングにおいてかなり重要な要素です.対象の「言語化」と言い換えても良い.基本は2ステップです.

  • 対象の分解
  • 対象の言語化

この2つは問題解決能力に直結します.プログラミングはその能力を鍛える手段だと考えると良い.

関数を多用するとトレースが大変になって余計に混乱する#

実行経路全体をトレースする,という状況を見直すべき.デバッグのような特殊な状況でない限り,実行経路全体をトレースする必要はない.

例えば次のプログラムを眺めたときに,toUpper()関数の中身を理解しなくても出力結果を予測できるはず.

void main() {
    char s[30] = "abc";
    toUpper(s);
    printf("%s\n", s);
}

重要な点は2つです.

  • 関数に適切な名前が付与されていること
  • 関数が適切に動作していること

この2つが満たされていれば,より高い抽象度でプログラムを記述することができます.toUpper()の内部処理のような抽象度の低いレベルでの理解やトレースが不要になります.

なぜ何種類もプログラミング言語があるのか?1つに統合してよいのでは?#

世界の自然言語を1つに統合してよいのでは?と同じ疑問です.様々な事情で不可能です.

  • 言語ごとにコミュニティが違う
  • 言語ごとに得手不得手がある
  • 言語ごとに機能の違いがある
  • 言語ごとに開発支援の手厚さも違う
  • そもそも最強の言語は存在しない・好みの問題もある
  • 言語は常に進化している・常に新たなアイデアが登場している