はじめに
久々にC言語で開発ってなった時に忘れていたことをメモします(基礎的なものばかりです)
String(文字列)
char型変数の宣言
C言語でchar型を宣言すると、1文字ずつの文字列がarrayとして格納されるイメージ。
char str[6] = "hello";
は実質6文字となる。文字列の最後がNULL
埋めされるため。
上記のような宣言の場合、コンパイラーがNULL埋めしてくれる。
下記のように宣言しても同じ。
char str[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
重要なのは、宣言したい変数の長さは、NULL
を考慮し最低+1の長さを指定しておく必要があること。
Tipsとしては、実は変数の長さを指定せずに変数宣言をしたとしても自動的に文字列+NULLで長さを決めてくれる。
char str[ ] = "world"; // size 6
C言語char型の罠
忘れてはいけないのが、C言語ではchar型array変数を定義するとポインターを作ったのと同じになる。
ここさえマスターすればC言語はなんとかいけるはず。
char a; // メモリサイズ:1byte
char *b; // メモリサイズのわかっていないchar型の配列
char c[16]; // メモリサイズ:16byteの配列
b = a; // NG : アドレスに実体を代入しているため
b = &a; // OK : 実体aのアドレスを代入しているため
b = c; // OK : 同一の型なので、OK。しかし渡しているのはcの先頭アドレス
b = &c; // NG : 'char *'型に 'char (*)[4]'を代入している
b = &c[0]; // OK : cの先頭インデックスのアドレスを代入
自身が一番わかりにくいと思ったのがb = &c
の例。
下記はincompatible pointer typeと言われる。
int main(){
func_a{char *a){
printf("s",a);
}
char a[4] = "aaa";
func_a(&a);
return 0;
}
こうだとOK。char a arrayのアドレスを渡しているので。
追記。C言語のこの仕様はほぼおまじない。
実際は関数受け側は*
はとしてポインタになっているが、実際はchar a[4]の実体を渡しているということ。
int main(){
func_a{char *a){
printf("s",a);
}
char a[4] = "aaa";
func_a(a);
return 0;
}
char型arrayの関数渡し
これはさらに混乱する仕様。完璧には理解できていないので、ご指摘いただけると助かります
二次元の場合でindexごとに値を変更したい場合は下記。
-
&foo[i]
とすることでindexのアドレスを関数に渡す
もし下記コードのようにfunc_a
’内にてchar型arrayを変更し、
func_a
以降でfooを使用したい場合。
- 関数呼び出し時は&でアドレス値を渡す(&をつける場合はindex指定のときだけ。)
- 関数の受け側(func_a)はchar**でfooを受け取る必要がある (関数呼び出し元のアドレスを返す必要があるため。
*で関数の受け側で変数を受け取っても、別アドレスを指してしまう。そのため、関数呼び出し元の後処理で変更後の変数を使えない) - 関数内でfooを変更する場合はchar*にする(関数で受け取った実体の値を変更する。返却するアドレスは
**
になっているので関数呼び出し元のアドレスを返却できる)
int func_a(char**); //ダブルポインターで宣言する必要あり
//func_bのprototypeは省略
int main() {
char foo[4][4];
for(int i=0; i<4; i++) {
//func_aでfooの値をmodifyする
return_code = func_a(&foo[i]);
//以降でfooを使用する
func_b(&foo[i]);
}
}
int func_a(char** array) {
//process
//*にすることでfooの実体を書き換えることができる
memcpy(*array, str, sizeof(array));
return return_code;
}
便利な文字列操作の標準関数
#include <string.h>
strlen(str) // strの長さを取得 (NULL文字はカウントしない)
strcat(str1,str2) // 2つの文字列を結合
strcpy(str1,str2) // str2をstr1にコピー
strlwr(str) // 小文字に変換
strupr(str) // 大文字に変換
strrev(str) // 文字列を逆に並び替える
strcmp(str1,str2) // 2つの文字列を比較、一致した場合は0を返す
strncat(str1,str2,n) // str2のn文字数str1にセット
strncpy(str1,str2,n) // str2のn文字数str1にコピー
strncmp(str1,str2,n) // str2のn文字数をstr1と比較、一致した場合は0を返す
strchr(str,c) // cの文字列がstrにあるかチェック、見つかった時点でのポインタを返す
strrchr(str,c) // cの文字列をstrの逆から検索、見つかった時点でのポインタを返す
strstr(str1,str2) // str2の文字列がstr1にあるかチェック、見つかった時点でのポインタを返す
##sprintf() と sscanf()
###sprintf()
sprintf
は異なる型の変数をひとまとめにしたい時に便利。
char info[100];
char name[] = "foo";
int age = 10;
sprintf(info, "%s is %d years old.", name, age);
printf("%s\n", info); //foo is 10 years old
###sscanf()
sscanf
は1つの文字列の変数からデータをバラしたい時に便利。
char info[ ] = "Jan 1st 2020";
char birth_month[10];
char birth_day[10];
int birth_year;
sscanf(info, "%s %s %d", birth_month, birth_day, &birth_year);
printf("I was born on %d, %s, %s.", birth_month, birth_day, birth_year); //I was born on Jan, 1st, 2020
Array(配列)
二次元配列
よく忘れるけど最初の[]が配列数、そのあとが長さ。
char test[3][5] = {
"abc",
"def",
"ghi"
};
長さ指定が面倒な時は下記。
char *test[] = {
"abc",
"def",
"ghi"
};
値の取得は下記の通り。
printf("%s\n", test[0]); //abc
printf("%s\n", test[1]); //def
printf("%s\n", test[2]); //ghi
配列要素数の求め方
int array = [10,20,30,40,50];
int sz = sizeof(array) / sizeof(array[0];
printf("size: %d",sz); //size:5 (int型は4byte, (4*5)/4となる)
sizeof(array)
が配列全体のサイズ。sizeof(array[0]
が配列1要素分の大きさ
Function Pointer
ポインタに関数を持たせることもできる。
#include <stdio.h>
void multiply(int num1,int num2); // function
int main() {
void (*funptr)(int, int); // function pointer
funptr = multiply; // pointer assignment
funptr(3,3); // function call
return 0;
}
void multiply(int num1,int num2) {
printf("result:%d\n", num1*num2); // result:9
}
void (*funptr)(int, int) = multiply
としても実装できる。
funcptr = &multiply
でももちろんOK。
arrayとして関数ポインタを実装させることもできる。
sum, subtract, multiply, divideの4関数が宣言されている前提とする。4関数はint2つをパラメーターとし、intをreturnする。
int result
int (*arrayptr[4])(int,int);
arrayptr[0] = sum;
arrayptr[1] = subtract;
arrayptr[2] = multiply;
arrayptr[3] = divide;
for (i=0; i<4; i++){
result = arrayptr[i](3,3);
printf("result:%d",result);
// result:6
// result:0
// result:9
// result:1
}
Structure(構造体)
typedef
よく、これなんで使われてる?ってなるんですが、これが無いと構造体の変数を宣言する際に
struct foo st1;
ってしないといけないところを、foo st1;
だけで宣言できるという利点があります。
typedef struct {
int a;
char b[40];
float c;
} foo;
foo st1;
foo st2;
あとはtypedef
で宣言された構造体を違う構造体にメンバとして入れちゃうこともできます。
typedef struct {
int x;
int y;
} point;
typedef struct {
float radius;
point center;
} circle;
int main (){
circle c1 {1.1, 1,2} //メンバ初期化。イメージ的にはこう->{1.1 {1,2}}
printf("%f","%d", "%d", c1.radius,c1.x,c1.y); //1.100000, 1,2
return 0;
}
Union (共用体)
Structureと何が違うの?って疑問が一番最初にきます。
メモリの使い方が違います。
共用体は全てのメンバのアドレスが同じになります。(構造体はそれぞれのメンバのアドレスは異なります。)
そのため、共用体のあるメンバ変数に値がアサインされた時、その時のメモリロケーションは他メンバ変数に値がアサインされるまで保持されます。
下記サンプルコードであれば、i_num
とf_num
のprintf
は意味を成しません。(最後のstr
のアサインによって前のアサインが上書きされているため)
union uni {
int i_num;
float f_num;
char str[10];
};
union uni test;
test.i_num = 123;
test.f_num = 12.34;
strcpy(test.str, "foo");
printf("%d\n", test.i_num); // garbage data
printf("%f\n", test.f_num); // garbage data
printf("%s\n", test.str);
メモリマネージメント
C言語には、必要なんですよね。。。
サンプルコードは、メモリ確保した後、free()
をしていません。(怠惰です、すみません)
これはダメです!メモリ確保後は必ず確保したメモリを解放すること
malloc
#include <stdlib.h>
を記述することで使用できます。
メモリ上に指定したサイズ分を確保する変数です。
#include <stdlib.h>
int *ptr;
ptr = malloc(5 * sizeof(*ptr)); // int=4byte分のメモリブロックを5つ作成する
if (ptr != NULL) {
*(ptr + 2) = 50; // 3つめのintブロックに値をアサインする
}
free
確保したメモリを解放する。
free(ptr);
##calloc
構造体におけるメモリ確保に使うことが多いっぽい。何メモリブロック作るのかの指定が可能。
typedef struct {
int num;
char *test;
} sample;
sample *samp;
int block_num = 3;
int i;
char str[ ] = "abc def ghi";
samp = calloc(block_num, sizeof(sample)); // Sample構造体のポインターsampにsample構造体3ブロック分のメモリ確保
if (samp != NULL) {
for (i = 0; i < block_num; i++) {
(samp+i)->num = i; // それぞれのblockの構造体メンバnumにiの値をアサイン
(samp+i)->test = malloc(sizeof(str)); // 構造体メンバのchar型testポインターにstr変数メモリ分のメモリを確保
strcpy((samp+i)->test, str); // それぞれのblockの構造体メンバtestにstrの値をアサイン
}
}
realloc
malloc
で作ったメモリスペースを拡張、修正したいときに使用
#include <stdlib.h>
int *ptr;
ptr = malloc(5 * sizeof(*ptr)); // int=4byte分のメモリブロックを5つ作成する
if (ptr != NULL) {
*(ptr + 2) = 50; // 3つめのintブロックに値をアサインする
}
ptr = realloc(20 * sizeof(*ptr)); // やっぱりint=4byte分のメモリブロックを20個作成する
*(ptr + 15) = 100;
文字列のメモリ確保について
malloc
時、sizeof
は使用せずにstrlen
を使う。(文字列の長さ+1するの忘れそう。。)
char str[20];
char *pstr = NULL;
strcpy(str, "hello");
pstr = malloc(strlen(str) + 1); // +1することを忘れずに。。
strcpy(pstr, str);
printf("%s", pstr); // hello
exit関数
C言語は公式には例外処理をサポートしていない。
そのため時々exit
関数が使われる。
EXIT_SUCCESS
およびEXIT_FAILURE
はstdlib.h
をincludeすることで使用できる。
#include <stdio.h>
#include <stdlib.h>
int main() {
int i1 = 1;
int i2 = 0;
if (i2 != 0)
printf("i1 / i2 = %d", i1/i2);
else {
printf("One of the values is 0. Program exiting.");
exit(EXIT_FAILURE);
}
return 0;
}
Include
<>
で囲めばライブラリの中のheaderファイルの定義。
""
で囲めばユーザー定義のheaderファイルの定義。
#include <stdio.h>
#include “custom_util.h”
##Predefined Macro definitions
char curr_time[10];
char curr_date[12];
strcpy(curr_time, __TIME__); // 現在時刻をstringで返却
strcpy(curr_date, __DATE__); // 現在日時をstringで返却
printf("%s %s\n", curr_time, curr_date);
printf("This is line %d\n", __LINE__); // 行数をintで返却