C++ Şablonlarına Alternatif Olarak Sahte Şablonları Kullanma
Tanıtım
Şablonların bir dil standardı olarak uygulanması sorunu, mql5.com forumunda birçok kez gündeme getirildi. MQL5 geliştiricilerinin reddetme duvarı ile karşı karşıya kaldığımda, özel yöntemler kullanarak şablonların uygulanmasına olan ilgim artmaya başladı. Çalışmalarımın sonucu bu makalede sunulmuştur.
C ve C++'ın Kısa Tarihçesi
C dili en başından beri, sistem görevlerini gerçekleştirme imkanı sunmak için geliştirildi. C dilinin yaratıcıları, dilin soyut bir yürütme ortamı modelini uygulamak yerine sadece sistem programcılarının ihtiyaçları için özellikler uyguladılar. Hepsinden önce, bunlar bellekle doğrudan çalışma yöntemleri, kontrol yapılarının iskeleti ve uygulamaların modül yönetimidir.
Aslında, dile daha fazlası eklenmedi; diğer tüm öğeler çalışma zamanı kitaplığına alındı. Bu yüzden bazı kötü niyetli kişiler bazen C dilinden yapılandırılmış derleyici olarak bahsederler. Ama ne derse desinler, yaklaşım çok başarılı görünüyordu. Bu sayede dilin sadeliği ve gücü arasında yeni bir orantı düzeyine ulaşıldı.
Böylece C, sistem programlama için evrensel bir dil olarak ortaya çıktı. Ama bu sınırlar içinde de kalmadı. 80'lerin sonlarında Fortran'ı liderlik konumundan indiren C, tüm dünyadaki programcılar arasında yaygın bir popülerlik kazandı ve farklı uygulamalarda yaygın bir şekilde kullanılmaya başlandı. Unix'in (dolayısıyla C dilinin) yeni nesil programcıların yetiştirildiği üniversitelerde dağıtılması popülaritesine önemli bir katkı sağlamıştır.
Madem her şey bu kadar sorunsuz da o zaman neden hala diğer diller de kullanılıyor ve var olmalarını destekleyen nedir? C dilinin Aşil topuğu, 90'larda belirlenen problemler için çok düşük seviyede. Bu sorunun iki yönü vardır.
İlk olarak, dil çok düşük seviyeli araçlar içerir: her şeyden önce, bellek ve adres aritmetiği ile çalışmadır. Bu nedenle, işlemci bit değerindeki bir değişiklik, birçok C uygulaması için çok fazla soruna neden olur. Öte yandan, C - soyut veri ve nesne türleri, polimorfizm ve istisnaların ele alınmasında üst düzey araçların eksikliği vardır. Bu nedenle, C uygulamalarında, bir görevin uygulanmasına yönelik bir teknik, genellikle onun maddi yönüne hakimdir.
Bu dezavantajları düzeltmeye yönelik ilk girişimler 80'lerin başında yapıldı. O sırada AT&T Bell Laboratuvarlarında Bjarne Stroustrup, C dilinin "sınıflı C" uzantısını geliştirmeye başladı. Geliştirme tarzı, belirli grupların çalışmasını daha uygun hale getirmek için farklı özellikler ekleyerek C diline özgü yaratma ruhunu karşılamış oldu.
C++'daki başlıca yenilik, yeni veri türlerini kullanma imkanı veren sınıfların mekanizmasıdır. Bir programcı, sınıf nesnesinin dahili olarak temsilini ve bu temsile erişmek için kullanılan işlev-yöntemler kümesini tanımlar. C++'ın yaratılmasının temel amaçlarından biri, önceden yazılmış kodun yeniden kullanım oranını artırmaktı.
C++ dilinin yeniliği sadece sınıfların tanıtılmasından ibaret değildir. İstisnaların yapısal olarak ele alınması mekanizmasını (eksikliği, güvenli uygulamaların geliştirilmesini karmaşıklaştırdı), şablonların mekanizmasını ve diğer birçok şeyi uygulamıştır. Bu nedenle, dilin ana gelişim çizgisi, ANSI С ile tam uyumluluğu korurken yeni üst düzey yapılar getirerek olanaklarını genişletmeye yönlendirildi.
Makro Yerleştirme Mekanizması Olarak Şablon
MQL5'te bir şablonun nasıl uygulanacağını anlamak için bunların C++'da nasıl çalıştığını anlamanız gerekir.
tanımını görelim.
MQL5'in şablonları yoktur, ancak bu, şablonlarla bir programlama stili kullanmanın imkansız olduğu anlamına gelmez. C++ dilindeki şablonların mekanizması, aslında, dilde derine yerleşik olan karmaşık bir makro oluşturma mekanizmasıdır. Başka bir deyişle, bir programcı bir şablon kullandığında, derleyici, bildirildiği yerde değil, karşılık gelen işlevin çağrıldığı veri türünü belirler.
Programcılar tarafından yazılan kod miktarını azaltmak için C++ ile tanıtılan şablonlar. Ancak bir programcı tarafından klavyede yazılan bir kodun derleyici tarafından oluşturulan kodla aynı olmadığını unutmamalısınız. Şablonların mekanizması, programların boyutunun küçülmesine neden olmadı; sadece kaynak kodlarının boyutunu küçülttü. Bu nedenle şablonlar kullanılarak çözülen asıl sorun, programcılar tarafından yazılan kodların azaltılmasıdır.
Makine kodu derleme sırasında oluşturulduğu için sıradan programcılar bir fonksiyonun kodunun bir kez mi yoksa birkaç kez mi üretildiğini görmezler. Bir şablon kodunun derlenmesi sırasında, işlevin kodu, şablonun kullanıldığı tür sayısı kadar üretilir. Temel olarak, bir şablon derleme aşamasında geçersizdir.
Şablonları C++'a tanıtmanın ikinci yönü, bellek atamasıdır. Mesele, C dilinde belleğin statik olarak atanmasıdır. Bu atamayı daha esnek hale getirmek üzere diziler için bellek boyutunu ayarlayan bir şablon kullanılır. Ancak bu yön, MQL4 geliştiricileri tarafından dinamik diziler biçiminde zaten uygulandı ve MQL5'te dinamik nesneler biçiminde de yapıldı.
Böylece, yalnızca türlerin yerleşimi sorunu çözülmeden kalır. MQL5 geliştiricileri, şablon değiştirme mekanizmasının kullanılmasına atıfta bulunarak, sorunu çözmeyi reddetti, derleyicinin kırılmasına izin verecek, bu da bir kod çözücünün ortaya çıkmasına neden olacaktı.
Eh, daha iyi bilirler. Ve tek bir seçeneğimiz var - bu paradigmayı özel bir şekilde uygulamak.
Öncelikle derleyiciyi veya dilin standartlarını değiştirmeyeceğimizi belirtelim. Yaklaşımı şablonların kendilerine uygulayarak değiştirmenizi öneririm. Derleme aşamasında şablon oluşturamıyorsak, bu makine kodunu yazmamıza izin verilmediği anlamına gelmez. Şablonların kullanımını ikili kod oluşturma bölümünden metin kodunun yazıldığı bölüme taşımanızı öneririm. Bu yaklaşıma "sahte şablonlar" diyelim.
Sahte Şablonlar
Bir sahte şablonun, bir C++ şablonuna kıyasla avantajları ve dezavantajları vardır. Dezavantajları, dosyaların taşınmasıyla ek manipülasyonları içerir. Avantajlar, dilin standartlarının belirlediğinden daha esnek olasılıkları içerir. Sözlerden eylemlere geçelim.
Sahte şablonları kullanmak için bir önişlemci analoğuna ihtiyacımız var. Bu amaçla 'Şablonlar' betiğini kullanacağız. Komut dosyası için genel gereksinimler şunlardır: belirli bir dosyayı okumalı (veri yapısını koruyarak), bir şablon bulmalı ve belirtilen türlerle değiştirmelidir.
Burada bir açıklama yapmam gerekiyor. Şablonlar yerine geçersiz kılma mekanizmasını kullanacağımız için geçersiz kılınması gereken türlerin sayısı kadar kod yeniden yazılacaktır. Başka bir deyişle, yerleştirme, analiz için verilen kodun tamamında gerçekleştirilecektir. Ardından, kod, her seferinde yeni bir yerleştirme oluşturarak, komut dosyası tarafından birkaç kez yeniden yazılacaktır. Böylece "makineler tarafından yapılan manuel iş" sloganını gerçekleştirebiliriz.
Komut Dosyası Kodunu Geliştirme
Gerekli girdi değişkenlerini belirleyelim:
- İşlenecek dosyanın adı.
- Geçersiz kılınacak veri türünü depolamak için bir değişken.
- Gerçek veri türleri yerine kullanılacak bir şablonun adı.
input string folder="Example templat";//name of file for processing input string type="long;double;datetime;string" ;//names of custom types, separator ";" string TEMPLAT="_XXX_";// template name
Komut dosyasının kodun yalnızca bir bölümünü çarpmasını sağlamak için işaretçilerin adlarını ayarlayın. Açılış işareti işlenecek parçanın başlangıcını ve kapanış işareti - sonunu belirtmek için tasarlanmıştır.
Senaryoyu kullanırken, işaretçileri okuma sorunuyla karşılaştım.
Analiz sırasında, MetaEditor'da bir belgeyi biçimlendirirken, yorum satırlarına genellikle bir boşluk veya tablolamanın (duruma bağlı olarak) eklendiğini keşfettim. İşaretçileri belirlerken önemli bir sembolün önündeki ve arkasındaki boşluklar silinerek sorun çözüldü. Bu özellik betikte otomatik olarak gerçekleştiriliyor ancak şöyle bir not var.
Bir işaretçi adı boşlukla başlamamalı veya bitmemelidir.
Kapanış işareti zorunlu değildir; yoksa, kod dosyanın sonuna kadar işlenecektir. Ama bir açılış işareti olması gerekmektedir. İşaretçilerin isimleri sabit olduğu için değişkenler yerine #define önişlemci yönergesini kullanıyorum.
#define startread "//start point" #define endread "//end point"
Bir tür dizisi oluşturmak için type_dates[] dizisini 'type' değişkenini kullanarak değerlerle dolduran void ParserInputType(int i,string &type_d[],string text) işlevini oluşturdum.
Komut dosyası bir dosyanın adını ve işaretçileri aldığında dosyayı okumaya başlar. Belgenin biçimlendirmesini kaydetmek için komut dosyası, bulunan satırları dizide kaydederek bilgileri satır satır okur.
Tabii ki, her şeyi tek bir değişkende eritebilirsiniz; ancak bu durumda hecelemeyi kaybedersiniz ve metin sonsuz bir satıra dönüşür. Bu nedenle dosyayı okuma işlevi, yeni bir dize almanın her yinelemesinde boyutunu değiştiren dize dizisini kullanır.
//+------------------------------------------------------------------+ //| downloading file | //+------------------------------------------------------------------+ void ReadFile() { string subfolder="Templates"; int han=FileOpen(subfolder+"\\"+folder+".mqh",FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI,"\r"); if(han!=INVALID_HANDLE) { string temp=""; //--- scrolling file to the starting point do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);} while(startread!=temp); string text=""; int size; //--- reading the file to the array until a break point or the end of the file while(!FileIsEnding(han)) { temp=text=FileReadString(han); // deleting symbols of tabulation to check the end StringTrimLeft(temp);StringTrimRight(temp); if(endread==temp)break; // flushing data to the array if(text!="") { size=ArraySize(fdates); ArrayResize(fdates,size+1); fdates[size]=text; } } FileClose(han); } else { Print("File open failed"+subfolder+"\\"+folder+".mqh, error",GetLastError()); flagnew=true; } }
Kullanım kolaylığı için dosya FILE_SHARE_READ modunda açılır. Düzenlenen dosyayı kapatmadan komut dosyasını başlatma imkanı verir. Dosya uzantısı 'mqh' olarak belirtilir. Böylece, komut dosyası, içerme dosyasında depolanan kodun metnini doğrudan okur. Mesele 'mqh' uzantılı bir dosyadır, aslında bir metin dosyasıdır; Dosyayı 'txt' olarak yeniden adlandırarak ve herhangi bir metin düzenleyiciyi kullanarak 'mqh' dosyasını açarak emin olabilirsiniz.
Okumanın sonunda, dizinin uzunluğu, başlangıç ve bitiş işaretleri arasındaki satır sayısına eşittir.
Şimdi bilgi analizine dönelim. Bilgileri analiz eden ve değiştiren işlev, bir dosyaya yazma işlevinden çağrılır void WriteFile(int count). Yorumlar fonksiyonun içinde verilmiştir.
void WriteFile(int count) { ... if(han!=INVALID_HANDLE) { if(flagnew)// if the file cannot be read { ... } else {// if the file exists ArrayResize(tempfdates,count); int count_type=ArraySize(type_dates); //--- the cycle rewrites the contents of the file for each type of the type_dates template for(int j=0;j<count_type;j++) { for(int i=0;i<count;i++) // copy data into the temporary array tempfdates[i]=fdates[i]; for(int i=0;i<count;i++) // replace templates with types Replace(tempfdates,i,j); for(int i=0;i<count;i++) FileWrite(han,tempfdates[i]); // flushing array in the file } } ... }
Veriler yerinde değiştirildiği ve dönüşümden sonra dizi değiştirildiği için bir kopyası ile çalışacağız. Burada verilerin geçici olarak depolanması için kullanılan tempfdates[] dizisinin boyutunu ayarlıyoruz ve fdates[] örneğine göre dolduruyoruz.
Ardından, Replace() işlevi kullanılarak şablonların değiştirilmesi gerçekleştirilir. İşlevin parametreleri şunlardır: işlenecek dizi (şablonun değiştirilmesinin gerçekleştirildiği yer), i satırlarının sayacı (dizi içinde hareket etmek için) ve j türlerinin sayacı (tür dizisinde gezinmek için).
İç içe iki döngümüz olduğu için kaynak kodu belirtilen türler kadar yazdırılır.
//+------------------------------------------------------------------+ //| replacing templates with types | //+------------------------------------------------------------------+ void Replace(string &temp_m[],int i,int j) { if(i>=ArraySize(temp_m))return; if(j<ArraySize(type_dates)) StringReplac(temp_m[i],TEMPLAT,type_dates[j]);// replacing templat with types }
Replace() işlevi denetimler içerir (bir dizinin var olmayan bir dizininin çağrılmasını önlemek için) ve iç içe geçmiş StringReplac() işlevini çağırır. İşlev adının standart StringReplace işlevine benzer olmasının bir nedeni vardır, aynı sayıda parametreye de sahiptirler.
Böylece yalnızca bir "e" harfi ekleyerek, değiştirmenin tüm mantığını değiştirebiliriz. Standart işlev, 'find' örneğinin değerini alır ve onu belirtilen 'değiştirme' dizesiyle değiştirir. Ve işlevim yalnızca yer değiştirmeyi değil, aynı zamanda 'find' komutundan önce sembol olup olmadığını da analiz eder (yani, 'find' komutunun bir kelimenin bir parçası olup olmadığını kontrol eder); ve varsa, 'find' yerine 'replacement' gelir ancak büyük durumda, aksi takdirde değiştirme olduğu gibi yapılır. Bu nedenle, ayar türlerine ek olarak, geçersiz kılınan verilerin adlarında bunları kullanabilirsiniz.
Yenilikler
Şimdi kullanım sırasında eklenen yeniliklerden bahsedeyim. Senaryoyu kullanırken işaretçi okuma sorunları olduğunu zaten belirtmiştim.
Sorun, void ReadFile() işlevinin içindeki aşağıdaki kodla çözülür:
string temp=""; //--- scrolling the file to the start point do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);} while(startread!=temp);
Döngünün kendisi önceki sürümde uygulandı, ancak StringTrimLeft() ve StringTrimRight() işlevleri kullanılarak tablolama sembollerinin kesilmesi yalnızca geliştirilmiş sürümde göründü.
Ayrıca yenilikler arasında çıktı dosyasının adından "templat" uzantısının kesilmesi de vardır, böylece çıktı dosyası kullanıma hazır hale gelir. Belirtilen bir dizeden belirli bir örneğin silme işlevi kullanılarak uygulanır.
Silme işlevinin kodu:
//+------------------------------------------------------------------+ //| Deleting the 'find' template from the 'text' string | //+------------------------------------------------------------------+ string StringDel(string text,const string find) { string str=text; StringReplace(str,find,""); return(str); }
Bir dosya adının kesilmesini gerçekleştiren kod, void WriteFile(int count) işlevinde bulunur:
string newfolder; if(flagnew)newfolder=folder;// if it is the first start, create an empty file of pre-template else newfolder=StringDel(folder," templat");// or create the output file according to the template
Buna ek olarak, bir ön şablonun hazırlanma şekli tanıtılır. Dosyalar/Şablonlar dizininde gerekli dosya yoksa, ön şablon dosyası olarak oluşturulacaktır.
Örnek:
//#define _XXX_ long //this is the start point _XXX_ //this is the end point
Bu satırları oluşturan kod, void WriteFile(int count) işlevinde bulunur:
if(flagnew)// if the file couldn't be read {// fill the template file with the pre-template FileWrite(han,"#define "+TEMPLAT+" "+type_dates[0]); FileWrite(han," "); FileWrite(han,startread); FileWrite(han," "+TEMPLAT); FileWrite(han,endread); Print("Creating pre-template "+subfolder+"\\"+folder+".mqh"); }
Kodun yürütülmesi, dosya okunurken bir hata olması durumunda 'doğru' değeri alan flagnew global değişkeni tarafından korunur.
Komut dosyasını kullanırken ek bir şablon ekledim. İkinci şablonun bağlantı süreci aynıdır. İçlerinde değişiklik gerektiren işlevler, ek bir şablonun bağlanması için OnStart() işlevine daha yakın yerleştirilir. Yeni şablonları bağlamak için bir yol dövüldü. Böylece ihtiyaç duyduğumuz kadar çok şablon bağlama imkanımız var. Şimdi işlemi kontrol edelim.
Kontrol İşlemi
Her şeyden önce, tüm gerekli parametreleri belirten betiği başlatalım. Açılan pencerede "Örnek şablon" dosya adını belirtin.
Özel veri türlerinin alanlarını ayırıcı olarak ';' işaretini kullanarak doldurun.
"Tamam" düğmesine basılır basılmaz Şablonlar dizini oluşturulur; "Örnek templat.mqh" ön şablon dosyasını içerir.
Bu olay, günlükte şu mesajla görüntülenir:
Ön şablonu değiştirelim ve betiği bir kez daha başlatalım. Bu sefer dosya, Şablonlar dizininde (ve dizinin kendisinde) zaten var, bu yüzden dosyanın açılmasıyla ilgili hata mesajı görüntülenmeyecek. Değiştirme, belirtilen şablona göre gerçekleştirilecektir:
//this_is_the_start_point _XXX_ Value_XXX_; //this_is_the_end_point
Oluşturulan "Example.mqh" dosyasını bir kez daha açın.
long ValueLONG; double ValueDOUBLE; datetime ValueDATETIME; string ValueSTRING;
Gördüğünüz gibi parametre olarak geçtiğimiz tip sayısına göre tek satırdan 4 satır yapılıyor. Şimdi şablon dosyasına aşağıdaki iki satırı yazın:
//this_is_the_start_point _XXX_ Value_XXX_; _XXX_ Type_XXX_; //this_is_the_end_point
Sonuç, betik işleminin mantığını net bir şekilde göstermektedir.
Öncelikle kodun tamamı bir tür veri ile yeniden yazılır, ardından başka bir türün işlenmesi gerçekleştirilir. Bu, tüm türler işlenene kadar tekrar edilir.
long ValueLONG; long TypeLONG; double ValueDOUBLE; double TypeDOUBLE; datetime ValueDATETIME; datetime TypeDATETIME; string ValueSTRING; string TypeSTRING;
Şimdi ikinci şablonu örnek metnine ekleyin.
//this_is_the_start_point _XXX_ Value_XXX_(_xxx_ ind){return((_XXX_)ind);}; _XXX_ Type_XXX_(_xxx_ ind){return((_XXX_)ind);}; //this_is_the_end_button
Sonuç:
long ValueLONG(int ind){return((long)ind);}; long TypeLONG(int ind){return((long)ind);}; double ValueDOUBLE(float ind){return((double)ind);}; double TypeDOUBLE(float ind){return((double)ind);}; datetime ValueDATETIME(int ind){return((datetime)ind);}; datetime TypeDATETIME(int ind){return((datetime)ind);}; string ValueSTRING(string ind){return((string)ind);}; string TypeSTRING(string ind){return((string)ind);};
Son örnekte, son satırdan sonra bilerek boşluk bıraktım. Bu boşluk, betiğin bir türün işlenmesini nerede sonlandırdığını ve bir başkasını işlemeye başladığını gösterir. İkinci şablonla ilgili olarak, türlerin işlenmesinin birinci şablona benzer şekilde yapıldığını not edebiliriz. İlk şablonun bir türü için karşılık gelen bir tür bulunamazsa, hiçbir şey yazdırılmaz.
Şimdi kodda hata ayıklama sorusuna açıklık getirmek istiyorum. Verilen örnekler hata ayıklama için oldukça basittir. Programlama sırasında, kodun oldukça büyük bir bölümünde hata ayıklamanız ve yapılır yapılmaz çarpmanız gerekebilir. Bunu yapmak için ön şablonda ayrılmış bir yorum satırı vardır: "//#define _XXX_ long".
Yorumları kaldırırsanız, şablonumuz gerçek bir tür olacaktır. Başka bir deyişle, derleyiciye şablonun nasıl yorumlanması gerektiğini anlatacağız.
Ne yazık ki, tüm türlerde bu şekilde hata ayıklayamayız. Ancak bir türde hata ayıklayabilir ve ardından 'define' içinde şablonun türünü değiştirebiliriz; böylece tüm türleri tek tek ayıklayabiliriz. Elbette hata ayıklama için dosyayı çağrılan dosyanın bulunduğu dizine veya Include dizinine taşımamız gerekiyor. Daha önce sahte şablonların dezavantajlarından bahsederken bahsettiğim hata ayıklamanın sakıncası budur.
Sonuç
Sonuç olarak, sözde şablon kullanma fikri ilginç ve oldukça üretken olsa da, sadece küçük bir uygulama başlangıcı olan bir fikir olduğunu söylemek istiyorum. Yukarıda açıklanan kod çalışıyor ve benim için kodu yazarken çok fazla saat kazandırmış olsa da, birçok soru hala açık. Her şeyden önce, standartların geliştirilmesi sorunudur.
Komut dosyam, şablonların blok değişimini uygular. Ancak bu yaklaşım zorunlu değildir. Belirli kuralları yorumlayan daha karmaşık bir çözümleyici oluşturabilirsiniz. Ancak buradan başlıyoruz. Büyük bir tartışma umuduyla. Düşünce çelişkilerden beslenir. İyi şanslar!
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/253
- Ücretsiz ticaret uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz