初めに
今回はC言語の配列と関数を少しまとめていきたいと思います。
(英語の部分は基本的に映像から取り上げたもので、JavaScriptの勉強からC言語へ移行するので解釈のときちょこっとC言語ではあまり使われてない言葉が出てくるかもしれない。)
(文章では映像や参考リンクから出た知らない言葉がたくさんあったので、いろいろと調べたうえで自分なりに解釈している部分があります。)
week2
変数とそのスコープ(Variables and Scope)
- local variable(ローカル変数):既定の範囲内だけアクセスできる。(例えば関数内に宣言されたローカル変数、関数内ならアクセスできるが、関数外ではできません。)
- global variable(グローバル変数):プログラムの中、どの関数からでもアクセスできる。
- 変数は宣言されるところが自分のスコープを決める。
#include <stdio.h>
int triple(int x);
int main(void)
{
// "result" is local variable
int result = triple(5); // caller
printf("result is %i\n", result);
return 0;
}
// "x" is local variable
int triple(int x) // callee
{
return x * 3;
}
// result is 15
- caller(呼び出し元):この例だと
main。 - callee(呼び出し先):この例だと
triple -
passed by value:C言語ではある関数が引数を取り入れるというのは、実際引数(変数)の値をコピーしてからその値を関数パラメータ(仮引数)に付与(assign)する。
- なので呼び出し元のローカル変数の値は新しい値で再び付与する、リアサイン(reassign)するまで値は変えられない。
// global is global variable
void triple(void);
float global = 0.5050;
int main(void)
{
triple(); // caller
printf("%f\n", global);
return 0;
}
void triple(void) // callee
{
global *= 3;
}
// 1.515000
グローバル変数はどこからできアクセスできる。void triple(void)のようにパラメータを通してコピーした値を代入(assign)するのではなく、変数に持っている値を直接操作したため、計算の結果をそのままグローバル変数globalに再代入(reassign)したのでglobalが保有している値は1.515000へ変えられました。
void triple(float local);
float global = 0.5050;
int main(void)
{
triple(global);
printf("global is %f\n", global);
return 0;
}
void triple(float local)
{
local *= 3;
printf("local is %f\n", local);
}
// local is 1.515000
// global is 0.505000
これでノーチェンジです!
下はちょっと別の例です。
int triple(int x);
int main(void)
{
int foo = 4;
// copy foo's value and put it to triple
printf("triple(foo) is %i\n", triple(foo));
printf("foo is %i\n", foo);
}
int triple(int x)
{
return x *= 3;
}
// triple(foo) is 12
// foo is 4
上の例ではfooはmainのスコープに生存するローカル変数で、引数として関数に入れると値だけtripleのxに代入する。foo変数に再代入するわけではないのでfooはそのまま4です。
int main(void)
{
int foo = 4;
// the right-hand side will execute first, and then put the value to foo
foo = triple(foo); // reassign
printf("foo is %i\n", foo);
}
int triple(int x)
{
return x *= 3;
}
// foo is 12
foo = triple(foo)が成立できるのは、常に=の右手側から計算が済んでから左手側に代入していく。なのでfoo = triple(foo)の行では変数fooが12になり、下のprintf("foo is %i\n", foo)に行くとfoo is 12が出力されました。
int increment(int x);
int main(void)
{
int x = 1;
int y;
y = increment(x);
printf("x is %i, y is %i\n", x, y);
}
int increment(int x)
{
x++;
return x;
}
// x is 1, y is 2
配列(Array)
- データ(数字/文字/文字列)を一連のメモリーアドレスを格納する構造。
- 一つのインデックス(index)は一つの要素に指している。
- 要素は文字なら配列のインデックスか、ポインタでアクセスするが、文字列なら配列のポインタで参照先をアクセスする。(配列が個々の文字列の配列を格納して、ポインタを通してアクセスする。)
// one string in array
#include <stdio.h>
#include <string.h>
int main(void)
{
char arr[] = {"abcd"};
int length = strlen(arr);
printf("One string array Elements are:\n");
for (int i = 0; i < length; i++)
{
// arr[i] is character
printf("%c\n", arr[i]);
}
printf("Length of %s is %d\n", arr, length);
return 0;
}
// One string array Elements are :
// a
// b
// c
// d
// Length of abcd is 4
// integer in array
#include <stdio.h>
int main(void)
{
int arr[] = {1, 2, 3, 4};
int length = sizeof(arr) / sizeof(arr[0]);
printf("Integer array Elements are:\n");
for (int i = 0; i < length; i++)
{
// arr[i] is integer
printf("%i\n", arr[i]);
}
printf("Length of integer array is %d\n", length);
return 0;
}
// Integer array Elements are:
// 1
// 2
// 3
// 4
// Length of integer array is 4
// Multiple string in array
#include <stdio.h>
int main(void)
{
// pointer
char *arr[] = {"Geek",
"Geeks",
"Geekfor"};
int length = sizeof(arr) / sizeof(arr[0]);
printf("Multiple string array Elements are:\n");
for (int i = 0; i < length; i++)
{
printf("%s\n", arr[i]);
}
printf("Length of multiple string array is %d\n", length);
printf("First element in *arr is %s\n", *arr);
return 0;
}
// Multiple string array Elements are : Geek
// Geeks
// Geekfor
// Length of multiple string array is 3
// First element in *arr is Geek
- 文字列の最後は
\0NUL文字列(nul-terminated string、文字列の終端を示す特殊文字)で括る。-
(void*) NULLはNULLポインタ定数の一つで、空((void*)NULL == (void*)0)を表し、どこのアドレスも参照していない状態です。整数0ではない。
-
// /0
#include <stdio.h>
int main(void)
{
char str[50];
printf("Input string: ");
scanf("%s", str); // array don't need "&"
int n = 0;
while (str[n] != '\0')
{
n++;
}
printf("%i\n", n);
return 0;
}
// Input string: abcdef
// 6
// check if a string is empty with length
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = ""; // initialize
printf("Input string: ");
scanf("%s", str);
int length = strlen(str);
if (length != 0)
{
for (int i = 0; i < length; i++)
{
printf("%c", str[i]);
}
printf("\n");
}
return 0;
}
// Input string: abc
// abc
- passed by reference。関数に配列を渡すとき、値ではなくアドレスの格納先(配列)を渡すことで、アドレスの参照を経由して値にアクセスする。なので要素を操作すると元の配列が変えられてしまう。
#include <stdio.h>
int set_int(int x);
void set_array(int array[4]);
int main(void)
{
int a = 10;
int b[4] = {0, 1, 2, 3};
set_int(a); // here doesn't set any variable to store return value
set_array(b);
printf("a is %d, b[0] is %d\n", a, b[0]);
return 0;
}
int set_int(int x)
{
x = 22;
return x;
}
void set_array(int array[4])
{
array[0] = 20;
}
// a is 10, b[0] is 20
関数(Function)
手続き(procedure)、方法(method)などプログラミング言語によって呼び方が様々だけど、関数(function)は一番よく使われると思います。
(自分の言葉で解釈するなら、問題を解決する最小単位です。)
#include <cs50.h>
#include <stdio.h>
// prototype declaration
// return-type function-name(parameter-declaration)
void print_name(string name);
int main(void)
{
// there is no data type called "string" in C
// data type "string" is provided from cs50 IDE
string s = get_string("Input something: "); // cs50 library
// call function
print_name(s); // callee
// in function "main", we don't have to return value, even it takes return type explicitly
// but in other function, we should return value to make sure it's legal
return 0;
}
// variable "name" is declared in function "print_name",
// which means "name" is a local variable only can be used in "print_name"
void print_name(string name) // caller
{
printf("hello, %s\n", name);
}
// Input something: abc
// hello, abc
(C言語ではstringというデータタイプがありません。ここstring s = get_stringでは、stringはCS50レクチャーが提供するIDE環境しか扱えません。C言語は文字列をchar型の配列にしてから扱います。)
- prototype declaration(プロトタイプ宣言):コンパイラにこれから使う関数を先に明示する。
- return-type function-name(parameter-declaration)
-
void fun1(int):(返り値なし・引数あり<整数>) -
int fun2(void):(返り値あり<整数>・引数なし) -
int fun3(int):(返り値あり<整数>・引数あり<整数>) -
void fun4(void):(返り値なし・引数なし) - parameter:仮引数。(関数宣言時に、関数内で使う引数をローカル変数として宣言する。)
- argument:実引数。(実際関数にインプットする値。)
-
return 0:0を返すことで実質上プログラムを終了する。- 返り値が求められる関数は基本的に
return ...を書く必要がある。mainだけはこのルールに従わなくても問題なくコンパイルできるが、returnを書くことで確実にプログラムを終了します。
- 返り値が求められる関数は基本的に
-
Example practices
#include <cs50.h>
#include <stdio.h>
int square(int n);
int main(void)
{
int x = get_int("When you're using get_... function, be sure to write string prompt: ");
printf("x^2 is %i\n", square(x)); // put input
return 0;
}
int square(int n)
{
return n * n; // get output
}
// When you're using get_... function, be sure to write string prompt: 5
// x^2 is 25
void cough(int n);
int main(void)
{
cough(3);
return 0;
}
void cough(int n)
{
for (int i = 0; i < n; i++)
{
printf("cough\n");
}
}
// cough
// cough
// cough
void say(string s, int n);
int main(void)
{
// string str = get_string("Give me any word: ");
// int num = get_int("Repeat times?: ");
// say(str, num);
say(get_string("Give me any word: "), get_int("Repeat times?: "));
return 0;
}
void say(string s, int n)
{
for (int i = 0; i < n; i++)
{
printf("%s\n", s);
}
}
// Give me any word: cough
// Repeat times?: 2
// cough
// cough
stringではなくchar型で試してみました。
void say(char arr[], int n);
int main(void)
{
char str1[] = "Hello, world!";
// printf("%s\n", str1);
// sizeof(str1) to get array's size
// sizeof(str1[0]) to take one element as divisor
say(str1, sizeof(str1) / sizeof(str1[0]));
return 0;
}
void say(char arr[], int length)
{
for (int i = 0; i < length; i++)
{
printf("%c", arr[i]);
}
printf("\n");
}
// Hello, world!
意外と苦戦しました。stringのイテレータではまず文字列を配列にして、配列の長さも計算してから関数に入れてやっと思う通り出力する...。(たぶんほかのヘッダーファイルやライブラリの関数なら手早くできるけど、とりあえずループで試してみた。)
#include <stdio.h>
#include <stdbool.h>
bool valid_triangle(int x, int y, int z);
int main(void)
{
int x, y, z;
printf("This is a trianlge valid test, please input length of three sides: \n");
scanf("%i", &x);
scanf("%i", &y);
scanf("%i", &z);
bool result = valid_triangle(x, y, z);
printf("result is %s\n", result ? "true" : "false");
return 0;
}
bool valid_triangle(int x, int y, int z)
{
if (x <= 0 || y <= 0 || z <= 0)
{
return false;
}
if ((x + y <= z) || (x + z <= y) || (y + z <= x))
{
return false;
}
return true;
}
// This is a trianlge valid test, please input length of three sides:
// 2
// 3
// 4
// result is true
Memo
ctype.h
-
int isalnum(int c):alphanumeric(英数字A-Z/a-z/0-9)のチェック。 -
int isalpha(int c):alphabetical(アルファベットA-Z/a-z)。 -
int isdigit(int c):digit(十進数数字0-9)。 -
int islower(int c):lower case(英小文字a-z)。 -
int isspace(int c):space(空白'',\f,\n,\r,\t,\v)。 -
int isupper(int c):upper case(英大文字A-Z)。
以上はtrue⇒0以外の値を返す/false⇒0を返す。
-
int tolower(int c):A-Z⇒a-z。英大文字以外そのまま返す。 -
int toupper(int c):a-z⇒A-Z。英小文字以外そのまま返す。
math.h
-
double ceil(double x):小数点切り上げ。-
float ceilf(float x); -
long double ceill(long double x);
-
-
double floor(double x):小数点切り捨て。-
float floorf(float x); -
long double floorl(long double x);
-
-
double log2(double x):2を底とする対数。二進対数。-
float log2f(float x); -
long double log2l(long double x);
-
-
double pow(double x, double y):xのy乗を返す。-
float powf(float x, float y); -
long double powl(long double x, long double y);
-
-
double round(double x):小数点以下四捨五入。-
float roundf(float x); -
long double roundl(long double x);
-
-
double sqrt(double x):xの平方根を求める。-
float sqrtf(float x); -
long double sqrtl(long double x);
-
stdio.h
-
int printf(const char *format, ...):標準出力へ書き込む関数。 -
int scanf(const char *format, ...):標準入力(stdio.h)から値を読み取ることができる。
stdlib.h
数値変換関数:
-
double atof(const char *nptr):pointer型変数nptrが指しているアドレスに、最初の部分(文字列)をdouble型へ変換する。 -
int atoi(const char *nptr):(i⇒int)。int型へ変換する。 -
long atol(const char *nptr):(l⇒long)。long int型へ変換する。
記憶域管理関数:
-
void free(void *ptr);:pointer型変数ptrが指しているアドレスの領域を解放し、ほかの割り付けに使用できるようにする関数。 -
void *malloc(size_t size):(m⇒memory, alloc⇒allocate)。size_t型変数sizeにより、指定されたサイズのオブジェクトの領域を確保する。 -
void *realloc(void *ptr, size_t size):(realloc⇒reallocate)。pointer型変数ptrが指している古いアドレスのデータをコピーし、size_t型変数sizeにより新しいサイズのアドレスへ記憶させる。- 古いアドレスのサイズの大きさ>新しいアドレスのサイズの大きさ⇒古いアドレスを返す。
- 古いアドレスのサイズの大きさ<新しいアドレスのサイズの大きさ⇒古いアドレスが解放され、新しいアドレスのサイズの一部として取り入れられ、新しいアドレスを返す。
疑似乱数列生成関数:
-
void srand(unsigned int seed):(s⇒seed, rand⇒random)。rand()の呼び出しで返す新しい疑似乱数列の種seedとして使用する関数。 -
int rand(void):(rand⇒random)。0以上RAND_MAX以下範囲の疑似乱数整数列を計算する関数。-
srand((unsigned int)time(NULL)):time(NULL)は現在の時間(秒)を返すことで、void srand(unsigned int seed)正数整数型の変数seedとして入れる。プログラムが毎回実行するとき現在時刻によって新しい疑似乱数列を生成する。 - これがないとコンパイルは一回だけなので既定の種がプログラムに記憶され、
rand()はいつも同じ種で疑似乱数を計算していくなので、本当の乱数とは言えません。 -
srand((unsigned int)time(NULL))は種を投入するためrand()より後に呼び出されてはいけません。rand()が先に記憶されている同じ種で計算してしまいます。
-
*:pointer型。
ptr: pointerの略語。あるアドレスを記憶(格納)する変数のこと。
**nptr: node of pointersの略語。
(const char *nptr), (const char *str): char array。文字列のこと。
size_t:size_t型。sizeof演算子の結果の符号なし整数型です。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[] = "123456789";
int x = atoi(str);
printf("string %s have been converted to integer %d\n", str, x);
return 0;
}
// string 123456789 have been converted to integer 123456789
int main(void)
{
int x = atoi("123456789");
printf("value of x is integer %d\n", x);
return 0;
}
// value of x is integer 123456789
int main(void)
{
char str[] = "abc";
int x = atoi(str);
printf("string %s have been converted to integer %d\n", str, x);
return 0;
}
// string abc have been converted to integer 0
string.h
-
int strcmp(const char *s1, const char *s2):(cmp⇒compare)。二つの文字列を比較する。 -
char *strcpy(char * restrict s1, const char * restrict s2):(cpy⇒copy)。文字列s2の値をコピーしてs1に。 -
size_t strlen(const char *s):(len⇒length)。変数sのいるアドレス(配列)の要素数を返す。つまり文字列の長さを返す。 -
char *strstr(const char *s1, const char *s2):文字列s1の中で文字列s2が現れる最初の位置を探し、そのポインタを返す。
time.h
-
time_t time(time_t *timer):時間を表現するための関数。-
time(NULL):現在の日付と時刻を秒単位で返す。
-
Practices
getchar()の練習です。
#include <stdio.h>
#include <ctype.h> // character type?
// getchar()
int main(void)
{
printf("Please input something: ");
int c = getchar();
printf("Your input is %c\n", c);
return 0;
}
// Please input something : ABC
// Your input is A
もともとgetchar()とisalpha()、isdigit()、islower()、isupper()と合わせて練習しようとしていたが、ランダムのASCIIコードにより生成された文字に条件式通して検証を行おうと思い、書いてみました。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
void printRandoms(int min, int max, int count);
int main(void)
{
// ASCII code
int start = 32; // ''
int end = 126; // ~
int count;
printf("Input count: ");
scanf("%i", &count);
printRandoms(start, end, count);
return 0;
}
void printRandoms(int min, int max, int count)
{
srand((unsigned)time(NULL));
for (int i = 0; i < count; i++)
{
int num = (rand() % (max - min + 1) + min);
// printf("%d\n", num);
// printf("%c\n", num);
// char c = num;
// int isalnum(int c) / int isalnum(char c)
if (isalnum(num))
{
printf("%c is alphanumeric\n", num);
}
}
}
// Input count: 5
// B is alphanumeric
// u is alphanumeric
// K is alphanumeric
この練習で初めて疑似乱数と種と乱数の関連が知りました。(stdlib.hのところにまとめました。)思ったよりずっと難しかったです。何となく数学の基礎概念を一定以上求められているように思ってしまいます。
型指定(Typecasting)
明確にインプットのタイプを指定して変換する。
#include <stdio.h>
int main(void)
{
for (int i = 65; i < 65 + 26; i++)
{
// explicit cast
printf("%c is %i\n", (char)i, i);
// %c, %i => output type
}
return 0;
}
アウトプット(またはデフォルト設置)に従い暗黙に変換する。
#include <stdio.h>
int main(void)
{
for (int i = 65; i < 65 + 26; i++)
{
// implicit cast
printf("%c is %i\n", i, i);
// %c, %i => output type
}
return 0;
}
int main(void)
{
// explicit cast a data type by declaration
// c++ => convert "char c" to integer, then implement the increment
for (char c = 'A'; c <= 'Z'; c++)
{
printf("%c is %i\n", c, (int)c);
}
return 0;
}
型変換(Type conversion)
-
Conversion Rank
- 1バイト⇒4バイト⇒8bitバイト。型変換は一般的に小さい方から大きい方へ変換する。大きい方から小さい方へ行くとデータロスが発生する。
-
Conversions in Assignment Expressions
- 代入式で明確に指定して変換する。
-
Conversion in other Binary Expressions
- 代入(
=)・四則演算(+,-,*,/)・比較(>,<,>=,<=)・等値(==,!=)・論理(%%,||)・ビット運算子(&,|,^,<<,>>)など使用する時暗黙に変換する。
- 代入(
-
Type Promotion
- 異なる型が運算するとき同じ型にするように、小さいサイズの型から大きいサイズの型へ変換する。
-
Type Demotion
- 大きいサイズの型から小さいサイズの型へ。
プログラムが運算するとき同等の型として揃わないとできないので、C言語が暗黙に自動変換することに頼るか、明確に指定して予期しない値の出力を避ける。(最終的に出力の変換指定子によって型変換するので、入力と出力両方気をつけないと、思います。)
下は練習です。
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(void)
{
char str[50];
printf("Enter a string in 50 letters: ");
scanf("%s", str);
for (int i = 0, n = strlen(str); i < n; i++)
{
if (str[i] >= 'a' && str[i] <= 'z')
{
// Implicit Type Conversion
// printf("%c", str[i] - 32); // output: char; input: (char - int);
// printf("%c", str[i] - ('a' - 'A'));
printf("%c", toupper(str[i]));
}
else
{
printf("%c", str[i]);
}
}
printf("\n");
return 0;
}
// Enter a string in 50 letters: abCD
// ABCD
Command Line Arguments
int main(int argc, char *argv[])
-
int argc(argument count):ここではプログラム実行時にCLIに入れる引数の個数を指す。スペースで区切る。(プログラム自体もカウントされる。) -
char *argv[](argument vector):CLIに入れる引数では、文字列の配列というポインタを使うこと。- 実際に入れた引数の個数よりも後ろのポインタにアクセスすると、別のメモリアドレスに入って予期しない出力をしたり、そのまま操作してしまうとデータを変えてしまったりする可能性があるそうです。
#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc == 2)
{
printf("hello! %s\n", argv[1]);
}
else
{
printf("hello, world\n");
}
return 0;
}
// week2/ $ ./argv David
// hello! David
この例では、
-
argv[0]:argv(ファイル名) -
argv[1]:David
int main(int argc, char *argv[])
{
for (int i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
// week2/ $ ./argv foo bar baz
// ./argv
// foo
// bar
// baz
int main(int argc, char *argv[])
{
for (int i = 0; i < argc; i++)
{
int length = strlen(argv[i]);
for (int j = 0; j < length; j++)
{
printf("%c\n", argv[i][j]);
}
}
return 0;
}
// week2/ $ ./argv foo
// .
// /
// a
// r
// g
// v
// f
// o
// o