苦しんで覚えるC言語
苦しんで覚えるC言語を読んでのメモ。
■変数の種類
データ型にはintやdoubleなどがある。[ データ型 ]
数値の種類のこと。サイズや表現形式が異なっている。
[2]実数を記憶する変数
実数(浮動小数点)を扱うにはdouble型を使える。
■型の変換
整数と実数とのかけ算を行うと、結果は実数になる。100*1.05 = 105.000000
なので、キャスト変換を行って整数(int)に変換する。
printf("%d\n",(int)(100*1.05));
// 105
%5d とかで最低5桁にしたりできて、整形しやすい。%05dならあいてる部分を0で埋めてくれる。
■入力用の関数
scanf関数は複数の入力をとることができる。(実用的にはアレな関数ですが)
#include <stdio.h>
int main(void)
{
int min,max,sum;
// 入力部分
printf("min , max > ");
scanf("%d , %d",&min ,&max);
//計算
sum = (min + max) * (max - min - 1) / 2;
printf("%d~%d の合計は %d です。\n",min,max,sum);
return 0;
}
■自作関数を作る
C言語の何か面倒な所。関数を使う前に、関数を明示的に前に出しておく。[ プロトタイプ宣言 ]
あらかじめ先頭で関数の形を宣言しておくことで、
他の全ての関数からその関数を使えるようにすること。
関数の戻り値は1つだけ
■関数内で生き残る変数
static変数は値はプログラムが終了するまで残り、グローバル変数のように自動的に0に初期化される。■文字列を扱う方法
char型の配列と文字リテラルを使えば簡潔に文字列を書く事ができる。
#include <stdio.h>
int main(void)
{
char str[] = "MARIO";
printf("%s\n",str);
return 0;
}
■文字列処理関数
sprintf(結果を記憶する配列,書式文字列,各種変数・・・); で文字列結合とかできるんだ。scanf関数も文字数指定をする事で入力が多くても切り捨てできる。
#include <stdio.h>
int main(void)
{
char str[32];
scanf("%32s",str);
printf("%s\n",str);
return 0;
}
文字配列同士の比較では==演算子を使うことは出来きないので、strcmp関数を使う。変数 = strcmp(文字配列1,文字配列2);
strcmp関数は一致したときに0を返す。
■&つけが必要な変数の正体
関数への引数値渡しの場合はコピーしたものが渡されるので、その関数内で変数を書き換えても元の変数には影響しない
&演算子を使い、参照渡しをすることで、その変数のアドレスを渡して、書き換えを行うと元の変数にも影響を与えられる。
配列を渡す場合は&演算子をつけなくてもよい。それは配列名は配列の最初の要素のアドレスを示しているため。
#include <stdio.h>
int main(void)
{
char str[256];
if(str == &str[0]){
printf("%p と %p は一致する", str ,&str[0]);
}
return 0;
}
■アドレスを記憶する変数
ポインタという呼び方は総称であり、正確には3種類に別れている。1つ目は、ポインタ型です。ポインタ型はintやdoubleのように独立しないで、他の型と組み合わせて使うことができる。
今まで出てきたint型やdouble型と同じような型です。
ただし、ポインタ型の場合、それらとは少し異なる特徴があります。
2つ目は、ポインタ値です。
これは、ポインタ型で扱える数値、要するにアドレスのことです。
整数や実数といった数値の区別と同様に、ポインタ値という区別があるのです。
3つ目は、ポインタ変数です。
これは、ポインタ型で宣言された、ポインタ値を記憶出来る変数のことです。
int型の変数やdouble型の変数と基本的には同じことです。
int型とポインタ型を合体させると、intへのポインタ型
double型の場合なら、doubleへのポインタ型
単独で存在するポインタ型としてvoid型がある。
[4]ポインタ変数
ポインタ変数にはアドレスを値として代入する。
int* p1,p2;
としたとき、p2はただのint型となる。
なので、変数名の前に*をつけた方が自然
int *p1,p2;
ポインタ変数の初期化はヌルポインタを使う。
int *p = NULL;
ポインタ変数は、通常変数モードと、ポインタ変数モードを持っている(デフォルトポインタ変数モード)
ポインタ変数に*をつけると通常モードになるので、*pは、通常変数モードに切り替わったポインタ変数p・
#include <stdio.h>
int main(void)
{
int *p,i;
p = &i;
*p = 10;// 通常変数モードに切り替えて、値の代入
printf("*p = %d\n",*p);// 値の参照も*をつける
printf("i = %d\n",i);// 値参照なので、iも書き換わる
return 1;
}
間接参照演算子 *pのような時の*をそういうらしい。■異なる型の変数をまとめる
構造体について構造体タグと構造体変数の宣言は別。
まずは構造体タグを作ってから、その構造体タグを元に構造体を変数を作る
#include <stdio.h>
#include <string.h>
struct student {// student は 構造体タグ名
int year;
int className;
int number;
char name[64];
double stature;
double weight;
};
int main(void)
{
struct student data;// 構造体変数 data を宣言してつかう
data.year = 10;
printf("%d \n", data.year);
strcpy(data.name, "MARIO");
printf("%s \n", data.name);
return 0;
}
C言語では、新しい型を宣言するtypedef(タイプデフ)がある。
struct student_tag {
int year; /* 学年 */
int clas; /* クラス */
int number; /* 出席番号 */
char name[64]; /* 名前 */
double stature; /* 身長 */
double weight; /* 体重 */
};
typedef struct student_tag student;// student_tagはstudent型になる。
// これは以下でも同じ意味になる。
typedef struct student_tag { // この場合student_tagを省略してもいい。
int year; /* 学年 */
int clas; /* クラス */
int number; /* 出席番号 */
char name[64]; /* 名前 */
double stature; /* 身長 */
double weight; /* 体重 */
} student;
■構造体の引数
構造体は配列のように、関数の引数に渡すこともできる。配列とは違って、先頭のアドレスが渡されるのではなく、すべての値をコピーした構造体としてわたされる。
#include <stdio.h>
#include <string.h>
typedef struct {
int year; /* 学年 */
int clas; /* クラス */
int number; /* 出席番号 */
char name[64]; /* 名前 */
double stature; /* 身長 */
double weight; /* 体重 */
} student;
void student_print(student data);
int main(void)
{
student data;
data.year = 3;
data.clas = 4;
data.number = 18;
strcpy(data.name,"MARIO");
data.stature = 168.2;
data.weight = 72.4;
student_print(data); /* 呼び出し */
return 0;
}
void student_print(student data)// dataの中身はコピーされてわたされる
{
printf("[学年]:%d\n",data.year);
printf("[クラス]:%d\n",data.clas);
printf("[出席番号]:%d\n",data.number);
printf("[名前]:%s\n",data.name);
printf("[身長]:%f\n",data.stature);
printf("[体重]:%f\n",data.weight);
return;
}
構造体の中にある配列もコピーされる。[2]構造体でもポインタ変数
構造体型のポインタ変数も作ることができる。その場合に(*構造体ポインタ変数名).要素名という感じで要素にアクセスできるけど、面倒くさい。
代わりに構造体ポインタ変数名->要素名という記法を使うことで簡潔に書くことができる。
#include <stdio.h>
#include <string.h>
typedef struct {
int year; /* 学年 */
int clas; /* クラス */
int number; /* 出席番号 */
char name[64]; /* 名前 */
double stature; /* 身長 */
double weight; /* 体重 */
} student;
int main(void){
student data;
student *pdata;
pdata = &data;
(*pdata).year = 10;
strcpy(pdata->name , "MARIO");// -> は *の代わりに使える
printf("%s is %d years old.", pdata->name, pdata->year);
return 0;
}
構造体変数を関数に渡すと、内容がすべてコピーして渡されるという事になっているため、コピーせずに直接構造体変数のアドレスを渡すことで高速化を行う事ができる。
また、呼び出し関数先で構造体変数の要素を書き換えるなどもできる。
■構造体の配列
構造体の配列student data[10]; // student型のdata配列
構造体配列なら、以下で同じ意味になるとか
(*data).year data->year data[0].year構造体配列を関数に渡すときは配列を渡すのと同じで、先頭のアドレスが渡される(コピーされない)
■テキストファイルの読み書き
C言語でファイルの読み書きはFILE型のポインタ変数を使ってやる
#include <stdio.h>
int main(void){
char str[100];
FILE *file;// FILE型 = 構造体が用意されてる
// 書き込み
file = fopen("test.txt", "w");//書き込み。ファイルがあっても空のファイルを作る
fprintf(file, "HELLOW WORLD");
fclose(file);
// 読み込み
file = fopen("test.txt" , "r");
fscanf(file, "%s", &str);// スペースまでしか読み込めない
printf("%s\n", str);// HELLOW
return 0;
}
バイナリの場合はモード文字列の最後にbをつけることで行える。■ドラッグへの対応
C言語で書かれたプログラムでコマンドライン引数を受け取るにはmain関数に受け取る引数を用意する。int main(int argc,char *argv[])ちなみにQuickRun.vimだと:Quickrun -args "hoge" で引数を渡して実行できる。
■不変の値の取り扱い
定数の書き方- #defineを使う
- const定数を使って宣言
- enum定数
フラグとかに便利。
#include <stdio.h>
int main(int argc,char *argv[])
{
enum {
STATE_NORMAL, // 0
STATE_SUCCESS,// 1
STATE_ERROR // 2
};
printf("NORMAL : %d\nSUCCESS : %d\nERROR : %d\n", STATE_NORMAL,STATE_SUCCESS,STATE_ERROR);
return 0;
}
また、key = value形式で書くことで任意の定数をつけることも可能
enum {
ENUM_0,
ENUM_1,
ENUM_5 = 5,
ENUM_6,
ENUM_7,
ENUM_9 = 9,
};
0,1,5,6,7,9という定数になる■配列を自由自在に作る
C言語では配列は要素数を動的に変更できないため、malloc(エムアロック)関数を使う。ポインタ変数 = malloc(必要なメモリのバイトサイズ);
メモリを確保できなかった場合は NULL が返る。
なので、nullチェックを行ってから使う。
malloc関数で確保したメモリを、ヒープと呼び、これにより確保された配列を、動的配列と呼ぶ。
[ ヒープ ]
長期的に使用される大きなサイズのメモリを格納する領域。
[ 動的配列 ]
malloc関数などを使用して、
プログラムの実行中に用意された任意のサイズの配列。
malloc関数によって確保されたメモリはプログラムが終了するまで残るので、
使わなくなった時点でfree関数を使ってメモリを解放する必要がある。(忘れるとメモリリークになりがち)
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int i;
int *heap;
heap = (int *)malloc(sizeof(int) * 10); // int * 10分のメモリを確保
// malloc関数はvoid型ポインタを返すのでint型ポインタにキャストする
if(heap == NULL) exit(0);
for(i=0;i<10;i++){
heap[i] = i;
}
printf("%d\n",heap[5]);
free(heap);
return 0;
}
この例だと別に配列でもいいけど、次用のように動的配列を拡張する際にmallocは必要。要素数を変更する、realloc(リアロック)関数を使う。
どちらにしても細かくメモリを確保するとフラグメンテーションが起きやすくなるので、ある程度の塊でメモリを確保するのがよいらしい。
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int i;
int *heap;
heap = (int *)malloc(sizeof(int) * 10); // int * 10分のメモリを確保
// malloc関数はvoid型ポインタを返すのでint型ポインタにキャストする
heap = (int *)realloc(heap, sizeof(int) * 100);// 確保し直す
free(heap);
return 0;
}
■分割の定石
変数や関数の宣言を行うと、コンパイラがその名前と形を記憶します。プロトタイプ宣言は宣言だけを行っていたので、定義は別になっていた。
これが、宣言と呼ばれる機能です。
そして、同時に、コンパイラは実際に変数や関数を作成します。
これが、定義と呼ばれる機能です。
[ extern宣言 ]
externを使用して宣言だけを行い定義は行わない宣言方法。
/* sum.h */ extern int sum(int min,int max); extern int Public; /* 変数のextern宣言 */ // まだ宣言されただけなので、Publicを別途定義する必要がある。 /* sum.c */ int Public; /* 変数の実体の作成 */これで、変数Publicは main.c からも sum.cも利用できるようになる。
(グローバルのグローバルみたいな宣言方法)
[3]ヘッダーファイルの重複防ぎ
#ifndef~#endif疑似命令を使って、重複宣言を防ぐ
/* sum.h */ #ifndef _INCLUDE_SUM_ #define _INCLUDE_SUM_ int sum(int min,int max); #endif
コメント(0件)
- TB-URL http://efcl.info/adiary/0122/tb/