Merhaba yazılımperver dostlarım, modern C++ yazılarımıza devam ediyoruz. C++ 11’den kalan bir kaç kabiliyetten biri olan Kullanıcı Tanımlı Değişmezlere (“user-defined literals”) göz atacağız.
Kullanıcı tanımlı olmayan değişmezler aslında C++ 11’den önce de dilde bulunuyordu (“built-in type” olarak nitelenebilir). Örneğin:
- 13 (10 luk düzende 12), 010 (8 lik düzende 8), 0x13 (16’lık düzende 19) olarak tam sayıları,
- 3.14, 2e1, kayan noktalı sayılar,
- ‘a’, ‘\t’, “deneme”, karakter ve metinler,
Yukarıdakilerin yanında, sonuna eklendikleri sayıların tipini ifade eden ekler de mevcut. Şöyle ki:
- 16U, unsigned int
- 010L, long
- 3.14F, float
- 0x12ULL, unsigned long long
İşte C++ 11, bunların yanında kendi değişmezlerimizi de tanımlamamıza imkan sağlama olanağı sunuyor. Burada, elbette bu belirli kaidelere göre yapılmaktadır ki “User-defined literals“ sayfasında detaylı bir şekilde anlatılmaktadır ama öncesinde bu mekanizma ile kodlarımız içerisinde neler yapabileceğimize ilişkin örneklere bakalım 😉
- Tam sayı değişmezlerine örneğin _km/_cm ekleyebiliriz. Ör. 12_km, 126_cm.
- Ya da kayan sayı değişmezleri ile de bunları kullanabiliriz. Ör. 123.32_km
- Metinlerle de kullanılabiliyor. Ör. “Merhaba”_str gibi kullanılabilir.
Örneklere baktığınız zaman, kullanıcı tanımlı değişmezlerin izlemesi gereken format hakkında bir fikriniz olduğunu düşünüyorum. Şöyle ki:
<“Built_in-Literal”>_<“Suffix”>
Formatı gördük peki bunları nasıl tanımlayabiliriz. Bu tanımlamalar, genel olarak değişmez operatörü (“literal operator”) olarak isimlendiriliyor. Hemen bakalım nasıl tanımlayabileceğimize:
<DönüşTipi> operator “” <SonEk> (<Parametreler>)
Şimdi uzunluklar için bunun bir kullanımına bakalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include<iostream> #include<iomanip> using namespace std; // Kilometre long double operator ""_km( long double x ) { return x * 1000; } // Metre long double operator "" _m( long double x ) { return x; } // Santimetre long double operator "" _cm( long double x ) { return x / 100.0; } int main() { double mesafe = 4.5_km; cout << "Metre cinsinden mesafe karsiliklari: " << '\n' << mesafe << '\n' << setprecision(8) << ( mesafe + 250.05_m ) << '\n' << ( 6.2_km / 2.0_m ) << '\n' << ( 1000.0_cm * 100.0_m ) << '\n' << ( 10.0_m * 100.0_m ) << '\n' << ( 0.1_km * 1000.0_cm ) << '\n'; return 0; } |
Örnekte de görebileceğiniz üzere birimler arası dönüşümleri bu mekanizma sayesinde çözmüş olduk. Tabi bunu, farklı şekillerde de halledebilirdik ama bu şekilde hem de daha okunabilir hem de tip güvenli bir kullanım elde etmiş olduk.
Burada operatörleri tanımlarken dikkat etmeniz gereken bir husus da, ilgili değişmez operatörlerinde sadece şu tipleri kullanabiliyoruz:
- unsigned long long int,
- long double,
- char,
- wchar_t,
- char16_t,
- char32_t,
- const char *,
- (const char * , std::size_t),
- (const wchar_t * , std::size_t),
- (const char16_t * , std::size_t),
- (const char32_t * , std::size_t).
Şimdi yukarıdaki örneği performans anlamında bir adım daha öteye götürebiliriz. Nasıl mı? constexpr’ları kullanarak. Bu sayede, dönüşümleri derleme zamanında hesaplanmasını sağlayabileceğiz. Nasıl yapacağız? Aşağıdaki gibi 😉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Kilometre constexpr long double operator ""_km( long double x ) { return x * 1000; } // Metre constexpr long double operator "" _m( long double x ) { return x; } // Santimetre constexpr long double operator "" _cm( long double x ) { return x / 100.0; } |
C++ bir takım hazır değişmezler de sunuyor. Bunlar ve hangi C++ standardı ile sunulduklarına aşağıdaki tablodan görebilirsiniz:
std::literals::complex_literals isim uzayı içerisinde tanımlı değişmezler |
|
A std::complex literal representing pure imaginary number (function) |
|
std::literals::chrono_literals isim uzayı içerisinde tanımlı değişmezler |
|
(C++14)
|
A std::chrono::duration, saati ifade eder (function) |
(C++14)
|
A std::chrono::duration, dakikayı ifade eder (function) |
(C++14)
|
A std::chrono::duration, saniyeyi ifade eder (function) |
(C++14)
|
A std::chrono::duration, mili saniyeyi ifade eder (function) |
(C++14)
|
A std::chrono::duration, mikrosaniyeyi ifade eder (function) |
(C++14)
|
std::chrono::duration, nano saniyeyi ifade eder (function) |
(C++20)
|
std::chrono::year, yılı ifade eder (function) |
(C++20)
|
std::chrono::day, ayın gününü ifade eder (function) |
std::literals::string_literals isim uzayı içerisinde tanımlı değişmezler |
|
(C++14)
|
Karakter dizisini basic_string tipine dönüştürür(function) |
std::literals::string_view_literals isim uzayı içerisinde tanımlı değişmezler |
|
(C++17)
|
Karakter dizisine ilişkin string view oluşturur (function) |
Özet olarak, bu mekanizma, tip güvenli (type safety) aritmetiksel işlemler ile daha emniyetli ve okunabilir kodlar yazmamıza yardımcı oluyor. Örneğin, bir kullanıcı tanımlı değişmez operatörünü bulamaz ise derleme hatası ile karşılaşabilirsiniz. Elbette, diğer bütün C++ özellikleri gibi çok dikkatli kullanmanız gerekiyor, aksi halde başınıza dert açabilir.
Kaynaklar kısmında verdiğim https://www.modernescpp.com/index.php/user-defined-literals sayfasında da güzel ve kapsamlı bir örnek var. O da mesafe birimlerini kullanıyor ama biraz daha farklı bir yaklaşımlar. Daha detaylı bir kullanım görmek isteyenler bu örneğe de göz atabilirler.
Evet dostlar, bir C++ 11 özelliğine daha birlikte bakmış olduk. Artık C++ 11 kabiliyetlerini bitirmeye oldukça yaklaştık. Bir sonraki yazımda görüşmek dileğiyle, kendinize iyi bakın sağlıkla kalın.
Kaynaklar
- https://en.cppreference.com/w/cpp/language/user_literal
- http://www.cplusplus.com/doc/hex/
- http://www.open-std.org/JTC1/SC22/wg21/docs/papers/2007/n2378.pdf
- https://www.modernescpp.com/index.php/user-defined-literals
- https://www.codeproject.com/Articles/447922/Application-of-Cplusplus11-User-Defined-Literals-t