【自作関数あり】C言語の基幹関数実装してみた

今回はコンピュタサイエンスの基礎として、標準で利用できる関数がどのように裏で動作しているか理解するために実装したので、qiitaに残しておくことにした。

基本的な関数たちを実装できる状態の方が他言語への応用が効くと考えたので、C言語の基幹関数を実装した。

どんな人間が書いているか気になる人は[普段メインで発信しているnote](https://note.com/yabukin_coffee)を参考にしていただきたい。

標準でライブラリに搭載されている関数ももちろんあるが、こんな関数実装してみよう！とやっているものもあるので、実際に存在しない関数も載せているのはご容赦いただきたい。

また、実際に存在する関数においても、標準の関数に完全に準拠しているわけではないことをご了承ください。

あくまでC言語の初学者が様々な関数を鵜呑みにせず実装してみて理解度を深めていく過程でのアウトプットであるので、（誤りも含めて）温かい目でご覧いただきたい。

それではレッツゴー！

## strlen
めちゃくちゃ多用する文字列の長さのカウント。
```c
int	strlen(char *str)
{
	int	i;

	i = 0;
	while (str[i] != '\0')
	{
		i++;
	}
	return (i);
}
```
## swap
int型で受け取った引数を入れ替える。型の宣言を変更すれば別の型でも使用可能。
```c
void	swap(int *a, int *b)
{
	int	tmp;

	tmp = *a;
	*a = *b;
	*b = tmp;
}
```
# 文字列コピー系
## strcpy
src(source)の文字列をdest(destination)の文字列にコピーする関数。
末尾のNULL止めを忘れないように注意。
```c
char	*strcpy(char *dest, char *src)
{
	char	*tmp;

	tmp = dest;
	while (*src != '\0')
	{
		*dest = *src;
		dest ++;
		src ++;
	}
	*dest = '\0';
	return (tmp);
}
```

## strncpy
strcpyのn文字指定バージョン。文字列の先頭から指定した文字列をコピーする。
```c
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が指定された場合はその限りではない。
```c
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で返す。
```c
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文字分比較する。
```c
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)を結合する。結合した末尾にヌル文字を追加することを忘れずに。
```c
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側の文字数を指定できる。最後には
```c
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
```c
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でメモリを確保しなければならない。
```c
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関数で確保するメモリの長さを調べている。
```c
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はお任せした。
```c
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
文字列の中に指定された文字列が存在するか検索する関数。存在したらそれ以降の文字列を返す。
```c
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
与えられた文字列の小文字を全て大文字に変換
```c
char	*strupcase(char *str)
{
	char	*tmp;

	tmp = str;
	while (*str != '\0')
	{
		if (*str >= 'a' && *str <= 'z')
		{
			*str -= 32;
		}
		str++;
	}
	return (tmp);
}
```
## strlowcase
```c
char	*strlowcase(char *str)
{
	char	*tmp;

	tmp = str;
	while (*str != '\0')
	{
		if (*str >= 'A' && *str <= 'Z')
		{
			*str += 32;
		}
		str++;
	}
	return (tmp);
}
```
## strcapitalize
文字列の先頭のみを大文字で、あとは小文字に変換する
```c
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型に変換して出力する関数
```c
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型で与えられる数値を指定のベースシステムに変換して出力する関数。ただしベースシステムが不適切な時のエラー処理は施していない。
```c
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進数に変換して返す関数。
```c
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);
}
```

# まとめ
今回はいろんな関数を実装してみました。
何も考えず機能として利用しがちな関数ですが、自分で実装すると明らかに力がつくのがわかりますね。
これからも関数にであったら自分でも実装できるか再現しながら学んでいきたいと思います。