Go Beginners Series: Data Structures, Structs, and Pointers

هنگام نوشتن برنامه های کاربردی متوسط در Go، باید مواردی مانند سازماندهی کد، مدیریت حافظه، دسترسی به داده ها و انعطاف پذیری کد را در نظر داشته باشید. اغراق نکنیم.
به عنوان مثال، شما فقط می توانید کارهای زیادی را با انواع داده های معمولی انجام دهید. در واقع، برای اکثر برنامهها، برای سازماندهی بهتر کد، خوانایی و ترکیبپذیری، باید چندین مقدار را در یک واحد تعریف و ذخیره کنید. همچنین، استفاده از نشانگرها در برنامههای Go شما مزایای زیادی دارد، مانند مدیریت کارآمد حافظه، دستکاری مستقیم دادهها، ایجاد ساختارهای داده پویا و بسیاری موارد دیگر.
در این مقاله، همه چیزهایی را که برای شروع استفاده از اشاره گرها و ساختارها برای ساخت برنامه های Go کارآمدتر و قابل اعتمادتر نیاز دارید، یاد خواهید گرفت.
ساختارهای داده
در برنامه نویسی، یک ساختار داده داده ها را سازماندهی و در حافظه کامپیوتر ذخیره می کند. یک ساختار داده را می توان محفظه ای در نظر گرفت که مجموعه ای از اقلام داده مرتبط را در خود نگه می دارد و روابط آنها را تعریف می کند. ساختارهای داده در برنامه نویسی ضروری هستند زیرا به شما امکان می دهند داده ها را ذخیره کرده و به آنها دسترسی داشته باشید و عملیات روی آن را به طور موثر انجام دهید.
انواع مختلفی از ساختارهای داده وجود دارد که هر کدام نقاط قوت و ضعف خود را دارند و هر کدام برای وظایف یا موارد استفاده خاص مناسب هستند.
ساختارهای داده رایج
آرایه ها: یک ساختار داده ساده که مجموعه ای از موارد از یک نوع را در مکان های حافظه پیوسته ذخیره می کند. آرایهها را میتوان با نمایهسازی در دسترس قرار داد و به پیادهسازی الگوریتمهای دسترسی متوالی به دادهها کمک کرد.
لیست های مرتبط: یک ساختار داده پویا که متشکل از دنباله ای از گره ها است که هر کدام شامل یک عنصر داده و یک اشاره گر به گره بعدی در سری است. لیست های پیوندی به پیاده سازی الگوریتم هایی کمک می کند که به عملیات درج و حذف مکرر نیاز دارند.
پشته ها: ساختار داده ای که عناصر را به ترتیب Last-In-First-Out (LIFO) ذخیره می کند. پشتهها برای پیادهسازی الگوریتمهایی که نیاز به عملیات بازگشت یا لغو دارند، مفید هستند.
دم: ساختار داده ای که عناصر را به ترتیب First-In-First-Out (FIFO) ذخیره می کند. خطوط به پیاده سازی الگوریتم هایی که نیاز به زمان بندی یا ارسال پیام دارند کمک می کند.
درختان: یک ساختار داده سلسله مراتبی که شامل گره هایی است که توسط لبه ها به هم متصل شده اند. درختان به نمایش روابط سلسله مراتبی بین عناصر داده، مانند ساختار یک سیستم فایل یا سازمان یک شرکت کمک می کنند.
نمودارها: ساختار داده ای که اشیاء (رئوس یا گره ها) را نشان می دهد که توسط لبه ها به هم متصل شده اند. نمودارها به مدل سازی روابط بین عناصر داده، مانند شبکه های اجتماعی و حمل و نقل کمک می کنند.
انتخاب ساختار داده به مشکل حل شده و الزامات عملکرد برنامه بستگی دارد. استفاده کارآمد از ساختارهای داده برای نوشتن برنامه های با کارایی بالا حیاتی است و درک مبادلات بین ساختارهای داده مختلف یک مهارت ضروری برای هر برنامه نویسی است.
بیایید نگاهی به ساختارها و نحوه استفاده از آنها در go در بخش بعدی بیاندازیم.
سازه ها
Go شما را قادر می سازد تا داده های مرتبط را در یک واحد با استفاده از ساختارها تعریف کنید. ساختار یک نوع داده است که گروهی از فیلدها را در خود جای می دهد. به عنوان مثال، اگر می خواهید اطلاعات کاربر را در یک مکان مشخص کنید، می توانید این کار را به صورت زیر انجام دهید:
type User struct {
name string
email string
accessLvl string
age int
}
کد بالا a را تعریف می کند User
ساخت با name
، email
، accessLvl
، و age
زمینه های. پس از تعریف ساختار، اکنون می توانید از مقدار زیر استفاده کنید:
user := User{"John Kenneth", "jken@ml.com", "admin", 40}
fmt.Printf("Your username is %s, your email is %s, and your access level is %s and your age is %d", user.name, user.email, user.accessLvl, user.age)
کد بالا a را تعریف می کند user
متغیر که نوعی از User
با یک مقدار برای تمام فیلدها، و مقادیر را با پیامی مانند این چاپ می کند:
Your username is John Kenneth, your email is jken@ml.com, and your access level is admin, and your age is 40
هنگام تعریف یک متغیر ساختار، لازم نیست برای همه فیلدها مقداری تعریف کنید. می توانید با آن کار کنید User
ساختار به این صورت:
user := User{
name: "John Kenneth",
email: "jkem@ml.com",
age: 40,
}
user.accessLvl = "admin"
کد بالا قسمت هایی از را تعریف می کند user
متغیرها بر اساس اعلامیه و سایر قسمت ها پس از آن. کد بالا همان نتیجه قبلی را نشان می دهد.
فیلدهای تعبیه شده
یک ساختار می تواند یک فیلد در داخل ساختار دیگر برای کاهش تکرار کد باشد. برای مثال می توانید از User
ساختار داخل Employee
struct برای جلوگیری از تکرار فیلدهای مشابه در داخل آن مانند این است:
type Employee struct {
User
salary int
role string
}
اکنون می توانید یک متغیر را بر اساس آن تعریف کنید Employee
ساختار مانند این است:
employee := Employee{
User: User{
name: "John Kenneth",
email: "jken@ml.com",
age: 40,
},
salary: 50000,
role: "Developer",
}
fmt.Printf("Your employee name is %s, your email is %s, and your access level is %s, and your age is %d. While your role is %s and your salary is %d", employee.name, employee.email, employee.accessLvl, employee.age, employee.role, employee.salary)
کد بالا یک پیام بر اساس employee
متغیر مانند این:
Your employee name is John Kenneth, your email is jken@ml.com, and your access level is admin, and your age is 40. While your role is Developer and your salary is 50000
توجه: نام فیلدها باید منحصر به فرد باشد. با این حال، اگر لازم است یک ساختار مشابه را دو بار جاسازی کنید، باید پیشوند آن را با نامی مانند زیر وارد کنید:
Other User
، و با نام قابل دسترسی خواهد بودOther
.
سازه های ناشناس
ساختارهای ناشناس در Go زمانی استفاده می شوند که شما نیاز به تعریف یک نمونه از ساختار دارید. شما می توانید ساختارهای ناشناس را به این صورت تعریف کنید:
car := struct {
brand string
model string
year int
price int
}{
brand: "Toyota",
model: "Vios",
year: 2020,
price: 1000000,
}
fmt.Printf("The brand of the car is %s, the model is %s, the year is %d, and the price is %d", car.brand, car.model, car.year, car.price)
کد بالا یک ساختار ناشناس را تعریف می کند car
و بر اساس مقادیر آن پیامی را چاپ می کند. کد بالا باز خواهد گشت:
The brand of the car is Toyota, the model is Vios, the year is 2020, and the price is 1000000
روش های گیرنده
Go به شما اجازه می دهد تا توابعی را تعریف کنید که فقط روی نمونه هایی از ساختارها کار می کنند. به عنوان مثال، اگر می خواهید تابعی ایجاد کنید که یک کارمند را توصیف کند، می توانید از یک تابع گیرنده مانند زیر استفاده کنید:
func (u *Employee) describe() string {
desc := fmt.Sprintf("Name: %s, Email: %s, Age: %d, Salary: %d, Role: %s", u.name, u.email, u.age, u.salary, u.role)
return desc
}
کد بالا یک تابع را تعریف می کند، describe
، که فقط روی نمونه هایی از Employee
یک پیام را با اطلاعات کارمند ساخته و چاپ می کند. اکنون می توانید از تابع روی متغیری مانند زیر استفاده کنید:
fmt.Println(employee.describe())
کد بالا باز خواهد گشت:
Name: John Kenneth, Email: jken@ml.com, Age: 40, Salary: 5000, Role: CEO
توجه: بریس هایی که قبل از نام تابع قرار می گیرند حاوی عبارت هستند گیرنده، یک اشاره گر از نوعی که تابع روی آن عمل می کند. بیشتر در مورد اشاره گرها در ادامه مقاله.
مزایای Structs in Go
در Go، ساختارها یک نوع داده ترکیبی قدرتمند هستند که مزایای متعددی را نسبت به انواع دادههای دیگر مانند آرایهها یا نقشهها ارائه میکنند. برخی از مزایای کلیدی استفاده از ساختارها در Go شامل موارد زیر است:
کپسوله سازی: ساختارها به شما این امکان را می دهند که داده های مرتبط را با هم در یک شی واحد کپسوله کنید، که می تواند به بهبود سازماندهی کد و قابلیت نگهداری کمک کند. این باعث میشود که استدلال در مورد دادههای مورد استفاده در برنامه شما آسانتر شود و خطر خطاهای ناشی از تغییر تصادفی دادههای اشتباه کاهش مییابد.
روش های ساختاری: ساختارهای Go میتوانند متدهای مرتبطی داشته باشند که به شما امکان میدهند رفتاری خاص برای یک نوع ساختار خاص تعریف کنید. این می تواند به ساده سازی کد شما و کاهش کد دیگ بخاری که باید بنویسید کمک کند.
نوع ایمنی: Go یک زبان استاتیک تایپ شده است، به این معنی که نوع متغیر در زمان کامپایل شناخته می شود. استفاده از ساختارها می تواند به اطمینان از سازگاری انواع داده های شما کمک کند و می تواند به کشف خطاها در زمان کامپایل کمک کند.
بهره وری: Structs in Go به گونه ای طراحی شده اند که سبک و کارآمد باشند، به این معنی که می توانند ساختارهای داده پیچیده را بدون اعمال جریمه عملکرد قابل توجه نشان دهند.
انعطاف پذیری: Structs در Go را می توان در ساختارهای دیگر تو در تو قرار داد و به شما این امکان را می دهد که ساختارهای داده پیچیده ای ایجاد کنید که به راحتی قابل دستکاری و پردازش باشند.
استفاده از ساختارهایی در Go که به بهبود سازماندهی کد، کاهش خطاها و بهبود عملکرد کمک می کند و همچنین برنامه ها را کارآمدتر، قابل نگهداری و انعطاف پذیرتر می کند.
بیایید اشاره گرها و نحوه استفاده از آنها را در بخش بعدی بررسی کنیم.
اشاره گرها
Go به شما این امکان را می دهد که با استفاده از نشانگرها، تغییر ناپذیری را برای کارایی در کد خود قربانی کنید. اشاره گر در Go آدرس حافظه یک متغیر معین است. به عنوان مثال، هنگامی که یک متغیر در Go ایجاد می کنید، یک آدرس حافظه منحصر به فرد به نظر می رسد 0xc00000c0c0
به طور خودکار به متغیر اختصاص داده می شود و می توان از آن برای انجام کارهای مختلف در کد خود استفاده کرد.
یک مورد عالی برای استفاده از اشاره گرها در Go، تغییر مقدار یک متغیر در داخل و خارج از محدوده تابع است تا از ایجاد کپی های بیش از حد از متغیر در سراسر برنامه جلوگیری شود. به عنوان مثال، وقتی تابعی ایجاد میکنید که یک متغیر از قبل تعریف شده را میپذیرد و تغییر میدهد، متغیر تغییر نخواهد کرد. در عوض، Go یک مرجع از آن متغیر را فقط برای فراخوانی تابع ایجاد می کند. بیایید نمونه ای از آن را با یک تابع ببینیم:
var age = 40
func updateAge(age int) {
age = 100
fmt.Println(age)
}
func main() {
fmt.Println("Here is the value of age before calling updateAge: ", age)
updateAge(age)
fmt.Println("Here is the value of age after calling updateAge: ", age)
}
کد بالا یک متغیر تعریف می کند، age
، یک تابع، updateAge
که یک عدد را می گیرد، مقدار آن را به آن اختصاص می دهد 100
، و آن را چاپ می کند. در نهایت، پیامی با مقدار از چاپ می کند age
قبل و بعد از تماس با updateAge
تابع با متغیر سن کد بالا باید نتیجه زیر را برگرداند:
Here is the value of age before calling updateAge: 40
100
Here is the value of age after calling updateAge: 40
را updateAge
تابع همانطور که انتظار می رفت کار می کرد، اما نمی توانست مقدار آن را تغییر دهد age
به دلیل اصل ارزش عبوری Go. بنابراین چگونه می توانید مقدار واقعی را تغییر دهید age
متغیر؟ ابتدا، بیایید ببینیم که چگونه آدرس حافظه یک متغیر را چاپ کنیم.
آدرس های حافظه
همانطور که قبلا ذکر شد، هر متغیری در Go دارای یک آدرس حافظه است که هنگام ایجاد به آن اختصاص داده شده است. با استفاده از دستور زیر می توانید به آدرس دسترسی پیدا کنید:
fmt.Println("age pointer is: ", &age) // age pointer is: 0x6bd320
کد بالا پیامی را با age
آدرس حافظه متغیر مانند این است:
age pointer is: 0x6bd320
برای خود آدرس حافظه کاربرد کمی وجود دارد، اما می توانید هر کاری که می خواهید با آن انجام دهید.
عدم ارجاع
می توانید مشکلی را که قبلا دیدید با updateAge
عملکرد با عدم ارجاع این روشی برای انتقال مقدار یک متغیر به یک تابع است تا بتواند متغیر را مستقیماً تغییر دهد. قسمت های زیر از بلوک کد قبلی را به این صورت ویرایش کنید:
func updateAge(age *int) {
*age = 100
fmt.Println(*age)
}
// function call
updateAge(&age)
کد بالا بازنویسی می کند updateAge
تابعی برای پذیرش یک اشاره گر به عنوان پارامتر به جای متغیر. سپس از ارجاع مجدد با پیشوند نام پارامتر با استفاده می کند *
و تماس می گیرد updateAge
عملکرد با اشاره گر با پیشوند age
متغیر با &
. کد بالا اکنون باید برگردد:
Here is the age before calling updateAge: 40
100
Here is the age after calling updateAge: 100
برای درک بهتر آدرس حافظه و نحو ارجاع مجدد، کد زیر را در نظر بگیرید:
fmt.Println("age pointer is: ", &age)
fmt.Println("age value is: ", age)
fmt.Println("You can also access the value using with: ", (*&age))
کد بالا موارد زیر را برمی گرداند:
age pointer is: 0x8ae320
age value is: 40
You can also access the value using with: 40
مزایای استفاده از اشاره گر در Go
استفاده از نشانگرها در کد Go به شما امکان می دهد داده ها را به طور موثرتری دستکاری و اصلاح کنید و مزایای زیادی دارد. بیایید در این بخش به بررسی برخی از آنها بپردازیم.
تغییر متغیرها
هنگامی که می خواهید مقدار متغیری را در داخل یک تابع تغییر دهید – اگر یک متغیر را به یک تابع بر اساس مقدار ارسال کنید، هر گونه تغییری که در متغیر داخل تابع ایجاد شود، روی متغیر اصلی تأثیر نخواهد گذاشت. در عوض با ارسال یک اشاره گر به متغیر، تابع می تواند مقدار اصلی را مستقیماً تغییر دهد.
از کپی غیر ضروری خودداری کنید
هنگامی که می خواهید از کپی ساختارهای داده بزرگ خودداری کنید – در Go، ارسال ساختارهای داده بزرگ بر اساس ارزش می تواند ناکارآمد باشد، زیرا نیاز به ایجاد یک کپی جدید از کل ساختار دارد. با ارسال یک اشاره گر به ساختار به جای آن، می توانید از این کپی غیر ضروری جلوگیری کنید و عملکرد را بهبود بخشید.
به اشتراک گذاری داده ها بین توابع
هنگامی که می خواهید داده ها را بین توابع به اشتراک بگذارید – در برخی موارد، ممکن است بخواهید داده ها را بین چندین تابع یا بسته به اشتراک بگذارید. ارسال یک اشاره گر به داده ها به چندین تابع اجازه می دهد تا به همان داده ها دسترسی پیدا کرده و آنها را تغییر دهند.
پیاده سازی ساختارهای داده
وقتی میخواهید ساختارهای داده مانند فهرستهای پیوندی یا درختان را پیادهسازی کنید – ساختارهای داده مانند فهرستهای پیوندی و درختها نیاز به ذخیرهسازی اشارهگرها به گرههای دیگر در ساختار دارند. با استفاده از اشاره گرها، می توانید به راحتی این ساختارهای داده پیچیده را ایجاد و دستکاری کنید.
مهم است که از اشاره گرها با دقت استفاده کنید و اطمینان حاصل کنید که حافظه را به درستی مدیریت می کنید تا از اشکالات و نشت حافظه جلوگیری کنید. با این حال، اشاره گرها می توانند ابزاری قدرتمند برای برنامه نویسی کارآمد و انعطاف پذیر در Go در صورت استفاده صحیح باشند.
نتیجه
ساختارها و اشاره گرها ویژگی های مهم Go هستند و درک آن می تواند بسیار پیچیده باشد. با این حال، امیدوارم این مقاله بتواند آنچه را که برای شروع کار با ساختارها و اشاره گرها در Go باید بدانید را به شما آموزش دهد. شما در مورد اشاره گرها، عدم ارجاع، ساختارها، ساختارهای جاسازی شده، ساختارهای ناشناس و مزایای استفاده از آنها در برنامه های Go خود یاد گرفتید.
لطفا در صورت داشتن هر گونه سوال، پیشنهاد یا اصلاح نظر خود را بنویسید. من مطمئن خواهم شد که به همه آنها پاسخ خواهم داد.