سلسلة ++c للمحترفين الدرس 10: مجاري التدفق (Streams) ومعالِجاتها في Cpp


محمد الميداوي

مجرى الدخل الخاص بالمستخدم ومجرى الخرج القياسي (user input and standard output)

#include <iostream>

int main() {
    int value;
    std::cout << "Enter a value: " << std::endl;
    std::cin >> value;
    std::cout << "The square of entered value is: " << value * value << std::endl;
    return 0;
}

مجاري التدفق النصية (String streams)

std::ostringstream هو صنف كائناته تبدو كمجرى خرْج (output stream)، أي يمكنك الكتابة فيه عبر العامل ‎operator<<‎، ولكنّه في الواقع يخزّن نتائج الكتابة، ويُتيحها على هيئة مجرى.

انظر هذا المثال:

#include <sstream>
#include <string>

using namespace std;
int main() {
    ostringstream ss;
    ss << "the answer to everything is " << 42;
    const string result = ss.str();
}

ينشئ ;ostringstream ss كائنًا مثل هذا الكائن، ويُعامل الكائن أولًا كمجرى عادي:

ss << "the answer to everything is " << 42;

ثم يمكن الحصول على المجرى الناتج كما يلي:

const string result = ss.str();

(السّلسلة النصّية ‎result‎ ستساوي ‎"the answer to everything is 42"‎). ذلك مفيد في حال أردنا التمثيل النصّي لصنف سبق تعريف تسلسلِ مجراه (stream serialization). على سبيل المثال، لنفترض أنّ لدينا الصنف التالي:

class foo {
    // التعليمات والأوامر البرمجية ستكون هنا.
};
ostream &operator<<(ostream &os, const foo &f);

للحصول على التمثيل النصي للكائن ‎foo‎:

foo f;

يمكننا استخدام:

ostringstream ss;
ss << f;
const string result = ss.str();

وعليه ستحتوي ‎result‎ التمثيل النصّي للكائن ‎foo‎.

طباعة المجموعات باستخدام iostream

الطباعة الأساسية (Basic printing)

تتيح std::ostream_iterator طباعة محتويات حاويات مكتبة القوالب القياسية STL في أيّ مَجرى خرج دون الحاجة إلى استخدام الحلقات، والوسيط الثاني المُمرَّر إلى مُنشِئ ‎std::ostream_iterator‎ يعين المحدِّد (delimiter). انظر المثال التالي:

std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ! "));

سوف يطبع:

1 ! 2 ! 3 ! 4 !

التحويل الضمني للأنواع (Implicit type cast)

تتيح std::ostream_iteratorتحويل (casting) نوع محتوى الحاوية ضمنيًا. في المثال التالي، ستطبع std::cout أعدادًا عشريّة ذات ثلاث منازل عشرية:

std::cout << std::setprecision(3);
std::fixed(std::cout);

وستستنسخ‏‏ ‎std::ostream_iterator‎ باستخدام النوع ‎float‎، بينما ستظلّ القيم المُضمّنة قيمًا عددية صحيحة (‎int‎):

std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(std::cout, " ! "));

تعيد الشّيفرة أعلاه النتيجة التالية على الرغم من أنّ المتجهة (‎std::vector‎) تحتوي أعدادًا صحيحة.

1.000 ! 2.000 ! 3.000 ! 4.000 !

التوليد والتحويل

تشكّل دّوال std::generate و std::generate_n و std::transform أداة فعّالة لمعالجة البيانات، فمثلًا، انظر المتجهة التالية:

std::vector<int> v = {1,2,3,4,8,16};

نستطيع باستخدامها أن نطبع القيمة البوليانية لتعليمة x is even للأعداد الزوجية بسهولة، كما يلي:

std::boolalpha(std::cout); // طباعة القيم البوليانية أبجديًا
std::transform(v.begin(), v.end(), std::ostream_iterator<bool>(std::cout, " "),
    [](int val) {
        return (val % 2) == 0;
    });

أو طباعة مربعات العناصر:

std::transform(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "),
    [](int val) {
        return val * val;
    });

أو طباعة N عدد عشوائي تفصلهم مسافة فارغة:

const int N = 10;
std::generate_n(std::ostream_iterator<int>(std::cout, " "), N, std::rand);

المصفوفات

يمكن تطبيق جميع هذه الاعتبارات تقريبًا على المصفوفات الأصلية، تمامًا كما هو الحال في القسم الخاص بقراءة الملفات النصية. انظر المثال التالي حيث نطبع القيم التربيعيّة لعناصِر مصفوفة أصلية:

int v[] = {1,2,3,4,8,16};
std::transform(v, std::end(v), std::ostream_iterator<int>(std::cout, " "),
    [](int val) {
        return val * val;
    });

معالِجات مجاري التدفق

معالِجات مجاري التدفق (Stream manipulators) هي دوال خاصّة مساعِدة للتحكّم في مجاري الدخل والخرج باستخدام العاملين ‎operator >>‎ و ‎operator<<‎. ويمكن تضمينها عبر ‎#include <iomanip>‎. تُبادِل std::boolalpha و std::noboolalpha بين التمثيل النصي والعددي للقيَم البوليانية. انظر المثال التالي:

std::cout << std::boolalpha << 1;
// true الخرج
std::cout << std::noboolalpha << false;
// 0 الخرج
bool boolValue;
std::cin >> std::boolalpha >> boolValue;
std::cout << "Value \"" << std::boolalpha << boolValue <<
    "\" was parsed as " << std::noboolalpha << boolValue;
// true الدخل
// تحليل قيمة الدخل على أنها 0
  • تحدد كل من std::showbase و std::noshowbase إن تم استخدام السابقة (Prefix) التي تشير إلى الأساس العادي.
  • تُستخدم كل من std::dec (عشري) و std::hex (ست عشري) و std::oct (ثماني) من أجل تغيير الأساس العددي للأعداد الصحيحة.
#include <sstream>

std::cout << std::dec << 29 << ' - ' <<
    std::hex << 29 << ' - ' <<
    std::showbase << std::oct << 29 << ' - ' <<
    std::noshowbase << 29 '\n';
int number;
std::istringstream("3B") >> std::hex >> number;
std::cout << std::dec << 10;
// الخرج
// 22 - 1D - 35 - 035
// 59

القيم الافتراضية هي ‎std::ios_base::noshowbase‎ و ‎std::ios_base::dec‎. يمكنك معرفة المزيد حول ‎std::istringstream‎ من الترويسة <sstream>

  • تحدد كل من std::uppercase و std::nouppercase ما إذا كانت الأحرف الكبيرة ستُستخدم في خرْج الأعداد العشريّة والأعداد الست عشرية، وليس لها أيّ تأثير على مجاري الدخل.
std::cout << std::hex << std::showbase <<
    "0x2a with nouppercase: " << std::nouppercase << 0x2a << '\n' <<
    "1e-10 with uppercase: " << std::uppercase << 1e-10 << '\n';
// الخرج:
// 0x2a with nouppercase: 0x2a
// 1e-10 with uppercase: 1E-10

القيمة الافتراضيّة هي std::nouppercase.

  • تغيّر std::setw(n)‎ - عرض (width) حقل الدخل/الخرج التالي إلى القيمة ‎n‎، وتتم إعادة تعيين خاصية العرض ‎n‎ إلى ‎0‎ عند استدعاء بعض الدّوال (انظر القائمة الكاملة).
std::cout << "no setw:" << 51 << '\n'
<< "setw(7): " << std::setw(7) << 51 << '\n'
<< "setw(7), more output: " << 13
<< std::setw(7) << std::setfill('*') << 67 << ' ' << 94 << '\n';

char* input = "Hello, world!";
char arr[10];
std::cin >> std::setw(6) >> arr;
std::cout << "Input from \"Hello, world!\" with setw(6) gave \"" << arr << "\"\n";

// الخرج:
// 51
// setw(7):      51
// setw(7): more output: 13*****67 94

// Hello, world! :الدخل
// "Hello" تعيد  setw(6) حيث "Hello, world!" الخرج: الدخل من 

لاحظ أن الخرج في الشيفرة السابقة سيكون ("Input from "Hello, world!" with setw(6) gave "Hello). وتكون القيمة الافتراضية std::setw(0)‎.

  • تُعدِّل كل من std::left و std::right و std::internal الموضعَ الافتراضي لمحارف الملء (fill characters) من خلال ضبط std::ios_base::adjustfield إلىstd::ios_base::leftو std::ios_base::right و std::ios_base::internal على الترتيب. كذلك، تُطبَّق std::left و std::right على أيّ خرج، كما تُطبّق std::internal على الأعداد الصحيحة والعشرية والنقديّة، وليس لها أيّ تأثير على مجاري الدخل.
#include <locale>
...
std::cout << std::left << std::showbase << std::setfill('*')
<< "flt: " << std::setw(15) << -9.87 << '\n'
<< "hex: " << std::setw(15) << 41 << '\n'
<< " $: " << std::setw(15) << std::put_money(367, false) << '\n'
<< "usd: " << std::setw(15) << std::put_money(367, true) << '\n'
<< "usd: " << std::setw(15)
<< std::setfill(' ') << std::put_money(367, false) << '\n';
// :الخرج
// flt: -9.87**********
// hex: 41*************
//   $: $3.67**********
// usd: USD *3.67******
// usd: $3.67

std::cout << std::internal << std::showbase << std::setfill('*')
<< "flt: " << std::setw(15) << -9.87 << '\n'
<< "hex: " << std::setw(15) << 41 << '\n'
<< " $: " << std::setw(15) << std::put_money(367, false) << '\n'
<< "usd: " << std::setw(15) << std::put_money(367, true) << '\n'
<< "usd: " << std::setw(15)
<< std::setfill(' ') << std::put_money(367, true) << '\n';
// الخرج
// flt: -**********9.87
// hex: *************41
//   $: $3.67**********
// usd: USD *******3.67
// usd: USD        3.67

std::cout << std::right << std::showbase << std::setfill('*')
<< "flt: " << std::setw(15) << -9.87 << '\n'
<< "hex: " << std::setw(15) << 41 << '\n'
<< " $: " << std::setw(15) << std::put_money(367, false) << '\n'
<< "usd: " << std::setw(15) << std::put_money(367, true) << '\n'
<< "usd: " << std::setw(15)
     << std::setfill(' ') << std::put_money(367, true) << '\n';
// الخرج
// flt: **********-9.87
// hex: *************41
//   $: **********$3.67
// usd: ******USD *3.67
// usd:       USD  3.67

القيمة الافتراضية هي ‎std::left‎.

إليك الشيفرة التالية:

#include <sstream>
...
std::cout << '\n' <<
"The number 0.07 in fixed:      " << std::fixed << 0.01 << '\n' <<
"The number 0.07 in scientific: " << std::scientific << 0.01 << '\n' <<
"The number 0.07 in hexfloat:   " << std::hexfloat << 0.01 << '\n' <<
"The number 0.07 in default:    " << std::defaultfloat << 0.01 << '\n';
double f;
std::istringstream is("0x1P-1022");
double f = std::strtod(is.str().c_str(), NULL);
std::cout << "Parsing 0x1P-1022 as hex gives " << f << '\n';

// الخرج
// 0.070000
// 7.000000e-02
// 0x1.1eb851eb851ecp-4
// 0.07
// 2.22507e-308

القيمة الافتراضية هي ‎‎std::ios_base::fmtflags(0)‎.

هناك خلل في بعض المصرّفات يؤدي إلى النتيجة التالية:

double f;
std::istringstream("0x1P-1022") >> std::hexfloat >> f;
std::cout << "Parsing 0x1P-1022 as hex gives " << f << '\n';
// الخرج:
//  Parsing 0x1P-1022 as hex gives 0
  • تتحكم كل من std::showpoint و std::noshowpoint فيما إذا كانت الفاصلة العشرية ستُضمَّن دائمًا في تمثيل الفاصلة العائمة (floating-point representation)، وهما ليس لهما أي تأثير على مجاري الدخل.
std::cout << "7.0 with showpoint: " << std::showpoint << 7.0 << '\n'
<< "7.0 with noshowpoint: " << std::noshowpoint << 7.0 << '\n';

النّاتج سيكون:

1.0 with showpoint: 7.00000
1.0 with noshowpoint: 7

القيمة الافتراضية هي std::showpoint.

  • تتحكم كل من std::showpos و std::noshowpos فيما إذا كانت العلامة ‎+‎ ستُعرض في الخرج عند عرض الأعداد الموجبة، وهما كذلك ليس لهما أيّ تأثير على مجاري الدخل.
std::cout     << "With showpos: " << std::showpos
<< 0 << ' ' << -2.718 << ' ' << 17 << '\n'
<< "Without showpos: " << std::noshowpos
<< 0 << ' ' << -2.718 << ' ' << 17 << '\n';
// الخرج:
// With showpos: +0 -2.718 +17
// Without showpos: 0 -2.718 17

القيمة الافتراضية ‎std::noshowpos‎.

  • تتحكم كل من std::unitbuf و std::nounitbuf في تفريغ مجرى الخرج بعد كل عملية، وهما كذلك ليس لهما أي تأثير على مجاري الدخل. تنفّذ ‎std::unitbuf‎ عمليّة التفريغ.
  • يحدّد std::setbase(base)‎ الأساس العددي (numeric base) للمجرى.
  • std::setbase(8)‎ تكافئ تعيين std::ios_base::basefield إلى ‎std::ios_base::oct‎ وتعيين std::setbase(16)‎ إلى std::ios_base::hex وتعيين std::setbase(10)‎ إلى ‎std::ios_base::dec‎.

إن كان الأساس العدديّ مخالفًا لـ 8 و10 و 16، فستُعيَّن std::ios_base::basefield إلى std::ios_base::fmtflags(0)‎، وذلك يعني أنّ الخرج سيكون عشريًا، أمّا الدخل فسيتعلّق بالأساس العددي (prefix-dependent).

القيمة الافتراضيّة لـ ‎std::ios_base::basefield‎ هي ‎std::ios_base::dec‎، لذلك، فافتراضيًا سيكون لدينا الأساس العشري ‎std::setbase(10)‎

#include <cmath>
#include <limits>
...
typedef std::numeric_limits < long double > ld;
const long double pi = std::acos(-1. L);

std::cout << '\n'
<< "default precision (6): pi: " << pi << '\n'
<< " 10pi: " << 10 * pi << '\n'
<< "std::setprecision(4): 10pi: " << std::setprecision(4) << 10 * pi << '\n'
<< " 10000pi: " << 10000 * pi << '\n'
<< "std::fixed: 10000pi: " << std::fixed << 10000 * pi << std::defaultfloat <<
'\n'
<< "std::setprecision(10): pi: " << std::setprecision(10) << pi << '\n'
<< "max-1 radix precicion: pi: " << std::setprecision(ld::digits - 1) << pi << '\n'
<< "max+1 radix precision: pi: " << std::setprecision(ld::digits + 1) << pi << '\n'
<< "significant digits prec: pi: " << std::setprecision(ld::digits10) << pi << '\n';
// الخرج
// pi: 3.14159
// 10pi: 31.4159
// 10pi: 31.42
// 10000pi: 3.142e+04
// std::fixed:         10000pi: 31415.9265
// std::setprecision(10):   pi: 3.141592654
// max-1 radix precicion:   pi: 3.14159265358979323851280895940618620443274267017841339111328125
// max+1 radix precision:   pi: 3.14159265358979323851280895940618620443274267017841339111328125
// pi: 3.14159265358979324

القيمة الافتراضية هي ‎std::setprecision(6)‎.

#include <sstream>
...
std::istringstream in("10 010 10 010 10 010");
int num1, num2;

in >> std::oct >> num1 >> num2;
std::cout << "Parsing \"10 010\" with std::oct gives: " << num1 << ' ' << num2 << '\n';

// الخرج:
// Parsing "10 010" with std::oct gives: 8 8

in >> std::dec >> num1 >> num2;
std::cout << "Parsing \"10 010\" with std::dec gives:   " << num1 << ' ' << num2 << '\n';
// الخرج:
// Parsing "10 010" with std::oct gives: 10 10

in >> std::resetiosflags(std::ios_base::basefield) >> num1 >> num2;
std::cout << "Parsing \"10 010\" with autodetect gives: " << num1 << ' ' << num2 << '\n';
// الخرج:
// Parsing "10 010" with std::oct gives: 10 8

std::cout << std::setiosflags(std::ios_base::hex |
std::ios_base::uppercase |
std::ios_base::showbase) << 42 << '\n';
// الخرج: OX2A
  • تحدد كل من std::skipws و std::noskipws ما إذا كانت دوال الدّخل المُنسّق (formatted input functions) ستتجاوز المسافة البادئة الفارغة. وليس لهما أي تأثير على مجاري الخرج.
#include <sstream>
...
char c1, c2, c3;
std::istringstream("a b c") >> c1 >> c2 >> c3;
std::cout << "Default behavior: c1 = " << c1 << " c2 = " << c2 << " c3 = " << c3 << '\n';
std::istringstream("a b c") >> std::noskipws >> c1 >> c2 >> c3;
std::cout << "noskipws behavior: c1 = " << c1 << " c2 = " << c2 << " c3 = " << c3 << '\n';
//  الخرج:
// Default behaviour: c1 = a  c2 = b  c3 = c
// noskipws behavior: c1 = a  c2 =    c3 = b

القيمة الافتراضية هي ‎std::ios_base::skipws‎.

  • std::quoted(s[, delim[, escape]])‎‏ [C++14] تدرج أو تستخرج السّلاسل النصية المُقتبسة (quoted) ذات المسافات الفارغة المُدمجة.
  • s - السّلسلة النصية المرادُ إدراجها أو استخلاصها.
  • delim - المحرف المراد استخدامه كمحدّد (delimiter)، القيمة الافتراضية هي ".
  • escape - المحرف المستخدم كحرف تهريب (escape character) ، القيمة الافتراضيّة هي \.
#include <sstream>
...

std::stringstream ss;
std::string in = "String with spaces, and embedded \"quotes\" too";
std::string out;

ss << std::quoted( in );
std::cout    << "read in     [" << in << "]\n" 
<< "stored as   [" << ss.str() << "]\n";

ss >> std::quoted(out);
std::cout << "written out [" << out << "]\n";
// الخرج
//    [String with spaces, and embedded "quotes" too] تُقرأ كـ
//   ["String with spaces, and embedded \"quotes\" too"] تُخزّن كـ
//  [String with spaces, and embedded "quotes" too] تُكتب كـ

معالِجات مجاري الخرج

  • std::ends - تُدرج محرفًا فارغًا '‎\0' في مجرى الخرْج. تبدو الصيغة الرسمية لهذا المعالِج كالتالي:
template <class charT, class traits>
std::basic_ostream<charT, traits>& ends(std::basic_ostream<charT, traits>& os);

هذا المعالِجُ يُدرج المحرفَ الفارغَ عبر استدعاء ‎os.put(charT())‎ عند استخدامه في تعبير ‎os << std::ends;‎

  • يفرِّغ كلا من std::endl و std::flush مجرى الإخراج ‎out‎ عن طريق استدعاء out.flush()‎، ممّا يؤدّي إلى عرض الخرج على الفور. لكنّ ‎std::endl‎ تدرج رمز نهاية السّطر ‎'\n'‎ قبل التفريغ. انظر:
std::cout    << "First line." << std::endl << "Second line. " << std::flush
<< "Still second line.";

الناتج:

First line.
Second line. Still second line.
  • std::setfill(c)‎ - تغيّر محرف الملء (fill character) إلى ‎c‎، تُستخدم غالبًا مع std::setw.
std::cout    << "\nDefault fill: " << std::setw(10) << 79 << '\n'
<< "setfill('#'): " << std::setfill('#')
<< std::setw(10) << 42 << '\n';
// الخرج:
// Default fill:         79
// setfill('#'): ########79
  • std::put_money(mon[, intl])‎‏‏ [C++11] - تُحوِّل القيمة النقدية ‎mon‎ (من النوع ‎long double‎ أو ‎std::basic_string‎ ) في التعبير ‎out << std::put_money(mon, intl)‎ إلى تمثيلها المحرفيّ كما هو محدد في std::money_put في الإعدادات المحلّية الخاصّة بالعملة الموجودة في ‎out‎. استخدم سلاسل العملات الدولية إذا كانت ‎intl‎ تساوي true وإلا فاستخدم رموز العملات.
long double money = 123.45;
// std::string money = "123.45"; :أو

std::cout.imbue(std::locale("en_US.utf8"));
std::cout    << std::showbase << "en_US: " << std::put_money(money)
<< " or " << std::put_money(money, true) << '\n';
// en_US: $1.23 or USD  1.23 :الخرج

std::cout.imbue(std::locale("ru_RU.utf8"));
std::cout    << "ru_RU: " << std::put_money(money)
<< " or " << std::put_money(money, true) << '\n';
// ru_RU: 1.23 руб or 1.23 RUB :الخرج

std::cout.imbue(std::locale("ja_JP.utf8"));
std::cout    << "ja_JP: " << std::put_money(money)
<< " or " << std::put_money(money, true) << '\n';
// ja_JP: ¥123 or JPY  123 :الخرج
  • std::put_time(tmb, fmt)‎‏‏ [C++11] - تُنسّق وتُخرِج قيمة تقويم تاريخ/وقت إلى std::tm بحسب تنسيق fmt.
  • tmb - مؤشّر إلى بنية التقويم (calendar time structure)‏‏ const std::tm*‎ كما أعادته localtime()‎ أو gmtime()‎.
  • fmt - مؤشّر إلى سلسلة نصّية منتهية بقيمة فارغة const CharT*‎ تمثّل تنسيق التحويل.
#include <ctime>
...
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);

std::cout.imbue(std::locale("ru_RU.utf8"));
std::cout << "\nru_RU: " << std::put_time(&tm, "%c %Z") << '\n';
// الخرج الممكن
// ru_RU: Вт 04 июл 2017 15:08:35 UTC

معالِجات مجاري الدخل (Input stream manipulators)

  • تستهلك std::ws المسافات البادئة في مجرى الدخل، وهي مختلفة عن std::skipws.
#include <sstream>
...
std::string str;
std::istringstream("  \v\n\r\t    Wow!There   is no whitespaces!") >> std::ws >> str;
std::cout << str;

// الخرج
// Wow!There   is no whitespaces!
  • std::get_money(mon[, intl])‎‏ [C++11] - في تعبير in >> std::get_money(mon, intl)‎، تحلّل دخل المحارف كقيمة نقديّة كما هو محدّد بواسطة std::money_get في الإعدادات المحلية في in، وتخزّن القيمة في mon (من نوع long double أو std::basic_string). سيتوقّع المعالِج سلاسل العملات الدوليّة المطلوبة إذا كانت intl تساوي true، وإلا فسَيتوقع رموز عملات اختيارية.
#include <sstream>
#include <locale>
...
std::istringstream in("$1,234.56 2.22 USD 3.33");
long double v1, v2;
std::string v3;

in.imbue(std::locale("en_US.UTF-8"));
in >> std::get_money(v1) >> std::get_money(v2) >> std::get_money(v3, true);
if    (in) {
std::cout    << std::quoted(in.str()) << " parsed as: "
<< v1 << ", " << v2 << ", " << v3 << '\n';
}
// الخرج
// "$1,234.56 2.22 USD  3.33" parsed as: 123456, 222, 333
  • تحلّل std::get_time(tmb, fmt)‎ ‏‏[C++11] قيمة التاريخ/الوقت المُخزّنة في ‎tmb‎ بتنسيق ‎fmt‎ المحدد.
  • ‎tmb‎ - مؤشّر صالح للكائن const std::tm*‎ حيث ستُخزَّن النتيجة.
  • ‎fmt‎ - مؤشّر إلى سلسلة نصيّة منتهية بقيمة فارغة const CharT*‎ تمثّل تنسيق التحويل.
#include <sstream>
#include <locale>
...
std::tm t = {};
std::istringstream ss("2011-Februar-18 23:12:34");
ss.imbue(std::locale("de_DE.utf-8"));
ss >> std::get_time(&t, "%Y-%b-%d %H:%M:%S");
if (ss.fail()) {
std::cout << "Parse failed\n";
}
else {
std::cout << std::put_time(&t, "%c") << '\n';
}
// الخرج
// Sun Feb 18 23:12:34 2011

هذا الدرس جزء من سلسلة مقالات عن C++‎.

ترجمة -بتصرّف- للفصل Chapter 13: C++ Streams والفصل Chapter 14: Stream manipulators من كتاب C++ Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن