Lecture 2 (2019/10/3)

本日やること

  • 変数の復習、配列、文字列、関数、制御文について学習します。
  • 下記の練習問題に挑戦してください。

VS Codeプラグインの紹介

C言語を使っていく上で便利なプラグインとその導入方法を紹介

VSCode C言語プラグインの導入

変数の復習

変数とは、数値や文字を格納しておく箱のようなもの。格納する内容に応じて、いくつかの「型」がある。

宣言:変数を使うときには、宣言が必要になる。変数を使う前には必ず宣言する必要がある。宣言を行うことにより、メモリの上に変数を格納する領域が確保される。

int a;

上記、intは型。ソフトウェアIでは整数型のintと、文字型のchar、実数型のdoubleを良く用いる。

そのほかの変数等:http://www9.plala.or.jp/sgwr-t/c/sec02.html#s2-2

宣言の書き方の例:

int a; int b;
int a, b;
int a; a = 1;
int a = 1; // 宣言と代入を同時に行うことを、変数を初期化するという
int b = 10, c = 100;

変数への代入操作は=を用いる。
a = 10;
b = 30;
c = a * b; // まずaかけるbを計算して、その計算結果の値をcに代入する。

変数の型

変数は,型が違えばサイズが異なる.すなわち表現できる数の範囲(幅)が違う.以下のプログラムを実行してみよう.

#include <stdio.h>
int main(void)
{
  char ichar = 1;
  short ishort = 1;
  int iint = 1;
  long ilong = 1;
  int i;
  for(i=1; i<65; i++){
    printf ("2^%2d = %5d %10d %11d %20ld\n", i, ichar*=2, ishort*=2, iint*=2, ilong*=2);
  }
  ichar = ishort = iint = ilong = -1;
  for(i=1; i<65; i++){
    printf ("-2^%2d = %5d %10d %11d %20ld\n", i, ichar*=2, ishort*=2, iint*=2, ilong*=2);
  }
  return 0;
}

型の変換(キャスト)

以下の(double)のように,型名を()でくくったものを値や変数の前に書くと,特定の型に変換することができる.

#include <stdio.h>
  int main(void)
  {
    printf("10/3         = %d\n", 10/3);
    printf("10.0/3.0     = %f\n", 10.0/3.0);
    printf("10/(float)3 = %f\n", 10/(double)3);
    return 0;
  }
  /*                                                                                                                                                  
10/3 = 3                                                                                                                                                   
10.0/3.0 = 3.333333                                                                                                                                       
10/(double) 3 = 3.333333                                                        
  */

配列

配列は、複数の同じ型の変数をまとめたもの。データを次々と読み出したりしたいときに用いる。

宣言は変数と同じであるが、配列の大きさ(要素の数)を指定する必要がある。この要素数分だけメモリ上に使える領域が確保される。

int a[5]; // aを配列の名前と呼ぶ。
a[0] = 1; a[1] = 2; // 代入操作を行うときには何番目の要素に代入するかを指定する。
int a[4] = {10, 20, 30, 40}; // 宣言と初期化を同時に行う場合
int a[] = {1, 2, 3, 4}; // {}を使って初期化する場合には宣言の際の要素数を省略できる。(見ればわかるので)

配列の要素にアクセスする際、要素のインデックス(番号)はゼロから始まる。つまりint a[3];で宣言した場合、アクセスできるのはa[0], a[1], a[2]の3つである。ここでa[3]にアクセス(値を読みだしたり、書き込んだり)すると「予期しない動作」を起こすのでダメ。

配列に必要なメモリ領域はプログラム実行時に確保され、プログラム終了時までそのまま保持される。このようなやり方を「静的な」メモリ領域の確保といい、要素数を変えることはできない。要素数があらかじめ見積もれない、あるいは動的に変わる可能性がある際はポインタを使う必要がある。その話はまた今度。

文字と文字列

1つの文字

コンピュータ内部では文字に「番号」がついている。英数字は0~127に対応した番号が国際標準として割り当てられている。(ASCIIコード)

文字型charは-128~127が入る型であるが、文字を格納するための型としてよく使われる。

#include <stdio.h>
int main(void)
{
char a = 'A'; // 「'」(シングルクォーテーション)で半角英数字を括るとその文字コードが得られる。
printf("%d\n", a); // 10進数整数でaを表示する
printf("%c\n", a); // 「%c」オプションを使うと数字に対応する文字を一文字表示する
return 0;
}

文字列

これに対し、文字列とは、文字の集まりである。基本的に文字列は文字の集まりで表される。

最も基本的な使い方は配列を用いて表現する方法である。

char a[4]; // 文字列+1文字の大きさの配列を用意する必要がある。
char a[] = "ABC";
char a[4] = "ABC"; //こちらでもよい。

文字一文字の時はシングルクォーテーションで囲ったが、文字列はダブルクォーテーションで囲む。

この場合配列aは3つではなく4つの要素からなり、先頭から順に'A', 'B', 'C'に対応する文字コードが配列内に格納される。そして最後の要素には'\0'で表現されるヌル(null)文字が入力される。どうしてそうなのかというと、それは、C言語で、文字列はヌル文字で終端であることを示すと取り決めをしたからである。

くどいようだが大事なことなのでもう一度言うC言語では文字列の終わりをヌル文字(\0)のあるなしで判断している。

+------+------+------+------+
| a[0] | a[1] | a[2] | a[3] |
+------+------+------+------+
| 'A'  | 'B'  | 'C'  | '\0' | 
+------+------+------+------+

文字列の表示方法はこちら。

printf("%s", a); // 文字列の表示は%sオプション

文字列の代入に=が使えるのは、初期化の時だけである。それ以外は以下のようにstrcpy()関数を使う。

char t[10];
strcpy(t, "Hello"); // ただし、使うためには #include <string.h> が必要

なので,文字列を格納する配列の要素数はかならず,文字数+1である必要がある.

○正しい例
char s[6] = "Hello";
char s[10]; 
printf("%s\n",s);
Hello
×間違い   
char s[5] = "Hello";

特殊な文字

\nのような\で始まる2文字のことをエスケープシーケンスと呼ぶ。代表的なものは以下である。

\0:ヌル文字 (NULL)
\b:バックスペース (BS)
\t:タブ (TAB)
\n:改行 (LF)
\r:復帰 (CR)

標準入力

単語数を数えるプログラムなどは,キーボード入力ではなく,ファイルの中の単語数を数えたいものです。sample.txt とい うテキストファイルをプログラムに食わせる(あたかも,このテキストファイルの中身をキーボード入力したかのようにプログラムに思わせる)手段は下記。

% ./a.out < sample.txt

"<" は「プログラムが"標準入力"からの入力を受け付ける場合,キーボード(これがデフォルトの"標準入力"である)ではなく,sample.txt を"標準入力"として扱って下さい」と"標準入力"の割り付け変更をして,./a.out を起動しています。

"標準入力"=標準の入力装置,"標準出力"=標準の出力装置,くらいに考えて下さい。

#include <stdio.h>
int main(void)
{
    int c;
    c = getchar();
    while (c != EOF)
    {
        putchar(c); // printf("%c",c);と意味は同じ
        c = getchar();
    }
    return 0;
}

ターミナルでインタラクティブに入力しながらEOFを入力するにはCtrl-D(コントロールを押しながらD)

同様にファイルに出力を書き出すこともできる。

% ./a.out > output.txt

考えてみよう&確かめてみよう

    1. 上記putchar(c)をputchar(c+1)にしたら何が起こるだろうか?
    2. printf("%d ", c);としたら何が起こるだろうか?

関数

関数とは,引数の情報を計算して戻り値を返すもの.複雑な処理を抽象化して再利用しやすくすることができる.

#include <stdio.h>
int addnum(int a, int b) 
//戻り値の型 関数名(引数の型 変数名. 引数の型 変数名, ...)
    int x;
    x = a + b; // 引数で指定した変数は関数内部で使える.
    return x; // return文で関数の出力値を指定
}
int main(void)
{
    int n;
    n = addnum(2, 3);
    printf("result = %d\n", n);
    return 0;
}

出力や入力を持たない場合は型にvoidと書く.

void hello(void)
{
    printf("Hello World\n");
}

変数のスコープ

  • グローバル変数
    • 関数の外で宣言した変数
    • 宣言以降に定義したすべての関数で,どこらからでも参照できる
  • ローカル変数
    • 関数の中で宣言した変数
    • 宣言した関数の中でのみ有効
    • 有効な範囲のことを「スコープ」と呼ぶ
#include <stdio.h>
 int y; //グローバル変数                                                       
 int z; //グローバル変数     
                                                  
 void myfunc(int a)
 {
   int z; // myfuncのローカル変数                                              
   int x; // myfuncのローカル変数                                              
   x = a; // ローカル                                                          
   y = a; // グローバル変数                                                    
   z = a; // グローバルではなくローカルが優先される                            
 }

 int main(void)
 {
   int x;  // mainのローカル変数                                               
   x = 10; // mainのローカル変数                                               
   y = 10; // グローバル変数                                                   
   z = 10; // グローバル変数                                                   
   printf("x,y,zの値は%d, %d, %d\n", x, y, z);
   myfunc(5);
   printf("x,y,zの値は%d, %d, %d\n", x, y, z);
   return 0;
 }

 /*                                                                            
x,y,zの値は10, 10, 10                                                          
x,y,zの値は10, 5, 10                                                           
 */

プロトタイプ宣言

強くおすすめする方法.

関数を定義してからmain関数を一番下に置くぶんにはこのプロトタイプ宣言は不要.しかし,これを逆にするとエラーになることがある.したがって,コンパイラに対して,「この関数はこういう型の引数を何個とって,この型の戻り値を返す」というのを教えておくことで,登場の順番に柔軟性を持たせることができる.(コンパイラが戻り値の型と,引数の数,型,並び順をチェックできるようになる.)

#include <stdio.h>
 int addnum( int, int );
 // プロトタイプ宣言戻り値の型と引数の型だけ宣言                               
 int main(void)
 {
   int n;
   n = addnum(2, 3);
   return 0;
 }
 int addnum(int a, int b)
   //戻り値の型 関数名(引数の型 変数名. 引数の型 変数名, ...)                  
 {
   int x;
   x = a + b; // 引数で指定した変数は関数内部で使える.                        
   return x; // return文で関数の出力値を指定                                   
 }

第1章のソースコードをコンパイル,実行してみる.

取り組む課題

まず,前述の第1章のソースコードを読んだ上で,自分なりに理解し,次の課題に取り組んでみること.

誰かに正解を見せてもらって全く問題ありませんが,もう一度自分でそのプログラムを手で組んでみてください.

news.txt の行番号を nl (nl=0,1,2,...) とし,単語番号を nw (nw=0,1,2,...) とする。int 型配列 numbers[] に対してnumbers [nw] = <nw番目の単語の長さ> x <nl+1>;

という形で値を代入し,配列長約 23,000 の配列を用意する。この配列データに対して,下記の課題を行いなさい。

注意:テキストではgetline関数を自分で作ろうとしているため定義がかぶってしまう。mygetlineなど、関数の名前を変えてから望んでください

課題1:int 型配列 numbers[] 及び,numbers[]の配列長 N を引数にとり,numbers[0]〜number[N-1]のうちの最大値を int 型で返す関数を作りなさい。

int get_max( int numbers[], int N )

ヒント1

for文などでnumbers[0]からnumbers[N-1]までのN個の要素を一つ一つ検査します.

i番目に読み込んだ値numbers[i]がこれまでに見た最大値maxよりも大きければ,numbers[i]をmaxに代入します.最終的な値maxが最大値なので,これをreturnします.

maxという変数は宣言した後初期化が必要です.どうしても動かない場合は初期化を疑いましょう.

課題2:int 型配列 numbers[] 及び、配列長 N を引数にとり,その平均値を double 型で返す関数を作りなさい。

double get_average( int numbers[], int N )

ヒント

全部足して最後にNで割れば良いです.

課題3:int 型配列 numbers[] 及び,配列長 N を引数にとり,その分散を double 型で返す関数を作りなさい。

double get_variance( int numbers[], int N )

ちなみに,max, average, variance の値は,max = 14850, avg = 2593.726, var = 4496978.976

ヒント2 穴開きコード

#include <stdio.h>

#define IN        1
#define OUT       0
#define MAXLEN    25
#define MAXNWORD  23000

int  get_max( int array[], int N )
{
}

double  get_average( int array[], int N )
{
}

double  get_variance( int array[], int N )
{
}


int main(void)
{
  int c, nl, nw, nc, state;
  int i, j;
  int word_initial, word_final, len;
  int word_length[MAXLEN];
  int numbers[MAXNWORD];

  for( i = 0 ; i < MAXLEN ; ++i ) {
    word_length[i] = 0;
  }
  for( i = 0 ; i < MAXNWORD ; ++i ) {
    numbers[i] = 0;
  }

  state = OUT;
  nl = nw = nc = 0;
  while( ( c = getchar() ) != EOF ) {
    ++nc;

    if( c == ' ' || c == '\n' || c == '\t' ) {
      if( state == IN ) {
        word_final = nc;
        len = word_final - word_initial;
        ++word_length[len];

        numbers[nw] = (nl+1)*len; // 深い意味はない。そう定義しただけ。
        // printf( "nw = %d, nl = %d, len = %d, numbers = %d\n", nw, nl, len, numbers[nw] );
        ++nw;
      }
      state = OUT;
    }
    else if( state == OUT ) {
      state = IN;
      word_initial = nc;
    }

    if( c == '\n' ) {
      ++nl;
    }
  }

  printf( "#line = %d #word = %d #character = %d\n", nl, nw, nc );
  printf( "max = %d, avg = %.3f, var = %.3f\n", get_max( numbers, nw ),
   get_average( numbers, nw ), get_variance( numbers, nw ) );

  return 0;
}


課題4:文字列strをチェックして、単語のアルファベットや数字が回文(ぽい並び)かどうか判定するプログラムを作成せよ。

madam, kayak, civic, 1001, 11011など. 単語だけでなく文章に対応してもらってもかまわない.

ヒント必ずしもこれを使わなくても良い.ファイルなどから読み込む形でもよい。

#include <stdio.h>
#include <string.h> //strlen()に使う.                                         
int check_kaibun(char str[]);
int main(void)
{
  char str[] = "kayak";
  if(check_kaibun(str)){
    printf("%s is palindrome.\n", str);
  }else{
    printf("%s is NOT palindrome.\n", str);
  }
  return 0;
}
int check_kaibun(char str[]){
  // 回文かチェックを行い,回文だったら1を返す.そうでなければ0を返すように処理                                                                             
  // ヒント:str[]の長さは strlen(str) でわかる.                              
  // 文字列の最初の文字とヌル文字を除く最後の文字が同じかどうか,
  // 真ん中の文字になるまで比較すればよい.                                                     
  return 結果;
}

復習と補足

論理演算子には優先順位があります。

#include <stdio.h>
int main(void)
{
  int  x;
  printf( "3       --> %d\n", 3 );
  printf( "x = 2+5 --> %d\n", x = 2+5 );
  printf( "x       --> %d\n", x );
  printf( "x == 5  --> %d\n", x == 5 );
  printf( "x == 7  --> %d\n", x == 7 );
  printf( "x != 7  --> %d\n", x != 7 );
  if( 1 ) {
    printf( "if(1)\n" );
  }
  if( 0 ) {
    printf( "if(0)\n" );
  }
  if( 5 ) {
    printf( "if(5)\n" );
  }
  if( 0 || 5 ) {
    printf( "if(0 || 5)\n" );
  }
  if( 0 && 5 ) {
    printf( "if(0 && 5)\n" );
  }
  printf( "0 || 5  --> %d\n", 0 || 5 );
  printf( "0 && 5  --> %d\n", 0 && 5 );
  if( x == 7 || 0 ) {
    printf( "if( x == 7 || 0 )\n" );
  }
  if( x == 0 || 3 ) {
    printf( "if( x == 0 || 3 )\n" );
  }
  if( x == (0 || 3) ) {
    printf( "if( x == (0 || 3) )\n" );
  }
  if( x == (7 || 0) ) {
    printf( "if( x == (7 || 0) )\n" );
  }
  if( x == 0 || x == 3 ) {
    printf( "if( x == 0 || x == 3 )\n" );
  }
  return 0;
}