Menu

C'de Tür Dönüşümü: Örtük Dönüşümler ve Açık Dönüşümler

C sürekli türler arasında dönüşüm yapar - bazen siz bir dönüşümle istediğiniz için, çoğu zaman kendiliğinden. İşte yükseltme kuralları, açık dönüşüm söz dizimi ve veriyi sessizce kaybeden dönüşümler.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

C sürekli türler arasında değer dönüştürür. Bu dönüşümlerden bazılarını bir dönüşümle kendiniz yazarsınız; çoğunu derleyici, sizin seçmediğiniz kurallar altında sessizce gerçekleştirir. Hangisinin hangisi olduğunu bilmek, "ortalamam neden hep 3?" ile söylediğini yapan kod arasındaki farktır.

Örtük Dönüşüm

Bir türden bir değer, başkasını bekleyen bir bağlamla karşılaştığında C onu dönüştürür:

Bilgi kaybedemeyen dönüşümler (int'ten double'a, char'dan int'e, short'tan long'a) genişleticidir ve her zaman güvenlidir. Diğer yöndeki dönüşümler daraltıcıdır ve veri kaybedebilir - yukarıdaki 3.9, -Wconversion ile istemedikçe hiçbir uyarı olmadan 3 oldu.

Alışıldık Aritmetik Dönüşümler

Bir ikili operatörün işlenenleri farklı türlerdeyken C, işi yapmadan önce onları ortak bir türe dönüştürür. Merdiven, alttan yukarı:

  1. int'ten küçük her şey (char, short, _Bool) int'e yükseltilir. Bu tam sayı yükseltmedir ve her zaman önce gerçekleşir.
  2. Taraflardan biri long double ise diğeri long double olur.
  3. Değilse biri double ise diğeri double olur.
  4. Değilse biri float ise diğeri float olur.
  5. Değilse, tam sayı türleri arasında rütbesi yüksek olan kazanır ve rütbeler eşitse işaretsiz kazanır.

O son kural gerçek hatalara yol açandır. Geri kalanı sezgiseldir.

Tam sayı yükseltme, char aritmetiğinin neden beklediğiniz gibi taşmadığını - ve sonucu bir char'a geri saklamanın neden taştığını - açıklar.

Açık Dönüşümler

Bir dönüşüm, parantez içindeki hedef türdür:

(double)x
(int)3.9
(char)65
(unsigned int)n

Hemen ardından gelen ifadeye uygulanır ve çok sıkı bağlanır - *, / ya da +'dan daha sıkı.

İlk satır tam sayı olarak böler (3 verir) ve sonra 3'ü 3.0'a dönüştürür - çok geç. İkincisi, bölmeden önce total'ı dönüştürür, dolayısıyla / operatörü bir double ve bir int görür, int'i yükseltir ve kayan nokta bölmesi yapar.

Bir işleneni dönüştürmek yeterlidir. Alışıldık aritmetik dönüşümler diğerini halleder.

Tam Sayı Bölmesini Düzeltmek

C'de bir dönüşüm yazmanın tek en yaygın sebebi budur:

Yüzde satırı öğreticidir: passed / n, 3 / 5'tir ve tam sayı olarak 0'dır ve 0 * 100 0'dır. Bölmeden önce çarpmak (100 * passed / n), 300 / 5 tam olduğu için dönüşüm olmadan bile düzeltir - ama bu yalnızca sayılar işbirliği yaptığında işe yarar. Dönüşüm güvenilir çözümdür.

Kırpma, Yuvarlama Değil

Kayan noktalı bir değeri bir tam sayıya dönüştürmek kesri atar. Sıfıra doğru kırpar - yuvarlamaz:

Bunu kendi makinenizde derlerseniz, math.h'nin Linux'ta bağlama zamanında -lm istediğini unutmayın.

Bir tehlike daha: tam sayı türü için çok büyük olan kayan noktalı bir değeri dönüştürmek bir sarma değil tanımsız davranıştır. (int)1e20 herhangi bir şey üretebilir. Değer sizin denetiminizde değilse dönüştürmeden önce aralığı kontrol edin.

char ve int

C'de bir char, bir karakter kodu tutan küçük bir tam sayıdır. İkisi arasında dönüşüm günlük iştir:

digit - '0', bir rakam karakterini değerine çevirmenin standart deyimidir ve on rakam karakterinin ardışık olması garanti olduğu için çalışır. Harfler için + 32 aritmetiği yerine ctype.h'deki toupper() ve tolower()'ı tercih edin - kayma bir ASCII olgusudur, bir C garantisi değil.

İlgili bir tuzak: ctype.h'deki isdigit ve toupper gibi fonksiyonlar, ya EOF olması ya da unsigned char olarak gösterilebilmesi gereken bir int alır. Negatif olan düz bir char geçirmek (düz char işaretli olabileceği için mümkündür) tanımsızdır. Onu dönüştürün: isdigit((unsigned char)c).

İşaretli/İşaretsiz Tuzağı

Dönüşüm merdiveninin 5. adımı - eşitlikte işaretsiz kazanır - C'nin en şaşırtıcı karşılaştırmasını üretir:

-1, bit örüntüsünü 4.294.967.295 olarak yeniden yorumlayan unsigned int'e dönüştürülür. Bu 1'den büyüktür, dolayısıyla karşılaştırma yanlıştır.

Aynı dönüşüm döngüleri sonsuza dek çalıştırır:

/* HATA: i işaretsizdir, dolayısıyla i >= 0 her zaman doğrudur. i 0 iken i-- sarar. */
for (size_t i = n - 1; i >= 0; i--) { ... }

Ve uzunluk denetimlerini başarısız kılar:

/* HATA: strlen, size_t (işaretsiz) döndürür. Metin 5'ten kısaysa
   len - 5 devasa bir sayıya sarar ve test geçer. */
if (strlen(s) - 5 > 0) { ... }

Bunu if (strlen(s) > 5) olarak yeniden yazın, çıkarma hiç gerçekleşmez.

Savunmalar: sayıları ve indeksleri baştan sona tek bir işaretlilikte tutun, -Wsign-compare ile derleyin (-Wextra içinde bulunur) ve karıştırmanız gerektiğinde, değerin negatif olamayacağını saptadıktan sonra açıkça dönüştürün.

Göstericileri Dönüştürmek

Dönüşümler gösterici türleri arasında da dönüştürür ve burada gerçek risk taşırlar, çünkü baytların kendisini değil belleğin nasıl yorumlandığını değiştirirler.

Küçük-sonlu (little-endian) bir makinede bu 01 00 00 00 yazdırır. Nesne gösterimini bir unsigned char * üzerinden incelemek, standardın açıkça onayladığı birkaç gösterici dönüşümünden biridir.

Diğer gösterici dönüşümlerinin çoğu onaylı değildir. Bir int'i bir float * üzerinden okumak katı takma ad (strict aliasing) kuralını ihlal eder ve derlense bile tanımsız davranıştır; baytları yeniden yorumlamak için bunun yerine memcpy kullanın.

Bilmeye değer iki gelenek. C'de void *, her nesne gösterici türüne ve türünden dönüşüm olmadan dönüşür; malloc'un sonucunu dönüştürmemeniz gerekmesinin sebebi budur:

int *arr = malloc(n * sizeof *arr);        /* doğru C */
int *arr = (int *)malloc(n * sizeof *arr); /* gereksiz; eksik bir başlığı gizler */

Dönüşüm C++'ta zorunludur; bu kadar çok kodun onu içermesinin sebebi budur. C'de <stdlib.h>'yi unutma hatasını gizleyebilir.

Ve printf("%p", ...) bir void * bekler, dolayısıyla oradaki gösterici argümanları gerçekten bir dönüşüme ihtiyaç duyar: printf("%p", (void *)p).

Bir Dönüşümün Yanlış Cevap Olduğu Zaman

Bir dönüşüm derleyiciyi susturur. Bazen derleyici haklıydı.

long big = 5000000000L;
int  small = (int)big;    /* dönüşüm gerçek veri kaybını gizler */

Değer gerçekten sığıyorsa dönüşüm, denetlediğinizi belgeler. Sığmayabiliyorsa dönüşüm, bir uyarıyı sessiz bir yanlış cevaba çevirmiştir. Birini yazmadan önce çözümün bir değişkenin tipini değiştirmek olup olmadığını sorun - int yerine double, int yerine size_t, long yerine long long. Bir dönüşüm esas olarak, (double)sum / count'ta olduğu gibi, iki doğru türün tek bir işlem için buluşması gerektiğinde doğru araçtır.

Sıkça Sorulan Sorular

C'de nasıl dönüşüm yapılır?

Hedef türü değerden önce parantez içine koyun: (double)x, (int)3.9, (char)65. Dönüşüm hemen ardından gelen ifadeye uygulanır, dolayısıyla (double)a / b önce a'yı dönüştürüp sonra böler, oysa (double)(a / b) tam sayı olarak böler ve sonucu dönüştürür.

C'de bir int'i float'a nasıl dönüştürürüm?

Atama bunu örtük olarak yapar - double d = 5; 5.0 saklar. Bir ifadenin içinde çoğu zaman açık bir dönüşüme ihtiyacınız olur: (double)total / count, tam sayı bölmesi yerine kayan nokta bölmesini zorlar.

C'de bir float'ı int'e dönüştürünce ne olur?

Kesirli kısım atılır - sıfıra doğru kırpılır, asla yuvarlanmaz. (int)3.9 3 ve (int)-3.9 -3'tür. Yuvarlamak için pozitifler için dönüşümden önce 0.5 ekleyin ya da math.h'deki round(), floor() veya ceil()'i kullanın.

İşaretli ile işaretsiz bir int'i karşılaştırmak neden yanlış cevap veriyor?

C'nin alışıldık aritmetik dönüşümleri işaretli değeri işaretsize dönüştürür, dolayısıyla -1 < 1u yanlıştır: -1 devasa bir pozitif sayı olur. Sayıları ve boyutları tek bir işaretlilikte tutun ya da değerin negatif olamayacağını denetledikten sonra açıkça dönüştürün.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA