• اندازه نوع کوتاه بدون امضا. انواع داده در C. برنامه نویسی C

    مبانی زبان

    کد برنامه و داده هایی که برنامه دستکاری می کند به صورت دنباله ای از بیت ها در حافظه کامپیوتر نوشته می شود. بیتکوچکترین عنصر است حافظه کامپیوتر، قابلیت ذخیره 0 یا 1 را دارد. روشن سطح فیزیکیمطابقت دارد ولتاژ الکتریکی، که معلوم است یا وجود دارد یا نه. با نگاهی به محتویات حافظه کامپیوتر، چیزی شبیه به زیر را مشاهده خواهیم کرد:
    ...

    درک چنین دنباله ای بسیار دشوار است، اما گاهی اوقات ما نیاز به دستکاری چنین داده های بدون ساختار داریم (معمولاً نیاز به این امر هنگام برنامه نویسی درایورهای دستگاه های سخت افزاری ایجاد می شود). C++ مجموعه ای از عملیات را برای کار با داده های بیتی ارائه می دهد. (ما در این مورد در فصل 4 صحبت خواهیم کرد.)
    به عنوان یک قاعده، ساختاری به دنباله ای از بیت ها تحمیل می شود و بیت ها را در گروه بندی می کنند بایت هاو کلمات. یک بایت شامل 8 بیت و یک کلمه شامل 4 بایت یا 32 بیت است. با این حال، تعریف یک کلمه می تواند در سیستم عامل های مختلف متفاوت باشد. اکنون انتقال به سیستم های 64 بیتی آغاز شده است و اخیراً سیستم هایی با کلمات 16 بیتی رایج شده اند. اگرچه اندازه بایت در اکثریت قریب به اتفاق سیستم ها یکسان است، ما همچنان به این مقادیر به عنوان دستگاه خاص اشاره خواهیم کرد.

    حالا می توانیم مثلاً در مورد یک بایت در آدرس 1040 یا یک کلمه در آدرس 1024 صحبت کنیم و بگوییم که بایت آدرس 1032 با بایت آدرس 1040 برابر نیست.
    با این حال، ما نمی دانیم که هر بایت، هر کلمه ماشینی چه چیزی را نشان می دهد. چگونه معنی 8 بیت خاص را بفهمیم؟ برای اینکه معنی این بایت (یا کلمه یا مجموعه دیگری از بیت ها) را بدون ابهام تفسیر کنیم، باید نوع داده های نمایش داده شده توسط این بایت را بدانیم.
    C++ مجموعه ای از انواع داده های داخلی را ارائه می دهد: کاراکتر، عدد صحیح، واقعی، و مجموعه ای از انواع ترکیبی و توسعه یافته: رشته ها، آرایه ها، اعداد مختلط. علاوه بر این، برای اقدامات با این داده ها، وجود دارد مجموعه پایهعملیات: مقایسه، حساب و عملیات دیگر. همچنین انتقال، حلقه، عبارات شرطی وجود دارد. این عناصر زبان C++ مجموعه‌ای از بلوک‌های ساختمانی را تشکیل می‌دهند که می‌توانید از آن‌ها سیستمی با هر پیچیدگی بسازید. اولین گام در تسلط بر C++ یادگیری این عناصر اساسی است که قسمت دوم این کتاب به آن اختصاص دارد.
    فصل 3 یک نمای کلی از انواع داخلی و توسعه یافته و مکانیسم هایی که توسط آنها می توانید انواع جدید ایجاد کنید ارائه می دهد. البته، اساساً این مکانیسم کلاسی است که در بخش 2.3 معرفی شده است. فصل 4 به عبارات، عملگرهای داخلی و اولویت آنها و تبدیل نوع می پردازد. فصل 5 در مورد دستورالعمل های زبان صحبت می کند. در نهایت، فصل 6 کتابخانه استاندارد C++ و انواع کانتینر، بردار و آرایه انجمنی را معرفی می کند.

    3. انواع داده C++

    این فصل یک نمای کلی ارائه می دهد تعبیه شده است، یا ابتدایی، انواع داده های زبان C++. با تعریف شروع می شود به معنای واقعی کلمهمانند 3.14159 یا pi و سپس مفهوم متغیر، یا هدف - شی، که باید یکی از انواع داده ها باشد. بقیه فصل به این موضوع اختصاص دارد توصیف همراه با جزئیاتهر نوع داخلی همچنین انواع داده های مشتق شده را برای رشته ها و آرایه های ارائه شده توسط کتابخانه استاندارد C++ فهرست می کند. اگرچه این انواع ابتدایی نیستند، اما برای نوشتن برنامه های C++ واقعی بسیار مهم هستند و می خواهیم هر چه زودتر خواننده را با آنها آشنا کنیم. ما چنین انواع داده ای را فراخوانی خواهیم کرد افزونهانواع پایه C++.

    3.1. تحت اللفظی

    C++ دارای مجموعه ای از انواع داده های داخلی برای نمایش اعداد صحیح و واقعی، نمادها و همچنین یک نوع داده "آرایه کاراکتر" است که برای ذخیره سازی استفاده می شود. رشته های کاراکتر. نوع char برای ذخیره کاراکترها و اعداد صحیح کوچک استفاده می شود. یک بایت ماشین را اشغال می کند. انواع short، int و long برای نمایش اعداد صحیح طراحی شده اند. این انواع فقط در محدوده مقادیری که اعداد می توانند بگیرند متفاوت هستند و اندازه های خاص انواع ذکر شده به اجرا بستگی دارد. معمولا short نصف کلمه ماشینی است، int یک کلمه، long یک یا دو کلمه است. در سیستم های 32 بیتی، int و long معمولاً یک اندازه هستند.

    انواع float، double و long double برای اعداد ممیز شناور هستند و از نظر دقت نمایش (تعداد ارقام قابل توجه) و محدوده متفاوت هستند. به طور معمول، شناور (تک دقت) یک کلمه ماشینی، double (دقت مضاعف) دو، و long double (دقت توسعه یافته) به سه کلمه نیاز دارد.
    char، short، int و long با هم تشکیل می دهند انواع عدد صحیح، که به نوبه خود می تواند باشد نمادین(امضا) و بدون امضا(بدون امضا). در انواع علامت دار، از سمت چپ ترین بیت برای ذخیره علامت استفاده می شود (0 مثبت، 1 منهای)، و بیت های باقی مانده حاوی مقدار هستند. در انواع بدون علامت، همه بیت ها برای مقدار استفاده می شوند. یک کاراکتر علامت دار 8 بیتی می تواند مقادیری از 128- تا 127 را نشان دهد و یک کاراکتر بدون علامت می تواند مقادیر 0 تا 255 را نشان دهد.

    وقتی در برنامه ای با عددی مانند 1 مواجه می شود، این عدد فراخوانی می شود تحت اللفظی، یا ثابت تحت اللفظی. یک ثابت، زیرا نمی توانیم مقدار آن را تغییر دهیم، و یک لفظ، زیرا مقدار آن در متن برنامه ظاهر می شود. Literal یک مقدار غیر آدرس‌پذیر است: اگرچه در واقع در حافظه دستگاه ذخیره می‌شود، اما راهی برای دانستن آدرس آن وجود ندارد. هر لفظی نوع خاصی دارد. بنابراین، 0 از نوع int، 3.14159 از نوع double است.

    حروف نوع صحیح را می توان به صورت اعشاری، اکتال و هگزادسیمال نوشت. در اینجا عدد 20 به صورت اعشاری، اکتال و هگزادسیمال به نظر می رسد:

    20 // اعشار
    024 // اکتال
    0x14 // هگز

    اگر حرف تحت اللفظی با 0 شروع شود به عنوان اکتال و اگر با 0x یا 0X شروع شود به عنوان هگزادسیمال در نظر گرفته می شود. نماد معمولی به عنوان یک عدد اعشاری در نظر گرفته می شود.
    به‌طور پیش‌فرض، تمام اعداد صحیح به صورت int امضا می‌شوند. شما می توانید با الحاق حرف L به انتهای عدد، صریحاً یک عدد صحیح را از نوع طولانی تعریف کنید (هم از L بزرگ و هم از L کوچک استفاده می شود، اما برای خوانایی، حروف کوچک نباید استفاده شود: به راحتی می توان آن را با حروف کوچک اشتباه گرفت.

    1). U (یا u) در انتها حرف را به صورت int بدون علامت و دو حرف UL یا LU را به صورت طولانی بدون علامت تعریف می کند. مثلا:

    128u 1024UL 1L 8Lu

    لفظ هایی که اعداد حقیقی را نشان می دهند را می توان با نقطه اعشار یا با نماد علمی (نمایی) نوشت. به طور پیش فرض آنها از نوع double هستند. برای مشخص کردن صریح نوع شناور، باید از پسوند F یا f و برای طولانی دوبل - L یا l استفاده کنید، اما فقط در مورد علامت گذاری با نقطه اعشار. مثلا:

    3.14159F 0/1f 12.345L 0.0 3el 1.0E-3E 2.1.0L

    کلمات true و false از نوع bool هستند.
    ثابت های کاراکتر تحت اللفظی قابل نمایش به صورت نویسه های تک نقل قولی نوشته می شوند. مثلا:

    "a" "2" """ "" (فضا)

    کاراکترهای ویژه (tab، carriage return) به صورت توالی فرار نوشته می شوند. دنباله های زیر تعریف شده اند (با یک کاراکتر بک اسلش شروع می شوند):

    خط جدید \n زبانه افقی \t پشتی \b برگه عمودی \v بازگشت کالسکه \r تغذیه برگه \f زنگ \a اسلش معکوس \\ سوال \؟ نقل قول تک \" نقل قول دوگانه \"

    دنباله فرار نمای کلیبه شکل \ooo است، که در آن ooo یک تا سه رقم اکتالی است. این شماره کد کاراکتر است. با استفاده از کد اسکی می توانیم لفظ های زیر را بنویسیم:

    \7 (تماس) \14 (خط جدید) \0 (تهی) \062 ("2")

    یک کاراکتر تحت اللفظی را می توان با L (به عنوان مثال، L"a") پیشوند، که به معنای نوع خاص wchar_t، یک نوع کاراکتر دو بایتی است که برای ذخیره کاراکترهای الفبای ملی استفاده می شود، اگر آنها را نتوان با نوع char معمولی نشان داد. مانند حروف چینی یا ژاپنی.
    String Literal رشته‌ای از کاراکترهای محصور در دو گیومه است. چنین لفظی ممکن است چندین خط را شامل شود، در این صورت یک بک اسلش در انتهای خط قرار می گیرد. کاراکترهای خاص را می توان با توالی فرار خود نشان داد. در اینجا نمونه هایی از لفظ رشته ای آورده شده است:

    "" (رشته خالی) "a" "\nCC\options\tfile."

    در واقع یک رشته لفظی یک آرایه است ثابت های کاراکتر، جایی که طبق قرارداد زبان های C و C++، آخرین عنصر همیشه یک کاراکتر خاص با کد 0 (\0) است.
    حرف "A" یک کاراکتر منفرد A را مشخص می کند و رشته تحت اللفظی "A" آرایه ای از دو عنصر را مشخص می کند: "A" و \0 (یک کاراکتر خالی).
    از آنجایی که یک نوع wchar_t وجود دارد، لفظ هایی از این نوع وجود دارد که مانند کاراکترهای منفرد، با پیشوند L نشان داده می شوند:

    L"یک رشته گسترده به معنای واقعی کلمه"

    رشته‌ای از نوع wchar_t آرایه‌ای از کاراکترهای هم‌نوع با پایان‌بندی تهی است.
    اگر دو یا چند حرف رشته (از نوع char یا wchar_t) در یک ردیف در آزمایش برنامه ظاهر شوند، کامپایلر آنها را در یک رشته به هم متصل می کند. مثلا متن زیر

    "دو" "بعضی"

    آرایه ای از هشت کاراکتر تولید می کند - دو نفره و کاراکتر تهی پایانی. نتیجه الحاق رشته نوع مختلفتعریف نشده اگر بنویسید:

    // این ایده خوبی نیست "دو" L" مقداری"

    سپس در برخی از کامپیوترها، نتیجه یک رشته معنادار خواهد بود، و در دیگری ممکن است چیزی کاملاً متفاوت باشد. برنامه هایی که از ویژگی های پیاده سازی یک کامپایلر خاص یا سیستم عامل، قابل حمل نیستند. ما به شدت از استفاده از چنین ساختارهایی منع می کنیم.

    تمرین 3.1

    تفاوت تعاریف لفظ های زیر را توضیح دهید:

    (a) "a"، L"a"، "a"، L"a" (b) 10، 10u، 10L، 10uL، 012، 0*C (c) 3.14، 3.14f، 3.14L

    تمرین 3.2

    اشتباهات در مثال های زیر چیست؟

    (الف) "چه کسی با F\144rgus می رود؟\014" (ب) 3.14e1L (ج) "دو" L" برخی" (د) 1024f (ه) 3.14UL (f) "نظر چند خطی"

    3.2. متغیرها

    تصور کنید که در حال حل مشکل افزایش 2 به توان 10 هستیم.

    #عبارتند از
    int main() (
    // راه حل اول
    کوت<< "2 raised to the power of 10: ";
    کوت<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
    کوت<< endl;
    بازگشت 0;
    }

    مشکل حل شده است، اگرچه ما مجبور بودیم بارها بررسی کنیم که آیا 2 واقعاً 10 بار تکرار شده است یا خیر. ما در نوشتن این دنباله طولانی دوتایی اشتباه نکردیم و برنامه نتیجه صحیح - 1024 را برگرداند.
    اما اکنون از ما خواسته شد که 2 را به توان 17 و سپس به 23 برسانیم. تغییر دادن متن برنامه در هر بار بسیار ناخوشایند است! و بدتر از آن، اشتباه کردن با نوشتن دو عدد اضافی یا رد کردن آن بسیار آسان است... اما اگر نیاز به چاپ جدول توان های دو از 0 تا 15 داشته باشید، چه؟ 16 بار دو خط را که یک شکل مشترک دارند تکرار کنید:

    کوت<< "2 в степени X\t"; cout << 2 * ... * 2;

    که در آن X به ترتیب 1 افزایش می یابد و تعداد لفظ مورد نیاز به جای نقطه جایگزین می شود؟

    بله، کار را انجام دادیم. بعید است مشتری به جزئیات بپردازد، زیرا از نتیجه راضی است. در زندگی واقعی، این رویکرد اغلب کار می کند، علاوه بر این، قابل توجیه است: مشکل تا حد زیادی نه به ظریف ترین روش، بلکه در چارچوب زمانی مورد نظر حل می شود. به دنبال یک گزینه زیباتر و شایسته تر می تواند اتلاف وقت غیرعملی باشد.

    در این صورت «روش brute force» پاسخ صحیح را می دهد، اما چقدر ناخوشایند و خسته کننده است که مشکل را به این شکل حل کنیم! ما دقیقا می دانیم که چه مراحلی را باید برداریم، اما خود مراحل ساده و یکنواخت هستند.

    درگیر مکانیسم های پیچیده تر برای یک کار، به عنوان یک قاعده، زمان مرحله آماده سازی را به میزان قابل توجهی افزایش می دهد. علاوه بر این، هرچه مکانیسم های پیچیده تری استفاده شود، احتمال خطا بیشتر می شود. اما حتی با وجود اشتباهات اجتناب ناپذیر و حرکات بد، استفاده از "فناوری بالا" می تواند مزایایی در سرعت توسعه به همراه داشته باشد، ناگفته نماند که این فناوری ها قابلیت های ما را بسیار گسترش می دهند. و - چه جالب! خود فرآیند تصمیم گیری می تواند جذاب شود.
    بیایید به مثال خود بازگردیم و سعی کنیم اجرای آن را از نظر فناوری بهبود دهیم. ما می توانیم از یک شی با نام برای ذخیره مقدار توانی که می خواهیم شماره خود را به آن افزایش دهیم استفاده کنیم. همچنین، به جای تکرار دنباله لفظی، می توانیم از عملگر حلقه استفاده کنیم. در اینجا به چه شکل خواهد بود:

    #عبارتند از
    int main()
    {
    // اشیاء از نوع int
    مقدار int = 2;
    بین قدرت = 10;
    کوت<< value << " в степени "
    << pow << ": \t";
    int res = 1;
    // عملگر حلقه:
    // تکرار محاسبه res
    // تا زمانی که cnt از pow بیشتر شود
    برای (int cnt=1; cnt<= pow; ++cnt)
    res = res * value;
    کوت<< res << endl;
    }

    value، pow، res و cnt متغیرهایی هستند که به شما امکان ذخیره، تغییر و بازیابی مقادیر را می دهند. دستور حلقه for بارهای رشته محاسبه نتیجه را تکرار می کند.
    بدون شک ما برنامه بسیار انعطاف پذیرتری ایجاد کرده ایم. با این حال، این هنوز یک تابع نیست. برای به دست آوردن یک تابع واقعی که می تواند در هر برنامه ای برای محاسبه درجه یک عدد استفاده شود، باید قسمت کلی محاسبات را انتخاب کنید و مقادیر خاصی را به عنوان پارامتر تنظیم کنید.

    int pow(int val, int exp) (برای (int res = 1; exp > 0; --exp) res = res * val; return res; )

    اکنون به دست آوردن هر درجه ای از عدد مورد نظر دشوار نخواهد بود. در اینجا نحوه اجرای آخرین کار ما آمده است - چاپ جدولی از دو توان از 0 تا 15:

    #عبارتند از extern int pow(int,int); int main() (int val = 2; int exp = 15;
    کوت<< "Степени 2\n";
    برای (int cnt=0; cnt<= exp; ++cnt)
    کوت<< cnt << ": "
    << pow(val, cnt) << endl;
    بازگشت 0;
    }

    البته، تابع ()pow ما هنوز به اندازه کافی عمومی نیست و به اندازه کافی قابل اعتماد نیست. نمی تواند روی اعداد واقعی کار کند، به اشتباه اعداد را به توان منفی افزایش می دهد - همیشه 1 را برمی گرداند. نتیجه افزایش یک عدد بزرگ به توان بزرگ ممکن است در یک متغیر int قرار نگیرد و سپس مقداری اشتباه تصادفی برگردانده می شود. ببینید نوشتن توابعی که قرار است به طور گسترده مورد استفاده قرار گیرند چقدر می تواند دشوار باشد؟ بسیار دشوارتر از پیاده سازی یک الگوریتم خاص با هدف حل یک مسئله خاص است.

    3.2.1. متغیر چیست

    متغیر، یا یک شی- این یک منطقه نامگذاری شده از حافظه است که ما از برنامه به آن دسترسی داریم. می توانید مقادیر را در آنجا قرار دهید و سپس آنها را بازیابی کنید. هر متغیر C++ نوع خاصی دارد که اندازه و مکان آن ناحیه حافظه، محدوده مقادیری که می‌تواند ذخیره کند و مجموعه عملیات‌های قابل اعمال بر آن متغیر را مشخص می‌کند. در اینجا مثالی از تعریف پنج شی از انواع مختلف آورده شده است:

    int student_count; دو برابر حقوق؛ bool on_loan; strins street_address; جداکننده char;

    یک متغیر، مانند یک literal، نوع خاصی دارد و مقدار آن را در بخشی از حافظه ذخیره می کند. آدرس پذیری- این چیزی است که تحت اللفظی فاقد آن است. دو مقدار مرتبط با یک متغیر وجود دارد:

    • خود مقدار یا مقدار r (از مقدار خوانده شده - مقدار برای خواندن) که در این ناحیه حافظه ذخیره می شود و هم در متغیر و هم در یک کلمه ذاتی است.
    • مقدار آدرس ناحیه حافظه مرتبط با متغیر یا l-value (از مقدار مکان - مقدار مکان) - مکانی که مقدار r ذخیره می شود. فقط به شی تعلق دارد.

    در بیان

    Ch = ch - "0"؛

    متغیر ch هم در سمت چپ و هم در سمت راست نماد تخصیص است. در سمت راست مقدار خوانده شده است (ch و کاراکتر تحت اللفظی "0"): داده های مرتبط با متغیر از ناحیه حافظه مربوطه خوانده می شود. در سمت چپ مقدار مکان است: نتیجه تفریق در ناحیه حافظه مرتبط با متغیر ch قرار می گیرد. به طور کلی، عملوند سمت چپ یک عملگر انتساب باید یک مقدار l باشد. ما نمی توانیم عبارات زیر را بنویسیم:

    // خطاهای کامپایل: مقادیر سمت چپ l-value نیستند // خطا: literal یک l-value نیست 0 = 1. // خطا: عبارت حسابی حقوق و دستمزد l-value نیست + حقوق * 0.10 = new_salary;

    عملگر تعریف متغیر حافظه را به آن اختصاص می دهد. از آنجا که یک شی فقط یک ناحیه حافظه مرتبط با آن دارد، چنین عملگر فقط یک بار در یک برنامه می تواند رخ دهد. اگر یک متغیر تعریف شده در یک فایل منبع باید در فایل دیگر استفاده شود، مشکلاتی ایجاد می شود. مثلا:

    // file module0.C // یک رشته نام fileName را تعریف می کند. // ... filename را روی یک مقدار تنظیم کنید
    // فایل module1.C
    // از شیء filename استفاده می کند
    // متأسفانه کامپایل نمی شود:
    // نام فایل در module1.C تعریف نشده است
    ifstream input_file(filename);

    C++ مستلزم آن است که یک شی قبل از اولین دسترسی به آن شناخته شود. این به دلیل نیاز به اطمینان از استفاده صحیح از شی با توجه به نوع آن است. در مثال ما، module1.C یک خطای کامپایل ایجاد می کند زیرا fileName در آن تعریف نشده است. برای جلوگیری از این خطا، باید به کامپایلر در مورد متغیر fileName تعریف شده از قبل اطلاع دهیم. این کار با استفاده از عبارت اعلان متغیر انجام می شود:

    // file module1.C // از شی fileName استفاده می کند // fileName اعلام می شود، یعنی برنامه دریافت می کند
    // اطلاعاتی در مورد این شی بدون تعریف ثانویه آن
    نام فایل رشته خارجی; ifstream input_file (نام فایل)

    یک اعلان متغیر به کامپایلر می گوید که یک شی با نام داده شده و از نوع مشخص شده در جایی از برنامه تعریف شده است. زمانی که متغیر تعریف می شود، هیچ حافظه ای به آن اختصاص داده نمی شود. (کلید کلیدی خارجی در بخش 8.2 مورد بحث قرار گرفته است.)
    یک برنامه می تواند شامل هر تعداد اعلان از همان متغیر باشد، اما فقط یک بار می توان آن را تعریف کرد. چنین اعلان‌هایی به راحتی در فایل‌های هدر قرار می‌گیرند، از جمله آنها در ماژول‌هایی که به آنها نیاز دارند. بنابراین می‌توانیم اطلاعات مربوط به اشیاء را در یک مکان ذخیره کنیم و در صورت لزوم امکان اصلاح آن را فراهم کنیم. (در بخش 8.2 در مورد فایل های هدر بیشتر صحبت خواهیم کرد.)

    3.2.2. نام متغیر

    نام متغیر یا مشخص کننده، می تواند شامل حروف لاتین، اعداد و کاراکتر زیرخط باشد. حروف بزرگ و کوچک در نام ها متفاوت است. زبان C++ طول یک شناسه را محدود نمی کند، اما استفاده از نام های بسیار طولانی مانند gosh_this_is_an_impossibly_name_to_type ناخوشایند است.
    برخی از کلمات کلیدواژه های C++ هستند و نمی توان از آنها به عنوان شناسه استفاده کرد. جدول 3.1 فهرست کاملی از آنها را ارائه می دهد.

    جدول 3.1. کلمات کلیدی C++

    asm خودکار بوول زنگ تفريح مورد
    گرفتن کاراکتر کلاس پایان const_cast
    ادامه هید پیش فرض حذف انجام دادن دو برابر
    dynamic_cast دیگر enum صریح صادرات
    خارجی نادرست شناور برای دوست
    رفتن به اگر خطی بین المللی طولانی
    قابل تغییر فضای نام جدید اپراتور خصوصی
    حفاظت شده عمومی ثبت نام reinterpret_cast برگشت
    کوتاه امضاء شده اندازه ایستا static_cast
    ساخت تعویض قالب این پرت كردن
    typedef درست است، واقعی تلاش كردن تایپ شده اسم را تایپ کن
    اتحاد. اتصال اتحاد باطل استفاده كردن مجازی خالی

    برای درک بیشتر متن برنامه خود، توصیه می کنیم از قراردادهای پذیرفته شده عمومی برای نامگذاری اشیا پیروی کنید:

    • نام متغیر معمولا با حروف کوچک نوشته می شود، به عنوان مثال index (برای مقایسه: Index نام نوع است و INDEX یک ثابت است که با استفاده از دستور #define preprocessor تعریف شده است).
    • شناسه باید معنایی داشته باشد که هدف شی را در برنامه توضیح دهد، به عنوان مثال: تولد_تاریخ یا حقوق.

    اگر چنین نامی از چند کلمه تشکیل شده باشد، مانند تاریخ تولد، آنگاه مرسوم است که کلمات را با یک کاراکتر زیرخط (تاریخ_تولد) جدا کنید، یا هر کلمه بعدی را با حروف بزرگ بنویسید (تاریخ_تاریخ). مشاهده شده است که برنامه نویسانی که به رویکرد شی گرا عادت کرده اند، حروف بزرگ کلمات را ترجیح می دهند، در حالی که کسانی_که_نوشته_C زیاد دارند از کاراکتر زیر خط استفاده می کنند. اینکه کدام یک از این دو روش بهتر است یک موضوع سلیقه ای است.

    3.2.3. تعریف شی

    در ساده‌ترین حالت، عملگر تعریف شی شامل موارد زیر است مشخص کننده نوعو نام شیو با نقطه ویرگول به پایان می رسد. مثلا:

    دو برابر حقوق؛ دستمزد دو برابر؛ ماه بین المللی; روز بین المللی; سال بین المللی؛ مسافت طولانی بدون علامت؛

    شما می توانید چندین شی از یک نوع را در یک دستور تعریف کنید. در این مورد، نام آنها با کاما از هم جدا شده است:

    دو برابر حقوق، دستمزد؛ بین ماه، روز، سال؛ مسافت طولانی بدون علامت؛

    صرف تعریف یک متغیر، مقدار اولیه آن را تعیین نمی کند. اگر یک شی به عنوان جهانی تعریف شود، مشخصات C++ تضمین می کند که مقدار اولیه آن به null می شود. اگر متغیر محلی باشد یا به صورت پویا (با استفاده از عملگر جدید) تخصیص داده شود، مقدار اولیه آن تعریف نشده است، یعنی ممکن است حاوی مقداری تصادفی باشد.
    استفاده از متغیرهایی مانند این یک اشتباه بسیار رایج است و همچنین تشخیص آن دشوار است. توصیه می شود به صراحت مقدار اولیه یک شی را مشخص کنید، حداقل در مواردی که مشخص نیست آیا شی می تواند خود را مقداردهی اولیه کند یا خیر. مکانیسم کلاس مفهوم سازنده پیش فرض را معرفی می کند که برای تخصیص مقادیر پیش فرض استفاده می شود. (ما قبلاً در این مورد در بخش 2.3 صحبت کردیم. در بخش های 3.11 و 3.15 کمی بعد در مورد سازنده های پیش فرض صحبت خواهیم کرد، جایی که در مورد کلاس های رشته و پیچیده از کتابخانه استاندارد بحث خواهیم کرد.)

    int main() ( // شی محلی بدون مقدار اولیه int ival;
    // شی از نوع رشته مقداردهی اولیه می شود
    // سازنده پیش فرض
    پروژه رشته;
    // ...
    }

    مقدار اولیه را می توان مستقیماً در عبارت تعریف متغیر مشخص کرد. در C++، دو شکل مقداردهی اولیه متغیر مجاز است - صریح، با استفاده از یک عملگر انتساب:

    Intival = 1024; string project = "Fantasia 2000";

    و ضمنی، با مقدار اولیه مشخص شده در پرانتز:

    Intival(1024); پروژه رشته ("فانتازیا 2000")؛

    هر دو گزینه معادل هستند و مقادیر اولیه را برای متغیر عدد صحیح ival روی 1024 و برای رشته پروژه "Fantasia 2000" تنظیم می کنند.
    هنگام تعریف متغیرها به عنوان یک لیست نیز می توان از مقداردهی اولیه صریح استفاده کرد:

    حقوق دو برابر = 9999.99، دستمزد = حقوق + 0.01; int month = 08; روز=07، سال=1955;

    متغیر به محض تعریف قابل مشاهده (و معتبر در برنامه) می شود، بنابراین می توانیم متغیر دستمزد را با مجموع متغیر حقوق تعریف شده جدید با مقداری ثابت مقداردهی اولیه کنیم. بنابراین تعریف این است:

    // صحیح، اما بی معنی int bizarre = عجیب و غریب;

    از نظر نحوی معتبر است، اگرچه بی معنی است.
    انواع داده های داخلی دارای دستور خاصی برای تنظیم مقدار تهی هستند:

    // ival روی 0 و dval روی 0.0 تنظیم شده است ival = int(); double dval = double();

    در تعریف زیر:

    // int() برای هر یک از 10 عنصر برداری اعمال می شود< int >ivec (10)؛

    هر یک از ده عنصر بردار با int() مقدار دهی اولیه می شود. (ما قبلاً در مورد کلاس برداری در بخش 2.8 صحبت کردیم. برای اطلاعات بیشتر در این مورد به بخش 3.10 و فصل 6 مراجعه کنید.)
    یک متغیر را می توان با بیانی با هر پیچیدگی، از جمله فراخوانی تابع، مقدار دهی اولیه کرد. مثلا:

    #عبارتند از #عبارتند از
    قیمت دو برابر = 109.99، تخفیف = 0.16;
    دو برابر فروش_قیمت (قیمت * تخفیف);
    string pet ("چروک"); extern int get_value(); intval = get_value();
    بدون علامت abs_val = abs(val);

    abs() یک تابع استاندارد است که مقدار مطلق یک پارامتر را برمی گرداند.
    get_value() یک تابع تعریف شده توسط کاربر است که یک مقدار صحیح را برمی گرداند.

    تمرین 3.3

    کدام یک از تعاریف متغیر زیر حاوی خطاهای نحوی است؟

    (a) int car = 1024، auto = 2048; (ب) int ival = ival; (ج) int ival(int()); د) دو برابر حقوق = دستمزد = 9999.99; (ه) cin >> int_value;

    تمرین 3.4

    تفاوت بین l-value و r-value را توضیح دهید. مثال بزن.

    تمرین 3.5

    تفاوت استفاده از متغیرهای name and student را در خط اول و دوم هر مثال بیابید:

    (الف) نام رشته خارجی؛ stringname("ورزش 3.5a"); (ب) بردار خارجی دانش آموزان؛ بردار دانش آموزان؛

    تمرین 3.6

    چه نام اشیایی در C++ مجاز نیست؟ آنها را طوری تغییر دهید که از نظر نحوی صحیح باشند:

    (الف) int double = 3.14159; (ب) بردار< int >_؛ (ج) فضای نام رشته؛ (د) رشته catch-22; (ه) char 1_or_2 = "1"; (f) float Float = 3.14f;

    تمرین 3.7

    تفاوت بین تعاریف متغیر جهانی و محلی زیر چیست؟

    رشته global_class. int global_int; int main() (
    int local_int;
    رشته local_class; //...
    }

    3.3. اشاره گرها

    اشاره گرها و تخصیص حافظه پویا به طور خلاصه در بخش 2.2 معرفی شدند. اشاره گریک شی است که حاوی آدرس یک شی دیگر است و به شما امکان می دهد آن شی را به طور غیر مستقیم دستکاری کنید. به طور معمول، اشاره گرها برای کار با اشیاء ایجاد شده به صورت پویا، برای ساختن ساختارهای داده مرتبط مانند لیست های پیوندی و درختان سلسله مراتبی، و برای ارسال اشیاء بزرگ - آرایه ها و اشیاء کلاس - به عنوان پارامتر به توابع استفاده می شوند.
    هر اشاره گر با نوعی از داده مرتبط است و نمایش داخلی آنها به نوع داخلی آن بستگی ندارد: هم اندازه حافظه اشغال شده توسط یک شی از نوع اشاره گر و هم محدوده مقادیر برای آنها یکسان است. تفاوت در نحوه برخورد کامپایلر با شی آدرس دهی شده است. اشاره گرها به انواع مختلف می توانند مقدار یکسانی داشته باشند، اما مکان حافظه که در آن انواع مربوطه قرار دارند می تواند متفاوت باشد:

    • اشاره گر به int حاوی مقدار آدرس 1000 به ناحیه حافظه 1000-1003 هدایت می شود (در یک سیستم 32 بیتی).
    • اشاره گر به دو برابر حاوی مقدار آدرس 1000 به ناحیه حافظه 1000-1007 هدایت می شود (در یک سیستم 32 بیتی).

    در اینجا چند نمونه آورده شده است:

    int *ip1, *ip2; مجتمع *cp; string*psstring; بردار *pvec; دو برابر *dp;

    نشانگر با یک ستاره جلوی نام نشان داده می شود. در تعریف متغیرهای لیست، یک ستاره باید قبل از هر اشاره گر قرار گیرد (به بالا نگاه کنید: ip1 و ip2). در مثال زیر، lp یک اشاره گر به یک شی از نوع long، و lp2 یک شی از نوع long است:

    طولانی *lp، lp2;

    در حالت زیر، fp به عنوان یک شی شناور تفسیر می شود و fp2 یک اشاره گر به آن است:

    Float fp، *fp2;

    عملگر حذف ارجاع (*) را می توان با فاصله از نام و حتی مستقیماً در مجاورت یک کلمه کلیدی نوع جدا کرد. بنابراین، تعاریف زیر از نظر نحوی صحیح و کاملاً معادل هستند:

    //warning: ps2 یک اشاره گر به رشته نیست! رشته* ps، ps2;

    می توان فرض کرد که هر دو ps و ps2 اشاره گر هستند، اگرچه اشاره گر تنها اولین آنهاست.
    اگر مقدار اشاره گر 0 باشد، آنگاه هیچ آدرس شی ای وجود ندارد.
    اجازه دهید یک متغیر از نوع int داده شود:

    Intival = 1024;

    در زیر نمونه هایی از تعریف و استفاده از اشاره گر به int pi و pi2 آورده شده است:

    //pi به آدرس صفر اولیه int *pi = 0;
    // pi2 با آدرس ival مقداردهی اولیه شد
    int *pi2 =
    // صحیح: pi و pi2 حاوی آدرس ival هستند
    pi = pi2;
    // pi2 حاوی آدرس صفر است
    pi2 = 0;

    به یک اشاره گر نمی توان مقداری را که آدرس نیست اختصاص داد:

    // خطا: pi نمی تواند int pi = ival باشد

    به طور مشابه، شما نمی توانید یک اشاره گر از یک نوع را به مقداری که آدرس یک شی از نوع دیگر است اختصاص دهید. اگر متغیرهای زیر تعریف شوند:

    دو dval; دو برابر *ps =

    سپس هر دو عبارت انتساب زیر باعث خطای کامپایل می شوند:

    // خطاهای کامپایل // انتساب نوع داده نامعتبر: int*<== double* pi = pd pi = &dval;

    اینطور نیست که متغیر pi نمی تواند آدرس یک شی dval را شامل شود - آدرس اشیاء انواع مختلف طول یکسانی دارند. چنین عملیات اختلاط آدرس عمدا ممنوع است، زیرا تفسیر کامپایلر از اشیا به نوع اشاره گر به آنها بستگی دارد.
    البته، مواقعی وجود دارد که ما به ارزش خود آدرس علاقه مند هستیم، نه شیئی که به آن اشاره می کند (مثلاً می خواهیم این آدرس را با آدرس دیگری مقایسه کنیم). برای حل چنین شرایطی، یک نشانگر خالی ویژه معرفی شده است که می تواند به هر نوع داده ای اشاره کند و عبارات زیر صحیح خواهد بود:

    // صحیح: void* می تواند حاوی // آدرس هایی از هر نوع باشد void *pv = pi; pv=pd;

    نوع شی ای که با void* به آن اشاره می کند ناشناخته است و ما نمی توانیم این شی را دستکاری کنیم. تنها کاری که می‌توانیم با چنین اشاره‌گری انجام دهیم این است که مقدار آن را به یک اشاره‌گر دیگر اختصاص دهیم یا آن را با مقداری آدرس مقایسه کنیم. (در بخش 4.14 بیشتر در مورد نشانگر خالی صحبت خواهیم کرد.)
    برای ارجاع به یک شی که دارای آدرس آن است، باید عملیات عدم ارجاع یا آدرس دهی غیرمستقیم را که با ستاره (*) مشخص می شود، اعمال کنید. داشتن تعاریف متغیر زیر:

    int ival = 1024;, ival2 = 2048; int*pi =

    // انتساب غیرمستقیم ival به ival2 *pi = ival2;
    // استفاده غیر مستقیم از ival به عنوان rvalue و lvalue
    *pi = abs(*pi); // ival = abs(ival);
    *pi = *pi + 1; // ival = ival + 1;

    هنگامی که عملیات آدرس (&) را به یک شی از نوع int اعمال می کنیم، نتیجه نوع int* را دریافت می کنیم.
    int*pi =
    اگر همین عملیات روی یک شی از نوع int* (اشاره‌گر به int) اعمال شود، یک اشاره‌گر به یک اشاره‌گر به int می‌گیریم، یعنی. بینایی**. int** آدرس یک شی است که حاوی آدرس یک شی از نوع int است. با عدم ارجاع ppi، یک شی از نوع int* حاوی آدرس ival دریافت می کنیم. برای به دست آوردن خود شی ival باید دو بار عملیات dereference ppi اعمال شود.

    int **ppi = π int *pi2 = *ppi;
    کوت<< "Значение ival\n" << "явное значение: " << ival << "\n"
    << "косвенная адресация: " << *pi << "\n"
    << "дважды косвенная адресация: " << **ppi << "\n"

    از اشاره گرها می توان در عبارات حسابی استفاده کرد. به مثال زیر توجه کنید، جایی که دو عبارت عملکردهای کاملاً متفاوتی را انجام می دهند:

    int i, j, k; int *pi = // i = i + 2
    *pi = *pi + 2; // آدرس موجود در پی را 2 افزایش دهید
    پی = پی + 2;

    شما می توانید یک مقدار صحیح به یک اشاره گر اضافه کنید، همچنین می توانید از آن کم کنید. افزودن 1 به یک اشاره گر، مقدار آن را با اندازه ناحیه حافظه اختصاص داده شده به یک شی از نوع مربوطه افزایش می دهد. اگر char 1 بایت بگیرد، int 4 و double به 8 ببرد، آنگاه با افزودن 2 به اشاره گرها به char، int و double مقدار آنها به ترتیب 2، 8 و 16 افزایش می یابد، چگونه می توان این را تفسیر کرد؟ اگر اشیایی از همان نوع یکی پس از دیگری در حافظه قرار گیرند، افزایش نشانگر به میزان 1 باعث می شود که به شی بعدی اشاره کند. بنابراین، محاسبات اشاره گر بیشتر در پردازش آرایه استفاده می شود. در هر مورد دیگر آنها به سختی قابل توجیه هستند.
    در اینجا مثالی معمولی از استفاده از حساب آدرس در هنگام تکرار بر روی عناصر یک آرایه با استفاده از یک تکرار کننده به نظر می رسد:

    اینتیا int *iter = int *iter_end =
    در حالی که (iter != iter_end) (
    do_something_with_value(*iter);
    ++iter;
    }

    تمرین 3.8

    تعاریف متغیر ارائه شده است:

    int ival = 1024، ival2 = 2048; int *pi1 = &ival، *pi2 = &ival2، **pi3 = 0;

    وقتی عملیات انتساب زیر انجام می شود چه اتفاقی می افتد؟ آیا در این مثال ها خطا وجود دارد؟

    (a) ival = *pi3; (e) pi1 = *pi3; (ب) *pi2 = *pi3; (f) ival = *pi1; (ج) ival = pi2; (g) pi1 = ival; (د) pi2 = *pi1; (h) pi3 =

    تمرین 3.9

    دستکاری اشاره گر یکی از مهم ترین جنبه های C و C++ است، اما اشتباه کردن آسان است. مثلا کد

    پی = پی = پی + 1024;

    تقریباً مطمئناً باعث می شود که پی به یک ناحیه تصادفی از حافظه اشاره کند. این اپراتور تخصیص چه کاری انجام می دهد و در چه صورت خطا نمی کند؟

    تمرین 3.10

    این برنامه حاوی خطای مربوط به استفاده نادرست از نشانگرها است:

    int foobar(int *pi) (*pi = 1024; return *pi; )
    int main() (
    int*pi2 = 0;
    int ival = foobar(pi2);
    بازگشت 0;
    }

    خطا چیست؟ چگونه می توانم آن را تعمیر کنم؟

    تمرین 3.11

    خطاهای دو تمرین قبلی ظاهر می شوند و به دلیل عدم اعتبارسنجی C++ مقادیر اشاره گر در زمان اجرا منجر به عواقب مرگبار می شوند. به نظر شما چرا چنین چکی اجرا نشد؟ آیا می توانید چند دستورالعمل کلی برای ایمن تر کردن کار با اشاره گر ارائه دهید؟

    3.4. انواع رشته

    C++ از دو نوع رشته پشتیبانی می کند، نوع داخلی که از C به ارث رسیده است و کلاس رشته از کتابخانه استاندارد C++. کلاس string ویژگی‌های بسیار بیشتری را ارائه می‌کند و بنابراین استفاده از آن راحت‌تر است، اما در عمل اغلب موقعیت‌هایی پیش می‌آید که باید از یک نوع داخلی استفاده کنید یا درک خوبی از نحوه عملکرد آن داشته باشید. (یک مثال تجزیه گزینه‌های خط فرمان است که به تابع main() منتقل می‌شود. ما این را در فصل 7 پوشش خواهیم داد.)

    3.4.1. نوع رشته داخلی

    همانطور که قبلاً ذکر شد، نوع رشته داخلی به C ++ منتقل شده از C به ارث رسیده است. یک رشته کاراکتری به عنوان یک آرایه در حافظه ذخیره می شود و با استفاده از یک اشاره گر به آن دسترسی پیدا می کند. از نوع char*. کتابخانه استاندارد C مجموعه ای از توابع را برای دستکاری رشته ها فراهم می کند. مثلا:

    // طول رشته را برمی گرداند int strlen(const char*);
    // دو رشته را با هم مقایسه می کند
    int strcmp(const char*, const char*);
    // یک رشته را در رشته دیگر کپی می کند
    char* strcpy(char*, const char*);

    کتابخانه استاندارد C بخشی از کتابخانه C++ است. برای استفاده از آن، باید یک فایل هدر اضافه کنیم:

    #عبارتند از

    اشاره گر به char که با آن به رشته دسترسی داریم، به آرایه کاراکترهای مربوط به رشته اشاره می کند. حتی زمانی که یک رشته را به صورت تحت اللفظی مانند می نویسیم

    Const char *st = "قیمت یک بطری شراب\n";

    کامپایلر تمام کاراکترهای رشته را در یک آرایه قرار می دهد و سپس آدرس اولین عنصر آرایه را به st اختصاص می دهد. چگونه می توان با استفاده از چنین اشاره گر با رشته ای کار کرد؟
    معمولاً از حساب آدرس برای شمارش کاراکترهای یک رشته استفاده می شود. از آنجایی که رشته همیشه با یک کاراکتر تهی به پایان می رسد، می توانید نشانگر را 1 افزایش دهید تا کاراکتر بعدی صفر شود. مثلا:

    در حالی که (*st++) ( ... )

    st ارجاع داده نمی شود و مقدار حاصل از نظر درستی بررسی می شود. هر مقدار غیر صفر درست در نظر گرفته می شود و بنابراین با رسیدن به یک کاراکتر با کد 0 حلقه به پایان می رسد.عملیات افزایشی ++ 1 را به نشانگر st اضافه می کند و در نتیجه آن را به کاراکتر بعدی ارتقا می دهد.
    در اینجا پیاده سازی تابعی که طول یک رشته را برمی گرداند چگونه ممکن است به نظر برسد. توجه داشته باشید که از آنجایی که یک اشاره گر می تواند حاوی مقدار تهی باشد (به چیزی اشاره نمی کند)، باید قبل از عملیات reference بررسی شود:

    int string_length(const char *st) (int cnt = 0; if (st) while (*st++) ++cnt; return cnt; )

    یک رشته نوع داخلی را می توان در دو حالت خالی در نظر گرفت: اگر نشانگر رشته دارای مقدار تهی باشد (پس اصلاً رشته ای نداریم) یا اگر به آرایه ای متشکل از یک کاراکتر تهی اشاره کند (یعنی یک رشته ای که حاوی یک کاراکتر مهم نیست).

    // pc1 هیچ آرایه کاراکتری را آدرس نمی دهد *pc1 = 0; // آدرس های pc2 کاراکتر null const char *pc2 = "";

    برای یک برنامه نویس تازه کار، استفاده از رشته هایی از نوع داخلی به دلیل سطح بسیار پایین پیاده سازی و ناتوانی در انجام محاسبات آدرس، مملو از خطا است. در زیر برخی از اشتباهات معمول مبتدیان را نشان خواهیم داد. کار ساده است: طول یک رشته را محاسبه کنید. نسخه اول نادرست است. درستش کن

    #عبارتند از const char *st = "قیمت یک بطری شراب\n"; int main() (
    intlen = 0;
    while (st++) ++len; کوت<< len << ": " << st;
    بازگشت 0;
    }

    در این نسخه، اشاره گر st حذف نشده است. بنابراین، این کاراکتری که توسط st به آن اشاره شده است برای برابری 0 نیست، بلکه خود اشاره گر است. از آنجایی که این اشاره گر در ابتدا یک مقدار غیر صفر (آدرس ردیف) داشت، هرگز صفر نمی شود و حلقه به طور نامحدود اجرا می شود.
    در نسخه دوم برنامه این خطا برطرف شده است. برنامه با موفقیت به پایان می رسد، اما نتیجه نادرست است. این بار کجا اشتباه می کنیم؟

    #عبارتند از const char *st = "قیمت یک بطری شراب\n"; int main()
    {
    intlen = 0;
    در حالی که (*st++) ++len; کوت<< len << ": " << st << endl;
    بازگشت 0;
    }

    خطا این است که پس از پایان حلقه، نشانگر st به کاراکتر اصلی literal خطاب نمی‌کند، بلکه کاراکتری را که پس از صفر پایانی این حرف در حافظه قرار دارد، نشان می‌دهد. هر چیزی می تواند در این مکان باشد و خروجی برنامه یک دنباله تصادفی از کاراکترها خواهد بود.
    می توانید سعی کنید این خطا را برطرف کنید:

    St = st - len; کوت<< len << ": " << st;

    اکنون برنامه ما چیزی معنادار را تولید می کند، اما نه به طور کامل. پاسخ به این شکل است:

    18: یک بطری شراب

    فراموش کردیم که در نظر بگیریم که کاراکتر تهی پایانی در طول محاسبه شده لحاظ نشده است. st باید با طول رشته به اضافه 1 جبران شود. در نهایت عبارت صحیح این است:

    St \u003d st - len - 1;

    و در اینجا نتیجه صحیح است:

    18: قیمت یک بطری شراب

    با این حال، نمی توانیم بگوییم که برنامه ما زیبا به نظر می رسد. اپراتور

    St \u003d st - len - 1;

    برای تصحیح اشتباهی که در مرحله اولیه طراحی برنامه ایجاد شده است - به طور مستقیم نشانگر st را افزایش می دهد. این عبارت در منطق برنامه نمی گنجد و درک کد اکنون دشوار است. اصلاحاتی از این نوع اغلب به عنوان وصله نامیده می شود - چیزی که برای ایجاد سوراخ در یک برنامه موجود طراحی شده است. راه حل بسیار بهتر، بازنگری در منطق است. یک گزینه در مورد ما این است که یک اشاره گر دوم را با مقدار st تعریف کنیم:

    Const char *p = st;

    حالا p را می توان در حلقه طول استفاده کرد و مقدار st را بدون تغییر باقی گذاشت:

    در حالی که (*p++)

    3.4.2. کلاس رشته

    همانطور که قبلاً دیدیم، استفاده از نوع رشته داخلی مستعد خطا است و به دلیل این واقعیت که در سطح بسیار پایین اجرا می شود، چندان راحت نیست. بنابراین، بسیار معمول است که کلاس یا کلاس‌های خود را برای نشان دادن یک نوع رشته ایجاد کنید - تقریباً هر شرکت، بخش یا پروژه فردی پیاده‌سازی خاص خود را از یک رشته داشت. چه بگویم، در دو چاپ قبلی این کتاب هم همین کار را کردیم! این امر باعث بروز مشکلاتی در سازگاری و قابلیت حمل برنامه ها شد. اجرای کلاس رشته استاندارد توسط کتابخانه استاندارد C++ به منظور پایان دادن به این اختراع دوچرخه بود.
    بیایید سعی کنیم حداقل مجموعه عملیاتی را که کلاس رشته باید داشته باشد را مشخص کنیم:

    • مقداردهی اولیه با آرایه ای از کاراکترها (رشته ای از نوع داخلی) یا شی دیگری از نوع رشته. نوع توکار قابلیت دوم را ندارد.
    • کپی کردن یک خط به خط دیگر برای یک نوع داخلی، باید از تابع strcpy() استفاده کنید.
    • دسترسی به کاراکترهای جداگانه یک رشته برای خواندن و نوشتن. در آرایه داخلی، این کار با استفاده از عملیات زیرنویس یا آدرس دهی غیر مستقیم انجام می شود.
    • مقایسه دو رشته برای برابری برای یک نوع داخلی، از تابع strcmp() استفاده کنید.
    • الحاق دو رشته، به دست آوردن نتیجه یا به عنوان یک رشته سوم یا به جای یکی از رشته های اصلی. برای یک نوع داخلی، از تابع strcat() استفاده می شود، اما برای دریافت نتیجه در یک خط جدید، باید از توابع strcpy() و strcat() به ترتیب استفاده کنید.
    • محاسبه طول یک رشته با استفاده از تابع strlen() می توانید طول یک رشته نوع داخلی را دریابید.
    • توانایی یافتن خالی بودن یک رشته. برای رشته های داخلی، دو شرط باید برای این منظور بررسی شود: char str = 0; //... اگر (! str || ! *str) بازگشت;

    کلاس string کتابخانه استاندارد C++ همه این عملیات را اجرا می کند (و خیلی بیشتر، همانطور که در فصل 6 خواهیم دید). در این قسمت با نحوه استفاده از عملیات پایه این کلاس آشنا می شویم.
    برای استفاده از اشیاء کلاس رشته، باید فایل هدر مناسب را وارد کنید:

    #عبارتند از

    در اینجا مثالی از یک رشته از بخش قبلی است که با یک شی از نوع رشته نمایش داده شده و با یک رشته کاراکتر مقداردهی اولیه شده است:

    #عبارتند از string st("قیمت یک بطری شراب\n");

    طول رشته توسط تابع عضو size() برگردانده می شود (طول شامل کاراکتر تهی پایانی نمی شود).

    کوت<< "Длина " << st << ": " << st.size() << " символов, включая символ новой строки\n";

    شکل دوم تعریف رشته یک رشته خالی را مشخص می کند:

    رشته st2; // خط خالی

    چگونه بفهمیم یک رشته خالی است؟ البته می توانید طول آن را با 0 مقایسه کنید:

    if (! st.size()) // صحیح: خالی

    با این حال، یک متد ()() ویژه نیز وجود دارد که مقدار true را برای رشته های خالی و false را برای رشته های غیر خالی برمی گرداند:

    if (st.empty()) // صحیح: خالی

    شکل سوم سازنده یک شی از نوع رشته را با یک شی دیگر از همان نوع مقداردهی اولیه می کند:

    رشته st3(st);

    رشته st3 با رشته st مقدار دهی اولیه می شود. چگونه می توانیم مطمئن شویم که این رشته ها مطابقت دارند؟ بیایید از عملگر مقایسه (==):

    اگر (st == st3) // مقداردهی اولیه با موفقیت انجام شد

    چگونه یک خط را به خط دیگر کپی کنیم؟ با عملگر انتساب معمول:

    st2 = st3; // st3 را در st2 کپی کنید

    برای به هم پیوستن رشته ها، از عملگر جمع (+) یا عملگر جمع-انتساب (+=) استفاده کنید. بگذارید دو خط داده شود:

    رشته s1("سلام،"); رشته s2("world\n");

    می‌توانیم یک رشته سوم متشکل از الحاق دو مورد اول بدست آوریم، به این ترتیب:

    رشته s3 = s1 + s2;

    اگر بخواهیم s2 را به انتهای s1 اضافه کنیم باید بنویسیم:

    S1 += s2;

    عملیات جمع می تواند اشیاء کلاس رشته را نه تنها در بین خود، بلکه با رشته هایی از نوع داخلی به هم پیوند دهد. می‌توانید مثال بالا را به گونه‌ای بازنویسی کنید که کاراکترهای خاص و نشانه‌های نقطه‌گذاری با یک نوع داخلی نشان داده شوند و کلمات قابل توجه اشیاء رشته کلاس باشند:

    Const char *pc = ", "; رشته s1 ("سلام"); رشته s2 ("جهان");
    رشته s3 = s1 + pc + s2 + "\n";

    چنین عباراتی کار می کنند زیرا کامپایلر می داند چگونه به طور خودکار اشیاء نوع داخلی را به اشیاء رشته ای تبدیل کند. همچنین می توان به سادگی یک رشته داخلی را به یک شی رشته اختصاص داد:

    رشته s1; const char *pc = "یک آرایه کاراکتر"; s1=pc; // درست

    با این حال، تبدیل معکوس کار نمی کند. تلاش برای انجام اولیه سازی رشته نوع داخلی زیر باعث خطای کامپایل می شود:

    Char*str = s1; // خطای کامپایل

    برای انجام این تبدیل، باید به صراحت تابع عضو را با نام کمی عجیب c_str():

    Char *str = s1.c_str(); // تقریبا درست است

    تابع ()c_str یک اشاره گر را به یک آرایه کاراکتری حاوی رشته شی رشته، همانطور که در نوع رشته داخلی است، برمی گرداند.
    مثال بالا در مورد مقداردهی اولیه یک اشاره گر char *str هنوز کاملاً صحیح نیست. ()c_str یک اشاره گر را به آرایه const برمی گرداند تا از تغییر مستقیم محتویات شیء از طریق این اشاره گر که دارای نوع است جلوگیری کند.

    کاراکتر Const *

    (در قسمت بعدی در مورد کلمه کلیدی const صحبت خواهیم کرد.) مقداردهی اولیه صحیح به صورت زیر است:

    Const char *str = s1.c_str(); // درست

    کاراکترهای منفرد یک شی رشته، مانند یک نوع داخلی، با استفاده از عملیات فهرست قابل دسترسی هستند. به عنوان مثال، در اینجا یک قطعه کد وجود دارد که همه نقاط را با زیرخط جایگزین می کند:

    Stringstr("fa.disney.com"); int size = str.size(); برای (int ix = 0; ix< size; ++ix) if (str[ ix ] == ".")
    str[ix] = "_";

    این تمام چیزی است که می‌خواستیم در مورد کلاس رشته بگوییم. در واقع این کلاس ویژگی ها و قابلیت های جالب تری دارد. فرض کنید مثال قبلی با فراخوانی یک تابع ()replacement نیز پیاده سازی شده است:

    Replace(str.begin()، str.end()، "."، "_");

    ()replace یکی از الگوریتم‌های تعمیم‌یافته‌ای است که در بخش 2.8 معرفی کردیم و در فصل 12 به تفصیل توضیح داده خواهد شد. رشته، و عناصر برابر با پارامتر سوم خود را با پارامتر چهارم جایگزین می کند.

    تمرین 3.12

    در عبارات زیر به دنبال خطا باشید:

    (a) char ch = "جاده طولانی و پر پیچ و خم"; (ب) int ival = (c) char *pc = (d) string st(&ch); (ه) pc = 0; (i) pc = "0"؛
    (f) st = pc; (j) st =
    (g) ch = pc; (k) ch = *pc;
    (h) pc = st; (l) *pc = ival;

    تمرین 3.13

    تفاوت رفتار عبارات حلقه زیر را توضیح دهید:

    while (st++) ++cnt;
    در حالی که (*st++)
    ++cnt;

    تمرین 3.14

    دو برنامه معادل معنایی داده شده است. اولی از نوع رشته داخلی استفاده می کند، دومی از کلاس رشته استفاده می کند:

    // ***** پیاده سازی با استفاده از رشته های C ***** #include #عبارتند از
    int main()
    {
    خطاهای int = 0;
    const char *pc = "رشته لفظی بسیار طولانی"; برای (int ix = 0; ix< 1000000; ++ix)
    {
    int len ​​= strlen(pc)؛
    char *pc2 = کاراکتر جدید[ len + 1 ];
    strcpy (pc2، pc)؛
    if (strcmp(pc2, pc))
    ++ خطاها؛ حذف pc2;
    }
    کوت<< "C-строки: "
    << errors << " ошибок.\n";
    ) // ***** پیاده سازی با استفاده از کلاس رشته ***** #include
    #عبارتند از
    int main()
    {
    خطاهای int = 0;
    string str("یک رشته تحت اللفظی بسیار طولانی"); برای (int ix = 0; ix< 1000000; ++ix)
    {
    int len ​​= str.size();
    string str2 = str;
    if (str != str2)
    }
    کوت<< "класс string: "
    << errors << " ошибок.\n;
    }

    این برنامه ها چه کار می کنند؟
    به نظر می رسد که اجرای دوم دو برابر سریعتر از اولین است. آیا انتظار چنین نتیجه ای را داشتید؟ چطور آن را توضیح می دهی؟

    تمرین 3.15

    آیا چیزی وجود دارد که بتوانید در بخش آخر مجموعه عملیات کلاس رشته را بهبود یا اضافه کنید؟ پیشنهادات خود را توضیح دهید

    3.5. مشخص کننده const

    بیایید کد زیر را مثال بزنیم:

    برای(int index = 0; index< 512; ++index) ... ;

    استفاده از 512 literal دو مشکل دارد. اولین مورد سهولت درک متن برنامه است. چرا باید کران بالای متغیر حلقه دقیقاً 512 باشد؟ چه چیزی پشت این ارزش پنهان است؟ او تصادفی به نظر می رسد ...
    مشکل دوم مربوط به سهولت اصلاح و نگهداری کد است. فرض کنید برنامه از 10000 خط تشکیل شده است و 512 به معنای واقعی کلمه در 4٪ آنها رخ می دهد. فرض کنید در 80 درصد موارد باید عدد 512 را به 1024 تغییر دهید.
    هر دوی این مشکلات به طور همزمان حل می شوند: ما باید یک شی با مقدار 512 ایجاد کنیم. با دادن یک نام معنی دار به آن، مانند bufSize، برنامه را بسیار قابل درک تر خواهیم کرد: مشخص است که حلقه دقیقاً چیست. متغیر با.

    فهرست مطالب< bufSize

    در این مورد، تغییر اندازه bufSize نیازی به بررسی 400 خط کد برای تغییر 320 خط از آنها ندارد. چقدر احتمال خطا به قیمت اضافه کردن یک شی کاهش می یابد! اکنون مقدار 512 است محلی شده است.

    bufSize = 512; // اندازه بافر ورودی // ... برای (int index = 0; index< bufSize; ++index)
    // ...

    یک مشکل کوچک باقی می ماند: متغیر bufSize در اینجا یک مقدار l است که می تواند به طور تصادفی در برنامه تغییر کند، که منجر به یک خطای سخت می شود. در اینجا یکی از رایج ترین اشتباهات وجود دارد - استفاده از عملگر انتساب (=) به جای مقایسه (==):

    // تغییر تصادفی مقدار bufSize اگر (bufSize = 1) // ...

    در نتیجه اجرای این کد، مقدار bufSize برابر با 1 می شود که می تواند منجر به رفتار کاملا غیرقابل پیش بینی برنامه شود. تشخیص خطاهای این نوع معمولاً بسیار دشوار است زیرا به سادگی قابل مشاهده نیستند.
    استفاده از مشخص کننده const این مشکل را حل می کند. با اعلام یک شی به عنوان

    Const int bufSize = 512; // اندازه بافر ورودی

    ما متغیر را به یک ثابت با مقدار 512 تبدیل می کنیم که مقدار آن قابل تغییر نیست: چنین تلاش هایی توسط کامپایلر متوقف می شوند: استفاده نادرست از عملگر انتساب به جای مقایسه، مانند مثال بالا، باعث خطای کامپایل می شود.

    // خطا: تلاش برای اختصاص یک مقدار به یک ثابت اگر (bufSize = 0) ...

    از آنجایی که نمی توان به یک ثابت مقداری اختصاص داد، باید در محل تعریف آن مقدار دهی اولیه شود. تعریف یک ثابت بدون مقداردهی اولیه آن نیز باعث خطای کامپایل می شود:

    Const double pi; // خطا: ثابت بدون مقدار اولیه

    حداقل دستمزد دو برابر = 9.60; // درست؟ خطا؟
    دو برابر *ptr =

    آیا کامپایلر باید این تخصیص را مجاز کند؟ از آنجایی که minWage یک ثابت است، نمی توان مقداری به آن اختصاص داد. از سوی دیگر، هیچ چیز ما را از نوشتن منع نمی کند:

    *ptr += 1.40; // شی minWage را تغییر دهید!

    به عنوان یک قاعده، کامپایلر قادر به محافظت در برابر استفاده از اشاره گرها نیست و در صورت استفاده از آنها به این روش، نمی تواند علامت خطا بدهد. این نیاز به تجزیه و تحلیل بیش از حد منطق برنامه دارد. بنابراین، کامپایلر به سادگی تخصیص آدرس ثابت ها به اشاره گرهای معمولی را ممنوع می کند.
    خوب، ما از فرصت استفاده از اشاره گر به ثابت محروم هستیم؟ خیر برای انجام این کار، اشاره گرهایی با مشخص کننده const وجود دارد:

    Const double *cptr;

    که در آن cptr یک اشاره گر به یک شی از نوع const double است. ظرافت در این واقعیت نهفته است که نشانگر خود یک ثابت نیست، به این معنی که می توانیم مقدار آن را تغییر دهیم. مثلا:

    Const double *pc = 0; const double minWage = 9.60; // صحیح: نمی توان minWage را با کامپیوتر تغییر داد
    pc = دو برابر dval = 3.14; // صحیح: نمی توان minWage را با کامپیوتر تغییر داد
    // اگرچه dval یک ثابت نیست
    pc = // صحیح dval = 3.14159; //درست
    * pc = 3.14159; // خطا

    آدرس یک شیء const فقط به یک اشاره گر به یک ثابت اختصاص داده می شود. با این حال، به چنین اشاره گر می توان آدرس یک متغیر معمولی را نیز اختصاص داد:

    کامپیوتر =

    یک اشاره گر ثابت اجازه نمی دهد که شی مورد نظر توسط آدرس دهی غیرمستقیم اصلاح شود. اگرچه dval در مثال بالا ثابت نیست، کامپایلر اجازه نمی دهد dval از طریق کامپیوتر تغییر کند. (دوباره، زیرا نمی تواند تعیین کند که نشانگر چه آدرسی را در هر نقطه از اجرای برنامه نگه می دارد.)
    در برنامه های واقعی، نشانگرهای ثابت اغلب به عنوان پارامترهای رسمی توابع استفاده می شوند. استفاده از آنها تضمین می کند که شیء ارسال شده به تابع به عنوان آرگومان واقعی توسط آن تابع تغییر نخواهد کرد. مثلا:

    // در برنامه های واقعی، نشانگرهای ثابت اغلب به عنوان پارامترهای رسمی توابع int strcmp (const char *str1, const char *str2) استفاده می شوند.

    (زمانی که در مورد توابع صحبت می کنیم، در فصل 7 بیشتر در مورد نشانگرهای ثابت صحبت خواهیم کرد.)
    همچنین نشانگرهای ثابتی وجود دارد. (به تفاوت بین اشاره گر ثابت و اشاره گر به ثابت توجه کنید!). یک اشاره گر ثابت می تواند هم یک ثابت و هم متغیر را نشان دهد. مثلا:

    Inter errNumb = 0; int *const currErr =

    در اینجا curErr یک اشاره گر ثابت به یک شی غیر ثابت است. این بدان معنی است که ما نمی توانیم آدرس یک شی دیگر را به آن اختصاص دهیم، اگرچه خود شیء قابل تغییر است. در اینجا نحوه استفاده از نشانگر curErr آمده است:

    کاری بکنید()؛ اگر (*curErr) (
    errorHandler();
    *curErr = 0; // correct: مقدار errNumb را بازنشانی کنید
    }

    تلاش برای اختصاص یک مقدار به یک اشاره گر ثابت باعث ایجاد یک خطای کامپایل می شود:

    CurErr = // خطا

    یک اشاره گر ثابت به یک ثابت، اتحاد دو حالت در نظر گرفته شده است.

    Const double pi = 3.14159; const double *const pi_ptr = π

    نه مقدار شی مورد اشاره توسط pi_ptr و نه مقدار خود اشاره گر را نمی توان در برنامه تغییر داد.

    تمرین 3.16

    مفهوم پنج تعریف زیر را توضیح دهید. آیا هیچ کدام از آنها اشتباه می کنند؟

    (الف) int i; (د) int *const cpi; (ب) Const int ic; (ه) const int *const cpic; (ج) const int *pic;

    تمرین 3.17

    کدام یک از تعاریف زیر صحیح است؟ چرا؟

    (الف) int i = -1; (ب) const int ic = i; (ج) const int *pic = (d) int *const cpi = (e) const int *const cpic =

    تمرین 3.18

    با استفاده از تعاریف تمرین قبلی، عملگرهای تخصیص صحیح را شناسایی کنید. توضیح.

    (الف) i = ic; (د) عکس = cpic; (ب) pic = (i) cpic = (ج) cpi = عکس; (f) ic = *cpic;

    3.6. نوع مرجع

    یک نوع مرجع، که گاهی اوقات نام مستعار نیز نامیده می شود، برای دادن یک نام اضافی به یک شی استفاده می شود. یک مرجع به شما امکان می دهد یک شی را به طور غیرمستقیم دستکاری کنید، درست مانند یک اشاره گر. با این حال، این دستکاری غیرمستقیم به نحو خاص مورد نیاز برای اشاره گرها نیاز ندارد. معمولاً از مراجع به عنوان پارامترهای رسمی توابع استفاده می شود. در این بخش، استفاده از اشیاء نوع مرجع به تنهایی را بررسی خواهیم کرد.
    یک نوع مرجع با مشخص کردن عملگر آدرس (&) در مقابل نام متغیر نشان داده می شود. پیوند باید مقداردهی اولیه شود. مثلا:

    Intival = 1024; // صحیح: refVal - ارجاع به ival int &refVal = ival; // خطا: مرجع باید به int مقداردهی شود

    Intival = 1024; // خطا: refVal از نوع int است، نه int* int &refVal = int *pi = // صحیح: ptrVal ارجاع به یک اشاره گر int است *&ptrVal2 = pi;

    هنگامی که یک مرجع را تعریف می کنید، نمی توانید آن را برای کار با یک شی متفاوت تغییر دهید (به همین دلیل است که مرجع باید در محل تعریف خود مقداردهی اولیه شود). در مثال زیر، دستور انتساب مقدار refVal را تغییر نمی دهد، مقدار جدید به متغیر ival اختصاص داده می شود، متغیری که refVal به آن آدرس می دهد.

    int min_val = 0; // ival مقدار min_val را می گیرد، // not refVal مقدار را به min_val تغییر می دهد refVal = min_val;

    RefVal += 2; 2 را به ival اضافه می کند، متغیری که refVal به آن ارجاع می دهد. به طور مشابه int ii = refVal; ii را به مقدار فعلی ival تنظیم می کند، int *pi = pi را با آدرس ival مقدار دهی اولیه می کند.

    // دو شی از نوع int تعریف شده اند int ival = 1024, ival2 = 2048; // یک مرجع و یک شی تعریف شده int &rval = ival, rval2 = ival2; // یک شی، یک اشاره گر و یک مرجع تعریف شده است
    int inal3 = 1024، *pi = ival3، &ri = ival3; // دو مرجع تعریف شده int &rval3 = ival3، &rval4 = ival2;

    یک مرجع ثابت را می توان با یک شی از نوع دیگر (البته اگر امکان تبدیل یک نوع به نوع دیگر وجود داشته باشد) و همچنین یک مقدار غیر آدرس مانند یک ثابت تحت اللفظی مقداردهی اولیه کرد. مثلا:

    دو dval = 3.14159; // فقط برای مراجع const درست است
    const int &ir = 1024;
    const int &ir2 = dval;
    const double &dr = dval + 1.0;

    اگر مشخص‌کننده const را مشخص نکنیم، هر سه تعریف مرجع باعث خطای کامپایل می‌شوند. با این حال، دلیل اینکه کامپایلر از چنین تعاریفی چشم پوشی نمی کند، مشخص نیست. بیایید سعی کنیم آن را بفهمیم.
    برای لفظ ها، این کم و بیش روشن است: ما نباید قادر باشیم به طور غیرمستقیم مقدار یک لفظ را با استفاده از اشاره گرها یا مراجع تغییر دهیم. در مورد اشیاء از نوع دیگر، کامپایلر شی اصلی را به یک مورد کمکی تبدیل می کند. مثلاً اگر بنویسیم:

    دو dval = 1024; const int &ri = dval;

    سپس کامپایلر آن را به چیزی شبیه به این تبدیل می کند:

    int temp = dval; const int &ri = temp;

    اگر بتوانیم یک مقدار جدید به مرجع ri اختصاص دهیم، در واقع نه dval، بلکه temp را تغییر می دهیم. مقدار dval یکسان باقی می ماند که برای برنامه نویس کاملاً واضح نیست. بنابراین، کامپایلر چنین اقداماتی را ممنوع می کند و تنها راه برای مقداردهی اولیه یک مرجع با یک شی از نوع متفاوت، اعلام آن به عنوان const است.
    در اینجا مثال دیگری از پیوندی است که درک آن برای اولین بار دشوار است. ما می خواهیم یک مرجع به آدرس یک شی const تعریف کنیم، اما اولین گزینه ما باعث ایجاد یک خطای کامپایل می شود:

    Const int ival = 1024; // خطا: مرجع ثابت مورد نیاز است
    int *&pi_ref =

    تلاش برای رفع مشکل با افزودن مشخص کننده const نیز با شکست مواجه می شود:

    Const int ival = 1024; // همچنان خطای const int *&pi_ref =

    دلیل ش چیه؟ با خواندن دقیق تعریف، خواهیم دید که pi_ref اشاره ای به یک اشاره گر ثابت به یک شی از نوع int است. و ما به یک اشاره گر non-const برای یک شی const نیاز داریم، بنابراین ورودی زیر صحیح خواهد بود:

    Const int ival = 1024; // درست
    int *const &piref=

    دو تفاوت اصلی بین یک مرجع و یک اشاره گر وجود دارد. ابتدا، مرجع باید در محل تعریف آن مقداردهی اولیه شود. ثانیاً، هر تغییری در مرجع، آن را تغییر نمی دهد، بلکه شیئی را که به آن اشاره می کند، تغییر می دهد. بیایید به نمونه هایی نگاه کنیم. اگر بنویسیم:

    int*pi = 0;

    ما نشانگر pi را صفر می کنیم، به این معنی که pi به هیچ شیئی اشاره نمی کند. در عین حال رکورد

    const int &ri = 0;
    یعنی چیزی شبیه این:
    int temp = 0;
    const int &ri = temp;

    در مورد عملیات تخصیص، در مثال زیر:

    int ival = 1024، ival2 = 2048; int *pi = &ival، *pi2 = pi = pi2;

    متغیر ival که توسط pi به آن اشاره شده است بدون تغییر باقی می ماند و pi مقدار آدرس ival2 را دریافت می کند. هر دو pi و pi2 همچنان به یک شی ival2 اشاره می کنند.
    اگر با لینک ها کار کنیم:

    Int &ri = ival، &ri2 = ival2; ri = ri2;

    // مثال استفاده از مراجع // مقدار در پارامتر next_value برگردانده می شود
    bool get_next_value(int &next_value); // بارگذاری بیش از حد عملگر ماتریس+(const Matrix&, const Matrix&);

    اینتیوال در حالی که (get_next_value(ival)) ...

    int &next_value = ival;

    (استفاده از مراجع به عنوان پارامترهای رسمی برای توابع با جزئیات بیشتر در فصل 7 مورد بحث قرار گرفته است.)

    تمرین 3.19

    آیا در این تعاریف خطایی وجود دارد؟ توضیح. چگونه آنها را تعمیر می کنید؟

    (الف) int ival = 1.01; (ب) int &rval1 = 1.01; (ج) int &rval2 = ival; (د) int &rval3 = (e) int *pi = (f) int &rval4 = pi; (g) int &rval5 = pi*; (h) int &*prval1 = pi; (i) const int &ival2 = 1; (j) const int &*prval2 =

    تمرین 3.20

    آیا هر یک از تکالیف زیر (با استفاده از تعاریف تمرین قبلی) اشتباه است؟

    (الف) rval1 = 3.14159; (ب) prval1 = prval2; (ج) prval2 = rval1; (د) *prval2 = ival2;

    تمرین 3.21

    در دستورالعمل های زیر به دنبال خطا باشید:

    (الف) int ival = 0; const int *pi = 0; const int &ri = 0; (ب) پی =
    ری =
    پی =

    3.7. bool را تایپ کنید

    یک شی از نوع bool می تواند یکی از دو مقدار درست و غلط را بگیرد. مثلا:

    // مقداردهی اولیه رشته string search_word = get_word(); // مقداردهی اولیه متغیر پیدا شده
    bool یافت = نادرست; رشته next_word; در حالی که (cin >> next_word)
    if (next_word == search_word)
    یافت = درست
    // ... // کوتاه نویسی: اگر (یافت == درست)
    اگر (پیدا شد)
    کوت<< "ok, мы нашли слово\n";
    دیگر cout<< "нет, наше слово не встретилось.\n";

    اگرچه bool یکی از انواع اعداد صحیح است، اما نمی توان آن را به عنوان علامت، بدون علامت، کوتاه یا طولانی اعلام کرد، بنابراین تعریف فوق اشتباه است:

    // خطا short bool found = false;

    اشیاء از نوع bool به طور ضمنی به نوع int تبدیل می شوند. مقدار true می شود 1 و false 0 می شود. به عنوان مثال:

    bool یافت = نادرست; int origin_count = 0; در حالی که (/* زمزمه */)
    {
    یافت = جستجو_برای(/* چیزی */); // مقدار یافت شده به 0 یا 1 تبدیل می شود
    وقوع_شمار += یافت شد; )

    به همین ترتیب، مقادیر انواع اعداد صحیح و اشاره گرها را می توان به مقادیر bool تبدیل کرد. در این مورد، 0 به عنوان نادرست، و هر چیز دیگر به عنوان درست تفسیر می شود:

    // تعداد رخدادهای extern int find(const string&); bool یافت = نادرست; if (found = find("sebbud")) // correct: found == true // یک اشاره گر به عنصر برمی گرداند
    extern int* find(int value); if (found = find(1024)) // صحیح: found == true

    3.8. شمارش ها

    اغلب لازم است متغیری تعریف شود که مقادیری را از یک مجموعه خاص بگیرد. فرض کنید یک فایل در هر یک از سه حالت باز می شود: برای خواندن، برای نوشتن، برای پیوست.
    البته برای نشان دادن این حالت ها می توان سه ثابت تعریف کرد:

    ورودی Const int = 1; خروجی const int = 2; const int append = 3;

    و از این ثابت ها استفاده کنید:

    bool open_file (رشته file_name، int open_mode); //...
    open_file("Phoenix_and_the_Crane"، ضمیمه)؛

    این راه حل قابل قبول است، اما کاملاً قابل قبول نیست، زیرا نمی توانیم تضمین کنیم که آرگومان ارسال شده به تابع open_file() فقط 1، 2 یا 3 باشد.
    استفاده از نوع برشماری این مشکل را حل می کند. وقتی می نویسیم:

    Enum open_modes (ورودی = 1، خروجی، پیوست)؛

    ما یک نوع جدید open_modes تعریف می کنیم. مقادیر معتبر برای یک شی از این نوع به 1، 2 و 3 محدود می شود و هر یک از مقادیر مشخص شده دارای یک نام یادگاری است. ما می توانیم از نام این نوع جدید برای تعریف یک شی از آن نوع و نوع پارامترهای رسمی تابع استفاده کنیم:

    Open_file باطل (رشته file_name، open_modes om);

    ورودی، خروجی و الحاقی هستند عناصر شمارش. مجموعه عناصر شمارش مجموعه مقادیر معتبر را برای یک شی از یک نوع معین مشخص می کند. متغیری از نوع open_modes (در مثال ما) با یکی از این مقادیر مقداردهی اولیه می شود، همچنین می توان هر یک از آنها را به آن اختصاص داد. مثلا:

    Open_file("ققنوس و جرثقیل"، پیوست);

    تلاش برای تخصیص مقداری به متغیری از این نوع که با یکی از عناصر شمارش متفاوت است (یا ارسال آن به عنوان پارامتر به یک تابع) باعث خطای کامپایل می شود. حتی اگر بخواهیم یک مقدار صحیح مربوط به یکی از عناصر شمارش را ارسال کنیم، باز هم با یک خطا مواجه خواهیم شد:

    // خطا: 1 یک عضو enum نیست open_modes open_file("Jonah", 1);

    راهی برای تعریف متغیری از نوع open_modes وجود دارد، مقدار یکی از عناصر شمارش را به آن اختصاص می دهیم و آن را به عنوان پارامتر به تابع ارسال می کنیم:

    open_modes om=input; // ... om = ضمیمه; open_file("TailTell"، om);

    با این حال، نمی توان نام چنین عناصری را به دست آورد. اگر یک دستور خروجی بنویسیم:

    کوت<< input << " " << om << endl;

    ما هنوز دریافت می کنیم:

    این مشکل با تعریف یک آرایه رشته ای حل می شود که در آن عنصر با شاخصی برابر با مقدار عنصر enum نام خود را در خود دارد. با چنین آرایه ای می توانیم بنویسیم:

    کوت<< open_modes_table[ input ] << " " << open_modes_table[ om ] << endl Будет выведено: input append

    همچنین، نمی‌توانید روی همه مقادیر enum تکرار کنید:

    // برای (open_modes iter = input; iter != append; ++inter) پشتیبانی نمی شود // ...

    از کلمه کلیدی enum برای تعریف شمارش استفاده می شود و نام عناصر در پرانتزهای مجعد مشخص می شود که با کاما از هم جدا می شوند. به طور پیش فرض، اولی 0، بعدی 1 و غیره است. اپراتور انتساب می تواند این قانون را تغییر دهد. در این حالت، هر عنصر بعدی بدون مقدار مشخصی 1 بیشتر از عنصر قبل از خود در لیست خواهد بود. در مثال ما به صراحت مقدار 1 را برای ورودی مشخص کرده ایم و خروجی و ضمیمه 2 و 3 خواهد بود. در اینجا مثال دیگری وجود دارد:

    // shape == 0, sphere == 1, cylinder == 2, polygon == 3 enum Forms( share, spre, cylinder, polygon );

    مقادیر صحیح مربوط به عناصر مختلف یک شمارش لازم نیست متفاوت باشند. مثلا:

    // point2d == 2، point2w == 3، point3d == 3، point3w == 4 تعداد امتیاز (point2d=2، point2w، point3d=3، point3w=4);

    شیئی که نوع آن enum است را می توان تعریف کرد، در عبارات استفاده کرد و به عنوان آرگومان به تابع ارسال کرد. چنین شیئی فقط با مقدار یکی از عناصر شمارش مقداردهی اولیه می شود و فقط آن مقدار به طور صریح یا به عنوان مقدار شی دیگری از همان نوع به آن اختصاص داده می شود. حتی مقادیر صحیح مربوط به عناصر شمارش معتبر را نمی توان به آن اختصاص داد:

    Void mumble() ( Points pt3d = point3d; // correct: pt2d == 3 // خطا: pt3w برای تایپ int Points pt3w = 3 مقداردهی اولیه می شود؛ // خطا: چند ضلعی در Points enum نیست pt3w = چند ضلعی؛ // صحیح است : هر دو شی از نوع Points pt3w = pt3d؛ )

    با این حال، در عبارات حسابی، یک enum را می توان به طور خودکار به نوع int تبدیل کرد. مثلا:

    const int array_size = 1024; // صحیح: pt2w به int تبدیل می شود
    int chunk_size = array_size * pt2w;

    3.9. "آرایه" را تایپ کنید

    ما قبلاً در بخش 2.1 آرایه ها را لمس کردیم. آرایه مجموعه ای از عناصر از همان نوع است که با شاخص - شماره ترتیبی عنصر در آرایه - به آنها دسترسی پیدا می شود. مثلا:

    اینتیوال

    ival را به عنوان متغیری از نوع int و دستورالعمل تعریف می کند

    int ia[ 10];

    آرایه ای از ده شی int را مشخص می کند. برای هر یک از این اشیاء، یا عناصر آرایه، با استفاده از عملیات گرفتن شاخص قابل دسترسی است:

    ایوال = ia[ 2 ];

    مقدار عنصر آرایه ia با اندیس 2 را به متغیر ival نسبت می دهد. به طور مشابه

    Ia[ 7 ] = ival;

    عنصر را در شاخص 7 برابر ival قرار می دهد.

    تعریف آرایه شامل یک نوع مشخص کننده، نام آرایه و اندازه است. اندازه تعداد عناصر آرایه (حداقل 1) را مشخص می کند و در پرانتز مربع قرار می گیرد. اندازه یک آرایه باید از قبل در زمان کامپایل مشخص باشد، و بنابراین باید یک عبارت ثابت باشد، اگرچه لزومی ندارد که یک کلمه واقعی باشد. در اینجا نمونه هایی از تعاریف درست و نادرست آرایه آورده شده است:

    extern int get_size(); // ثابت buf_size و max_files
    const int buf_size = 512، max_files = 20;
    intstaff_size = 27; // صحیح: char ثابت input_buffer[ buf_size ]; // صحیح: عبارت ثابت: 20 - 3 char *fileTable[ max_files-3 ]; // خطا: حقوق دو برابر ثابت نیست[ staff_size ]; // خطا: یک عبارت ثابت نیست int test_scores[ get_size() ];

    اشیاء buf_size و max_files ثابت هستند، بنابراین تعاریف آرایه input_buffer و fileTable صحیح هستند. اما staff_size یک متغیر است (البته به مقدار ثابت 27 مقداردهی اولیه شده است)، بنابراین حقوق غیرقانونی است. (کامپایلر قادر به یافتن مقدار متغیر staff_size در زمان تعریف آرایه حقوق نیست.)
    عبارت max_files-3 را می توان در زمان کامپایل ارزیابی کرد، بنابراین تعریف آرایه fileTable از نظر نحوی صحیح است.
    شماره گذاری عناصر از 0 شروع می شود، بنابراین برای یک آرایه 10 عنصری، محدوده شاخص صحیح 1 - 10 نیست، بلکه 0 - 9 است. در اینجا مثالی از تکرار روی همه عناصر آرایه آمده است:

    int main() (const int array_size = 10; int ia[ array_size ]; for (int ix = 0; ix< array_size; ++ ix)
    ia[ ix ] = ix;
    }

    هنگام تعریف یک آرایه، می‌توانید به صراحت آن را با فهرست کردن مقادیر عناصر آن در پرانتزها، که با کاما از هم جدا شده‌اند، مقداردهی اولیه کنید:

    const int array_size = 3; int ia[ array_size ] = ( 0, 1, 2 );

    اگر به صراحت لیستی از مقادیر را مشخص کنیم، نمی توانیم اندازه آرایه را مشخص کنیم: کامپایلر تعداد عناصر را خودش محاسبه می کند:

    // آرایه با اندازه 3 int ia = ( 0, 1, 2 );

    وقتی هم اندازه و هم لیست مقادیر به صراحت مشخص شده باشد، سه گزینه وجود دارد. وقتی اندازه و تعداد مقادیر مطابقت دارند، همه چیز واضح است. اگر لیست مقادیر کوتاهتر از اندازه مشخص شده باشد، عناصر باقیمانده آرایه به صفر مقداردهی اولیه می شوند. اگر مقادیر بیشتری در لیست وجود داشته باشد، کامپایلر یک پیام خطا نمایش می دهد:

    // ia ==> ( 0, 1, 2, 0, 0 ) const int array_size = 5; int ia[ array_size ] = ( 0, 1, 2 );

    یک آرایه کاراکتری را می توان نه تنها با لیستی از مقادیر کاراکترها در پرانتزهای فرفری، بلکه با یک رشته واقعی نیز مقداردهی کرد. با این حال، تفاوت هایی بین این روش ها وجود دارد. بیایید بگوییم

    Const char cal = ("C"، "+"، "+"); const char cal2 = "C++";

    بعد آرایه ca1 3 است، بعد آرایه ca2 4 است (کاراکتر تهی پایانی در حرف های رشته ای در نظر گرفته می شود). تعریف زیر باعث خطای کامپایل می شود:

    // error: string "Daniel" از 7 عنصر تشکیل شده است const char ch3[ 6 ] = "Daniel";

    به یک آرایه نمی توان مقدار آرایه دیگری را اختصاص داد و مقداردهی اولیه یک آرایه توسط آرایه دیگر نیز غیرقانونی است. همچنین استفاده از آرایه از منابع مجاز نیست. در اینجا نمونه هایی از استفاده صحیح و نادرست از آرایه ها آورده شده است:

    const int array_size = 3; int ix, jx, kx; // صحیح: آرایه ای از اشاره گرها از نوع int* int *iar = ( &ix, &jx, &kx ); // خطا: آرایه های مرجع نامعتبر هستند int &iar = ( ix, jx, kx ); int main()
    {
    int ia3( array_size ]; // صحیح است
    // خطا: آرایه های داخلی قابل کپی نیستند
    ia3 = ia;
    بازگشت 0;
    }

    برای کپی کردن یک آرایه به آرایه دیگر، باید این کار را برای هر عنصر جداگانه انجام دهید:

    const int array_size = 7; int ia1 = ( 0، 1، 2، 3، 4، 5، 6); int main() (
    int ia3[اندازه_آرایه]; برای (int ix = 0; ix< array_size; ++ix)
    ia2[ ix ] = ia1[ ix ]; بازگشت 0;
    }

    یک شاخص آرایه می تواند هر عبارتی باشد که یک نتیجه از نوع عدد صحیح به دست می دهد. مثلا:

    int someVal, get_index(); ia2[ get_index() ] = someVal;

    تاکید می کنیم که زبان C++ نه در زمان کامپایل و نه در زمان اجرا کنترلی بر شاخص های آرایه ارائه نمی دهد. خود برنامه نویس باید اطمینان حاصل کند که ایندکس از مرزهای آرایه فراتر نمی رود. خطاهای ایندکس بسیار رایج هستند. متأسفانه، یافتن نمونه‌هایی از برنامه‌هایی که کامپایل و حتی اجرا می‌شوند، اما با این وجود حاوی خطاهای مهلکی هستند که دیر یا زود منجر به خرابی می‌شوند، چندان دشوار نیست.

    تمرین 3.22

    کدام یک از تعاریف آرایه زیر حاوی خطا است؟ توضیح.

    (الف) int ia[buf_size]؛ (د) int ia[ 2 * 7 - 14 ] (ب) int ia[ get_size() ]; (ه) char st[ 11 ] = "بنیادی"; (ج) int ia[ 4 * 7 - 14 ];

    تمرین 3.23

    قطعه کد زیر باید هر عنصر آرایه را با مقدار شاخص مقداردهی اولیه کند. اشتباهاتی که مرتکب شده اید را پیدا کنید:

    int main() (const int array_size = 10; int ia[ array_size ]; for (int ix = 1; ix<= array_size; ++ix)
    ia[ia] = ix; //...
    }

    3.9.1. آرایه های چند بعدی

    در C++ امکان استفاده از آرایه های چند بعدی وجود دارد که باید با مرز سمت راست هر بعد در براکت های مجزا اعلان شوند. در اینجا تعریف آرایه دو بعدی آمده است:

    Int ia[ 4 ][ 3 ];

    مقدار اول (4) تعداد سطرها و دومی (3) تعداد ستون ها را مشخص می کند. شی ia به عنوان آرایه ای از چهار رشته با سه عنصر تعریف می شود. آرایه های چند بعدی نیز می توانند مقداردهی اولیه شوند:

    Int ia[ 4 ][ 3 ] = ( ( 0, 1, 2 ), ( 3, 4, 5 ), ( 6, 7, 8 ), ( 9, 10, 11 ) );

    بریس های مجعد داخلی که لیست مقادیر را به خطوط تقسیم می کنند اختیاری هستند و معمولاً برای خوانایی کد استفاده می شوند. مقداردهی اولیه زیر دقیقاً مشابه مثال قبلی است، اگرچه کمتر واضح است:

    int ia = (0,1,2,3,4,5,6,7,8,9,10,11)؛

    تعریف زیر فقط اولین عناصر هر ردیف را مقداردهی اولیه می کند. عناصر باقیمانده صفر خواهند بود:

    Int ia[ 4 ][ 3 ] = ( (0)، (3)، (6)، (9));

    اگر بریس های مجعد داخلی را حذف کنید، نتیجه کاملاً متفاوت است. هر سه عنصر ردیف اول و عنصر اول دوم مقدار مشخص شده را دریافت می کنند و بقیه به طور ضمنی به 0 مقداردهی اولیه می شوند.

    Int ia[ 4 ][ 3 ] = ( 0, 3, 6, 9 );

    هنگام دسترسی به عناصر یک آرایه چند بعدی، باید از شاخص ها برای هر بعد استفاده کنید (آنها در پرانتز مربع قرار دارند). راه‌اندازی اولیه یک آرایه دو بعدی با استفاده از حلقه‌های تودرتو به این صورت است:

    int main() (const int rowSize = 4; const int colSize = 3; int ia[ rowSize ][ colSize ]; for (int = 0; i< rowSize; ++i)
    برای (int j = 0; j< colSize; ++j)
    ia[ i ][ j ] = i + j j;
    }

    طرح

    Ia [ 1، 2 ]

    از نظر نحو C++ معتبر است، اما به معنای آن چیزی نیست که یک برنامه نویس بی تجربه انتظار دارد. این به هیچ وجه اعلان یک آرایه دو بعدی 1 در 2 نیست. یک مجموع در پرانتز لیستی از عبارات جدا شده با کاما است که به مقدار نهایی 2 منجر می شود (عملگر "کاما" را در بخش 4.2 ببینید. ). بنابراین، اعلام ia معادل ia است. این یک فرصت دیگر برای اشتباه کردن است.

    3.9.2. رابطه بین آرایه ها و اشاره گرها

    اگر تعریف آرایه ای داشته باشیم:

    Int ia = ( 0، 1، 1، 2، 3، 5، 8، 13، 21);

    پس یک علامت ساده از نام آن در برنامه به چه معناست؟

    استفاده از شناسه آرایه در یک برنامه معادل تعیین آدرس اولین عنصر آن است:

    به طور مشابه، دو راه برای دسترسی به مقدار عنصر اول یک آرایه وجود دارد:

    // هر دو عبارت اولین عنصر *ia; ia;

    برای گرفتن آدرس عنصر دوم آرایه، می نویسیم:

    همانطور که قبلا ذکر کردیم، بیان

    همچنین آدرس عنصر آرایه دوم را می دهد. بر این اساس، ارزش آن از دو طریق زیر به ما داده می شود:

    *(ia+1)؛ ia;

    به تفاوت عبارات توجه کنید:

    *ia+1 و *(ia+1)؛

    عملیات ارجاع بالاتری دارد یک اولویتاز عملیات جمع (برای اطلاعات بیشتر در مورد اولویت عملگر به بخش 4.13 مراجعه کنید). بنابراین عبارت اول ابتدا متغیر ia را تغییر می دهد و اولین عنصر آرایه را می گیرد و سپس 1 را به آن اضافه می کند و عبارت دوم مقدار عنصر دوم را ارائه می دهد.

    شما می توانید از طریق یک آرایه با استفاده از یک شاخص، همانطور که در بخش قبل انجام دادیم، یا با استفاده از نشانگرها، تکرار کنید. مثلا:

    #عبارتند از int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 )؛ int *pbegin = ia؛ int *pend = ia + 9؛ while (pbegin != pend) ( کوت<< *pbegin <<; ++pbegin; } }

    اشاره گر pbegin با آدرس اولین عنصر آرایه مقدار دهی اولیه می شود. هر تکرار از طریق حلقه این اشاره گر را 1 افزایش می دهد، به این معنی که به عنصر بعدی منتقل می شود. چگونه بدانیم کجا اقامت کنیم؟ در مثال ما یک نشانگر pend دوم تعریف کرده ایم و آن را با آدرسی که آخرین عنصر آرایه ia را دنبال می کند مقداردهی اولیه می کنیم. به محض اینکه مقدار pbegin برابر با pend شد، می دانیم که آرایه تمام شده است. بیایید این برنامه را بازنویسی کنیم تا ابتدا و انتهای آرایه به عنوان پارامتر به یک تابع عمومی منتقل شود که می تواند یک آرایه با هر اندازه ای را چاپ کند:

    #عبارتند از void ia_print(int *pbegin, int *pend) (
    در حالی که (pbegin != pend) (
    کوت<< *pbegin << " ";
    ++pbegin;
    }
    ) int main()
    {
    int ia = ( 0، 1، 1، 2، 3، 5، 8، 13، 21);
    ia_print(ia, ia + 9);
    }

    تابع ما جهانی تر شده است، با این حال، فقط می تواند با آرایه های int کار کند. راهی برای حذف این محدودیت نیز وجود دارد: تبدیل تابع داده شده به یک الگو (الگوها به طور خلاصه در بخش 2.5 معرفی شدند):

    #عبارتند از قالب void print(elemType *pbegin, elemType *pend) ( while (pbegin != pend) (cout<< *pbegin << " "; ++pbegin; } }

    اکنون می‌توانیم تابع print() خود را برای چاپ آرایه‌هایی از هر نوع فراخوانی کنیم:

    int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 )؛ double da = ( 3.14, 6.28, 12.56, 25.12 )؛ string sa = ( "piglet", " چشم، "pooh"); print(ia، ia+9);
    print(da,da+4);
    چاپ (sa, sa+3);
    }

    ما نوشتیم تعمیم یافته استتابع. کتابخانه استاندارد مجموعه ای از الگوریتم های عمومی را ارائه می دهد (که قبلاً در بخش 3.4 به این موضوع اشاره کردیم) که به این روش پیاده سازی شده اند. پارامترهای چنین توابعی نشانگرهایی به ابتدا و انتهای آرایه هستند که با آنها اقدامات خاصی را انجام می دهند. به عنوان مثال، در اینجا نحوه فراخوانی به الگوریتم مرتب سازی تعمیم یافته است:

    #عبارتند از int main() ( int ia = ( 107, 28, 3, 47, 104, 76 ); string sa = ( "piglet", "eeyore", "pooh"); sort(ia, ia+6);
    مرتب سازی (sa, sa+3);
    };

    (ما الگوریتم های تعمیم یافته را به تفصیل در فصل 12 مورد بحث قرار خواهیم داد؛ نمونه هایی از استفاده از آنها در ضمیمه آورده شده است.)
    کتابخانه استاندارد C++ شامل مجموعه ای از کلاس هاست که استفاده از کانتینرها و اشاره گرها را در بر می گیرد. (ما در این مورد در بخش 2.8 بحث کردیم.) در بخش بعدی، بردار استاندارد نوع ظرف را بررسی خواهیم کرد که یک پیاده سازی شی گرا از یک آرایه است.

    3.10. کلاس برداری

    استفاده از کلاس برداری (به بخش 2.8 مراجعه کنید) جایگزینی برای استفاده از آرایه های داخلی است. این کلاس ویژگی های بسیار بیشتری را ارائه می دهد، بنابراین استفاده از آن ترجیح داده می شود. با این حال، شرایطی وجود دارد که نمی توان آرایه هایی از نوع داخلی را کنار گذاشت. یکی از این موقعیت ها پردازش پارامترهای خط فرمان است که به برنامه منتقل می شود که در بخش 7.8 به آن خواهیم پرداخت. کلاس برداری، مانند کلاس رشته، بخشی از کتابخانه استاندارد C++ است.
    برای استفاده از وکتور، باید یک فایل هدر اضافه کنید:

    #عبارتند از

    دو رویکرد کاملاً متفاوت برای استفاده از بردار وجود دارد، بیایید آنها را اصطلاح آرایه و اصطلاح STL بنامیم. در حالت اول، یک شی از کلاس بردار به همان روشی که یک آرایه از نوع داخلی استفاده می شود. بردار یک بعد معین تعریف می شود:

    بردار< int >ivec (10)؛

    که همان تعریف آرایه ای از نوع داخلی است:

    int ia[ 10];

    برای دسترسی به عناصر منفرد بردار، از عملیات گرفتن شاخص استفاده می شود:

    void simp1e_examp1e() (const int e1em_size = 10؛ برداری< int >ivec(e1em_size); int ia[ e1em_size ]; برای (int ix = 0; ix< e1em_size; ++ix)
    ia[ ix ] = ivec[ ix ]; //...
    }

    می‌توانیم اندازه یک بردار را با استفاده از تابع size() دریابیم و با استفاده از تابع ()() خالی بودن بردار را بررسی کنیم. مثلا:

    چاپ_بردار خالی (بردار ivec) (اگر (ivec.empty()) بازگشت؛ برای (int ix=0; ix< ivec.size(); ++ix)
    کوت<< ivec[ ix ] << " ";
    }

    عناصر برداری با مقادیر پیش فرض مقداردهی اولیه می شوند. برای انواع عددی و اشاره گرها، این مقدار 0 است. اگر اشیاء کلاس به عنوان عناصر عمل کنند، آغازگر آنها توسط سازنده پیش فرض مشخص می شود (به بخش 2.3 مراجعه کنید). با این حال، آغازگر را نیز می توان به صراحت با استفاده از فرم مشخص کرد:

    بردار< int >ivec (10, -1)؛

    هر ده عنصر بردار برابر با 1- خواهد بود.
    یک آرایه از نوع داخلی را می توان به طور صریح با یک لیست مقداردهی اولیه کرد:

    int ia[ 6 ] = (-2، -1، 0، 1، 2، 1024);

    برای یک شی از کلاس برداری، عمل مشابه امکان پذیر نیست. با این حال، چنین شیئی را می توان با آرایه ای از نوع داخلی مقداردهی کرد:

    // 6 عنصر ia در بردار ivec کپی می شود< int >ivec(ia، ia+6)؛

    دو اشاره گر به سازنده بردار ivec ارسال می شود - یک اشاره گر به ابتدای آرایه ia و به عنصری که بعد از آخرین نشانگر است. به عنوان لیستی از مقادیر اولیه، مجاز است که نه کل آرایه، بلکه برخی از محدوده آن را مشخص کنید:

    // 3 عنصر کپی می شود: بردار ia، ia، ia< int >ivec(&ia[2]، &ia[5]);

    تفاوت دیگر بین یک بردار و یک آرایه از نوع داخلی، توانایی مقداردهی اولیه یک شی از نوع برداری با دیگری و استفاده از عملگر انتساب برای کپی کردن اشیا است. مثلا:

    بردار< string >svec; void init_and_assign() (// یک بردار توسط بردار دیگر مقداردهی اولیه می شود< string >user_names(svec); // ... // یک بردار در دیگری کپی می شود
    svec = user_names;
    }

    صحبت از اصطلاح STL، منظور ما یک رویکرد بسیار متفاوت برای استفاده از یک برداری است. به جای اینکه فوراً اندازه مناسب را تنظیم کنیم، یک بردار خالی تعریف می کنیم:

    بردار< string >متن;

    سپس با استفاده از توابع مختلف به آن عناصر اضافه می کنیم. برای مثال، تابع push_back() یک عنصر را در انتهای یک بردار درج می کند. در اینجا یک قطعه کد است که دنباله ای از خطوط را از ورودی استاندارد می خواند و آنها را به یک بردار اضافه می کند:

    کلمه رشته while (cin >> word) ( text.push_back(word); // ... )

    اگرچه می توانیم از عملیات take index برای تکرار بر روی عناصر یک بردار استفاده کنیم:

    کوت<< "считаны слова: \n"; for (int ix =0; ix < text.size(); ++ix) cout << text[ ix ] << " "; cout << endl;

    معمول‌تر در این اصطلاح استفاده از تکرارکننده‌ها است:

    کوت<< "считаны слова: \n"; for (vector::iterator it = text.begin(); it != text.end(); ++آن) cout<< *it << " "; cout << endl;

    تکرارکننده یک کلاس کتابخانه استاندارد است که در واقع نشانگر عنصری از یک آرایه است.
    اصطلاح

    تکرار کننده را حذف می کند و خود عنصر برداری را می دهد. دستورالعمل

    اشاره گر را به عنصر بعدی می برد. نیازی به ترکیب این دو رویکرد نیست. هنگام تعریف بردار خالی، از اصطلاح STL پیروی کنید:

    بردار ivec;

    اشتباه است اگر بنویسیم:

    ما هنوز یک عنصر از بردار ivec نداریم. تعداد عناصر با استفاده از تابع size() مشخص می شود.

    شما همچنین می توانید اشتباه مخالف را مرتکب شوید. اگر یک بردار با اندازه ای تعریف کرده باشیم، برای مثال:

    بردار ia (10);

    سپس قرار دادن عناصر با افزودن عناصر جدید به عناصر موجود، اندازه آن را افزایش می دهد. اگرچه این امر بدیهی به نظر می رسد، با این وجود، یک برنامه نویس تازه کار به خوبی می تواند بنویسد:

    Const int size = 7; int ia [ اندازه ] = ( 0، 1، 1، 2، 3، 5، 8); بردار< int >ivec (اندازه)؛ برای (int ix = 0; ix< size; ++ix) ivec.push_back(ia[ ix ]);

    این به معنای مقداردهی اولیه بردار ivec با مقادیر عناصر ia بود که به جای آن یک بردار ivec با اندازه 14 دریافت کردیم.
    با پیروی از اصطلاح STL، نه تنها می توانید عناصر یک بردار را اضافه کنید، بلکه حذف کنید. (ما همه اینها را به تفصیل و با مثال هایی در فصل 6 پوشش خواهیم داد.)

    تمرین 3.24

    آیا در تعاریف زیر اشتباهاتی وجود دارد؟
    int ia[ 7] = ( 0، 1، 1، 2، 3، 5، 8);

    (الف) بردار< vector< int >>ivec;
    (ب) بردار< int >ivec = (0، 1، 1، 2، 3، 5، 8)؛
    ج) بردار< int >ivec(ia، ia+7)؛
    (د) بردار< string >svec = ivec;
    (ه) بردار< string >svec(10، string("null"));

    تمرین 3.25

    تابع زیر را اجرا کنید:
    bool is_equal(const int*ia, int ia_size,
    بردار const &ivec)؛
    تابع is_equal() دو ظرف را عنصر به عنصر مقایسه می کند. در مورد اندازه های مختلف ظروف، "دم" طولانی تر در نظر گرفته نمی شود. واضح است که اگر همه عناصر مقایسه شده برابر باشند، تابع true و اگر حداقل یکی متفاوت باشد، false را برمی گرداند. از یک تکرار کننده برای تکرار روی عناصر استفاده کنید. یک تابع main() بنویسید که is_equal() را فراخوانی کند.

    3.11. کلاس پیچیده

    کلاس اعداد مختلط کلاس دیگری از کتابخانه استاندارد است. طبق معمول، برای استفاده از آن، باید فایل هدر را وارد کنید:

    #عبارتند از

    یک عدد مختلط دو بخش دارد - واقعی و خیالی. قسمت خیالی جذر یک عدد منفی است. یک عدد مختلط معمولاً به صورت نوشته می شود

    که در آن 2 قسمت واقعی و 3i قسمت خیالی است. در اینجا نمونه هایی از تعاریف پیچیده اشیاء آورده شده است:

    // عدد خیالی خالص: 0 + 7-i مختلط< double >پوره (0، 7); // قسمت خیالی 0: 3 + مجتمع Oi است< float >rea1_num (3); // هر دو بخش واقعی و خیالی 0: 0 + 0-i مختلط هستند< long double >صفر؛ // مقدار دهی اولیه یک عدد مختلط توسط مختلط دیگر< double >purei2 (purei);

    از آنجایی که پیچیده، مانند برداری، یک الگو است، می‌توانیم آن را با float، double و long double مانند مثال‌های بالا نمونه‌سازی کنیم. همچنین می توانید آرایه ای از عناصر پیچیده را تعریف کنید:

    مجتمع< double >مزدوج[ 2 ] = (مختلط< double >(2، 3)، مجتمع< double >(2, -3) };

    مجتمع< double >*ptr = پیچیده< double >&ref = *ptr;

    3.12. دستورالعمل typedef

    دستورالعمل typedef به شما امکان می دهد مترادف یک نوع داده داخلی یا تعریف شده توسط کاربر را مشخص کنید. مثلا:

    دستمزد دو برابر Typedef; وکتور typedef vec_int; typedef vec_int test_scores; typedef bool in_attendance; typedef int *Pint;

    نام های تعریف شده با دستورالعمل typedef را می توان به همان روشی که تعیین کننده نوع استفاده کرد:

    // دو ساعتی، هفتگی؛ دستمزد ساعتی، هفتگی؛ // بردار vecl(10);
    vec_int vecl(10); // بردار test0 (c1ass_size); const int c1ass_size = 34; test_scores test0(c1ass_size); // بردار< bool >حضور؛ بردار< in_attendance >حضور (c1ass_size)؛ // int *table[ 10]; جدول پینت [ 10 ];

    این دستورالعمل با کلمه کلیدی typedef و به دنبال آن یک نوع مشخص کننده شروع می شود و با یک شناسه به پایان می رسد که نام مستعار برای نوع مشخص شده می شود.
    نام های تعریف شده با دستورالعمل typedef برای چه مواردی استفاده می شود؟ با استفاده از نام های یادگاری برای انواع داده، می توانید درک برنامه خود را آسان تر کنید. همچنین مرسوم است که از چنین نام هایی برای انواع پیچیده پیچیده استفاده شود که در غیر این صورت درک آنها دشوار است (به مثالی در بخش 3.14 مراجعه کنید)، برای اعلام اشاره گر به توابع و توابع عضو یک کلاس (به بخش 13.6 مراجعه کنید).
    در زیر نمونه سوالی است که تقریباً همه به آن پاسخ اشتباه می دهند. این خطا به دلیل درک نادرست دستورالعمل typedef به عنوان یک جایگزین ساده متنی کلان ایجاد می شود. تعریف ارائه شده است:

    Typedef char *cstring;

    نوع متغیر cstr در اعلان زیر چیست:

    extern const cstring cstr;

    پاسخی که واضح به نظر می رسد این است:

    Const char *cstr

    با این حال، این درست نیست. مشخص کننده const به cstr اشاره دارد، بنابراین پاسخ صحیح یک اشاره گر const به char است:

    Char *const cstr;

    3.13. مشخص کننده فرار

    یک شی فرار (ناپایدار، ناهمزمان قابل تغییر) اعلام می شود اگر بتوان مقدار آن را بدون توجه کامپایلر تغییر داد، مانند متغیری که توسط ساعت سیستم به روز می شود. این مشخص کننده به کامپایلر می گوید که کد را برای کار با شی داده شده بهینه نکند.
    مشخص کننده فرار مانند مشخص کننده const استفاده می شود:

    فرار int disp1ay_register; وظیفه فرار *curr_task; volatile int ixa[ max_size ]; فرار صفحه نمایش bitmap_buf;

    display_register یک شی ناپایدار از نوع int است. curr_task یک اشاره گر به یک شیء Task ناپایدار است. ixa یک آرایه ناپایدار از اعداد صحیح است و هر عنصر از چنین آرایه ای ناپایدار در نظر گرفته می شود. bitmap_buf یک شی فرار از کلاس Screen است که هر یک از اعضای داده آن نیز فرار در نظر گرفته می شود.
    تنها هدف از استفاده از مشخص کننده فرار این است که به کامپایلر بگوییم که نمی تواند تعیین کند چه کسی و چگونه می تواند مقدار یک شی معین را تغییر دهد. بنابراین، کامپایلر نباید کدی را که از این شیء استفاده می کند، بهینه کند.

    3.14. کلاس جفت

    کلاس جفت کتابخانه استاندارد C++ به ما امکان می دهد در صورت وجود رابطه معنایی بین آنها، یک جفت مقادیر را با یک شی تعریف کنیم. این مقادیر می توانند انواع مشابه یا متفاوت باشند. برای استفاده از این کلاس، باید یک فایل هدر اضافه کنید:

    #عبارتند از

    به عنوان مثال، دستورالعمل

    جفت کردن< string, string >نویسنده ("جیمز"، "جویس")؛

    یک شی نویسنده از نوع جفت ایجاد می کند که از دو مقدار رشته تشکیل شده است.
    بخش‌های مجزای یک جفت را می‌توان با استفاده از اعضای اول و دوم بازیابی کرد:

    رشته firstbook; اگر (Joyce.first == "James" &&
    Joyce.second == "جویس")
    firstBook = "Stephen Hero";

    اگر نیاز به تعریف چندین شی از یک نوع از این کلاس دارید، استفاده از دستورالعمل typedef راحت است:

    جفت Typedef< string, string >نویسندگان؛ نویسندگان proust("marcel", "proust"); نویسندگان جویس ("جیمز"، "جویس"); نویسندگان musil("robert", "musi1");

    در اینجا مثال دیگری از استفاده از یک جفت آورده شده است. مقدار اول شامل نام برخی از شی است، دومی نشانگر عنصر جدول مربوط به این شی است.

    ورودی کلاس؛ extern EntrySlot* 1ook_up(string); جفت typedef< string, EntrySlot* >SymbolEntry; SymbolEntry current_entry("نویسنده"، 1ook_up("نویسنده"));
    // ... if (EntrySlot *it = 1ook_up("ویرایشگر")) (
    current_entry.first = "ویرایشگر";
    current_entry.second = آن;
    }

    (ما به بحث کلاس جفت در فصل 6 در مورد انواع کانتینر و الگوریتم های عمومی در فصل 12 باز خواهیم گشت.)

    3.15. انواع کلاس ها

    مکانیسم کلاس به شما اجازه می دهد تا انواع داده های جدید ایجاد کنید. انواع رشته، برداری، مختلط و جفتی را که در بالا توضیح داده شد معرفی کرد. در فصل 2، ما در مورد مفاهیم و مکانیسم هایی صحبت کردیم که از رویکرد شی گرا و شی گرا پشتیبانی می کنند و از پیاده سازی کلاس Array به عنوان مثال استفاده می کنیم. در اینجا، بر اساس رویکرد شی، ما یک کلاس ساده String ایجاد می کنیم، که اجرای آن به درک، به ویژه، بارگذاری بیش از حد اپراتور کمک می کند - ما در مورد آن در بخش 2.3 صحبت کردیم. (در فصل های 13، 14 و 15 دروس به تفصیل بحث شده است). توضیح مختصری در مورد کلاس داده ایم تا مثال های جالب تری ارائه دهیم. خواننده‌ای که تازه شروع به یادگیری C++ کرده است، می‌تواند این بخش را نادیده بگیرد و منتظر توضیح سیستماتیک‌تر کلاس‌ها در فصل‌های بعدی باشد.)
    کلاس String ما باید از مقداردهی اولیه با یک شی کلاس String، یک string literal و یک نوع رشته داخلی و همچنین عملیات تخصیص مقادیر این نوع به آن پشتیبانی کند. برای این کار از سازنده کلاس و یک عملگر انتساب اضافه بار استفاده می کنیم. دسترسی به کاراکترهای منفرد از یک رشته به عنوان یک عملگر دریافت شاخص اضافه بار اجرا می شود. علاوه بر این، ما به تابع size() برای دریافت اطلاعات در مورد طول رشته نیاز داریم. عملیات مقایسه اشیاء از نوع String و یک شی String با رشته ای از نوع داخلی. و همچنین عملیات I/O شی ما. در نهایت، ما توانایی دسترسی به نمایش داخلی رشته خود را به عنوان یک رشته نوع داخلی اجرا می کنیم.
    تعریف کلاس با کلمه کلیدی کلاس و به دنبال آن یک شناسه، نام کلاس یا نوع شروع می شود. به طور کلی، یک کلاس از بخش هایی تشکیل شده است که قبل از کلمات عمومی (باز) و خصوصی (بسته) قرار می گیرند. یک بخش باز معمولاً شامل مجموعه ای از عملیات پشتیبانی شده توسط کلاس است که متدها یا توابع عضو کلاس نامیده می شوند. این توابع عضو، رابط عمومی کلاس را تعریف می کنند، به عبارت دیگر، مجموعه اقداماتی را که می توان روی اشیاء آن کلاس انجام داد. یک بخش خصوصی معمولاً شامل اعضای داده ای است که یک پیاده سازی داخلی را ارائه می دهند. در مورد ما، اعضای داخلی شامل _string - یک اشاره گر به char، و همچنین _size از نوع int هستند. _size اطلاعات مربوط به طول رشته را ذخیره می کند و _string آرایه ای از کاراکترها است که به صورت پویا تخصیص داده شده است. در اینجا تعریف کلاس به نظر می رسد:

    #عبارتند از کلاس رشته; istream& operator>>(istream&, String&);
    جریان و اپراتور<<(ostream&, const String&); class String {
    عمومی:
    // مجموعه ای از سازنده ها
    // برای مقدار دهی اولیه خودکار
    // Stringstrl; // رشته ()
    // String str2("literal"); // رشته (const char*);
    // رشته str3(str2); // رشته (const String&); string();
    رشته (const char*);
    رشته (const String&); // ویرانگر
    ~string(); // عملگرهای انتساب
    // strl = str2
    // str3 = "a string literal" String& operator=(const String&);
    String& operator=(const char*); // عملگرهای برابری
    // strl == str2;
    // str3 == "یک رشته تحت اللفظی"; عملگر bool==(const String&);
    bool operator==(const char*); // بارگذاری بیش از حد اپراتور دسترسی بر اساس شاخص
    // strl[ 0 ] = str2[ 0 ]; char& operator(int); // دسترسی به اعضای کلاس
    int size() ( return _size; )
    char* c_str() ( return _string; ) private:
    int_size;
    char*_string;
    }

    کلاس String سه سازنده دارد. همانطور که در بخش 2.3 بحث شد، مکانیسم اضافه بار اجازه می دهد تا چندین پیاده سازی از توابع با نام یکسان تعریف شوند، تا زمانی که همه آنها در تعداد و/یا نوع پارامترهایشان متفاوت باشند. اولین سازنده

    این سازنده پیش فرض است زیرا به مقدار اولیه صریح نیاز ندارد. وقتی می نویسیم:

    برای str1، چنین سازنده ای نامیده می شود.
    دو سازنده باقیمانده هر کدام یک پارامتر دارند. بله، برای

    رشته str2 ("رشته کاراکتر");

    سازنده نامیده می شود

    رشته (const char*);

    رشته str3(str2);

    سازنده

    رشته (const String&);

    نوع سازنده فراخوانی شده با نوع آرگومان واقعی تعیین می شود. آخرین سازنده، String(const String&)، سازنده کپی نامیده می شود زیرا یک شی را با یک کپی از یک شی دیگر مقدار دهی اولیه می کند.
    اگر بنویسید:

    stringstr4(1024);

    این باعث یک خطای کامپایل می شود زیرا سازنده ای با پارامتر نوع int وجود ندارد.
    یک اعلامیه اپراتور بارگذاری شده دارای فرمت زیر است:

    عملگر return_type op(parameter_list);

    جایی که عملگر یک کلمه کلیدی است و op یکی از عملگرهای از پیش تعریف شده است: +، =، ==، و غیره. (برای تعریف دقیق نحو، به فصل 15 مراجعه کنید.) در اینجا اعلان عملگر زیر بارگذاری شده است:

    Char& operator(int);

    این عملگر دارای یک پارامتر واحد از نوع int است و یک مرجع به char برمی گرداند. اگر لیست پارامترهای نمونه های جداگانه متفاوت باشد، یک اپراتور بارگذاری شده می تواند خود بارگذاری شود. برای کلاس String خود، دو عملگر تخصیص و برابری متفاوت ایجاد خواهیم کرد.
    برای فراخوانی یک تابع عضو، از نقطه (.) یا فلش (->) اپراتورهای دسترسی عضو استفاده کنید. فرض کنید اعلان شی از نوع String داریم:

    شی رشته ("Danny");
    String *ptr = new String("Anna");
    Stringarray;
    در اینجا فراخوانی تابع size() برای این اشیاء به نظر می رسد:
    بردار اندازه (3)؛

    // دسترسی اعضا برای اشیاء (.); // اشیاء 5 اندازه هستند[ 0 ] = object.size(); // دسترسی اعضا برای نشانگرها (->)
    // ptr دارای اندازه 4 است
    sizes[ 1 ] = ptr->size(); // دسترسی اعضا (.)
    // آرایه اندازه 0 دارد
    sizes[ 2 ] = array.size();

    به ترتیب 5، 4 و 0 را برمی گرداند.
    عملگرهای بارگذاری شده مانند عملگرهای معمولی به یک شی اعمال می شوند:

    نام رشته ("Yadie"); Stringname2 ("Yodie"); // عملگر bool==(const String&)
    اگر (نام == نام 2)
    برگشت؛
    دیگر
    // String& operator=(const String&)
    name=name2;

    یک اعلان تابع عضو باید در داخل تعریف کلاس باشد و یک تعریف تابع می تواند داخل یا خارج از تعریف کلاس باشد. (هر دو size() و c_str() در داخل یک کلاس تعریف شده اند.) اگر تابعی خارج از یک کلاس تعریف شده باشد، باید مشخص کنیم که به کدام کلاس تعلق دارد. در این مورد، تعریف تابع در یک فایل منبع، مثلا String.C، و تعریف خود کلاس در یک فایل هدر (در مثال ما String.h) قرار می‌گیرد، که باید در فایل منبع گنجانده شود:

    // محتویات فایل منبع: String.C // شامل تعریف کلاس String
    #include "String.h" // شامل تعریف تابع strcmp() است
    #عبارتند از
    bool // نوع برگشتی
    رشته:: // کلاسی که تابع به آن تعلق دارد
    عملگر== // نام تابع: عملگر برابری
    (const String &rhs) // لیست پارامترها
    {
    if (_size != rhs._size)
    بازگشت نادرست؛
    strcmp(_stringq، rhs._string) را برگردانید؟
    غلط درست؛
    }

    به یاد بیاورید که strcmp() یک تابع کتابخانه استاندارد C است که دو رشته نوع داخلی را با هم مقایسه می کند و اگر رشته ها مساوی باشند 0 و در غیر این صورت غیر صفر است. عملگر شرطی (?:) مقدار قبل از علامت سوال را آزمایش می کند. اگر درست باشد، مقدار عبارت سمت چپ کولون برگردانده می شود، در غیر این صورت در سمت راست دو نقطه است. در مثال ما، اگر strcmp() یک مقدار غیر تهی را برگرداند، مقدار عبارت false است و اگر null باشد true است. (اپراتور شرطی در بخش 4.7 پوشش داده شده است.)
    عملیات مقایسه اغلب استفاده می شود، تابعی که آن را پیاده سازی می کند کوچک است، بنابراین مفید است که این تابع را داخلی (داخلی) اعلام کنید. کامپایلر متن تابع را به جای فراخوانی جایگزین می کند، بنابراین زمانی برای چنین فراخوانی صرف نمی شود. (توابع داخلی در بخش 7.6 مورد بحث قرار می گیرند.) یک تابع عضو که در یک کلاس تعریف شده است به طور پیش فرض داخلی است. اگر خارج از کلاس تعریف شده باشد، برای اعلام آن به صورت inline، باید از کلمه کلیدی inline استفاده کنید:

    Inline bool String::operator==(const String &rhs) (// همینطور)

    تعریف یک تابع درون خطی باید در یک فایل هدر حاوی تعریف کلاس باشد. با تعریف مجدد عملگر == به عنوان یک عملگر داخلی، باید خود متن تابع را از فایل String.C به فایل String.h منتقل کنیم.
    در زیر اجرای عملیات مقایسه یک شی String با یک رشته نوع داخلی است:

    bool درون خطی String::operator==(const char *s) ( return strcmp(_string, s) ? false: true; )

    نام سازنده با نام کلاس یکی است. برای برگرداندن یک مقدار در نظر گرفته نمی شود، بنابراین نیازی نیست که مقدار بازگشتی را چه در تعریف و چه در بدنه آن مشخص کنید. سازنده های متعددی می تواند وجود داشته باشد. مانند هر تابع دیگری، آنها را می توان به صورت درون خطی اعلام کرد.

    #عبارتند از // رشته درون خطی سازنده پیش فرض::String()
    {
    _size=0;
    _string=0;
    ) Inline String::String(const char *str) ( if (! str) ( _size = 0; _string = 0; ) other ( _size = str1en(str); strcpy(_string, str);) // سازنده کپی
    رشته درون خطی:: رشته (const String &rhs)
    {
    اندازه = rhs._size;
    if(!rhs._string)
    _string=0;
    دیگر(
    _string = کاراکتر جدید[_size + 1];
    } }

    از آنجایی که ما به صورت پویا حافظه را با عملگر جدید تخصیص دادیم، باید آن را با یک فراخوانی آزاد کنیم تا زمانی که دیگر به شی String نیاز نداریم، آن را حذف کنیم. یکی دیگر از عملکردهای عضو ویژه به این منظور خدمت می کند، تخریبگر، که به طور خودکار برای یک شیء زمانی که آن شیء وجود ندارد فراخوانی می شود. (به فصل 7 در مورد طول عمر شی مراجعه کنید.) نام یک تخریبگر از کاراکتر tilde (~) و نام کلاس تشکیل شده است. در اینجا تعریف تخریبگر کلاس String آورده شده است. اینجاست که ما عملیات حذف را برای آزاد کردن حافظه اختصاص داده شده در سازنده فراخوانی می کنیم:

    رشته درون خطی: :~String() (حذف _string;)

    هر دو اپراتور تخصیص بارگذاری شده از کلمه کلیدی ویژه این استفاده می کنند.
    وقتی می نویسیم:

    نام رشته ("اورویل")، name2 ("ویلبر");
    name = "Orville Wright";
    این یک اشاره گر است که به شی name1 در بدنه تابع عملیات تخصیص آدرس می دهد.
    این همیشه به شی کلاسی که تابع از طریق آن فراخوانی می شود اشاره می کند. اگر
    ptr->size();
    obj[1024];

    سپس در داخل size() مقدار این آدرس ذخیره شده در ptr خواهد بود. در داخل عملیات get index، این شامل آدرس obj است. با عدم ارجاع به این (با استفاده از *this)، خود شی را دریافت می کنیم. (این اشاره گر به تفصیل در بخش 13.4 توضیح داده شده است.)

    رشته و رشته درون خطی::operator=(const char *s) (اگر (! s) ( _size = 0؛ حذف _string؛ _string = 0; ) دیگری ( _size = str1en(s؛ حذف _string؛ _string = کاراکتر جدید[ _size + 1 ]; strcpy(_string, s); ) *this;)

    هنگام اجرای عملیات تخصیص، اغلب یک اشتباه مرتکب می شود: آنها فراموش می کنند بررسی کنند که آیا شیء کپی شده همان چیزی نیست که در آن کپی می شود یا خیر. ما این بررسی را با استفاده از همین نشانگر انجام خواهیم داد:

    رشته و رشته درون خطی::operator=(const String &rhs) ( // در عبارت // namel = *pointer_to_string // this is name1, // rhs - *pointer_to_string. if (this != &rhs) (

    در اینجا متن کامل عملیات اختصاص دادن یک شی از همان نوع به یک شی String است:

    رشته و رشته درون خطی::operator=(const String &rhs) ( if (this != &rhs) (حذف _string؛ _size = rhs._size؛ if (! rhs._string)
    _string=0;
    دیگر(
    _string = کاراکتر جدید[_size + 1];
    strcpy(_string, rhs._string);
    }
    }
    بازگشت *این;
    }

    عملیات زیرنویس تقریباً مشابه پیاده سازی آرایه ای است که در بخش 2.3 ایجاد کردیم:

    #عبارتند از کاراکتر درون خطی&
    رشته::عملگر (عنصر int)
    {
    عنوان (elem >= 0 && elem< _size);
    بازگشت _string[elem];
    }

    عملگرهای ورودی و خروجی به عنوان توابع مجزا و نه اعضای کلاس پیاده سازی می شوند. (در بخش 15.2 به دلایل این موضوع خواهیم پرداخت. بخش های 20.4 و 20.5 بارگذاری بیش از حد عملگرهای ورودی و خروجی کتابخانه iostream را مورد بحث قرار می دهند.) اپراتور ورودی ما نمی تواند بیش از 4095 کاراکتر را بخواند. setw() یک دستکاری کننده از پیش تعریف شده است، تعداد مشخص شده کاراکترها را منهای 1 از جریان ورودی می خواند، بنابراین اطمینان می دهد که بافر داخلی inBuf سرریز نمی شود. (فصل 20 به طور مفصل درباره دستکاری‌کننده setw() بحث می‌کند.) برای استفاده از دستکاری‌کننده‌ها، باید فایل هدر مناسب را وارد کنید:

    #عبارتند از istream& operator داخلی>>(istream &io, String &s) ( // محدودیت مصنوعی: 4096 کاراکتر const int 1imit_string_size = 4096; char inBuf[ limit_string_size ]; // setw() بخشی از کتابخانه iostream است // بلوک خواندن را محدود می کند اندازه به 1imit_string_size -l io >> setw(1imit_string_size) >> inBuf; s = mBuf; // String::operator=(const char*); return io; )

    دستور خروجی نیاز به دسترسی به نمایش داخلی رشته دارد. از آنجایی که اپراتور<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

    جریان داخلی و اپراتور<<(ostream& os, const String &s) { return os << s.c_str(); }

    در زیر یک نمونه برنامه است که از کلاس String استفاده می کند. این برنامه کلمات را از جریان ورودی می گیرد و تعداد کل آنها و همچنین تعداد کلمات "the" و "it" را می شمارد و حروف صدادار مواجه شده را ثبت می کند.

    #عبارتند از #include "String.h" int main() (int aCnt = 0، eCnt = 0، iCnt = 0، oCnt = 0، uCnt = 0، theCnt = 0، itCnt = 0، wdCnt = 0، notVowel = 0؛ / / کلمات "The" و "It"
    // با عملگر==(const char*) بررسی می کنیم
    رشته but, the("the"), it("it"); // عملگر >> (ostream&، String&)
    در حالی که (cin >> buf) (
    ++wdCnt; // اپراتور<<(ostream&, const String&)
    کوت<< buf << " "; if (wdCnt % 12 == 0)
    کوت<< endl; // String::operator==(const String&) and
    // رشته::operator==(const char*);
    if (buf == the | | buf == "The")
    ++theCnt;
    دیگر
    اگر (buf == آن || buf == "این")
    ++itCnt; // رشته::s-ize() را فراخوانی می کند
    برای (int ix = 0; ix< buf.sizeO; ++ix)
    {
    // رشته:: عملگر(int) را فراخوانی می کند
    سوئیچ (buf[ix])
    {
    case "a": case "A": ++aCnt; زنگ تفريح؛
    مورد "e": مورد "E": ++eCnt; زنگ تفريح؛
    case "i": مورد "I": ++iCnt; زنگ تفريح؛
    case "o": case "0": ++oCnt; زنگ تفريح؛
    case "u": case "U": ++uCnt; زنگ تفريح؛
    پیش فرض: ++notVowe1; زنگ تفريح؛
    }
    }
    ) // اپراتور<<(ostream&, const String&)
    کوت<< "\n\n"
    << "Слов: " << wdCnt << "\n\n"
    << "the/The: " << theCnt << "\n"
    << "it/It: " << itCnt << "\n\n"
    << "согласных: " < << "a: " << aCnt << "\n"
    << "e: " << eCnt << "\n"
    << "i: " << ICnt << "\n"
    << "o: " << oCnt << "\n"
    << "u: " << uCnt << endl;
    }

    بیایید برنامه را آزمایش کنیم: یک پاراگراف از یک داستان کودکانه که توسط یکی از نویسندگان این کتاب نوشته شده است را به آن پیشنهاد می کنیم (در فصل 6 با این داستان آشنا خواهیم شد). این هم خروجی برنامه:

    آلیس اِما موهای بلند و قرمزی دارد. باباش می گوید وقتی باد میان موهایش می زند، تقریباً زنده به نظر می رسد، مثل یک پرنده آتشین در حال پرواز. او به او می گوید که یک پرنده آتشین زیبا، جادویی اما رام نشده است. او به او می گوید: "بابا، شوش، چنین چیزی وجود ندارد." با خجالت می پرسد: یعنی بابا آنجا هست؟ کلمات: 65
    the/The: 2
    آن/آن: 1
    حروف صامت: 190
    a:22
    e:30
    من:24
    تقریبا 10
    u:7

    تمرین 3.26

    در پیاده سازی سازنده ها و عملگرهای انتساب ما تکرارهای زیادی وجود دارد. همانطور که در بخش 2.3 انجام دادیم، کد تکراری را به یک تابع عضو خصوصی جداگانه منتقل کنید. مطمئن شوید که گزینه جدید کار می کند.

    تمرین 3.27

    برنامه آزمون را تغییر دهید تا صامت های b، d، f، s، t را نیز بشمارید.

    تمرین 3.28

    یک تابع عضو بنویسید که با استفاده از اعلان زیر تعداد وقوع یک کاراکتر را در یک رشته می‌شمارد:

    Class String ( public: // ... int count(char ch) const; // ... );

    تمرین 3.29

    عملگر الحاق رشته (+) را طوری پیاده کنید که دو رشته را به هم متصل کرده و نتیجه را در یک شی String جدید برگرداند. در اینجا اعلان تابع است:

    Class String ( public: // ... String operator+(const String &rhs) const; // ... );

    آخرین به روز رسانی: 1396/09/17

    هر متغیر یک نوع خاص دارد. و این نوع تعیین می کند که یک متغیر چه مقادیری می تواند داشته باشد، چه عملیاتی را می توان با آن انجام داد و چند بایت در حافظه اشغال خواهد کرد. انواع داده های اساسی زیر در زبان C++ تعریف شده اند:

      bool : نوع بولی. می تواند یکی از دو مقدار true (true) و false (false) را بگیرد. ردپای حافظه برای این نوع دقیقاً تعریف نشده است.

      char : نشان دهنده یک کاراکتر ASCII است. 1 بایت (8 بیت) در حافظه اشغال می کند. می تواند هر مقداری از -128 تا 127 یا 0 تا 255 را ذخیره کند

      علامت امضا شده: نشان دهنده یک کاراکتر است. 1 بایت (8 بیت) در حافظه اشغال می کند. می تواند هر مقداری را بین -128 و 127 ذخیره کند

      char unsigned: نشان دهنده یک کاراکتر است. 1 بایت (8 بیت) در حافظه اشغال می کند. می تواند هر مقداری بین 0 تا 255 را ذخیره کند

      wchar_t : نشان دهنده یک کاراکتر گسترده است. در ویندوز 2 بایت (16 بیت) در حافظه مصرف می کند، در لینوکس 4 بایت (32 بیت). می تواند هر مقدار را از 0 تا 65535 (برای 2 بایت) یا از 0 تا 4294967295 (برای 4 بایت) ذخیره کند.

      char16_t : نشان دهنده یک کاراکتر یونیکد است. 2 بایت (16 بیت) در حافظه اشغال می کند. می تواند هر مقدار از 0 تا 65535 را ذخیره کند

      char32_t : نشان دهنده یک کاراکتر یونیکد است. 4 بایت (32 بیت) در حافظه اشغال می کند. می تواند هر مقداری از 0 تا 4294967295 را ذخیره کند

      short : یک عدد صحیح را در محدوده -32768 تا 32767 نشان می دهد. 2 بایت (16 بیت) در حافظه اشغال می کند.

      این نوع همچنین دارای مترادف های short int , signed short int , signed short است .

      کوتاه بدون علامت : یک عدد صحیح را در محدوده 0 تا 65535 نشان می دهد. 2 بایت (16 بیت) در حافظه اشغال می کند.

      این نوع همچنین دارای مترادفی برای int کوتاه بدون علامت است.

      int : یک عدد صحیح را نشان می دهد. بسته به معماری پردازنده، می تواند 2 بایت (16 بیت) یا 4 بایت (32 بیت) طول بکشد. محدوده مقادیر حدی نیز به ترتیب می تواند از 32768- تا 32767 (برای 2 بایت) یا از -2 147 483 648 تا 2 147 483 647 (برای 4 بایت) متغیر باشد. اما در هر صورت سایز باید بزرگتر یا مساوی سایز نوع کوتاه و کمتر یا مساوی سایز نوع بلند باشد.

      این نوع دارای مترادف های امضا شده و امضا شده است.

      int بدون علامت: یک عدد صحیح مثبت را نشان می دهد. بسته به معماری پردازنده، می تواند 2 بایت (16 بیت) یا 4 بایت (32 بیت) طول بکشد، و به همین دلیل، محدوده مقادیر حدی می تواند متفاوت باشد: از 0 تا 65535 (برای 2 بیت) بایت)، یا از 0 تا 4294967295 (برای 4 بایت).

      بدون علامت می تواند به عنوان مترادف برای این نوع استفاده شود.

      long : یک عدد صحیح را در محدوده -2147483648 تا 2147483647 نشان می دهد. 4 بایت (32 بیت) حافظه را اشغال می کند.

      این نوع همچنین دارای مترادف های long int ، signed long int و signed long است

      طولانی بدون علامت: یک عدد صحیح را در محدوده 0 تا 4294967295 نشان می دهد. 4 بایت (32 بیت) حافظه را اشغال می کند.

      مترادفی برای int طولانی بدون علامت دارد.

      long long : یک عدد صحیح در محدوده -9223372036854775808 تا +9223372036854775807 را نشان می دهد. معمولاً 8 بایت (64 بیت) در حافظه.

      دارای مترادف long long int , signed long long int و signed long long .

      طولانی بدون علامت: یک عدد صحیح را در محدوده 0 تا 18446744073709551615 نشان می دهد. معمولاً 8 بایت (64 بیت) در حافظه است.

      مترادفی برای int طولانی بدون علامت دارد.

      float: یک عدد ممیز شناور با دقت معمولی را در محدوده +/- 3.4E-38 تا 3.4E+38 نشان می دهد. 4 بایت (32 بیت) حافظه را اشغال می کند

      double : یک عدد ممیز شناور با دقت مضاعف را در محدوده +/- 1.7E-308 تا 1.7E+308 نشان می دهد. 8 بایت (64 بیت) حافظه را اشغال می کند

      long double : یک عدد واقعی ممیز شناور با دقت دو برابر حداقل 8 بایت (64 بیت) را نشان می دهد. بسته به اندازه حافظه اشغال شده، محدوده مقادیر معتبر ممکن است متفاوت باشد.

      void : بدون مقدار تایپ کنید

    بنابراین، تمام انواع داده ها به جز void را می توان به سه گروه تقسیم کرد: کاراکتر (char، wchar_t، char16_t، char32_t)، عدد صحیح (short، int، long، long long) و انواع ممیز شناور (float، double، long double).

    انواع شخصیت

    یک برنامه از انواع char، wchar_t، char16_t و char32_t برای نمایش کاراکترها استفاده می کند.

    بیایید چند متغیر تعریف کنیم:

    Charc="d"; wchar_td="c";

    متغیری از نوع char یک کاراکتر در گیومه تکی به عنوان مقدار خود می گیرد: char c = "d" . همچنین می توانید عددی را از محدوده مشخص شده در فهرست بالا اختصاص دهید: char c = 120 . در این حالت، مقدار متغیر c، کاراکتری خواهد بود که در جدول کاراکترهای اسکی دارای کد 120 است.

    شایان ذکر است که برای خروجی کاراکترهای wchar_t به کنسول، نباید از std::cout استفاده کنید، بلکه از std::wcout stream استفاده کنید:

    #عبارتند از int main() (char a = "H"؛ wchar_t b = "e"؛ std::wcout<< a << b << "\n"; return 0; }

    در عین حال، جریان std::wcout می تواند با char و wchar_t کار کند. و جریان std::cout برای متغیر wchar_t کد عددی خود را به جای کاراکتر نمایش می دهد.

    استاندارد C++11 انواع char16_t و char32_t را اضافه کرد که یونیکد گرا هستند. با این حال، در سطح سیستم‌عامل، رشته‌هایی برای کار با این انواع هنوز پیاده‌سازی نشده‌اند. بنابراین، اگر نیاز به نمایش مقادیر متغیرهای این نوع در کنسول دارید، باید متغیرها را به انواع char یا wchar_t تبدیل کنید:

    #عبارتند از int main() (char a = "H"؛ wchar_t b = "e"؛ char16_t c = "l"؛ char32_t d = "o"؛ std::cout<< a << (char)b << (char)c << (char)d << "\n"; return 0; }

    در این حالت، خروجی با cast به char - (char) قبل از متغیرها قرار می گیرد، به طوری که مقادیر متغیرهای b، c و d به char تبدیل می شوند و می توان با استفاده از std در کنسول چاپ کرد: جریان cout.

    انواع عدد صحیح

    انواع اعداد صحیح با انواع زیر نشان داده می شوند: کوتاه، کوتاه بدون علامت، int، بدون علامت int، طولانی، بدون علامت طولانی، طولانی طولانی، و بدون علامت طولانی طولانی:

    کوتاه a = -10; کوتاه بدون علامت b= 10; int c = -30; بدون علامت int d = 60; طولانی e = -170; طولانی بدون علامت f = 45; بلند بلند g = 89;

    انواع نقطه شناور

    انواع ممیز شناور و/یا کسری با انواعی مانند float، double و long double نشان داده می شوند:

    شناور a = -10.45; دو برابر b = 0.00105; طولانی دو برابر c = 30.890045;

    اندازه های نوع داده

    در لیست بالا، برای هر نوع، اندازه ای که در حافظه اشغال می کند مشخص شده است. با این حال، شایان ذکر است که توسعه دهندگان کامپایلر می توانند محدودیت های اندازه برای انواع را به تنهایی بر اساس قابلیت های سخت افزاری رایانه انتخاب کنند. استاندارد فقط حداقل مقادیری را که باید باشد را تعیین می کند. به عنوان مثال، برای انواع int و short حداقل مقدار 16 بیت، برای نوع طولانی 32 بیت، برای نوع long double است. در این صورت اندازه نوع بلند نباید از اندازه نوع int کمتر باشد و اندازه نوع int نباید از اندازه نوع کوتاه و اندازه نوع بلند دوبل باید کمتر باشد. بزرگتر از دو برابر باشد به عنوان مثال، کامپایلر g++ در ویندوز از 12 بایت برای دوبل طولانی استفاده می کند، در حالی که کامپایلر تعبیه شده در ویژوال استودیو و همچنین اجرا می شود، از 8 بایت برای دوبل طولانی استفاده می کند. یعنی حتی در یک پلتفرم، کامپایلرهای مختلف می توانند اندازه برخی از انواع داده ها را به روش های مختلف نزدیک کنند. اما به طور کلی، از اندازه هایی استفاده می شود که در بالا هنگام توصیف انواع داده ها نشان داده شده است.

    با این حال، شرایطی وجود دارد که شما باید دقیقاً اندازه یک نوع خاص را بدانید. و برای این کار، C++ دارای عملگر sizeof() است که اندازه حافظه را بر حسب بایت برمی‌گرداند که یک متغیر اشغال می‌کند:

    #عبارتند از int main() (عدد دوبل طولانی = 2؛ std::cout<< "sizeof(number) =" << sizeof(number); return 0; }

    خروجی کنسول هنگام کامپایل به g++:

    sizeof(تعداد) = 12

    در عین حال، هنگام تعریف متغیرها، درک این نکته مهم است که ارزش یک متغیر نباید فراتر از محدودیت هایی باشد که برای نوع آن مشخص شده است. مثلا:

    شماره کوتاه بدون امضا = -65535;

    کامپایلر G++ هنگام کامپایل کردن برنامه با این خط، با این خطا که مقدار -65535 در محدوده مقادیر معتبر برای نوع کوتاه بدون علامت نیست و کوتاه می شود، می دهد.

    در ویژوال استودیو، کامپایل ممکن است بدون خطا بگذرد، اما متغیر عدد روی 2 تنظیم می شود که نتیجه تبدیل -65535 به یک کوتاه بدون علامت است. یعنی باز هم نتیجه دقیقاً آن چیزی که انتظار می رود نخواهد بود. مقدار یک متغیر فقط مجموعه ای از بیت ها در حافظه است که بر اساس نوع خاصی تفسیر می شوند. و برای انواع مختلف، مجموعه بیت های یکسانی را می توان متفاوت تفسیر کرد. بنابراین، در نظر گرفتن محدوده ای از مقادیر برای یک نوع معین هنگام تخصیص یک مقدار به یک متغیر مهم است.

    مشخص کننده خودکار

    گاهی اوقات تعیین نوع یک عبارت دشوار است. و طبق آخرین استانداردها، می توانید به کامپایلر اجازه دهید نوع شی را استنباط کند. و این همان چیزی است که مشخص کننده خودکار برای آن استفاده می شود. علاوه بر این، اگر متغیری را با مشخص کننده خودکار تعریف کنیم، این متغیر باید با مقداری مقداردهی اولیه شود:

    شماره خودکار = 5;

    بر اساس مقدار اختصاص داده شده، کامپایلر نوع متغیر را استنباط می کند. متغیرهای بدون مقدار اولیه با مشخص کننده خودکار مجاز نیستند.

    این برگه تقلب اطلاعاتی در مورد انواع داده های اصلی زبان برنامه نویسی C++ و ویژگی های اجرای آنها ارائه می دهد. همچنین در انتهای رکورد جدولی با محدوده مقادیر این نوع وجود دارد.

    مفهوم نوع داده

    هدف اصلی هر برنامه پردازش داده است. انواع مختلف داده ها به طور متفاوتی ذخیره و پردازش می شوند. در هر زبان الگوریتمی، هر نتیجه ارزیابی ثابت، متغیر، عبارت یا تابع باید نوع خاصی داشته باشد.

    نوع داده تعریف می کند:

    • نمایش داخلی داده ها در حافظه کامپیوتر؛
    • مجموعه مقادیری که مقادیر از این نوع می توانند بگیرند؛
    • عملیات و توابعی که می توان برای مقادیر این نوع اعمال کرد.

    بر اساس این ویژگی ها، برنامه نویس نوع هر مقدار مورد استفاده در برنامه را برای نمایش اشیاء واقعی انتخاب می کند. یک اعلان نوع اجباری به کامپایلر اجازه می دهد تا اعتبار ساختارهای مختلف برنامه را بررسی کند. نوع مقدار، دستورالعمل‌های ماشینی را تعیین می‌کند که برای پردازش داده‌ها استفاده می‌شود.

    همه انواع زبان C++ را می توان به دو دسته تقسیم کرد اصلی و کامپوزیت . C++ شش را تعریف می کند عمدهانواع داده برای نمایش مقادیر صحیح، واقعی، نمادین و منطقی. بر اساس این انواع، برنامه نویس می تواند توضیحاتی را وارد کند تشکیل دهندهانواع اینها شامل آرایه‌ها، شماره‌ها، توابع، ساختارها، مراجع، اشاره‌گرها، اتحادیه‌ها و کلاس‌ها هستند.

    انواع داده های پایه در C++

    انواع داده های پایه (استاندارد) اغلب حسابی نامیده می شوند زیرا می توان از آنها در عملیات حسابی استفاده کرد. موارد زیر برای توصیف انواع اصلی تعریف شده است:

    1. int (عدد صحیح);
    2. کاراکتر (شخصیت)؛
    3. wchar_t (کاراکتر گسترده)؛
    4. bool (بولی)؛
    5. شناور (واقعی)؛
    6. دوبل (واقعی با دقت مضاعف).

    چهار عدد اول را عدد صحیح می نامند ( کل ) دو مورد آخر انواع ممیز شناور . کدی که کامپایلر برای مدیریت مقادیر صحیح تولید می کند با کد مقادیر ممیز شناور متفاوت است.

    چهار تا هست مشخص کننده نوع با مشخص کردن نمایش داخلی و محدوده مقادیر انواع استاندارد:

    • کوتاه (کوتاه)؛
    • طولانی (طولانی)؛
    • امضا شده (نشانه)؛
    • بدون امضا (بدون امضا).

    نوع عدد صحیح (int)

    اندازه نوع int توسط استاندارد تعریف نشده است، اما به کامپیوتر و کامپایلر بستگی دارد. برای یک پردازنده 16 بیتی، 2 بایت برای مقادیر از این نوع، برای یک پردازنده 32 بیتی - 4 بایت اختصاص داده می شود.

    مشخص کننده کوتاه در جلوی نام نوع به کامپایلر می گوید که بدون توجه به ظرفیت پردازنده، 2 بایت برای تعداد مورد نیاز است. مشخص کننده طولانی به این معنی است که مقدار عدد صحیح 4 بایت خواهد بود. بنابراین در یک کامپیوتر 16 بیتی معادل های int و short int و در کامپیوتر 32 بیتی int و long int هستند.

    نمایندگی داخلی مقادیر از نوع عدد صحیح - یک عدد صحیح در کد باینری. هنگام استفاده از مشخص کننده علامت، مهم ترین بیت عدد به عنوان علامت (0 - عدد مثبت، 1 - منفی) تفسیر می شود. مشخص‌کننده بدون علامت اجازه می‌دهد فقط اعداد مثبت نمایش داده شوند، زیرا مهم‌ترین رقم بخشی از کد عدد در نظر گرفته می‌شود. بنابراین، محدوده مقادیر نوع int به مشخص کننده ها بستگی دارد. محدوده‌های مقادیر مقادیر نوع عدد صحیح با مشخص‌کننده‌های مختلف برای رایانه‌های سازگار با IBM PC در جدول «محدوده‌های ارزش انواع داده‌های ساده» در انتهای ورودی آورده شده است.

    به‌طور پیش‌فرض، همه انواع اعداد صحیح علامت‌دار در نظر گرفته می‌شوند، به این معنی که مشخص‌کننده علامت‌دار را می‌توان حذف کرد.

    ثابت هایی که در برنامه با آنها مواجه می شوند، مطابق با نوع آنها، یک نوع یا نوع دیگری را اختصاص می دهند. اگر این نوع به دلایلی برای برنامه نویس مناسب نیست، می تواند با استفاده از پسوندهای L، l (طولانی) و U، u (بدون علامت) نوع مورد نیاز را به صراحت مشخص کند. به عنوان مثال، ثابت 32L از نوع long و 4 بایت خواهد بود. می توانید از پسوندهای L و U به طور همزمان استفاده کنید، به عنوان مثال، 0x22UL یا 05Lu.

    توجه داشته باشید

    انواع short int، long int، signed int و unsigned int را می توان به ترتیب به short، long، signed و unsigned کوتاه کرد.

    نوع کاراکتر (کاراکتر)

    مقدار یک نوع کاراکتر، تعداد بایت‌هایی است که برای قرار دادن هر کاراکتری از مجموعه کاراکتر برای یک رایانه خاص کافی است، که منجر به نام نوع آن می‌شود. به طور معمول، این 1 بایت است. نوع کاراکتر مانند سایر انواع عدد صحیح می تواند دارای علامت یا بدون علامت باشد. مقادیر امضا شده می توانند مقادیری از -128 تا 127 را ذخیره کنند. هنگام استفاده از مشخص کننده بدون علامت، مقادیر می توانند از 0 تا 255 متغیر باشند. این برای ذخیره هر کاراکتری در مجموعه کاراکترهای ASCII 256 کاراکتری کافی است. مقادیر نوع char نیز برای ذخیره اعداد صحیحی که از مرزهای محدوده های مشخص شده تجاوز نمی کنند استفاده می شود.

    نوع کاراکتر توسعه یافته (wchar_t)

    نوع wchar_t برای کار با مجموعه ای از کاراکترها طراحی شده است که 1 بایت برای کدگذاری کافی نیست، مانند Unicode. اندازه این نوع به پیاده سازی بستگی دارد. به عنوان یک قاعده، با نوع کوتاه مطابقت دارد. ثابت های رشته ای از نوع wchar_t با پیشوند L نوشته می شوند، به عنوان مثال، L"Gates".

    نوع بولی (bool)

    مقادیر Boolean فقط می توانند مقادیر true و false را که کلمات رزرو شده هستند، بگیرند. نمایش داخلی false 0 (صفر) است. هر مقدار دیگری به عنوان درست تفسیر می شود. هنگامی که به یک نوع عدد صحیح تبدیل می شود، true مقدار 1 را دارد.

    انواع ممیز شناور (شناور، دوبل و بلند دوبل)

    استاندارد C++ سه نوع داده را برای ذخیره مقادیر واقعی تعریف می کند: float، double و long double.

    انواع داده های ممیز شناور در حافظه کامپیوتر متفاوت از انواع داده های صحیح ذخیره می شوند. نمایش داخلی یک عدد واقعی از دو بخش تشکیل شده است - مانتیس و توان. در رایانه های سازگار با IBM PC، مقادیر شناور 4 بایت را اشغال می کنند که یک بیت برای علامت مانتیس، 8 بیت برای توان و 23 بیت برای مانتیس اختصاص داده می شود. مانتیس عددی بزرگتر از 1.0 اما کمتر از 2.0 است. از آنجایی که بالاترین رقم مانتیس همیشه 1 است، ذخیره نمی شود.

    برای مقادیر نوع double با اشغال 8 بایت، 11 و 52 رقم به ترتیب برای دستور و مانتیس در نظر گرفته شده است. طول آخوندک دقت عدد را تعیین می کند، در حالی که طول نما محدوده آن را تعیین می کند.همانطور که در جدول انتهای ورودی مشاهده می کنید، با تعداد یکسانی از بایت های اختصاص داده شده برای مقادیر float و long int، محدوده مقادیر معتبر آنها بسیار متفاوت است. به دلیل فرم نمایندگی داخلی.

    مشخص کننده طولانی قبل از نام نوع دوگانه نشان می دهد که 10 بایت به مقدار آن اختصاص داده شده است.

    ثابت های ممیز شناور به طور پیش فرض از نوع double هستند. با استفاده از پسوندهای F, f (شناور) و L, l (طولانی) می توانید به صراحت نوع یک ثابت را مشخص کنید. برای مثال، ثابت 2E+6L از نوع long double و ثابت 1.82f از نوع float خواهد بود.

    برای نوشتن برنامه های چند پلتفرمی، نمی توانید در مورد اندازه نوع int فرضیاتی داشته باشید. برای دریافت آن باید از عملگر sizeof استفاده کنید که اندازه نوع را بر حسب بایت برمی گرداند. به عنوان مثال، در یک سیستم عامل MS-DOS، sizeof(int) به 2 و در ویندوز 98 یا OS/2، نتیجه 4 خواهد بود.

    استاندارد ANSI محدوده مقادیر را برای انواع اصلی مشخص نمی کند، فقط نسبت بین اندازه آنها تعریف شده است، به عنوان مثال:

    sizeof(float) ≤ slzeof(دوبل) ≤ sizeof(طولان دوبل)
    sizeof(char) ≤ slzeof(کوتاه) ≤ sizeof(int) ≤ sizeof(طولانی)

    توجه داشته باشید

    حداقل و حداکثر مقادیر مجاز برای انواع عدد صحیح وابسته به پیاده سازی است و در فایل هدر آورده شده است ()، ویژگی های انواع واقعی - در فایل () و همچنین در قالب کلاس numeric_limits

    نوع خالی

    علاوه بر موارد ذکر شده، نوع void یکی از انواع اصلی زبان است، اما مجموعه مقادیر این نوع خالی است. برای تعریف توابعی که مقداری را برنمی‌گردانند، برای تعیین یک لیست خالی از آرگومان‌های تابع، به‌عنوان نوع پایه برای اشاره‌گرها و در عملیات ریخته‌گری استفاده می‌شود.

    محدوده ارزش انواع داده های ساده در C++ برای رایانه های سازگار با IBM PC

    س: عبارت کامپیوتر سازگار با IBM PC به چه معناست؟
    A: رایانه سازگار با IBM PC (eng. IBM PC compatible) - رایانه ای که از نظر معماری به رایانه های شخصی IBM، XT و AT نزدیک است. کامپیوترهای سازگار با IBM PC مبتنی بر ریزپردازنده‌های سازگار با Intel 8086 هستند (و همانطور که می‌دانید، تمام پردازنده‌های جدید اینتل کاملاً با 8086 سازگار هستند). در واقع، این تقریباً تمام رایانه های مدرن است.

    انواع مختلفی از انواع اعداد صحیح و واقعی، که در محدوده و دقت نمایش داده‌ها متفاوت هستند، معرفی شده‌اند تا برنامه‌نویس را قادر می‌سازد تا از قابلیت‌های یک سخت‌افزار خاص به بهترین شکل استفاده کند، زیرا سرعت محاسبات و مقدار حافظه وجود دارد. به انتخاب نوع بستگی دارد اما یک برنامه بهینه‌سازی شده برای یک نوع کامپیوتر ممکن است برای پلتفرم‌های دیگر غیرقابل حمل شود، بنابراین به طور کلی، از وابستگی به ویژگی‌های خاص انواع داده‌ها باید اجتناب شود.

    تایپ کنید محدوده ارزش اندازه (بایت)
    بوول درست و نادرست 1
    کاراکتر امضا شده -128 … 127 1
    کاراکتر بدون امضا 0 … 255 1
    امضا کوتاه بین المللی -32 768 … 32 767 2
    بدون امضا کوتاه 0 … 65 535 2
    امضای طولانی مدت -2 147 483 648 … 2 147 483 647 4
    بدون امضا طولانی int 0 … 4 294 967 295 4
    شناور 3.4e-38 … 3.4e+38 4
    دو برابر 1.7e-308 … 1.7C+308 8
    دوبل بلند 3.4e-4932 … 3.4e+4932 10

    برای انواع واقعی، جدول مقادیر مطلق مقادیر حداقل و حداکثر را نشان می دهد.

    برچسب ها: انواع داده C++، خودکار، decltype، استنتاج نوع خودکار

    انواع داده ها

    درست مانند C، متغیرها در C++ باید یک نام معتبر داشته باشند. یعنی باید متشکل از اعداد، حروف و خط زیر باشد، نباید با عدد شروع شود و با کلمات خدماتی همخوانی نداشته باشد، که اکنون بیشتر شده است.

    تراز alignof و and_eq
    asm خودکار بیتند بیتور
    بوول زنگ تفريح مورد گرفتن
    کاراکتر char16_t char32_t کلاس
    تکمیلپایانconstexprconst_cast
    ادامه هیدdecltypeپیش فرضحذف
    انجام دادن دو برابر dynamic_cast دیگر
    enumصریحصادراتخارجی
    نادرستشناوربرایدوست
    رفتن بهاگرخطیبین المللی
    طولانیقابل تغییرفضای نامجدید
    نه جز نه not_eq nullptr
    اپراتوریاor_eqخصوصی
    حفاظت شدهعمومیثبت نامreinterpret_cast
    برگشتکوتاهامضاء شدهاندازه
    ایستاstatic_adsertstatic_castساخت
    تعویضقالباینthread_local
    پرت كردندرست است، واقعیتلاش كردنtypedef
    تایپ شدهاسم را تایپ کناتحاد. اتصالبدون امضا
    استفاده كردنمجازیخالیفرار
    wchar_tدر حالی کهxorxor_eq

    مانند C، C++ یک زبان حساس به حروف کوچک و بزرگ است.

    انواع داده های پایه

    انواع داده های پایه در C++ را می توان به چند گروه تقسیم کرد

    نوع علامت. برای ذخیره یک کاراکتر می توان از متغیرهای نوع علامت دار استفاده کرد. ساده ترین نوع آن char است که اندازه آن 1 بایت است. همچنین انواعی برای نمایش کاراکترهای بزرگتر از یک بایت وجود دارد.

    در واقع، این انواع نیز در C هستند، ما در مطالعه نمایش رشته ها به تفصیل نمی پردازیم.

    انواع داده های عدد صحیح همانطور که در C، آنها می توانند اصلاح کننده های امضا شده و بدون علامت داشته باشند. همانطور که در C، انواع اصلی عبارتند از char، int، long و long long. هیچ چیز جدیدی در اینجا ظاهر نشده است.

    اعداد اعشاری. نشان دهنده انواع شناور، دوبل و دوبل طولانی است. در مقایسه با si چیز جدیدی نیست.

    تمام انواعی که در بالا توضیح داده شد، حسابی نیز نامیده می شوند. علاوه بر آنها، یک نوع خالی نیز وجود دارد - void (همچنین در مقایسه با C چیز جدیدی نیست) و یک اشاره گر تهی. اکنون به جای NULL با ویژگی های شگفت انگیزش، یک نوع بنیادی جدید nullptr_t با یک مقدار nullptr وجود دارد که یک اشاره گر تهی را ذخیره می کند و فقط با خودش برابر است. در این حالت می توان آن را به یک اشاره گر تهی از نوع دلخواه فرستاد.

    C++ نوع بولی را معرفی کرد. فقط دو مقدار ممکن true و false را ذخیره می کند.

    C++ همچنین از بسیاری از انواع داده های ترکیبی پشتیبانی می کند که بعداً مورد بحث قرار خواهد گرفت.

    اعلان و راه اندازی متغیرها

    در C++، متغیرها را می‌توان در هر جایی از یک تابع اعلان کرد، نه فقط در ابتدای بلوک کد، متغیرها را می‌توان در داخل یک حلقه for نیز اعلان کرد.

    شناور a; شناور b; floatsum; پله شناور؛ a = 3.0f; b = 4.3f; مجموع = 0.0f; گام = 0.05f. برای (float i = a; i< b; i += step) { sum += i * i; } float mid = sum / (b - a) / step;

    متغیرها را می توان در هنگام ایجاد مقداردهی اولیه کرد، مانند C

    int x = 0;

    یا با استفاده از سازنده

    intx(0); دو برابر d (3.2);

    علاوه بر این، در C ++ 2011، به اصطلاح. مقداردهی اولیه یکنواخت، که به شما امکان می دهد از یک نحو برای مقداردهی اولیه هر شی استفاده کنید

    StructPoint (int x; int y; ); struct Point position = ( 3, 4 ); Point *pt = new Point(6, 8); int length(5);

    استنتاج تایپ کنید

    در C++ 2011 از کلمه کلیدی auto برای تعیین خودکار نوع متغیرها استفاده می شود. اغلب می توان نوع متغیر را از سمت راست مقداردهی اولیه تعیین کرد. در صورتی که کامپایلر بتواند به طور واضح نوع را تعیین کند، می توان آن را با استفاده از کلمه کلیدی خودکار مشخص کرد:

    خودکار x = 3; //معادل int x = 3; نقطه خودکار = نقطه جدید; //معادل Point *point = new Point

    علاوه بر این، با استفاده از کلمه سرویس decltype می توان نوع متغیر را مطابق با نوع موجود تنظیم کرد.

    int intX = 42; decltype(intX) intY = 33; //معادل int intY = 33; خودکار pt1 = newPoint; decltype(pt1) p2 = new Point(2, 6); //معادل //Point *pt1 = new Point; //Point *pt2 = new Point(2, 6)

    رشته های

    هیچ رشته ای از نوع پایه در C++ وجود ندارد. با این حال، یک کتابخانه رشته استاندارد وجود دارد که یک کلاس برای کار با رشته ها فراهم می کند.

    #عبارتند از #عبارتند از void main() ( std::string first_name = "Vasya"; std::string last_name = ( "Pupkin"); //رشته الحاق auto full_name = first_name + " " + last_name; std::string *department = std جدید ::string("بخش کپی و اسکن")؛ std::cout<< full_name << std::endl; //сравнение строк std::string a = "A"; std::string b = "B"; if (first_name.compare(last_name) >0) ( std::cout<< a + " >" + ب<< std::endl; } else { std::cout << a + " < " + b << std::endl; } //подстрока std::string subs = department->substr(0, 10); std::out<< subs << std::endl; //замена подстроки std::cout << last_name.replace(0, 1, "G") << std::endl; //вставка std::string new_department = department->insert(department->length()، " and shreddering"); std::out<< new_department << std::endl; delete department; system("pause"); }

    در ادامه با جزئیات بیشتر با کتابخانه رشته استاندارد آشنا خواهیم شد.

    در زبان C، مفاهیم "نوع داده" و "تغییر کننده نوع" متمایز می شوند. نوع داده عدد صحیح و اصلاح کننده دارای امضا یا بدون علامت است. یک عدد صحیح امضا شده دارای مقادیر مثبت و منفی خواهد بود، در حالی که یک عدد صحیح بدون علامت فقط مقادیر مثبت خواهد داشت. پنج نوع اساسی در زبان C وجود دارد.

    • کاراکتر - شخصیت.
    • یک متغیر نوع char دارای اندازه 1 بایت است، مقادیر آن کاراکترهای مختلفی از جدول کد است، به عنوان مثال: 'f'، ':'، 'j' (هنگامی که در برنامه نوشته می شود، آنها در یک نقل قول قرار می گیرند. ).

    • int یک عدد صحیح است.
    • اندازه یک متغیر از نوع int در استاندارد C تعریف نشده است. در اکثر سیستم های برنامه نویسی، اندازه یک متغیر از نوع int با اندازه کل کلمه ماشین مطابقت دارد. به عنوان مثال، در کامپایلرهای پردازنده های 16 بیتی، یک متغیر int اندازه 2 بایت دارد. در این حالت، مقادیر علامت گذاری شده این متغیر می تواند از -32768 تا 32767 باشد.

    • شناور واقعی است
    • کلمه کلیدی float به شما اجازه می دهد تا متغیرهای ممیز شناور را تعریف کنید. مقادیر آنها دارای یک قسمت کسری است که با یک نقطه از هم جدا شده است، به عنوان مثال: -5.6، 31.28 و غیره. اعداد حقیقی را می توان به صورت ممیز شناور نیز نوشت، به عنوان مثال: -1.09e+4. عدد قبل از نماد "e" را آخوندک و بعد از "e" - ترتیب نامیده می شود. یک متغیر float 32 بیت در حافظه اشغال می کند. می تواند مقادیری در محدوده 3.4e-38 تا 3.4e+38 داشته باشد.

    • دقت دو برابر واقعی؛
    • کلمه کلیدی double به شما امکان می دهد یک متغیر واقعی با دقت دوگانه تعریف کنید. دو برابر بیشتر از یک متغیر شناور فضای حافظه اشغال می کند. یک متغیر از نوع double می تواند مقادیری در محدوده 1.7e-308 تا 1.7e+308 داشته باشد.

    • باطل - ارزشی ندارد.
    • کلمه کلیدی void برای خنثی کردن مقدار یک شی استفاده می شود، مانند اعلام تابعی که هیچ مقداری را بر نمی گرداند.

    انواع متغیر:

    برنامه ها بر روی داده های مختلفی کار می کنند که می توانند ساده و ساختارمند باشند. داده های ساده عبارتند از اعداد صحیح و واقعی، نمادها و اشاره گرها (آدرس اشیاء در حافظه). اعداد صحیح ندارند و اعداد حقیقی یک قسمت کسری دارند. داده های ساخت یافته آرایه ها و ساختارها هستند. آنها در زیر مورد بحث قرار خواهند گرفت.

    متغیر مکانی در حافظه کامپیوتر است که یک نام دارد و مقداری را ذخیره می کند. مقدار یک متغیر می تواند در طول اجرای برنامه تغییر کند. هنگامی که یک مقدار جدید در یک سلول نوشته می شود، مقدار قدیمی پاک می شود.

    نامگذاری معنادار متغیرها سبک خوبی است. نام متغیر می تواند از یک تا 32 کاراکتر باشد. می توانید از حروف کوچک و بزرگ، اعداد و کاراکتر زیرخط که یک حرف در C در نظر گرفته می شود استفاده کنید. کاراکتر اول باید یک حرف باشد. نام متغیر نمی تواند با کلمات رزرو شده یکسان باشد.

    char تایپ کنید

    char به صرفه ترین نوع است. نوع کاراکتر می تواند امضا شده یا بدون علامت باشد. به عنوان "char Signed" (نوع امضا شده) و "Signed char" (نوع بدون علامت) مشخص می شود. یک نوع علامت دار می تواند مقادیر را در محدوده -128 تا +127 ذخیره کند. بدون علامت - از 0 تا 255. 1 بایت حافظه (8 بیت) برای متغیر نوع char اختصاص داده شده است.

    کلیدواژه های علامت دار و بدون علامت نشان می دهند که بیت صفر متغیر اعلام شده چگونه تفسیر می شود، یعنی اگر کلمه کلیدی بدون علامت مشخص شده باشد، بیت صفر به عنوان بخشی از عدد تفسیر می شود، در غیر این صورت بیت صفر به عنوان یک علامت تفسیر می شود.

    int را تایپ کنید

    مقدار صحیح int می تواند کوتاه (کوتاه) یا طولانی (طولانی) باشد. کلمه کلیدی کوتاه بعد از کلیدواژه های امضا شده یا بدون امضا قرار می گیرد. بنابراین، انواعی وجود دارد: امضای کوتاه بین‌المللی، بدون علامت کوتاه بین‌المللی، امضای طولانی بین‌المللی، بدون علامت بلند int.

    متغیری از نوع signed short int (signed short integer) می‌تواند مقادیری از -32768 تا +32767 داشته باشد، int short unsigned (عدد صحیح کوتاه بدون علامت) - از 0 تا 65535. دقیقاً دو بایت حافظه (16 بیت) برای آن اختصاص داده شده است. هر یک از آنها.

    هنگام اعلام یک متغیر از نوع signed short int، کلمات کلیدی signed و short را می توان حذف کرد و چنین نوع متغیری را می توان به سادگی int اعلام کرد. همچنین می توان این نوع را با یک کلمه کلیدی کوتاه اعلام کرد.

    یک متغیر short int بدون علامت را می توان به عنوان int بدون علامت یا کوتاه بدون علامت اعلام کرد.

    هر int طولانی امضا شده یا طولانی int بدون علامت دارای 4 بایت حافظه (32 بیت) است. مقادیر متغیرهای این نوع می تواند به ترتیب از 2147483648- تا 2147483647 و از 0 تا 4294967295 متغیر باشد.

    همچنین متغیرهایی از نوع long long int وجود دارد که 8 بایت حافظه (64 بیت) به آنها اختصاص داده شده است. آنها می توانند امضا یا بدون امضا باشند. برای نوع علامت دار، محدوده مقادیر از -9223372036854775808 تا 9223372036854775807، برای نوع بدون علامت، از 0 تا 18446744073709551615 است. یک کلمه علامت‌دار با دو کلید به سادگی می‌تواند طولانی باشد.

    تایپ کنید دامنه محدوده هگزادسیمال اندازه
    کاراکتر بدون امضا 0 … 255 0x00 ... 0xFF 8 بیت
    کاراکتر امضا شده
    یا به سادگی
    کاراکتر
    -128 … 127 -0x80...0x7F 8 بیت
    بدون امضا کوتاه
    یا به سادگی
    بدون امضایا کوتاه بدون امضا
    0 … 65535 0x0000 ... 0xFFFF 16 بیت
    امضا کوتاه بین المللییا امضا شده
    یا به سادگی
    کوتاهیا بین المللی
    -32768 … 32767 0x8000…0x7FFF 16 بیت
    بدون امضا طولانی int
    یا به سادگی
    طولانی بدون امضا
    0 … 4294967295 0x00000000 ... 0xFFFFFFFF 32 بیت
    طولانی امضا کرد
    یا به سادگی
    طولانی
    -2147483648 … 2147483647 0x80000000 ... 0x7FFFFFFF 32 بیت
    طولانی بدون امضا 0 … 18446744073709551615 0x0000000000000000 … 0xFFFFFFFFFFFFFFFFFF 64 بیت
    امضای طولانی مدت
    یا به سادگی
    طولانی طولانی
    -9223372036854775808 … 9223372036854775807 0x80000000000000000 ... 0x7FFFFFFFFFFFFFFF 64 بیت

    اعلان متغیرها

    متغیرها در یک بیانیه اعلام می شوند. یک بیانیه شامل مشخصات نوع و لیستی از نام متغیرها با کاما است. در انتها باید یک نقطه ویرگول وجود داشته باشد.

    [modifiers] typespecifer id [, id] ...

    اصلاح کننده ها کلمات کلیدی امضا شده، بدون امضا، کوتاه، طولانی هستند.
    یک نوع مشخص کننده یک کلمه کلیدی char یا int است که نوع متغیری را که اعلام می شود مشخص می کند.
    شناسه نام متغیر است.

    Charx; int a, b, c; بدون امضا long long y;

    هنگام اعلام یک متغیر، می توانید آن را مقداردهی اولیه کنید، یعنی یک مقدار اولیه به آن اختصاص دهید.

    int x = 100;

    عدد 100 بلافاصله پس از اعلان بر روی متغیر x نوشته می شود، بهتر است متغیرهای مقدار دهی اولیه را در خطوط جداگانه اعلام کنید.