فرشید عزیزی
فرشید عزیزی
خواندن ۱۵ دقیقه·۳ ماه پیش

دوره آموزشی #C - قسمت دوم - VARIABLES & TYPES

دوره آموزشی #C - قسمت اول

خلاصه: در این آموزش با متغیرهای #C از جمله اعلان متغیرها(declaring variables)، تخصیص مقادیر(assigning values) به متغیرها و نمایش متغیرها در console آشنا خواهید شد.

مقدمه ای بر متغیرهای سی شارپ :

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

  • ابتدا داده‌ها را از ورودی‌های کاربر، فایل‌ها یا API شخص ثالث(third-party) دریافت کنید.
  • دوم، پردازش داده ها.
  • سوم، خروجی نتیجه را روی صفحه نمایش دهید یا آن را در یک فایل یا پایگاه داده ذخیره کنید.
برای ذخیره داده ها در حین اجرای برنامه از متغیرها استفاده می کنید.

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

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

type variableName;

در این syntax:

  • در اینجا type می تواند built-in types یا custom types باشد. به عنوان مثال، int built-in type نشان دهنده اعداد صحیح است، و string built-in type نشان دهنده رشته های متنی است. توجه داشته باشید که بعداً با custom types آشنا خواهید شد.
  • همچنین variableName یک شناسه معتبر است.

متداول ترین built-in types :

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

  • نوع int یک عدد صحیح علامت دار(signed)شده 32 بیتی با محدوده مجاز -2,147,483,648 تا 2,147,483,647 است. به دلیل ظرفیت های ذخیره سازی و محدوده مجاز معقول، int تا حد زیادی رایج ترین نوع عدد صحیح مورد استفاده است.
  • نوع uint همان int است، اما این نسخه بدون امضای آن است. این فقط به این معنی است که محدوده مجاز شامل اعداد منفی نمی شود، بنابراین محدوده آن 0 تا 4,294,967,295 است.
  • نوع byte و sbyte هر دو اعداد صحیح 8 بیتی هستند. محدوده مجاز برای byte (به عنوان بایت علامت دار شناخته می شود، به این معنی که اعداد منفی را مجاز می کند) 128- تا 127 است و محدوده مجاز برای یک نوع sbyte از 0 تا 255 است. به دلیل این محدوده کوچک، byte و sbyte ها معمولا استفاده نمی شوند. مگر اینکه بدانید شماره شما در این محدوده باقی خواهد ماند.
  • نوع short و ushort هر دو اعداد صحیح 16 بیتی هستند. محدوده مجاز برای short از 32768- تا 32767 و محدوده مجاز برای ushort بدون علامت 0 تا 65535 است. در حالی که این محدوده به طور قابل توجهی بزرگتر از byte و sbyte ها است، short و ushort ها نیز به اندازه int معمولا استفاده نمی شوند.
  • در نهایت، long و ulong نیز وجود دارد. همانطور که از نام آنها پیداست، این کلمات کلیدی برای اعداد بسیار طولانی رزرو شده اند. آنها اعداد صحیح 64 بیتی هستند. محدوده مجاز برای long از -9,223,372,036,854,775,808 تا 9,223,372,036,854,775,807 و محدوده مجاز برای ulong بدون علامت 0 تا 18,446,744,075,075,709 است. به دلیل این محدوده های عظیم، آنها به مقدار زیادی حافظه اختصاص داده شده برای ذخیره نیاز دارند. بنابراین، انواع داده های long و ulong نیز به اندازه int استفاده نمی شوند.

دسته Floating Points :

سه نوع مختلف از اعداد ممیز شناور وجود دارد. هر کدام از نظر range، performance و دقت(precision) متفاوت هستند.

  • ابتدا به float نگاه خواهیم کرد که دارای کمترین range، بهترین عملکرد و دقت هفت رقمی است. هنگامی که به نتایج بسیار سریع نیاز دارید، اما واقعاً به خطاهای گرد کردن اهمیت نمی دهید، به طور ایده آل از float استفاده می کنید.
  • نوع double دارای محدوده مجاز قابل ملاحظه ای بزرگتر از float است، عملکرد خوبی دارد (اما نه به خوبی float)، و دقت 15-16 رقمی دارد که بیش از دو برابر بیشتر از float است. این رایج ترین نوع ممیز شناور است. به طوری که به طور پیش فرض، سی شارپ از نوع داده double برای همه اعداد نوع شناور استفاده می کند. نوع داده double برای هر نوع محاسبات ممیز شناور ایده آل است، از این رو محبوب ترین نوع در این دسته بندی است. تنها سناریویی که در آن نباید از double استفاده کنید، برای داده های پولی و تراکنش های مالی است.
  • نوع decimal. این نوع داده 100% قادر به بیان دقیق هر عدد ممیز شناور در محدوده مجاز خود است، چیزی که float و double قادر به انجام آن نیستند. به همین دلیل است که نوع decimal به طور گسترده در معاملات مالی استفاده می شود. به دلیل این دقت بالا، عملکرد برای یک نوع decimalکندتر از یک float یا یک double است. نوع داده decimalدارای محدوده بسیار بزرگ، عملکرد کندتر، اما دقت بکر است.

دسته Objects :

خوب object built-in type نام مستعار NET framework Object. است که تمام کلاس های دیگر از آن مشتق شده اند. نوع object این توانایی را دارد که از طریق فرآیندهای boxing و unboxing شکل هر type دیگری را به خود بگیرد و به type گفته شده تبدیل شود.

بیایید به مثالی از یک object نگاه کنیم که به شکل چندین built-in type دیگر است. این مزیت اصلی استفاده از یک نوع object است. یک نقطه ضعف استفاده از object type این است که زمان اجرای برنامه شما را کند
می کند. بنابراین اگر می دانید متغیر شما چه typeی خواهد بود، باید از آن type استفاده کنید. اما در اینجا مثالی از انعطاف پذیری نوع object آورده شده است.

object obj = "hello"
//declare object variable as a string
Console.WriteLine("Current value of obj: " + obj);
obj = 22;
//change it to an integer
Console.WriteLine("Current value of obj: " + obj);
obj = 'i';
//change it to a char
Console.WriteLine("Current value of obj: " + obj);
obj = "hello there"
//change it back to a string
Console.WriteLine("Current value of obj: " + obj);

همه خطوط بدون هیچ مشکلی کامپایل می شوند.

دسته Chars :

دو نوع داده char عبارتند از char و string.شما یک char را در سی شارپ با احاطه کردن آن با نقل قول های تکی (' ') نشان می دهید. string آرایه ای از charها است.

یک char یک کاراکتر literal یونیکد است، اما می‌تواند یک دنباله hexadecimal ، یک کاراکتر unicode نیز باشد.
char ch = 'x'; //Unicode literal character
char hexChar = '\xF'; //hexadecimal number
char unicodeChar = '\u0058'; //Unicode character
char space = (char)32; //type casted from an integer
string str = "hello world" //example of a string

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

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

فرض کنید ما یک رشته جدید ایجاد می کنیم، سپس آن را اصلاح می کنیم و توضیح می دهیم که در تمام این مدت در حافظه چه اتفاقی می افتد.
وقتی رشته ای ایجاد می کنید، تغییر ناپذیر(immutable) است. یعنی فقط خواندنی(read-only) است. وقتی چیزی تغییرناپذیر یا فقط خواندنی است، به این معنی است که نمی توان آن را در زمان بعدی تغییر داد.(لطفا اشتباه برداشت نکنید شما می توانید رشته را در طول برنامه خود اصلاح کنید ما در حال بررسی اتفاقاتی هستیم که در حافظه و پشت صحنه برنامه ما اتفاق می افتد )
چرا رشته ها تغییر ناپذیر هستند ؟
در سی شارپ، CLR (Common Language Runtime) مسئول تعیین محل ذخیره رشته ها است. گفتیم که رشته آرایه ای از کاراکترها است. CLR یک آرایه را برای ذخیره رشته ها پیاده سازی می کند. آرایه ها یک ساختار داده با اندازه ثابت هستند، به این معنی که نمی توان آنها را به صورت پویا افزایش یا کاهش داد. هنگامی که به یک آرایه یک اندازه اختصاص داده می شود، اندازه آن قابل تغییر نیست. برای بزرگتر کردن یک آرایه، داده ها باید کپی و در یک آرایه جدید کلون شوند، که توسط CLR در یک بلوک جدید از حافظه قرار می گیرد. اگر یک رشته را ویرایش کنید، در واقع آن رشته را تغییر نمی دهید. بلکه CLR در حال ایجاد یک مرجع حافظه جدید برای رشته اصلاح شده است و رشته اصلی از طریق جمع آوری زباله(garbage collection) از حافظه حذف می شود.

پس وقتی یک رشته را تغییر می دهید، در واقع فقط یک شی جدید در حافظه ایجاد می کنید.

مزایای تغییر ناپذیری یا immutable بودن رشته ها چیست ؟
چرا باید از رشته های تغییرناپذیر استفاده کنید؟ یکی از مزیت ها این است که آنها thread safe هستند. اگر با یک multi threaded system کار می کنید، خطر deadlock یا مشکلات concurrency وجود نخواهد داشت، زیرا وقتی یک رشته را تغییر می دهید، در واقع فقط یک شی جدید در حافظه ایجاد می کنید. مزیت دیگر این است که نگران تغییر تصادفی آنها نخواهید بود. شما نیازی به انجام اقدامات ایمنی اضافی (به عنوان مثال یک کپی شی دفاعی) ندارید که ممکن است لازم باشد با یک شیء قابل تغییر انجام دهید.
معایب تغییر ناپذیری یا immutable بودن رشته ها چیست ؟
مسئله اصلی این است که تغییر مداوم رشته ها می تواند منجر به مشکلات عملکرد شود.
از آنجایی که رشته تغییرناپذیر است، آنچه در حافظه اتفاق می‌افتد این است که با هر بار اصلاح و تغییر یک رشته CLR فضای جدیدی را در حافظه اختصاص می‌دهد و یک متغیر جدید را ذخیره می‌کند، و هر بار بلوک‌های بزرگ‌تری برای ذخیره داده‌های بیشتر ایجاد می‌کند.
اگر این کار را هزار، ده هزار یا یک میلیون بار انجام دهیم چه می شود؟ این امر مستلزم تخصیص مقدار زیادی از حافظه توسط CLR و مقدار زیادی کار برای جمع‌آوری زباله است. با همه این اتفاقات، به احتمال زیاد شاهد کاهش عملکرد خواهید بود. اگر در چنین سناریویی قرار گرفتید، ممکن است یک شیء قابل تغییر همان چیزی باشد که به آن نیاز دارید. یک پیشنهاد یک StringBuilder object است.


دسته bool

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

مقدار پیش فرض نوع bool false است.
bool check = true;
Console.WriteLine(check ? "Checked" : "Not checked"); // output: Checked
Console.WriteLine(false ? "Checked" : "Not checked"); // output: Not checked
هنگام مقایسه دو مقدار با استفاده از عملگرهای مقایسه یا برابری، مقداری از نوع bool را دریافت خواهید کرد.

قراردادهای نامگذاری

قراردادهای نامگذاری دات نت استانداردهایی هستند که چگونه نام گذاری متغیرها، متدها، کلاس ها و سایر عناصر را مشخص می کند.

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

  • استاندارد Camel Case (camelCase) : در این استاندارد حرف اول کلمه همیشه با حرف کوچک و بعد از آن هر کلمه با یک حرف بزرگ شروع می شود.
  • استاندارد Pascal Case (PascalCase): در این استاندارد حرف اول هر کلمه با حرف بزرگ است.
  • استاندارد Underscore Prefix (_underScore): در این استاندارد نام با زیر خط (_) شروع شده سپس کلمه بعد از _ از camelCase استفاده کنید.


مقدار دهی متغییرها :

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

int age;
age = 18;

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

int age = 18;
سی شارپ یک زبان type-safe است. این بدان معنی است که کامپایلر اطمینان حاصل می کند که متغیر همیشه مقداری از نوع اعلام شده را ذخیره می کند.
در مثال بالا، متغیر age همیشه یک عدد صحیح را ذخیره می کند. اگر رشته ای به آن اختصاص دهید، کامپایلر با خطا مواجه می شود.


نمایش متغییر ها :

برای نمایش متغیر age در console ، از متد Console.WriteLine به صورت زیر استفاده می کنید:

int age = 18;
Console.WriteLine(age);

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

Console.WriteLine($"The age is {age}");

در این statement:

  • ابتدا رشته را با نماد $ قرار دهید.
  • دوم، متغیر (age) را داخل {} قرار دهید.

وقتی کامپایلر رشته ای را با پیشوند $ می بیند، تمام متغیرهای درون {} را با مقادیر متناظرشان جایگزین می کند.


اعلان چندین متغییر :

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

double weight = 90.1;
double height = 1.90;

اگر متغیرها دارای یک نوع هستند، می توانید آنها را در یک عبارت اعلام کنید و از کاما (,) برای جدا کردن دو متغیر مانند زیر استفاده کنید:

double weight = 90.1,
height = 1.90;
Console.WriteLine($"The weight is {weight}kg and height is {height}m");
bmi = weight / (height * height);
Console.WriteLine($"BMI: {bmi:0.#}");

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

{ bmi: 0.#}

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


کلمه کلیدی var :

از کلمه کلیدی C# var برای اعلام متغیرهای implicit-typed استفاده کنید.

به عنوان مثال:

string txt = "Welcom to C#"

در این مثال، ما از نوع string برای متغیر txt و یک رشته لیترال به عنوان مقدار اولیه استفاده می کنیم. txt یک متغیر explicit-typed است.

با این حال، از مقدار "Welcom to C#"، کامپایلر می تواند متغیر را به عنوان رشته استنباط کند. بنابراین explicit-typed زاید است.

برای جلوگیری از این افزونگی، می‌توانید از کلمه کلیدی var به جای نوع صریح در ابتدای اعلان متغیر مانند زیر استفاده کنید:

var txt = "Welcom to c#"

در این مثال، txt یک متغیر implicit-typed است.
کلمه کلیدی var نوع خاصی از متغیر را نشان نمی دهد. این مختصر نحوی برای هر نوع است که کامپایلر
می تواند از مقداردهی اولیه یک اعلان متغیر استنباط کند.

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

var txt = "Welcom to c#"
txt= 20; // error

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

var txt;
از کلمه کلیدی var برای متغیری با مقداردهی اولیه استفاده کنید که کامپایلر می تواند یک type را از آن استنتاج کند.

نکات اضافی :

مقادیر حداقل و حداکثر :
هر نوع عدد Integer دارای ثابت های MinValue و MaxValue است که حداقل و حداکثر نوع را ارائه می دهد. برای دسترسی به این مقادیر، از عملگر نقطه (.) استفاده می کنید. مثلا:

Console.WriteLine($"int range: ({int.MinValue},{int.MaxValue})");

مقایسه دو عدد شناور :

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

bool result = 0.3 == 0.1 + 0.1 + 01;
Console.WriteLine(result); // false

دلیل آن این است که عبارت مقداری را برمی گرداند که تقریباً برابر با 0.3 است نه دقیقا خود 0.3

Console.WriteLine(0.1 + 0.1 + 0.1); // output : 0.30000000000000004


افزایش خوانایی اعداد در کد :

اگر یک عدد(چه در دسته Integers و چه در دسته Floating Points) بزرگ است، می توانید از جداکننده رقم (_) برای خوانایی بیشتر استفاده کنید.مثلا:

int prize = 1_000_000;

برای ایجاد یک رشته با طول صفر :

از String.Empty مانند زیر استفاده می کنید:

string txt= String.Empty;

که معادل است با :

string txt= ""

بدست آوردن طول یک رشته :

string txt = "welcom to c#"
Console.WriteLine(txt.Length);

الحاق دو رشته :

string txt = "welcom to" + " c#"
Console.WriteLine(txt);

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

string txt = "welcom to"
txt+= " C#"
Console.WriteLine(txt);

دسترسی به یک کارکتر منفرد در رشته :

سی شارپ یک رشته را به عنوان مجموعه ای از کاراکترهای فقط خواندنی ذخیره می کند. برای دسترسی به یک کاراکتر منفرد در یک رشته، از نماد براکت [] با یک شاخص استفاده می‌کنید:

string txt = "welcom to c#"
Console.WriteLine(txt[0]); // w

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

string txt = "welcom to c#"
txt[0] = 'A';


ـEscape sequences :

یک literal string می‌تواند شامل کاراکترهای ویژه مانند tabs, newlines، ... با استفاده از بک اسلش (\) باشد. به آنها Escape sequences می گویند. مثلا:

string txt = "hello\World"
Console.WriteLine(txt); //output : hello world

ـVerbatim string:

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

برای رفع این مشکل، می‌توانید یک رشته لیترال را با پیشوند علامت @ به یک رشته کلمه به کلمه تبدیل کنید. مثلا:

string path = @"C:\programsFiles\"
Console.WriteLine(path); //output : C:\programsFiles\


ـInterpolated string :

فرض کنید یک متغیر به name دارید:

string name = "reza"

و شما می خواهید متغیر را در یک رشته لیترال جاسازی کنید.
برای انجام این کار، پیشوند رشته لیترال را کاراکتر $ قرار داده و متغیر را در داخل {} قرار دهید:

string name = "reza"
string txt= $"Hi{name}!"
Console.WriteLine(txt); //output : Hello Joe!

یک رشته تحت لیترال با پیشوند $ یک رشته درون یابی(Interpolated string) است.هنگام مواجهه با پیشوند $، کامپایلر متغیر {name} را با مقدار آن جایگزین می کند. این ویژگی درون یابی رشته ای نامیده می شود.

هنگام مواجهه با پیشوند $، کامپایلر متغیر {name} را با مقدار آن جایگزین می کند. این ویژگی درون یابی رشته ای نامیده می شود.


بیشتر بخوانید : دوره آموزشی #C - قسمت سوم - CONTROL FLOW

بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core

https://zarinp.al/farshidazizi

دوره آموزشیسی شارپ
Software Engineer
شاید از این پست‌ها خوشتان بیاید