CとRの類似点

RはC言語で書かれているため、両者には共通点が多いようだ

#include <stdio.h>

main () {
	int age;
	age = 22;

	if (age >= 20)
	{
		puts("You are an adult");
	} else {
		puts("You are a child");
	}
}

基本的な構文、演算子等は酷似している。(そもそもどの言語も似通っているかもしれないが)

a ==  b : aとbの値が等しい

a != b :aとbの値が等しくない

a >= b, a <= b :aとbの大小

(a == b) && (c==d) : aとbが等しい かつ cとdが等しい    ※Rでは&を使用

(a == b) || (c==d): aとbが等しい または cとdが等しい  ※Rでは|を使用

a % b:aをbで割った余り                                     ※Rでは a%%b

 

目立った違いは、冒頭の#include <stdio.h>, 変数の型の定義,main()の部分, あとはセミコロンくらいのように思える。なお、セミコロンをif文の後につけると、if文が機能しなくなるとのこと。(下記if文の条件は終了し、スキップしてしまう)

#include <stdio.h>

main () {
	int age;
	age = 20;

	if (age = 20) ;  //このセミコロンがあるとif文が無効となる
	{
		puts("Congratulations!");
	}
}

Rで扱ったswitch文もほぼ同様の感覚。

#include <stdio.h>

main () {
	int result;
	result = 0;

	switch (result) {
		case 0:
			puts("Very Bad");
		break;
		case 1:
			puts("Poor");
		break;
		case 2:
			puts("As usual");
		break;
		case 3:
			puts("Good");
		break;
		case4:
			puts("Very Good");
		break;
		default:
			puts("Error");
		break;
	}
}

for文、while文も同様。

#include <stdio.h>

main () {
	int counter;

	for (counter=0; counter <=8; counter +=1) {
		printf("%d\n",counter);
	}

	long long num;
	num = 1;
	while (num <= 1000000000000)
	{
		printf("%d\n",num);
		num *= 2;
	}
}

最後のwhile文はオーバーフローしたのか、途中で値が0となった。何故か。

1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
1073741824
-2147483648  ←突然負の値が登場し、以後0に。
0
0
0
0
0
0
0
0

Rと同様の間隔で扱える範囲については、思ったより学習は早く進められるかもしれない。

C言語 配列オーバーの動き

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

プロがおっしゃるに、C言語では以下のような事象があるとのこと。

  • C言語のプログラムのバグの半分以上は配列の添え字が大きいことに起因する
#include <stdio.h>

main () {
        int days[12] =
                {
                31,28,31,30,31,30,
                31,31,30,31,30,31
                } ;

        int i;
        for (i = 1; i <= 12; i++) {
                printf("%d月は、%d日まであります。\n", i, days[i-1]);
        }
}

上記ではdays[12]を定義しているが、成分名はdays[0], days[1], …のように0から開始する。当然days[12]はない。days[12]にアクセスしようとすると(これを著者は"配列オーバー"という)、どうなるかは「わからない」とのこと。これは、C言語が配列オーバーのときにどのような動作をするかを、規定していないからだそう。固まって動かなくなることもあるらしい。

(恐ろしいのでメモ…)

C言語 ポインタ変数(1) 定義

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

ポインタ変数とは

「変数などの場所を覚えておくための変数」

ポインタ変数を使うと、1つの変数で、複数の別の変数の代わりをすることが出来たり、配列でポインタ変数を扱うと演算が出来る、とのこと。

#include <stdio.h>
main () {
        int a[5] = {1,2,3,4,5};
        int *p;
        int i;
        p = a; //配列の先頭ポインタを取得 p = &a[0]の省略形

        for (i = 0; i < 5; i++)
        {
                printf("%d\n",*(p+i));
        }
}

ポインタpを int *p によって定義。p = aによってaの1番目の要素(a[0])を割り当てる。for文の中ではp+i(つまりa[0+i])を表示されている。

ポインタの使い道の1つとして、配列の成分にポインタを割り当てて、ポインタを動かすことで配列に対して演算を実行して使う、ということか。

以下出力。

1    //a[0]    for文中では*p+0
2    //a[0+1] for文中では*p+1
3    //a[0+2] for文中では*p+2
4    //a[0+3] for文中では*p+3
5    //a[0+4] for文中では*p+4

なお、以下のように出力を*(p+i)ではなく、pとすると、pが覚えている場所の数列が表示される。

#include <stdio.h>
main () {
        int a[5] = {1,2,3,4,5};
        int *p;
        int i;
        p = a; //配列の先頭ポインタを取得 p = &a[0]の省略形

        for (i = 0; i < 5; i++)
        {
                printf("%d\n",*(p+i));
                printf("%d\n",p+i);
        }
}

以下出力。iが1増加する毎になぜか4増加している。これは何故だろうか。

1        //for文中では*p+0
954535584      //for文中ではp+0
2              //for文中では*p+1
954535588      //for文中ではp+1
3              //for文中では*p+2
954535592      //for文中ではp+2
4              //for文中では*p+3
954535596      //for文中ではp+3
5              //for文中では*p+4
954535600      //for文中ではp+4

【追記】

ポインタ変数を定義するときは、

   char *p,  int *p

などと書き、1 ) ポインタ変数にアドレスを格納する際

   p = 文字列,  p = &変数, p = &a[i] または a (aが配列の場合) 

などと書く。2) ポインタ変数にそのものに値を格納する際は、

   *p = 文字列,  *p = 変数

などと書く。ポインタ自体はwindowsのショートカットのようなものであり、1)の行為はショートカット先を決める行為、2)の行為はショートカット開き変数を編集することに相当する。と理解した。

 【追記2】

&はアドレスを取得する演算子、*は中身を取得する演算子 である。

 

Linux Tips (OOM Killer対策)

Linuxサーバー上で、OOM Killerなるものに処理中のプロセスを遮断される事例が多発。メモリが限界値を超えると、最も占有が大きいプロセスを自動的に遮断する模様。

以下のように書き込むことで、落とされたくないプロセスを選定出来る。

for i in $ (ps ux | grep "文字列" -3 | cut -f 2 -d " "); do echo -17 > echo -17 > /proc/(PID)/oom_adj; done

なお、特定な邪魔なプロセスを全て遮断するときは、下記。

for i in $ (ps ux | grep "文字列" -3 | cut -f 2 -d " "); do kill -KILL $I; done

よくわからん…。

C言語 文字列

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

C言語では、文字列を文字の配列として扱う。例えばgenomeなら、6つの文字の配列 char a[6]として扱う。a[0] = gだし、a[5] = eとなる(成分の開始番号は0)。実際は、文字列の最後にはnull文字 \0が必要。らしい。

文字列操作を行う関数を使ってみた。(本で学んだ関数の知識を、自分なりに使った)

使った関数を整理しておくと:

  • gets() 文字列の入力を要求する関数。入力値は引数に代入される。 <studio.h>
  • atoi()     文字列を整数に変換する関数。gets() → atoi()の順番で使う。<stdlib.h>
  • strcpy() 文字列の代入を行う関数。第1引数に第2引数を代入する。<string.h>
  • strncpy() 同上。ただし、第3引数で文字列の長さを指定した上で行う。<string.h>
  • strcmp() 第1引数と第2引数が等しい場合に0を返す関数。<string.h>
  • strcat()   第1引数の後ろに第2引数を結合する関数。 <string.h>
  • strlen()   文字列の長さ(バイト数)を測る関数。 <string.h>
  • sprintf()  フォーマットを指定した文字列を生成。詳細は下記参照。<studio.h>
#include <stdio.h>
#include <stdlib.h>                             //関数 atoi を使用するため
#include <string.h>                             //関数 strcpy,strncpy,strcmp,strcat,strlen を使用するため

main () {
        char str[10];                           //文字配列の定義。10は文字列の長さの上限値
        int num;

        puts("何でもよいので、数値を入力して下さい。");
        gets(str);                              //キーボードから入力する関数gets
        num = atoi(str);                        //入力した文字列を数値に変換する関数atoi(数値以外を入力すると0)
        printf("入力された数値は%dです\n",num); //文字が入力された場合は0が返る

        strcpy(str,"でも実は違います");         //文字列の代入に用いるstrcpy関数
        puts(str);

        strncpy(str,"ちなみに、文字列が長すぎると配列オーバーを招くので危険です",9);    //文字列の上限数を設定して代入するstrncpy関数
        str[9] = '\0';                                                                  //strは上限値10。最後にnull文字を付加する
        puts(str);

        puts("ところで、パスワードを入力して下さい。");
        char pass[50];

        gets(pass);
        if (strcmp(pass,"password") == 0) {     //引数の一致に対して0を返す関数 strcmp関数
                puts("OKです");
        } else {
                puts("NGです");
        }

        puts("次に、文字列を結合してみましょう。");
        char a[100];
        char b[100];

        puts("1つめを入力してください");
        gets(a);
        puts("2つめを入力してください");
        gets(b);

        strcat(a,b);                            //第一引数に、第二引き数を結合する関数 strcat関数
        puts(a);
        printf("入力した文字列の長さは%dです\n",strlen(a));     //文字列の長さを返す strlen関数

        puts("最後にsprintif関数の練習です。");
        puts("今まで入力した値をおさらいします。");

        char osarai[1000];
        sprintf(osarai,"%d,%s,%s,%s",num,pass,a,b);     //文字列をまとめる関数sprintf
        puts(osarai);
}

出力は以下。

何でもよいので、数値を入力して下さい。
123                                //入力した値
入力された数値は123です
でも実は違います
ちなみ                 
ところで、パスワードを入力して下さい。
password               //入力した値
OKです
次に、文字列を結合してみましょう。
1つめを入力してください
今日は                //入力した値
2つめを入力してください
雨です                //入力した値
今日は雨です
入力した文字列の長さは18です          //不明点1
最後にsprintif関数の練習です。
今まで入力した値をおさらいします。
-476741143,password,今日は雨です,雨です   //不明点2

【不明点】

  • 全角ひらがなの場合、文字列の長さは1文字あたり3カウントされているが、なぜか?
  • atoi関数で文字列から数値に変換した値を格納したnumが最後の表示でおかしな値になっているのはなぜか?

C言語 関数の作成 (1)

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

C言語改訂版1 はじめてのプログラミング (CD-ROM付) (プログラミング学習シリーズ)

C言語の関数の構成は、

開始部分、変数の宣言部分、処理部分、終了部分

  • 開始部分 変数の型 関数名(引数の型, 引数名, 引数の型, 引数名, …) (例 void main () とか、int larger (int a)とか)
  • 変数の宣言部分 さっきやった
  • 処理部分 さっきやった
  • 終了部分 return 変数で返り値を返す(Rと似てる)

結局、どの言語も似たようなことを考えているので、発想は同じらしい。ただ、Rと比較すると「変数の型」に対して厳密さを要求していると感じられる。(Rはいちいち何型か事前に定義しなくても良い)

 

日付を入力すると、曜日を返す関数を作ってみた。

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

void dayfunction (int year, int month, int day) {

        int yeardays;
        int monthdays;
        int totaldays;
        int resyear;
        int nuruu;
        int a[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
        int i;
        int adj;
        int result;
        char dayname[100];

        resyear = year % 4;
        nuruu = year/4;
        monthdays = 0;
        adj = 0;

        if (resyear  == 0) {
                yeardays = (year-1)*365 + nuruu - 1;

                if (month >= 3) {
                        adj = 1;
                }

                        if (month == 1) {
                                monthdays = 0;
                        } else {
                                for (i = 0; i < (month-1); i++) {
                                        monthdays = monthdays + a[i];
                                }
                        }
                monthdays = monthdays + adj;
                totaldays = yeardays + monthdays + day;

        } else {
                yeardays = (year-1)*365 + nuruu;

                        if (month == 1) {
                                monthdays = 0;
                        } else {
                                for (i = 0; i < (month-1); i++) {
                                        monthdays = monthdays + a[i];
                                }
                        }
                totaldays = yeardays + monthdays + day;
        }

        result = totaldays % 7;

        switch (result) {
                case 0:
                        strcpy(dayname,"Satureday");    //文字列の代入はstrcpy関数を使う
                        break;
                case 1:
                        strcpy(dayname,"Sunday");
                        break;
                case 2:
                        strcpy(dayname,"Monday");
                        break;
                case 3:
                        strcpy(dayname,"Tuesday");
                        break;
                case 4:
                        strcpy(dayname,"Wednesday");
                        break;
                case 5:
                        strcpy(dayname,"Thursday");
                        break;
                case 6:
                        strcpy(dayname,"Friday");
                        break;
                default:
                        puts("計算不可");
                        break;
        }
        puts(dayname);
}

main() {
        char yearnum[100];
        int year;
                puts("曜日を求めたい年月日を入力します");
                puts("年を入力");
                gets(yearnum);
                year = atoi(yearnum);   //文字列で取得したyearをint型に変換

        char monthnum[100];
        int month;
                puts("月を入力");
                gets(monthnum);
                month = atoi(monthnum); //文字列で取得したmonthをint型に変換

        char daynum[100];
        int day;
                puts("日を入力");
                gets(daynum);
                day = atoi(daynum);     //文字列で取得したdayをint型に変換

        dayfunction(year,month,day);
}

3時間以上かかりましたが…。最初打ち込んだときは、次の間違いを犯していました。

  • 関数dayfunctionの返り値をchar型(冒頭を char dayfunction (…))として、最後をreturn daynameとしていた ⇒ returnはどうやら相手が数値でないとダメらしい
  • 関数dayfunction中のfor文のiの上限値を i < (month-2) としていた ⇒ for文の変域の間違いはコンパイル時にエラーが出ない(この場合month=1ならエラーのはずが、コンパイル時にはエラーが出ない)。コンパイル時にエラーが出ないと探すのに苦労する。この場合は等号を含むか、month-1としないとダメ。
  • main()部分でatoi関数を使うための冒頭 #include <stdlib.h>をつけ忘れていた ⇒ ヘッダー確認はプログラム作成工程に組み込まないと無駄に苦労する

とりあえず、基本は理解したような気がします。これによるとキリストが生まれた日(1年1月1日)は日曜日だそうです。あれ、生まれた日だっけ??

【付記】

  • int adjを定義した後、当初はadj =0 を書いていなかったことにより、曜日がずれてうまくいかなかった。int adjと書いた時点でデフォルト値(0でない値???)が入っているのか?実験したところ、定義のみ表記した整数には0が代入されているようだが…