ログイン新規登録

Qiitaにログインして、便利な機能を使ってみませんか?

あなたにマッチした記事をお届けします

便利な情報をあとから読み返せます

0

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

最終更新日 投稿日 2024年04月04日

今回はコンピュタサイエンスの基礎として、標準で利用できる関数がどのように裏で動作しているか理解するために実装したので、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);
}

まとめ

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

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
  3. ダークテーマを利用できます
ログインすると使える機能について

コメント

この記事にコメントはありません。

いいね以上の気持ちはコメントで

Qiita Conference 2024 4月17日(水)~19(金)開催!

Qiita Conferenceは、Qiita最大規模のテックカンファレンスです!

基調講演ゲスト(敬称略)

牛尾剛、 市谷聡啓、 けんすう、 ゆる言語学ラジオ、 田中邦裕、小城久美子、 飯沼亜紀

0

新規登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる

ソーシャルアカウントでログイン・新規登録

メールアドレスでログイン・新規登録