今回はコンピュタサイエンスの基礎として、標準で利用できる関数がどのように裏で動作しているか理解するために実装したので、qiitaに残しておくことにした。
基本的な関数たちを実装できる状態の方が他言語への応用が効くと考えたので、C言語の基幹関数を実装した。
どんな人間が書いているか気になる人は普段メインで発信しているnoteを参考にしていただきたい。
標準でライブラリに搭載されている関数ももちろんあるが、こんな関数実装してみよう!とやっているものもあるので、実際に存在しない関数も載せているのはご容赦いただきたい。
また、実際に存在する関数においても、標準の関数に完全に準拠しているわけではないことをご了承ください。
あくまでC言語の初学者が様々な関数を鵜呑みにせず実装してみて理解度を深めていく過程でのアウトプットであるので、(誤りも含めて)温かい目でご覧いただきたい。
それではレッツゴー!
strlen
めちゃくちゃ多用する文字列の長さのカウント。
int strlen(char *str)
{
int i;
i = 0;
while (str[i] != '\0')
{
i++;
}
return (i);
}
swap
int型で受け取った引数を入れ替える。型の宣言を変更すれば別の型でも使用可能。
void swap(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
文字列コピー系
strcpy
src(source)の文字列をdest(destination)の文字列にコピーする関数。
末尾のNULL止めを忘れないように注意。
char *strcpy(char *dest, char *src)
{
char *tmp;
tmp = dest;
while (*src != '\0')
{
*dest = *src;
dest ++;
src ++;
}
*dest = '\0';
return (tmp);
}
strncpy
strcpyのn文字指定バージョン。文字列の先頭から指定した文字列をコピーする。
char *strncpy(char *dest, char *src, unsigned int n)
{
unsigned int i;
char *tmp;
i = 0;
tmp = dest;
while (i < n && *src != '\0')
{
*dest = *src;
dest++;
src++;
i++;
}
while (i < n)
{
*dest = '\0';
dest ++;
i++;
}
return (tmp);
}
strlcpy
strncpyの返り値が特殊なバージョン。基本的にはdestの文字列の長さとコピーするsizeの長さの合計を返す。destの長さよりも身近なsizeが指定された場合はその限りではない。
unsigned int strlcpy(char *dest, char *src, unsigned int size)
{
unsigned int i;
unsigned int src_len;
unsigned int dest_len;
i = 0;
src_len = strlen(src);
dest_len = strlen(dest);
if (size == 0)
return (src_len);
while (i < size - 1 && *src != '\0')
{
*dest = *src;
dest ++;
src ++;
i++;
}
*dest = '\0';
return (src_len);
}
文字列比較
strcmp
与えられた二つの文字列を比較する。同じだったら返り値が0、異なる場合ば辞書順で比較した上で差分をintで返す。
int strcmp(char *s1, char *s2)
{
while (*s1 != '\0' || *s2 != '\0')
{
if (*s1 != *s2)
{
return (*s1 - *s2);
}
s1++;
s2++;
}
return (0);
}
strncmp
strcmpのn文字指定バージョン。文字列の先頭からn文字分比較する。
int strncmp(char *s1, char *s2, unsigned int n)
{
unsigned int i;
i = 0;
while ((*s1 != '\0' || *s2 != '\0') && i < n)
{
if (*s1 != *s2)
{
return (*s1 - *s2);
}
s1++;
s2++;
i++;
}
return (0);
}
文字列結合
strcat
文字列を引数で二つ受け取り、dest(destination)の末尾にsrc(source)を結合する。結合した末尾にヌル文字を追加することを忘れずに。
char *strcat(char *dest, char *src)
{
int dest_len;
int src_len;
dest_len = strlen(dest);
src_len = strlen(src);
dest += dest_len;
while (*src != '\0')
*dest++ = *src++;
*dest = '\0';
return (dest - src_len - dest_len);
}
strncat
strcatのn文字指定バージョン。destの末尾に結合するsrc側の文字数を指定できる。最後には
char *strncat(char *dest, char *src, unsigned int nb)
{
unsigned int d;
unsigned int s;
unsigned int nbc;
d = strlen(dest);
s = strlen(src);
dest += d;
nbc = nb;
while (*src != '\0' && nb > 0)
{
*dest = *src;
src++;
dest++;
nb--;
}
*dest = '\0';
if (s < nbc)
{
return (dest - s - d);
}
else
{
return (dest - nbc - d);
}
}
strlcat
unsigned int strlcat(char *dest, char *src, unsigned int size)
{
unsigned int dest_len;
unsigned int src_len;
unsigned int i;
dest_len = strlen(dest);
src_len = strlen(src);
i = 0;
if (size == 0)
return (src_len);
else if (dest_len < size)
{
while (src[i] != '\0' && dest_len < size - 1)
{
dest[dest_len] = src[i];
i++;
dest_len++;
}
dest[dest_len] = '\0';
return (dest_len + src_len - i);
}
else
return (src_len + size);
}
strdup
strcpyのコピー先の文字列が指定されていないバージョン。与えられていないので、必要な分だけmallocでメモリを確保しなければならない。
char *strdup(char *src)
{
int i;
int len;
char *dest;
char *src_copy;
len = 0;
src_copy = src;
while (*src)
{
len++;
src++;
}
dest = (char *)malloc((len + 1) * sizeof(char));
if (dest == NULL)
return (NULL);
i = 0;
while (i < len)
{
dest[i] = src_copy[i];
i++;
}
dest[i] = '\0';
return (dest);
}
文字列結合・分割・検索
strjoin
複数の文字列を指定されたセパレーション文字列を挟みながら文字列を結合させる関数。total_len関数で確保するメモリの長さを調べている。
int total_len(int size, char **strs, char*sep)
{
int i;
int j;
int total_len;
i = 0;
total_len = 0;
while (i < size)
{
j = 0;
while (strs[i][j] != '\0')
{
total_len += ft_strlen(&strs[i][j]);
j++;
}
i++;
}
total_len += ft_strlen(sep) * (size - 1);
return (total_len);
}
char *strjoin(int size, char **strs, char*sep)
{
int i;
int j;
int k;
char *tmp;
i = 0;
k = 0;
if (size == 0)
{
tmp = (char *)malloc(sizeof(char));
return (tmp);
}
tmp = (char *)malloc(sizeof(char) * (ft_total_len(size, strs, sep) + 1));
while (i < size)
{
j = 0;
while (strs[i][j] != '\0')
tmp[k++] = strs[i][j++];
j = 0;
while (sep[j] != '\0' && i < size - 1)
tmp[k++] = sep[j++];
i++;
}
tmp[k] = '\0';
return (tmp);
}
split
与えられた文字列を、指定されたアルファベットで区切って分割する関数。作成したstrdupに分割後の文字列のmallocはお任せした。
int next_len(char *str, char *charset)
{
int i;
int j;
i = 0;
while (str[i])
{
j = 0;
while (charset[j])
{
if (str[i] == charset[j])
{
return (i);
}
j++;
}
i++;
}
return (i);
}
int sep_len(char *str, char *charset)
{
int i;
int j;
i = 0;
while (str[i])
{
j = 0;
while (charset[j])
{
if (str[i] == charset[j])
{
i++;
j = -1;
}
j++;
}
return (i);
}
return (i);
}
int count(char *str, char *charset)
{
unsigned int i;
unsigned int j;
unsigned int flag;
int count;
i = 0;
flag = -1;
count = 0;
while (str[i])
{
j = 0;
while (charset[j])
{
if (str[i] == charset[j])
{
if (flag != i - 1)
count++;
flag = i;
}
j++;
}
i++;
}
return (count);
}
char **split(char *str, char *charset)
{
int i;
int str_len;
char **tmp;
char *origin;
tmp = (char **)malloc(sizeof(char *) * (count(str, charset) + 2));
if (!tmp || !str)
return (NULL);
str += sep_len(str, charset);
origin = str;
i = 0;
while (*str)
{
str_len = next_len(str, charset);
tmp[i] = strdup(origin, str_len);
str += next_len(str, charset) + sep_len(str + str_len, charset);
origin = str;
i++;
}
tmp[i] = 0;
return (tmp);
}
strstr
文字列の中に指定された文字列が存在するか検索する関数。存在したらそれ以降の文字列を返す。
char *str02(char *str, char *to_find)
{
int i;
int j;
i = 0;
while (str[i] != '\0' && i < ft_length(str) - ft_length(to_find) + 1)
{
j = 0;
if (str[i] == to_find[j] && str[i] != '\0' && to_find[j] != '\0')
{
while (str[i] == to_find[j] && str[i] != '\0' && to_find[j] != '\0')
{
i++;
j++;
}
if (j == ft_length(to_find))
{
return (&str[i - j]);
}
}
i++;
}
return ((void *)0);
}
char *strstr(char *str, char *to_find)
{
if (to_find[0] == '\0')
{
return (str);
}
return (str02(str, to_find));
}
文字列変換
strupcase
与えられた文字列の小文字を全て大文字に変換
char *strupcase(char *str)
{
char *tmp;
tmp = str;
while (*str != '\0')
{
if (*str >= 'a' && *str <= 'z')
{
*str -= 32;
}
str++;
}
return (tmp);
}
strlowcase
char *strlowcase(char *str)
{
char *tmp;
tmp = str;
while (*str != '\0')
{
if (*str >= 'A' && *str <= 'Z')
{
*str += 32;
}
str++;
}
return (tmp);
}
strcapitalize
文字列の先頭のみを大文字で、あとは小文字に変換する
char *strcapitalize(char *str)
{
int i;
int flag;
i = 0;
flag = 1;
while (str[i] != '\0')
{
if ((str[i] >= 'a' && str[i] <= 'z') && flag == 1)
str[i] -= 32;
else if ((str[i] >= 'A' && str[i] <= 'Z') && flag == 0)
str[i] += 32;
if ((str[i] >= 'a' && str[i] <= 'z'))
flag = 0;
else if ((str[i] >= 'A' && str[i] <= 'Z'))
flag = 0;
else if ((str[i] >= '0' && str[i] <= '9'))
flag = 0;
else
flag = 1;
i++;
}
return (str);
}
型変換
atoi
文字列の最初の数字をint型に変換して出力する関数
int atoi(char *str)
{
int i;
int sign;
long ans;
i = 0;
ans = 0;
sign = 1;
while ((str[i] >= 9 && str[i] <= 13) || str[i] == ' ')
i++;
if (str[i] == '-')
{
sign *= -1;
i++;
}
while (str[i] >= '0' && str[i] <= '9')
{
ans *= 10;
ans += str[i] - '0';
i++;
}
return ((int)ans * sign);
}
itoa
putnbr_base
int型で与えられる数値を指定のベースシステムに変換して出力する関数。ただしベースシステムが不適切な時のエラー処理は施していない。
void ft_from_deci(long nbr, char *base)
{
char c;
if (nbr >= ft_len(base))
ft_from_deci(nbr / ft_len(base), base);
c = base[nbr % ft_len(base)];
write(1, &c, 1);
}
void ft_putnbr_base(int nbr, char *base)
{
long num;
num = nbr;
if (num < 0)
{
write(1, "-", 1);
num = -num;
}
ft_from_deci(num, base);
}
atoi_base
指定されたベースシステムで表記された数が文字列で与えられるので、10進数に変換して返す関数。
long base_check(char *base)
{
long i;
long j;
long count;
count = 0;
while (base[count])
{
if (base[count] == '+' || base[count] == '-'
|| base[count] == ' ' || (base[count] >= 9 && base[count] <= 13))
return (-1);
count++;
}
i = 0;
while (i < count - 1)
{
j = i + 1;
while (j < count)
{
if (base[i] == base[j])
return (-1);
j++;
}
i++;
}
return (count);
}
void to_deci(char *str, char *base, long *ans, long tmp)
{
long i;
long j;
long str_len;
i = 0;
while (str[i])
{
j = 0;
str_len = strlen(str, base);
while (base[j])
{
if (str[i] == base[j])
{
tmp = j;
while (--str_len > i)
tmp *= base_check(base);
*ans += tmp;
i++;
break ;
}
j++;
}
if (!base[j])
return ;
}
}
int atoi_base(char *str, char *base)
{
long i;
long sign;
long ans;
char *tmp;
i = 0;
ans = 0;
sign = 1;
if (base_check(base) == -1)
return (0);
while ((str[i] >= 9 && str[i] <= 13) || str[i] == ' ')
i++;
while (str[i] == '+' || str[i] == '-')
{
if (str[i] == '-')
sign *= -1;
i++;
}
tmp = str + i;
to_deci(tmp, base, &ans, 0);
return ((int)ans * sign);
}
まとめ
今回はいろんな関数を実装してみました。
何も考えず機能として利用しがちな関数ですが、自分で実装すると明らかに力がつくのがわかりますね。
これからも関数にであったら自分でも実装できるか再現しながら学んでいきたいと思います。