大島聡史 情報基盤センター助教
•
目標
–
(C言語を全く知らない状態から)
C言語をある程度使えるようになる
•
C言語の基本的な知識を得る
•
ベクトルや行列を使った計算を行うプログラムを作れるよ
うになる
2015/9/7-82
•
C言語とは?
–
手続き型言語
•
書いてある処理を(基本的に)上から順番に行っていく
•
C言語の用途
–
システムソフトウェア(OS、コンパイラ、通信……)
–
科学技術計算、数値計算(特に計算速度が重要なもの、Fortran
で行うことも多い)
–
電化製品などの組み込みシステム
•
C言語の主な構成要素
–
関数、変数・配列、フロー制御(分岐・繰り返し)、コメント
2015/9/7-83
1. テキストエディタでソースコードを書く
–
emacsやviなどのテキストエディタ
–
eclipseなどの統合開発環境
–
WordやWriterなどのリッチな文字装飾機能を持つエディタ(ワープロ
ソフト)は(普通は)使わない
2. コンパイラを使って実行可能形式に変換(コンパイル)する
–
gccなど →(特に指定しないと)a.outというファイルが作成される
3. 実行する
–
./a.out
•
メモ
–
C言語を用いて書かれたプログラムを実行するにはコンパイルが必要
–
コンパイル不要のプログラミング環境もある
• シェルスクリプト、Perl、Python、Ruby、JavaScriptなど 2015/9/7-84
•
書く内容
–
何故か初めてのプログラミング
では「Hello, World!」と出力す
るという伝統(?)がある……
–
ファイル名は何でも良いが、拡
張子は .c とする(例 source.c)
•
注意点など
–
C言語プログラムは全て半角英
数で書く
• C言語では日本語が使えない……と いうわけではないが、「コメント 文」以外では使わないこと • 全角スペースもNG • タブはOK(基本的に半角スペース と同じように扱われる)–
「;」を忘れない
• 文(処理)の区切りは改行ではなく セミコロン–
改行は適当に入れて良い
• 「区切りっぽいところ」はだいたい OK • 逆に、改行せずに違う処理を入れて も良い – ;のあとにつなげてよい – int main以降は一行にまとめられる #include <stdio.h> #include <stdlib.h>int main(int argc, char**argv){ printf(“Hello, World!¥n”); return EXIT_SUCCESS; } “ ” はダブルクオーテーション ¥ はバックスラッシュ ¥n は改行を意味する特殊な記述 2015/9/7-8
5
•
コンパイル
–
最低限必要な内容
•
gccは代表的なCコンパイラの一つ、オープンソース
•
書き間違いがあれば指摘してくれる
–
よく使うオプション(コンパイラによって違う)
•
出力ファイル名を指定する
•
問題があるかもしれないときに警告する
•
コンパイラによる最適化を行う
•
デバッグのときに色々な情報を出す
•
実行
–
./a.out
gcc source.c
-o ファイル名
-Wall
-O -O2 -O3 など
-g
• カレントディレクトリにあるプログラムを実行するときには ./ が必要 • 別のディレクトリにあるプログラムを実行するときは気にしなくて良い • 実行権が必要(普通は勝手に付くので気にしなくて良い) 詳しくはman gccかgcc --helpかオンラインの資料を参照 2015/9/7-86
#include <stdio.h> #include <stdlib.h>
int main(int argc, char**argv){ printf(“Hello, world!¥n”); return EXIT_SUCCESS; } #include • 別のファイルの内容を取り込むための記述 • 他人(ライブラリ)に提供されている機能 を利用する時などに使う • “”で括ったときはソースと同じ場所にある ファイル、<>で括ったときはあるルールに より決められた場所にあるファイル(環境 変数やコンパイルオプションで設定可能) main • int型引数とchar**型引数をとることができるint型の関数 • プログラムが実行開始される場所なので絶対に必要 printf • 指定されたものを画面に表示するための関数 • stdio.hというヘッダファイルをincludeすると使える return • 処理の結果を伝える記述 • return EXIT_SUCCESS; はプログラムが正常終了したことを意味する記述、 stdlib.hというヘッダファイルをincludeすると使える 後述 • 関数 • 引数 • int型 • char**型 利用可能な機能の情報が書かれたテキストファイル(と思っておこう) 2015/9/7-8
7
•
変数
–
値を保持するための入れ物
•
関数
–
手続きをひとまとめにしたもの
–
関数を実行中に別の関数を実行することも可能
–
引数として値を受け取ることや、実行元に結果を返
すことも可能
•
してもしなくても構わない
2015/9/7-88
•
宣言の例「ここから○○という変数を使います」
–
int a;
int型の変数a
–
float v, x;
float型の変数vとx
•
主なデータ型の例「変数にはこのような種類の値が入ります」
–
char
8bit整数(文字にも使う)
–
int
32bit整数
–
float
32bit単精度浮動小数(1.0f のように書く)
–
double
32bit倍精度浮動小数
–
unsigned
別のデータ型の前に付けて、
0以上の値にしか使えないようにする
–
void
型無し(特殊なもの)
•
利用例(参照と代入)
–
x = 10;
xに10を設定(代入)
–
y = x * 2;
yにxの2倍の値を設定(xを参照して2倍化してyに代入)
• 型によって表現できる範囲や必 要なデータ量(使用するメモリ の量)が違う • 宣言時の値は不定 • int a = 0; のように宣言と初期 化を同時に行うことも可能 -128 ~ 127 -2,147,483,648 ~ 2,147,483,647 +- 10^38 +- 10^308 2015/9/7-89
•
いわゆる普通の四則演算はもちろんとして、様々な
演算子が用意されている
–
算術演算子、代入演算子、比較演算子、論理演算子
演算子 種別 例 備考 + 加算 - 減算 * 乗算 ∗ / 除算 ⁄ 整数型に使うと切り捨て % 剰余 % 整数型でしか利用できない 演算子 種別 例 備考 = 代入 に を代入する ○= 算術演算 代入 ○は算術演算子○ を演算して に代入算術演算子
代入演算子
もう少し あとで 2015/9/7-810
•
データ型の違う値や変数を計算しようとするとどうなる?
–
可能であれば、拡張や切り詰めが行われて計算が実行される
–
(-Wallオプションを付けてコンパイルすれば)
コンパイラが警告やエラーを出すので気がつける……こともある
–
「(データ型)値」「(データ型)変数」によりデータ型の変換が可能
–
計算の優先順位
• (強)単項>2項算術>2項比較>2項代入>2項論理(弱) • (強)乗算 > 除算 > 加算 > 減算(弱) • 不安があるときや読みやすくしたいときは「( )」で括ったり型を明示したりすると良い • x = ( a + b ) * ( c + (( d + e ) * f )) int x; double d; d = 1.6; x = d; xには1が入る int x; double d; d = 1.6; x = 2 * d * d; xには2でも4でもなく5が入る (高精度側で計算してから切り詰められる)int x; double d; d = 1.6; x = 2 * (int)d * (int)d; (int)1.6 = 1 であるため、xには2が入る
•
関数の構造、書き方
– 引数は複数設定可能、返値は1つだけ • 複数の値を返したい場合は「参照渡し」を使う • 返値が不要な場合はvoid型 – ただし返した値は使われないどころか受け取られなくても構わないため、適当な型にして適当な値を返す ことが多い→「とりあえずint型にしておこう」 • 引数変数に何かを代入しても、呼び出し元に影響は無い(「参照渡し」では影響する)•
関数の実行方法
– 関数名(引数); – returnの結果を受け取る場合は 受け取り先 = 関数名(引数);•
関数の宣言(プロトタイプ宣言)
– 実際に使う場所より前に関数の形状(名前、引数型、返値型)を書いておく – 読みやすくするためでもあるが、複数ソースコードを用いる際に重要(後述) – もちろん宣言ではなく実体をその場に書いてしまっても良い 返値の型 関数名(引数の設定){ 関数の中身 return 返値; } int hoge(){return 1;}に対して x = hoge();でもよいしhoge();でもよい • 型情報などがおかしくても動いてしま うことは多い(注意が必要) • 特にmain関数は引数無しやvoid型、 返値無しでも良い • コンパイラが警告を出す可能性は高い 2015/9/7-812
•
変数を使うためには、使うより前に宣言が行われていなければならない
– 宣言がなければ変数の存在を認識できない•
関数内で宣言した(使い始めた)変数は、その関数の中でしか使えない
– 関数の途中で宣言した場合は宣言の後でのみ使える – 参考:古いC言語では関数やブロックの最初でしか変数を宣言できなかった•
{ と } で括ると変数の存在範囲を制限できる
– 括弧内で宣言した変数は括弧内でしか使えない•
一番外側(どの関数の中でもない)の変数は「グローバル変数」
– どこからでも読み書きできるため便利だがバグの温床、注意が必要 – グローバル変数 ⇔ ローカル変数•
同じスコープで同じ名前の変数を宣言することはできない
– 型が違ってもダメ – スコープが異なる場合は狭い範囲が優先される(外と内では別の変数が存在す るかのように扱われる) 2015/9/7-813
•
定数=値が変わらない(変えられない)変数
•
宣言方法:constというキーワードを付ける
–
例 const int n = 10;
–
書き換えられないので宣言と同時に代入する
•
書き換えられないことに何の意味があるのか?
–
プログラム中に出てくる値をわかりやすい文字列で表
現できる(意味がわかりやすくなる)
–
何度も使う値を一気に変更できる
•
プログラムの先頭にまとめて書いておくことが多い
–
関数の引数に使うことで「この関数内ではこの引数と
して与えられたものを書き換えませんよ」と宣言でき
る(→値渡しと参照渡し)
2015/9/7-814
•
変数名・関数名・定数の名前に関する制限
–
先頭文字はアルファベットか一部の記号
–
短くても長くても良いが、使いやすい・わかりやす
い程度にするべき
•
参考:ハンガリアン記法(従っても従わなくても良い)
•
文字と文字列の違い(詳細は後述)
–
文字 ‘a’ シングルクオーテーションで括る
–
文字列 “a” “abc” ダブルクオーテーションで括る
–
文字はchar(-128から127の数値と等価)、文字列
はcharの配列
2015/9/7-815
#include <stdio.h> #include <stdlib.h>
// 一行コメント:関数のプロトタイプ int getnum(int n);
int main(int argc, char**argv){ int x=1, y;
y = getnum(x);
printf(“Hello, world! %d¥n”, y); return EXIT_SUCCESS; } /* コメント(複数行いれたい場合) 引数を2倍にするだけの関数 */ int getnum(int n){ return n * 2; } • int型変数を返すgetnum関数が存在する ことを宣言 • プログラムの実行はmain関数から • int型の変数xとyを用意、xは初期値1 • getnum関数に変数xを渡して処理させる • getnumの結果を変数yに格納 • printfに文字列と変数yを渡して表示 %dはint型変数を表示する記法(後述) • EXIT_SUCCESSを返して終了 • getnumはint型引数を受け取りint型の 値を返す関数 • 受け取った変数nを2倍にして返す • コメントには//か/**/を使う 2015/9/7-8
16
•
C言語プログラムの基本的な構造がわかった
•
変数値を色々と書き換えて表示するプログラ
ムが作れるようになった
#include <stdio.h> #include <stdlib.h> int getnum(int);int main(int c, char**v){ int x=1,y=1,z=1; printf(“%d¥n”, x=getnum(x)); printf(“%d¥n”, z=getnum(y=getnum(y))); printf(“%d %d %d¥n”, x, y, z); return EXIT_SUCCESS; }
int getnum(int n){return n * 2;}
• 実は宣言時に引数の名前は 不要 • mainの引数の名前を変え ても良い • 関数呼び出し内に関数呼び 出しを入れても良いし、そ の際に代入を行っても良い (自分が混乱しないように 注意) • 出力結果 2 4 2 2 4 %dの数と変数の数は 同じにすること 2015/9/7-8
17
•
条件分岐処理
–
if (条件式){ 真・手続き } else { 偽・手続き }
• else 以降は省略可能•
繰り返し処理(ループ)
–
while (条件式) { 繰り返し実行させたい対象手続き }
• 条件式を評価し真の間ループを続ける–
do { 対象手続き } while (条件式)
• ループを行ってから条件式を評価、真の間ループを続ける–
for (初期化; 条件式; 再初期化) { 対象手続き }
• 値を変えながらループをする。• for (i = 0; i < 10; i++){ print(“%d¥n”, i); }
1. ループに入る際に初期化部分を実行 2. 条件式を評価し、真ならループを行う 3. ループ後、再初期化を行い2に戻る
–
条件式には真偽を返す式が入る
• 条件式が満たされ続けると無 限ループになるため注意 • 特にwhileとdoは終了条件が 成立するように対象手続きを 書く必要がある(条件式の中 に書くという方法もあるが) 2015/9/7-818
•
比較演算子と論理演算子
–
「条件式」によく利用される
演算子 種別 例 備考 < 小なり <= 以下 > 大なり >= 以上 == 等しい 代入との間違いに注意 != 異なる !&& AND && ((x!=1) && (y==1)) 等とつかう || OR || ((x!=1) || (y==1)) 等とつかう ! NOT ! 単項演算子 !((x!=1) && (y==1)) & AND & ビット演算のAND
| OR | ビット演算のOR
比較演算子は偽の場合整数型0、 真の場合はそれ以外の整数が入る (一般的には1が入る)
•
インクリメント・デクリメント
–
変数の値を+1, -1する(変数の値が更新される)
•
++A Aに1を加える、加えた後のAを返す
•
A++ Aに1を加える、加える前のAを返す
–
forループの制御によく用いられる
•
複合代入演算子(「演算子=」)
–
元となる変数に対して演算を行いながら代入する
•
元の変数に対して参照と更新が行われる
–
例
•
x += 1, x -= 1
•
x *= 2, x /= 2
•
x &= 1, x |= 1, x ^= 1
2015/9/7-820
•
長さを持つ変数を使うことができる
–
関連した値をひとまとめにして扱うときなどに使う
–
数値計算プログラミングにおいては
ベクトルの表現
として一般的
•
定義方法
–
右は全て
int型
長さ3
の配列の例
•
アクセス方法
–
変数名[添え字番号]
–
添え字番号(インデックス)は 0 から 配列長-1 まで
–
注意:配列のサイズを超えてもエラーが起きずに実行されることがある
• 予期せぬデータ改変などが起こるため注意すること–
配列そのものへの代入はできない
• int a[3]; a = 数値or変数; といった記述はNG(例外あり)
• int a[3]; • int b[3] = {1, 2, 3}; • int c[] = {1, 2, 3}; • int d[3] = {1, 2}; • int e[3] = {}; 初期値は不定 初期値は順に1, 2, 3 初期値は順に1, 2, 3、長さ指定が無い例 初期値は順に1, 2, 0、個数が足りないと 0 初期値は順に0, 0, 0、{}だけの場合は全部 0 ※double型などでも同じような挙動となる 2015/9/7-8
21
#include <stdio.h> #include <stdlib.h>
int main(int argc, char **argv) { int a[3]; int b[3] = {1, 2, 3}; int c[] = {1, 2, 3}; int d[3] = {1, 2}; int i; for (i = 0; i < 3; i++) { printf("a[%d]=%d, ", i, a[i]); printf("b[%d]=%d, ", i, b[i]); printf("c[%d]=%d, ", i, c[i]); printf("d[%d]=%d ¥n" , i, d[i]); } printf("a[%d]=%d¥n", 3, a[3]); return EXIT_SUCCESS; } a[0]=1627408016, b[0]=1, c[0]=1, d[0]=1 a[1]=-1, b[1]=2, c[1]=2, d[1]=2 a[2]=1, b[2]=3, c[2]=3, d[2]=0 a[3]=4198562 出力結果 • 初期化されていないため 何が表示されるか不定 • 範囲外を参照してしまっているた め何が表示されるかわからない • エラー終了してしまうこともある • Segmentation Faultなどの 出力を伴うことがある for (i = 0; i < 配列の要素数; i++) というループは頻出パターン 2015/9/7-8
22
•
行列はどのように表現すれば良いだろうか?
–
長い配列を用意して使う
•
3行4列の行列であれば12要素あればよい matrix[12]
•
アクセスする際にはその都度位置を算出
–
二次元の配列を使う
•
3行4列の二次元配列 matrix[3][4]
•
これ以降は二次元配列にて表現することにする
正直、どちらを使っても構わない
使い方が悪いと性能が低下するが、
正しく使う分には問題ない
コンパイラによる最適化に影響することがある
2015/9/7-823
•
表現方法
–
X[a][b]
長さa*bの二次元配列
–
Y[a][b][c]
長さa*b*cの三次元配列
–
Z[a][b][c][d]
長さa*b*c*dの四次元配列
•
二次元配列と行列の対応付けイメージ
–
matrix[3][4]
•
長さ4のベクトルが
3本束ねられている
ようなイメージ
matrix[0][0] matrix[0][3] matrix[2][3] matrix[2][0] 2015/9/7-824
•
配列の宣言
•
配列へのアクセス
int a[3][4]; int b[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; int c[2][2][2] = {{{ 1, 2},{ 3, 4}}, {{ 5, 6},{ 7, 8}}}; int d[][2] = {{1,2},{3,4}}; ※改行は見やすさの都合によるもの 3x4行列の作成 作成と同時に定数にて初期化 b[0][0]=1, b[0][1]=2, …… 作成と同時に初期化 括弧の位置と数に注意 左端の次元指定だけは省略可能 int a[3][4], b[3][4]; for(i=0; i<3; i++){for(j=0; j<4; j++){ a[i][j] = i*4+j; b[i][j] = a[i][j] * 2; } } • 配列の値をまとめて変更するよう な機能はないため、ループを用い て処理をするのが一般的 • memset関数などが役に立つが今 回は扱わない • iループとjループは逆にしても動く が性能が落ちやすい(「後半」、 最適化の中で触れる) 2015/9/7-8
25
•
(基本的に)データはメモリ上に配置される
•
メモリには番地(アドレス)が振られている
–
番地がないと訪ねられない
•
「&」記号を使うとアドレスを参照できる
•
「*」記号を使うとそのアドレスにあるデータ(値)を参照できる
•
変数とは、あるアドレスを先頭に必要バイト数分を占有するもの
•
配列とは、連続するアドレスを先頭に必要バイト数分*要素数分だ
け占有するもの
いきなり計算機(コンピュータアーキテクチャ)の話になってしまうが…… 0001 0002 0003 0004 ↑番地(アドレス) ←1byte(8bit)分のメモリ 2015/9/7-826
int a = 1; int *b = NULL; b = &a; printf(“a = %d, *b = %d¥n”, a, *b); a = 2; printf(“a = %d, *b = %d¥n”, a, *b); *b = 3; printf(“a = %d, *b = %d¥n”, a, *b); printf(“&a = %x, b = %x¥n”, &a, b); a = 1, *b = 1 a = 2, *b = 2 a = 3, *b = 3 &a = 24cb04, b = 24cb04 実行結果 • b = &a bはaと同じ「番地」を指し示す状態となる • a = 2 aを変更したため、同じ番地のデータを示す「*b」も変更された • *b = 3 「*b」にあるデータはaのこと、そのためaもbも変更された • 最後はaのアドレスとbを表示。%xによってアドレスを16進数で表示させると同 じであることがわかる。(具体的な値は環境によって異なるが、とにかく&aと bは同じ値である。コンパイラに型が合わない旨の警告を出されると思うが、今 回はわかっていてやっているため気にしなくても良い。) NULLは何も無い、空っぽの意味。 (基本的に)0と等価。ポインタ の初期化時によく利用する。 2015/9/7-8
27
•
引数が通常の単体変数の場合、関数内で引数を書き換えても呼び出
し元に影響がない:値渡し
–
変数の値のコピーが渡されると思えば良い
–
コピーが変更されてもオリジナルに影響は生じない
•
引数がポインタの場合、関数内で引数を書き換えると呼び出し元に
影響が生じる:参照渡し
–
変数のアドレスが渡されると思えば良い
–
アドレスを辿ればオリジナルにも影響が生じる
–
引数にconstをつけると、書き換えられない引数となる
void func1(int a){ a = a + 1;
}
void func2(int *a){ *a = *a + 1; } int n = 1; func1(n); printf(“a = %d¥n”, n); func2(&n); printf(“a = %d¥n”, n); a = 1 a = 2 実行結果 2015/9/7-8
28
•
参照渡しがわかると、SWAP関数を作ることが
できる
–
どうすれば良いのか考えてみよう
–
ヒント:値を変えたいのだから参照渡しは必須、直
接交換できないのなら一時的に別の変数に……
void swap(?, ?){ …… } int a = 1; int b = 2; printf(“a = %d, b = %d¥n”, a, b); swap(?, ?); printf(“a = %d, b = %d¥n”, a, b); 2015/9/7-829
•
入力
–
これまで計算用のデータをソースコードに直書きしてきたが、実
際の問題(例えば大きな行列の入力)で行うのは無理がある
•
出力
–
多くの出力を行うと画面が溢れる、時間もかかる
•
入力を受け取ったり、ファイルを読み書きしたりするには
どうすれば良いだろうか?
–
実行時引数の活用、文字と文字列の扱い方
–
実行時に長さの決まる配列を作る方法
–
もう少し詳しいprintfの使い方
–
キーボード入力の受け取り方
–
ファイル入出力の方法
2015/9/7-830
•
作成したプログラムを実行する際に与える引数をプログラ
ム中で使うことができる
–
./a.out 1024
•
実行時引数はmain関数の引数として与えられる
–
int main(int argc, char **argv)
•
argc 引数の数
•
argv 引数として与えられたもの一覧
•
注意:第一引数(argv[0])には必ずプログラム名(./a.outなど)が入る
–
例:./a.out size 1024
•
argc = 3
•
argv[0] = “./a.out”
•
argv[1] = “size”
•
argv[2] = “1024”
argvは全て「文字列」として与えられる ため、そのままでは問題サイズなど(数 値)には使えない 2015/9/7-831
•
C言語における文字と文字列の表現方法
–
文字
•
一文字だけを ‘’ (シングルクオーテーション)で括って使う
•
char型、つまり-128~127の整数値と等価(→printfで試そう)
–
文字列
•
0文字以上を “” (ダブルクオーテーション)で括って使う
•
char型の配列、char[N]
•
実は末尾に ¥0 という特殊な値が設定されている(終端文字)ため、配
列の長さとしては見た目の文字列より1つ長くなる
– char str[] = “abc”; とすると、strの長さは4、str[]={‘a’,’b’,’c’,’¥0’}と等価 – charの配列なので、配列自体の代入はできない
» char str[] = “abc”; に対して str = “xyz”; はNG » 文字列操作用の関数群を使う
•
argvがchar**なのは「文字列の配列」であるため
•
文字列に対して操作をするための関数が数多く用意さ
れている(ここでは紹介だけ)
–
コピー strcpy, strncpy など
–
検索
strstr, strchr など
–
出力
putc, puts, putchar など
–
取得
scanf など
•
引数として取得した文字列を数字に変換するにはatoi
が便利
–
int atoi(const char *)
•
char型の配列を渡すとint型の数値を返してくれる、argvから数字を得
る際に有用
•
constが付いているがただのchar*を渡してOK
•
問題:引数に与えられた長さのベクトルを用意して、
各要素の値を2倍にするプログラムを作成せよ。ただ
し、ベクトルの各要素の初期値は配列のインデック
ス値とする。
int main(int argc, char **argv) { int length; int vector[?]; length = atoi(argv[1]); 実行しないと長さがわからない? 長さがわからない配列?? 2015/9/7-8
34
実行時に長さの決まる配列はどうやって
作れば良いのだろうか?
※「十分に長い配列」を用意しておくという別解もあるにはある (十分に長いとはいったいいくつなのか?、あまりにも長すぎるとメモリ が不足して実行できない、コード書き換え再コンパイル……)•
動的な(実行時の)配列の作成にはmalloc関数を使う
– target = (データ型*)malloc(作成サイズ);
– 例
• int *vector; または int vector[];を宣言しておく • vector = (int*)malloc(sizeof(int)*10); – sizeofは対象の長さ(バイト数)を返す関数 • 引数はデータ型、使いたいデータ型を指定する – 作成した時点では中身が不定なので注意が必要(→calloc) – 利用可能なメモリ容量を使い尽くすほど確保しようとするとエラーする
•
不要になったらfree関数で破棄する
– free(vector); 判定のためについでにvector=NULL;することが多い – 破棄せずにプログラムを終えても構わない – 多重malloc/freeや不要なfree/mallocの繰り返しを行わないように注意 • ループ処理に注意:多重はエラー終了する、繰り返しは実行時間が延びる•
引数として受け取った数値分の配列を作るには?
– N=atoi(argv[1])を使ってmalloc(sizeof(データ型)*N)を実行すれば良い int型10個分の場所を確保し、 そのアドレスを受け取った、 と思えば良い 2015/9/7-835
•
行列(二次元配列)の動的確保
1. 二段階malloc
• 行数分だけmallocしたうえで、各要素から列数分だけmallocしなおす2. 大きな領域を確保して使う
• 全要素分のメモリを確保して、別途、列の先頭を指し示すポインタを持つ ※赤→はmalloc、 青→は参照を意味する 2015/9/7-836
•
row行col列の行列を動的に作成する例
2015/9/7-8
37
int **mat;
mat = (int**)malloc(sizeof(int*)*row); for(i=0; i<row; i++){
mat[i] = (int*)malloc(sizeof(int)*col); }
// 行列に対する計算はここで行う for(i=0; i<row; i++){
free(mat[i]); } free(mat); int **mat; mat = (int**)malloc(sizeof(int*)*row); mat[0] = (int*)malloc(sizeof(int)*row*col); for(i=1; i<row; i++){
mat[i] = mat[i-1]+sizeof(int)*col; } // 行列に対する計算はここで行う free(mat[0]); free(mat); • 行数分の列ポインタを作成し、各 行のための領域を個別に確保 • 行をまたいだメモリ(アドレス) が連続にならない(性能が下がる 原因となる)可能性が0ではない • 行数分の列ポインタを作成するのは一緒 だが、全要素分のメモリを一度に確保し、 各行の先頭位置をあわせる • mallocが減った分、freeも減る
•
配列を受け取る関数の作り方
•
引数に[]や*を使えば良い
•
sizeof関数で配列長を取得
できないためlenで長さを
渡すという想定
•
もちろん、関数内で値を
更新すれば呼び出し元の
値も変化する
•
実は int vector[N]に対して
&vector[0]とvectorは
等価である
void funcA(int len, int v[]){ int i;
for(i=0; i<len; i++){ printf(“ %d”, v[i]); }
printf(“¥n”); }
void funcB(int len, int *v){ int i;
for(i=0; i<len; i++){ printf(“ %d”, v[i]); }
printf(“¥n”); }
•
mallocして返すためにはポインタを一段階深
くせねばならない(と覚えておこう)
–
二次元配列の場合もやはりもう一段階深くなる
void my_alloc(int N, int **v){
*v = (int*)malloc(sizeof(int)*N);
}
void my_free(int **v){
free(*v);
}
int *vector = NULL;
my_alloc(10, &vector);
// vectorを用いた操作
for(i=0; i<10; i++)vector[i] = i;
for(i=0; i<10; i++)printf(" %d", vector[i]); printf("¥n");
my_free(&vector);
•
printfとは:指定されたフォーマット(書式)に従って文字列
を出力する関数
–
printf(“フォーマット指定文字列”, 変数, …, 変数);
–
printf(“今日は%d日です¥n”, day);
•
フォーマット指定文字列の使い方と例
–
%<フラグ><最小フィールド幅><精度><長さ修飾子><変換指定文字>
• フラグ:左詰め(-)、正符号付け(+)、ゼロで埋める(0) など • 最小フィールド幅:数値出力幅 • 精度:小数点以下の桁数 • 長さ修飾子:(省略) • 変換指定文字:(次ページ) • “%2d” 整数2文字分以上の幅で出力 • “%-2d” 〃、左詰めされる • “%02d” 〃、1桁の場合は0を付加 • “%+d” 正の値には+が付く • “%2.1f” 整数部分2文字 + 小数1桁を表示 • “%%” %そのものを表示 2015/9/7-840
•
変換指定文字は表示したい変数の型と合わせる必要
がある
–
合わない場合は適当に変換される(予期せぬ出力になるこ
ともあるので注意)
指定文字 意味 対象変数のデータ型 %c 1文字として出力する 文字型 %d 10進数で出力する 整数型 %x 16進数で出力する %o 8進数で出力する %f [-]dddd.ddddddの形式で出力する 浮動小数点型 %e 指数形式で出力する %g %fと%eから自動選択される %s 文字列として出力する 文字列 2015/9/7-841
•
エスケープシーケンス
–
特殊な意味を持つ文字列、いずれも円記号(バック
スラッシュ)で始まる
エスケープシーケンス 意味 ¥n 改行 ¥a 警告音(音が出る) ¥t タブ ¥b バックスペース ¥¥ ¥ ¥’ ‘ ¥” “ ¥0 文字列の終端 2015/9/7-842
•
printfの亜種について
–
printfは書式に従い画面(標準出力)に結果を表示するもの
–
sprintfを使うと結果を文字列に格納できる
•
使い方
sprint(格納先char配列, “フォーマット指定文字列”, 変数, …, 変数);
•
例 char str[0xff];
int i=1, j=2;
sprint(str, “i = %d, j = %d¥n”, i, j);
printf(str);
実行結果:i = 1, j = 2
•
0xffは16進表記で255のこと、格納するのに十分な
長さがあれば良い
•
s
n
printf関数を使うと長さの制限ができて安全
•
実はprintfにはchar配列をそのまま渡しても良い
2015/9/7-843
•
sscanf関数:printfと同様のフォーマット文字列を用いて、逆に文字列か
ら変数への格納を行うことができる
– sscanf(元となる文字列, “フォーマット指定文字列”, 代入先の位置, …); – sscanf(buf, “今日は%d日です”, &day);
– char dofweek[3]; scanf(buf, “今日の曜日は%3sです”, dofweek);
•
フォーマット指定文字列の例
– %<フィールド幅><変換指定文字>
– フィールド幅は変換する文字数か文字列の短い方 – sscanf(“1234”, “%2d”, &a); なら a=12になる
– sscanf(“1234”, “%8d”, &a); なら a=1234になる
– 文字列を受けるときは、代入する変数のサイズ-1を超えないよう必ず指定する こと(終端文字¥0が最後の 1つを埋めるため ) – floatは%fだがdoubleは%lfとなることに注意(printfでは両方%fで良い) 格納先のアドレス(ポインタ)が必要。大雑把には • 変数に格納する場合は&をつける • 配列に格納する場合は&をつけない 2015/9/7-8
44
•
sscanf関数ではなくscanf関数を使うと、変数ではなくキー
ボード入力(標準入力)から入力を受け取ることができる
–
scanf(“フォーマット指定文字列”, 代入先, …);
–
scanf(“%10s”, str); 文字列(10文字分)の取得
–
scanf(“%d”, &value); 整数値の取得
•
変な入力(フォーマットに合わない文字列や長い文字列な
ど)をされてしまった際の処理が難しいなどの問題がある
ため、積極的に使う必要は無い
–
実行時引数やファイルの入出力で十分である
2015/9/7-845
•
基本手順:開いて、読み書きして、閉じる
•
開く:fopen(ファイル名, モード)
–
モード
• r:読み専用 • w:書き専用(上書き) • a:追記 • r+:読み書き • w+:書き読み(上書き) • a+:読み追記–
上書きの場合、開いた瞬間に
ファイルの内容が空っぽになる
–
w, a, w+, a+ ではファイルが存在し
ない場合には自動的に作成される
–
失敗するとNULLが返る
•
閉じる:fclose(ファイルポインタ)
#include <stdio.h> #include <stdlib.h>int main(int argc, char**argv){ FILE *fp;//ファイルポインタ fp = fopen(“filename”, “r”); if(fp == NULL){ perror(“fopen:”); exit(EXIT_FAILURE); } /* ここに読み書き処理 */ fclose(fp); return EXIT_SUCCESS; } 2015/9/7-8
46
• perrorはエラーメッセージの出力 • exitは(関数終了ではなく) プログラム終了処理•
fgets:テキストファイルを行単位で読む
–
第一引数:読み込んだ内容を格納するchar配列
–
第二引数:読み込み先のサイズ
• サイズ-1か改行があるまで読み込む。読めなかった分は次回読む • -1は¥0を自動付加するため。 • 改行も読むので実質行のサイズより2つは大きくとる–
第三引数:fopenから得られたファイルポインタ
–
ファイルを全部読み終わるかエラーするとNULLが返る
•
fscanf:scanf/sscanfと同様だが入力元がファイルポインタ
–
返り値は読み込み個数、終端orエラー時にはEOFが返る
•
頻出事例:fscanfでファイルから要素毎に変数へ代入、また
はfgetsで一行読んでsscanfにより変数へ代入
char s[16];while(fgets(s, 16, fp) != NULL){ sscanf(s,“format”, &value);}
•
fprintf:テキストファイルをフォーマット指定文
字列に従って書き込む
–
第一引数:fopenから得られたファイルポインタ
–
第二引数以降:printfと同じ
–
フォーマット指定文字列不要(変数値なし、テキスト
のみ)ならfputsでも十分
–
その他の入出力関数の例:fread, fwriteなど
fprintf(fp, “format”, value, …);
fputs(文字列, fp);
2015/9/7-8
49
#include <stdio.h> #include <stdlib.h>
int main(int argc, char **argv){ int **mat = NULL;
int i, j, row, col; char tmp[0xff];
FILE *fp;
fp = fopen("data.dat", "r"); fgets(tmp, 0xff, fp);
sscanf(tmp, "%d %d", &row, &col);
printf("%d rows, %d cols¥n", row, col); mat = (int**)malloc(sizeof(int*)*row);
mat[0] = (int*)malloc(sizeof(int)*row*col); for(i=1; i<row; i++){
mat[i] = mat[i-1]+sizeof(int)*col; }
for(i=0; i<row; i++){ for(j=0; j<col; j++){
fscanf(fp, "%d", &mat[i][j]); }
}
fclose(fp);
for(i=0; i<row; i++){ for(j=0; j<col; j++){ printf(" %d", mat[i][j]); } printf("¥n"); } free(mat[0]); free(mat); return 0; } プログラム例(エラー処理をしていない点に注意) 3 4 11 21 31 41 12 22 32 42 13 23 33 43 対象ファイル data.dat の中身 • 一行目:行数 列数 • 二行目から:各行の要素