اطلاق شی گرایی در برنامه نویسی

0

به ساختار نوشتاری زبان برنامه نویس ++C شی گرا اطلا ق می شود یعنی چه ؟

حسین برخورداری سوال پاسخ داده شده اکتبر 13, 2020
گذاشتن نظر
1

به ساختار نوشتاری زبان برنامه نویس ++C شی گرا اطلا ق می شود یعنی چه ؟

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

سپس ، “بمب”. نویسنده اصلی آموزش استفاده شده ASCII بر کپی رایت خود ایستاده و ما را از استفاده مجدد از کار وی محروم می کند. متأسفانه ، مارکوس نتوانست زمان بیشتری را صرف این پروژه کند و بنابراین نیروی محرک اصلی از بین رفت.

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

این دوره و آموزش فقط با کمک بسیاری از افراد قابل تحقق بود. من می خواهم از افراد آکادمی شبکه Globewide (GNA) ، به ویژه جوزف وانگ و سوزان ریدینگ تشکر کنم.

ریكاردو نصیف و دیوید كلین پیشنهادهایی را برای بهبود خوانایی آموزش به من ارائه می دهند.

آموزش کامل بصورت نامه و DIN A4 به صورت رایگان در PostScript در دسترس است. لطفا http://www.zib.de/mueller/Course/Tutorial/Postscript/ را ببینید .
برلین ، آلمان
پیتر مولر

1. مقدمه
This tutorial is a collection of lectures to be held in the on-line course Introduction to Object-Oriented Programming Using C++ . In this course, object-orientation is introduced as a new programming concept which should help you in developing high quality software. Object-orientation is also introduced as a concept which makes developing of projects easier. However, this is not a course for learning the C++ programming language. If you are interested in learning the language itself, you might want to go through other tutorials, such as C++: Annotations توسط فرانك بروككن و كارل كوبات. در این آموزش فقط مفاهیم زبانی که برای ارائه مثالهای کدگذاری مورد نیاز است معرفی می شوند. و چه چیزی شی گرایی را به چنین موضوعی داغ تبدیل می کند؟ صادقانه بگویم ، همه چیزهایی که تحت اصطلاح شی گرایی فروخته می شوند واقعاً جدید نیستند. به عنوان مثال ، برنامه هایی وجود دارد که به زبانهای رویه ای مانند Pascal یا C نوشته شده اند و از مفاهیم شی گرا استفاده می کنند. اما چند ویژگی مهم وجود دارد که به ترتیب این زبانها از عهده آنها بر نمی آیند یا از عهده آنها بر نمی آیند.

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

آموزش به شرح زیر تنظیم شده است. در فصل  2 یک مرور مختصر از برنامه نویسی رویه ای برای تازه کردن دانش خود در آن زمینه ارائه شده است. انواع داده های انتزاعی در فصل 3 به عنوان مفهوم اساسی شی گرا معرفی شده اند. پس از آن می توانیم تعریف اصطلاحات کلی را شروع کنیم و شروع به مشاهده جهان از اشیا کنیم (فصل 4 ). در فصل های بعدی مفاهیم اساسی شی گرا ارائه شده است (فصل  5 و 6 ). در فصل های  7 تا 9 ، C ++ به عنوان نمونه ای از زبان برنامه نویسی شی گرا معرفی شده است که از کاربرد گسترده ای برخوردار است. سرانجام فصل  10 نشان می دهد که چگونه برنامه نویسی شی گرا را در یک مثال واقعی اعمال کنید.

2 بررسی تکنیک های برنامه نویسی

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
این فصل یک بررسی کوتاه از تکنیک های برنامه نویسی است. ما برای نشان دادن خصوصیات خاص و اشاره به ایده ها و مشکلات اصلی آنها از یک مثال ساده استفاده می کنیم.

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

  • برنامه نویسی بدون ساختار ،
  • برنامه نویسی رویه ای ،
  • برنامه نویسی مدولار و
  • برنامه نویسی شی گرا.

این فصل به شرح زیر تنظیم شده است. بخشهای  2.1 تا 2.3 به طور خلاصه سه روش اول برنامه نویسی را شرح می دهد. متعاقباً ، ما یک مثال ساده از نحوه استفاده از برنامه نویسی مدولار برای پیاده سازی یک ماژول لیست پیوسته ارائه می دهیم (بخش  2.4 ) با استفاده از این ، ما در بخش 2.5 مشکلات مربوط به این نوع تکنیک ها را بیان می کنیم  . سرانجام ، بخش  2.6 تکنیک برنامه نویسی چهارم را توصیف می کند.

2.1 برنامه نویسی بدون ساختار
 معمولاً افراد یادگیری برنامه نویسی را با نوشتن برنامه های کوچک و ساده که فقط از یک برنامه اصلی تشکیل شده اند ، شروع می کنند. در اینجا “برنامه اصلی” مخفف دنباله ای از دستورات یا دستورات است که داده هایی را تغییر می دهد که در کل برنامه جهانی هستند . ما می توانیم این را نشان دهیم همانطور که در شکل 2.1 نشان داده شده است  .

Figure 2.1:  Unstructured programming. The main program directly operates on global data.

img1 - اطلاق شی گرایی در برنامه نویسی

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

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

شکل 2.2:   اجرای رویه ها. پس از پردازش جریان کنترل ها در محلی که تماس گرفته شده است پیش می روند.

img2 - اطلاق شی گرایی در برنامه نویسی

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

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

شکل 2.3:   برنامه نویسی رویه ای. برنامه اصلی تماس ها را برای رویه ها هماهنگ می کند و داده های مناسب را به عنوان پارامتر تحویل می دهد.

img3 - اطلاق شی گرایی در برنامه نویسی

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

2.3 Modular Programming
 با برنامه ریزی مدولار روش های یک ویژگی مشترک با هم در ماژول های جداگانه گروه بندی می شوند . بنابراین یک برنامه دیگر فقط از یک قسمت منفرد تشکیل نمی شود. اکنون به چندین قسمت کوچکتر تقسیم شده است که از طریق تماس های رویه ای با یکدیگر ارتباط برقرار می کنند و کل برنامه را تشکیل می دهند (شکل  2.4 ).

شکل 2.4:   برنامه نویسی مدولار. برنامه اصلی فراخوانی رویه ها را در ماژول های جداگانه هماهنگ می کند و داده های مناسب را به عنوان پارامتر تحویل می دهد.

img4 - اطلاق شی گرایی در برنامه نویسی

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

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

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

شکل 2.5:  Structure of a singly linked list.

img5 - اطلاق شی گرایی در برنامه نویسی

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

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

فرض کنید می خواهید لیستی را به زبان برنامه نویسی مدولار مانند C یا Modula-2 برنامه ریزی کنید. همانطور که فکر می کنید لیست ها یک ساختار داده ای مشترک است ، تصمیم می گیرید آن را در یک ماژول جداگانه پیاده سازی کنید . به طور معمول ، شما نیاز به نوشتن دو فایل دارید: تعریف رابط و فایل پیاده سازی . در این فصل ما از یک کد شبه بسیار ساده استفاده خواهیم کرد که باید بلافاصله آن را درک کنید. بیایید فرض کنیم که نظرات در “/ * … * /” ضمیمه شده اند. پس ممکن است تعریف رابط کاربری ما شبیه به شکل زیر باشد:

<span>    / * </span><span>
     * تعریف رابط برای ماژولی که </span><span>
     * لیست پیوند داده شده برای ذخیره داده ها از هر نوع را پیاده سازی می کند. </span><span>
     * / </span>
       <span>
    MODULE Single-Linked-List-1 </span>
<span>
    BOOL list_initialize ()؛ </span><span>
    BOOL list_append (هر داده) </span><span>
    BOOL list_delete ()؛ </span><span>
         list_end ()؛ </span>
<span>
    ANY list_getFirst ()؛ </span><span>
    ANY list_getNext ()؛ </span><span>
    BOOL list_isEmpty ()؛ </span>
<span>
    تک لیست-پیوندی-پایان 1</span>

تعاریف رابط فقط آنچه در دسترس است را توصیف می کند و نه اینکه چگونه در دسترس است. شما پنهان اطلاعات از اجرای در فایل پیاده سازی. این یک اصل اساسی در مهندسی نرم افزار است ، بنابراین اجازه دهید آن را تکرار کنیم: شما اطلاعات مربوط به اجرای واقعی را پنهان می کنید (information hiding). This enables you to change the implementation, for example to use a faster but more memory consuming algorithm for storing elements without the need to change other modules of your program: The calls to provided procedures remain the same.

ایده این رابط کاربری به شرح زیر است: قبل از استفاده از لیست ، باید لیست_initialize () را فراخوانی کنیم تا متغیرهای محلی ماژول را مقدار دهی کنیم. دو روش زیر روش های دسترسی ذکر شده را ضمیمه و حذف می کند . الحاق روش بحث مفصل تر نیاز دارد. توابع list_append () یک داده آرگومان از نوع دلخواه می گیرد. این لازم است زیرا شما می خواهید از لیست خود در چندین محیط مختلف استفاده کنید ، از این رو نوع عناصر داده ای که باید در لیست ذخیره شوند از قبل مشخص نیست. در نتیجه، شما مجبور به استفاده از یک نوع خاص هر که اجازه می دهد تا به اطلاعات اختصاص از هر نوع آن foot - اطلاق شی گرایی در برنامه نویسی. روش سومهنگام خاتمه برنامه ، باید list_end () فراخوانی شود تا ماژول بتواند متغیرهای داخلی مورد استفاده خود را تمیز کند. به عنوان مثال ممکن است بخواهید حافظه اختصاص داده شده را آزاد کنید.

با دو روش بعدی list_getFirst () و list_getNext () مکانیزم ساده ای برای عبور از لیست ارائه می شود. پیمایش با استفاده از حلقه زیر انجام می شود:

<span>    هر داده </span>
<span>
    داده <- list_getFirst ()؛ </span><span>
    در حالی که داده معتبر است انجام کاری </span><span>
        (داده) </span><span>
        داده <- list_getNext ()؛ </span><span>
    پایان</span>

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

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

<span>    / * </span><span>
     * یک ماژول لیست برای بیش از یک لیست. </span><span>
     * / </span>
<span>
    MODULE Single-Linked-List-2 اعلام </span>
<span>
    نوع نوع list_handle_t؛ </span>
<span>
    list_handle_t list_create ()؛ </span><span>
                  list_destroy (list_handle_t this)؛ </span><span>
    BOOL list_append (list_handle_t this ، هر داده) </span><span>
    ANY list_getFirst (list_handle_t this) ؛ </span><span>
    ANY list_getNext (list_handle_t this) ؛ </span><span>
    BOOL list_isEmpty (list_handle_t this) ؛ </span>
    <span>
    END Single-Linked-List-2 ؛</span>

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

شما با استفاده از list_create () می توانید دسته ای از لیست جدید بنابراین خالی را بدست آورید. هر روش دیگر شامل ویژه پارامتر این که فقط شناسایی لیست در سوال. اکنون همه رویه ها به جای لیست جهانی ماژول ، با این دسته کار می کنند.

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

2.5 Modular Programming Problems
 بخش قبلی نشان می دهد که شما از قبل با در نظر گرفتن برخی مفاهیم شی گرا برنامه ریزی کرده اید. با این حال ، مثال حاکی از برخی مشکلات است که اکنون به شرح آنها می پردازیم.

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

<span>    PROCEDURE foo () BEGIN </span><span>
        list_handle_t myList؛ </span><span>
        myList <- list_create ()؛ </span>
<span>
        / * با myList کاری انجام دهید * / </span><span>
        ... </span>
<span>
        list_destroy (myList)؛ </span><span>
    پایان</span>

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

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

<span>    PROCEDURE foo () BEGIN </span><span>
        list_handle_t myList؛ / * لیست ایجاد و مقداردهی اولیه می شود * / </span>
<span>
        / * کاری با myList انجام دهید * / </span><span>
        ... </span><span>
     پایان / * myList از بین می رود * /</span>

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

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

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

2.5.3 نوع ایمنی موجود نیست
 در مثال لیست ما باید از نوع خاص ANY استفاده کنیم تا به لیست اجازه دهیم هر داده ای را که دوست داریم حمل کند. این بدان معنی است که کامپایلر نمی تواند ایمنی نوع را تضمین کند. مثال زیر را در نظر بگیرید که کامپایلر نمی تواند صحت را بررسی کند:

<span>    PROCEDURE foo () BEGIN </span><span>
        SomeDataType data1؛ </span><span>
        SomeOtherType data2؛ </span><span>
        list_handle_t myList؛ </span>
<span>
        myList <- list_create ()؛ </span><span>
        list_append (myList ، data1) ؛ </span><span>
        list_append (myList ، data2) ؛ / * اوه * / </span>
<span>
        ... </span>
<span>
        list_destroy (myList)؛ </span><span>
    پایان</span>

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

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

<span>    list_handle_t <Apple> list1؛ / * لیستی از سیب * / </span><span>
    list_handle_t <Car> list2؛ / * لیستی از اتومبیل * /</span>

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

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

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

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

2.6 برنامه نویسی شی گرا
 برنامه نویسی شی گرا برخی از مشکلاتی را که قبلاً ذکر شد حل می کند. در مقابل به روش های دیگر، ما در حال حاضر یک وب سایت از تعامل دارند اشیاء ، هر ایالت خود را خانه دار، (شکل  2.6 ).

Figure 2.6:  Object-oriented programming. Objects of the program interact by sending messages to each other.

img6 - اطلاق شی گرایی در برنامه نویسی

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

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

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

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

2.7 تمرین

1
نمونه های لیست شامل نوع خاص ANY است تا به لیستی امکان انتقال داده از هر نوع را بدهد. فرض کنید می خواهید یک ماژول برای یک لیست تخصصی از اعداد صحیح بنویسید که بررسی نوع را ارائه می دهد. تمام آنچه شما دارید تعریف رابط ماژول Singly-Linked-List-2 است .

(آ)
تعریف رابط برای ماژول Integer-List چگونه به نظر می رسد؟
(ب)
درباره مشکلاتی که با استفاده از نوع ANY برای عناصر لیست در ماژول Single-Linked-List-2 وارد می شوند بحث کنید .
(ج)
راه حل های ممکن برای این مشکلات چیست؟

2
تفاوت های اصلی مفهومی بین برنامه نویسی شی گرا و سایر تکنیک های برنامه نویسی چیست؟

3
اگر با یک زبان برنامه نویسی مدولار آشنا هستید سعی کنید ماژول Singly-Linked-List-2 را پیاده سازی کنید . پس از آن ، لیستی از اعداد صحیح و لیستی از لیست های صحیح را با کمک این ماژول پیاده سازی کنید.

3 نوع داده چکیده

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
برخی از نویسندگان برنامه نویسی شی گرا را به عنوان برنامه نویسی انواع داده های انتزاعی و روابط آنها توصیف می کنند. در این بخش انواع داده های انتزاعی را به عنوان یک مفهوم اساسی برای شی گرا معرفی می کنیم و مفاهیم مورد استفاده در مثال لیست آخرین بخش را با جزئیات بیشتری کشف می کنیم.

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

شکل 3.1:   مدلی را از مسئله انتزاع ایجاد کنید.

img7 - اطلاق شی گرایی در برنامه نویسی

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

  • داده که آسیب دیده اند و
  • عملیات که شناسایی شده اند

توسط مشکل

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

  • نام،
  • اندازه،
  • تاریخ تولد،
  • شکل،
  • شماره اجتماعی ،
  • شماره اتاق،
  • رنگ مو،
  • سرگرمی ها

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

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

To sum up, abstraction is the structuring of a nebulous problem into well-defined entities by defining their data and operations. Consequently, these entities combine data and operations. They are not decoupled from each other.

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

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

شکل 3.2 یک ADT را نشان می دهد که از ساختار داده ها و عملیات انتزاعی تشکیل شده است. فقط عملیات از خارج قابل مشاهده هستند و رابط را تعریف می کنند.

شکل 3.2:   یک نوع داده انتزاعی (ADT).

img8 - اطلاق شی گرایی در برنامه نویسی

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

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

تعریف (نوع داده انتزاعی) نوع داده انتزاعی (ADT) است با خواص زیر مشخص می شود:

1یک نوع صادر می کند .2این مجموعه ای از عملیات را صادر می کند . این مجموعه رابط نامیده می شود .3عملکردهای رابط تنها و تنها سازوکار دسترسی به ساختار داده نوع است.4بدیهیات و پیش شرط ها دامنه کاربرد را از نوع تعریف می کنند.

با اولین ویژگی ایجاد بیش از یک نمونه از ADT به عنوان مثال با مثال کارمند امکان پذیر است. همچنین ممکن است مثال لیست 2 را بخاطر داشته باشید  . در نسخه اول ما لیستی را به عنوان ماژول پیاده سازی کرده ایم و فقط قادر به استفاده همزمان از یک لیست هستیم. نسخه دوم “handle” را به عنوان “لیست شی” معرفی می کند. با توجه به آنچه که اکنون آموخته ایم ، دسته همراه با عملیاتی که در ماژول لیست تعریف شده است ، یک لیست ADT را تعریف می کند :

1
وقتی از دستگیره استفاده می کنیم متغیر مربوطه را از نوع List تعریف می کنیم .
2
رابط به نمونه هایی از نوع List توسط فایل تعریف رابط تعریف می شود.
3
از آنجا که پرونده تعریف رابط شامل نمایش واقعی دسته نیست ، نمی توان آن را مستقیماً اصلاح کرد.
4
دامنه برنامه با معنای معنایی عملیات ارائه شده تعریف می شود. بدیهیات و پیش شرط ها شامل عباراتی مانند

  • “” یک لیست خالی یک لیست است. “
  • “بگذارید l = (d1 ، d2 ، d3 ، … ، dN) یک لیست باشد. سپس l.append (dM) منجر به l = (d1 ، d2 ، d3 ، … ، dN ، dM) می شود. “
  • “اولین عنصر لیست فقط در صورت خالی نبودن لیست قابل حذف است.”

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

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

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

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

  • هر دو قسمت در یک آرایه دو مقداری ذخیره می شوند که در آن مقدار اول قسمت واقعی را نشان می دهد و مقدار دوم قسمت خیالی عدد مختلط را نشان می دهد. اگر x نشان دهنده قسمت واقعی و y قسمت خیالی است ، می توانید به فکر دسترسی به آنها از طریق اشتراک آرایه باشید: x = c [0] و y = c [1].
  • هر دو قسمت در یک رکورد دو ارزش ذخیره می شوند. اگر نام عنصر قسمت واقعی r باشد و قسمت خیالی i باشد ، x و y را می توان با: x = c .r و y = c بدست آورد.i.

نکته 3 تعریف ADT می گوید برای هر دسترسی به ساختار داده باید عملیاتی تعریف شود. به نظر می رسد مثال های دسترسی فوق با این نیاز مغایرت دارد. آیا این واقعاً درست است؟

بیایید دوباره به دو امکان نمایش اعداد خیالی نگاه کنیم. بیایید به قسمت واقعی بچسبیم. در نسخه اول ، x برابر با c [0] است. در نسخه دوم ، x برابر c .r است. در هر دو مورد x برابر با “چیزی” است. این “چیزی” است که با ساختار داده واقعی مورد استفاده متفاوت است. اما در هر دو مورد ، عمل انجام شده “برابر” معنای یکسانی برای اعلام دارد X به به بخشی واقعی از عدد مختلط برابر ج : هر دو مورد اخبار تخصصی شبکه معانی است.

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

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

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

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

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

<span>    لیست <Apple> لیستOfApples؛
</span>

براکت های زاویه ای اکنون نوع داده ای را که نوع مختلفی از لیست ADT عمومی برای آن باید ایجاد شود ، در بر می گیرد. listOfApples همان رابط هر لیست دیگر را ارائه می دهد ، اما در نمونه هایی از نوع Apple کار می کند .

3.4 نمادگذاری
از آنجایی که ADT دیدی انتزاعی برای توصیف خصوصیات مجموعه موجودات ارائه می دهد ، استفاده از آنها از یک زبان برنامه نویسی خاص مستقل است. بنابراین ما در اینجا یک علامت گذاری معرفی می کنیم که از [ 3 ] به تصویب رسیده است . هر توصیف ADT از دو قسمت تشکیل شده است:

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

به عنوان نمونه توضیحات عدد صحیح ADT ارائه شده است. بگذارید k یک عبارت صحیح باشد:

عدد صحیح ADT است

داده ها

دنباله ای از ارقام به صورت اختیاری با علامت مثبت یا منفی پیشوند می شوند. ما به این عدد کامل امضا شده به عنوان N اشاره می کنیم .

عملیات

constructor
یک عدد صحیح جدید ایجاد می کند.
add(k)
یک عدد صحیح جدید ایجاد می کند که حاصل جمع N و k است .در نتیجه ، شرط بعد این عملیات جمع  =  N + k است . این را با دستوراتی که در زبان های برنامه نویسی استفاده می شود اشتباه نگیرید! آن است و نه یک معادله ریاضی که بازده “ درست “” برای هر مقدار مجموع ، N و K پس از افزودن انجام شده است.

زیر (k)
مشابه به اضافه ، این عملیات یک عدد صحیح جدید از تفاوت هر دو مقادیر صحیح ایجاد می کند. بنابراین پیش شرط این عملیات جمع  =  N – k است .

مجموعه (k)
N را به k تنظیم کنید . شرط بعدي اين عمل N  =  k است .

پایان

توضیحات بالا مشخصات مربوط به ADT Integer است . لطفا توجه داشته باشید که ما از کلماتی برای نام عملیاتی مانند “ʻadd” استفاده می کنیم. به جای آن می توانیم از علامت شهودی “+” استفاده کنیم ، اما این ممکن است منجر به سردرگمی شود: شما باید عمل “+” را از کاربرد ریاضی “+” در شرط پساب تشخیص دهید. نام این عمل فقط نحو است در حالی که معناشناسی توسط پیش شرط ها و شرط های پسین مرتبط توصیف می شود. با این حال ، همیشه ایده خوبی است که هر دو را برای سهولت در خواندن مشخصات ADT ترکیب کنید.

زبان های برنامه نویسی واقعی در انتخاب پیاده سازی دلخواه برای ADT آزاد هستند. به عنوان مثال ، آنها ممکن است عملیات اضافه شده را با عملگر infix “+” پیاده سازی کنند و منجر به یک نگاه بصری تر برای اضافه کردن عدد صحیح شود.

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

ADT با تأکید اصلی بر داده های درگیر ، ساختار آنها ، عملیات و همچنین بدیهیات و پیش شرط ها ، عملکرد را تعریف می کند. در نتیجه ، برنامه نویسی شی گرا “برنامه نویسی با ADT” است: ترکیب عملکرد ADT های مختلف برای حل یک مشکل. بنابراین موارد (اشیا) ADT (کلاس ها) به صورت پویا ایجاد ، تخریب و مورد استفاده قرار می گیرند.

3.6 تمرین

1
عدد صحیح ADT .

(آ)
چرا هیچ پیش شرطی برای عملیات add و sub وجود ندارد ؟
(ب)
بدیهی است که توصیف ADT از عدد صحیح ناقص است. روش های mul ، div و هر روش دیگری را اضافه کنید. با مشخص کردن شرایط قبل و بعد از آن ، تأثیرات آنها را توصیف کنید.

2
کسری ADT طراحی کنید که خصوصیات کسرها را توصیف کند.

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

3
خصوصیات انواع داده های انتزاعی را با کلمات خود توصیف کنید.
4
چرا ضروری است که برای تعریف نوع داده انتزاعی ، بدیهیات و پیش شرط ها ذکر شود؟
5
رابطه خود را با کلمات خود توصیف کنید

  • نوع داده های انتزاعی ،
  • نوع داده انتزاعی عمومی و نوع داده انتزاعی مربوطه ،
  • نمونه هایی از یک نوع داده انتزاعی عمومی.

4 مفهوم شی گرا

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
بخشهای قبلی برخی از مفاهیم “هدف گرا” را معرفی می کنند. با این حال ، آنها در یک محیط رویه ای یا به صورت کلامی اعمال شدند. در این بخش ، این مفاهیم را با جزئیات بیشتری بررسی کرده و به آنها اسامی می دهیم که در زبانهای برنامه نویسی شی گرا موجود استفاده می شود.

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

دوباره عدد صحیح ADT را در نظر بگیرید . زبان های برنامه نویسی مانند Pascal ، C ، Modula-2 و سایر برنامه ها از قبل پیاده سازی را برای آن ارائه می دهند. گاهی به آن int یا integer گفته می شود . هنگامی که متغیری از این نوع ایجاد کردید می توانید از عملیات ارائه شده آن استفاده کنید. به عنوان مثال ، می توانید دو عدد صحیح اضافه کنید:

<span>  int i، j، k؛ / * سه عدد صحیح تعریف کنید * / </span>
<span>
  i = 1؛ / * اختصاص 1 به عدد صحیح i * / </span><span>
  j = 2؛ / * اختصاص 2 به عدد صحیح j * / </span><span>
  k = i + j؛ / * جمع i و j را به k اختصاص دهید * /</span>

بیایید با قطعه کد بالا بازی کنیم و رابطه را با ADT Integer شرح دهیم . خط اول سه نمونه i ، j و k را از نوع Integer تعریف می کند . در نتیجه ، برای هر نمونه باید سازنده عملیات ویژه فراخوانی شود. در مثال ما ، این کار به صورت داخلی توسط کامپایلر انجام می شود. کامپایلر حافظه را برای نگه داشتن مقدار یک عدد صحیح ذخیره می کند و نام مربوطه را به آن متصل می کند. اگر به i مراجعه کنید ، در واقع به این قسمت حافظه اشاره می کنید که با تعریف i “ساخته شده” است . به صورت اختیاری ، ممکن است کامپایلرها مقدار اولیه حافظه را انتخاب کنند ، به عنوان مثال ممکن است آن را روی 0 (صفر) تنظیم کنند.

خط بعدی

<span>  من = 1؛
</span>

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

مجموعه ای را با آرگومان 1 در مثال Integer انجام دهید. این به شرح زیر نوشته شده است: i.set (1).

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

{پیش شرط: i = n در جایی که n یک عدد صحیح است}
i.set (1)
{پیش شرط: i = 1}
فراموش نکنید که ما در حال حاضر در مورد سطح ADT صحبت می کنیم! در نتیجه ، شرایط شرایط ریاضی هستند.

سطح دوم سطح پیاده سازی است ، جایی که نمایشی واقعی برای عملیات انتخاب می شود. در C علامت برابر “=” عملیات set () را اجرا می کند. با این حال ، در پاسکال نمایندگی زیر انتخاب شد:

<span>  من: = 1؛
</span>

در هر صورت ، مجموعه عملکرد ADT اجرا می شود.

بیایید کمی این مراحل را تحت فشار قرار دهیم و نگاهی به خط بیندازیم

<span>  k = i + j ؛
</span>

بدیهی است که “+” برای اجرای عملیات add انتخاب شده است . ما می تواند بخشی “ به عنوان خوانده شده من + J ” به عنوان “ اضافه ارزش J به ارزش من ”، در نتیجه در سطح ADT این نتایج در

{شرط: بگذارید i = n 1 و j = n 2 با n 1 ، n 2 عدد صحیح باشد}
i.add (j)
{پیش شرط: i = n 1 و j = n 2}
شرط پس زمینه تضمین می کند که i و j انجام نمی دهند ارزشهای آنها را تغییر دهید لطفا مشخصات add را بیاد بیاورید . این می گوید که یک عدد صحیح جدید ایجاد می شود که مقدار آن جمع است. در نتیجه ، ما باید سازوکاری برای دسترسی به این نمونه جدید فراهم کنیم. ما این کار را با مجموعه انجام می دهیمعملیاتی که بر روی k اعمال می شود :

{پیش شرط: اجازه دهید k = n در جایی که n یک عدد صحیح است}
k.set (i.add (j))
{پس شرط: k = i + j }
همانطور که مشاهده می کنید ، برخی از زبان های برنامه نویسی نمایشی را انتخاب می کنند که تقریبا برابر با فرمول ریاضی است در شرایط قبل و بعد استفاده می شود. این امر باعث می شود که گاهی مخلوط نکردن هر دو سطح دشوار باشد.

4.2 کلاس
کلاس واقعی است نمایندگی از ADT. بنابراین جزئیات پیاده سازی برای ساختار داده مورد استفاده و عملیات را فراهم می کند. ما با ADT Integer بازی می کنیم و کلاس خود را برای آن طراحی می کنیم:

<span>  class Integer { 
    features </span><span>
  : </span><span>
    int i Methods </span>
<span>
  : </span><span>
    setValue (int n) </span><span>Integer addValue (Integer j) </span><span>
  }</span>

در مثال بالا و همچنین در مثالهایی که دنبال می کنیم ، از علامت گذاری استفاده می کنیم که مخصوص زبان برنامه نویسی نیست. در این کلاس نشانه گذاری {…} تعریف کلاس را نشان می دهد. ویژگی های دو بخش در پرانتزهای حلقه ای محصور شده اند: و روش ها: که تعریف ساختار داده و عملکرد ADT مربوطه را تعریف می کند. باز هم ما دو سطح را با اصطلاحات مختلف از هم متمایز می کنیم: در سطح پیاده سازی ، ما از “ویژگی ها” صحبت می کنیم که عناصر ساختار داده ها در سطح ADT هستند. همین امر در مورد “روش” هایی که اجرای عملیات ADT هستند اعمال می شود.

در مثال ما ، ساختار داده فقط از یک عنصر تشکیل شده است: دنباله امضا شده از رقم. ویژگی مربوطه یک عدد صحیح معمولی از یک زبان برنامه نویسی استfoot - اطلاق شی گرایی در برنامه نویسی. We only define two methods setValue() and addValue() representing the two operations set and add.

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

4.3 شی
 مثال کارمندان فصل 3 را به یاد بیاورید  . ما در مورد نمونه هایی از کارمندان انتزاعی صحبت کرده ایم . این نمونه ها “نمونه هایی” واقعی از یک کارمند انتزاعی هستند ، از این رو ، آنها حاوی مقادیر واقعی برای نشان دادن یک کارمند خاص هستند. ما به این موارد اشیا می گوییم .

اشیا توسط یک نام قابل شناسایی هستند . بنابراین می توانید دو شی objects قابل تشخیص با مجموعه مقادیر یکسان داشته باشید. این شبیه زبان های برنامه نویسی “سنتی” است که می توانید داشته باشید ، مثلاً دو عدد صحیح i و j هر دو برابر “2” هستند. لطفاً برای استفاده از دو عدد صحیح به استفاده از “lasti” و “j” در جمله آخر توجه کنید. ما به مجموعه مقادیر در یک زمان خاص به عنوان حالت جسم اشاره می کنیم.

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

وضعیت شی با توجه به روشهایی که روی آن اعمال می شود تغییر می کند. ما به این توالی احتمالی تغییرات حالت به عنوان رفتار جسم اشاره می کنیم:

تعریف (رفتار) رفتار از یک شی توسط مجموعه ای از روش است که می تواند بر روی آن اعمال تعریف شده است.

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

4.4 پیام
 یک برنامه در حال اجرا مجموعه ای از اشیا where است که در آن اشیا are ایجاد ، تخریب و برهم کنش می شوند . این تعامل براساس پیامهایی است که از یک شی به شی another دیگر ارسال می شود و از گیرنده می خواهد روشی را روی خودش اعمال کند. برای اینکه شما از این ارتباط درک کنید ، بیایید به کلاس Integer که در بخش 4.2 ارائه شده  است ، برگردیم . در زبان برنامه نویسی شبه ما می توانیم اشیا new جدید ایجاد کنیم و روشها را بر روی آنها فراخوانی کنیم. به عنوان مثال ، ما می توانیم استفاده کنیم

<span>  عدد صحیح i؛ / * تعریف یک عدد صحیح جدید * / </span><span>
  i.setValue (1)؛ / * مقدار آن را روی 1 تنظیم کنید /</span>

برای بیان این واقعیت که شی صحیح i باید مقدار آن را روی 1 تنظیم کند. این پیام “اعمال متد setValue با آرگومان 1 بر روی خود” است. به شی i ارسال می شود . ما ارسال پیام با “” را یادداشت می کنیم. این علامت گذاری در C ++ نیز استفاده می شود. سایر زبانهای شی گرا ممکن است از علامت گذاری های دیگر استفاده کنند ، به عنوان مثال “- img9 - اطلاق شی گرایی در برنامه نویسی“.

ارسال پیام با استفاده از یک شی to برای استفاده از یک روش ، شبیه فراخوانی رویه ای در زبان های برنامه نویسی “سنتی” است. با این حال ، در شی گرا نمایی از اشیا autonom خودمختار وجود دارد که از طریق تبادل پیام با یکدیگر ارتباط برقرار می کنند. اشیا هنگام دریافت پیام با استفاده از روش هایی روی خود واکنش نشان می دهند. آنها همچنین ممکن است اجرای یک روش را انکار کنند ، به عنوان مثال اگر شی calling فراخوان مجاز به اجرای روش درخواستی نباشد.

در مثال ما ، پیام و روشی که باید پس از دریافت پیام اعمال شود دارای همان نام هستند: ما “setValue با آرگومان 1” را به شی i ارسال می کنیم که “setValue (1)” را اعمال می کند.

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

  • نام از این روش و
  • استدلال از روش.

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

تعریف (روش) یک روش با یک کلاس مرتبط است. یک شی a یک روش را به عنوان واکنش به دریافت پیام فراخوانی می کند.
4.5 خلاصه
مشاهده یک برنامه به عنوان مجموعه اشیا inte متقابل یک اصل اساسی در برنامه نویسی شی گرا است. اشیا in موجود در این مجموعه با دریافت پیام ها واکنش نشان می دهند و وضعیت آنها را با توجه به فراخوانی روش هایی تغییر می دهند كه باعث پیام های دیگر ارسال شده به اشیا other دیگر می شود. این در شکل 4.1 نشان داده شده است  .

شکل 4.1:   برنامه ای متشکل از چهار شی.

img10 - اطلاق شی گرایی در برنامه نویسی

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

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

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

1
صبر کنید ، تا یک کلید فشرده شود.
2
دریافت مقدار کلیدی
3
نوشتن مقدار کلیدی در موقعیت مکان نشانگر فعلی
4
موقعیت مکان نما را پیش ببرید

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

4.6 تمرین

1
کلاس

(آ)
چه چیزی یک کلاس را از ADT متمایز می کند؟
(ب)
یک کلاس برای مجموعه ADT طراحی کنید . چه نمایندگی هایی را برای عملیات ADT انتخاب می کنید؟ چرا؟

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

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

5 مفهوم شی گرا دیگر

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
در حالی که سخنرانی قبلی مفاهیم اساسی برنامه نویسی شی گرا را معرفی می کند ، این سخنرانی جزئیات بیشتری در مورد ایده شی گرا ارائه می دهد. این بخش عمدتا از [ 2 ] به تصویب رسیده است foot - اطلاق شی گرایی در برنامه نویسی.

5.1 روابط
در تمرین  3.6 . 5 شما در حال حاضر روابط بین انواع داده های انتزاعی و نمونه ها را بررسی کرده و آنها را با کلمات خود توصیف می کنید. بیایید در اینجا با جزئیات بیشتر برویم.

رابطه ای از نوع
در نظر بگیرید که باید یک برنامه نقاشی بنویسید. این برنامه امکان ترسیم اشیا various مختلف مانند نقاط ، دایره ها ، مستطیل ها ، مثلث ها و موارد دیگر را فراهم می کند. برای هر شی object شما یک تعریف کلاس ارائه می دهید. به عنوان مثال ، کلاس point فقط مختصات خود را مشخص می کند:

<span>  کلاس Point { </span><span>
  ویژگی ها: </span><span>
    int x ، </span>
<span>
  روش های y : </span><span>
    setX (int newX) </span><span>
    getX () </span><span>
    setY (int newY) getY </span><span>
    () </span><span>
  }</span>

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

<span>  class Circle { </span><span>
  ویژگی ها: </span><span>
    int x ، y ، 
  روش های </span><span>
        شعاع </span>
<span>: </span><span>
    setX (int newX) </span><span>
    getX () </span><span>
    setY (int newY) getY </span><span>
    () </span><span>
    setRadius (newRadius) </span><span>
    getRadius () </span><span>
  }</span>

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

  • هر دو کلاس دارای دو عنصر داده x و y هستند . در کلاس Point این عناصر موقعیت نقطه را توصیف می کنند ، در مورد کلاس Circle آنها مرکز دایره را توصیف می کنند. بنابراین ، x و y در هر دو کلاس معنی یکسانی دارند: آنها موقعیت شی مرتبط را با تعریف یک نقطه توصیف می کنند.
  • هر دو کلاس یک روش یکسان برای بدست آوردن و تنظیم مقدار دو عنصر داده x و y ارائه می دهند .
  • کلاس دایره “ اضافه می کند ” یک عنصر داده جدید شعاع و روش های دسترسی مربوطه.

با دانستن خصوصیات کلاس Point می توان یک دایره را به عنوان یک نقطه به علاوه شعاع و روش های دستیابی به آن را توصیف کرد. بنابراین ، یک دایره “نوعی” از نقطه است. با این حال ، یک دایره تا حدودی “تخصصی” تر است. ما این را به صورت گرافیکی نشان می دهیم همانطور که در شکل 5.1 نشان داده شده است  .

شکل 5.1:   تصویر “ یک نوع از ” رابطه.

img11 - اطلاق شی گرایی در برنامه نویسی

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

رابطه ای است
رابطه قبلی در سطح کلاس برای توصیف روابط بین دو کلاس مشابه استفاده می شود. اگر اشیا objects از دو کلاس را ایجاد کنیم ، از رابطه آنها به عنوان رابطه “a-a” یاد می کنیم.

از آنجا که کلاس Circle نوعی از کلاس Point است ، یک نمونه از Circle ، مثلاً acircle ، یک نقطه استfoot - اطلاق شی گرایی در برنامه نویسی . در نتیجه ، هر دایره مانند یک نقطه رفتار می کند. به عنوان مثال ، می توانید با تغییر مقدار x ، نقاط را در جهت x حرکت دهید . به همین ترتیب ، با تغییر مقدار x آنها ، حلقه ها را در این جهت حرکت می دهید .

شکل  5.2 این رابطه را نشان می دهد. در این شکل و شکل های زیر ، اشیا با استفاده از مستطیل هایی با گوشه های گرد ترسیم شده اند. نام آنها فقط از حروف کوچک تشکیل شده است.

شکل 5.2:   تصویر “ است-یک رابطه.

img12 - اطلاق شی گرایی در برنامه نویسی

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

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

<span>  class Logo { </span><span>
  ویژگی ها: </span><span>
    دایره دایره 
  روش های </span><span>
    مثلث مثلث </span>
<span>: </span><span>
    تنظیم (نقطه کجا) </span><span>
  }</span>

ما این را در شکل 5.3 نشان می دهیم  .

شکل 5.3:   تصویر رابطه “بخشی از”

img13 - اطلاق شی گرایی در برنامه نویسی

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

شکل 5.4:   تصویر رابطه “دارای-یک” است.

img14 - اطلاق شی گرایی در برنامه نویسی

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

<span>  class Circle از نقطه {به ارث می برد</span><span> 
  ویژگیها: 
  روشهای </span><span>
    شعاع int </span>
<span>: </span><span>
    setRadius (int newRadius) </span><span>
    getRadius () </span><span>
  } به</span>

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

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

<span>  دایره </span><span>
  مدور acircle.setX (1) / * به ارث برده از نقطه * / </span><span>
  acircle.setY (2)</span><span> 
  acircle.setRadius (3) / * اضافه شده توسط Circle * /</span>

“آیا من” نیز بیان می کند ، ما می توانیم از یک دایره در هر جایی که یک نقطه انتظار می رود استفاده کنیم. به عنوان مثال ، می توانید یک تابع یا روش بنویسید ، مثلاً حرکت () ، که باید یک نقطه را در x حرکت دهد جهت حرکت دهد:

<span>  move (Point apoint، int deltax) { </span><span>
    apoint.setX (apoint.getX () + deltax)</span><span> 
  }</span>

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

<span>  دایره دایره </span><span>
    ... </span><span>
  حرکت ( دایره ، 10) / * حرکت دایره با حرکت * / </span><span>
                      / * نقطه مرکزی آن * /</span>

بیایید سعی کنیم اصطلاح “ارث” را رسمی کنیم:

تعریف (ارث) وراثت مکانیسم که اجازه می دهد یک کلاس A به خواص به ارث می برند از یک کلاس ب ما می گوییم “ به ارث برده از B ” است. بنابراین اشیا of کلاس A بدون نیاز به تعریف مجدد به ویژگی ها و روش های کلاس B دسترسی دارند. تعریف زیر دو اصطلاح را تعریف می کند که ما می توانیم هنگام استفاده از کلاسهای ارثی به کلاسهای شرکت کننده اشاره کنیم.

تعریف (Superclass / Subclass) اگر کلاس A از کلاس B ارث برده شود ، B را ابر کلاس A می نامند. A را کلاس B می نامند . اشیاء از یک زیر کلاس می توان مورد استفاده که در آن اشیاء از فوق مربوط انتظار می رود. این به دلیل این واقعیت است که اشیا the زیر کلاس همان رفتار اشیا را دارند.

در ادبیات ممکن است اصطلاحات دیگری برای “ابر کلاس” و “زیر کلاس” پیدا کنید. کلاس های فوق کلاس را والدین نیز می نامند . به زیر کلاس ها ممکن است کلاس های کودک یا فقط گفته شود کلاسهای مشتق شده .

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

یک طرح رسم معمول استفاده از خطوط پیکان برای نشان دادن رابطه وراثت بین دو کلاس یا شی است. در مثالهای ما از “herhherits-from” استفاده کرده ایم. در نتیجه ، خط پیکان دار از زیر کلاس به سمت فوق کلاس شروع می شود همانطور که در شکل 5.5 نشان داده شده است  .

شکل 5.5:   یک نمودار ارثی ساده.

img15 - اطلاق شی گرایی در برنامه نویسی

In the literature you also find illustrations where the arrowed lines are used just the other way around. The direction in which the arrowed line is used, depends on how the corresponding author has decided to understand it.

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

در بخشهای زیر یک خط پیکان دار علامت گذاری نشده نشانگر “ارث بردن از” است.

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

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

به عنوان نمونه برنامه نقاشی ما را دوباره در نظر بگیرید. فرض کنید ما در حال حاضر یک کلاس String داریم که امکان مدیریت راحت متن را فراهم می کند. به عنوان مثال ، ممکن است روشی برای الحاق متن دیگر داشته باشد. در برنامه ما می خواهیم از این کلاس برای افزودن متن به اشیا drawing نقاشی احتمالی استفاده کنیم. خوب است که از روال های موجود مانند move () نیز برای انتقال متن به اطراف استفاده کنید. در نتیجه ، منطقی است که به یک متن قابل نقاشی اجازه دهید نکته ای داشته باشد که موقعیت آن را در منطقه نقاشی مشخص کند. بنابراین ما یک کلاس جدید DrawableString استخراج می کنیم که ویژگی ها را از Point و String به ارث می برد همانطور که در شکل 5.6 نشان داده شده است  .

شکل 5.6:   یک رشته قابل ترسیم استخراج کنید که خصوصیات Point و String را به ارث می برد .

img16 - اطلاق شی گرایی در برنامه نویسی

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

<span>  کلاس DrawableString از Point ، String 
              به ارث می رسد { </span><span>
  ویژگی ها: </span><span>/ * همه از ابر کلاس ها به ارث برده اند * / </span>
<span>
  متدها: </span><span>
              / * همه از ابر کلاس ها به ارث برده اند * / </span><span>
  }</span>

ما می توانیم از اشیا of کلاس DrawableString مانند نقاط و رشته ها استفاده کنیم. از آنجا که یک drawablestring یک است نقطه ما می توانیم آنها را در اطراف حرکت

<span>  DrawableString dstring </span><span>
  ... </span><span>
  move (dstring، 10) </span><span>
  ...</span>

از آنجا که این یک رشته است ، می توانیم متن دیگری را به آنها اضافه کنیم:

<span>  dstring.append ("روباه قهوه ای قرمز ...")
</span>

اکنون زمان تعریف وراثت چندگانه فرا رسیده است:

تعریف (وراثت چندگانه) اگر کلاس A از بیش از یک کلاس ارث برسد ، یعنی. A از B1 ، B2 ، … ، Bn به ارث می برد ، ما در مورد وراثت چندگانه صحبت می کنیم . اگر حداقل دو ابر کلاس آن خصوصیاتی را با یک نام تعریف کنند ، این ممکن است تعارضات نامگذاری را در A ایجاد کند.

تعریف فوق تعارضات نامگذاری را نشان می دهد که اگر بیش از یک ابر کلاس از یک زیر کلاس از همان نام برای ویژگی یا روش استفاده کند. برای مثال ، فرض کنید کلاس String یک متد setX () را تعریف می کند که رشته را به دنباله ای از کاراکترهای “X” تنظیم می کند foot - اطلاق شی گرایی در برنامه نویسی. این س arال مطرح می شود که چه چیزی باید توسط DrawableString به ارث برسد ؟ نسخه Point ، String یا هیچ کدام از آنها؟

این درگیری ها حداقل به دو روش قابل حل است:

  • ترتیب ارائه ابر کلاس ها مشخص می کند که کدام یک از ویژگی ها با نام ایجاد کننده تضاد قابل دسترسی خواهد بود. دیگران “پنهان” می شوند.
  • زیر کلاس باید تعارض را با ارائه یک ویژگی با نام و تعریف نحوه استفاده از موارد فوق کلاس خود حل کند.

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

اگر ضرب کلاس D از ابر کلاسهای B و C که خود از یک ابر کلاس A مشتق شده اند ، نوع خاصی از تعارض نامگذاری وارد می شود . این منجر به نمودار ارث می شود همانطور که در شکل 5.7 نشان داده شده است  .

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

img17 - اطلاق شی گرایی در برنامه نویسی

این س arال مطرح می شود که کلاس D واقعاً از ابر کلاسهای B و C چه خصوصیاتی را به ارث می برد . برخی از زبانهای برنامه نویسی موجود با استخراج D با این نمودار ویژه وراثت را حل می کنند

  • خواص A plus
  • خصوصیات B و بدون خصوصیاتی که از A به ارث برده اند .

در نتیجه ، D نمی تواند تعارض نامگذاری را با نام کلاس A معرفی کند . با این حال ، اگر B و C خصوصیاتی با همان نام اضافه کنند ، D با تضاد نامگذاری روبرو می شود.

راه حل ممکن دیگر این است که D از هر دو مسیر وراثت ارث می برد. در این راه حل ، D دارای دو نسخه از خصوصیات A است : یکی توسط B به ارث می رسد و دیگری توسط C .

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

5.4 کلاسهای چکیده
با وراثت ، ما می توانیم یک زیر کلاس را مجبور کنیم که همان خصوصیات فوق کلاس آنها را ارائه دهد. در نتیجه ، اشیا of یک زیر کلاس مانند اشیای فوق کلاس خود رفتار می کنند.

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

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

<span>  کلاس انتزاعی DrawableObject { </span><span>
  ویژگی ها: </span>
<span>
  روش ها: </span><span>
    چاپ () </span><span>
  }</span>

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

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

<span>  class Point از DrawableObject به ارث می برد { </span><span>
  ویژگی ها: </span><span>
    int x ، </span>
<span>
  روش های y : </span><span>
    setX (int newX) </span><span>
    getX () </span><span>
    setY (int newY) getY </span><span>
    () </span><span>
    print () / * تعریف مجدد نقطه * / </span><span>
  }</span>

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

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

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

5.5 تمرین

1
وراثت. مثال برنامه نقاشی را دوباره در نظر بگیرید.

(آ)
با ارث بردن از کلاس Point کلاس Rectangle را تعریف کنید . نقطه باید گوشه سمت چپ بالای مستطیل را نشان دهد. صفات کلاس شما چیست؟ چه روشهای اضافی را معرفی می کنید؟

(ب)
تمام نمونه های فعلی بر اساس نمای دو بعدی ساخته شده اند. اکنون می خواهید اشیا 3D سه بعدی مانند کره ، مکعب یا مکعب را معرفی کنید. با استفاده از کلاس 3D-Point کلاس Sphere را طراحی کنید . نقش نقطه را در یک کره مشخص کنید. از چه رابطه ای بین کلاس Point و 3D-Point استفاده می کنید ؟

(ج)
حرکت () برای اشیا 3D سه بعدی چه عملکردی دارد ؟ تا می توانید دقیق باشید.

(د)
نمودار وراثت را از جمله کلاس های زیر DrawableObject ، Point ، Circle ، Rectangle ، 3D-Point و Sphere ترسیم کنید .

(ه)
به نمودار وراثت شکل 5.8 نگاهی بیندازید  .

شکل 5.8:   نمودار ارثی جایگزین برای کلاس Sphere .

img18 - اطلاق شی گرایی در برنامه نویسی

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

<span>  class Sphere از دایره به ارث می برد { </span><span>
  ویژگی ها: </span><span>
    int z / * اضافه کردن بعد سوم * / </span>
<span>
  روش ها: </span><span>
    setZ (int newZ) </span><span>
    getZ () </span><span>
  }</span>

دلایل مزایا و معایب این گزینه را ذکر کنید.

2
ارث چندگانه. نمودار وراثت نشان داده شده در شکل  5.9 را با نمودار  5.7 مقایسه کنید . در اینجا ، ما نشان می دهیم که B و C هر یک نسخه خود را از A دارند .

شکل 5.9:   تصویر معنایی وراثت چندگانه دوم.

img19 - اطلاق شی گرایی در برنامه نویسی

چه درگیری های نامگذاری می تواند رخ دهد؟ سعی کنید موارد را با بازی در کلاسهای مثال ساده تعریف کنید.

6 مفهوم شی گرا حتی بیشتر

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
ما با ارائه مقدمه ای کوتاه از الزام آور استاتیک در مقابل دینامیک ، به تور خود در جهان مفاهیم شی گرا ادامه می دهیم. با این کار ، ما می توانیم چند شکلی را به عنوان مکانیزمی معرفی کنیم که به اشیا اجازه می دهد بفهمند در زمان اجرا چه کاری باید انجام دهند. اما ابتدا ، در اینجا یک بررسی اجمالی در مورد انواع عمومی وجود دارد.

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

در آن زمان ، وقتی یک تعریف کلاس را یادداشت می کنیم ، باید بتوانیم بگوییم که این کلاس باید یک نوع عمومی تعریف کند. با این حال ، ما نمی دانیم کلاس با کدام نوع استفاده می شود. در نتیجه ، ما باید بتوانیم کلاس را با کمک یک “مکان نگهدارنده” که به آن ارجاع می دهیم ، تعریف کنیم ، مثل اینکه نوع کار کلاس است. بنابراین ، تعریف کلاس الگویی از یک کلاس واقعی را در اختیار ما قرار می دهد . تعریف کلاس واقعی هنگامی ایجاد می شود که یک شی particular خاص را اعلام کنیم. بیایید این را با مثال زیر نشان دهیم. فرض کنید ، شما می خواهید یک کلاس لیست تعریف کنید که باید از نوع عمومی باشد. بنابراین ، می توان اشیا list لیست شده را برای سیب ، اتومبیل یا هر نوع دیگر اعلام کرد.

<span>  کلاس الگو فهرست برای T { </span><span>
    ویژگی ها: </span><span>
      ... / * ساختار داده مورد نیاز برای پیاده سازی * / </span><span>
                / * لیست * / </span>
<span>
    روش ها: </span><span>
      ضمیمه (عنصر </span><span>
      T ) T getFirst () </span><span>
      T getNext () </span><span>
      bool more () </span><span>
  }</span>

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

<span>  لیست Apple appleList </span><span>
  Apple anApple، </span><span>
        دیگری </span><span>
  Apple appleList.append (دیگری </span><span>
  Apple ) appleList.append (anApple)</span>

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

<span>  class list { </span><span>
    ویژگی ها: </span><span>
      ... / * ساختار داده مورد نیاز برای پیاده سازی * / </span><span>
                / * لیست * / </span>
<span>
    روش ها: </span><span>
      ضمیمه (عنصر </span><span>
      سیب ) Apple getFirst () </span><span>
      Apple getNext () </span><span>
      bool more () </span><span>
  }</span>

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

<span>  لیست برای Apple appleList </span><span>
  لیست برای گلابی گلابی لیست </span><span>
  ...</span>

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

<span>  لیست برای لیست لیست سیب </span><span>
  لیست لیست دیگر برای اپل</span>

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

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

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

6.2 اتصال استاتیک و پویا
در زبان های برنامه نویسی به شدت تایپ شده معمولاً باید متغیرها را قبل از استفاده اعلام کنید. این همچنین به معنی تعریف متغیر است که در آن کامپایلر فضای متغیر را ذخیره می کند. به عنوان مثال ، در پاسکال عبارتی مانند

<span>  var i: عدد صحیح؛
</span>

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

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

<span>  var i: عدد صحیح؛ </span><span>
  ... </span><span>
  i: = 'رشته'؛</span>

ما این نوع خاص از اتصال را “استاتیک” می نامیم زیرا در زمان کامپایل ثابت می شود.

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

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

<span>  ... / * عدم نمایش i * / </span><span>
  i: = 123 / * ایجاد i به عنوان یک عدد صحیح * /</span>

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

تعریف (اتصال پویا) اگر نوع T یک متغیر با نام N به طور ضمنی با محتوای آن مرتبط باشد ، می گوییم که N به صورت پویا به T متصل می شود .

هر دو اتصال در زمان اتصال نوع به متغیر متفاوت هستند. مثال زیر را در نظر بگیرید که فقط با اتصال دینامیکی امکان پذیر است:

<span>  اگر شرط شرطی () == درست باشد پس </span><span>
    n: = 123 </span><span>
  دیگری </span><span>
    n: = 'abc' </span><span>
  endif</span>

نوع n بعد از دستور if به ارزیابی شرایط شرطی بستگی دارد () . اگر TRUE باشد ، n از نوع عدد صحیح است در حالی که در حالت دیگر از نوع رشته است.

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

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

<span>  v: = 123 / * v عدد صحیح است * / </span><span>
  ... / * از v به عنوان عدد صحیح استفاده کنید * / </span><span>
  v: = 'abc' / * v "سوئیچ" به رشته * / </span><span>
  ... / * از v به عنوان رشته استفاده کنید * /</span>

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

<span>  بولی ISNULL (اعضای هیات من) { </span><span>
    اگر (من == 0) پس از آن </span><span>
      بازگشت واقعی </span><span>
    دیگری </span><span>
      بازگشت کاذب </span><span>
    ENDIF </span><span>
  }</span>

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

<span>  boolean isNull (واقعی r) { </span><span>
    if (r <0.01 و r> -0.99) سپس </span><span>
      TRUE </span><span>
    else را </span><span>
      برگردانید FALSE </span><span>
    endif </span><span>
  }</span>

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

  • نام تابع (یا متد) و
  • انواع از آن لیست پارامتر .

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

<span>  var i: عدد صحیح </span><span>
  var r: real </span>
<span>
  i = 0 </span><span>
  r = 0.0 </span>
<span>
  ... </span>
<span>
  if (isNull (i)) سپس ... / * استفاده از isNull (int) * / </span><span>
  ... </span><span>
  if (isNull (r)) سپس .. / / استفاده از isNull است (واقعی) * /</span>

تعریف ( چند شکلی (2)) اگر تابعی (یا روش) با ترکیبی از تعریف شود

  • نام آن و
  • لیست انواع پارامترهای آن

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

آخرین نوع چندشکلی به یک جسم اجازه می دهد تا روش های صحیحی را انتخاب کند. تابع move () را مجدداً در نظر بگیرید که یک شی از کلاس Point را به عنوان آرگومان خود در نظر می گیرد. ما این تابع را با هر شی of از کلاسهای مشتق شده استفاده کرده ایم ، زیرا رابطه a-a برقرار است.

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

<span>  نمایش (DrawableObject o) { </span><span>
    ... </span><span>
    o.print () </span><span>
    ... </span><span>
  }</span>

We would like to use this function with objects of classes derived from DrawableObject:

<span>  دایره 
  دائره </span><span>
  Point </span><span>apoint مستطیل </span>
<span>
  صفحه مستطیل مستطیل (apoint ) / * باید apoint.print () * / </span><span>
  display (circle) / * باید acircle.print را فراخوانی کند () * / </span><span>
  display (arectangle) / * باید فراخوانی arectangle.print () * /</span>

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

<span>  کلاس پایه {</span><span>
  ویژگی های:</span>
<span>
  مواد و روش ها:</span><span>
    foo مجازی ()</span><span>
    بار()</span><span>
  }</span>
<span>
  class مشتق شده از Base {</span><span>
  ویژگی های:</span>
<span>
  مواد و روش ها:</span><span>
    foo مجازی ()</span><span>
    بار()</span><span>
  }</span>
<span>
  نسخه ی نمایشی (پایه o) {</span><span>
    o.foo ()</span><span>
    o.bar ()</span><span>
  }</span>
<span>
  پایه پایه</span><span>
  مشتق شده</span>
<span>
  نسخه ی نمایشی (abase)</span><span>
  نسخه ی نمایشی (تبلیغ شده)</span>

در این مثال دو کلاس Base و Derived را تعریف می کنیم . هر کلاس دو روش foo () و bar () را تعریف می کند . روش اول بصورت مجازی تعریف شده است . این بدان معنی است که در صورت فراخوانی این روش ، تعریف آن باید توسط محتوای شی ارزیابی شود.

سپس یک تابع demo () تعریف می کنیم که یک شی Base را به عنوان آرگومان خود در نظر می گیرد. در نتیجه ، ما می توانیم از این تابع با اشیای کلاس مشتق شده به عنوان رابطه is-a استفاده کنیم. ما این تابع را به ترتیب با یک شی ase Base و یک شی ived مشتق شده فراخوانی می کنیم .

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

<span>  foo () Base نامیده می شود.</span><span>
  نوار () Base نامیده می شود.</span><span>
  foo () از مشتق شده به نام.</span><span>
  نوار () Base نامیده می شود.</span>

چرا اینطور است؟ بذار ببینیم چه اتفاقی میافتد. اولین فراخوان برای نمایش () از یک شی Base استفاده می کند. بنابراین ، آرگومان تابع با یک شی کلاس Base “پر” می شود . هنگامی که زمان به روش فراخوان آن است که foo () قابلیت های آن واقعی است بر اساس محتوای کنونی مربوط به شی انتخاب O . این بار ، یک شی Base است. در نتیجه ، foo () همانطور که در کلاس Base تعریف شده است فراخوانی می شود.

تماس به نوار () است نه به این وضوح محتوا. به عنوان مجازی علامت گذاری نشده است . در نتیجه ، bar () در محدوده کلاس Base فراخوانی می شود .

فراخوان دوم برای نمایش () یک شی Der مشتق شده را به عنوان استدلال خود در نظر می گیرد. بنابراین ، آرگومان o با یک شی Der مشتق شده پر می شود. با این حال ، o فقط نمایانگر قسمت Base از شی provided ارائه شده است .

اکنون ، فراخوان foo () با بررسی محتوای o ارزیابی می شود ، از این رو در محدوده Derived فراخوانی می شود . از طرف دیگر ، نوار () هنوز در محدوده Base ارزیابی می شود .

تعریف (چندشکلی (3)) اشیا of ابر کلاس ها را می توان با اشیا of از زیر کلاس آنها پر کرد. اپراتورها و روش های کلاسهای فرعی را می توان تعریف کرد تا در دو زمینه ارزیابی شود:

1براساس نوع جسم ، منجر به ارزیابی در محدوده ابر کلاس می شود.2براساس محتوای شی ، منجر به ارزیابی در محدوده زیر کلاس موجود است.نوع دوم چندشکلی نام دارد .
7 مقدمه ای بر C ++

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
این بخش اولین قسمت از مقدمه C ++ است. در اینجا ما به C که C ++ از آن استفاده شده تمرکز می کنیم. C ++ زبان برنامه نویسی C را با تایپ قوی ، برخی ویژگی ها و – مهمتر از همه – مفاهیم شی گرا گسترش می دهد.

7.1 زبان برنامه نویسی C
C که در اواخر دهه 1970 توسعه یافت ، به دلیل توسعه U NIX که تقریباً به طور کامل به این زبان نوشته شده بود ، موفقیت بزرگی کسب کرد [ 4 ]. برخلاف سایر زبانهای سطح بالا ، C توسط برنامه نویسان برای برنامه نویسان نوشته شده است. بنابراین بعضی اوقات ، مثلاً موارد عجیب و غریب را اجازه می دهد که در زبانهای دیگر مانند پاسکال به دلیل تأثیر بد آن بر سبک برنامه نویسی ، ممنوع است. به هر حال ، C با برخی از رشته ها استفاده می شود ، زبان C به خوبی زبان های دیگر است.

نظر در C در / *  …  * / ضمیمه شده است . نظرات نمی توانند تو در تو قرار بگیرند.

7.1.1 انواع داده ها
جدول  7.1 انواع داده داخلی C را توصیف می کند. اندازه مشخص شده بر روی بایت بر روی یک کامپیوتر 386 با سیستم عامل Linux 1.2.13 اندازه گیری می شود. دامنه ارائه شده بر اساس مقدار Size است. می توانید اطلاعات مربوط به اندازه یک نوع داده را با عملگر sizeof بدست آورید.

جدول 7.1:   انواع داخلی

img20 - اطلاق شی گرایی در برنامه نویسی

متغیرهای این نوع به سادگی با مقدمه نام با نوع تعریف می شوند:

<span>  int an_int؛</span><span>
  شناور a_float؛</span><span>
  طولانی طولانی یک_بسیار_ طولانی_تر؛</span>

با ساختار می توانید چندین نوع مختلف را با هم ترکیب کنید. در زبانهای دیگر این کار را بعضاً رکورد می نامند :

<span>  ساختار تاریخ_ها {</span><span>
    int روز ، ماه ، سال ؛</span><span>
  تاریخ</span>

تعریف فوق از aDate همچنین اعلان ساختاری به نام date_s است . ما می توانیم متغیرهای دیگری از این نوع را با ارجاع ساختار به نام تعریف کنیم:

<span>  ساختار date_s anotherDate؛
</span>

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

<span>  ساختار زمان_ها {</span><span>
    int ساعت ، دقیقه ، ثانیه</span><span>
  }؛</span>

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

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

7.1.2 بیانیه ها
C تمام عبارات کنترل جریان معمول را تعریف می کند. عبارات با یک نقطه ویرگول “؛” خاتمه می یابند. ما می توانیم چندین جمله را با قرار دادن آنها در براکت های حلقه ای در بلوک ها گروه بندی کنیم. در داخل هر بلوک ، می توان متغیرهای جدیدی را تعریف کرد:

<span>  {</span><span>
    بین من / * تعریف جهانی i * /</span><span>
    من = 1؛ / * من مقدار 0 را تعیین می کنم * /</span><span>
    {/ * شروع بلوک جدید * /</span><span>
      بین من / * تعریف محلی i * /</span><span>
      من = 2؛ / * مقدار آن را روی 2 تنظیم کنید /</span><span>
    } / * بستن بلوک * /</span><span>
    / * اینجا من دوباره 1 بلوک خارجی هستم * /</span><span>
  }</span>

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

جدول 7.2:   اظهارات.

img21 - اطلاق شی گرایی در برنامه نویسی

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

<span>  {</span><span>
    int ix ، جمع ؛</span><span>
    جمع = 0؛</span><span>
    ix = 0 ؛ /* مقداردهی اولیه */</span><span>
    while (ix <10) {/ * شرط * /</span><span>
      من = من + 1 ؛</span><span>
      ix = ix + 1 ؛ /* گام */</span><span>
    }</span><span>
  }</span>
<span>
  {</span><span>
    int ix ، جمع ؛</span><span>
    جمع = 0؛</span><span>
    برای (ix = 0؛ ix <10؛ ix = ix + 1)</span><span>
      من = من + 1 ؛</span><span>
  }</span>

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

7.1.3 عبارات و عملگرها
در C تقریباً همه چیز یک عبارت است. به عنوان مثال ، عبارت انتساب “=” مقدار عملوند سمت راست خود را برمی گرداند. به عنوان یک “اثر جانبی” همچنین مقدار عملوند سمت چپ را تعیین می کند. بدین ترتیب،

<span>  ix = 12 ؛
</span>

مقدار ix را 12 قرار می دهد (با فرض اینکه ix نوع مناسبی دارد). اکنون که انتساب نیز بیان است ، می توانیم چندین مورد را با هم ترکیب کنیم. مثلا:

<span>  kx = jx = ix = 12 ؛
</span>

چه اتفاقی می افتد؟ تخصیص اول مقدار kx سمت راست آن را اختصاص می دهد . این مقدار انتساب به jx است . اما این مقدار انتساب به ix است . مقدار این مورد آخر 12 است که به jx بازمی گردد که به kx بازمی گردد . بنابراین ما بیان کردیم

<span>  ix = 12 ؛</span><span>
  jx = 12؛</span><span>
  kx = 12 ؛</span>

در یک خط

حقیقت در C به شرح زیر تعریف شده است. مقدار 0 (صفر) مخفف FALSE است. هر مقدار دیگری درست است. به عنوان مثال ، تابع استاندارد strcmp () دو رشته را به عنوان آرگومان در نظر می گیرد و -1 را برمی گرداند اگر اولی پایین تر از دوم باشد ، 0 اگر برابر باشند 0 و اگر بزرگتر اول باشد 1 را برمی گرداند. برای مقایسه اینکه دو رشته str1 و str2 برابر هستند ، اگر ساختار زیر را مشاهده کنید :

<span>  if (! strcmp (str1، str2)) {</span><span>
    / * str1 برابر است با str2 * /</span><span>
  }</span><span>
  دیگر {</span><span>
    / * str1 با str2 برابر نیست * /</span><span>
  }</span>

علامت تعجب نشان دهنده NOT بولی است. بنابراین این عبارت درصورت TRUE ارزیابی می شود که strcmp () 0 را برگرداند .

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

جدول 7.3:   اپراتورها.

img22 - اطلاق شی گرایی در برنامه نویسی

بیشتر این اپراتورها از قبل برای شما شناخته شده اند. با این حال ، برخی نیاز به توضیح بیشتری دارند. اول از همه توجه کنید که عملگرهای باینری باینری & ، ^ و اولویت کمتری نسبت به عملگرهای برابری == و ! = دارند . در نتیجه ، اگر می خواهید الگوهای بیت را مانند گذشته بررسی کنید

<span>  if ((الگو و MASK) == MASK) {</span><span>
    ...</span><span>
  }</span>

شما باید عملیات باینری را درون پرانتز قرار دهید foot - اطلاق شی گرایی در برنامه نویسی.

عملگرهای افزایش ++ و img23 - اطلاق شی گرایی در برنامه نویسیبا مثال زیر قابل توضیح است. اگر توالی دستور زیر را داشته باشید

<span>  a = a + 1 ؛</span><span>
  b = a ؛</span>

می توانید از اپراتور پیش هزینه استفاده کنید

<span>  b = ++ a؛
</span>

به همین ترتیب ، اگر ترتیب زیر را داشته باشید:

<span>  b = a ؛</span><span>
  a = a + 1 ؛</span>

می توانید از اپراتور postincrement استفاده کنید

<span>  b = a ++ ؛
</span>

بنابراین ، اپراتور پیش مخرج ابتدا متغیر مربوط به خود را افزایش می دهد و سپس مقدار جدید را برمی گرداند ، در حالی که اپراتور postincrement ابتدا مقدار را برمی گرداند و سپس متغیر خود را افزایش می دهد. همین قوانین برای اپراتور قبل و بعد از دکوراسیون اعمال می شود img23 - اطلاق شی گرایی در برنامه نویسی.

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

<span>  a [i] = من ++ ؛
</span>

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

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

<span>  اگر (a> b) </span><span>
    حداکثر = a؛</span><span>
  دیگر</span><span>
    حداکثر = ب</span>

این نوع دستورات اگر کوتاهتر باشند می توان نوشت

<span>  حداکثر = (a> b)؟ a: b؛
</span>

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

<span>  expr1 = (expr1) روشن (expr2)
</span>

مثلا

<span>  i = i * (j + 1) ؛
</span>

در این واگذاری ها مقدار چپ نیز در سمت راست نشان داده می شود. با استفاده از گفتار غیررسمی می توانیم این عبارت را به صورت ” تنظیم مقدار i بر روی مقدار فعلی i ضرب در مجموع مقدار j و 1 ” بیان کنیم. با استفاده از روشی طبیعی تر ، ترجیح می دهیم ” ضرب من با مجموع مقدار j و 1 “. C به ما این امکان را می دهد که این نوع تکالیف را به اختصار

<span>  i * = j + 1 ؛
</span>

تقریباً با همه عملگرهای باینری می توانیم این کار را انجام دهیم. توجه داشته باشید که انتساب عملگر فوق واقعاً فرم طولانی را اجرا می کند اگرچه ” j + 1″ در پرانتز نیست.

آخرین اپراتور unusal عملگر کاما است ، . بهتر است با یک مثال توضیح داده شود:

<span>  من = 0؛</span><span>
  j = (i + = 1، i + = 2، i + 3)؛</span>

این عملگر آرگومانهای خود را می گیرد و آنها را از چپ به راست ارزیابی می کند و مقدار درست ترین عبارت را برمی گرداند. بنابراین ، در مثال بالا ، عملگر ابتدا ” i  + = 1″ را ارزیابی می کند که به عنوان یک اثر جانبی ، مقدار i را افزایش می دهد . سپس عبارت بعدی ” i  + = 2″ ارزیابی می شود که 2 به i اضافه می کند و منجر به مقدار 3 می شود. عبارت سوم ارزیابی می شود و مقدار آن به عنوان نتیجه عملگر برمی گردد. بنابراین ، j اختصاص داده شده است 6.

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

<span>  ماتریس int [10] [5]؛ // ماتریس 2 تاریک</span><span>
  بین من</span>
<span>
  ...</span><span>
  i = ماتریس [1،2] ؛ // کار نخواهیم کرد !!</span><span>
  i = ماتریس [1] [2] ؛ // خوب</span>

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

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

<span>  ix = 12 ؛</span><span>
  jx = 12؛</span><span>
  kx = 12 ؛</span>

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

<span>  ix = 1 ؛</span><span>
  4711؛</span><span>
  jx = 2؛</span>

اما بیایید این چیزهای عجیب را فراموش کنیم. بیایید به چیز مفیدتری برگردیم. بیایید در مورد توابع صحبت کنیم .

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

توابع مشابه متغیرها اعلام می شوند ، اما آرگومان های خود را در پرانتز قرار می دهند (حتی اگر هیچ آرگومان وجود نداشته باشد ، باید پرانتز مشخص شود):

<span>  int sum (int to)؛ / * اعلامیه جمع با یک استدلال * /</span><span>
  int bar ()؛ / * اعلامیه میله بدون استدلال * /</span><span>
  void foo (int ix، int jx)؛</span><span>
                    / * اعلامیه foo با دو استدلال * /</span>

برای تعریف واقعی یک تابع ، فقط بدنه آن را اضافه کنید:

<span>  int sum (int to) {</span><span>
    int ix ، ret ؛</span><span>
    ret = 0؛</span><span>
    برای (ix = 0؛ ix <to؛ ix = ix + 1)</span><span>
      ret = ret + ix؛</span><span>
    بازگشت ret؛ / * مقدار عملکرد بازگشتی * /</span><span>
  } / * جمع * /</span>

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

7.1.5 اشاره گرها و آرایه ها
یکی از رایج ترین مشکلات در برنامه نویسی در C (و گاهی اوقات ++ C) درک نشانه ها و آرایه ها است. در C (++ C) هر دو با تفاوتهای کوچک اما اساسی بسیار مرتبط هستند. شما با قرار دادن یک ستاره بین نوع داده و نام متغیر یا عملکرد ، یک اشاره گر را اعلام می کنید:

<span>  char * strp؛ / * strp "اشاره گر به کاراکتر" است * /
</span>

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

<span>  * strp = 'a'؛ / * یک شخصیت واحد * /
</span>

همانند سایر زبانها ، باید مقداری فضای مقداری را که نشانگر به آن نشان می دهد فراهم کنید. از یک نشانگر برای نویسه ها می توان برای نشان دادن دنباله ای از نویسه ها استفاده کرد: رشته . رشته ها در C توسط یک کاراکتر خاص NUL (0 یا به عنوان char ” img25 - اطلاق شی گرایی در برنامه نویسی“) خاتمه می یابد . بنابراین ، می توانید رشته هایی با هر طول داشته باشید. رشته ها در دو نقل قول محصور شده اند:

<span>  strp = "سلام"؛
</span>

در این حالت ، کامپایلر به طور خودکار نویسه پایان دهنده NUL را اضافه می کند. اکنون ، strp به دنباله ای از 6 کاراکتر اشاره می کند. شخصیت اول “h” است ، شخصیت دوم “e” و غیره است. ما می توانیم با یک شاخص در strp به این نویسه ها دسترسی پیدا کنیم :

<span>  strp [0] / * h * /</span><span>
  strp [1] / * e * /</span><span>
  strp [2] / * l * /</span><span>
  strp [3] / * l * /</span><span>
  strp [4] / * o * /</span><span>
  strp [5] / * \ 0 * /</span>

اولین کاراکتر همچنین برابر است با “* strp ” که می تواند به عنوان “* ( strp + 0)” نوشته شود. این امر منجر به چیزی به نام حسابگر اشاره گر می شود و یکی از ویژگی های قدرتمند C است. بنابراین ، ما معادلات زیر را داریم:

<span>  * strp == * (strp + 0) == strp [0]</span><span>
           * (strp + 1) == strp [1]</span><span>
           * (strp + 2) == strp [2]</span><span>
           ...</span>

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

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

<span>  char str [6]؛
</span>

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

<span>  str = "سلام"؛ / * خطا * /
</span>

زیرا این بدان معنی است که نشانگر را به نقطه “h” تغییر دهید. ما باید رشته را در قسمت حافظه ارائه شده کپی کنیم. بنابراین ما از تابعی به نام strcpy () استفاده می کنیم که بخشی از کتابخانه استاندارد C است.

<span>  strcpy (str ، "hallo") ؛ /* خوب */
</span>

با این حال توجه داشته باشید که ما می توانیم از str در هر موردی که نشانگر یک نویسه انتظار می رود استفاده کنیم ، زیرا این یک اشاره گر (ثابت) است.

7.1.6 یک برنامه اول
در اینجا ما اولین برنامه را که اغلب استفاده می شود معرفی می کنیم: برنامه ای که “سلام ، جهان!” را روی صفحه شما چاپ می کند:

<span>  # شامل <stdio.h></span>
<span>
  / * متغیرهای جهانی باید اینجا باشند * /</span>
<span>
  / * تعاریف عملکرد باید اینجا باشد * /</span>
<span>
  int</span><span>
  اصلی () {</span><span>
    قرار می دهد ("سلام ، جهان!")؛</span><span>
    بازگشت 0؛</span><span>
  } / * اصلی * /</span>

خط اول چیز عجیبی به نظر می رسد. توضیح آن نیاز به برخی اطلاعات در مورد نحوه مدیریت برنامه های C (و C ++) توسط کامپایلر دارد. مرحله تدوین تقریباً به دو مرحله تقسیم می شود. اولین مرحله “پیش پردازش” نام دارد و برای تهیه کد C خام استفاده می شود. در این حالت این مرحله از خط اول به عنوان آرگومان استفاده می کند تا فایلی به نام stdio.h را در منبع گنجانده باشد. براکت های زاویه فقط نشان می دهد که این فایل در مسیر جستجوی استاندارد تنظیم شده برای کامپایلر شما جستجو می شود. پرونده خود برخی از تعریف ها و تعریف ها را برای ورودی / خروجی استاندارد ارائه می دهد. به عنوان مثال ، تابعی را به نام put () اعلام می کند . مرحله پیش پردازش نیز نظرات را پاک می کند.

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

تابع main () می تواند آرگومان هایی را نشان دهد که پارامترهای خط فرمان را نشان می دهد. ما فقط آنها را در اینجا معرفی می کنیم اما بیشتر توضیح نمی دهیم:

<span>  # شامل <stdio.h></span>
<span>
  int</span><span>
  اصلی (int argc، char * argv []) {</span><span>
    int ix ؛</span><span>
    برای (ix = 0؛ ix <argc؛ ix ++) </span><span>
      printf ("٪ d. استدلال من٪ s \ n است" ، ix ، argv [ix])؛</span><span>
    بازگشت 0؛</span><span>
  } / * اصلی * /</span>

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

7.2 بعدی چیست؟
این بخش کاملاً کامل نیست. ما فقط می خواهیم بیان C را به شما بیان کنیم. ما همچنین می خواهیم برخی مفاهیم اساسی را معرفی کنیم که در بخش زیر استفاده خواهیم کرد. برخی از مفاهیم C در ++ C بهبود یافته اند. به عنوان مثال ، C ++ مفهوم مراجعی را ارائه می دهد که اجازه می دهد چیزی مشابه تماس با مرجع در تماس های عملکردی فراخوانی شود.

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

<span>  انسان می شود</span><span>
  مرد چاپی</span><span>
  انسان قرار می دهد</span><span>
  مرد scanf</span><span>
  مرد strcpy</span>

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

8 از C تا C ++

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
این بخش پسوندهایی را به زبان C ارائه می دهد که توسط C ++ معرفی شده اند [ 6 ]. همچنین با مفاهیم شی گرا و تحقق آنها سروکار دارد.

8.1 برنامه های افزودنی اساسی
 بخشهای زیر پسوندهایی را برای مفاهیم قبلاً معرفی شده C. ارائه می دهند . بخش  8.2 پسوندهای شی گرا را ارائه می دهد.

C ++ نظر جدیدی اضافه می کند که توسط دو خط برش (//) معرفی می شود و تا انتهای خط ادامه دارد. شما می توانید از هر دو سبک نظر استفاده کنید ، به عنوان مثال برای اظهار نظر در مورد تعداد زیادی کد:

<span>  / * C نظر می تواند شامل // باشد و می تواند در طول زمان تغییر کند</span><span>
     چندین خط * /</span><span>
  // / * این نظر به سبک C ++ است * / تا انتهای خط</span>

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

8.1.1 انواع داده ها
C ++ نوع داده جدیدی به نام مرجع را معرفی می کند . شما می توانید آنها را طوری تصور کنید که گویی “اصطلاحات مستعاری” برای متغیرها یا اشیا `” واقعی “هستند. از آنجا که نام مستعار بدون قسمت واقعی مربوط به آن وجود ندارد ، شما نمی توانید منابع واحدی را تعریف کنید. از ampersand (&) برای تعریف مرجع استفاده می شود. مثلا:

<span>  int ix ؛ / * ix متغیر "واقعی" است * /</span><span>
  int & rx = ix؛ / * rx "مستعار" برای ix است * /</span>
<span>
  ix = 1 ؛ / * همچنین rx == 1 * /</span><span>
  rx = 2 ؛ / * همچنین ix == 2 * /</span>

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

جدول  8.1 از [ 1 ] به تصویب رسیده است و مروری بر اظهارات احتمالی را در اختیار شما قرار می دهد. از این نظر کامل نیست که همه ترکیبات ممکن را نشان می دهد و برخی از آنها در اینجا معرفی نشده اند ، زیرا ما قصد نداریم از آنها استفاده کنیم. با این حال ، این مواردی است که شما احتمالاً اغلب استفاده خواهید کرد.

جدول 8.1:   عبارات اعلامیه.

img26 - اطلاق شی گرایی در برنامه نویسی

در C و C ++ می توانید با استفاده از تعدیل کننده const ، جنبه های خاص یک متغیر (یا شی) را ثابت اعلام کنید. جدول بعدی  8.2 ترکیبات احتمالی را لیست کرده و معنی آنها را توصیف می کند. پس از آن، برخی از نمونه های ارائه شده که نشان می دهد استفاده از توایع .

جدول 8.2:   عبارات اعلامیه ثابت.

img27 - اطلاق شی گرایی در برنامه نویسی

حال بیایید چند نمونه از متغیرهای محتوا و نحوه استفاده از آنها را بررسی کنیم. اظهارات زیر را در نظر بگیرید (دوباره از [ 1 ]):

<span>  بین من // فقط یک عدد صحیح معمولی است</span><span>
  int * ip ؛ // اشاره گر غیر اولیه به</span><span>
                                // عدد صحیح</span><span>
  int * const cp = & i؛ // نشانگر ثابت به عدد صحیح</span><span>
  const int ci = 7؛ // عدد صحیح ثابت</span><span>
  ساختار int * cip؛ // نشانگر به عدد صحیح ثابت</span><span>
  const int * const cicp = & ci؛ // نشانگر ثابت به ثابت</span><span>
                                // عدد صحیح</span>

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

<span>  i = ci؛ // اختصاص عدد صحیح ثابت به عدد صحیح</span><span>
  * cp = ci؛ // عدد صحیح ثابت را به متغیر اختصاص دهید</span><span>
                // که توسط اشاره گر ثابت ارجاع می شود</span><span>
  cip = & ci؛ // نشانگر را به عدد ثابت ثابت تغییر دهید</span><span>
  cip = cicp؛ // نشانگر را روی عدد ثابت ثابت بر روی تنظیم کنید</span><span>
                // متغیر مرجع اشاره گر ثابت به</span><span>
                // عدد صحیح ثابت</span>

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

<span>  ci = 8 ؛ // نمی تواند مقدار ثابت ثابت را تغییر دهد</span><span>
  * cip = 7 ؛ // نمی تواند عدد صحیح ثابت ارجاع شده را تغییر دهد</span><span>
                // توسط نشانگر</span><span>
  cp = & ci؛ // نمی تواند مقدار اشاره گر ثابت را تغییر دهد</span><span>
  ip = cip ؛ // این امکان تغییر مقدار را می دهد</span><span>
                // عدد صحیح ثابت * cip با * ip</span>

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

<span>  # شامل <stdio.h></span>
<span>
  int main () {</span><span>
    const int ci = 1؛</span><span>
    ساختار int & cr = ci؛</span><span>
    int & r = ci؛ // یک عدد صحیح موقتی برای مرجع ایجاد کنید</span><span>
    // cr = 7؛ // نمی تواند به مرجع ثابت مقدار اختصاص دهد</span><span>
    r = 3 ؛ // تغییر مقدار عدد صحیح موقتی</span><span>
    چاپ ("ci ==٪ d، r ==٪ d \ n"، ci، r)؛</span><span>
    بازگشت 0؛</span><span>
  }</span>

هنگام کامپایل با GNU g ++ ، کامپایلر هشدار زیر را صادر می کند:

تبدیل از “const int” به “int & “ساختار را حذف می کند
آنچه در واقع اتفاق می افتد ، این است که کامپایلر به طور خودکار یک متغیر موقتی را با مقدار ci ایجاد می کند که مرجع r برای آن آغاز می شود. در نتیجه ، هنگام تغییر r مقدار عدد صحیح موقتی تغییر می کند. این متغیر موقتی تا زمانی که مرجع r زندگی کند ، زندگی می کند .

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

8.1.2 توابع
C ++ اضافه بار عملکرد را همانطور که در بخش 6.3 تعریف شده است ،  فراهم می کند. به عنوان مثال ، ما می توانیم دو تابع مختلف max () تعریف کنیم ، یکی که حداکثر دو عدد صحیح را برمی گرداند و دیگری حداکثر دو رشته را برمی گرداند:

<span>  # شامل <stdio.h></span>
<span>
  int max (int a، int b) {</span><span>
    if (a> b) a را برگردانید؛</span><span>
    بازگشت ب؛</span><span>
  }</span>
<span>
  char * max (char * a ، char * b) {</span><span>
    if (strcmp (a، b)> 0) a را برگردانید.</span><span>
    بازگشت ب؛</span><span>
  }</span>
<span>
  int main () {</span><span>
    printf ("حداکثر (19 ، 69) =٪ d \ n" ، حداکثر (19 ، 69))؛</span><span>
    printf ("حداکثر (abc، def) =٪ s \ n"، max ("abc"، "def"))؛</span><span>
    بازگشت 0؛</span><span>
  }</span>

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

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

<span>  void foo (int byValue، int & byReference) {</span><span>
    byValue = 42؛</span><span>
    byReference = 42؛</span><span>
  }</span>
<span>
  نوار خالی () {</span><span>
    int ix ، jx ؛</span>
<span>
    ix = jx = 1 ؛</span><span>
    foo (ix ، jx) ؛</span><span>
    / * ix == 1 ، jx == 42 * /</span><span>
  }</span>

8.2 اولین برنامه های افزودنی شی گرا
 در این بخش نحوه استفاده از مفاهیم شی گرا در بخش  4 را در ++ C ارائه می دهیم.

8.2.1 کلاسها و اشیا
C ++ امکان تعریف و تعریف کلاس ها را فراهم می کند. نمونه هایی از کلاسها را اشیا می نامند . دوباره مثال برنامه نقاشی بخش 5 را به یاد بیاورید  . در آنجا ما یک کلاس Point ایجاد کرده ایم . در C ++ این به این شکل است:

<span>  کلاس point {</span><span>
    int _x ، _y ؛ // مختصات نقطه</span>
<span>
  عمومی: // قسمت رابط شروع شود</span><span>
    void setX (ساختار بین المللی)</span><span>
    void setY (ساختار بین المللی)</span><span>
    int getX () {Return _x؛ }</span><span>
    int getY () {Return _y؛ }</span><span>
  }؛</span>
<span>
  نقطه apoint؛</span>

این اعلان یک کلاس نقطه و یک شی را تعریف می کند apoint . شما می توانید یک تعریف کلاس را به عنوان یک تعریف ساختار با توابع (یا “روش ها”) در نظر بگیرید. علاوه بر این ، می توانید حقوق دسترسی را با جزئیات بیشتری مشخص کنید . به عنوان مثال ، _x و _y خصوصی هستند ، زیرا عناصر کلاس ها به صورت پیش فرض خصوصی هستند. در نتیجه ، ما به صراحت باید حقوق دسترسی را “تغییر” دهیم تا موارد زیر را به صورت عمومی اعلام کنیم . ما این کار را با استفاده از کلمه کلیدی public و به دنبال آن دو نقطه انجام می دهیم: هر عنصر زیر این کلمه کلیدی اکنون از خارج کلاس قابل دسترسی است.

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

<span>  کلاس Foo {</span><span>
    // خصوصی به صورت پیش فرض ...</span>
<span>
  عمومی:</span><span>
    // آنچه در زیر می آید عمومی است تا ...</span>
<span>
  خصوصی: </span><span>
    // ... اینجا ، جایی که ما به خصوصی برگشتیم ...</span>
<span>
  عمومی:</span><span>
    // ... و بازگشت به عموم.</span><span>
  }؛</span>

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

<span>  ساختار کلاس {</span><span>
  public: // عناصر سازه به طور پیش فرض عمومی هستند</span><span>
    // عناصر ، روش ها</span><span>
  }؛</span>

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

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

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

برعکس ، فراخوانی به روشهای تعیین شده منجر به فراخوانی عملکرد “واقعی” می شود. ما این روش ها را خارج از اعلامیه کلاس تعریف می کنیم. این امر ضروری می کند ، مشخص شود که تعریف روش به کدام کلاس تعلق دارد. به عنوان مثال ، یک کلاس دیگر ممکن است فقط یک متد setX () را تعریف کند که کاملا متفاوت از Point است . ما باید بتوانیم دامنه تعریف را تعریف کنیم. بنابراین ما از عملگر دامنه “::” استفاده می کنیم:

<span>  void Point :: setX (ساختار بین المللی) {</span><span>
    _x = وال ؛</span><span>
  }</span>
<span>
  void Point :: setY (ساختار داخلی) {</span><span>
    _y = val؛</span><span>
  }</span>

در اینجا ما متد setX () ( setY () ) را در محدوده کلاس Point تعریف می کنیم . شی apoint می تواند از این روش ها برای تنظیم و بدست آوردن اطلاعات در مورد خودش استفاده کند:

<span>  نقطه apoint؛</span>
<span>
  apoint.setX (1) ؛ // مقداردهی اولیه</span><span>
  apoint.setY (1) ؛</span>
<span>
  //</span><span>
  // x از اینجا مورد نیاز است ، بنابراین ، ما آن را در اینجا تعریف می کنیم و</span><span>
  // آن را به مختصات x apoint مقداردهی اولیه کنید</span><span>
  //</span>
<span>
  int x = apoint.getX ()؛</span>

این س aboutال مطرح می شود که چگونه روش های “می دانند” از کدام شی مورد استناد قرار می گیرند. این کار با عبور ضمنی یک اشاره گر به شی فراخوانی شده به روش انجام می شود. ما می توانیم در این روش به این نشانگر دسترسی پیدا کنیم . در تعریف متدهای setX () و setY () به ترتیب از اعضای کلاس _x و _y استفاده می شود. اگر توسط یک شی فراخوانی شود ، این اعضا “به طور خودکار” به شی correct صحیح ترسیم می شوند. ما می توانیم از این برای نشان دادن آنچه واقعاً اتفاق می افتد استفاده کنیم:

<span>  void Point :: setX (ساختار بین المللی) {</span><span>
    این -> _ x = وال ؛ // این را برای استناد به مرجع استفاده کنید</span><span>
                      // هدف - شی</span><span>
  }</span>
<span>
  void Point :: setY (ساختار داخلی) {</span><span>
    این -> _ y = val؛</span><span>
  }</span>

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

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

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

<span>  کلاس point {</span><span>
    int _x ، _y ؛</span>
<span>
  عمومی:</span><span>
    نقطه() {</span><span>
      _x = _y = 0 ؛</span><span>
    }</span>
<span>
    void setX (ساختار بین المللی)</span><span>
    void setY (ساختار بین المللی)</span><span>
    int getX () {Return _x؛ }</span><span>
    int getY () {Return _y؛ }</span><span>
  }؛</span>

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

 <span>
  کلاس point { </span><span>
    int _x ، _y ؛ </span>
     <span>
  عمومی: </span><span>
    نقطه() {</span><span>
      _x = _y = 0 ؛ </span><span>
    }  </span><span>
    نقطه (ساختار int x ، ساختار int y) {</span><span>
      _x = x ؛</span><span>
      _y = y ؛</span><span>
    }</span>
     <span>
    void setX (ساختار بین المللی)</span><span>
    void setY (ساختار بین المللی) </span><span>
    int getX () {Return _x؛ }</span><span>
    int getY () {Return _y؛ }</span><span>
  }؛</span>

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

<span>  نقطه apoint؛ // نقطه :: نقطه ()</span><span>
  نقطه bpoint (12 ، 34) ؛ // Point :: Point (const int ، const int)</span>

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

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

  <span>
  کلاس point {  </span><span>
    int _x ، _y ؛  </span>
      <span>
  عمومی:  </span><span>
    نقطه() { </span><span>
      _x = _y = 0 ؛  </span><span>
    }   </span><span>
    نقطه (ساختار int x ، ساختار int y) {</span><span>
      _x = x ؛</span><span>
      _y = y ؛ </span><span>
    } </span><span>
    نقطه (نقطه نقطه و از) {</span><span>
      _x = از ._x ؛</span><span>
      _y = از._y ؛</span><span>
    }</span>
      <span>
    void setX (ساختار بین المللی) </span><span>
    void setY (ساختار بین المللی)  </span><span>
    int getX () {Return _x؛ }</span><span>
    int getY () {Return _y؛ }  </span><span>
  }؛</span>

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

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

<span>  نقطه apoint؛ // نقطه :: نقطه ()</span><span>
  نقطه bpoint (apoint) ؛ // Point :: Point (ثابت نقطه &)</span><span>
  cpoint point = apoint؛ // Point :: Point (ثابت نقطه &)</span>

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

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

<span>  void foo () {</span><span>
    لیست لیست // لیست :: لیست () مقدار اولیه را برای</span><span>
                    // لیست خالی.</span><span>
    ... // اضافه کردن / حذف عناصر</span><span>
  } // تماس تخریبی!</span>

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

تخریب کننده ها مشابه سازنده ها اعلام می شوند. بنابراین ، آنها همچنین از نام پیشوند یک tilde (~) از کلاس تعریف استفاده می کنند:

<span>  کلاس point {</span><span>
    int _x ، _y ؛</span>
 <span>
  عمومی:</span><span>
    نقطه() {</span><span>
      _x = _y = 0 ؛</span><span>
    }</span><span>
    نقطه (ساختار int x ، ساختار int y) {</span><span>
      _x = xval ؛</span><span>
      _y = yval ؛</span><span>
    }</span><span>
    نقطه (نقطه نقطه و از) {</span><span>
      _x = از ._x ؛</span><span>
      _y = از._y ؛</span><span>
    }</span>
<span>
    ~ نقطه () {/ * کاری برای انجام دادن نیست! * /}</span>
 <span>
    void setX (ساختار بین المللی)</span><span>
    void setY (ساختار بین المللی)</span><span>
    int getX () {Return _x؛ }</span><span>
    int getY () {Return _y؛ }</span><span>
  }؛</span>

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

9 اطلاعات بیشتر در مورد C ++

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
این بخش مقدمه ما در مورد C ++ است. ما مفاهیم شی گرا “واقعی” را معرفی می کنیم و به این سوال پاسخ می دهیم که چگونه یک برنامه C ++ در حقیقت چگونه نوشته شده است.

9.1 ارث
 در زبان شبه ما ، ما وراثت را با “ارث از” تنظیم می کنیم. در C ++ این کلمات با دو نقطه جایگزین می شوند. به عنوان مثال بیایید یک کلاس برای نقاط سه بعدی طراحی کنیم. البته ما می خواهیم از کلاس Point موجود خود استفاده مجدد کنیم . ما طراحی کلاس خود را به شرح زیر شروع می کنیم:

<span>  کلاس Point3D: نقطه عمومی {</span><span>
    int _z؛</span>
  <span>
  عمومی:</span><span>
    Point3D () {</span><span>
      setX (0) ؛</span><span>
      setY (0) ؛</span><span>
      _z = 0 ؛</span><span>
    }</span><span>
    Point3D (ساختار int x ، ساختار int ، ساختار int z) {</span><span>
      setX (x) ؛</span><span>
      setY (y) ؛</span><span>
      _z = z ؛</span><span>
    }</span>
<span>
    3 Point3D () {/ * کاری برای انجام دادن نیست * /}</span>
<span>
    int getZ () {Return _z؛ }</span><span>
    void setZ (const int val) {_z = val؛ }</span><span>
  }؛</span>

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

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

جدول 9.1:   حقوق دسترسی و ارث.

img28 - اطلاق شی گرایی در برنامه نویسی

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

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

9.1.2 ساخت و ساز
 وقتی نمونه ای از کلاس Point3D ایجاد می کنیم سازنده آن فراخوانی می شود. از آنجا که Point3D از Point مشتق شده است سازنده کلاس Point نیز فراخوانی می شود. با این حال ، قبل از اجرای بدنه سازنده کلاس Point3D ، این سازنده فراخوانی می شود . به طور کلی ، قبل از اجرای بدنه سازنده خاص ، سازندگان هر ابر کلاس فراخوانی می شوند تا قسمت خود از شی object ایجاد شده را مقداردهی اولیه کنند.

وقتی ما یک شی با ایجاد می کنیم

<span>  Point3D نقطه (1 ، 2 ، 3) ؛
</span>

سازنده دوم Point3D فراخوانی می شود. قبل از اجرای بدنه سازنده ، سازنده Point () فراخوانی می شود تا قسمت نقطه نقطه جسم را مقدار دهی اولیه کند . خوشبختانه ، ما سازنده ای را تعریف کرده ایم که هیچ استدلالی ندارد. این سازنده مختصات 2D _x و _y را به 0 (صفر) مقدار اولیه می دهد . همانطور که Point3D فقط از Point مشتق شده است ، هیچ فراخوانی سازنده دیگری وجود ندارد و بدنه Point3D (const int ، const int ، const int) اجرا می شود. در اینجا روش های setX () و setY () را فراخوانی می کنیم مختصات 2D را صریحاً لغو کند. متعاقباً مقدار مختصات سوم _z تنظیم می شود.

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

<span>  کلاس Point3D: نقطه عمومی {</span><span>
    ...</span>
<span>
  عمومی:</span><span>
    Point3D () {...}</span><span>
    Point3D (</span><span>
      ساختار int x ، </span><span>
      بین المللی ، </span><span>
      const int z): نقطه (x ، y) {</span><span>
        _z = z ؛</span><span>
    }</span><span>
    ...</span><span>
  }؛</span>

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

<span>  کلاس مرکب {</span><span>
    قسمت قسمت</span><span>
    ...</span>
<span>
  عمومی:</span><span>
    مرکب (const int partParameter): part (partParameter) {</span><span>
      ...</span><span>
    }</span><span>
    ...</span><span>
  }؛</span>

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

<span>  نقطه (): _x (0) ، _y (0) {}</span><span>
  نقطه (ساختار int x ، ساختار int y): _x (x) ، _y (y) {}</span>

شما باید از این روش مقداردهی اولیه هرچه بیشتر استفاده کنید ، زیرا به کامپایلر اجازه می دهد متغیرها و اشیا initial را به جای ایجاد با مقدار پیش فرض ، به درستی مقداردهی اولیه کند و از یک تکلیف اضافی (یا مکانیزم دیگر) برای تعیین مقدار آن استفاده کند.

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

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

<span>  کلاس DrawableString: public point ، عمومی DrawableObject {</span><span>
    ...</span>
<span>
  عمومی:</span><span>
    DrawableString (...):</span><span>
      نقطه(...)،</span><span>
      DrawableObject (...) {</span><span>
        ...</span><span>
    }</span><span>
    ~ DrawableString () {...}</span><span>
    ...</span><span>
  }؛</span>

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

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

<span>  کلاس DrawableObject {</span><span>
  عمومی:</span><span>
    چاپ باطل مجازی ()؛</span><span>
  }؛</span>

Class DrawableObject یک متد print () را تعریف می کند که مجازی است. ما می توانیم از این کلاس سایر کلاسها را بدست آوریم:

<span>  class point: public DrawableObject {</span><span>
    ...</span><span>
  عمومی:</span><span>
    ...</span><span>
    void print () {...}</span><span>
  }؛</span>

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

<span>  نمایشگر بی اعتبار (ساختار DrawableObject & obj) {</span><span>
    // هر چیز لازم را تهیه کنید</span><span>
    obj.print () ؛</span><span>
  }</span>

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

<span>  کلاس رنگ {</span><span>
  عمومی:</span><span>
    مجازی ~ رنگ ()؛ </span><span>
  }؛</span>
<span>
  کلاس قرمز: رنگ عمومی {</span><span>
  عمومی:</span><span>
    ~ قرمز ()؛ // واقعیت از Color به ارث رسیده است</span><span>
  }؛</span>
<span>
  کلاس LightRed: قرمز عمومی {</span><span>
  عمومی:</span><span>
    ~ LightRed () ؛</span><span>
  }؛</span>

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

<span>  رنگ * پالت [3] ؛</span><span>
  پالت [0] = قرمز جدید؛ // به صورت پویا یک شی جدید قرمز ایجاد کنید</span><span>
  پالت [1] = LightRed جدید؛</span><span>
  پالت [2] = رنگ جدید ؛</span>

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

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

<span>  حذف پالت [0]؛</span><span>
  // تخریب کننده تماس بگیرید ~ قرمز () و به دنبال آن ~ رنگ ()</span><span>
  حذف پالت [1]؛</span><span>
  // تماس ~ LightRed () ، ~ قرمز () و ~ Color ()</span><span>
  حذف پالت [2]؛</span><span>
  // تماس ~ رنگ ()</span>

تماس های مختلف تخریب کننده فقط به دلیل استفاده از تخریب گرهای مجازی اتفاق می افتد. اگر ما آنها را مجازی اعلام نمی کردیم ، هر حذف فقط ~ Color () را فراخوانی می کرد (زیرا پالت [i] از نوع اشاره گر به رنگ است ).

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

<span>  کلاس DrawableObject {</span><span>
    ...</span><span>
  عمومی:</span><span>
    ...</span><span>
    چاپ باطل مجازی () = 0؛</span><span>
  }؛</span>

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

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

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

<span>  مجتمع کلاس {</span><span>
    دو برابر _ واقعی ، </span><span>
           _imag؛</span>
<span>
    عمومی:</span><span>
      Complex (): _real (0.0) ، _imag (0.0) {}</span><span>
      پیچیده (ساخت واقعی دو برابر ، ساخت تصور دوگانه):</span><span>
        _ واقعی (واقعی) ، _imag (تصور) {}</span>
<span>
      Complex add (const Complex op) ؛</span><span>
      کفال پیچیده (const Complex op)؛</span><span>
      ...</span><span>
    }؛</span>

سپس می توانیم از اعداد مختلط استفاده کنیم و با آنها “محاسبه” کنیم:

<span>  مجتمع a (1.0 ، 2.0) ، b (3.5 ، 1.2) ، c ؛</span>
<span>
  c = a.add (b) ؛</span>

در اینجا c را جمع a و b می کنیم . گرچه کاملاً درست است ، اما شیوه بیان مناسبی را ارائه نمی دهد. آنچه ما ترجیح می دهیم از آن استفاده کنیم “+” شناخته شده برای بیان جمع دو عدد مختلط است. خوشبختانه C++ به ما امکان می دهد تقریباً همه اپراتورهای آن را برای انواع تازه ایجاد شده بیش از حد بارگیری کنیم . به عنوان مثال ، ما می توانیم برای کلاس Complex خود یک عملگر “+” تعریف کنیم :

<span>  مجتمع کلاس {</span><span>
    ...</span>
<span>
  عمومی:</span><span>
    ...</span>
<span>
    عملگر پیچیده + (const Complex & op) {</span><span>
      Double real = _real + op._real ،</span><span>
             imag = _imag + op._imag؛</span><span>
      بازگشت (پیچیده (واقعی ، تصور)) ؛</span><span>
    }</span>
<span>
    ...</span><span>
  }؛</span>

در این حالت ، عملگر + را به عضوی از کلاس Complex در آورده ایم . بیان فرم

<span>  c = a + b ؛
</span>

به تماس متد ترجمه می شود

<span>  c = a.operator + (b) ؛
</span>

بنابراین ، عملگر دودویی + فقط به یک استدلال نیاز دارد. اولین استدلال به طور ضمنی توسط شی inv استناد شده ارائه می شود (در این مورد a ).

با این حال ، یک تماس اپراتور همچنین می تواند به عنوان یک تماس عملکردی معمول تفسیر شود ، مانند

<span>  c = عملگر + (a ، b) ؛
</span>

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

<span>  مجتمع کلاس {</span><span>
    ...</span>
<span>
  عمومی:</span><span>
    ...</span>
<span>
    Double real () {Return _real؛ }</span><span>
    Double imag () {Return _imag؛ }</span>
<span>
    // نیازی به تعریف اپراتور در اینجا نیست!</span><span>
  }؛</span>
<span>
  عملگر مجتمع + (Complex & op1 ، Complex & op2) {</span><span>
    Double real = op1.real () + op2.real () ،</span><span>
           imag = op1.imag () + op2.imag ()؛</span><span>
    بازگشت (پیچیده (واقعی ، تصور)) ؛</span><span>
  }</span>

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

9.5 دوستان
ما می توانیم توابع یا کلاسها را به عنوان دوست یک کلاس تعریف کنیم تا به آنها امکان دسترسی مستقیم به اعضای داده های خصوصی آن را بدهد. به عنوان مثال ، در بخش قبلی ، ما می خواهیم عملکردی برای عملگر + داشته باشیم تا به اعضای داده خصوصی _real و _imag کلاس Complex دسترسی داشته باشد. بنابراین ما عملگر + را به عنوان دوست کلاس Complex اعلام می کنیم :

<span>  مجتمع کلاس {</span><span>
    ...</span>
  <span>
  عمومی:</span><span>
    ...</span>
<span>
    دوست اپراتور مجتمع + (</span><span>
      مجتمع ساختاری و ، </span><span>
      مجتمع ساختاری و</span><span>
    )</span><span>
  }؛</span>
<span>
  عملگر مجتمع + (const Complex & op1 ، const Complex & op2) {</span><span>
    دو برابر واقعی = op1._real + op2._real ،</span><span>
           imag = op1._imag + op2._imag؛</span><span>
    بازگشت (پیچیده (واقعی ، تصور)) ؛</span><span>
  }</span>

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

9.6 نحوه نوشتن برنامه
 تاکنون ، ما فقط بخشهایی از برنامه ها یا برنامه های بسیار کوچکی را ارائه داده ایم که به راحتی می توان آنها را در یک پرونده مدیریت کرد. با این حال ، پروژه های بزرگتر ، مثلاً یک برنامه تقویم ، باید به بخشهایی قابل مدیریت تقسیم شوند که اغلب ماژول نامیده می شوند . ماژول ها در پرونده های جداگانه ای پیاده سازی می شوند و اکنون به طور خلاصه درباره نحوه مدولاسیون در C و C ++ بحث خواهیم کرد. این بحث بر اساس U NIX است و کامپایلر G + C C است. اگر از صورت های فلکی دیگری استفاده می کنید ، موارد زیر ممکن است در کنار شما متفاوت باشد. این امر به ویژه برای کسانی که از محیط توسعه یکپارچه (IDE) ، مثلاً Borland C ++ استفاده می کنند ، بسیار مهم است.

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

جدول 9.2:   پسوندها و انواع پرونده ها.

img29 - اطلاق شی گرایی در برنامه نویسی

در این آموزش ما برای پرونده های هدر ، .h برای پرونده های C ++ و .tpl برای پرونده های تعریف الگو از .h استفاده خواهیم کرد . حتی اگر ما در حال نوشتن “ تنها ” کد C، آن را حس می کند به استفاده از .cc به زور کامپایلر آن را به عنوان C ++. این ترکیب هر دو را ساده می کند ، زیرا سازوکار داخلی نحوه تنظیم نام در برنامه توسط کامپایلر بین هر دو زبان متفاوت است . foot - اطلاق شی گرایی در برنامه نویسی

9.6.1 مراحل تدوین
 فرآیند تدوین ، پرونده های .cc را می گیرد ، آنها را پیش پردازش می کند (حذف نظرات ، اضافه کردن پرونده های هدر) foot - اطلاق شی گرایی در برنامه نویسیو تبدیل آنها به پرونده های شیfoot - اطلاق شی گرایی در برنامه نویسی . پسوندهای معمولی برای آن نوع پرونده .o یا .obj هستند .

پس از تدوین موفقیت آمیز ، مجموعه پرونده های شی توسط یک لینک دهنده پردازش می شود . این برنامه پرونده ها را با هم ترکیب می کند ، کتابخانه های لازم را اضافه foot - اطلاق شی گرایی در برنامه نویسیمی کند و یک مورد اجرایی ایجاد می کند. در زیر U NIX این پرونده a.out نامیده می شود اگر مورد دیگری مشخص نشده باشد. این مراحل در شکل 9.1 نشان داده شده است  .

شکل 9.1:   مراحل تدوین.

img30 - اطلاق شی گرایی در برنامه نویسی

با کامپایلرهای مدرن می توان هر دو مرحله را ترکیب کرد. به عنوان مثال ، برنامه های نمونه کوچک ما می توانند به صورت زیر با کامپایلر G + C ++ کامپایل و مرتبط شوند (البته “ampleexample.cc” فقط یک نام نمونه است):

<span>  مثال gcc.cc
</span>

9.6.2 یادداشتی درباره سبک
 از پرونده های هدر برای توصیف رابط پرونده های پیاده سازی استفاده می شود. در نتیجه ، آنها در هر فایل پیاده سازی که از رابط کاربری فایل پیاده سازی خاص استفاده می کند ، گنجانده شده اند. همانطور که در بخشهای قبلی ذکر شد ، این گنجاندن با کپی محتوای فایل هدر در هر دستور پیش پردازنده #include حاصل می شود ، که منجر به یک پرونده C ++ خام “عظیم” می شود.

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

<span>  #ifndef MACRO</span><span>
  # تعریف MACRO / * تعریف MACRO * /</span><span>
  ...</span><span>
  # اندیف</span>

خطوط بین #ifndef و #endif فقط در صورتی گنجانده شده است که MACRO از قبل تعریف نشده باشد. ما می توانیم از این مکانیسم برای جلوگیری از چندین نسخه استفاده کنیم:

<span>  / *</span><span>
  ** مثالی برای یک فایل هدر که "بودن" را بررسی می کند</span><span>
  ** قبلاً گنجانده شده است. فرض کنید ، نام فایل هدر است</span><span>
  ** "myheader.h" است</span><span>
  * /</span>
<span>
  #ifndef __MYHEADER_H</span><span>
  # تعریف __MYHEADER_H</span>
<span>
  / *</span><span>
  ** اعلامیه های رابط به اینجا بروید</span><span>
  * /</span>
<span>
  #endif / * __MYHEADER_H * /</span>

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

9.7 تمرین

1
پلی مورفیسم . توضیح دهد که چرا

<span>	  نمایشگر خالی (ساختار DrawableObject obj)
</span>

خروجی دلخواه تولید نمی کند.

10 لیست – مطالعه موردی

آکادمی شبکه جهانی گلوباید پیتر مولر (GNA)
pmueller@uu-gna.mit.edu
10.1 انواع عمومی (الگوها)
 در C ++ به انواع داده های عمومی الگوهای کلاسfoot - اطلاق شی گرایی در برنامه نویسی یا به طور خلاصه فقط الگوها گفته می شود . یک الگوی کلاس مانند تعریف کلاس عادی به نظر می رسد ، جایی که برخی از جنبه ها توسط متغیرهایی نمایش داده می شوند . در مثال لیست آینده ما از این مکانیزم برای تولید لیست برای انواع مختلف داده استفاده می کنیم:

<span>  الگو <کلاس T></span><span>
  لیست کلاس: ... {</span><span>
  عمومی:</span><span>
    ...</span><span>
    باطل ضمیمه (ساختار T)</span><span>
    ...</span><span>
  }؛</span>

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

هر آرگومان در تعریف کلاس زیر یک حفره یا سوراخ را مشخص می کند. در مثال ما ، می خواهیم کلاس List برای انواع مختلف داده تعریف شود. می توان گفت ، که ما می خواهیم یک کلاس از لیست ها را تعریف کنیم foot - اطلاق شی گرایی در برنامه نویسی. در این حالت کلاس لیست ها با توجه به نوع اشیا they موجود در آنها تعریف می شود. ما از نام T برای مکان نگهدار استفاده می کنیم. اکنون ما از T در هر مکان که به طور معمول نوع اشیا actual واقعی انتظار می رود استفاده می کنیم. به عنوان مثال ، هر لیست روشی را برای الحاق یک عنصر به آن ارائه می دهد. اکنون می توانیم این روش را همانطور که در بالا نشان داده شده با استفاده از T تعریف کنیم .

اکنون یک تعریف واقعی لیست باید نوع لیست را مشخص کند. اگر به عبارت class که قبلاً استفاده کردیم پایبند بمانیم ، باید یک نمونه کلاس ایجاد کنیم . از این نمونه کلاس می توانیم نمونه های شی object “واقعی” ایجاد کنیم:

<span>  لیست <int> integerList؛
</span>

در اینجا ما یک نمونه کلاس از یک لیست ایجاد می کنیم که از اعداد صحیح به عنوان عناصر داده استفاده می کند. نوع بسته شده در براکت های زاویه ای را مشخص می کنیم. کامپایلر اکنون استدلال ارائه “ INT ” اعمال می شود و به صورت خودکار و تعریف کلاس که در آن حفره یا سوراخ T است جایگزین از نوع int ، برای مثال، آن را تولید از روش زیر اعلامیه برای الحاق () :

<span>  void append (ساختار اطلاعات int)
</span>

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

<span>  الگو <کلاس K ، کلاس T> </span><span>
  فرهنگ لغت کلاس {</span><span>
    ...</span><span>
  عمومی:</span><span>
    ...</span><span>
    K getKey (ساختار T از)</span><span>
    T getData (کلید ثابت K) ؛</span><span>
    ...</span><span>
  }؛</span>

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

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

<span>  الگو <کلاس T ، اندازه int></span><span>
  پشته کلاس {</span><span>
    T _store [size]؛</span>
<span>
  عمومی:</span><span>
    ...</span><span>
  }؛</span>
<span>
  پشته <int، 128> mystack؛</span>

در این مثال ، mystack مجموعه ای از اعداد صحیح است که از یک آرایه 128 عنصر استفاده می کند. با این حال ، در ادامه ما از کلاس های پارامتری استفاده نخواهیم کرد.

10.2 شکل و پیمایش
 در بحث زیر ما بین شکل ساختار داده و استراتژی های عبور از آن تفاوت قائل می شویم . اولین مورد “نگاه” است که در حال حاضر اطلاعات زیادی در مورد بلوک های ساختاری ساختار داده فراهم می کند.

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

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

1
عنصر فعلی . تکرار کننده از عناصر داده به صورت یکجا بازدید می کند. عنصری که در حال حاضر بازدید می شود “عنصر فعلی” نامیده می شود.
2
عملکرد جانشین . اجرای مرحله به عنصر داده بعدی به استراتژی پیمایش اعمال شده توسط تکرار کننده بستگی دارد. از “تابع جانشین” برای برگرداندن عنصری که بعدی است بازدید می شود استفاده می شود: این جانشین عنصر فعلی را برمی گرداند.
3
شرط خاتمه . تکرار کننده باید مکانیزمی برای بررسی بازدید یا عدم بازدید از همه عناصر ارائه دهد.

10.3 ویژگی های لیست های پیوند خورده
 هنگام انجام کاری شی گرا ، اولین سوالی که باید بپرسید این است

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

شکل 10.1:   بلوک های اساسی ساختاری یک لیست به هم پیوسته.

img5 - اطلاق شی گرایی در برنامه نویسی

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

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

سه نوع گره را می توان تشخیص داد:

  • گره اول ( سر ) ، که هیچ سلفی ندارد ،
  • گره های میانی ، که دقیقاً یک سلف و دقیقاً یک جانشین دارند و
  • آخرین گره ( دم ) ، که هیچ جانشینی ندارد.

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

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

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

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

می توان گره جدیدی را در جلوی لیست قرار داد که:

  • نشانگر آن روی هد فعلی تنظیم شده است ،
  • گره جدید هد جدید می شود.

به همین ترتیب ، یک گره جدید به راحتی می تواند به دم اضافه شود:

  • نشانگر دم روی گره جدید تنظیم شده است ،
  • گره جدید به دم جدید تبدیل می شود.

عملکرد معکوس برای قرار دادن در جلو ، حذف از جلو است :

  • گره جانشین سر به سر جدید تبدیل می شود ،
  • گره سر قبلی کنار گذاشته می شود.

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

سرانجام ، سه بدوی ارزان دیگر نیز وجود دارد که معنای آنها مستقیماً رو به جلو است. بنابراین ، ما دیگر آنها را بررسی نخواهیم کرد. با این حال ، ما آنها را در اینجا برای کامل بودن ارائه می دهیم:

  • get-first : گره سر (داده های) را برمی گرداند ،
  • get-last : (داده های گره دم) را برمی گرداند و
  • is-خالی است : آیا لیست خالی باشد یا خیر.

10.4 اجرای شکل

10.4.1 الگوهای گره
 بلوک اصلی یک لیست گره است . بنابراین ، بیایید ابتدا یک کلاس برای آن اعلام کنیم. یک گره چیزی بیشتر از اشاره گر به گره دیگر ندارد. بیایید فرض کنیم که این همسایه همیشه در سمت راست است.

به اعلامیه کلاس Node نگاهی بیندازید .

<span>  گره کلاس {</span><span>
    گره * _راست؛</span>
<span>
  عمومی:</span><span>
    گره (گره * راست = NULL): _ راست (راست) {}</span><span>
    گره (گره ثابت و Val): _ راست (val._right) {}</span>
<span>
    const گره * راست () const {بازگشت _راست؛ }</span><span>
    گره * & right () {return _right؛ }</span>
<span>
    گره و عملگر = (ساختار گره و Val) {</span><span>
      _راست = val._right ؛</span><span>
      بازگشت * این؛</span><span>
    }</span>
<span>
    عملگر const int == (ساختار گره و Val) ساختار {</span><span>
      بازگشت _راست == val._right ؛</span><span>
    }</span><span>
    عملگر const int! = (const Node & Val) ساختار {</span><span>
      بازگشت! (* این == وال)؛</span><span>
    }</span><span>
  }؛</span>

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

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

<span>  کلاس Foo {</span><span>
    ...</span><span>
    int foo () ساختار؛</span><span>
    int foo ()؛</span><span>
  }؛</span>

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

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

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

عملگر نابرابری “! =” با استفاده از تعریف عملگر برابری پیاده سازی می شود. به یاد بیاورید ، که این به شی فراخوانی اشاره دارد ، بنابراین ،

<span>  گره a ، b ؛</span><span>
  ...</span><span>
  اگر (a! = b) ...</span>

منجر به تماس با اپراتور خواهد شد! = () با این مجموعه به آدرس a . ما این کار را با استفاده از عملگر مرجع استاندارد “*” انجام می دهیم. اکنون ، * این یک شی از کلاس Node است که با استفاده از عملگر == () با یک شی دیگر مقایسه می شود . در نتیجه ، از تعریف عملگر == () کلاس Node استفاده می شود. با استفاده از عملگر استاندارد boolean NOT “!” نتیجه را نفی کرده و مقدار واقعی عملگر را بدست می آوریم ! = () .

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

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

<span>  الگو <کلاس T></span><span>
  کلاس DataNode: گره عمومی {</span><span>
    T _ داده ها ؛</span>
<span>
  عمومی:</span><span>
    DataNode (ساختار T داده ، DataNode * سمت راست = NULL):</span><span>
      گره (راست) ، _ داده (داده) {}</span><span>
    DataNode (ساختار DataNode و Val):</span><span>
      گره (val) ، _ داده (val._data) {}</span>
<span>
    ساختار DataNode * سمت راست () ساختار { </span><span>
      Return ((DataNode *) گره :: راست ())؛</span><span>
    }</span><span>
    DataNode * & right () {return ((DataNode * &) Node :: right ())؛ }</span>
<span>
    const T & data () const {Return _data؛ }</span><span>
    T & data () {Return _data؛ }</span>
<span>
    DataNode & عملگر = (ساختار DataNode & val) {</span><span>
      گره :: عملگر = (val) ؛</span><span>
      _data = val._data ؛</span><span>
      بازگشت * این؛</span><span>
    }</span>
<span>
    عملگر const int == (const DataNode & val) ساختار {</span><span>
      برگشت(</span><span>
        گره :: اپراتور == (val) &&</span><span>
        _data == val._data) ؛</span><span>
    }</span><span>
    عملگر const int! = (const DataNode & val) ساختار {</span><span>
      بازگشت! (* این == وال)؛</span><span>
    }</span><span>
  }؛</span>

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

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

<span>  الگو <کلاس T></span><span>
  کلاس ListBase {</span><span>
  عمومی:</span><span>
    virtual ~ ListBase () {} // تخریب کننده را مجبور به بودن می کند</span><span>
                            // مجازی</span><span>
    فضای خالی مجازی () = 0؛</span>
<span>
    خلا v مجازی putInFront (داده های ساختاری T) = 0؛</span><span>
    ضمیمه باطل مجازی (داده های ساختاری T) = 0؛</span><span>
    void virtual delFromFront () = 0؛</span>
<span>
    ساختار مجازی T & getFirst () ساختار = 0؛</span><span>
    T مجازی و getFirst () = 0؛</span><span>
    ساختار مجازی T & getLast () ساختار = 0؛</span><span>
    T مجازی و getLast () = 0؛</span>
<span>
    ساختار مجازی int isEmpty () const = 0؛</span><span>
  }؛</span>

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

برای عملیات get-first و get-last دو نسخه اعلام کرده ایم. یکی برای استفاده در یک زمینه ثابت و دیگری در یک متن متغیر است.

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

<span>  الگو <کلاس T></span><span>
  لیست کلاس: لیست عمومی فهرست <T> {</span><span>
    DataNode <T> * _head، * _tail؛</span>
<span>
  عمومی:</span><span>
    لیست (): _head (NULL) ، _ tail (NULL) {}</span><span>
    لیست (فهرست و اعتبار): _head (NULL) ، _ tail (NULL) { </span><span>
      * این = val؛ </span><span>
    }</span><span>
    مجازی ~ لیست () {flush ()؛ }</span><span>
    فضای خالی مجازی ()؛</span>
<span>
    خلا v مجازی putInFront (داده های T T)</span><span>
    باطل مجازی ضمیمه (داده های ثابت T) ؛</span><span>
    خالی مجازی delFromFront ()؛</span>
<span>
    ساختار مجازی T & getFirst () ساختار {Return _head-> data ()؛ }</span><span>
    T مجازی و getFirst () {return _head-> data ()؛ }</span><span>
    ساختار مجازی T & getLast () ساختار {Return _tail-> data ()؛ }</span><span>
    T مجازی و getLast () {return _tail-> data ()؛ }</span>
<span>
    ساختار مجازی int isEmpty () ساختار {return _head == NULL؛ }</span>
<span>
    لیست و عملگر = (لیست لیست و اعتبار) {</span><span>
      فلاش ()؛</span><span>
      DataNode <T> * walkp = val._head؛</span><span>
      while (walkp) ضمیمه (walkp-> data ()) ؛</span><span>
      بازگشت * این؛</span><span>
    }</span>
<span>
    اپراتور const int == (لیست const و Val) ساختار {</span><span>
      if (isEmpty () && val.isEmpty ()) بازگشت 1؛</span><span>
      DataNode <T> * thisp = _head ،</span><span>
                  * valp = val._head ؛</span><span>
      while (thisp && valp) {</span><span>
        if (thisp-> data ()! = valp-> data ()) بازگشت 0؛</span><span>
        thisp = thisp-> right ()؛</span><span>
        valp = valp-> right ()؛</span><span>
      }</span><span>
      بازگشت 1؛</span><span>
    }</span><span>
    اپراتور const int! = (لیست const و Val) ساختار {</span><span>
      بازگشت! (* این == وال)؛</span><span>
    }</span>
<span>
    کلاس دوست ListIterator <T>؛</span><span>
  }؛</span>

سازندگان عناصر لیست _head و _tail را به NULL که نشانگر NUL در C و C ++ است ، مقدماتی می کنند . شما باید بدانید که چگونه می توانید سایر روش ها را از تجربه برنامه نویسی خود پیاده سازی کنید. در اینجا ما فقط اجرای روش putInFront () را ارائه می دهیم :

<span>  الگو <class T> باطل است</span><span>
  لیست <T> :: putInFront (داده های ساختاری T) {</span><span>
    _head = DataNode جدید <T> (داده ، _head) ؛</span><span>
    اگر (! _ دم) _ دم = _ سر؛</span><span>
  } / * putInFront * /</span>

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

همچنین به نحوه استفاده از مکان یاب T توجه کنید . اگر ما می توانیم یک نمونه کلاس از فهرست الگوی کلاس ایجاد کنیم ، مثلا List img31 - اطلاق شی گرایی در برنامه نویسیint ،img9 - اطلاق شی گرایی در برنامه نویسی این امر همچنین می تواند باعث ایجاد یک نمونه کلاس از DataNode کلاس ، یعنی DataNode img31 - اطلاق شی گرایی در برنامه نویسیint شودimg9 - اطلاق شی گرایی در برنامه نویسی .

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

10.5 اجرای تکرار
در بخش  10.2 ما مفهوم تکرار کننده ها را برای عبور از یک ساختار داده معرفی کرده ایم. تکرار کنندگان باید سه ویژگی را پیاده سازی کنند:

  • عنصر فعلی .
  • عملکرد جانشین .
  • شرط خاتمه .

به طور کلی ، تکرار کننده داده های مرتبط با عنصر فعلی را پی در پی برمی گرداند. بدیهی است که روشی وجود دارد ، مثلاً جریان () که این قابلیت را پیاده سازی می کند. نوع بازگشتی این روش به نوع داده ذخیره شده در ساختار داده خاص بستگی دارد. به عنوان مثال ، هنگام تکرار از فهرست img31 - اطلاق شی گرایی در برنامه نویسیintimg9 - اطلاق شی گرایی در برنامه نویسی نوع بازگشت باید int باشد.

تابع جانشین ، مثلاً succ () ، از اطلاعات اضافی استفاده می کند که در عناصر ساختاری ساختار داده ذخیره می شود. در مثال لیست ما ، این گره ها هستند که داده ها و یک اشاره گر را به همسایه راست خود منتقل می کنند. نوع عناصر سازه ای معمولاً با داده های خام متفاوت است. دوباره لیست img31 - اطلاق شی گرایی در برنامه نویسیint راimg9 - اطلاق شی گرایی در برنامه نویسی در نظر بگیرید که succ () باید از DataNode img31 - اطلاق شی گرایی در برنامه نویسیintimg9 - اطلاق شی گرایی در برنامه نویسی به عنوان عناصر ساختاری استفاده کند.

شرط خاتمه با استفاده از روشی ، مثلاً خاتمه () ، که اگر تمام عناصر داده مربوط به ساختار داده مرتبط بازدید شده باشد ، TRUE را برمی گرداند . تا زمانی که () succ می تواند عنصری را که هنوز بازدید نکرده است پیدا کند ، این روش نادرست را برمی گرداند .

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

<span>  الگو <class Data، class Element></span><span>
  تکرار کننده کلاس {</span><span>
  حفاظت شده:</span><span>
    عنصر _ شروع کنید ،</span><span>
            _جاری؛</span>
    <span>
  عمومی:</span><span>
    تکرار کننده (ساخت شروع عنصر): </span><span>
      _ شروع (شروع) ، _ جریان (شروع) {}</span><span>
    تکرار کننده (سازنده تکرار و اعتبار):</span><span>
      _start (val._start) ، _current (val._current) {}</span><span>
    مجازی ~ تکرار کننده () {}</span>
<span>
    const مجازی جریان داده () const = 0؛</span><span>
    خالی بودن مجازی succ () = 0؛</span><span>
    const مجازی int خاتمه () const = 0؛</span>
<span>
    بازگرداندن خالی مجازی () {_current = _start؛ }</span>
<span>
    تکرار کننده و اپراتور = (تکرار کننده تکرار و اعتبار) {</span><span>
      _start = val._start ؛</span><span>
      _ جریان = val._current؛</span><span>
      بازگشت * این؛</span><span>
    }</span>
<span>
    اپراتور const int == (const تکرار کننده و Val) ساختار {</span><span>
      Return (_start == val._start && _current == val._current)؛</span><span>
    }</span><span>
    عملگر const int! = (تکرار کننده تکرار و اعتبار) ساختار {</span><span>
      بازگشت! (* این == وال)؛</span><span>
    }</span><span>
  }؛</span>

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

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

آخرین مرحله در روند اجرای تکرار کننده ، تکرار لیست است. این تکرار کننده ارتباط زیادی با فهرست الگوی کلاس ما دارد ، به عنوان مثال ، مشخص است که عناصر ساختاری الگوهای کلاس DataNode هستند . تنها نوع “باز” ​​نوع مربوط به داده است. یک بار دیگر ، ما از مکانیزم الگو برای ارائه تکرارکننده های لیست برای انواع مختلف لیست استفاده می کنیم:

<span>  الگو <کلاس T></span><span>
  class ListIterator: تکرار کننده عمومی <T، DataNode <T> *> {</span><span>
  عمومی:</span><span>
    ListIterator (لیست لیست <T> و لیست):</span><span>
      Iterator <T، DataNode <T> *> (list._head) {}</span><span>
    ListIterator (ساختار ListIterator و موج):</span><span>
      Iterator <T، DataNode <T> *> (val) {}</span>
<span>
    ساختار مجازی T جریان () const {بازگشت _ جریان-> داده ()؛ }</span><span>
    void virtual succ () {_current = _current-> right ()؛ }</span><span>
    مجازی const int خاتمه () ساختار {</span><span>
      بازگشت _ جریان == NULL؛</span><span>
    }</span>
<span>
    T و عملگر ++ (int) {</span><span>
    	T & tmp = _current-> داده ()؛</span><span>
    	succ ()؛</span><span>
    	بازگشت tmp؛</span><span>
    }</span>
<span>
    ListIterator & عملگر = (ساختار ListIterator & val) {</span><span>
      تکرار کننده <T ، DataNode <T> *> :: عملگر = (val) ؛</span><span>
      بازگشت * این؛</span><span>
    }</span><span>
  }؛</span>

الگوی کلاس ListIterator از Iterator گرفته شده است . نوع داده البته نوعی است که لیست کننده تکرار لیست برای آن اعلام می شود ، از این رو مکان نگهدار T را برای نوع داده تکرار کننده داده درج می کنیم . روند تکرار با کمک عناصر ساختاری نوع DataNode حاصل می شود . بدیهی است که عنصر شروع ، رئیس لیست _head است که از نوع DataNode img31 - اطلاق شی گرایی در برنامه نویسیimg9 - اطلاق شی گرایی در برنامه نویسی* است . ما این نوع را برای نوع عنصر Element انتخاب می کنیم .

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

اولین سازنده لیست را به عنوان آرگومان خود عبور داده و قسمت تکرار کننده آن را بر اساس آن مقدار اولیه می کند. از آنجا که هر ListIterator img31 - اطلاق شی گرایی در برنامه نویسیTimg9 - اطلاق شی گرایی در برنامه نویسی یکی از دوستان List img31 - اطلاق شی گرایی در برنامه نویسیTimg9 - اطلاق شی گرایی در برنامه نویسی است ، به اعضای خصوصی لیست دسترسی دارد. ما از این استفاده می کنیم تا تکرار کننده را با اشاره به سر لیست ، شروع کنیم.

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

روش های بعدی فقط سه ویژگی مورد نیاز را تعریف می کنند. اکنون که عناصر ساختاری تعریف شده به عنوان DataNode img31 - اطلاق شی گرایی در برنامه نویسیimg9 - اطلاق شی گرایی در برنامه نویسی* داریم ، از آنها به صورت زیر استفاده می کنیم:

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

    1
    لیست خالی است در این مورد عنصر فعلی است در حال حاضر NULL زیرا لیست سر _head است NULL .
    2
    عنصر فعلی به آخرین عنصر رسید. در این حالت فراخوانی تابع جانشین قبلی ، عنصر فعلی را در همسایه سمت راست آخرین عنصر که NULL است تنظیم می کند .

ما همچنین یک اپراتور اضافه بار “++” اضافه کرده ایم. برای متمایز کردن این اپراتور از اپراتور preincrement ، یک استدلال صحیح اضافی (ناشناس) می گیرد. از آنجا که ما فقط از این آرگومان برای اعلام نمونه اولیه عملگر صحیح استفاده می کنیم و چون از مقدار آرگومان استفاده نمی کنیم ، نام آرگومان را حذف می کنیم.

آخرین روش عملگر انتساب بیش از حد مجاز برای تکرارکنندگان لیست است. مشابه اپراتورهای انتساب قبلی ، ما فقط از تکالیف فوق کلاس های تعریف شده قبلی استفاده مجدد می کنیم. Iterator img31 - اطلاق شی گرایی در برنامه نویسیimg9 - اطلاق شی گرایی در برنامه نویسی:: عملگر = () در این مورد.

سایر متدها و عملگرها ، یعنی rewind () ، عملگر == () و عملگر! = () همه از کلاس الگو Iterator به ارث برده می شوند .

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

<span>  int</span><span>
  اصلی () {</span><span>
    لیست <int> لیست؛</span><span>
    int ix ؛</span>
<span>
    برای (ix = 0؛ ix <10؛ ix ++) list.append (ix)؛</span>
    <span>
    ListIterator <int> iter (لیست)؛</span><span>
    while (! iter.terminate ()) {</span><span>
      printf ("٪ d"، iter.current ())؛</span><span>
      iter.succ ()؛</span><span>
    }</span><span>
    قرار می دهد ("")؛</span><span>
    بازگشت 0؛</span><span>
  }</span>

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

<span>  while (! iter.terminate ()) </span><span>
    چاپ ("٪ d" ، تکرار ++) ؛</span>

10.7 بحث

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

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

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

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

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

یک راه حل این است که استراتژی های اصلاح توسط کلاس های “خارجی” به عنوان تکرار کننده ها اجرا نمی شوند. در عوض ، آنها توسط وراثت اجرا می شوند. در صورت نیاز به یک لیست مرتب شده ، این یک لیست تخصصی از لیست عمومی است. این لیست مرتب شده یک متد اضافه می کند ، مثلاً insert () ، که یک عنصر جدید را با توجه به استراتژی اصلاح وارد می کند.

برای ایجاد این امکان ، الگوی لیست ارائه شده باید تغییر کند. زیرا اکنون ، کلاسهای مشتق شده باید به گره سر و دم دسترسی داشته باشند تا این استراتژی ها را پیاده سازی کنند. در نتیجه ، _head و _ tail باید محافظت شوند .

10.7.2 تکرار کنندگان
اجرای تکرار کننده ارائه شده فرض می کند که ساختار داده در طول استفاده از تکرار کننده تغییر نکند. برای توضیح این مثال زیر را در نظر بگیرید:

<span>  لیست <int> ilist؛</span><span>
  int ix ؛</span>
<span>
  برای (ix = 1؛ ix <10؛ ix ++) </span><span>
    ilist.append (ix) ؛</span>
<span>
  ListIterator <int> iter (ilist)؛</span>
<span>
  while (! iter.terminate ()) {</span><span>
    printf ("٪ d"، iter.current ())؛</span><span>
    iter.succ ()؛</span><span>
  }</span><span>
  printf ("\ n") ؛</span>
<span>
  ilist.putInFront (0)؛</span>
<span>
  iter.rewind ()؛</span><span>
  while (! iter.terminate ()) {</span><span>
    printf ("٪ d"، iter.current ())؛</span><span>
    iter.succ ()؛</span><span>
  }</span><span>
  printf ("\ n") ؛</span>

این قطعه کد چاپ می کند

<span>  1 2 3 4 5 6 7 8 9</span><span>
  1 2 3 4 5 6 7 8 9</span>

بجای

<span>  1 2 3 4 5 6 7 8 9</span><span>
  0 1 2 3 4 5 6 7 8 9</span>

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

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

10.8 تمرین

1
مشابه تعریف اپراتور postincrement در کلاس الگو ListIterator ، می توان یک عملگر preincrement را به صورت زیر تعریف کرد:

<span>	  T و عملگر ++ () {</span><span>
	    succ ()؛</span><span>
	    Return _current-> data ()؛</span><span>
	  }</span>

چه مشکلاتی پیش می آید؟

2
روش زیر را اضافه کنید

<span>	  int حذف (ساختار T و داده)؛
</span>

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

3
یک الگوی کلاس CountedList را از فهرست بگیرید که عناصر آن را شمارش می کند. یک متد شمارش () از نوع دلخواه اضافه کنید که تعداد واقعی عناصر ذخیره شده در لیست را برمی گرداند. سعی کنید تا حد امکان از لیست استفاده مجدد کنید .

4
در مورد مسئله تکرار کننده که در بخش 10.7 بحث شده است  . راه حل های ممکن برای اجازه دادن به تغییر لیست در حالی که تکرار کننده آن استفاده می شود چیست؟

منابع

1
بورلند بین المللی، شرکت
راهنمای برنامه نویس .
بورلند اینترنشنال ، شرکت ، 1993

2
اوته کلاوسن
برنامه نویسی شی گرا .
Springer Verlag ، 1993.
شابک 3-540-55748-2.

3
ویلیام فورد و ویلیام تاپ.
ساختارهای داده با C ++ .
Prentice-Hall، Inc.، 1996.
شابک 0-02-420971-6.

4
برایان د. کرنیگان و دنیس ام. ریچی.
زبان برنامه نویسی C .
Prentice-Hall، Inc.، 1977

5
دنیس ام. ریچی.
توسعه و زبان C .
در دومین کنفرانس تاریخچه زبانهای برنامه نویسی ، کمبریج ، ماساچوست ، آوریل 1993.

6
بارنه اشتروستروپ.
برنامهنویسی ++ C زبان .
آدیسون-وسلی ، چاپ دوم ، 1991.
ISBN 0-201-53992-6.

راه حلهایی برای تمرینات
 این بخش به عنوان مثال راه حل تمرینات سخنرانی های قبلی را ارائه می دهد.

A.1 بررسی تکنیک های برنامه نویسی

1
بحث ماژول Single-Linked-List-2 .

(آ)
تعریف رابط ماژول Integer-List

<span>   لیست کامل ماژول</span>
<span>
   اعلام نوع int_list_handle_t؛</span>
<span>
   int_list_handle_t int_list_create ()؛</span><span>
   BOOL int_list_append (int_list_handle_t این ،</span><span>
                           داده های int)</span><span>
   INTEGER int_list_getFirst (int_list_handle_t this)؛</span><span>
   INTEGER int_list_getNext (int_list_handle_t this)؛</span><span>
   BOOL int_list_isEmpty (int_list_handle_t this)؛</span>
   <span>
   لیست انتگرال پایان؛</span>

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

<span>   در حالی که داده معتبر است
</span>

داده ها با فراخوانی به list_getFirst () مقداردهی اولیه شدند . لیست عدد صحیح روش int_list_getFirst () باز می گردد یک عدد صحیح، در نتیجه، چنین چیزی مانند “ عدد صحیح نامعتبر “” که ما می تواند برای مقابله حلقه ختم استفاده وجود دارد.

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

A.2 انواع داده های انتزاعی

1
عدد صحیح ADT .

(آ)
هر دو عمل add و sub را می توان برای هر مقداری که N نگه داشته باشد اعمال کرد . بنابراین ، این عملیات را می توان در هر زمان اعمال کرد: هیچ محدودیتی در استفاده از آنها وجود ندارد. با این حال ، می توانید این را با پیش شرطی برابر کنید که برابر با درست باشد .

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

<span>  بسیاری (k)</span><span>
  div (k)</span><span>
  شکم ()</span>

این عملیات MUL هیچ پیش شرط نیاز ندارد. این مشابه اضافه و فرعی است . البته شرط پسین res  =  N * k است . عملیات بعدی DIV نیاز به ک نمی شود 0 (صفر). در نتیجه ، ما پیش شرط زیر را تعریف می کنیم: k برابر 0 نیست. آخرین عمل abs مقدار N را در صورت مثبت بودن N یا 0 یا – N در صورت منفی بودن N را برمی گرداند . باز هم مهم نیست که مقدار N چیستوقتی این عملیات اعمال می شود در اینجا شرایط پس زمینه آن وجود دارد:

اگر N > = 0 باشد
abs = N
دیگر
abs = – N

2
کسر ADT .

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

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

  • برای بدست آوردن مقدار nominator / مخرج ،
  • برای تعیین مقدار nominator / مخرج ،
  • برای جمع کردن کسری که جمع را جمع می کند ،
  • برای کسر فرعی کسری که اختلاف را برمی گرداند ،

(ج)
در اینجا برخی از بدیهیات و پیش شرط های هر کسری که برای ADT نیز وجود دارد ، آورده شده است:

  • مخرج نباید برابر 0 (صفر) باشد ، در غیر این صورت مقدار کسر تعریف نشده است.
  • اگر nominator 0 (صفر) باشد مقدار کسر برای هر مقدار مخرج 0 است.
  • هر عدد کامل را می توان با کسری نشان داد که نام گذار عدد و مخرج آن 1 است.

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

4
ما باید بدیهیات را بیان کرده و پیش شرط هایی را برای اطمینان از استفاده صحیح از موارد ADT ها تعریف کنیم. به عنوان مثال ، اگر 0 را به عنوان عنصر خنثی جمع اعداد صحیح اعلام نکنیم ، یک عدد عددی ADT وجود دارد که هنگام افزودن 0 به N کار عجیبی را انجام می دهد . این چیزی نیست که از یک عدد صحیح انتظار می رود. بنابراین ، بدیهیات و پیش شرط ها ابزاری را برای اطمینان از عملکرد “ADT” مطابق میل ما فراهم می کنند.

5
شرح روابط

(آ)
یک نمونه یک نماینده واقعی یک ADT است. بنابراین “مثالی” از آن است. در مواردی که ADT اعلام می کند از “یک عدد کامل امضا شده” به عنوان ساختار داده خود استفاده می کند ، یک نمونه در واقع دارای یک مقدار است ، مثلا “-5”.
(ب)
ADT های عمومی همان خصوصیات ADT مربوطه را تعریف می کنند. با این حال ، آنها به نوع خاصی دیگر اختصاص یافته اند. به عنوان مثال ، لیست ADT خصوصیات لیست ها را تعریف می کند. بنابراین ، ممکن است یک ضمیمه عملیاتی (elem) داشته باشیم که عنصر elem جدیدی را به لیست اضافه کند. ما در مورد نوع elem در واقع نمی گوییم ، فقط این که بعد از این عملیات آخرین عنصر لیست خواهد بود. اگر اکنون از یک لیست عمومی ADT استفاده می کنیم ، نوع این عنصر مشخص است: توسط پارامتر عمومی ارائه می شود.
(ج)
نمونه های همان ADT عمومی را می توان به عنوان “خواهر و برادر” مشاهده کرد. اگر هر دو ADT عمومی ADT یکسان داشته باشند ، آنها “پسر عموی” نمونه هایی از ADT عمومی دیگر خواهند بود.

A.3 مفاهیم شی گرا

1
کلاس

(آ)
کلاس اجرای واقعی از یک ADT است. به عنوان مثال ، یک ADT برای اعداد صحیح ممکن است شامل عملیاتی باشد که برای تعیین مقدار نمونه خود تنظیم شده است. این عملیات در زبانهایی مانند C یا Pascal به طور متفاوتی اجرا می شود. در C علامت برابر “=” عملکرد مجموعه ای را برای اعداد صحیح مشخص می کند ، در حالی که در پاسکال از رشته کاراکتر “: =” استفاده می شود. در نتیجه ، کلاسها با ارائه روشها عملیات را اجرا می کنند . به طور مشابه ، ساختار داده ADT توسط ویژگی های کلاس پیاده سازی می شود .

(ب)
مجموعه کلاس

<span>  مجتمع کلاس {</span><span>
  ویژگی های:</span><span>
    واقعی واقعی ،</span><span>
         خیالی</span>
<span>
  مواد و روش ها:</span><span>
    : = (مجتمع c) / * مقدار را روی مقدار c * قرار دهید</span><span>
    realPart واقعی ()</span><span>
    قسمت واقعی ()</span><span>
    Complex + (Complex c)</span><span>
    مجتمع - (مجتمع ج)</span><span>
    مجتمع / (مجتمع ج)</span><span>
    مجتمع * (مجتمع ج)</span><span>
  }</span>

ما نمادهای عملگر شناخته شده “+” را برای جمع ، “-” برای تفریق ، “/” برای تقسیم و “*” را برای ضرب انتخاب می کنیم تا عملیات مربوطه از مجموعه ADT انجام شود . بنابراین ، اشیا of کلاس Complex می توانند مانند موارد زیر استفاده شوند:

<span>  مجتمع c1 ، c2 ، c3</span><span>
  c3: = c1 + c2</span>

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

<span>  c3: = c1. + (c2)
</span>

ممکن است بخواهید “+” را با “dadd” جایگزین کنید تا به نمایشی برسید که تاکنون استفاده کرده ایم. با این حال ، باید بتوانید درک کنید که “+” چیزی غیر از نام متفاوت برای “ʻadd” نیست.

2
تعامل اشیا

3
نمای شی.

4
پیام ها.

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

(ب)
اینترنت چندین اشیا provides را فراهم می کند. دو مورد از شناخته شده ترین آنها “مشتری” و “سرور” است. به عنوان مثال ، شما برای دسترسی به داده های ذخیره شده در سرور FTP (شی) از سرویس گیرنده FTP (شی) استفاده می کنید. بنابراین ، می توانید این را به گونه ای مشاهده کنید که گویا سرویس گیرنده “پیامی را به سرور می فرستد که درخواست داده های ذخیره شده در آنجا را دارد.

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

A.4 مفاهیم شی گرا بیشتر

1
وراثت.

(آ)
تعریف کلاس مستطیل :

<span>  مستطیل کلاس از نقطه {به ارث می برد</span><span>
  ویژگی های:</span><span>
    عرض _ عرض ، // عرض مستطیل</span><span>
        _ ارتفاع // ارتفاع مستطیل</span>
<span>
  مواد و روش ها:</span><span>
    setWidth (int newWidth)</span><span>
    getWidth ()</span><span>
    setHeight (int newHeight)</span><span>
    getHeight ()</span><span>
  }</span>

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

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

(ب)
اشیا 3D سه بعدی کره را یک مرکز در فضای سه بعدی و شعاع تعریف می کند. مرکز یک نقطه در فضای سه بعدی است ، بنابراین ، ما می توانیم کلاس Sphere را به صورت زیر تعریف کنیم :

<span>  class Sphere از 3D-Point به ارث می برد {</span><span>
  ویژگی های:</span><span>
    int _radius؛</span>
<span>
  مواد و روش ها:</span><span>
    setRadius (int newRadius)</span><span>
    getRadius ()</span><span>
  }</span>

این شبیه کلاس دایره برای فضای 2 بعدی است. اکنون ، 3D-Point فقط یک نقطه با بعد اضافی است:

<span>  کلاس 3D-Point از نقطه {به ارث می برد</span><span>
  ویژگی های:</span><span>
    int _z؛</span>
<span>
  مواد و روش ها:</span><span>
    setZ (int newZ) ؛</span><span>
    getZ () ؛</span><span>
  }</span>

در نتیجه ، 3D-Point و Point با یک رابطه وجود دارد.

(ج)
عملکرد حرکت () . حرکت () همانطور که در بخش تعریف شده است ، اجازه می دهد تا اشیا سه بعدی در محور X حرکت کنند ، بنابراین فقط در یک بعد. این کار را فقط با اصلاح قسمت 2 بعدی اشیا 3D سه بعدی انجام می دهد. این قسمت 2D توسط کلاس Point به طور مستقیم یا غیرمستقیم توسط اشیا 3D سه بعدی به ارث برده می شود.

(د)
نمودار ارث (شکل A.1 را ببینید  ).

شکل A.1:   نمودار ارث برخی اشیا draw قابل ترسیم.

img32 - اطلاق شی گرایی در برنامه نویسی

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

2
ارث چندگانه. نمودار وراثت در شکل  5.9 بدیهی است که تعارضات نامگذاری را براساس خصوصیات کلاس A معرفی می کند .با این حال ، این خصوصیات با دنبال کردن مسیر از D به A به طور منحصر به فرد مشخص می شوند . بنابراین ، D می تواند با دنبال کردن مسیر وراثت از طریق B ، خصوصیات A را که توسط B به ارث برده شود ، تغییر دهد . به همین ترتیب ، D می تواند با دنبال کردن مسیر وراثت از طریق C ، خصوصیات A را که توسط C به ارث می رود تغییر دهد . در نتیجه ، این تضاد نامگذاری لزوماً منجر به خطا نمی شود ، به شرطی که مسیرها مشخص شده باشد.

A.5 اطلاعات بیشتر در مورد C ++

1
پلی مورفیسم. هنگام استفاده از امضا

<span>  نمایشگر خالی (ساختار DrawableObject obj)
</span>

توجه داشته باشید اول ، که در C ++ تابع یا روش پارامترها از نظر مقدار عبور می کنند. در نتیجه ، obj یک کپی از استدلال واقعی فراخوانی عملکرد ارائه شده است. این بدان معنی است که DrawableObject باید کلاسی باشد که بتوان از آن اشیا را ایجاد کرد. این نه مورد، اگر DrawableObject یک کلاس انتزاعی است (به عنوان آن است که چاپ () به عنوان روش خالص تعریف شده است.)اگر یک چاپ متد مجازی وجود دارد () که توسط کلاس DrawableObject تعریف شده است ، بنابراین (چون obj فقط یک کپی از استدلال واقعی است) این روش فراخوانی می شود. این روشی نیست که توسط کلاس استدلال واقعی تعریف شود (زیرا دیگر هیچ نقشی مهم ندارد!)

A.6 لیست – مطالعه موردی

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

<span>T و عملگر ++ () {</span><span>
  succ ()؛</span><span>
  Return (_current؟ _current-> data (): (T) 0)؛</span><span>
}</span>

با این حال ، این عملکردی ندارد زیرا اکنون چیزی در مورد T فرض می کنیم . باید بتوان آن را به نوعی مقدار “NULL” انداخت.

2
افزودن روش حذف. ما راه حل کد را نمی دهیم. در عوض الگوریتم می دهیم. روش remove () باید روی لیست تکرار شود تا زمانی که به عنصری با مورد داده درخواستی برسد. سپس این عنصر را پاک کرده و 1 را برمی گرداند. اگر لیست خالی است یا اگر مورد داده یافت نشد ، 0 (صفر) برمی گرداند.در طی تکرار ، حذف () باید مورد داده ارائه شده را به طور پی در پی با موارد موجود در لیست مقایسه کند. در نتیجه ، ممکن است مقایسه ای مانند این وجود داشته باشد:

<span>  if (data == current-> data ()) {</span><span>
    // مورد را پیدا کرد</span><span>
  }</span>

در اینجا ما از عملگر معادله ، “==” برای مقایسه هر دو مورد داده استفاده می کنیم. از آنجا که این موارد می توانند از هر نوع باشند ، به ویژه می توانند اشیایی از کلاسهای تعریف شده توسط کاربر باشند. س isال این است: “برابری” برای انواع جدید چگونه تعریف می شود؟ در نتیجه ، برای اجازه دادن به remove () به درستی کار می کند ، لیست باید فقط برای انواع استفاده شده به طور صحیح عملگر مقایسه (یعنی ، “==” و “! =”) تعریف شود. در غیر این صورت ، از مقایسه های پیش فرض استفاده می شود ، که ممکن است به نتایج عجیبی منجر شود.

3
Class CountedList . لیست شمارش شده لیستی است که تعداد عناصر موجود در آن را ردیابی می کند. بنابراین ، هنگامی که یک مورد داده اضافه می شود ، تعداد یک افزایش می یابد ، هنگامی که یک مورد حذف می شود ، یک مورد کاهش می یابد. باز هم ، ما اجرای کامل را نمی دهیم ، بلکه ترجیح می دهیم یک روش ( ضمیمه ) و نحوه تغییر آن را نشان دهیم:

<span>  countedList کلاس: لیست عمومی {</span><span>
    int _count؛ // تعداد عناصر</span><span>
    ...</span><span>
  عمومی:</span><span>
    ...</span><span>
    ضمیمه باطل مجازی (داده های ثابت T) {</span><span>
      _ حساب ++؛ // آن را افزایش دهید و ...</span><span>
      لیست :: ضمیمه (داده) // ... use list ضمیمه</span><span>
    }</span><span>
    ...</span><span>
  }</span>

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

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

منبع : www.tiem.utk.edu

حسین برخورداری سوال پاسخ داده شده اکتبر 13, 2020
گذاشتن نظر
شما در حال مشاهده 1 از 1 پاسخ هستید ، برای دیدن همه پاسخها اینجا را کلیک کنید .
پاسخ خود را بنویسید .
  • فعال
  • بازدیدها2082 times
  • پاسخ ها1 پاسخ
ورود به متاورس | متاورس ایرانی
ورود به متاورس ایران یا همان متاورس ملی

علامت ذره بین Tutorials سمت راست به رنگ قرمز به شما کمک خواهد کرد .

جدید ترین سوالات پرسیده شده

انتخاب ورزش رزمی 0 پاسخ ها | 1 رای
وزارت تعاون کار و رفاه اجتماعی نماد اعتماد الکترونیک اسناد و املاک کشور مرکز آموزش ویدیویی انجمن حم فروشگاه ملی تولید کنندگان مدیریت بر مدیران حم سامانه حیوانات رسانه ملی اخبار متا دانشگاه متاورس استخدام | دانش فروشگاه حم تبلیغات ملی بازار NFT متاورس رنگ نقشه ملی املاک و مستغلات