PythonでOpenCVを使えるようにする
方法
まずは例にならって環境を最新にする
sudo apt-get update sudo apt-get upgrade
これで必要なパッケージをインストールするだけ
sudo apt-get install libopencv-dev sudo apt-get install python-opencv
numpyが必要なので入っていない場合はそれもインストール
それでちょっとテスト
#codeing:utf-8 import numpy as np import cv2 img = cv2.imread("ここにファイル名"); cv2.imshow("作成するウィンドウ名(なんでもいい)",img) cv2.waitKey()
ついでにヒストグラムの時に便利なライブラリもまとめていれておく
sudo apt-get install python python-dev sudo apt-get install python-numpy python-scipy python-matplotlib
ここでさらにヒストグラムを出してみる
#coding:utf-8 import cv2 import pylab as plt #グラフ可視化 im = cv2.imread("ファイル名") hist = cv2.calcHist([im],[0],None,[256],[0,256]) plt.plot(hist) plt.xlim([0,256]) plt.show()
C言語 関数とプログラム構造
4章 関数とプログラム構造
C言語で書かれたプログラムは大抵、大きな計算処理を小さな多くの関数に分割して作成される
その関数群は一つのソースファイルであるか、全体を通して言えば複数のソースファイルに分割されている
そしていくつかの大きな関数からプログラムができることはまずない
なぜこのような構造をとるかと言われると、それにはいくつかの理由がある
知る必要でない部分以外の操作に関する情報を隠す為
次にコードを細分化し変更を容易にするため
などといったものである
ここではそういった関数に関することやそれらからなるプログラムの構造について話していく
4.1 関数の基本事項
関数の基本事項を説明するために、有名なfizzbuzz問題を汚いコードで無駄に関数に分割して考える
今回そのコードは以下のようにfizzbuzzをとく
範囲は1~50までで、その範囲の数を全て格納した配列を用意する
用意した配列を先頭から値を取り出し
fizzbuzzを倍数によって考えそれぞれの結果でフラグとして値を返す
そのフラグをswitchで条件分岐にかけ結果を出力する
#include<stdio.h> #define MAX 50 #define START 1 #define END 50 #define FIZZBUZZ 1 #define FIZZ 2 #define BUZZ 3 void makeArray(int array[]); int calcFizzBuzz(int num); int main(){ int array[MAX]; int i; makeArray(array); i = 0; while(i < END){ switch(calcFizzBuzz(array[i++])){ case FIZZBUZZ: printf("FizzBuzz\n"); break; case FIZZ: printf("Fizz\n"); break; case BUZZ: printf("Buzz\n"); break; default: printf("%d\n",i); } } return 0; } void makeArray(int array[]){ int i; for(i = START; i <= END; i++){ array[i-1] = i; } } int calcFizzBuzz(int num){ if(num%15 == 0){ return FIZZBUZZ; }else if(num%5 == 0){ return BUZZ; }else if(num%3 == 0){ return FIZZ; } return -1; }
今回は無駄にフラグ関係にdefineを使ったりしてみた
そして、例としてあげたプログラムからわかるように関数は
戻り値の型 関数名(引数){ 処理 }
という形をとり、戻り値の型がvoidか存在しない時returnで値を返す必要があり
return 式;
という形をとり、これ以降に処理や計算を記述した場合、その処理は呼ばれることがないため
無限ループや処理の途中で抜けるようなbreakに近い動作をする
また戻り値が必要ない場合、関数内の処理が全て終わると呼出元にその制御を渡す
ある関数で値をあるところでは返し、またあるところでは返さないという様な構造を取る関数はまず作成するべきではなく
そのような関数はさまざまなエラーやバグの原因となるのでゴミになってしまう
4.2 非整数を返す関数
いままでは何も値を返さない、もしくは整数を返す関数を扱ってきた
それでは整数ではない値を返す関数はどうすべきだろうか
今回はひとつの例としてdoubleを返す関数を扱う
そして今回も4.1に続き説明の都合上、無意味に整数で高さと幅を受け取りその面積をdouble型で返す
dirty_s関数というゴミ同然の関数を作り説明する
#include<stdio.h> double dirty_s(int w,int h); int main(){ int w,h; int i; double sum; double dirty_s(int,int); w = h = 1; sum = 0; for(i = 0; i < 10; i++){ sum += dirty_s(w,h); } printf("%f\n",sum); return 0; } double dirty_s(int w,int h){ return (double)w*h; }
今回は今までにない書き方をしている部分がある
それは10行目の
double dirty_s(int,int);
である
これには非整数を返す場合特に必要はないがつけるのが望ましい
というのも、まだこれは短いプログラムでありソースファイルもひとつしかないが
大きなプログラムになると、外部から独自の関数を呼び出した時それが何型を返すのかわからないとすごく面倒なので
型 関数名(型,....);
のように宣言できる
配列の時は
型 関数名(型 [],....);
などとなる
これはコードの可読性を上げるという面でも覚えておくといい
4.3 外部変数
C言語で書かれたプログラムというのは主に変数と関数、それといくつかの外部オブジェクトによって形成されている
ここで紹介する外部変数というものはすべて関数の外で宣言され多数の関数で使用できる
またC言語では関数内に関数を定義することができないので関数はすべて外部的と言える
さらに、外部変数や関数は同じ名前で参照すれば別個コンパイルされた関数でもすべて同じものの参照となる外部リンクという性質をもっている
外部変数は広域的にアクセスすることができるから、引数や戻り値の代用として扱えたりもする
そして外部変数はその名前でアクセスする限り(基本的に)どの関数からでも参照できる
そうした性質を持つため、長い引数リストを使う関数なんかに外部変数を使用するのがよかったりする
便利そうに見えるこの外部変数というもの、しかしこれらの使用には注意しないといけなくて
プログラム構造が悪くなったり、関数間で多くのデータが結合しているプログラム(変更しにくい)になってしまう
ただ一般的に関数の内部で定義する変数はルーチンに入った時に生成されルーチンが終わると消滅するのに対して
外部変数は永久的であり、ひとつの関数の呼び出しで得た値を次の関数呼び出しても保持しているため
2つの関数間でデータを共有する必要があり、互いにもう一方の関数を呼び出すことをしないなら外部変数を使うべきと言える
これらを踏まえてすごく効率の悪いソートアルゴリズムを組んでみる
今回は少しわかりやすいようにスタックの構造をとってソートする
入力は以下のように仮定する(実際には改行で区切って入力される)
6 1 9 8 2 7 4 5 3 10
#include<stdio.h> #include<string.h> #define MAX 10 #define NEXIST -1//すでに探索したもの void init();//初期化処理 void push(int); int pop(void); int getMAX(int []); int res[MAX];//結果を格納する int base[MAX];//入力から与えられた数列を格納する int stack_top = 0; int main(void){ int i; int num,max; init(); for(i = 0; i < MAX; i++){ scanf("%d",&num); base[i] = num; } while(stack_top < MAX){ max = getMAX(base); push(max); } //結果出力 printf("---sort result---\n"); for(i = 0; i < MAX; i++){ printf("%d\n",pop()); } return 0; } void push(int number){ res[stack_top++] = number; } int pop(){ int num = res[--stack_top]; return num; } int getMAX(int array[]){ int max = 0; int position; int i; for(i = 0; i < MAX; i++){ if(max < array[i]){ max = array[i]; position = i; } } array[position] = NEXIST; return max; } void init(void){ memset(res,0,MAX); memset(base,0,MAX); }
今回外部変数との関係で注目すべきは15行目の
int stack_top = 0;
という外部変数と、それを使用している
void push(int);
と
int pop(void);
という関数
これらはstack_topという外部変数を使いつつ、互いに呼び出すことはないので
先ほど紹介した外部変数を使うに適した構造といえる
また外部変数は2つの関数で変化させているが49~52行のpop関数で正しく表示できるように
他の関数での変更が外部変数に適応されているため、データを共有できている
4.4 通用範囲に関する規則
C言語で書かれたプログラムは関数と外部変数が主体であり、すべてを同時にコンパイルする必要はない
プログラムのソースファイルを数個に分けて保存し、以前コンパイルしたライブラリからロードしてもいい
こうしたことを踏まえてここで話すのは通用範囲、つまりスコープに関することである
変数や関数の通用範囲はその名前を使うことのできるプログラムの部分である
つまり自動変数は他の関数で参照できないというのはこれに関係している
特に自動変数に商店を当てるなら、上の理由から別の関数に同じ名前の局所変数があったとしても関係はない
イメージでいうなら、関数はひとつの処理、データの集まりで関数の内部は関数として独立している
それに比べて関数、外部変数における通用範囲はコンパイルされるべきファイルで宣言された点からファイルの終わりまで続く
つまり同じファイルであれば単に名前を参照するだけで呼び出すことができる
しかし、外部変数が定義される前に参照されたり異なるソースファイルで定義されているとextern宣言が必要になる
4.5 ヘッダーファイル
実際にプログラムを組んでいくと、どのような構成にするかは人それぞれであるが
関数ごとにソースファイルに分割したりして可読性を確保する場合がある
そうしたことをしていくうちに、構成で迷うのが複数のソースファイルで共通する処理をどうするかである
処理と書いたが実際には宣言と定義である
これだけはなるべく集中化しておくのがいいだろう
なぜなら共有部分をまとめて宣言定義しておくことで、プログラムを書き換える場合に比較的少ない箇所を訂正するだけで済むから
そこで共通のデータや処理をヘッダーファイル(.h)にまとめておく
4.4のことも含めていうならヘッダーファイルに宣言、定義類を固めておいて
なおかつ、extern宣言しておくことでinclude先で使いやすくなる
4.6 静的変数
ここまで、外部変数や関数の通用範囲やそれらを超える他のファイルなどの外部変数、関数の通用範囲に応じた定義宣言の話をしてきたが
特に外部変数はそれより外の通用範囲に向けて使う使い方と、そのソースファイル内でしか使用しないものがある
宣言したソースファイルないでのみ使用する外部変数はその場所に使用を限定する場合staticをつけることで、オブジェクトの通用範囲をコンパイルされつつ
そのソースファイルの残りの部分にのみ適応できる
これは主に(というかほとんど)変数に使用されるが、関数に適応することも可能で関数に使用した場合その関数は外部から呼び出せなくなる
更にはstaticは内部変数に適応できて、これもその関数内に限定されるが他のものとは違い
関数が呼び出されるたびに生成されるのではなく、一度生成されると存在し続ける
つまり単一の関数においてその内輪で永久的なメモリが与えられる
4.7 レジスタ変数
register宣言をすることはコンパイラにその変数が頻繁に使われることを知らせるのによく使われる
その目的はregister変数をマシンのレジスタに置くことでプログラムを小さく速くできるからで、この指示はコンパイラが無視する場合がある
これらの宣言は自動変数と関数の仮引数に使える
しかしこれは実際のところハードウェアに左右され、また実際にレジスタに置かれるかどうかにかかわらずレジスタ変数のアドレスは求めることができない
4.8 ブロック構造
C言語では関数の中に関数を作るような構造にはできないが
変数においてはブロック構造のような構造をとることができる
具体的には以下のように宣言、定義できる
if(0 < number){ int i; for(i = 0; i < number; i++){ } }
この場合ではnumberが正であるときの分岐でブロック外にiがあったとしてもそれとは無関係になる
またブロック内で定義された自動変数はブロックに入るたびに初期化されるが
static変数であった場合はブロックに最初に入った時に初期化される
仮引数も含めて自動変数は同じ名前の外部変数や関数を隠す働きがあるため
なので以下の場合
int x,y; void function(double x){ double y; }
関数内でxを参照するとそれは仮引数のdouble型xであり
yについても関数内であれば同様のことがいえる
またスタイルの問題として、外側の通用範囲にある変数名を隠すような書き方は避けるべきで
そうでないと混乱やエラーの原因となってしまう
4.9 初期化
初期化については、これまでのコードの中で度々あったがここではもう少し初期化の規則についてまとめる
まず明示的な宣言がない場合は外部変数と静的変数は0に初期化されることが保証される
しかし自動変数とレジスタ変数は値が不定となるためメモリを無駄遣いしていると言える
スカラー変数においては名前のあとに等号と式を置くことで初期化できる
int x = 1;
外部変数と静的変数においては初期化は定数式でなければいけない
初期化は概念的にプログラムの実行時に一度だけ行われる
自動変数とレジスタ変数は初期化が関数やブロックに入るたびに行われる
また自動変数とレジスタ変数においては初期化が定数式とは限らない
前もって決まっていて正しいもの(関数の引数など)であればそれを含んだ任意の式でも構わない
事実として特に自動変数の初期化式は代入文の省略形でありどちらを好むかは趣味の問題である
一方配列では初期化式のリストをかっこで囲み、カンマで区切ったものをつけることによって初期化する
int p_num[] = {2,3,5,7,11,13};
配列のサイズが省略された場合カッコの中の要素数がその配列のサイズとなる
また指定したサイズより配列にたいする初期化式が少ないなら残り要素は0になる
ただしサイズを超えたものはエラーとして処理される
文字列の初期化についてはダブルクオーテーションで囲まれたものになるが
これは実は特殊で以下のようになっている
char hello[] = "hello"; char hello[] = {'h','e','l','l','o'};
4.10 再帰
C言語の関数は再帰が使える
再帰とは関数の処理がその関数自身を呼び出すことである
簡単な例としては、ユークリッドの互除法を再帰を使って実装できる
#include<stdio.h> int gcd(int,int); int main(){ int a = 5; int b = 25; printf("%d\n",gcd(a,b)); return 0; } int gcd(int a,int b){ if(b == 0){ return a; }else{ return gcd(b,a%b); } }
このようにreturnにつけて行うこともできるし、それがなくても再帰は行える
ただし再帰を使う場合、処理中の値のスタックを保持しないといけないためメモリの節約にならないこともある
さらに処理速度の向上もあまり見込めない
しかし再帰を使わないプログラムに比べてずっと書きやすく理解しやすくなることが多い
再帰は特にツリー構造のようなデータ構造に対して有効である
4.11 Cのプリプロセッサ
Cではプリプロセッサによりある程度の言語仕様が与えられる
これがよく使われるのはコンパイル中にファイルをロードするincludeとトークンを任意の文字列で置換するdefineだろう
4.11.1 ファイルの取り込み
ファイルの取り込みは#defineや宣言の集まりを取り扱うのを簡単にする
#include"ファイル名"
あるいは
#include<ファイル名>
はそのファイルの内容に置き換えられる
ファイル名にダブルクオーテーションがついていた場合呼び出しもとのファイルがある場所からそのファイルを探索する<>で囲まれいる場合は標準ライブラリか、後にインストールされたライブラリを参照する
またincludeされるファイルにincludeが存在してもいい
ソースファイルの先頭は共通のdefine文やextern宣言を取り込むため
またはstdio.hのようなヘッダに出ているライブラリ関数のプロトタイプ宣言にアクセスするためでもある
#include宣言は大きなプログラムで宣言を結びつけるのによく使われ、悪い書き方をしているものを防ぐ役目もある
ただ取り込むべきファイルの内容を変更した時はそれを使うすべてのファイルをコンパイルし直す必要がある
4.11.2 マクロの置換
ここで話すことはつまりはdefineである
#define name 置換テキスト
の形で宣言されるマクロ置換はnameを置換テキストで置き換えることを要求する
#define中の名前は変数名と同じ形を持ち、置換テキストは任意である。
通常、置き換えられるテキストは行の後に続き長い場合行末に/をつけることで次の行に続けることが可能
defineを用いて定義された名前の通用範囲はそのソースファイルの最後までである
また宣言中に以前の定義名を使用することもできるが、ダブルクォーテーションなど引用符に囲まれた中や定義名が含まれるものには置換が発生しない
少し前に記述したように置換テキストは任意であるため以下のような書き方ができる
#define forever for(;;)
これでforeverという名前の新しい無限ループが宣言される
また引数をつけた宣言も可能で
#define max(A,B) ((A) > (B) ? (A) : (B))
のように定義すると
int a,b,c; a = 2; b = 1; c = max(a,b);//c == 2
となる
ここでの仮引数は実引数と置換される
引数を首尾一貫して使う場合にはどんなデータ型でもよく
データ型が違ったとしても関数とは違い新たなmaxを宣言する必要はない
そして少し注意が必要な点がある
マクロ置換は置換テキストが展開されるためインクリメント演算子などを含む式が引数として渡されるの良くないことになる
defineだけでなく名前を定義するのであれば#undefも使用できる
undefはこれはそのルーチンがマクロではなく関数であることを保証するために使用する
#undef getchar int getchar(void){ }
引用符月の文字列の中では仮引数は置換されない
しかし置換テキストの中でパラメータ名の前に#がついているならその組み合わせはパラメータで実引数で置き換える形で引用符付き文字列のなかで展開される
これはデバッグ用のプリントマクロを次のように文字列の連結と組み合わせるのにつかえる
#define dprintf(expr) printf(#expr " = %g\n",expr)
一方プリプロセッサ用の演算子##はマクロ展開の最中に実引数を連結する一つの方法であり
以下のように使える
#define paste(front,back) front ## back
こうすることでpaste(hello,world)ならば
helloworldという文字記号を作れる
4.11.3 条件付き取り込み
defineでも条件分岐ができて
例えばhello.hというヘッダーを確実に一度だけ取り込むなら以下のように書ける
#if !defined(HELLO) #define HELLO /*hello.hの内容*/ #endif
本来はまずif行で取り込まれてそこから#endifまたは#elif、#elseまで取り込まれる
またif文中のdefinedはかっこないの名前が定義されているなら1それでなければ0になるというもの
またifが関係している文はこれ以外にもあり
名前が定義されているかというテストをする特殊形式の#ifndefと#ifdefがあり
これらを使うことでC言語にないtrue or falseを擬似的に実装できる
#ifndef Boolean #define Boolean int #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif
またさっきのdefinedのコードを書き直すこともできて
#ifndef HELLO #define HELLO //ここにhello.hの内容 #endif
C言語 制御の流れ
3章 制御の流れ
ここでは今まで普通に使ってきた制御に関することをもっと掘り下げていく
3.1 ブロックと文
これは凄く簡単なことで、C言語では式に;(セミコロン)を置くことで文になる
なので宣言や代入などそれら単体で文として独立する
また中カッコ{}は、それらで囲まれる部分を一つのブロックとして扱い、単一の文と同等の意味を持たせる
関数で関数がもつ処理を{}で囲んだりif~elseやfor,whileなどでブロックを使うことがその典型例である
3.2 if~else
判定を行うときはif~else文を用いる
else文は省略可能であり、正式にはこう書く
if (条件){
処理1
}else{
処理2
}
{}を省略することもできるが、それは時に曖昧さをもつ
以下の例をあげると
if(0 <= number) if(b < a) max = a; else max = b;
これは人間には解釈できるがコンパイラにとっては内側のifと同列にelseがあるように解釈される
特に以下の様な時に有害である
int array[10]; int i; memset(array,-1,sizeof(array)); array[0] = 1; if(0 <= array[0]) for(i = 1; i < sizeof(array); i++){ if(0 <= array[i]){ printf("...\n"); } } else printf("error");
これらの予期せぬ動作を防ぐ為には{}を用いてブロックを明示的に区別するのが得策といえる
3.3 else if
3.1ではif elseの様に
条件一つに対して、真か偽かとその判定に対する処理の多くて二つしか実装できなかったが
else ifを使うことで更に多くの条件判定を行える
if(条件1){
}else if(条件2){
}else{
}
上記に示す様にifでは条件1,else ifでは条件2、elseではそれ以外のときとなる
これは多分岐の条件判定を実装する一般的な方法で、最後のelseは省略できる
またこれら同列に並ぶif,else if,elseは上から順番に判定されどれかに当てはまった段階でその後の判定を全てスキップする
補足ではあるがif文に於て、elseはそれまで続く全ての条件に当てはまらない場合に実行されるのでエラー検出に用いることもできる
3.4 switch
これはif,else if,elseを用いた多分岐条件判断でよく使われる
以下の様な構文で用いられる
switch(式){
case 定数式:
文
default:
文
}
if文と同様にdefaultは省略可能で、caseは幾つでも定義できる
だが少し注意が必要である
#include<stdio.h> int main(){ int i; for(i = 1; i <= 10; i++){ switch(i){ case 1: case 2: case 3: case 4: case 5: printf("i <= 5\n"); break; default: printf("over 5\n"); } } return 0; }
上記のプログラムではcaseの後の文でbreakが宣言されていないため
1~5までの条件判断で処理を一つにまとめている
つまりbreak文が無いことで、次のcase文に移っていく
これはifで書くと
if(i <= 5)
もしくは
if(i == 1 || i == 2 || i == 3 || i == 4 || i == 5)
と解釈できる(switchと同じ意味合いであるなら後者)
3.5 ループ(forとwhile)
これまで説明こそしなかったものの、ループは問題なく使ってきた
まずはwhile文から
whileは以下の様な構造をとる
while(式){
文
}
while文ではまず式が評価され式が0とならないなら文に移り、式を評価して0になるまで繰り返される
次にfor文
forは以下のような構造をとる
for(式1;式2;式3){
文1
}
そして以下の様に書けば、whileとforは同じ意味合いをもつ
for(i = 0; i < 100; i++){}
これをwhileで書くと
i = 0; while(i < 100){ i++; }
これで同じ意味合いになる
またforにおいて、三つの要素は文法的に式であり、一般的には式1と式3が代入又は関数呼出で式2が関係式となる
この3つの要素はどれを省略してもいいが、セミコロンを省略することはできない。
式1、式3は省略された場合単に展開されないだけだが、式2の関係式が省略されると、それは永久に真であることを示す
つまり、以下の様にfor文を書くと無限ループになる
for(;;){
}
無限ループはwhileにもありwhileでは以下の様に書く
while(1){ }
また無限ループから脱出する際にはbreakかreturnを使うことになるだろう
ここのループの話ではreverse関数という文字列sを逆順にする関数を考えながらコンマ演算子について触れておく
コンマ演算子といえば、変数宣言や初期化やその他諸々の様々な場合で使用してきたものとは違い
for文に適応できて以下の様に書ける
void reverse(char s[]){ int c,i,j; for(i = 0, j = strlen(s)-1; i < j; i++,j--){ c = s[i]; s[i] = s[j]; s[j] = c; } }
ただこのコンマ演算子はあまり乱用するべきではない
使用してもいいのは、reverse関数のようにi,jが強く関連しているときなどである
3.6 ループ(do~while)
次に三番目のループ構文としてdo~while文を紹介する
これは基本的にはwhile文の様に式を評価してループするしくみなのだが、式に真か偽か関係なくdoのブロック内に含まれる処理を必ず一回は実行する
以下のような構文である
do{
文
}while(式);
もし、一回目の実行の後に式を評価し真であるならまた文を実行する
経験上for,while文ほど多用はしないが、たまに使いどころがくるので知っておくべき
3.7 breakとcontinue
breakとcontinueは主にループ内で多用し、breakにおいてはswitch文でも使用する
これらはループから早く抜ける為にあり、breakを使えばそれが含まれる最も内側のループから抜けることができるし
continueを使えば適切なタイミングでループをスキップし、次のループに移れる
3.8 gotoとラベル
C言語にはgotoという、ネストになったループから一発で抜けてしまうようなものがある
何度もifとbreakを書かなくていいという簡潔なメリットがあるが形式的にはgotoは不可欠なものでない
実際にそれらを使用しなくてもプログラムは書けるし、むしろgotoを使用するような構造のプログラムを書いてしまうことの方がまずいことである
gotoでは指定したラベルまで一気に飛べるため、以下のように使う
for(i = 0; i < n; i++){ for(j = 0; j < m; j++){ if(a[i] == b[j]){ goto found; } } } found://ラベル
また一度に多くの処理を飛ばしてしまうことがありgotoを使ったプログラムはgotoを使わないプログラムに比べて保守しにくく
バグの原因となるので、あまり使うべきではない
C言語 データ型、式、演算子
2章 データ型、式、演算子
前回の1章でざっくりとした入門を終えたので、今度はひとつひとつ掘り下げていきます。
はじめは、大抵どの入門書でもはじめに扱うデータ型、式、演算子というものです。
まず宣言と演算子を交えて…
宣言は変数を並べて、型を定義し、時には初期値を決める為に
演算子は変数に対して何を行うか、さらには式で変数や定数を結合し新しい値をもたせるのに使われる。
一方でそれらのオブジェクトが持つことが出来る値の集合と、どのような演算ができるかという事を示すのが型である
今回使用している、プログラミング言語CのANSI規格では基本的な型と式に変更と追加が加えられ
型の中でも、特にすべての整数型にはsigned、unsignedという型があり符号なし定数や16進文字定数の記法ができた
浮動小数点型では、単精度での演算も可能で拡張精度のためのlong double型も存在する。
文字配列、つまり文字列の中でも特に定数はコンパイル時に連結が可能となった
オブジェクトではconstを付加することで変更不可に出来たり、算術間の自動変換規則ではより豊富なデータ型を扱える様に強化されている。
2.1 変数名
これは誰もが知っていることかもしれないが一応解説。
まず変数名と記号定数名にはいくつかの規則がある。
それは名前の1文字目は英字でないといけないということで、下線は英字に含まれる
そして、伝統的に変数名は小文字で
定数名、特に記号定数は大文字で記述することになっている(そうしなくても良い)
それ以外に、内部変数名の長さを31文字以内にすることや、言語で定義されているキーワードを使用しない
といったものがある
なので、変数名はその変数自体が持つ意味合いやそれに関係する名前にするのがいいと言われている
特に局所変数、内部変数には短い名前を、外部変数にはそれなりに長い名前をつけるのがいいとされている
2.2 データ型とサイズ
C言語には次のような数の基本的なデータ型があるだけ
データ型 | 意味合い |
---|---|
char | 1バイト、ローカルな文字セット内に1文字保持する |
int | 整数、通常ホスト計算機の自然な整数サイズ |
float | 単精度浮動小数点数 |
double | 倍精度浮動小数点数 |
これらの基本型には修飾子をつけることが可能で整数にはshortとlongというものがあり次の様に適応できる
short int c1; long int c2;
こうした宣言ではintを省略できる。というか実際には省略されることがおおい
この意図は可能な限りshortとlongによって異なる長さの整数を扱える様にするべきというところにある
intは通常、特定の計算機にとって自然な大きさが取られていてshortは16bit、longは32bitであることが一般的である
具体的にいうとintは16bitか32bitのどちらかである。その点がハードによって違う
それらを考慮してshortはintより大きくてはいけない
またintはlongより長くてはいけないということがある
整数にはlong,shortがあるがcharにはsigned,unsignedがある
charが8bitの長さだとすると
signedは符号ありを示すもので-128から127の間の範囲である
unsignedは符号なし、つまり値が必ず0か正の数で0~255までの範囲である。これは印字可能な文字と関係があり
具体的には印字可能な文字を数値で表すと必ず正の整数なのでその点に関係する
一方、浮動小数点数にはlong doubleという拡張精度の浮動小数点数を表すものがある。
整数と同様にfloat,double,long doubleの長さは同じであることもあれば異なることもある
2.3 定数
1234のような整数定数はintとして扱われる。123456789Lのように末尾にLまたは小文字のlをつけるとそれはlongになる
それに加え大きすぎてintでは格納出来ない数字もlongとして扱われるがそのような使い方はおすすめしない
一方、符号なしのunsignedの場合定数には末尾にU,uをつけるため
unsigned long型だった場合、UL,ulを末尾につけるなどある
浮動小数点数は小数点(123.4)や指数部(1e-2)あるいはその両方を含むものであり
それらに接尾子が無い限りdoubleとして扱われf,Fを末尾につけるとfloatになる
さらにl,Lをつけた場合はlong doubleとして扱われる
整数では基本的に10進数を扱うことが多いが、8進数や16進数を標準で扱える
8進数の場合、先頭の数字が0
16進数の場合は先頭が0xもしくは0Xがつく
例として10進数で31を表現するなら
8進数で037
16進数で0x1fか0X1fと表現する
この時unsignedやlongを型にしているならulやULなどの接尾子を付加できる
次に文字定数について、文字定数は宣言するときにシングルクォーテーションで対象を囲む
例えば、Aという文字を文字定数として扱いたいなら'A'とする。
そして文字定数、文字とは数値であるため'A'を格納する定数は実質的に65という数値を格納している。
なのでC言語には数値型しかないと言われる。
またこの性質を使うと上の例で65を定義するときに'A'と書くとそれだけ特定の値と独立しコードが見やすくなる
ただむやみに、その性質を乱用するべきではない
そして然るべき時に使うことで、文字とは数値と同様の意味合いを持つ
この性質が一番よく使われるときは文字と文字の比較の時で、文字または文字列においてある種の文字は\nの様にエスケープ系列で表現される
これらは2文字のように見えるがそれが表すのは一文字である
文字を定数として定義するときはシングルクォートで囲んだが文字列を定数として定義するときはダブルクォーテーションで囲む
'A'//文字 "A"//文字列
ダブルクォーテーションで囲まれたものは文字列として扱われるため、例え1文字であっても文字列として扱われる
またこれら定数は数値と同様にdefineを用いて定数化することも出来る
定数式はコンパイル時に評価されるため以下の様に使用できる
#define MAX 100; char str[MAX+1];
ここまでは簡単、でもこれまで紹介したそれらより少しめんどくさい文字列
文字列は技術的に言うと、各要素が1文字の配列である
また文字列の最後にはヌル文字('\0')という文字列の終わりを示す文字が内部表現として追加されるので物理的な容量としてダブルクォーテーションで囲まれたものより1多い
これは文字列の長さに制限がないことを示すが、プログラム上でその長さを求めるためには最後までスキャンする必要がある
その例として、文字列の長さを返すstrlen関数を例に上げる
strlen関数はこのようになっている
int strlen(char s[]){ int i; while(s[i] != '\0'){ //null文字でないなら ++i; } return i; }
そもそもstrlen関数とはstring.hという標準ヘッダで制限されている関数である
そしてこの関数は内部表現として追加されたnull文字を除く文字列の長さを返している
この他に列挙定数というものがあり、enumを用いて以下のように宣言する
enum boolean{ YES,NO }
この場合、YES,NOの値を明示的に宣言していないので
YES = 0, NO = 1という値が暗黙的に宣言されている
それを利用して以下の様に書くことも出来る
enum months{ JAN = 1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC }
この場合先頭の定数が1という値を保持しているのでFEB = 2,MAR = 3というようになる
また列挙では名前が異なっている必要があるが、値は異なる必要は無い
列挙は名前と定数値を結びつけるのに便利で、defineの代用として使用もできるし
上のコードの様に、値が自動生成されるという点でも利点がある。
2.4 宣言
すべての変数は宣言を必要とする。
宣言は同じ型をまとめて1行で宣言したり、すべてバラバラに宣言したりできる
int i,max,min; char str[MAX]; int i; int max; int min; chsr str[MAX]; //上下で同じ意味
前者はコードを簡潔に出来る
後者はコメントなどが挿入しやすく、変更も容易である
また宣言と初期化を同時に行うことも可能
ただ初期は通例として一回行われるとき、式に定数を含むのが望ましい
さらに変数にconstという修飾子をつけることで変更不可な変数を生成することが出来る
const int i = 10; i = 11;//これはできない
変更不可というよりは初期化後に再代入できないため、関数の引数にconstをつけることもでき配列にも適応出来る
2.5 算術演算子
- ,-,*,/の二項演算子
%のモジュロ演算子がある
これらには実行優先度というものがあり+,-は算術演算子の中では同じ優先度であるがその他の算術演算子より優先度が低い
ざっくり言えば数学の四則演算と同じ優先度になっている
少し注意が必要なのが/,%の2つ
まずモジュロ演算子の%は浮動小数点に適応できない
さらに/は小数が切り捨てされるがそれらの方向は機種に依存する、負の演算についても同様
加えてオーバーフローやアンダーフローも機種に依存する
2.6 関係演算子と論理演算子
まず関係演算子として
すべて優先順位が等しい<,<= ,> ,>=があるこれは右辺左辺の大小を評価するものである
一つ下の優先順位には等値演算子と呼ばれる != ,==がある
さらに関係演算子は算術演算子よりも優先度が低い。これには注意が必要である
より興味深いのは論理演算子である&&,||である
これら論理演算子で結び付いている式は、左から右へ評価が行われその評価は対象の式が真か偽か判定出来た時点で終了する。
なので1章で示したgetline関数を例に示すと
for(i = 0; i < lim-1 && (c = getchar()) != '\n' && c != EOF; i++){ }
この処理ではまず i < lim -1が判定されなければならない。なぜならこの評価が偽であるなら先に進んで文字を入力しては行けないからだ
同様に!= EOFをgetcharの前に呼び出してはいけない
一方、&&の優先順位は||より高く、これらは関係演算子や等値演算子より低い
最後に単項の否定演算子!は0ではない真の被演算数の値を0にし
0つまり偽の被演算数の値を1にする
if(!valid)
というのは、つまりは
if(valid == 0)
とおなじであるということだ
2.7 型変換
異なる型の被演算数を一緒に計算すると、少数の規則によって共通の型に変換される
一般的には浮動小数点と整数を一緒に計算するときに、整数を浮動小数点に変換したり
それ以外で情報が欠損しない範囲でより広い型に変換されたりすることがある
ただし、情報の欠損があるとしても大きい型の値を狭い型へ変換や代入を行うことは警告が出るかもしれないが文法違反ではない
まずcharから
charは単に小さい整数だから算術式の中で自由に扱える
これは文字変換に対し、ある種の柔軟性を生む
例として文字を整数に変換するatoi関数の単純なものを紹介する
int atoi(char s[]){ int i,n; n=0; for(i = 0; s[i] >= '0' && s[i] <= '9';++i){ n = 10*n+(s[i] - '0'); } return n; }
この例でs[i] - '0'が計算可能なのは文字が数値、厳密には整数だからである
charからintへの変換例として、ASCII文字セットに対してのみ1文字を小文字に変換する関数lowerがある
int lower(int c){ if(c >= 'A' && c <= 'Z'){ return c+'a'-'A'; }else{ return c; } }
小文字は数値として一定の距離内にあり、かつ各文字は連続していて、A〜Zの間には文字しかなく
すべて数値として扱えるためASCII文字セットにのみ適応できる
ところでctype.hという標準ヘッダをしっているだろうか
これらに含まれる関数は文字セットとは独立した形で、検査変換などを行う関数の一群で
例えばtolower(c)はcが大文字であればcの小文字の値を返すからtolowerはlower関数の移植可能な代替版となる
またisdigit(c)という関数は、 c >= '0' && c <= '9'という検査の代替として使えるため今後はそれをつかっていく
次に文字から整数の変換で起こる微妙な点についてまとめてみる
それは文字を整数に変換した時にその数がsigned,unsignedであるか、つまり符号があるかないかという点である
これは計算機のアーキテクチャによって違いがでるもののC言語の定義では、すべて正の数であることが保証されている
しかし文字変数のビットパターンが正か負かという判断は計算機、ハードウェアに左右されるのでsigned,unsignedを明記するのがいいとされている
ここまで少し書いてきた型の暗黙的変換は大抵プログラマーの予想どおりの変換になる
式の中に、異なる型が混ざっているなら低い型は高い型に変換され、結果として式が返す結果は高い型となる
この変換にはまだ規則があるがそれは後述、unsignedの被演算数がなければ大まかな規則だけ知っていれば十分
基本的に暗黙的な型変換は
被演算数がlong doubleを含む場合は他方をlong doubleに変換し
それではなく被演算数がdoubleを含むなら他方をdoubleに
それではなく被演算数がfloatを含むならなら他方をfloatに
そうでないならcharとshortをintに変換する
そして、被演算数がlongを含むなら他方をlongに変換する
ここで注意すべきことはfloatが自動的にdoubleに変換されることはないこと
しかし、一般的にmath.hにあるような数学関数で倍精度が使われる
floatを使う理由は大きな配列の容量を減らすことであったり、計算時間を短くすることにある
ところでunsignedな被演算数が入ってくると変換の規則がもっと複雑になる。
問題はsigned,unsignedの比較が様々な整数の型に影響されるため機種に依存することである。
例として、
int->16bit
long->32bit
とすると
- 1L<1U
となる
何故ならintである1Uがsigned longに格上げされるためである
しかし
1UL<-1U
も成立していまう
これは、-1Lがunsigned long に格上げされるためである
変換は代入時にも行われ、右辺の値が左辺の型に変換され、それが結果の型となる
ここまでで述べてきた様に、文字は符号拡張されたり、されなかったりして整数に変換される
より長い整数は余分な上位ビットが捨てられて、より短い整数に変換される
つまり以下の例で
int i; char c; i = c; c = i;
cの値は不変になる
これは符号拡張の有無に関係なく言えることである。
しかし、代入の順序を逆にすると情報の欠損が起こる場合がある
ちなみにfloatからintへの変換では、小数部が切り捨てになり、doubleからfloatではまるめられるか切り捨てかは機種に依存する
最後にキャストについて
キャストとは明示的な型変換(強制的でもある)で、任意の式に適応できる
例えば、math.hに含まれるsqrt関数では引数にdoubleを仮定しているからnを整数とするなら
sqrt((double)n);
とすればdoubleに変換され引数として渡される
この時、元の変数や値が変わることはない
また実際はある程度の大きさまでは、暗黙的型変換が適応される。
2.8 インクリメントとデクリメントの演算子
C言語にはインクリメントとデクリメント演算子として、ここまではっきりとは紹介してこなかった演算子がある
まず、被演算数に1加える++のインクリメント演算子
そして、被演算数に-1するデクリメント演算子
実際には、既に使用していて
if(c == '\n'){ ++nl; }
の様な形で使用していた
見慣れない点はこれらの演算子が変数の前にある、前置演算子(++n,--n)の場合と変数の後ろにある後置演算子(n++,n--)の場合がある点で
例で言えば、どちらともnの値を±1できるが前置演算子ではnを先に±1し、後置演算子ではnを後で±1する
なのでこうなる
int x; int y; int n = 5; int m = 5; x = n++; y = ++m;
上の例で、xにはnの5が代入されてからnは6になる
またyには先にmが+1されて6が代入されmも6となる
2.9 ビットごとの論理演算子
C言語には6つのビット処理演算子を持っていて、これはunsigned,signedに関らず整数にのみ適応できる。
演算子 | 意味 |
---|---|
& | ビットごとのAND |
ビットごとのOR | |
^ | ビットごとの排他的OR |
<< | 左シフト |
>> | 右シフト |
~ | 1の補数(単項演算子) |
はてな記法上の問題でビット毎のORについてはかけなかったが「|」の記号を用いる
まず&演算子に付いてはビットをマスクするのによく使われ
n = n & 0117;
とあるなら、低位7ビット以外を0にする
|演算子はビットをオンにする役目を持ち
x = x | SET_ON;
とあるならSET_ON中で1になってるビットに対応するxのビットが1になる
ビットごとの排他的ORの^演算子では、対応するビットが異なる時にそのビットは1にされ
同じビットであるときは0にセットされる
ビットごとの演算子&,|は論理演算子&&,||と違うことに注意しなければならない
それは後者が左から右へ評価していくためである
なので x&yが0なら x&&yは1となる
シフト演算子は左被演算数と右被演算数で指定したビット数だけシフトするのに使われる
x << 2;
とあるならxを左へ2ビットシフトすることを意味し、シフトし出来た空白は0で埋められるため
この場合、左から2ビットが0になる
単項演算子~は1の補数を求めるのに使用される
これは各ビットが1なら0に0なら1に反転させるからである。
例えば
x = x ~ 0117;
とあるなら
xの下6ビットを0にする意味をもつ
2.10 代入演算子と式
次の様な式
i = i + 2;
では左辺が右辺に再び現れる
これは次の様にまとめて書くことが出来る
i += 2;
このような+=を代入演算子という
大抵の2項演算子はそれに対応する代入演算子をもつ
しかしこれには注意が必要である
以下の例で
x += y+1;
とするとこれはどう解釈できるだろうか
x = x + (y+1);
だろうか、それとも
x = x + y + 1;
正解は前者である
またこの代入演算子は様々な式に適応できて
例えば以下のようなfor文もかける
int i; for(i = 0; i < 100; i+=2){ }
こうあるならiを+1ではなく+2しながらループを回すことができる
2.11 条件式
以下のプログラムは最大値を代入するものである
if(b < a){ max = a; }else{ max = b; }
これは、3項演算子を用いて以下の様に書ける
x = (b < a)? a : b;
この二つの文は全く同じことをしている
またこれは、必ずしも代入する必要はなくprintfなど関数に値を渡す場合にもしようでき
//最大値を表示する printf("%d\n",(b < a)? a : b);
とも書ける
C言語 やさしい入門
この前、プログラミング言語Cという
C言語の開発者が書いている本を買ったので
それで勉強したことをまとめていく
1.第一章 やさしい入門
この章では細かい点を抜きにしてCを手短に紹介している
1.1 手始めに
まず避けては通れない
「hello,world」の出力をしてみよ
というもの
これは、Cを以前少しだけやっていたのでわかる
#include<stdio.h> //int,return 0;は無くてもいい int main(){ printf("hello,world\n"); return 0; }
こうなる
これをコンパイルするには
個人的にはgccを使うけど、別にどっちを使ってもいい
そして、何もオプションを付けないならa.outという実行可能ファイルができるので
それを実行すれば、「hello,world」と表示される
まずはこの世界一有名なプログラムから重要な部分を解説していく
まず一行目の
#include<stdio.h>
これをおまじないとまとめてしまうヌルい入門書やサイトがあるが
そうごまかさないで意味を理解する
includeとはなにかヘッダーやファイルを参照するという意味で
今回であれば、stdio.hという標準入出力に関するヘッダーファイルについての情報をこのファイルに含めるという明示的な宣言である
これが無いとprintfなどが使えない
javaでいうところのimportと同じ意味合い
次に
int main(){ printf("hello,world\n"); return 0; }
このmain関数
Cではmain関数から実行されるため、必ずこの関数を実装しないと行けない
mainの前のintは戻り値の型で、戻り値が無い場合voidや、省略が可能。
return は戻り値で、ここで関数の先頭(今回ならmainの前)で宣言している型と戻り値の型が一致している必要がある
また戻り値の型を宣言しているのにreturnしないのもだめ
今回のhello,worldプログラムでは別に戻り値を出す必要はないので
こうも書ける
#include<stdio.h> main(){ printf("hello,world\n"); }
この方が短くなる
そしてこの場合大して必要ないreturn文も省略できる
ここまでのプログラムでも今後書くコードでも言えるのが
Cのプログラムは関数と変数で大部分が構成される
そして関数には処理となる計算などの文
それを支える変数が大抵実装される
また関数の他にこの後出てくる構造体なんかもよく出る
1.2変数と算術式
次のように
0 -17
20 -6
40 4
60 15
80 26
100 37
といった℃=(5/9)(F-32)で出せる
華氏の温度、摂氏の温度の対応を出力するプログラムから
C言語をもう少し掘り下げてみる
#include<stdio.h> main(){ int fahr,celsius; int lower,upper,step; lower = 0;//温度表の下限 upper = 100;//温度表の上限 step = 20;//刻み fahr = lower; while(fahr <= upper){ celsius = 5 * (fahr-32)/9; printf("%d\t%d\n",fahr,celsius); fahr = fahr + step; } }
やっぱりCのプログラムはmain関数に書く(今は)
ここでは変数、算術式、コメント、宣言なんかが使われている
まずココでの変数とその宣言は
int fahr,celsius; int lower,upper,step;
これで、すべてint型で宣言されている
同じ型であれば,でつないで宣言できる
そしてCで使うすべての変数は一般的に使う前に普通は実行可能文の前に宣言して置かなければならない
データ型に関しては後述。
コメントは//で書くのはお馴染み。
あと今回はループ文も使用されていて
while(fahr <= upper){ //処理 }
この記述。
今回使用したwhile文では以下のように動く
まず()の中の条件文をテストする
↓
これが真であれば{}の中の処理が実行される
↓
最初に戻る
逆に()内の条件に対して偽になればループを抜ける
次に算術式
今回で言うならこれがわかりやすい
celsius = 5 * (fahr-32)/9;
そういえばなぜ先に今回は5をかけていたかというと
整数の割り算では切り捨てが行われるため、5/9は0となってしまうため
すべての結果が0になってしまう
そのため先に5倍したものを9で割っている
そして、一番気になるprintfの中にある\tと%d
まず\tはエスケープシーケンスの一種でタブスペースを取るもの
エスケープシーケンスであれば\nの改行も同じエスケープシーケンスの一種
そして%d
これは%記法といわれ、それぞれ対応する引数を代入できる特殊な記法でCではよく使う
printf("%d\t%d\n",fahr,celsius);
今回のprintfでは一個目の%dがfahr,二個目がcelsiusの値を指している
そして大事なこととして
printfはC言語の一部ではない
C自体には入力も出力も定義されておらず、printfは有用な関数に過ぎないということ
ココではC自体に重点を置くため、printfやscanfには深く触れない触れない
そしてさっきのプログラムには少し問題がある
それは、表示が左揃えで少し見にくいこと
なので少なくとも100までの表示である今回のプログラムであるなら
%3dと桁数をdの前に記述することで右揃えにすることができる
printf("%3d\t%3d\n",fahr,celsius);
実は更に重要な問題があり、最初の-17は-17℃ではなく正確には-17.8℃であるということ
それを修正するのにはちょっとした手直しが必要になる
#include<stdio.h> main(){ float fahr,celsius; int lower,upper,step; lower = 0;//温度表の下限 upper = 100;//温度表の上限 step = 20;//刻み fahr = lower; while(fahr <= upper){ celsius = (5.0/9.0) * (fahr-32.0); printf("%3.0f\t%3.1f\n",fahr,celsius); fahr = fahr + step; } }
これでいい
具体的にいうならfloatで宣言しなおして、%記法の部分をdからf(浮動小数点)に直し
整数の部分で結果に影響しそうな部分を浮動小数点に変更した
実際には32は32.0に変更する必要はない
なぜなら暗黙の型変換が適応され、32.0と解釈されるから
でも、可読性を考えて浮動小数点表記にした
これでも十分な感じはあるがこのプログラムは次の単元でもっと簡単に書ける様になる
1.3 for文
while文の他にループでfor文というものがある
一般に回数がわかっていたりするときに使われることが多い
(whileは回数不明な時)
といっても実際にはどちらでも実装できる
というわけで、for文で実装してみた
#include<stdio.h> main(){ int fahr; for(fahr = 0; fahr <= 100; fahr += 20){ printf("%3d\t%3.2f\n",fahr,(5.0/9.0)*(fahr-32)); } }
今回の場合、上限と大体の計算回数がわかってるため
for文を使用することで、変数宣言を一回にし
更にデータの種類を1種類にすることができた
またこれを踏まえて、for文とはなにか
例でわかるようにfor文はwhile文を一般化したもので役割は3つある
まず変数の初期化
例であげれば
fahr = 0;
の部分である。
次にループ本体に入る前に一回だけ実行される
fahr <= 100;
というループを制御する条件文
これに真であれば、ループを開始し
偽であればループに入らず次の処理を実行する
そして
fahr += 20
という大抵はインクリメントのステップ
これには大体の演算処理が適応出来る
これらを踏まえて、while文とfor文はどちらを使ってもいいが
大抵for文の方がコンパクトにまとまるのでfor文を使うことをおすすめする
そして本にはこの後演習問題が掲載されていたのでやってみる
演習
これまでループ文を用いて書いてきたプログラムの出力結果を逆順になるように修正せよ
これはすごく簡単でこの手の問題はfor文を使うことで書ける
#include<stdio.h> main(){ int fahr; for(fahr = 100; 0 <= fahr; fahr -= 20){ printf("%3d\t%3.2f\n",fahr,(5.0/9.0)*(fahr-32)); } }
これでいい
変数初期化を上限に変更し、範囲を0以上に変え
一回のループごとにループ変数の値を20引いていく
1.4 記号定数
温度換算の話はこれでは終わらない
さっきのfor文のプログラムに記号定数の要素を追加していくと更にわかりやすくなる
というのも、今この記事で上限は何で、一回ごとのループ変数は何でと
解説しているからそれぞれの数字の意味、変数の値の意味を理解しているが
実際はそう簡単ではなく、数カ月後の自分自身や
あるいは誰か他人が見ることも考えられる、そうなるとああいった数値は意味不明だ
そういっためんどくさい理解を防ぐために記号定数を使用する
記号定数の定義は以下の様に行う
#define 記号名 値
これを用いて1.3でfor文を使用したプログラムをわかりやすく変更してみる
#include<stdio.h> #define LOWER 0 //下限 #define UPPER 100 //上限 #define STEP 20 //ステップサイズ main(){ int fahr; for(fahr = LOWER; fahr <= UPPER; fahr += STEP){ printf("%3d\t%3.2f\n",fahr,(5.0/9.0)*(fahr-32)); } }
この様に、極力意味不明な数値を記号定数defineで定義することにより
プログラムで使用している数値の意味合いがわかりやすくなる
また記号名を大文字で定義しているが、これは小文字の変数名と混同しないようにするためで小文字でも定義できる
なので、大抵定数は大文字で定義するのが通例である
1.5 文字入出力
次にプログラムで文字を扱うことを考えてみる
ここで初めてプログラミングをする人にとっては一気に難易度があがるのでしっかり解説していきたい
まず文字をどう扱うか
この章では文字をキーボードからの入力により取得しそれを出力する。
という形式を標準ライブラリを使用しつつ取る。
標準ライブラリで文字を扱うことは非常に簡単で
入出力がどこで発生し、どこで出力されるかによらず文字はストリーム(流れ)として扱われる
これらのテキストストリームは行に分割された文字の継起である
各行は、0またはそれ以上の個数の文字の列とその最後につく改行記号によって形成される
標準ライブラリでこれらを実現するにはまずgetchar()という関数が使える
これは一度に1文字キーボード入力を受け取り、受け取った文字を返す関数なので以下のコードは
c = getchar();
cにキーボード入力から受け取った1文字を格納する
出力するにはputcharが使用でき、これは変数に格納された文字を出力する
上のコードでcの値を出力したいなら
putchar(c);
というコードでいい
1.5.1 ファイルの複写
getchar,putcharがあれば、入出力に関してあまり知らなくても役立つプログラムを書くことができる
もっとも単純なプログラムは、入力を出力へ一度に1文字ずつ複写するプログラムである
言語で仕組みを表すなら
1文字取ってくる
while 文字がファイルの終わりでない
読み込んだばかりの文字を出力
新しい文字を取ってくる
といった様に書ける
これをCのプログラムで表現するなら
#include<stdio.h> main(){ int c;//文字は整数値 c = getchar(); while(c != EOF){ putchar(c); c = getchar(); } }
!=は「等しくない」ことであり
EOF(end of file)はファイルの終わりにつくものである
C言語では文字は整数値として扱われるので、整数値を格納出来るデータ型なら大抵格納できる
文字データを格納するのにはchar型を使用するのが一般的だがココでint型を使用するのには微妙だが重要な理由がある
その問題となるのが文字の終わりと正しいデータをどう区別するかという課題である
これを解決するのには、どの値とも混同しないで区別可能な値をgetcharが返す必要がありそれを担うのがEOFである
なので、上記のコードで整数型変数cはプログラムが受け入れる任意の文字に、EOFを付加するだけの十分なサイズを保持している必要があるので
この場合はcharではなくintを使用した
またEOFはstdio.hで定義されている整数であり、それがどのcharとも等しくない値であればその値を具体的に決める必要はない
それに加え記号定数を使用することで、プログラムでどのような値を使用しようと影響はしないことが保証される
そして複写を行うプログラムは経験をある程度つんだ人ならもう少し短く書ける
#include<stdio.h> main(){ int c;//文字は整数値 while((c = getchar()) != EOF){ putchar(c); } }
1.5.2 文字、行数、単語のカウント
ここらのカウント系はすべてまとめて行う
まずは文字カウントから
これはすごく簡単で、getchar関数が1文字取得(実際には文字列でもいけるがココでは1文字ということにする)するという性質を利用する
#include<stdio.h> main(){ int c;//文字は整数値 int count = 0;//文字カウント用の変数 while((c = getchar()) != EOF){ putchar(c); count++; } printf("%d\n",count); }
これでいい
新しくカウント用の変数を初期化した状態で宣言しそれをインクリメントしていくことでカウントしている
ただこれでは少し問題がある
intが機種によってサイズが異なるため最悪16bitとして扱われた場合最大値が32767であり
比較的小さい入力でオーバーフローを起こす可能性がある
それを防ぐために少なくとも32bitのlongに変更するべきだろう
次に行数のカウント
これは少し考えるかもしれないが実は簡単
まず入力の性質を考える、テキストストリームは行であるから必ず文末に改行の記号が入るので
それをカウントすればいい
#include<stdio.h> main(){ int c;//文字は整数値 int count = 0;//文字カウント用の変数 while((c = getchar()) != EOF){ putchar(c); if(c == '\n')count++; } printf("%d",count); }
これでいい
cに含まれる改行の内部表現を見つけることで行と判断しカウントしていく
この時、シングルクォーテーションで囲まれたものを文字定数といい
それらは数値に変換される、そのため==が適応出来る
ダブルクォーテーションで囲むと、文字列扱いになるのでNG
次に単語をカウントしていく
この時、まず行単位で考え探索が単語の内か外かどちらにあるか判断する
例えば、
hello worldとあったとしよう
それをプログラミング言語的に解釈すると
hello(空白)world(改行)
となる
そのため、単語の外に出た回数をカウントは単語数と一致する
このとき単語の外に出たか判断するのは、改行、タブ、スペースの3種類である
コードはこうなる
#include<stdio.h> #define IN 1//単語の内 #define OUT 0//単語の外 main(){ int c; int count_word = 0; int state = OUT;//はじめは単語の外と仮定 while((c = getchar()) != EOF){ //空白、タブ、改行を探索する if(c == ' ' || c == '\t' || c == '\n'){ state = OUT; }else if(state == OUT){ state = IN; count_word++; } } printf("%d\n",count_word); }
この様にgetchar,putchar以外にも
文字定数やif文を使用すると、以前と比べ有用なプログラムを書くことが出来る
1.6 配列
1.5で作成したプログラムを更に改良してみる
具体的には配列を使用して、単語の数、受け取った値に含まれる数字(0~9)の個数を表示してみようというもの
単語の数はさっきのプログラムをそのまま使える
数字のカウントは、サイズが10の配列を生成して添字に対応させる
#include<stdio.h> #define IN 1//単語の内 #define OUT 0//単語の外 #define NUMBER_SIZE 10//数える数字(0~9) main(){ int c; int count_word = 0; int state = OUT;//はじめは単語の外と仮定 int numbers[NUMBER_SIZE], i; //配列の値を初期化する for(i = 0; i < NUMBER_SIZE; i++){ numbers[i] = 0; } while((c = getchar()) != EOF){ //空白、タブ、改行を探索する if(c == ' ' || c == '\t' || c == '\n'){ state = OUT; }else if(state == OUT){ state = IN; count_word++; } //数字の探索 if(c >= '0' && c <= '9'){ numbers[c-'0']++; } } //それぞれの数字の個数を表示 for(i = 0; i < NUMBER_SIZE; i++){ printf("%d:%d\n",i,numbers[i]); } printf("words:%d\n",count_word); }
今回は数字の種類までカウントさせるため
10個の変数より、サイズが10のint型の配列にカウントした値を格納するほうが賢い
C言語の配列は扱いにくく、奥が深いけど入門編ではここまでしか扱わない
このあとじっくりと解説していく
ただ、プログラミング経験者やポインタで挫折した人に覚えてほしいことがある
それは、配列のアドレスは固定されたサイズのメモリ領域を確保するということです
あとこれは少し入門から外れますが、string.hのmemsetという関数を使用すればもう少しコードが短くなります
#include<stdio.h> #include<string.h> #define IN 1//単語の内 #define OUT 0//単語の外 #define NUMBER_SIZE 10//数える数字(0~9) main(){ int c; int count_word = 0; int state = OUT;//はじめは単語の外と仮定 int numbers[NUMBER_SIZE], i; //配列の値を初期化する memset(numbers,0,sizeof(numbers)); while((c = getchar()) != EOF){ //空白、タブ、改行を探索する if(c == ' ' || c == '\t' || c == '\n'){ state = OUT; }else if(state == OUT){ state = IN; count_word++; } //数字の探索 if(c >= '0' && c <= '9'){ numbers[c-'0']++; } } //それぞれの数字の個数を表示 for(i = 0; i < NUMBER_SIZE; i++){ printf("%d:%d\n",i,numbers[i]); } printf("words:%d\n",count_word); }
1.7関数
最初に言ったようにC言語のプログラムは関数と変数で構成されているといっても過言ではないほど関数と変数を多用する
今まで標準ライブラリのコードを使ってきたが、この章では実際に関数を自作して学習していく
まずC言語で関数とはある特定の処理をまとめた一つの固まりで、C言語開発者いわく
よく設計された関数とは何をしているかが明確なのではなく、何ができるか明確なものと言われます。
またよくプログラムの中で、一回しか呼ばれない関数などがありますがこれはプログラムを見やすくする為に作成されたもので
プログラム全体の可読性を上げる為に関数を作る場合もあります
それらを踏まえて少し関数を自作してみます
まず、powerという名前である正の整数と指数を受け取りべき乗を計算、計算結果を返すような関数を作成します
そこで気をつけるべきことは、main関数よりしたに独自の関数を追加しないことです
これは一部例外を除いてエラーになります
その一部例外とは、プロトタイプ宣言をした時です
プロトタイプ宣言とは引数のデータ型、関数名、引数と関数の処理を除いた事をmain関数の前に記述することで
その関数をmain関数のしたに書いてもエラーにはならず
また記述が簡単なので可読性も上がります
というわけでこれからはプロトタイプ宣言をして関数を実装していくことにします
#include<stdio.h> //プロトタイプ宣言 int power(int base,int index); int main(){ int n = 2; int i; for(i = 1; i <= 10; i++){ printf("result:%d\n",power(n,i));//2のi乗を計算し結果を表示 } return 0; } int power(int base,int index){ int result = 1; int i; for(i = 0; i < index; i++){ result *= base; } return result; }
まず見てわかるように、4行目がプロトタイプ宣言でこれが重要
後は結果を計算し、returnするだけ
ただ指数計算は計算結果が大きくなりやすいので戻り値をlongにしてもいいかもしれない
これがそこそこ簡単な自作関数
関数はどんな順序であらわれても良く、ひとつのソースファイルでも複数に分割されていてもいい
つまり、呼び出す順番も定義する順番も関係なく
またincludeさえされていれば、複数のソースファイルから呼び出せるというわけ
そして関数について少し追加で解説
power関数の引数、これはどの関数について言えることだが
引数は純粋にその関数のものであるから他の関数から参照できない
そして文末のreturn
これは本来
return 式;
という形をとるが、必ずしも意味のある値を返す必要はなく
何も返さない場合制御が呼び出し側に戻る
なのでvoid、つまり戻り値が無いときにreturn ;と書いてもいい
また今回はmain関数にreturn 0;を追加したが、今まで説明したことを考えると
これでmain関数を抜け制御が呼び出し側に移るが、この場合呼び出し側はプログラムが実行される環境であり、0は正常終了、それ以外の値は異常終了といった意味合いを持つ
なので今後はこれをmain関数につけることにする
ちなみに戻り値の型は無くてもよい
1.8 引数 値による呼び出し
C言語では引数はすべて値で渡される
これは呼び出された関数には一時変数が生成されそれによって値が渡されていることを示す
なので、引数の値を関数内で変更するなどといったことはできず
それをやるにはその変数のアドレスを渡す必要がある
ただ配列については話が別で配列を引数として渡すと、アドレスが渡されるので変更可能となる
1.9 文字と配列
というわけで1.8の流れで配列を扱う
多分一番多用するのが文字の配列、C言語には文字列が無いため文字の配列でそれを実装することがしばしばある
というわけで1群の行を読み込み、一番長い行を表示するようなプログラムを書いてみる
これを擬似コードにしてみるとこう
while(まだ行がある){
if 以前の行より長い
それ格納
その行の長さも格納
}
print 一番長い行
となる。
これに必要なのは行を読み込むgetlineという関数と格納するときに複製するcopyという関数
getlineという関数では行を読み込むが、行の終わりを示す文字
終端文字が必要になってくることも考慮する必要がある
copyは結果を一時的に安全な場所に保持しておくために使用する
#include<stdio.h> #define MAXLINE 1000//1000行を最大とする //プロトタイプ宣言 int getLine(char line[],int maxline); void copy(char to[],char from[]); int main(){ int len; int max; char line[MAXLINE]; char longest[MAXLINE]; max = 0; while(0 < (len = getLine(line,MAXLINE))){ if(max < len){ max = len; copy(longest,line); } } //行が存在するなら if(0 < max){ printf("%s\n",longest); } } int getLine(char line[],int maxline){ int c; int i; for(i = 0; i < maxline-1 && (c = getchar()) != EOF && c != '\n'; i++){ line[i] = c; } if(line[i] == '\n'){ i++; } line[i] = '\0';//終端文字 return i; } void copy(char to[],char from[]){ int i; i = 0; //終端文字までコピー while((to[i] = from[i]) != '\n'){ i++; } }
このように文字列は文字の配列として扱うことができ
配列を引数で渡すと、アドレスが渡されるため関数内での処理が適応されるため
正確な結果が表示される
詳しくが後述
まとめ
これで変数のスコープを残して(単純に書くのがめんどくさかった)ほぼ入門編は終わり
これから今回解説した部分を掘り下げていく
java【数値】
まずは、数値の中で比較的扱う頻度の高い整数の話から
1.整数
数学の世界には正の数にも負の数にも無限大までありますが
プログラミングの世界には整数といえど限りがあります
そしてjavaには整数を扱う5つのデータ型が存在し、それぞれ範囲が設定されています
データ型 | 範囲 |
---|---|
byte | -128以上127以下 |
char | 0以上65535以下 |
short | -32768以上32767以下 |
int | -2147483648以上2147483647以下 |
long | -9223372036854775808以上9223372036854775807以下 |
2.ビット長と値の範囲
1.整数で整数を扱うデータ型の値の範囲を示しました
ですが、実際なんでこんな中途半端なのかと言われればそれはビットと関係があります
そのため、まず各データ型のビット長を示します
データ型 | ビット長 |
---|---|
byte | 8 |
char | 16 |
short | 16 |
int | 32 |
long | 64 |
ビットとは2つの状態を取る自称を抽象化した時の単位のことで
2進数の世界で桁数に相当するものをビット長という
これらビット長がデータ型の値の範囲を決めていきます
実際どのように影響しているかというと、このビット長を超える整数を格納しようとすると桁あふれという現象が起こり
あふれた分を格納可能な下限に足した数になってしまい、大抵の場合これは負数になってしまうためバグなんかの原因になります
int max = Integer.MAX_VALUE; System.out.println(max);//2147483647が出力 max++; System.out.println(max);//-2147483648
しかし桁溢れはこのように最初は格納範囲内だったのに、処理をしていくうちに格納範囲を超えてしまった場合におきて
はじめから整数リテラルとして定義されているもので範囲外のものはコンパイルエラーになります
3.整数型の使い分け
実際ここまでデータ型を扱ってきて、5つある整数のデータ型をどう使い分けるか
char,byte型
これらはそれぞれ文字を扱う場合、byteを扱う場合に限定するべきというのが定説です
一応小さい整数値として使用できるが、上記の場合以外ではint型と実行効率が大差無い上に無理をして使うとint型より実行効率が悪くなってしまいます
short int long型
これら3つの場合は「どれでもいいならintを使う」が定説です
実際にはCPUが何bitかということで判断するべきらしいのですが現在では32bit,64bitともにint型が一番安定した実行効率をほこるという考えがあるようです
なのでlongは極力、intで格納しきれない数の時にのみ使うべきです
またshortを使う場面はほとんどなく現代のマシンスペックではこれを使うだけのメリットが無く無理に使うとchar,byte型と同様に性能の悪化を招く危険性があります
4.四則演算
整数を扱うにはなくてはならないのが四則演算
javaでは4つの算術演算の二項演算子があります
演算子 | 意味 |
---|---|
+ | 和 |
- | 差 |
積 | |
/ | 商 |
これは実際の数学と同じでほとんど直感的に扱うことが出来ますがいくつかの注意点があります
・大きな数の和での桁あふれ
・絶対値の大きな負数での差の桁あふれ
・絶対値の大きな数同士の積
・あまりが切り捨てになる
・0による割り算が例外を発生させる点
これらに気をつければ問題なく扱うことが出来るでしょう
そして、上記の注意点の時にさらっと書いたのですが/演算子は商しか考えていなくあまりが出る場合は切り捨てられます
そこであまりを扱う剰余演算子の「%」があります
例えば奇数、偶数判定や「fizzbuzz」問題をとくときに使用できて
for(int i = 1; i <= 100; i++){ if(i%2 == 0){//2であまり0の時つまり2で割り切れる、偶数の時 System.out.println(i); } }
このコードでは2で割ってあまり0の時、つまり2で割り切れるので偶数の時のみiを出力するというものです
%演算子はこのように使います
5.符号反転
- を付けることで差を表すだけでなく符号反転も出来ます
int n = 10; System.out.println(-n);//print -> -10
6.型変換
はじめの方に型と種類の違いについて説明しましたが
javaは静的型付けなので違う型の変数に代入するとコンパイルエラーになります
例えば下記の例
int i = 0; String str = i;//これがコンパイルエラー
というわけで、小さい範囲の型の値をそれより大きい範囲の型には代入できるますが
縮小変換はコンパイルエラーになることがあります
しかし範囲内であれば、理論上格納可能です
それでも小さい型から大きい型の様に自動的に変換はされないので、強制的に変換する必要があります
またその強制的(明示的)変換をキャストといいます
int i = 0; short i_s = i;//これはコンパイルエラー int i = 0; short i_s = (short)i;//これがキャスト、コンパイルエラーにはならない
このように縮小変換時に便利でも範囲を超えている数に対してキャストし格納しようとすると桁溢れのような
想定外の結果がでてしまうので注意が必要です
それと数値型同士でのキャストでは問題ありませんが、文字列やboolean型とはキャストしてもコンパイルエラーになります
7.浮動小数点数
整数の次は浮動小数点数の説明をします
まずjavaでは浮動小数点数を扱うためにfloat,doubleという2つのデータ型が用意されています
浮動小数点型 | ビット長 |
---|---|
float | 32 |
double | 64 |
浮動小数点数といっても、ざっくり言えば小数のことです
なので扱いも整数とあまり変わりませんが、リテラル表記には多少注意が必要です
・fまたはFで終わるならfloat、d,Dならdouble
・無表記ならdoubleになる
という点で多少の注意が必要です
また指数を指定するリテラル表記もあり
1.8e1 //1.8*10の1乗->18
といったように定義出来ます
8.範囲を超える数を扱う
整数で言うlong型の格納範囲を超える整数を格納、扱う方法はないように感じますが
java.math.BigIntegerクラスを使うことで、範囲外の大きな数も扱うことが出来ます
それと同様に整数ではなく浮動小数点数を扱うものはjava.math.BigDecimalクラスです
このあたりは紹介するとキリがないのでリファレンスを参照してください