thisdesuのブログ

C言語はじめました

にほんブログ村 IT技術ブログ C/C++へ

列挙型(emun)

列挙型とはちょっと分かり難いけど、実値にこだわらず沢山の定数が必要な時に便利です。実値はユニークが保障されているし列挙した順に1から順に振ってくれます。

enum TODOU { HOKKAIDO, AOMORI, IWATE } ;

int main() {
    enum TODOU admin;

    printf("[%d]\n", HOKKAIDO );
    printf("[%d]\n", AOMORI );
    printf("[%d]\n", IWATE );
}


実行

$ gcc main.c
$ ./a.out
1
2
3


でも、番号を指定したいなーっと思ったら=で設定して下さい。以後はその値からカウントアップされます。

enum TODOU { HOKKAIDO, AOMORI=3, IWATE } ;


実行

$ gcc main.c
$ ./a.out
1
3
4

構造体(struct)

沢山の変数を扱うときに決まった並びや、それを繰り返し利用する際に、この体系を定義するのを構造体と言います。
名前は何でもよいです。(予約名は除く)例えばint,int,char[10]の変数の並びの構造体を、kata と言う名前として定義も可能です。

struct kata
{
    int    first;
    int    second;
    char    third[10];
};


宣言する時は普通の変数の時と同じで、配列も出来ますしポインターとしても出来ます。

int main
{
    struct kata nor;
    struct kata arr[10];
    struct kata *ptr;
}


この構造体の中の1つ1つはメンバと呼びます。これらを参照や設定する場合には、実態なら[.]を使って名称をしてします。アドレスなら[->]を使います。

int main
{
    struct kata nor;
    struct kata arr[10];
    struct kata *ptr;

    nor.first = 10;
    printf("[%s]\n",nor.first);

    nor[0].first = 10;
    printf("[%s]\n",nor[0].first);

    prt = (struct kata)malloc(sizeof(struct kata));
    memset(ptr,0x00,sizeof(struct kata));

    nor->first = 10;
    printf("[%s]\n",nor->first);
}

ちょっと一息(include)

インクルード(include)とは、#include と書いた位置に指定したファイルを埋め込みます。同じ記述を使いたい場合にはincludeで読み込みましょう。どんな感じに読まれるのはコンパイラプリプロのブログ参照してください。
よく使うのはヘッダファイルと言われる拡張子[.h]のファイル。これってただの作法です。ファイル名なので何でもいいです。

main.c

#include <stdio.h>

#include "this.desu"

int main(){
  printf("%s\n",FOO);
  return;
}


this.desu

#define FOO "bar"

 

$ gcc main.c
$ ./a.out
bar


それでも、作法にもちょっと気になる部分があります。ファイル名を囲む作法。サンプルでもstdio.hは[<>]で囲まれてますね。前例のthis.desuは[""]で囲まれてますね。これはちコンパイルする際に標準の場所を*から*探しに行ってくれるのか、自分で作ったファイルから探してくれるのか違いです。標準のファイルは[""]でも、自分同じ名称で無ければ結局探してくれますので良いのですが、もし同じファイルがあったら自分のが利用されます。まっ作法的に[<>]はシステムのもの[""]は自分が作ったものが分かるように分けて書きましょう。

文字区切りstrtok

文字列を特定の文字ごとに区切りたい時など便利か関数=strtokです。例えば"123-456-78-9-0A"の文字列が在った時に、-で区切って簡単に取得したいときに便利です。
が、、、、この関数には2つの悪魔が住んでるので要注意。後から説明します

■サンプル

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

int main() {
  char       string[]    ="123-456-78-9-0A";
  char       *p;

  p = strtok(string,"-");
  while( p != 0x00 ) {
       printf("[%s]\n",p);
     p = strtok(0x00,"-");
  }
}


■結果

$ gcc main.c
$ ./a.out
[123]
[456]
[78]
[9]
[0A]


さて1つ目の悪魔。サンプルで2回目以降の取得する時の関数の引数にnull(0x00)を指定していて、あれ?アドレス指定してないのに?って思うでしょう。
そう、この関数はステータスを保持しちゃう関数で、同時に2つは使えないんです。途中で使うと情報を上書きして消えてしまうんです。超要注意

■サンプル

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

int main() {
  char       string[]    ="123-456-78-9-0A";
  char       *p;
  char       string2[]    ="XXX-yyy-z-xyz-xyz";
  char       *p2;

  p = strtok(string,"-");
  printf("[%s]\n",p);

  p = strtok(0x00,"-");
  printf("[%s]\n",p);

  p2 = strtok(string2,"-");
  printf("[%s]\n",p);

  p = strtok(0x00,"-");
  printf("[%s]\n",p);
}


■結果

$ gcc main.c
$ ./a.out
[123]
[456]
[456]
[yyy]


そして2つ目の悪魔。またまたサンプルで2回目以降の取得する時の関数の引数にnull(0x00)を指定していたの覚えているでしょうか?なぜ0x00?って思ったしょう。それってstringの終端じゃね?そうです。strtokで取得した文字列はstringになってましたね。はい。お気付きですね。この関数。最初に指定した文字列の値を変更してるんです。区切り文字の箇所の0x00を入れ込んでるんです。次に探す頭が0x00から続けているんです。
この関数を”探す”って利用すると危険です。元の値を変更するって事を覚えておいてください。さらに変更不可の領域(const)t)を設定すると例により落ちます。

■サンプル

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

int main() {
  char       string[]    ="123-456-78-9-0A";
  char       *p;

  printf("[%s]\n",string);
  p = strtok(string,"-");
  printf("[%s]\n",p);
  printf("[%s]\n",string);

  p = strtok(0x00,"-");
  printf("[%s]\n",p);
  printf("[%s]\n",string);
}


■結果

$ gcc main.c
/a.out
[123-456-78-9-0A]
[123]
[123]
[456]
[123]

printf

C言語では最もよく使う関数でしょう(派生も含め)。この関数は色んな型の値を指定した書式で表示(標準出力)してくれる関数ですが、この指定の仕方が沢山あって覚えるのが大変なのですが、基本的なものは覚えましょう。

■printf書式

printf("フォーマット",値1,値2,,,,);

フォーマットは固定の文字も含む事が出来て、このフォーマットの中に値の書式を指定します。書式は[%]から始まる記号で構造の値の数だけ指定でき、左から値の順に指定することになります。
ただし、値の型と指定した書式と形式が有っていないと意図しない結果や落ちますので要注意。

■指定書式

%c :1文字を表示しますchar型用
%s :string用
%d :整数型(int,short)用
%f :実数型(float)用
%x :16進数で表示します(int,short)用


■サンプル1

#include <stdio.h>

int main() {
  char c = 'c';
  char s[7] = "string";
  int d = 11;
  float f = 12.3;
  int x = 10;

  printf("%c\n",  c );
  printf("%s\n",  s );
  printf("%d\n",  d );
  printf("%f\n",  f );
  printf("%x\n",  x );

}

■実行結果

$ gcc main.c
$ ./a.out
c
string
11
12.300000
a


また1つの書式にも色々は指定ができます。

%10d  :右詰10文字で整数を表示
%010d :10文字でゼロパディング(先頭を0で埋める)表示
%.1f  :小数点以下1位で表示(四捨五入)
%0.1f :小数点以下1位で表示(四捨五入)
%10s  :右詰10文字で文字列を表示
%-10s :左詰10文字で文字列を表示※見た目分かりませんが後ろがスペースで埋まります。


■サンプル1

#include <stdio.h>

int main() {
  int   d = 11;
  float f = 1.254;
  char  s[6] = "hello";

  printf("%d\n",  d );
  printf("%10d\n",  d );
  printf("%010d\n",  d );

  printf("%f\n",  f );
  printf("%.1f\n",  f );

  printf("[%s]\n",  s );
  printf("[%10s]\n",  s );
  printf("[%-10s]\n",  s );
}

■実行結果

$ gcc main.c
$ ./a.out
11
        11
0000000011
1.254000
1.3
[hello]
[     hello]
[hello     ]


■参考
printfの仲間

printf
fprintf
sprintf
snprintf
vprintf
vfprintf
vsprintf
vsnprintf

四則演算

すこし基本に戻って四則演算を頑張ってみる。
基本的には数式と変わり有りませんが、ちょっと記号が異なるので要注意。

+ :足し算
- :引き算
* :掛け算
/ :割り算
% :余り


■サンプル(intの例)

#include <stdio.h>

int main() {
  int s;
  int d;

  s = 11;
  d = 5;

  printf("%d\n", ( s + d ) );
  printf("%d\n", ( s - d ) );
  printf("%d\n", ( s * d ) );
  printf("%d\n", ( s / d ) );
  printf("%d\n", ( s % d ) );
}

■実行結果

$ gcc main.c
$ ./a.out
16
6
55
2
1

メモリー

C言語で通なければならない関門のメモリ。使ってるメモリが状態がどうなってて、何をどう使ってるのを把握しておかないとnull pointerでスタックトレースなんて生温い結果じゃなくて、segmentation fault。はい終了。原因なんて教えてくれれやしない。プログラム終了。「落ちた」なんて連呼されることになります。
メモリは静的メモリと動的メモリの2種類があります。静的メモリとは変数を宣言したら勝手に確保してくれるお手軽メモリ。「 char a[10] 」でsize(char*10)分のメモリが自動で確保されます。しかし、動的メモリは自分で確保してから変数に割り当ててあげなければなりません。なんだか面倒ですね。
しかし静的メモリは上限(スタック領域)があるし、なによりスコープ(見える範囲や確保されている範囲)があるので、全体的に使いたいときなど逆に使い辛いです。グローバル変数なんてスコープもありますが、スパゲッティの名人になれそうなワードです。

■静的メモリ

--- main.c ---
#include <stdio.h>

void func() ;
void sub() ;

int ggg; //グローバル変数

int main() {
    int lll; //ローカル変数

    ggg = 1;
    lll = 2;

    printf("main ggg:%d lll:%d\n",ggg,lll);
    func();
    printf("main ggg:%d lll:%d\n",ggg,lll);
    sub();
    printf("main ggg:%d lll:%d\n",ggg,lll);
}

void func() {
     int lll;// ローカル変数 main.cのlllとは別物

     lll = 4;

     printf("func ggg:%d lll:%d\n",ggg,lll);

     ggg = 5;
     lll = 6;

     printf("func ggg:%d lll:%d\n",ggg,lll);

    // ここでlllは開放
}
--- sub.c ---
#include <stdio.h>

extern ggg;//グローバルを見えるようする

void sub() {
     int lll;// ローカル変数 main.cのlllとは別物

     lll = 7;

     printf("sub  ggg:%d lll:%d\n",ggg,lll);

     ggg = 8;
     lll = 9;

     printf("sub  ggg:%d lll:%d\n",ggg,lll);

     // ここでlllは開放
}

■結果

$ gcc main.c sub.c
$ ./a.out
main ggg:1 lll:2
func ggg:1 lll:4
func ggg:5 lll:6
main ggg:5 lll:2
sub  ggg:5 lll:7
sub  ggg:8 lll:9
main ggg:8 lll:2


■動的メモリ
メモリを確保しないと

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

int main() {
    char   a[4];
    char   *b;

    strcpy(a,"123");//すでにaの領域は確保されているので使える

    strcpy(b,"123");//まだbの領域は無いので落ちる
    printf("[%s][%s]\n",a,b);
}

■結果

$ gcc main.c
$ ./a.out
Segmentation fault

メモリを確保(malloc)する。使い終わったら開放(free)してね。

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

int main() {
    char   a[4];
    char   *b;

    strcpy(a,"123");//すでにaの領域は確保されているので使える
    b = (char*)malloc(4);
    strcpy(b,"123");//まだbの領域は無いので落ちる
    printf("[%s][%s]\n",a,b);
    free(b);//開放は忘れずに。メモリリークです。
}

■結果

$ gcc main.c
$ ./a.out
[123][123]