`libft`, standart C kütüphanesindeki bazı temel işlevlerin nasıl çalıştığını anlamak için bir fırsat sunar. Genellikle, bu işlevleri yeniden uygulamak ve kendi kütüphanenizi oluşturmak için temel bir alıştırma olarak kullanılır. Bu işlevler, bellek işlemleri, dize manipülasyonu, karakter işlemleri ve listeler gibi çeşitli alanlara yayılmıştır.
Bu işlevler, genellikle bellek alanının manipülasyonu ve işlenmesi gereken durumlarda kullanılır. Örneğin, bir dizi içinde belirli bir değerin aranması, bellek alanının belirli bir değerle doldurulması veya bellek bloklarının karşılaştırılması gibi işlemlerde bu fonksiyonlara ihtiyaç duyulur.
void *ft_memset(void *s, int c, size_t n);
s
: Doldurulacak bellek bloğuna işaretçi.c
: Yerleştirilecek değer.n
: Belleğe yazılacak byte sayısı.
void *ft_calloc(size_t count, size_t size)
{
void *ptr;
ptr = malloc(size * count);
if (!(ptr))
return (NULL);
ft_bzero(ptr, (count * size));
return (ptr);
}
void ft_bzero(void *s, size_t n);
s
: Sıfırlanacak bellek bloğunun başlangıç adresi.n
: Sıfırlanacak byte sayısı.
void ft_bzero(void *s, size_t n)
{
ft_memset(s, 0, n);
}
Açıklama: ft_memset işlevini çağırır ve bu işlev aracılığıyla bellek bloğunu 0 ile doldurur. Yani, ft_bzero aslında ft_memset fonksiyonunu çağırmakla görevlidir.
void *ft_calloc(size_t count, size_t size);
count
: Tahsis edilecek öğe sayısı.size
: Her öğenin boyutu (byte cinsinden).
void *ft_calloc(size_t count, size_t size)
{
void *ptr;
ptr = malloc(size * count);
if (!(ptr))
return (NULL);
ft_bzero(ptr, (count * size));
return (ptr);
}
Açıklama: ft_calloc, bellek tahsis eder ve ardından bu bellek alanını sıfırlar. Bu işlev, genellikle dinamik bellek yönetimi için kullanılır.
void *ft_memcpy(void *dest, const void *src, size_t n);
dest
: Kopyalanan verinin hedef adresi.src
: Kopyalanan verinin kaynak adresi.n
: Kopyalanacak byte sayısı.
void *ft_memcpy(void *dst, const void *src, size_t n)
{
size_t i;
if (!dst && !src)
return (0);
i = 0;
while (i < n)
{
((unsigned char *)dst)[i] = ((unsigned char *)src)[i];
i++;
}
return (dst);
}
Açıklama: ft_memcpy, bir bellek bloğunu başka bir bellek bloğuna kopyalar. Bu işlev, genellikle veri kopyalama işlemleri için kullanılır.
void *ft_memmove(void *dest, const void *src, size_t n);
dest
: Kopyalanan verinin hedef adresi.src
: Kopyalanan verinin kaynak adresi.n
: Kopyalanacak byte sayısı.
void *ft_memmove(void *dst, const void *src, size_t n)
{
if (!dst && !src)
return (NULL);
if (dst < src)
ft_memcpy(dst, src, n);
else if (dst > src)
{
while (n--)
*((unsigned char *)(dst + n)) = *((unsigned char *)(src + n));
}
return (dst);
}
Açıklama: ft_memmove, bir bellek bloğunu başka bir bellek bloğuna güvenli şekilde taşır. Bellek alanları çakışsa (overlap) bile veri kaybı olmadan çalışır; bu yönüyle ft_memcpy'den farklıdır.
void *ft_memchr(const void *s, int c, size_t n);
s
: Arama yapılacak bellek bloğunun adresi.c
: Aranan karakter.n
: Arama yapılacak maksimum byte sayısı.
void *ft_memchr(const void *s, int c, size_t n)
{
const unsigned char *str;
str = s;
while (n > 0)
{
if (*str == (unsigned char) c)
return ((void *)str);
n--;
str++;
}
return (NULL);
}
Açıklama: ft_memchr, bir bellek bloğunda belirli bir karakteri arar ve bulursa o karakterin adresini döner. Eğer karakter bulunamazsa NULL döner.
int ft_memcmp(const void *s1, const void *s2, size_t n);
s1
: Karşılaştırılacak ilk bellek bloğunun adresi.s2
: Karşılaştırılacak ikinci bellek bloğunun adresi.n
: Karşılaştırılacak maksimum byte sayısı.
int ft_memcmp(const void *s1, const void *s2, size_t n)
{
const unsigned char *s1_uc;
const unsigned char *s2_uc;
size_t index;
index = 0;
s1_uc = (const unsigned char *)s1;
s2_uc = (const unsigned char *)s2;
while (index < n)
{
if (s1_uc[index] != s2_uc[index])
return (s1_uc[index] - s2_uc[index]);
index++;
}
return (0);
}
Açıklama: ft_memcmp, iki bellek bloğunu karşılaştırır ve ilk farklı karakterin ASCII değerlerinin farkını döner. Eğer bloklar tamamen aynıysa 0 döner.
Dize işlemleri, metinsel verilerin manipülasyonu ve işlenmesi için kullanılan işlevlerdir. Bu işlevler, bir dizenin uzunluğunu hesaplamak, başka bir dizeye kopyalamak, bir dizeye yeni karakterler eklemek, belirli bir karakteri bir dizide aramak, alt dize aramak veya iki dizeyi karşılaştırmak gibi çeşitli işlemleri gerçekleştirir.
size_t ft_strlen(const char *s);
s
: Uzunluğu hesaplanacak dizi.
size_t ft_strlen(const char *str)
{
size_t len = 0;
while (str[len] != '\0')
len++;
return len;
}
Açıklama: ft_strlen, bir dizinin uzunluğunu hesaplar. Dizi sonuna kadar giderek karakterleri sayar ve son karakter olan null terminatörü ('\0') saymaz.
size_t ft_strlcpy(char *dst, const char *src, size_t size);
dst
: Kopyalanacak dizi.src
: Kaynak dizi.size
: Kopyalanacak maksimum byte sayısı.
size_t ft_strlcpy(char *dst, const char *src, size_t size)
{
size_t counter;
counter = 0;
if (size > 0)
{
while ((size - 1) > counter && src[counter])
{
dst[counter] = src[counter];
counter++;
}
dst[counter] = '\0';
}
while (src[counter])
counter++;
return (counter);
}
Açıklama: ft_strlcpy, bir diziyi başka bir diziye güvenli bir şekilde kopyalar. Kopyalama işlemi sırasında hedef dizinin boyutunu kontrol eder ve taşmayı önler. Eğer hedef dizi yeterince büyük değilse, kopyalama işlemi tamamlanmaz ve sonuna null terminatör eklenir.
size_t ft_strlcat(char *dst, const char *src, size_t size);
dst
: Dizeye eklenecek dizi.src
: Eklenen dizi.size
: Maksimum hedef dizinin boyutu.
size_t ft_strlcat(char *dest, const char *src, size_t size)
{
size_t leng;
size_t counter;
counter = 0;
leng = ft_strlen(dest);
if (size - 1 > leng && size > 0)
{
while (src[counter] != '\0' && size - 1 > leng + counter)
{
dest[counter + leng] = src[counter];
counter++;
}
dest[leng + counter] = 0;
}
if (leng >= size)
leng = size;
return (leng + ft_strlen(src));
}
Açıklama: ft_strlcat, bir diziyi başka bir diziye güvenli bir şekilde ekler. Eklenen dizinin boyutunu kontrol eder ve taşmayı önler. Eğer hedef dizi yeterince büyük değilse, ekleme işlemi tamamlanmaz ve sonuna null terminatör eklenir.
char *ft_strchr(const char *s, int c);
s
: Arama yapılacak dizi.c
: Aranan karakter.
char *ft_strchr(const char *s, int c)
{
while ((char)c != *s)
{
if (*s == 0)
return (0);
s++;
}
return ((char *)s);
}
Açıklama: ft_strchr, bir dizide belirli bir karakteri arar ve bulursa o karakterin adresini döner. Eğer karakter bulunamazsa NULL döner.
char *ft_strrchr(const char *s, int c);
s
: Arama yapılacak dizi.c
: Aranan karakter.
char *ft_strrchr(const char *s, int c)
{
int i;
i = ft_strlen(s);
while (i >= 0)
{
if (s[i] == (unsigned char)c)
return ((char *)s + i);
i--;
}
return (NULL);
}
Açıklama: ft_strrchr, bir dizide belirli bir karakteri sondan arar ve bulursa o karakterin adresini döner. Eğer karakter bulunamazsa NULL döner.
char *ft_strnstr(const char *haystack, const char *needle,
size_t len);
haystack
: Arama yapılacak dizi.needle
: Aranan alt dizi.len
: Arama yapılacak maksimum karakter sayısı.
char *ft_strnstr(const char *haystack, const char *needle, size_t len)
{
const char *h;
const char *n;
size_t counter;
if (!needle[0])
return ((char *)haystack);
while (*haystack && len > 0)
{
h = haystack;
n = needle;
counter = 0;
while (n[counter] == h[counter] && n[counter] && len - counter > 0)
counter++;
if (n[counter] == '\0')
return ((char *) haystack);
len--;
haystack++;
}
return (NULL);
}
Açıklama: ft_strnstr, bir alt dizenin bir dizideki pozisyonunu bulur. Eğer alt dize bulunamazsa NULL döner. Arama işlemi sırasında maksimum karakter sayısını kontrol eder.
int ft_strncmp(const char *s1, const char *s2, size_t n);
s1
: Karşılaştırılacak ilk dizi.s2
: Karşılaştırılacak ikinci dizi.n
: Karşılaştırma yapılacak maksimum karakter sayısı.
int ft_strncmp(const char *s1, const char *s2, size_t size)
{
size_t i;
i = 0;
while (i < size && (s1[i] != '\0' || s2[i] != '\0'))
{
if (s1[i] != s2[i])
return ((unsigned char)s1[i] - (unsigned char)s2[i]);
i++;
}
return (0);
}
Açıklama: ft_strncmp, iki dizeyi belirli bir sayıda karakterle karşılaştırır. Eğer dizeler eşitse 0 döner, eğer farklıysa ilk farklı karakterlerin ASCII değerlerinin farkını döner.
int ft_atoi(const char *str);
str
: Dönüştürülecek dizi.
int ft_atoi(const char *str)
{
int i;
int sign;
int container;
i = 0;
sign = 1;
container = 0;
while (str[i] == 32 || (str[i] >= 9 && str[i] <= 13))
i++;
if (str[i] == '-' || str[i] == '+')
{
if (str[i] == '-')
sign *= -1;
i++;
}
while (str[i])
{
if (!(ft_isdigit(str[i])))
return (container * sign);
else
container = container * 10 + (const char ) str[i] - 48;
i++;
}
return (container * sign);
}
Açıklama: ft_atoi, bir diziyi bir tam sayıya dönüştürür. Dönüştürme işlemi sırasında boşlukları ve işaretleri (pozitif/negatif) dikkate alır. Eğer dizi geçerli bir tam sayı değilse, en yakın geçerli değeri döner.
char *ft_itoa(int n);
n
: Dönüştürülecek tam sayı..
static int ft_string_leng(int num)
{
int i;
if (num == 0)
return (1);
i = 0;
while (num > 0 || num < 0)
{
num /= 10;
i++;
}
return (i);
}
char *ft_itoa(int n)
{
int len;
char *str;
long nbr;
nbr = n;
len = ft_string_leng(nbr);
if (n < 0)
{
len++;
nbr *= -1;
}
str = malloc(sizeof(char) * len + 1);
if (!str)
return (NULL);
str[len] = '\0';
while (nbr > 0)
{
str[--len] = (nbr % 10) + 48;
nbr /= 10;
}
if (n < 0)
str[0] = '-';
if (n == 0)
str[0] = '0';
return (str);
}
Açıklama: ft_itoa, bir tam sayıyı bir diziye dönüştürür. Dönüştürme işlemi sırasında negatif işaretini ve null terminatörü dikkate alır. Eğer dizi geçerli bir tam sayı değilse, NULL döner.
char *ft_strdup(const char *s);
s
: Kopyalanacak dizi.
char *ft_strdup(const char *s)
{
char *str;
size_t leng;
size_t i;
leng = ft_strlen(s);
str = (char *)malloc(sizeof(char) * (leng + 1));
if (!(str))
return (NULL);
i = 0;
while (s[i] != '\0')
{
str[i] = s[i];
i++;
}
str[i] = '\0';
return (str);
}
Açıklama: ft_strdup, bir dizinin kopyasını yapar. Kopyalama işlemi sırasında yeni dizinin boyutunu kontrol eder ve taşmayı önler. Eğer dizi yeterince büyük değilse, NULL döner.
void ft_striteri(char *s, void (*f)(unsigned int, char*));
s
: İşlem yapılacak dize.f
: Her karakter üzerinde çağrılacak işlev.
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
i = 0;
while (s[i])
{
f(i, &s[i]);
i++;
}
return ;
}
Açıklama: ft_striteri, bir dizenin her karakteri üzerinde belirtilen işlemi gerçekleştirir. İşlem sırasında her karakterin adresini ve indeksini işlevin parametreleri olarak geçirir.
char *ft_strjoin(const char *s1, const char *s2);
s1
: Birleştirilecek ilk dizi.s2
: Birleştirilecek ikinci dizi.
char *ft_strjoin(char const *s1, char const *s2)
{
char *str;
unsigned int leng;
unsigned int i;
i = 0;
if (!s1 || !s2)
return (NULL);
leng = ft_strlen(s1) + ft_strlen(s2);
str = malloc(sizeof(char) * (leng + 1));
if (!str)
return (NULL);
while (*s1)
{
str[i] = *s1++;
i++;
}
while (*s2)
{
str[i] = *s2++;
i++;
}
str[i] = '\0';
return (str);
}
Açıklama: ft_strjoin, iki diziyi birleştirir ve yeni bir dizi oluşturur. Birleştirme işlemi sırasında yeni dizinin boyutunu kontrol eder ve taşmayı önler. Eğer dizi yeterince büyük değilse, NULL döner.
char *ft_strmapi(const char *s, char (*f)(unsigned int,
char));
s
: İşlem yapılacak dize.f
: Her karakter üzerinde çağrılacak işlev.
char *ft_strmapi(char const *s, char (*f)(unsigned int, char))
{
char *str;
unsigned int i;
if (!s)
return (NULL);
str = (char *)malloc(ft_strlen(s) + 1);
if (!str)
return (NULL);
i = 0;
while (s[i] != '\0')
{
str[i] = f(i, s[i]);
i++;
}
str[i] = '\0';
return (str);
}
Açıklama: ft_strmapi, bir dizenin her karakteri üzerinde belirtilen işlemi gerçekleştirir ve yeni bir dizi oluşturur. İşlem sırasında her karakterin adresini ve indeksini işlevin parametreleri olarak geçirir.
char *ft_strtrim(const char *s1, const char *set);
s1
: İşlem yapılacak dize.set
: Kaldırılacak karakter kümesi.
static int ft_checkset(char c, char const *set)
{
size_t i;
i = 0;
while (set[i])
{
if (set[i++] == c)
return (1);
}
return (0);
}
char *ft_strtrim(char const *s1, char const *set)
{
char *m;
size_t start;
size_t end;
size_t i;
if (!s1 || !set)
return (NULL);
start = 0;
end = ft_strlen(s1);
while (s1[start] && ft_checkset(s1[start], set))
start++;
while (end > start && ft_checkset(s1[end - 1], set))
end--;
m = (char *)malloc(sizeof(char) * (end - start) + 1);
if (!m)
return (NULL);
i = 0;
while (start < end)
m[i++] = s1[start++];
m[i] = '\0';
return (m);
}
Açıklama: ft_strtrim, bir dizenin başındaki ve sonundaki belirli karakterleri kaldırır. Kaldırma işlemi sırasında yeni dizinin boyutunu kontrol eder ve taşmayı önler. Eğer dizi yeterince büyük değilse, NULL döner.
char **ft_split(char const *s, char c);
s
: Bölünecek dize.c
: Ayırıcı karakter.
static unsigned int ft_word_counter(const char *s, char control)
{
unsigned int word;
word = 0;
while (*s)
{
if (*s == control)
s++;
else
{
while (*s != control && *s)
s++;
word++;
}
}
return (word);
}
static unsigned int ft_charlen(const char *s, char c)
{
unsigned int i;
i = 0;
while (s[i] && s[i] != c)
i++;
return (i);
}
char **free_all(char **result)
{
int i;
i = 0;
while (result[i])
{
free(result[i]);
i++;
}
free(result);
return (NULL);
}
char **ft_split(char const *s, char c)
{
char **arr;
unsigned int j;
unsigned int a;
arr = (char **)malloc((ft_word_counter(s, c) + 1) * sizeof(char *));
if (!arr)
return (NULL);
a = -1;
while (*s)
{
while (*s == c)
s++;
if (*s)
{
arr[++a] = (char *)malloc((ft_charlen(s, c) + 1) * sizeof(char));
if (!arr[a])
return (free_all(arr));
j = 0;
while (*s && *s != c)
arr[a][j++] = *s++;
arr[a][j] = '\0';
}
}
arr[++a] = NULL;
return (arr);
}
Açıklama: ft_split, belirtilen ayırıcı karaktere göre bir dizeyi bölüp bir dize dizisi oluşturur. Bölme işlemi sırasında yeni dizinin boyutunu kontrol eder ve taşmayı önler. Eğer dizi yeterince büyük değilse, NULL döner.
int ft_tolower(int c);
c
: Dönüştürülecek karakterin ASCII değeri.
int ft_tolower(int c)
{
if (c >= 65 && c <= 90)
return (c + 32);
return (c);
}
Açıklama: ft_tolower, bir karakteri küçük harfe dönüştürür. Dönüştürme işlemi sırasında karakterin ASCII değerini kontrol eder ve büyük harf ise küçük harfe dönüştürür.
int ft_toupper(int c);
c
: Dönüştürülecek karakterin ASCII değeri.
int ft_toupper(int c)
{
if (c >= 'a' && c <= 'z')
return (c - 32);
return (c);
}
Açıklama: ft_toupper, bir karakteri büyük harfe dönüştürür. Dönüştürme işlemi sırasında karakterin ASCII değerini kontrol eder ve küçük harf ise büyük harfe dönüştürür.
Karakter işlemleri, genellikle bir karakterin belirli bir özelliğe sahip olup olmadığını kontrol etmek için kullanılan işlemlerdir. Örneğin, bir karakterin bir harf olup olmadığını, bir rakam olup olmadığını, bir yazdırılabilir karakter olup olmadığını veya bir ASCII karakteri olup olmadığını belirlemek gibi işlemleri içerir. Bu tür işlemler, genellikle karakter dizilerini veya kullanıcıdan alınan girdileri işlerken karakterlerin özelliklerini kontrol etmek için kullanılır.
int ft_isalnum(int c);
c
: Kontrol edilecek karakterin ASCII değeri.
int ft_isalnum(int c)
{
return (ft_isalpha(c) || ft_isdigit(c));
}
Açıklama: ft_isalnum, bir karakterin alfasayısal veya sayısal olup olmadığını kontrol eder. Eğer karakter alfasayısal veya sayısal ise 1; aksi halde 0 döner.
int ft_isalpha(int c);
c
: Kontrol edilecek karakterin ASCII değeri.
int ft_isalpha(int c)
{
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
}
Açıklama: ft_isalpha, bir karakterin alfasayısal olup olmadığını kontrol eder. Eğer karakter alfasayısal ise 1; aksi halde 0 döner.
int ft_isascii(int c);
c
: Kontrol edilecek karakterin ASCII değeri.
int ft_isascii(int c)
{
return (c >= 0 && c < 128);
}
Açıklama: ft_isascii, bir karakterin standart ASCII karakteri olup olmadığını kontrol eder. Eğer karakter standart ASCII ise 1; aksi halde 0 döner.
int ft_isdigit(int c);
c
: Kontrol edilecek karakterin ASCII değeri.
int ft_isdigit(int c)
{
return (c <= '9' && c >= '0');
}
Açıklama: ft_isdigit, bir karakterin bir rakam karakteri olup olmadığını kontrol eder. Eğer karakter bir rakam ise 1; aksi halde 0 döner.
int ft_isprint(int c);
c
: Kontrol edilecek karakterin ASCII değeri.
int ft_isdigit(int c)
{
return (c <= '9' && c >= '0');
}
Açıklama: ft_isprint, verilen bir karakterin basılabilir (printable) karakter olup olmadığını kontrol eder. Basılabilir karakterler ASCII kod 32 ile 126 arasındadır ve klavye üzerinde yazdırılabilir karakterlerin çoğunu temsil eder. Fonksiyon, verilen karakterin bu aralıkta olup olmadığını kontrol eder. Eğer karakter yazdırılabilir ise 1; aksi halde 0 döner.
malloc, bellekte dinamik olarak bellek ayırmak için kullanılan bir işlevdir. malloc, belirli bir boyutta bellek bloğu ayırır ve bu bloğun başlangıç adresini döner. malloc, genellikle diziler, yapılar veya diğer veri yapıları için bellek ayırmak için kullanılır.
int main() {
// 10 integer değeri için bellek tahsis et
int *ptr = (int *)malloc(10 * sizeof(int));
// Bellek tahsisi başarılı mı kontrol et
if (ptr == NULL) {
printf("Bellek tahsis edilemedi!");
return -1;
}
// Belleği kullan
for (int i = 0; i < 10; i++) {
ptr[i] = i * 2;
printf("%d ", ptr[i]);
}
// Belleği serbest bırak
free(ptr);
return 0;
}
calloc, bellekte dinamik olarak bellek ayırmak için kullanılan bir işlevdir. malloc ile benzer şekilde çalışır, ancak calloc, ayırdığı belleği sıfırlarla başlatır. calloc, genellikle diziler, yapılar veya diğer veri yapıları için bellek ayırmak için kullanılır.
Type casting, bir veri türünü başka bir veri türüne dönüştürme işlemidir. C dilinde, tür dönüşümü genellikle iki şekilde yapılır: otomatik (implicit) ve açık (explicit) tür dönüşümü. Otomatik tür dönüşümü, derleyici tarafından otomatik olarak yapılırken, açık tür dönüşümü, programcı tarafından belirtilir.
int main() {
int a = 10;
double b;
// int tipindeki a değişkenini double tipine dönüştürerek b'ye atadık
b = (double)a;
printf("a: %d\n", a);
printf("b: %.2f\n", b);
return 0;
}
Overlap durumu, iki veya daha fazla bellek alanının birbirini kapsadığı durumları ifade eder. Bu durum, bellek yönetimi sırasında sorunlara yol açabilir. Örneğin, bir bellek bloğu üzerinde işlem yaparken, başka bir bellek bloğunun üzerine yazılması gibi durumlar overlap olarak adlandırılır.
// overlap fonksiyonu, iki dizinin çakışıp çakışmadığını kontrol eder
int overlap(int arr1[], int size1, int arr2[], int size2) {
// Birinci dizinin son elemanı ikinci dizinin ilk elemanından küçükse veya
// ikinci dizinin son elemanı birinci dizinin ilk elemanından küçükse çakışma yoktur
if (arr1[size1 - 1] < arr2[0] || arr2[size2 - 1] < arr1[0]) {
return 0; // Çakışma yok
} else {
return 1; // Çakışma var
}
}
int main() {
int arr1[] = {1, 2, 3, 4, 5};
int arr2[] = {4, 5, 6, 7, 8};
int size1 = sizeof(arr1) / sizeof(arr1[0]);
int size2 = sizeof(arr2) / sizeof(arr2[0]);
if (overlap(arr1, size1, arr2, size2)) {
printf("Diziler çakışıyor.\n");
} else {
printf("Diziler çakışmıyor.\n");
}
return 0;
}
Static, bir değişkenin veya fonksiyonun kapsamını ve ömrünü belirleyen bir anahtar kelimedir. Static değişkenler, yalnızca tanımlandıkları blok içinde geçerlidir ve programın çalışması boyunca bellekte saklanır. Static fonksiyonlar ise yalnızca tanımlandıkları dosya içinde erişilebilir.
size_t, C dilinde bellek boyutunu temsil etmek için kullanılan bir veri türüdür. Genellikle dizilerin boyutunu veya bellek tahsis işlemlerinde kullanılır. size_t, işaretçi aritmetiği ve bellek yönetimi işlemlerinde güvenli bir şekilde kullanılabilir.
Proje hakkında tüm görüş, öneri ve sorularınız için dilediğiniz zaman iletişime geçebilirsiniz.
daha çok yazı, bilgi ve içerik için takipte kalmayı unutmayın.
Projenin orijinal kaynak kodlarına aşağıdaki bağlantıdan ulaşabilirsiniz:
🔗 Proje GitHub Linki