نحوه راه اندازی گوشی های هوشمند و رایانه های شخصی پرتال اطلاعاتی

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

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

  • حروف لاتین بزرگ، کوچک A، B، C…، x، y، z و زیرخط.
  • اعداد عربی از 0 تا 9;
  • کاراکترهای خاص: ()، | , () + - /% *. \":؟< > = ! & # ~ ; ^
  • فاصله، برگه و کاراکترهای خط جدید.

در تست برنامه می توانید استفاده کنید نظرات... اگر متنی با دو اسلش جلو // و با یک کاراکتر خط جدید به پایان می رسد یا بین / * و * / محصور می شود، کامپایلر آن را نادیده می گیرد.

داده ها در C ++

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

برای تشکیل انواع دیگر داده ها، پایه و به اصطلاح مشخص کننده ها C ++ چهار مشخص کننده نوع داده را تعریف می کند:

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

نوع عدد صحیح

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

یک نوع دامنه اندازه
بین المللی -2147483648…2147483647 4 بایت
بدون امضا 0…4294967295 4 بایت
امضا شده -2147483648…2147483647 4 بایت
داخلی کوتاه -32768…32767 2 بایت
طولانی مدت -2147483648…2147483647 4 بایت
بدون امضا کوتاه 0…65535 2 بایت

نوع واقعی

یک عدد ممیز شناور به شکل mE + - p نشان داده می شود، جایی که m مانتیس است (عدد صحیح یا کسری با نقطه اعشار)، p مرتبه (عدد صحیح) است. معمولا ارزش هایی مانند شناوراشغال 4 بایت، و دو برابر 8 بایت جدول محدوده واقعی:

شناور 3.4E-38 ... 3.4E + 38 4 بایت
دو برابر 1.7E-308 ... 1.7E + 308 8 بایت
دوبل بلند 3.4E-4932 ... 3.4E + 4932 8 بایت

نوع بولی

متغیر از نوع بوولفقط دو مقدار می تواند بگیرد درست است، واقعی (درست است، واقعی ) یا کاذب (دروغ گویی ). هر مقداری غیر از صفر به این صورت تعبیر می شود درست است، واقعی.معنی نادرستدر حافظه به صورت 0 نمایش داده می شود.

نوع خالی

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

تبدیل نوع داده

C ++ بین دو نوع تبدیل نوع داده تمایز قائل می شود: صریح و ضمنی.

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

#include "stdafx.h" #include با استفاده از namespace std. int main () (int i = 5؛ float f = 10.12؛ cout<> void ")؛ بازگشت 0؛)

#include "stdafx.h"

#عبارتند از

با استفاده از namespace std.

int main ()

int i = 5; float f = 10.12;

کوت<< i / f ;

سیستم ("مکث >> void")؛

بازگشت 0;

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

  • تبدیل صریحبرخلاف آنچه به طور ضمنی توسط برنامه نویس انجام می شود. چندین راه برای انجامش وجود دارد:
  1. تبدیل به سبک سی: (شناور) الف
  2. تبدیل به سبک C ++: شناور ()

همچنین، تبدیل نوع را می توان با استفاده از عملیات زیر انجام داد:

static_cast<>() const_cast<>() reinterpret_cast<>() dynamic_cast<> ()

static_cast<> ()

const_cast<> ()

reinterpret_cast<> ()

dynamic_cast<> ()

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

نوع Static_cast<Тип>(یک شی)؛

با استفاده از static_cast، نمی‌توانید constness را از یک متغیر حذف کنید، اما این در توان عبارت زیر است. const_cast- فقط زمانی استفاده می شود که لازم باشد ثابت ها را از جسم حذف کنیم. نحو به این صورت خواهد بود:

یک نوعconst_cast< یک نوع> (یک شی);

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

یک نوعتفسیر مجدد_قالب< یک نوع> (یک شی);

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

یک نوعپویا _قالب< یک نوع> (یک شی);

شخصیت ها را کنترل کنید

شما قبلاً با برخی از این "کاراکترهای کنترل" آشنا هستید (مثلاً با \ n). همه آنها با یک بک اسلش شروع می شوند و همچنین توسط نقل قول های دوگانه احاطه شده اند.

تصویر

کد هگزادسیمال

نام

صدای بیپر

عقبگرد

ترجمه صفحه (فرمت)

ترجمه خطی

برگشت محموله

زبانه افقی

زبانه عمودی

تفاوت مهم بین زبان C و سایر زبان ها (PL1، FORTRAN و غیره) عدم وجود یک اصل پیش فرض است که منجر به نیاز به اعلام صریح همه متغیرهای مورد استفاده در برنامه همراه با نشان دادن انواع مربوط به آنها می شود. .

اعلان های متغیر دارای فرمت زیر هستند:

[memory-class-specifier] type-specifier specifier [= Initiator] [, specifier [= Initiator]] ...

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

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

Initiator - یک مقدار اولیه یا لیستی از مقادیر اولیه را مشخص می کند که (که) در زمان اعلام به یک متغیر اختصاص داده می شود.

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

1.2.1 دسته بندی نوع داده

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

انواع عدد صحیح: انواع شناور: char float int دوبل کوتاه بلند دوبل علامت بلند بدون علامت

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

Const double A = 2.128E-2; const B = 286; (به معنی Const int B = 286 است)

نمونه هایی از اعلان داده های ترکیبی در زیر مورد بحث قرار خواهند گرفت.

1.2.2. نوع داده عدد صحیح

برای تعریف داده ها از نوع عدد صحیح، از کلمات کلیدی مختلفی استفاده می شود که محدوده مقادیر و اندازه ناحیه حافظه اختصاص داده شده برای متغیرها را تعیین می کند (جدول 6).

جدول 6

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

بدون علامت int n; بدون علامت int b; int c; (با امضای c دلالت دارد). بدون امضا d; (به معنی int بدون علامت d) است. امضا شده f; (به طور ضمنی با امضای f).

توجه داشته باشید که اصلاح کننده char برای نشان دادن یک کاراکتر (از آرایه ای از نمایش کاراکترها) یا برای اعلان حرف های رشته ای استفاده می شود. مقدار یک شی از نوع char یک کد (به اندازه 1 بایت) است که مطابق با کاراکتری است که باید نمایش داده شود. برای نشان دادن کاراکترهای الفبای روسی، تغییر دهنده نوع شناسه داده به شکل کاراکتر بدون علامت است، زیرا کدهای حروف روسی از مقدار 127 فراتر می رود.

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

برای مثال:

A = sizeof (int)؛ b = sizeof (طولانی). c = sizeof (بدون علامت طولانی)؛ d = اندازه (کوتاه)؛

همچنین توجه داشته باشید که ثابت‌های هشت‌گانه و هگزادسیمال نیز می‌توانند اصلاح‌کننده بدون علامت داشته باشند. این با تعیین پیشوند u یا U بعد از ثابت به دست می آید؛ ثابت بدون این پیشوند، امضا شده در نظر گرفته می شود.

برای مثال:

0xA8C (int signed)؛ 01786l (امضای طولانی)؛ 0xF7u (int بدون علامت)؛

1.2.3. داده های شناور

برای متغیرهایی که یک عدد ممیز شناور را نشان می‌دهند، از اصلاح‌کننده‌های نوع زیر استفاده می‌شود: float، double، long double (در برخی از پیاده‌سازی‌های زبان C long double وجود ندارد).

یک مقدار شناور 4 بایت طول می کشد. از این تعداد، 1 بایت برای علامت، 8 بیت برای توان اضافی و 23 بیت برای مانتیس رزرو شده است. توجه داشته باشید که مهم ترین بیت آخوندک همیشه 1 است، بنابراین پر نمی شود؛ بنابراین، محدوده مقادیر برای متغیر ممیز شناور تقریباً 3.14E-38 تا 3.14E + 38 است.

یک دابل 8 بیت در حافظه اشغال می کند. فرمت آن شبیه به float است. بیت های حافظه به صورت زیر تخصیص داده می شوند: 1 بیت برای علامت، 11 بیت برای توان و 52 بیت برای مانتیس. با در نظر گرفتن بیت مرتبه بالا حذف شده آخوندک، محدوده مقادیر از 1.7E-308 تا 1.7E + 308 است.

شناور f, a, b; دو برابر x، y;

1.2.4. اشاره گرها

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

نوع-مشخص کننده [تغییرکننده] * مشخص کننده.

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

کلمات کلیدی const, near, far, large را می توان به عنوان اصلاح کننده هنگام اعلام اشاره گر استفاده کرد. کلمه کلیدی const نشان می دهد که نشانگر را نمی توان در برنامه تغییر داد. اندازه متغیری که به عنوان اشاره گر اعلام می شود به معماری رایانه و مدل حافظه استفاده شده که برنامه برای آن کامپایل می شود بستگی دارد. نشانگرهای انواع داده های مختلف نباید طول یکسانی داشته باشند.

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

بدون امضا int * a; / * متغیر a یک اشاره گر به نوع int بدون علامت (اعداد صحیح بدون علامت) * / double * x; / * متغیر x نشان دهنده دقت مضاعف نوع داده های نقطه شناور * / char * fuffer است. / * یک اشاره گر به نام fuffer اعلام می شود که به متغیری از نوع char * / double nomer اشاره می کند. void * آدرس آدرس = & nomer; (دوگانه *) آدرس ++; / * آدرس متغیر به عنوان یک اشاره گر به یک شی از هر نوع اعلام می شود. بنابراین، می توان آدرس هر شیئی را به آن اختصاص داد (& عمل محاسبه یک آدرس است). با این حال، همانطور که در بالا ذکر شد، تا زمانی که نوع داده ای که به آن اشاره می کند به صراحت مشخص نشود، هیچ عملیات حسابی را نمی توان روی یک اشاره گر انجام داد. این را می توان با استفاده از یک عملیات ریخته گری (double *) برای تبدیل آدرس ها به یک اشاره گر به دو برابر و سپس افزایش آدرس انجام داد. * / const * dr; / * متغیر dr به عنوان یک اشاره گر به یک عبارت ثابت، یعنی. مقدار یک اشاره گر می تواند در طول اجرای برنامه تغییر کند، اما مقداری که به آن اشاره می کند نمی تواند. * / char بدون علامت * const w = & obj. / * متغیر w به عنوان یک اشاره گر ثابت برای داده های نوع char بدون علامت اعلام می شود. این بدان معنی است که در کل برنامه w به همان ناحیه از حافظه اشاره می کند. محتوای این قسمت قابل تغییر است. */

1.2.5. متغیرهای برشمرده شده

متغیری که بتواند مقداری را از فهرستی از مقادیر بگیرد، متغیر شمارش شده یا شمارش نامیده می شود.

یک اعلامیه شمارش با کلمه کلیدی enum شروع می شود و دارای دو قالب ارائه است.

قالب 1. enum [enum-tag-name] (enumeration-list) descriptor [, descriptor ...];

قالب 2. enum enum-tag-name descriptor [, descriptor ..];

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

یک متغیر از نوع شمارش می‌تواند مقادیر یکی از ثابت‌های نام‌گذاری شده در لیست را بگیرد. ثابت های لیست نامگذاری شده از نوع int هستند. بنابراین، حافظه مربوط به متغیر شمارش، حافظه مورد نیاز برای تطبیق مقدار int است.

متغیرهای نوع enum را می توان در عبارات شاخص و به عنوان عملوند در عملیات حسابی و رابطه ای استفاده کرد.

در قالب اول 1، نام ها و مقادیر شمارش در لیست شمارش مشخص شده است. انتخابی enumeration-tag-name شناسه‌ای است که تگ شمارش تعریف شده توسط لیست شمارش را نام‌گذاری می‌کند. توصیفگر یک متغیر شمارش را نامگذاری می کند. بیش از یک متغیر از نوع شمارش را می توان در یک اعلان مشخص کرد.

List-Enumeration شامل یک یا چند ساختار از فرم است:

شناسه [= عبارت ثابت]

هر شناسه یک عنصر از شمارش را نام می برد. همه شناسه ها در لیست enum باید منحصر به فرد باشند. در صورت عدم وجود یک عبارت ثابت، اولین شناسه با مقدار 0، شناسه بعدی با مقدار 1 و غیره مطابقت دارد. نام یک ثابت شمارش معادل مقدار آن است.

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

1. یک متغیر می تواند حاوی مقادیر تکراری باشد.

2. شناسه‌های موجود در فهرست شمارش باید از همه شناسه‌های دیگر در همان محدوده، از جمله نام متغیرهای معمولی و شناسه‌های فهرست‌های دیگر شمارش‌ها متمایز باشند.

3. نام انواع شمارش باید متمایز از نام های دیگر انواع شمارش، ساختارها و مخلوط در همان محدوده باشد.

4. مقدار می تواند از آخرین عنصر لیست شمارش پیروی کند.

تعداد هفته (SUB = 0، / * 0 * / VOS = 0، / * 0 * / POND، / * 1 * / VTOR، / * 2 * / SRED، / * 3 * / HETV، / * 4 * / PJAT / * 5 * /) rab_ned;

در این مثال، تگ شماره شمارش شده با مجموعه مقادیر مربوطه، و متغیر rab_ned از نوع week اعلام شده است.

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

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

1.2.6. آرایه ها

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

توصیفگر نوع مشخص کننده [ثابت - بیان]؛

دسته مشخص کننده نوع؛

توصیفگر یک شناسه آرایه است.

type-specifier نوع عناصر آرایه اعلام شده را مشخص می کند. عناصر آرایه نمی توانند توابع و عناصر خالی باشند.

بیان ثابت براکتی تعداد عناصر آرایه را مشخص می کند. Const-expression را می توان هنگام اعلام یک آرایه در موارد زیر حذف کرد:

وقتی اعلام شد، آرایه مقداردهی اولیه می شود،

آرایه به عنوان پارامتر رسمی تابع اعلام می شود،

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

هر بیان ثابت در براکت مربع تعداد عناصر را برای یک بعد معین از آرایه تعریف می کند، بنابراین یک اعلان آرایه دو بعدی شامل دو عبارت ثابت، یک بیان سه بعدی و غیره است. توجه داشته باشید که در زبان C اولین عنصر آرایه دارای اندیس برابر با 0 است.

Int a; / * به عنوان یک ماتریس a a a a a a * / double b نشان داده شده است. / * بردار 10 عنصر از نوع دوگانه * / int w = ((2، 3، 4)، (3، 4، 8)، (1، 0، 9));

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

در زبان C می‌توانید از برش‌های آرایه مانند سایر زبان‌های سطح بالا (PL1 و غیره) استفاده کنید، اما محدودیت‌هایی برای استفاده از برش‌ها اعمال می‌شود. بخش ها با حذف یک یا چند جفت براکت تشکیل می شوند. جفت براکت ها را فقط می توان از راست به چپ و کاملاً متوالی انداخت. بخش‌هایی از آرایه‌ها در سازماندهی فرآیند محاسباتی در توابع زبان C که توسط کاربر ایجاد شده است، استفاده می‌شوند.

اگر هنگام فراخوانی یک تابع، s بنویسید، رشته صفر آرایه s ارسال می شود.

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

نمونه ای از اعلان یک آرایه کاراکتری.

char str = "اعلام یک آرایه کاراکتر";

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

1.2.7. سازه های

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

ساختار (فهرست تعریف)

ساختار باید حداقل شامل یک جزء باشد. تعریف سازه ها به شرح زیر است:

توصیفگر نوع داده؛

که در آن data-type نوع ساختار اشیاء تعریف شده در توصیفگرها را نشان می دهد. در ساده ترین شکل خود، توصیفگرها شناسه یا آرایه هستند.

ساختار (x دوگانه، y؛) s1، s2، sm. ساختار (int year; char moth, day;) date1, date2;

متغیرهای s1 و s2 به عنوان ساختارهایی تعریف می شوند که هر کدام از دو جزء x و y تشکیل شده است. متغیر sm به عنوان آرایه ای از نه ساختار تعریف می شود. هر یک از دو متغیر date1, date2 از سه جزء سال، پروانه، روز تشکیل شده است. > p> راه دیگری برای مرتبط کردن نام با نوع ساختار وجود دارد، این روش مبتنی بر استفاده از تگ ساختار است. یک تگ ساختار شبیه به یک تگ شمارش شده است. تگ ساختار به صورت زیر تعریف می شود:

تگ struct (لیست توضیحات؛);

جایی که تگ یک شناسه است.

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

دانش آموز ساختاری (نام کاراکتر؛ شناسه داخلی، سن؛ char prp؛)؛

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

struct tag list-identifiers;

struct Studeut st1, st2;

استفاده از تگ های ساختاری برای توصیف ساختارهای بازگشتی ضروری است. استفاده از تگ های ساختار بازگشتی در زیر مورد بحث قرار گرفته است.

گره ساختار (داده های int؛ گره ساختار * بعدی؛) st1_node;

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

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

St1.name = "ایوانف"; st2.id = st1.id; st1_node.data = st1.age;

1.2.8. انجمن ها (مخلوط)

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

اتحاد (توضیح عنصر 1؛ ... توضیحات عنصر n;);

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

دسترسی به اعضای اتحادیه به همان روشی است که ساختارها انجام می دهند. تگ اتحاد را می توان به همان روشی که تگ ساختار رسمی کرد.

اتحادیه برای اهداف زیر استفاده می شود:

مقدار دهی اولیه شیء حافظه استفاده شده، اگر در هر زمان معینی فقط یک شی از تعداد زیادی فعال باشد.

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

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

اتحادیه (char fio; char adres; int vozrast; int telefon;) inform; اتحاد (int ax; char al;) ua;

هنگام استفاده از شی اطلاعات از نوع اتحادیه، می توانید فقط عنصری را که مقدار را دریافت کرده است پردازش کنید. پس از تخصیص یک مقدار به عنصر inform.fio، ارجاع به عناصر دیگر بی معنی است. ترکیب ua امکان دسترسی جداگانه به بایت های ua.al و ua.al بالایی عدد دو بایتی ua.ax را می دهد.

1.2.9. فیلدهای بیتی

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

ساختار (شناسه بدون علامت 1: طول فیلد 1؛ شناسه بدون علامت 2: طول میدان 2؛)

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

ساختار (بدون علامت a1: 1؛ بدون علامت a2: 2؛ بدون علامت a3: 5؛ بدون علامت a4: 2;) prim;

ساختارهای Bitfield همچنین می توانند شامل اجزای کاراکتر باشند. چنین مولفه هایی به طور خودکار در مرزهای کلمه مناسب قرار می گیرند و برخی از بیت های کلمات ممکن است بلااستفاده بمانند.

1.2.10. متغیرهایی با ساختار قابل تغییر

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

مثالی را در نظر بگیرید که در آن اطلاعاتی در مورد اشکال هندسی بر اساس استفاده ترکیبی از ساختار و اتحاد ارائه شده است.

شکل ساختار (مساحت دوگانه، محیط؛ / * مولفه های مشترک * / نوع int؛ / * ویژگی مؤلفه * / اتحاد / * شمارش اجزا * / (شعاع دوگانه؛ / * دایره * / دو برابر a؛ / * مستطیل * / دو برابر b ; / * مثلث * /) geom_fig;) fig1, fig2;

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

توجه داشته باشید که به جای کامپوننت نوع از نوع int، توصیه می شود از یک نوع شمارش شده استفاده کنید. به عنوان مثال، چنین

تعداد شکل_شطرنج (CIRCLE، BOX، TRIANGLE)؛

ثابت های CIRCLE، BOX، TRIANGLE به ترتیب مقادیر 0، 1، 2 را دریافت خواهند کرد. متغیر نوع را می توان به عنوان دارای یک نوع برشماری اعلام کرد:

enum figure_type شطرنج;

در این مورد، کامپایلر C به برنامه نویس در مورد تخصیص بالقوه اشتباه هشدار می دهد، مانند:

figure.type = 40;

به طور کلی، یک متغیر ساختار از سه بخش تشکیل می شود: مجموعه ای از اجزای مشترک، یک برچسب جزء فعال، و بخشی با اجزای متغیر. شکل کلی یک ساختار متغیر به شرح زیر است:

ساختار (کامپوننت‌های مشترک؛ برچسب مؤلفه فعال؛ اتحاد (شرح مؤلفه 1؛ توصیف مؤلفه 2؛ ::: توصیف مؤلفه n;) شناسه اتحادیه؛) شناسه ساختار؛

مثالی از تعریف یک متغیر ساختاری به نام helth_record

ساختار (/ * اطلاعات عمومی * / نام کاراکتر؛ / * نام * / سن بین‌المللی؛ / * سن * / جنسیت کاراکتر؛ / * جنسیت * / / * برچسب مؤلفه فعال * / / * (وضعیت تأهل) * / فهرست شرایط ارزشی . ) ثبت_سلامتی؛ enum marital_status (SINGLE، / * مجرد * / MARRIGO، / * متاهل * / DIVOREED / * مطلقه * /);

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

Helth_record.neme، helth_record.ins، helth_record.marriage_info.marriage_date.

1.2.11. تعریف اشیا و انواع

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

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

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

با این حال، باید به خاطر داشت که برخی از ترکیبات اصلاح کننده مجاز نیستند:

عناصر آرایه نمی توانند توابع باشند،

توابع نمی توانند آرایه ها یا توابع را برگردانند.

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

برای تفسیر توصیفات پیچیده، یک قانون ساده پیشنهاد شده است که به نظر می رسد "از درون به بیرون" و شامل چهار مرحله است.

1. با یک شناسه شروع کنید و به سمت راست نگاه کنید تا ببینید آیا پرانتز یا پرانتز وجود دارد یا خیر.

2. اگر هستند، این قسمت از توصیفگر را تفسیر کنید و سپس به سمت چپ برای ستاره نگاه کنید.

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

4. تعیین کننده نوع را تفسیر کنید.

Int * (* comp) (); 6 5 3 1 2 4

در این مثال، متغیر comp (1) به صورت آرایه ای از ده (2) اشاره گر (3) به توابع (4) نشانگر (5) را به مقادیر صحیح (6) باز می گرداند.

Char * (* (* var) ()); 7 6 4 2 1 3 5

متغیر var (1) به عنوان یک اشاره گر (2) به یک تابع (3) اعلام می شود که یک اشاره گر (4) را به آرایه (5) از 10 عنصر، که اشاره گر (6) به مقادیر کاراکتر هستند، برمی گرداند.

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

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

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

Typedef double (* MATH) (); / * MATH - نام نوع جدید نشان دهنده یک اشاره گر به تابعی است که مقادیر دوگانه را برمی گرداند * / MATH cos. / * cos یک اشاره گر به یک تابع است که مقادیری از نوع double * / / * را برمی گرداند * می توان یک اعلان معادل * / double (* cos) () ایجاد کرد. typedef char FIO / * FIO - آرایه از چهل کاراکتر * / FIO شخص. / * متغیر شخص آرایه ای از چهل کاراکتر است * / / * این معادل اعلان * / char person است.

هنگام اعلام متغیرها و انواع، از نام نوع در اینجا استفاده می شود (MATH FIO). علاوه بر این، نام های نوع را می توان در سه حالت دیگر استفاده کرد: در لیست پارامترهای رسمی، در اعلام توابع، در عملیات نوع ریخته گری و در عملیات sizeof (عملیات ریخته گری نوع).

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

نوع مشخص کننده چکیده-توصیف;

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

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

1.2.12. مقداردهی اولیه داده ها

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

قالب 1: = آغازگر;

فرمت 2: = (لیست - آغازگر)؛

فرمت 1 هنگام مقداردهی اولیه متغیرهای انواع پایه و اشاره گرها و فرمت 2 برای مقداردهی اولیه اشیاء ترکیبی استفاده می شود.

متغیر tol با "N" مقداردهی اولیه می شود.

const long megabute = (1024 * 1024)؛

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

static int b = (1,2,3,4);

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

static int b = ((1،2)، (3،4));

هنگام تنظیم اولیه یک آرایه، می توانید یک یا چند بعد را حذف کنید

static int b type_ specifier identifier [, identifier] ...

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

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

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

Int x = 100;

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

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

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

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

نمونه هایی از توضیحات:

char a, b; / * متغیرهای a و b از نوع هستند

char * / intх; / * متغیر x - از نوع int

* / char sym; / "متغیرهای sym از نوع char شرح داده شده است.

* / int count.num; / * تعداد و تعداد از نوع int * /

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

مثال ها: char backch = "\ 0";

بیایید انواع اصلی را در زبان C در نظر بگیریم.

int - کل ("عدد صحیح").مقادیر این نوع اعداد صحیح از یک محدوده محدود هستند (معمولاً از 32768 تا 32767). محدوده با اندازه سلول برای نوع تعیین می شود و به رایانه خاص بستگی دارد. علاوه بر این، کلمات خاصی وجود دارد که می توان با نوع int استفاده کرد: short int ("عدد صحیح کوتاه")، int بدون علامت ("عدد صحیح بدون علامت" - "عدد صحیح بدون علامت")، long int ("عدد صحیح طولانی") که کوتاه می شود. یا برعکس، دامنه نمایش اعداد را گسترش دهید.

کاراکتر- شخصیت ("شخصیت"). مقدار معتبر برای این نوع یک کاراکتر است (با متن اشتباه نشود!). شخصیت به صورت آپاستروف نوشته شده است.

مثال ها:"x" 2 "?"

در حافظه کامپیوتر، یک کاراکتر یک بایت را اشغال می کند. در واقع، یک کاراکتر ذخیره نمی شود، بلکه یک عدد - یک کد کاراکتر (از 0 تا 255) ذخیره می شود. تمام کاراکترهای معتبر و کدهای مربوط به آنها در جداول رمزگذاری ویژه نشان داده شده است.

در زبان C، مجاز است از نوع char به عنوان عددی استفاده شود، یعنی برای انجام عملیات با کد کاراکتر، با استفاده از مشخص کننده نوع عدد صحیح در براکت - (int).

شناور - واقعی (نقطه شناور).مقادیر این نوع اعداد هستند، اما برخلاف char و int، لزوما اعداد صحیح نیستند.

12.87 -316.12 -3.345e5 12.345e-15

اعداد واقعی با دقت دو برابر.این نوع شبیه به نوع شناور است، اما دامنه مقادیر بسیار گسترده تری دارد (به عنوان مثال، برای سیستم برنامه نویسی Borland-C از 1.7E-308 تا 1.7E + 308 به جای محدوده از 3.4E-38 تا 3.4E + 38 برای نوع شناور). اما افزایش برد و دقت نمایش اعداد منجر به کاهش سرعت اجرای برنامه و استفاده بیهوده از رم کامپیوتر می شود.


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

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

بیایید مثالی از استفاده از رشته ها بزنیم.

برنامه 84

# عبارتند از اصلی ()

scanf ("% s"، رشته)؛

printf ("% s"، رشته)؛

این مثال آرایه ای از 31 سلول حافظه را توصیف می کند که 30 مورد از آنها می توانند یک عنصر از نوع char را در خود جای دهند. زمانی که تابع scanf ("% s"، رشته) فراخوانی می شود، وارد می شود. هنگام تعیین یک آرایه کاراکتر، "&" وجود ندارد.

اشاره گرها. اشاره گر - نوعی نمایش نمادین از آدرس حافظه اختصاص داده شده به متغیر.

به عنوان مثال، & name یک اشاره گر به نام متغیر است.

در اینجا & عملیات به دست آوردن آدرس است. آدرس واقعی یک عدد است و نمایش نمادین & name یک ثابت اشاره گر است.

در زبان C نیز متغیرهایی از نوع اشاره گر وجود دارد. همانطور که مقدار متغیری از نوع char یک کاراکتر است و مقدار متغیری از نوع int یک عدد صحیح است، مقدار متغیری از نوع اشاره گر آدرس مقداری است.

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

ptr = / * آدرس نام را به متغیر ptr اختصاص می دهد * /

ما در این مورد می گوییم که prt یک نام "اشاره گر به" است. تفاوت بین ptr و & name در این است که prt یک متغیر است، در حالی که & name یک ثابت است. در صورت لزوم، می توانید نقطه متغیر ptr را به شی دیگری تبدیل کنید:

ptr= / * ptr به bah اشاره می کند، نه نام * /

حال مقدار متغیر prt آدرس متغیر bah است. فرض کنید می دانیم که متغیر ptr حاوی ارجاع به متغیر bah است. سپس برای دسترسی به مقدار این متغیر می توانید از عملیات آدرس دهی غیر مستقیم * استفاده کنید:

val = * ptr; / * مقداری را که توسط ptr به آن اشاره می شود تعریف کنید * / دو عملگر آخر، با هم، معادل زیر هستند:

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

مثال:پرستار = 22;

ptr = /* اشاره گر به پرستار * /

نتیجه تخصیص مقدار 22 به متغیر val است.

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

نمونه هایی ازشرح صحیح اشاره گرها: int * pi; char * pc;

مشخصات نوع، نوع متغیری را که نشانگر به آن ارجاع می دهد، و کاراکتر * خود متغیر را به عنوان اشاره گر تعریف می کند. شرح فرم int * pi; می گوید pi یک اشاره گر است و * پی یک int است.

زبان C امکان تعریف نام انواع داده را فراهم می کند. شما می توانید با استفاده از تعریف typedef به هر نوع داده ای یک نام اختصاص دهید و در آینده هنگام توصیف اشیا از این نام استفاده کنید.

فرمت: typedef<старый тип> <новый тип> مثال: typedef long LARGE; / * بزرگ را تعریف می کند که معادل طولانی * / است

تعریف typedef هیچ نوع جدیدی را معرفی نمی کند، فقط یک نام جدید برای نوع موجود اضافه می کند. متغیرهایی که به این روش توصیف می شوند دقیقاً همان ویژگی های متغیرهایی را دارند که به صراحت توصیف شده اند. تغییر نام نوع برای معرفی نام‌های نوع معنی‌دار یا مخفف برای بهبود درک برنامه و بهبود قابلیت حمل برنامه استفاده می‌شود (نام‌های یک نوع داده ممکن است در رایانه‌های مختلف متفاوت باشند).

عملیات.زبان C با طیف گسترده ای از عملیات (بیش از 40) متمایز می شود. در اینجا ما فقط موارد اصلی، تب را در نظر خواهیم گرفت. 3.3.

عملیات حسابی. این شامل

اضافه (+)،

تفریق (دودویی) (-)،

ضرب (*)،

بخش (/)،

باقیمانده تقسیم (%)،

تفریق (یونی) (-).

در زبان C یک قانون وجود دارد: اگر تقسیم و تقسیم کننده از نوع int باشند، تقسیم به طور کامل انجام می شود، یعنی قسمت کسری نتیجه کنار گذاشته می شود.

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

برنامه 85

#عبارتند از

5 = -3 + 4 * 5 - 6; printf ("% d \ n"، s)؛

s = -3 + 4% 5 - 6; printf ("% d \ n"، s)؛

s = -3 * 4٪ - 6/5; printf ("% d \ n"، s)؛

s = (7 + 6)% 5/2; printf ("% d \ n"، s)؛

نتیجه اجرای برنامه: 11 1 0 1

جدول 3.3 ارشدیت و ترتیب عملیات

مبانی زبان

کد برنامه و داده های دستکاری شده توسط برنامه به صورت دنباله ای از بیت ها در حافظه کامپیوتر نوشته می شود. بیت- این کوچکترین عنصر حافظه رایانه است که می تواند 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 یک کلمه، طولانی یک یا دو کلمه است. در سیستم های 32 بیتی، int و long معمولاً یک اندازه هستند.

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

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

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

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

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

یک). 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 تماس \ یک بک اسلش \\ سوال \؟ نقل قول تک \"نقل مضاعف\"

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

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

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

"" (خط خالی) "a" "\ nCC \ toptions \ tfile. \ n" "یک چند خط \ رشته تحت اللفظی ادامه آن را با یک اسلش علامت می دهد"

در واقع، یک رشته لفظی آرایه‌ای از ثابت‌های کاراکتر است که طبق قرارداد 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 خط ارزیابی زمان‌های pow نتیجه را تکرار می‌کند.
بدون شک ما برنامه بسیار انعطاف پذیرتری ایجاد کرده ایم. با این حال، این هنوز یک تابع نیست. برای به دست آوردن یک تابع واقعی که می تواند در هر برنامه ای برای محاسبه توان یک عدد استفاده شود، باید قسمت کلی محاسبات را انتخاب کنید و مقادیر خاصی را با پارامترها تنظیم کنید.

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 ++ نوع خاصی دارد که اندازه و مکان آن ناحیه حافظه، محدوده مقادیری که می‌تواند ذخیره کند و مجموعه عملیات‌های قابل اعمال برای آن متغیر را مشخص می‌کند. در اینجا مثالی از تعریف پنج شی از انواع مختلف آورده شده است:

تعداد_دانشجوی داخلی; دو برابر حقوق؛ bool on_loan; strins street_address; جدا کننده char;

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

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

در بیان

Ch = ch - "0"؛

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

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

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

// file module0.C // یک شیء را تعریف می‌کند. // ... یک مقدار به fileName اختصاص دهید
// فایل module1.C
// از شی fileName استفاده می کند
// افسوس که کامپایل نمی کند:
// نام فایل در module1.C تعریف نشده است
ifstream input_file (نام فایل)؛

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 دیگر شمارش صریح صادرات
خارجی نادرست شناور برای دوست
قابل اعتماد و متخصص اگر خطی بین المللی طولانی
قابل تغییر فضای نام جدید اپراتور خصوصی
حفاظت شده عمومی ثبت نام reinterpret_cast برگشت
کوتاه امضاء شده اندازه استاتیک static_cast
ساخت تعویض قالب این پرتاب كردن
typedef درست است، واقعی تلاش كردن تایپ شده نام را تایپ کنید
اتحاد. اتصال voidunion استفاده كردن مجازی خالی

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

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

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

3.2.3. تعریف شی

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

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

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

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

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

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

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

Int ival = 1024; string project = "Fantasia 2000";

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

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

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

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

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

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

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

// ival مقدار 0 و dval 0.0 int ival = int (); double dval = double ();

در تعریف زیر:

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

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

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

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

تمرین 3.3

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

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

تمرین 3.4

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

تمرین 3.5

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

(الف) نام رشته خارجی؛ نام رشته ("تمرین 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; رشته * pstring; بردار * pvec؛ دو برابر * dp;

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

طولانی * lp، lp2;

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

Float fp، * fp2;

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

// توجه: ps2 یک اشاره گر به یک رشته نیست! رشته * ps, ps2;

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

Int ival = 1024;

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

// پی به صفر 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 =

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

وقتی عملیات گرفتن آدرس (&) را به یک شی از نوع int اعمال می کنیم، نتیجه نوع int * را دریافت می کنیم.
int * pi =
اگر همان عملیات را روی یک شی از نوع int * (اشاره‌گر به 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
* پی = * پی + 2; // آدرس موجود در پی را 2 افزایش دهید
پی = پی + 2;

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

Int ia; 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 اضافه می کند و بنابراین آن را به کاراکتر بعدی منتقل می کند.
اجرای تابعی که طول یک رشته را برمی گرداند به این صورت است. توجه داشته باشید که از آنجایی که یک اشاره گر می تواند حاوی مقدار تهی باشد (به چیزی اشاره نمی کند)، باید قبل از عدم ارجاع بررسی شود:

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

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

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

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

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

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

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

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

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

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

18: بطری های شراب ena

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

St = st - len - 1;

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

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

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

St = 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) بازگشت;

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

#عبارتند از

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

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

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

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

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

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

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

اگر (! St.size ()) // صحیح: خالی

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

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

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

رشته st3 (st);

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

اگر (st == st3) // مقداردهی اولیه کار کرد

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

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

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

رشته s1 ("سلام"); رشته s2 ("جهان \ 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 char * str = s1.c_str (); // درست

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

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

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

جایگزین (str.begin ()، str.end ()، "."، "_");

جایگزین () یکی از الگوریتم‌های عمومی است که در بخش 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

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

در حالی که (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)؛
اگر (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;
اگر (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 است محلی شده است.

Int 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 است. ظرافت در این واقعیت نهفته است که نشانگر خود یک ثابت نیست، به این معنی که می توانیم مقدار آن را تغییر دهیم. برای مثال:

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

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

کامپیوتر =

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

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

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

Int errNumb = 0; int * const currErr =

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

کاری بکنید ()؛ اگر (* curErr) (
error Handler ();
* 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. نوع مرجع

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

Int ival = 1024; // صحیح: refVal اشاره ای به ival int & refVal = ival است. // خطا: پیوند باید int مقداردهی اولیه شود

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

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

Int min_val = 0; // ival مقدار min_val را دریافت می کند، // به جای 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 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 int ival = 1024; // خطا: مرجع ثابت مورد نیاز است
int * & pi_ref =

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

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

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

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

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

Int * pi = 0;

پی را به صفر مقداردهی اولیه می کنیم، به این معنی که پی به هیچ شیئی اشاره نمی کند. همزمان ضبط

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 &);

Int ival; در حالی که (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 می تواند یکی از دو مقدار درست و نادرست را داشته باشد. برای مثال:

// مقداردهی اولیه رشته رشته 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 &); bool یافت = نادرست; if (found = find ("sebbud")) // correct: found == true // یک اشاره گر به عنصر برمی گرداند
extern int * find (int value); اگر (یافت = یافت (1024)) // صحیح: یافت == درست است

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 عضوی از enumeration open_modes open_file نیست ("Jonah", 1);

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

حالتهای باز om = ورودی; // ... om = ضمیمه; open_file ("TailTell"، om);

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

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

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

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

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

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

// برای (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);

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

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

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

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

3.9. نوع آرایه

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

Int ival;

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

Int ia [10];

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

ایوال = ia [2];

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

Ia [7] = ival;

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

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

خارجی int get_size (); // ثابت buf_size و max_files
const int buf_size = 512، max_files = 20;
int staff_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 [اندازه_آرایه] = (0، 1، 2);

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

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

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

// ia ==> (0, 1, 2, 0, 0) const int array_size = 5; int ia [اندازه_آرایه] = (0، 1، 2);

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

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

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

// خطا: رشته "دانیل" از 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]؛ برای (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 ارائه شدند):

#عبارتند از قالب چاپ خالی (elemType * pbegin, elemType * pend) (در حالی که (pbegin! = pend) (cout<< *pbegin << " "; ++pbegin; } }

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

Int main () (int ia = (0، 1، 1، 2، 3، 5، 8، 13، 21)؛ دابل دا = (3.14، 6.28، 12.56، 25.12)؛ رشته sa = ("خوکک"، " eeyore "," pooh "); print (ia, ia + 9);
چاپ (دا، دا + 4)؛
چاپ (sa، sa + 3)؛
}

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

#عبارتند از int main () (int ia = (107، 28، 3، 47، 104، 76)؛ string sa = ("piglet"، "eeyore"، "pooh")؛ مرتب سازی (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]; //...
}

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

چاپ_بردار خالی (بردار 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، O، 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); // ...)

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

کوت<< "считаны слова: \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 نداریم. تعداد عناصر با استفاده از تابع اندازه () مشخص می شود.

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

بردار 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، رشته ("تهی"))؛

تمرین 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 * جدول [10]; جدول پینت [10];

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

Typedef char * cstring;

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

extern const cstring cstr;

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

Const char * cstr

با این حال، این درست نیست. مشخص کننده const به cstr اشاره دارد، بنابراین پاسخ صحیح یک اشاره گر ثابت به 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 >نویسندگان؛ نویسندگان پروست ("مارسل"، "پروست"); نویسندگان جویس ("جیمز"، "جویس"); نویسندگان musil ("robert"، "musi1");

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

Slot ورودی کلاس؛ extern EntrySlot * 1ook_up (رشته); جفت typedef< string, EntrySlot* >SymbolEntry; SymbolEntry current_entry ("نویسنده"، 1ook_up ("نویسنده"));
// ... اگر (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 و یک نوع رشته داخلی و همچنین عملیات تخصیص مقادیر این نوع به آن پشتیبانی کند. برای این کار از سازنده کلاس و یک عملگر انتساب اضافه بار استفاده می کنیم. دسترسی به کاراکترهای مجزای رشته به‌عنوان یک عملیات دریافت فهرست بارگذاری شده اجرا می‌شود. علاوه بر این، به تابع اندازه () برای دریافت اطلاعات در مورد طول رشته نیاز داریم. عملیات مقایسه اشیاء از نوع String و یک شی String با یک رشته از نوع داخلی. و همچنین عملیات ورودی/خروجی شی ما. در نهایت، ما توانایی دسترسی به نمایش داخلی رشته خود را به عنوان یک نوع رشته داخلی اجرا خواهیم کرد.
تعریف کلاس با کلمه کلیدی کلاس شروع می شود و به دنبال آن یک شناسه - نام کلاس یا نوع آن وجود دارد. به طور کلی، یک کلاس شامل بخش هایی است که قبل از آن کلمات عمومی و خصوصی قرار می گیرند. یک بخش عمومی معمولاً شامل مجموعه ای از عملیات پشتیبانی شده توسط یک کلاس است که متدها یا توابع عضو کلاس نامیده می شوند. این توابع عضو، رابط عمومی یک کلاس را تعریف می کنند، به عبارت دیگر، مجموعه ای از اقدامات را که می توان روی اشیاء یک کلاس انجام داد. یک بخش خصوصی معمولاً شامل اعضای داده ای است که اجرای داخلی را ارائه می دهند. در مورد ما، اعضای داخلی _string - یک اشاره گر به char، و همچنین _size از نوع int هستند. _size اطلاعاتی در مورد طول رشته نگه می دارد و _string آرایه ای از کاراکترها به صورت پویا تخصیص داده می شود. تعریف کلاس به این صورت است:

#عبارتند از کلاس رشته; istream & operator >> (istream &, String &);
ostream & operator<<(ostream&, const String&); class String {
عمومی:
// مجموعه ای از سازنده ها
// برای مقداردهی اولیه خودکار
// String strl; // رشته ()
// رشته str2 ("لفظی"); // رشته (const char *);
// رشته str3 (str2); // رشته (const String &); رشته ();
رشته (const char *);
رشته (const String &); // ویرانگر
~ رشته (); // عملگرهای انتساب
// strl = str2
// str3 = "یک رشته واقعی" String & operator = (const String &);
رشته و عملگر = (Const char *); // عملگرهایی برای بررسی برابری
// strl == str2;
// str3 == "یک رشته واقعی"; عملگر bool == (const String &);
عملگر bool == (const char *); // بارگذاری اپراتور دسترسی بر اساس فهرست
// strl [0] = str2 [0]; char & operator (int); // دسترسی به اعضای کلاس
اندازه int () (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 &)، کپی نامیده می شود زیرا یک شی را با یک کپی از یک شی دیگر مقدار دهی اولیه می کند.
اگر بنویسید:

رشته str4 (1024);

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

عملگر Return_type op (parameter_list);

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

Char & operator (int);

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

شی رشته ("دنی")؛
رشته * ptr = رشته جدید ("Anna");
آرایه رشته ای؛
فراخوانی تابع size () برای این اشیا به این صورت است:
بردار سایز (3)؛

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

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

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

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

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

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

Inline bool String :: عملگر == (const String & rhs) (// همان)

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

رشته bool درون خطی :: عملگر == (const char * s) (strcmp (_string, s) را برمی گرداند؟ غلط: درست؛)

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

#عبارتند از // رشته درون خطی سازنده پیش فرض :: رشته ()
{
_size = 0;
_string = 0;
) رشته درون خطی :: رشته (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];
} }

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

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

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

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

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

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

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

Inline String & String :: operator = (const String & rhs) (// در عبارت // namel = * pointer_to_string // this is name1, // rhs - * pointer_to_string. If (this! = & Rhs) (

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

رشته و رشته درونی :: عملگر = (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)
{
ادعا (elem> = 0 && elem< _size);
return _string [elem];
}

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

#عبارتند از istream و اپراتور داخلی >> (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
// رشته :: عملگر == (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: 2
آن / آن: 1
حروف صامت: 190
الف: 22
ه: 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: // ... عملگر رشته + (const String & rhs) const; // ...);

مقالات مرتبط برتر