PDA

View Full Version : مقاله: کلاس اعداد مختلط به همراه سربارگذاری عملگرها



Salar Ashgi
چهارشنبه 06 شهریور 1387, 16:28 عصر
سلام به همه دوستان ، این برنامه که تو سی پلاس پلاس نوشتم ، مثالی از کلاس ها و

سر بارگذاری عملگرها (برنامه نویسی شی گرا ء) میباشد !!!! کد جالب و آموزنده ای برای

کسانی که میخواهند با برنامه نویسی شی گرا ء آشنا شوند ، میباشد !!!!

کد برنامه خدمت شما عزیزان :


#include <iostream>
#include <conio>
#include <math>
class Complex{
friend ostream &operator<<(ostream&,Complex&);
friend istream &operator>>(istream&,Complex&);
friend Complex operator+(Complex&,Complex&);
friend Complex operator-(Complex&,Complex&);
friend Complex operator*(Complex&,Complex&);
friend Complex operator/(Complex&,Complex&);
friend Complex operator~(Complex&);//jazr
public:
Complex operator=(Complex);// set
int operator==(Complex&);
int operator!=(Complex&);
Complex();
private:
double R,I;
};
Complex::Complex(){
I=0; R=0;
}
ostream &operator<<(ostream &output,Complex &f){
output<<f.R<<"+"<<f.I<<"i\n";
return output;
}
istream &operator>>(istream &input,Complex &f){
cout<<"Enter R :\n";
cin>>f.R;
cout<<"Enter I :\n";
cin>>f.I;
return input;
}
Complex operator+(Complex &a,Complex &b){
Complex c;
c.R = a.R + b.R;
c.I = a.I + b.I;
return c;
}
Complex operator*(Complex &a,Complex &b){
Complex c;
c.R = (a.R * b.R - a.I * b.I);
c.I = (a.R * b.I + a.I * b.R);
return c;
}
Complex operator/(Complex &a,Complex &b){
Complex c;
double k = pow(b.R,2)+pow(b.I,2);
c.R = (double)(a.R * b.R + a.I * b.I)/k;
c.I = (double)(a.I * b.R - a.R * b.I)/k;
return c;
}
Complex operator-(Complex &a,Complex &b){
Complex c;
c.R = a.R - b.R;
c.I = a.I - b.I;
return c;
}
Complex Complex::operator=(Complex a){
R = a.R;
I = a.I;
return *this;
}
int Complex::operator==(Complex &f){
if(R==f.R && I==f.I)
return 1;
else
return -1;
}
Complex operator~(Complex &a){
Complex c;
double r = sqrt(pow(a.I,2)+pow(a.R,2));
c.R = sqrt((r+a.R)/2);
c.I = sqrt((r-a.R)/2);
return c;
}
int Complex::operator!=(Complex &f){
if(R!=f.R || I!=f.I)
return 1;
else
return -1;
}
int main(){
Complex a,b;
Complex c,d,e,f,g,h,i;
cin>>a>>b;
cout<<"-------------------\n";
c=a+b;
d=a-b;
e=a*b;
f=a/b;
g=a;
h=~a;
cout<<"Check Equvaliance of a & b :\n";
int ans=(a==b);
cout<<ans<<endl;
cout<<"-------------------\n";
cout<<"Check a!=b \n";
int ans2=(a!=b);
cout<<ans2<<endl;
cout<<"-------------------\n";
cout<<"Add:\n";
cout<<c;
cout<<"-------------------\n";
cout<<"Tafazol:\n";
cout<<d;
cout<<"-------------------\n";
cout<<"Product:\n";
cout<<e;
cout<<"-------------------\n";
cout<<"Division\n";
cout<<f;
cout<<"-------------------\n";
cout<<"Set By a:\n";
cout<<g;
cout<<"-------------------\n";
cout<<"Jazre a:\n";
cout<<h;
cout<<"-------------------\n";
getch();
}

موفق و پیروز باشید !!!!:لبخندساده:

A_Salimi
پنج شنبه 07 شهریور 1387, 10:41 صبح
سلام دوست من

ممنون از اینکه این برنامه رو قرار دادید ولی بهتر بود که بیشتر توضیح میدادید این برنامه دقیقا چه کاری رو انجام میده ؟
کلا در مورد بارگذاری مجدد عملگرها مشکلاتی دارم که میخواستم اگه برای شما و یا دوست دیگری مقدوره به من جواب بدید :

اول اینکه در سربارگذاری مجدد عملگر >> و<< ostream & دقیقا چه مفهومی داره ؟




friend ostream &operator<<(ostream&,Complex&);


و &ی که قبل از operator میاد چه معنایی داره ؟

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

مثلا چه موقع مقدار بازگشتی یک شی از کلاس است ؟ و یا چه زمانی از *this استفاده میکنیم ؟

Overload بعضی دیگر از عملگر ها هم مشکلاتی دارم ... مثلا چرا در Overload عملگر new با اینکه تعریف آن از تابع void استفاده میکنیم آن تابع دارای مقدار بازگشتی و return است ؟ و چرا قبل از
operator از * استفاده میکنیم ؟



Void *operator new (size_t size){
return pointer_to_memory;
}


با تشکر

Salar Ashgi
پنج شنبه 07 شهریور 1387, 14:08 عصر
اول سلام ،


ممنون از اینکه این برنامه رو قرار دادید ولی بهتر بود که بیشتر توضیح میدادید این برنامه دقیقا چه کاری رو انجام میده ؟


فکر کنم کسی که با برنامه نویسی شی گرا ء آشنا باشه ، کد واسش کاملا گویاست ، حال

آنکه توضیحاتی در داخل کد داده شده است !!!! برنامه نویس باید بتونه با دیدن یک کد نحوه

اجرا و الگوریتم اونو درک کنه !!!


اول اینکه در سربارگذاری مجدد عملگر >> و<< ostream & دقیقا چه مفهومی داره ؟


>> همان دستور cout از کلاس ostream میباشد و >> برای سربارگذاری این عملگر است

<< همان دستور cin از کلاس istream میباشد و << برای سربارگذاری این عملگر است !!!

& در سی پلاس پلاس به دو معناست : 1)آدرس یک متغیر 2) ارجاع به یک متغیر

در اینجا مورد دوم برقرار است !!!

و کد
friend ostream &operator<<(ostream&,Complex&);

یک ارجاع به شی ای از کلاس ostream ایجاد میکند و در این حال cout بجای ostream قرار

میگیرد !!! و می دانیم که تابع برای سربارگذاری ورودی و خروجی روی اشیا باید تابع دوست

باشد !!!! گذاشتن علامت refrence (&) در اینجا برای فعال کردن خاصیت Cascading ، یعنی

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


cin>>a>>b>>c>>d>>...
cout<<a<<b<<c<<d...



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

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

سربارگذاری شده جمع باید یک شی از این نوع برگرداند !!!!


چه زمانی از *this استفاده میکنیم ؟

this یک اشاره گر به شی ای است که داریم آنرا فراخوانی می کنیم ، باید دقت کنیم که

این اشاره گر را نمی توان برای توابع دوست و توابع ایستا (static ) بکار برد !!!!


چرا در Overload عملگر new با اینکه تعریف آن از تابع void استفاده میکنیم آن تابع دارای مقدار بازگشتی و return است ؟ و چرا قبل از
operator از * استفاده میکنیم ؟


به خاطر آنکه برگشتی این تابع ، خالی نیست بلکه یک اشاره گر به void است !!! به همین

دلیل از * استفاده می کنیم و دارای return است !!!!

========================================

موفق و پیروز باشید !!! در پناه حق تعالی ، انشاالله !!!

سوالی اگه بود باز در خدمتم !!!

C++Lover
پنج شنبه 07 شهریور 1387, 14:42 عصر
salar_cpp_cs عزیز کد شما شفاف و درست است.
اما بهتر است موارد زیر را هم رعایت کنید.

1. پارامترهای ورودی برای شفاف سازی و جلوگیری از اشتباهات بهتر است &const type تعریف شوند. برای نمونه:


Complex operator+(const Complex &a,const Complex &b)


2. مقدار دهی به شئی همیشه از طریق IStream انجام نمی شود. ممکن است استفاده کننده بخواهد شئی را در داخل برنامه مقدار دهی کند. پس بهتر بود یک Constructor هم برای این منظور می گذاشتید. در برنامه شما چون این Constructor وجود ندارد و داده های کلاس نیز private تعریف شده اند اصلا راهی برای مقداردهی از داخل برنامه وجود ندارد. این Constructor می تواند به این شکل باشد.

Complex(double i, double r)

3. برای overload کردن =operator هم درستش اینه:


const Complex& Complex::operator=(const Complex& a){
R = a.R;
I = a.I;
return *this;
}

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

4. دو عملگر == و =! باید به صورت زیر const تعریف شوند. چون این دو عملگر هیج تغییری روی خود شئی نمی دهند و در توابعی که پارامتر ورودی به صورت const دارند ایجاد مشکل می کند.


int operator==(const Complex&) const;
int operator!=(const Complex&) const;


5. و در آخر اینکه باید تکلیف exception ها هم مشخص بشه.

پیروز باشید.

A_Salimi
پنج شنبه 07 شهریور 1387, 20:18 عصر
با تشکر از پاسخهای جناب salar_cpp_cs

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




#include <iostream.h>
#include <conio.h>
class myClass {
public:
int x;
myClass *operator ->() { return this; }
};
//***********
int main()
{
clrscr();
myClass ob ;
ob -> x = 10; // same as ob.x
cout << "ob.x=" << ob.x << ", ob-> x =" << ob -> x;
getch();
return 0;
}

در اینجا عملگر -> بارگذاری مجدد شده .چرا کنار کلمه کلیدی operator ، * داریم ؟ و چرا اینجا this داریم و نه *this ؟
ممکنه یک توضیح کلی در این مورد بدهید ؟
در مورد اشاره گر به void هم توضیحاتی داده بودید . . . ممکنه بیشتر توضیح بدید که اشاره گر به void
چه معنایی دارد ؟

با تشکر

bsng110
پنج شنبه 07 شهریور 1387, 22:31 عصر
سلام دوست من<br />
<br />
ممنون از اینکه این برنامه رو قرار دادید ولی بهتر بود که بیشتر توضیح میدادید این برنامه دقیقا چه کاری رو انجام میده ؟<br />
کلا در مورد بارگذاری مجدد عملگرها مشکلاتی دارم که میخواستم اگه برای شما و یا دوست دیگری مقدوره به من جواب بدید :<br />
<br />
اول اینکه در سربارگذاری مجدد عملگر &gt;&gt; و&lt;&lt; ostream &amp; دقیقا چه مفهومی داره ؟<br />
<br />
<br />
<div align="left">
friend ostream &amp;operator&lt;&lt;(ostream&amp;,Complex&amp;);</div> <br />
<br />
و &amp;ی که قبل از operator میاد چه معنایی داره ؟<br />
<br />
در مورد مقادیری هم که این توابع بارگذاری مجدد بر میگردونن مشکل دارم :<br />
<br />
مثلا چه موقع مقدار بازگشتی یک شی از کلاس است ؟ و یا چه زمانی از *this استفاده میکنیم ؟<br />
<br />
Overload بعضی دیگر از عملگر ها هم مشکلاتی دارم ... مثلا چرا در Overload عملگر new با اینکه تعریف آن از تابع void استفاده میکنیم آن تابع دارای مقدار بازگشتی و return است ؟ و چرا قبل از <br />
operator از * استفاده میکنیم ؟<br />
<br />
<div align="left">
Void *operator new (size_t size){<br />
return pointer_to_memory;<br />
}</div><div align="right"><br />
با تشکر<br />
</div><br />
<br />
هوالحکیم <br />
سلام <br />
با اجازه دوستان، بنده نیز کمی کامل تر پاسخ سوالات شما را می دهم. عملگرهای << و >> در اصل عملگر های شیفت دودویی اعداد می باشند و مثلاً 3<<22 به این معناست که عدد 22 دهدهی را به صورت باینری در نظر گرفته و سه مرتبه به سمت راست شیفت حسابی دهیم! می دانید که 22 دهدهی برابر است با 10110 بنابراین پس از سه مرتبه شیفت به سمت راست 10 باقی می ماند. و یا عملگر >> به معنای شیفت حسابی به سمت چپ می باشد بنابراین 2>>5 برابر است با 20 دهدهی! چگونه؟ همانطور که می دانید، شیفت به سمت راست، مانند تقسیم بر دو عمل می کند و شیفت به سمت چپ مانند ضرب در دو عمل می کند بنابراین دو مرتبه شیفت به سمت چپ معادل ضرب در چهار می باشد و 4*5 هم برابر است با 20! می توانید بیتهای 5 را نیز که برابر است با 101 دو مرتبه به سمت چپ شیفت دهید(10100) و معادل دهدهی آن را بدست آورید که جواب همان 20 می باشد.
اما در کلاس جریان ها یا stream ها، از این دو عملگر برای درج و حذف یا به عبارتی خواندن و نوشتن در جریان ها استفاده می شود. می دانید که در دستور cin>>a در اصل cin یک جریان می باشد که این جریان، داده هایی را در خود ذخیره می کند و به محض اینکه ما بخواهیم، این داده ها را به ما ارائه می دهد. در اصل یک واسط برای ما می باشد. جریان ها در اتصلات شبکه، خواندن و نوشتن در فایل ها، پورت ها و ... خیلی استفاده می شوند. عملگر << را عملگر استخراج از جریان گویند چرا که اگر به استفاده آن دقت کنید(<<cin) مثل این است که از جریان در حال استخراج یک چیزی هستیم و عملگر >> را عملگر درج در جریان گویند(باز هم به نحوه استفاده آن دقت کنید: >>cout )! حال چنانچه بخواهیم عملگر های درج و استراخ از جریان ها را برای کلاسی گرانبار کنیم، همانطور که می دانید، شیء سمت چپ این عملگر ها، یک جریان یا stream می باشد بنابراین مجبوریم که از توابع دوست(friend) برای این عمل استفاده کنیم چرا که شیء سمت چپ عملگر، از کلاس ما نمی باشد و از کلاس جریان ها می باشد(مگر اینکه برویم در کلاس جریان ها و این عملگر را برای اشیاء کلاس خودمان گرانبار کنیم! که این عمل غیر ممکن نیست، ولی اصلاً پیشنهاد نمی شود! معایب زیادی دارد!) اما در مورد گرانباری این عملگر، معمولاً جریان ها را به صورت ارجاعی ارسال می کنند، چرا؟ همانطور که می دانید به طور کلی در زبان های خانواده C، پارامتر ها به صورت فراخوانی با مقدار ارسال می شوند یعنی چنانچه یک متغییر یا یک شیء را به یک تابع ارسال کنیم، یک کپی از آن شیء به تابع مربوطه ارسال می شود. اما کپی یک جریان اصلاً کار صحیحی نمی باشد، چرا؟ فرض کنید این جریان کپی شد و در داخل تابع شما دچار مشکل شد، آن وقت دیگر این مشکل در سطح تابع فراخوانی کننده، معلوم نمی باشد! یا فرض کنید در تابع شما چیزی در این جریان درج شد، آن وقت در سطح تابع فراخواننده، این اطلاعات وجود ندارد چرا که این اطلاعات در شیک شیء دیگر درج شده است و آن شیء هم اکنون از بین رفته است. خلاصه به این دلایل و دلایل مشابه دیگر، جریان ها را به صورت ارجاعی ارسال می کنند. متغییرهای ارجاعی را نیز با یک علامت & نمایش می دهند یعنی ابتدا تایپ داده ای متغییر و سپس علامت & و سپس نام متغییر نوشته می شود. مثلاً int &a یک متغییر ارجاعی از نوع داده ای int می باشد. شما می توانید در برنامه خود یک متغییر مثلاً به نام int b تعریف کنید و سپس توسط دستور a=b به متغییر a بگویید که این متغییر یک ارجاع به متغییر b می باشد. دقت کنید که متغییر های ارجاعی حتماً باید در زمان تعریف مقدار دهی شوند چرا که در اصل متغییر های ارجاعی نیز یک نوع اشاره گر محسوب می شوند و نباید به صورت garbage(نا معلوم) بمانند چرا که هر لحظه ممکن است کاربر از آن متغییر استفاده کند و چنانچه معلوم نباشد این متغییر ارجاع به کجا دارد، مشکل بوجود می آید. متغییر های ارجاعی فقط در ++C و #C وجود دارند و در C وجود نداشتند. هر گونه تغییری در متغییر های ارجاعی، موجب تغییر در متغیر های متناظر آنها نیز خواهد شد مثلاً اگر در مثلاً بالا a=2 را اجرا کنید، خودبه خود b نیز دو خواهد شد. از خوبی های متغییر های ارجاعی این است که در اصل این متغیر ها چونکه یک نوع اشاره گر هستند، همیشه دارای sizeof یا اندازه 4 بایت می باشند(اشاره گر ها نیز همیشه 4 بایتی هستند). اما در مورد علامت متغییر های ارجاعی نیز باید گفت که علامت & می تواند به نوع داده ای بچسبد(ostream& output)، و یا اینکه به نام متغییر بچسبد(ostream &output) و یا اینکه بین این دو نوشته شود(ostream & output) و در هیچ کدام از این حالت ها، کامپایلر خطا نمی گیرد ولی شاید بهتر باشد که از روش اول استفاده کنید چرا که خوانایی بهتری دارد.
اما در مورد اون قضیه که دوست خوبمون C++Lover گفتند، فکر کنم دیگه ابهامی وجود نداشته باشد چرا که ارسال یک کپی از شیء(چه عدد مختلط و چه یک جریان) به تابعی که هیچ تغییری در آن انجام نمی دهد و فقط قصد خواندن چند مقدار از صفات شیء را دارد، کاری بیهوده است و موجب کاهش کارایی می شود.
اما در مورد * ها باید گفت که * در ++C برای دو منظور به کار می رود. یکی عمل ضرب می باشد که قابل گرانباری نیز می باشد و دیگری عمل ارجاع به یک آدرس حافظه! همانطور که می دانید، در ++C امکان دسترسی به خانه های حافظه را نیز داریم یعنی می توانیم یک متغییر تعریف کنیم که به هر جای مجازی از حافظه برنامه دسترسی پیدا کرده و آن قسمت را تغییر دهیم(که این کار کمی خطرناک نیز می باشد). برای تعریف یک اشاره گر، کافیست پس از نوع داده ای، یک علامت * قرار دهیم مثلاً int *a یک اشاره گر از نوع int می باشد که متغییر a می تواند به یک خانه حافظه به صورت int دسترسی پیدا کند. من قبلاً یک مقاله کوچیک در مورد انواع اشاره گر ها نوشتم که آن را در زیر قرار می دهم و بعد ادامه بحث را برای شما خواهم گفت(به نقل از http://enghelper.com/forum/viewtopic.php?t=33&postdays=0&postorder=asc&start=8 ):
================================================== ==

انواع اشاره گر ها :
ر
می دانم که بسیاری از شما با اشاره گر ها آشنا می باشید ولی در این قسمت می خواهم تقسیم بندی را که خود برای اشاره گر ها انجام داده ام معرفی نمایم و سپس یک مثال کاربردی از اشاره گر ها بیان کنم .
می دانید که اشاره گر ها یک خانه حافظه با طول ثابت 4 بایت می باشند . (دقت کنید که برای تمام تایپ ها چه مبنایی و چه اشیاء کلاس ها طول اشاره گر ها 4 بایت می باشند) دوستانی که زبان ماشین و اسمبلی خوانده اند بهتر درک می کنند چرا که با سگمنت و افست آشنا هستند و می دانند چرا اشاره گر ها که در اصل آدرس خانه های حافظه اصلی (RAM) می باشند، 4 بایتی بوده و همچنین با ساختن آنها توسط کد های اسمبلی آشنا هستند . (نا گفته نماند که بعضی از کامپایلر ها مثل TC++ 3.0 اشاره گر های خود را دو بایتی می گیرند که این موضوع به کامپایلر وابسته است و این کامپایلر فقط اشاره گر هایی را پشتیبانی می کند که به افست داخل data segment برنامه کامپایل شده، اشاره می کنند .) نحوهر تعریف یک اشاره گر از نوع TYPE (چه مبنایی و چه کلاس یا ساختار) با نام NAME در ++C به صورت زیر می باشد :
;TYPE *NAME
اغلب توسط دستور typedef برای * TYPE یک نام اختیار می کنند بنابراین چنانچه جایی ;typedef TYPE * TYPEPTR مشاهده نمودید تعجب ننمایید یعنی تایپ جدید TYPEPTR معادل * TYPE می باشد . در ++C اشاره گر ها را نمی توان مستقیماً مقدار دهی نمود و تنها مقداری که مستقیماً می توان به آنها انتساب داد NULL یا صفر می باشد . NULL یک ماکرو یا ثابت نمادی است که توسط دستور define# معادل صفر تعریف شده است . چنانچه از این ثابت در برنامه خود استفاده می نمایید باید یکی از فایل های سرآمد که در آن این ثابت تعریف شده است را به برنامه خود اضافه نمایید ولی سری که درد نمی کند دستمال نمی بندند به جای NULL مستقیماً از صفر استفاده نمایید . اما چناچه از NULL استفاده نمایید خوانایی برنامه شما بیشتر می شود . برای مقدار دهی به یک اشاره گر از عملگر & استفاده می شود . دقت کنید که این عملگر برای and بیتی دو متغییر نیز استفاده می شود بنابراین چنانچه از آن به صورت صحیح استفاده ننمایید ممکن است برنامه شما اشتباه عمل نمایید و یا دچار خطا شود . فرض کنید در برنامه متغییری به صورت ;int number=2 داریم برای تعریف یک اشاره گر می توان به صورت
;int *ptr=&x عمل نمود که در این صورت متغییر x باید قبل از این دستور تعریف شده باشد . همچنین می توان این دو دستور را در هم ادغام کرد یعنی نوشت :
;int number=2,*ptr=&x . همچنین می توان مقدار ptr را بعد از تعریف تعیین نمود یعنی نوشت : ;ptr=&x . برای دسترسی به محتوای مکانی که اشاره گر به آن اشاره می کند از عملگر * استفاده می شود . مثلاً برای تغییر محتوای متغییر x می توان به دو صورت عمل کرد : ;x=6 و یا ;*ptr=6 . دقت کنید که در عبارت هایی که از عملگر های . و <- استفاده می نمایید، حق تقدم * کمتر از عملگر های دسترسی به عضو مستقیم و غیر مستقیم می باشد یعنی دستور ;obj.x=2* به این معنی است که متغییر x از شی obj یک اشاره گر است که با استفاده از دستور بالا مقدار خانه حافظه ای که به آن اشاره می کند را به 2 تغییر داده ایم و دستور ;obj).x=2)* یعنی اینکه obj یک اشاره گر از نوع اشیاء یک کلاس می باشد که توسط *obj به خود شی دسترسی پیدا کرده ایم و حال محتوای عضو x این شی را به 2 تغییر داده ایم . حال به بیان انواع اشاره گر ها می پردازیم .
الف) اشاره گر ها از دیدگاه یک متغییر : می دانیم که محتوای یک خانه حافظه می تواند ثابت و یا متغییر باشد و با تعریف اعداد و اشیاء ثابت و متغییر آشنا می باشید . اشاره گر ها نیز به همین صورت می باشند به عبارتی اگر بتوان در طول برنامه مقدار اشاره گر را تغییر داد یعنی آدرس مکانی که به آن اشاره می کند را عوض کرد، آن اشاره گر متغییر است در غیر این صورت ثابت است .
نحوه تعریف یک اشاره گر متغییر : ;TYPE *NAME
نحوه تعریف یک اشاره گر ثابت : ;TYPE *const NAME
همانطور که می بینید بر خلاف تایپ های عادی، در اشاره گر های ثابت عنوان const بعد از TYPE * می آید . می توان حدس زد چرا این عمل را انجام داده اند . شاید برای اینکه بتوان در برنامه خود نوشت : ;int x=2,*const ptr=&x .
ب) اشاره گر ها از نظر نحوه دسترسی : نحوه دسترسی یک اشاره گر به خانه حافظه آن به دو صورت می باشد . فقط خواندنی ، خواندنی و نوشتنی . خواندنی یعنی اینکه فقط می تواند محتوای خانه حافظه را بازیابی کند و به ما برگرداند و خواندنی – نوشتنی یعنی اینکه می تواند علاوه بر خواندن مقدار محتوای آن خانه حافظه را نیز تغییر دهد .
نحوه تعریف یک اشاره گر با دسترسی خواندنی – نوشتنی : ;TYPE *NAME
نحوه تعریف یک اشاره گر با دسترسی فقط خواندنی : ;cnost TYPE *NAME
البته شایان ذکر است که اشاره گر های فقط خواندنی را می توان توسط عمل قالب ریزی به صورت ضمنی (یعنی فقط یک لحظه در برنامه) به اشاره گر هایی با دسترسی کامل تبدیل کرد . مثلاً فرض کنید ;int x=2 و ;const int *ptr=&x را داشته باشیم . توسط دستور ;int *)*ptr=5) می توان دسترسی خواندنی – نوشتنی به اشاره گر ptr داد که چنین کاری معمولاً توصیه نمی شود .
ج) اشاره گر ها از نظر نوع اشاره : همانطور که تا به حال مشاهده کردید اشاره گر های ما تا به حال به یک خانه حافظه اشاره می کردند . به نظر شما دستور ;TYPEPTR *NAME به چه معنی است . گفتیم که TYPEPTR یک typedef برای * TYPE است به عبارتی دستور بالا به صورت ;TYPE **NAME می باشد . این نوع اشاره گر ها را اشاره گر ناظر بر اشاره گر نامند به عبارتی این اشاره گر ها به یک اشاره گر اشاره می کنند نه یک متغییر معمولی . به نظر شما آیا دستور ;TYPE ***NAME نیز باید با معنی باشد ؟ خیر چرا که دیگر مفهومی ندارد چرا که اگر به یک اشاره گر ناظر بر متغییر معمولی نیاز دارید یک ستاره کافیست و اگر به اشاره گر ناظر بر اشاره گر نیاز دارید دو ستاره کافیست و سه ستاره دیگر کاملاً بی معنی است .
حال به بیان کاربرد این اشاره گر ها می پردازیم :
در قدیم که زبان C استفاده می شد، به دلیل عدم وجود متغییر های نام (alias) برای اینکه بتوان در یک متغییر ارجاعی برای یک تابع فرستاد از دسترسی توسط آدرس استفاده می شد یعنی به جای اینکه متغییر را به تابع بفرستند که فراخوانی با مقدار شود و فقط محتوای آن در تابع فراخوانی شده تغییر کند، آدرس آن را توسط یک اشاره گر می فرستادند و در تابع هر گونه تغییر بر روی خانه حافظه ای که اشاره گر به آن اشاره می کرد، مسلماً در توابع دیگر نیز تأثیر داشت چرا که همان خانه حافظه که در تابع فرخوان صدا زده شده بود، تغییر می کرد . در بعضی موارد نیاز بود خود اشاره گر نیز تغییر کند و برای رفع این مشکل نیز از اشاره گر ناظر بر اشاره گر استفاده می کردند . البته در++C اشاره گر های ارجاعی نیز داریم که به صورت
;TYPE *&alias_ptr,*main_ptr تعریف می شوند و چنانچه توسط دستور ;alias_ptr=main_ptr این دو اشاره گر به هر مرتبط شوند، alias_ptr حکم یک نام دیگر برای main_ptr را دارد به عبارت چه در دستورات ما ...=alias_ptr* نوشته شود و چه main_ptr* هر دو دارای یک عملکرد است ولی خود متغییر های ارجاعی (یا دسترسی به وسیله نام مستعار) نیز مکانیزمی شبیه اشاره گر دارد . به عبارتی چنانچه دستور ;++alias_ptr اجرا شود که این دستور موجب می شود اشاره گر alias_ptr به اندازه (sizeof(*alias_ptr اضافه شود (به آدرس جلو تر اشاره کند)، مانند این عمل کرده ایم که بنویسیم ;++main_ptr . کار کردن با متغییر های ارجاعی بسیار آسان تر از اشاره گر های ناظر بر اشاره گر می باشد ولی حالا می خواهم یک کاربرد از اشاره گر ها را برای شما بگویم که فقط توسط خود اشاره گر ها پشتیبانی می شود نه متغییر های ارجاعی . می دایند که در ++C برای گرفتن یک حافظه پویا باید از طریق اشاره گر ها عمل نمود . مثلاً برای ساختن یک آرایه پویا به طول size از نوع int باید دستورات :int size=10,*ptr و ;[ptr=new int[size را در برنامه خود بنویسیم که می توان این عمل را به صورت خلاصه تر یعنی به صورت
;[int size=10,*ptr=new int[size نیز نوشت یعنی در زمان تعریف اشاره گر به ازای مقدار دهی اولیه یک حافظه پویا برای آن گرفت . چنانچه size معلوم باشد نیاز به نوشتن آن نیست یعنی کافیست بنویسیم : ;[int *ptr=new int[10 . همچنین چنانچه size=1 باشد نیاز به نوشتن آن نمی باشد یعنی دستور ;int *ptr=new int یک خانه حافظه پویا به ما اختصاص می دهد . دقت کنید که چنانچه دستور ;[int *ptr=new int[0 را اجرا نمایید باز هم یک خانه حافظه به شما اختصاص داده می شود ؟!! (در TC++ 3.0 این موضوع بررسی شده است شاید این از معایب و یا حُسن های عملگر new باشد) همچنین به هنگام پس دادن یک حافظه پویا باید توسط دستور ;delete []ptr عمل کرد . دقت کنید چنانچه شما فقط یک حافظه پویا گرفته باشید باز هم این دستور عمل می کند و چنانچه size خانه هم گرفته باشید باز هم این دستور درست عمل می کند ولی چنانچه size را هم در [] بنویسید در TC++ 3.0 فقط یک warning به شما داده می شود و به طول شما اعتنایی نمی شود و عملگر delete حافظه گرفته شده را کاملاً پس می دهد . برای حذف یک حافظه نیاز به [] نمی باشد و می توان آن را با دستور ;delete ptr حذف نمود . اما آرایه های ++C چگونه ساخته می شوند ؟ هنگامی که ما در برنامه می نویسیم ;[int a[10 یک اشاره گر از نوع int که ثابت است و نمی توان آن را تغییر داد ساخته می شود و به اندازه 10 عدد integer حافظه گرفته شده و آدرس ابتدای آن در اشاره گر a قرار می گیرد . ما می توانیم خود با دستور ;[int *const ptr=new int[10 چنین عملی را انجام دهیم با این تفاوت که مقدار طول آرایه می تواند متغییر باشد یعنی می توان متغییر ;int size را تعریف کرد و سپس توسط دستور ;cin>>size مقدار آن را از کاربر دریفات کرده و پس از آن دستور
;[int *const ptr=new int[size را به کار برد که حافظه پویایی گرفته ایم که نیاز نبوده است قبل از زمان کامپایل مقدار آن مشخص باشد و مانند یک آرایه عمل می کند یعنی در طول برنامه نمی توان مقدار ptr را تغییر داد اما می توان حافظه آن را پس داد که در این صورت اشاره گر ptr دیگر غیر قابل استفاده می باشد مگر اینکه با دستور ;[int *)ptr=new int[10) دوباره برای آن حافظه گرفته شود و یا با دستوری مشابه به خانه دیگری از حافظه اشاره کند .
حال به سراغ ساخت یک ماتریس پویا برویم . برای ساخت یک ماتریس باید دید خود را قوی تر کنیم . همانطور که می دانید یک ماتریس آرایه ای از آرایه ها می باشد . یعنی چی ؟ شبه کد زیر را در نظر بگیرد :
كد:
int row,col,**ptr;
cout<<"\nPlease enter number of row : ";
cin>>row;
cout<<"\nPlease enter number of col : ";
cin>>col;
ptr=new int *[row];
for(int i=0;i<row;i++)
ptr[i]=new int[col];
با استفاده از این شبه کد ما می توانیم یک ماتریس پویا بگیریم که ابعاد آن در زمان اجرا مشخص می شود . تا سطر پنجم که فکر نکنم مشکلی داشته باشید . در سطر ششم آرایه ای به طول تعداد سطر ها از حافظه رزرو می کنیم که این آرایه هر عنصر آن یک اشاره گر ناظر بر متغییر صحیح می باشد . حال برای هر اشاره گر را به اندازه تعداد ستون ها حافظه رزور می کنیم . در نهایت ما row*col حافظه برای عناصر ماتریس و به اندازه row اشاره گر برای ذخیره آدرس ابتدای هر سطر از ماتریس و یک اشاره گر به نام ptr برای اشاره به اولین عنصر از آرایه سطر ها در نظر گرفته ایم . جالب است بدانید که ++C نیز به همین صورت برای ما حافظه های چند بعدی می سازد . البته ++C به صورت زیر عمل می کند :
كد:
int row,col,*const *const ptr=0;
cout<<"\nPlease enter number of row : ";
cin>>row;
cout<<"\nPlease enter number of col : ";
cin>>col;
(int **)ptr=new int *[row];
for(int i=0;i<row;i++)
(int *)ptr[i]=new int[col];
به عبارتی اشاره گر ها ثابت می باشند و حق تغییر آنها را نداریم . حال شاید با خود بگویید که اشکالی ندارد، در ماتریس های ++C نیز با استفاده از قالب ریزی اشاره گر ها را تغییر می دهیم اینجاست که باید بگویم تست کردن این موضوع با شما ولی همین قدر بدانید که این قدر کامپایلر با هوش است که یک متغییر lvalue برای ما بر نمی گرداند که بتوان آن را کم و زیاد و یا تغییر داد . (متغییر های lvalue متغییر هایی هستند که می توانند سمت چپ انتساب قرار گیرند و در مقابل آنها متغییر های rvalue را داریم که می توانند سمت راست انتساب قرار گیرند.) البته این نکته را نیز باید بیان کنم که کامپایلر در اصل چنین ساختاری می سازد ولی نیاز نیست که مانند ما چندین اشاره گر برای ابتدای سطر ها بسازد و با توجه به شواهدی فکر کنم اصلاً چنین عملی انجام نمی دهد ولی مکانیزمی که فراهم می کند به صورت توضیح داده شده عمل می کند . فکر کنم دیگر نیازی به توضیح چگونگی ساخت یک مکعب نباشد چرا که روش آن نیز به همین صورت است فقط در مرحله اول به طول بعد اول اشاره گر ناظر بر اشاره گر گرفته و در مرحله دوم حلقه for برای هر اشاره گر آرایه قبلی یک آرایه از نوع اشاره گر ناظر بر متغییر می گیرد و در حلقه دوم باید حلقه سومی نوشته شود که برای هر خانه از آرایه جدید یک آرایه به طول بعد سوم از نوع متغییر ها بسازد .
مشکل این روش این می باشد که اولاً هنوز هم ماتریس پویا نیست و نمی توان بر حسب نیاز به راحتی طول آن را کم و زیاد کرد و یا بعضی از سطر ها آن دارای اندازه های مختلف باشند و یا نوع های مختلفی (مانند char و double) در آنها ذخیره کرد . برای حل این مشکلات از لیست های پیوندی استفاده می شود . اما برای ساخت ماتریس های پویای یک نوع روش دیگری نیز وجود دارد . نمی دانم آیا با کلاس vector آشنا هستید یا خیر ؟ این کلاس که از کلاس های استاندارد (STL (Standard Template Library می باشد یک آرایه پویا را پشتیبانی می کند و به صورت الگو پیاده سازی شده است و توابع متنوع و زیادی دارد . برای اطلاعات بیشتر در مورد این کلاس و STL می توانید به فصل آخر کتاب ساختمان داده جناب آقای جعفر نژاد قمی مراجعه نمایید . (شرمنده، از غلط های املایی این کتاب صرف نظر کنید و چنانچه اشتباه تایپی در آن یافتید به بزرگی خود ببخشید)
حال با تعریف یک :vector< vector <int> > x ما می توانیم یک ماتریس کاملاً پویا داشته باشیم . دقت کنید که در تعریف شی x که آرایه ای از آرایه ها می باشد، چنانچه بنویسیم ;vector< vector>> x ممکن است کامپایلر خطا بگیرد چرا که << را معادل عملگر شیفت در نظر می گیرد به همین دلیل بهتر است این دو عملگر را جدا نوشت . چنانچه در آینده فرصتی شد، درباره STL نیز مطالب کاملی جمع آوری کرده و در سایت قرار خواهم داد .
================================================== ==
در مورد تایپ * void نیز قبلاً در http://barnamenevis.org/forum/showthread.php?t=119593 توضیح داده ام(در توضیحات تابع memcpy):


دقت کنید که * void یک تایپ مبنایی می باشد و معرف یک اشاره گر به هر جایی از حافظه می باشد به عبارتی * char یعنی اشاره گری به حافظه ای که در آن کاراکتر ذخیره شده است و * int یعنی اشاره گر به حافظه ای که در آن اعداد int ذخیره شده اند اما * void یعنی اشاره گر به حافظه ای که معلوم نیست در آن چه عبارتی ذخیره شده است بنابراین حتماً به هنگام استفاده از آن ابتدا باید آن را تفسیر کرده و سپس استفاده نمود . مثلاً فرض کنید ;void *ptr و ;char str[10]={"AliReza"},*pstr,ch را در اختیار داریم . جهت مقدار دهی ptr کافیست دستور ;ptr=str را بنویسیم . چنانچه بخواهیم اشاره گر pstr را با استفاده از ptr مقدار دهی کنیم پس از تفسیر ptr انتساب را انجام می دهیم یعنی دستور ;pstr=(char *)ptr را می نویسیم . چنانچه بخواهیم به کاراکتر های str دسترسی داشته باشیم (مثلاً کاراکتر شماره یک یعنی 'l' را در متغییر ch ذخیره کنیم) می توانیم از هر یک از دستورات ;[ch=((char *)ptr)[1 و یا ;(ch=*((char *)ptr+1 استفاده کنیم .

دقت کنید که * نیز مانند & می تواند به سه صورت استفاده شود یعنی int* a و int *a و int * a هر سه مورد قبول اند و این نکته باعث گیج شدن شما نشود. به عبارتی خروجی تابع new در مثال شما، یک *void یا * void بود و چسبیدن * به نام تابع هیچ ربطی به نام تابع ندارد.
اما در مورد this و فرق اون با this* که پرسیدید، باید بگم که this یک اشاره گر به شیء جاری می باشد. گاهی اوقات ما در متدهای یک کلاس نیاز داریم، که نام شیء جاری را بنویسیم و از آن استفاده کنیم، مثلاً اگر پارامتر یک تابع همنام با یکی از صفات تابع باشد، کامپایلر چگونه باید تشخیص دهد که وقتی ما می نویسیم، a منظورمان نام صفت a کلاس بوده است یا نام پارامتر ارسالی به متد مورد نظر؟ کامپایلر برای این منظور به صورت پیش فرض a را پارامتر ارسالی در نظر می گیرد و this->a را صفت کلاس. همچنین این شرایط می تواند برای یک متغییر محلی نیز در یک متد بوجود آید و ... . به طور کلی وقتی ما در یک متد نام یک صفت را می نویسیم، مثلاً در کلاس اعداد مختلط آقای salar_cpp_cs وقتی ما می نویسیم R کامپایلر به صورت پیش فرض this->R را در نظر می گیرد چرا که برای کامپایلر باید روشن واضح باشد که این متغییر به کجا تعلق دارد. بنابراین this اشاره گری به شیء جاری می باشد. اما هر جا بخواهیم به این اشاره گر به عنوان یک شیء نگاه کنیم نه یک اشاره گر، از عملگر دسترسی به آدرس یا اشاره گر استفاده می کنیم یعنی می نویسیم this* . دقت کنید که عملگر دسترسی به آدرس دارای تقدم کمتری از . و <- می باشد بنابراین چنانچه بخواهیم به یک صفت از کلاس توسط خود شیء آن در داخل متدی از کلاس استفاده کنیم، یا اینکه می نویسیم this->myattribute یا اینکه this).myattribute*) ! مسلم است که روش اول بهتر است ولی روش دوم نیز اشکالی ندارد و می توان از آن استفاده کرد. اما یک جا نیز استفاده از this* الزامی است؟! هنگامی که خروجی متدی یک شیء می باشد(دقت کنید یک شیء نه یک اشاره گر) و می خواهیم شیء جاری را که متد را فراخوانی کرده است، برگردانیم، مجبوریم بنویسیم: this* تا به جای اشاره گر، یک شیء در اختیار داشته باشیم و آن را ارسال کنیم.
امیدوارم مشکل شما حل شده باشد.
سوالی بود، در خدمتیم.
یا علی
حق نگهدارتون

C++Lover
جمعه 08 شهریور 1387, 03:40 صبح
اما در مورد اون قضیه که دوست خوبمون C++Lover گفتند، فکر کنم دیگه ابهامی وجود نداشته باشد چرا که ارسال یک کپی از شیء(چه عدد مختلط و چه یک جریان) به تابعی که هیچ تغییری در آن انجام نمی دهد و فقط قصد خواندن چند مقدار از صفات شیء را دارد، کاری بیهوده است و موجب کاهش کارایی می شود.

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

در مورد ارسال پارامترها فقط خواندنی به صورت const و همچنین تعریف متدهایی که تغییری در داده های شئی نمیدن به صورت const یک مثال می زنم. (فکر کنم شما فقط در این مورد گلایه دارید و با بقیه موافقید)
فرض کنیم که یک برنامه نویس دیگه یک کتابخانه بزرگ یا متوسط که توسط دوستمون با روش بالا ایجاد شده (یعنی بدون در نظر گرفتن نکاتی که من اشاره کردم) رو میخواد استفاده کنه. به طور قانونی برنامه نویسهای با تجربه و حرفه ای ++C این نکات رو کاملا رعایت میکنن چون در غیر این صورت در پروژه های بزرگ مشکلات زیادی پیش میاد. حالا برای مثال در مورد استفاده نکردن const برای نوع متد != operator براتون میگم چه مشکلی پیش میاد.
کد زیر رو در نظر بگیرید که از کلاس Complex شما استفاده می کنه.


01: void SomeFunc(const Complex& c1, const Complex& c2)
02: {
03: if (c1 != c2) // oops!
04: // then do something
05: // etc
06: }

این کد کامپایل نمیشه چون != operator به صورت const تعریف نشده و برنامه نویس در این متد به طور قانونی پارامترهای فقط خواندنی اش رو به صورت const تعریف کرده. و در نتیجه کامپایلر میگه که نمی تونه از &const Complex به &Complex تغییر type بده. این رو در نظر بگیرید که این خط مشی ها برای کارا تر شدن کد نوشته شده و قابل استفاده مجدد بودن کدها بسیار موثر و لازمند. یعنی اگر کسی بخواد از این کلاس Complex استفاده کنه باید کلا استاندارد رو کنار بزاره و خودشو با روش بد این کد هماهنگ کنه.

در مورد const تعریف کردن پارامترهای ورودی هم دلایل بسیار خوبی وجود داره. برای مثال برای عملگر = ما همیشه انتظار داریم که مقدار سمت راست RValue باید فقط خوانده شود و نباید تغییر کند، بنابراین طبق استاندارد خروجی عملگر را &const type تعریف میکنیم تا هم از ساخته شدن شئی ها موقت جلوگیری بشه و هم RValue فقط خواندنی باشه. حال اگر پارامترهای ورودی به صورت &const type تعریف نشده باشند در = های تو در تو و = های درون عبارت دچار مشکل می شویم و کد کامپایل نمی شود.
برای مثال کد زیر کامپایل نمیشود.

c1 = c2 + (c3 = c4)

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

من چندین مورد متفاوت رو در پست قبلیم اشاره کرده بودم. شما هم اگر ایرادی در نوشته های من می بینید بهتر است مثلا بگید که با فلان قسمت موافقم و با فلانی به این دلیل موافق نیستم.
نه با یک دلیل و بدون اشاره به مورد خاص همه رو خلاصه کنید.

در ضمن هدف من فقط آگاهی دادنه نه چیز دیگه.

bsng110
شنبه 09 شهریور 1387, 17:17 عصر
هوالحکیم.
سلام.
ببخشید ظاهراً سو تفاهم شده. منظور بنده این بود که حق با شماست و بنده دیگر نمی خواستم توضیح دهم که چرا حق با شماست و به همین دلیل بود که گفتم، دیگر توضیح دلیل & const بودن را نمی دهم. چنانچه به صورت بدی این قضیه را مطرح نموده ام، بنده را ببخشید. چه کنم، ما بی ثواتیم و درست بلد نیستیم حرف بزنیم!
باز هم شرمنده و الا حق با شماست.
در ضمن برعکس حرف بالا نیز وجود دارد یعنی بعضی مواقع نیز مجبوریم از const Type حالی استفاده کنیم نه & const Type؟ مثلاً فرض کنید که می خواهیم عملگر جمع یک عدد مختلط با یک عدد حقیقی را برای اعداد صحیح گرانبار کنیم:


const Complex Complex::operator +(const int &a){
Complex c;
c=*this;
c.R += a;
return c;
}

در اینصورت برنامه ما برای دستور 2+c2=c1 کار نمی دهد و می گوید نمی تواند 2 را که یک لیترال عددی است، به متغییر ارجاعی تبدیل کند!
به هر حال بابت اینکه چنین مسائلی را مطرح کردید، بینهایت سپاسگزارم و از اینکه اینقدر به کارایی و مسائل این چنینی اهمیت می دهید، خیلی خوشحالم.
یا علی
حق نگهدارتون