روش های مختلف مقایسه مقادیر در Go — به زبان ساده
در این مطلب قصد داریم در مورد روشهای مختلف مقایسه مقادیر در Go و همچنین مرتبسازی مقادیر و ترتیببندی آنها صحبت کنیم. اگر شما قبلاً با مفاهیم شیءگرایی آشنا باشید، میدانید که مقایسه وهلههای اشیا یا از طریق نوعی overload کردن عملگر صورت میگیرد و یا با override کردن یک متد ()equal یا ()compareTo و الصاق به شیء برای تعریف شیوه مقایسه یک وهله با دیگری، انجام مییابد.
در زبان برنامهنویسی Go مجموعه کوچکی از انواع شناخته شده وجود دارند که حتی انواع تعریفشده از سوی کاربر بر مبنای آنها تعریف میشوند. از این رو قواعد Go برای مقایسه کردن مقادیر مستقیماً در این زبان منظور شده است. به طور کلی دو مقدار تنها در صورتی میتوانند مقایسه شوند که یک نوعشان (یا نوع تشکیلدهنده) یکی باشد. با این حال این قاعده کلی، ظرافتهای زیادی دارد که در بخش بعدی بیشتر توضیح خواهیم داد.
مقایسه مقادیر در Go
برابری در زبان Go با استفاده از عملگرهای مقایسهای == و =! تست میشود. اغلب انواع را میتوان برای برابر بودن تست کرد، برخی از آنها پشتیبانی محدودی دارند، در حالی که برخی دیگر هیچ نوع پشتیبانی ندارند. عملگرهای ترتیببندی >، =>، < و =< برای تست مقادیر با انواعی که میتوان مرتبسازی شوند مورد استفاده قرار میگیرند. در ادامه به بررسی شیوه مقایسه هر نوع در Go میپردازیم.
مقادیر بولی
مقادیر بولی را میتوان با مقادیر از پیش تعریفشده true یا false مقایسه کرد. در صورت اقدام به مقایسه یک مقدار بولی با مقدار عددی با خطا مواجه خواهیم شد.
1var a := true
2if a != (10 == 20) {
3 fmt.Println("a not true")
4}
5// following blows up at compilation
6if a == 1 { ... }
اعداد صحیح و اعشاری
مقایسه مقادیر عددی نیز چنان که انتظار داریم و با تبعیت از قاعده کلی پیشگفته در زمینه برابری و ترتیببندی انجام مییابد.
1import (
2 "fmt"
3 "math"
4)
5func main() {
6 a := 3.1415
7 if a != math.Pi {
8 fmt.Println("a is not pi")
9 }
10}
با این حال به دلیل محدودیتهای نوعبندی صریح در Go اعداد صحیح را تنها با اعداد صحیح و اعداد اعشاری را نیز تنها با اعداد اعشاری میتوان مقایسه کرد. اگر تلاش کنید مقادیر اعشاری را با اعداد صحیح یا برعکس مقایسه کنید، سیستم نوعبندی نیازمند یک تبدیل خواهد بود یا ریسک شکست در زمان کامپایل پدید میآید.
1func main() {
2 a := 3.1415
3 b := 6
4 if a != b {
5 fmt.Println("a is not b")
6 }else if a <= b {
7 fmt.Println("a is in the right position")
8 }
9}
10// Boom, blows up with:
11// operation: a != b (mismatched types float64 and int)
در مثال قبلی، این کد تنها در صورتی کار میکرد که هر دو متغیر با نوع یکسانی اعلان شده باشند، یا به نوع یکسانی تبدیل میشدند.
1func main() {
2 a := 3.1415
3 b := 6
4 if a != float64(b) {
5 fmt.Println("a is not b")
6 }
7}
اعداد مختلط
اعداد مختلط نیز میتوانند برای برابری تست شوند. دو عدد مختلط تنها در صورتی برابر هستند که بخشهای حقیقی و موهومی آنها به ترتیب با هم برابر باشند.
1func main() {
2 a := complex(-3.25, 2)
3 b := -3.25 + 2i
4 if a == b {
5 fmt.Println("a complex as b")
6 }
7}
اما به دلیل ماهیت اعداد مختلط، امکان پشتیبانی از عملیات ترتب بندی آنها در زبان Go وجود ندارد.
1func main() {
2 a := complex(-3.25, 2)
3 b := -3.25 + 2i
4 if a < b {
5 fmt.Println("a complex as b")
6 }
7}
8//compilation error
9//invalid operation: a <= b (operator <= not defined on complex128)
مقادیر رشتهای
مقادیر رشتهای از هر دو عملگر استاندارد برابری و ترتیببندی پشتیبانی میکنند. هیچ تابع اضافی برای مقایسه رشتهها لازم نیست. مقادیر رشتهای را میتوان به صورت خودکار به صورت الفبایی با استفاده از ==، =!، <، >، و >= مقایسه کرد.
1func main() {
2 cols := []string{
3 "xanadu", "red", "fulvous",
4 "white", "green", "blue",
5 "orange", "black", "almond"}
6 for _, col := range cols {
7 if col >= "red" || col == "black" {
8 fmt.Println(col)
9 }
10 }
11}
مقادیر struct
دو مقدار struct را میتوان برای برابر بودن با استفاده از مقایسه مقدار فیلدهای متناظر مورد تست قرار داد. به طور کلی دو مقدار struct تنها در صورتی برابر هستند که نوع یکسانی داشته باشند و فیلدهای متناظرشان برابر باشند.
1func main() {
2 p1 := struct {a string; b int}{"left", 4}
3 p2 := struct {a string; b int}{a: "left", b: 4}
4 if p1 == p2 {
5 fmt.Println("Same position")
6 }
7}
در قطعه کد فوق struct به نام p1 با p2 برابر است، چون هر دو از یک نوع هستند و مقادیر فیلد متناظرشان نیز یکسان است. هر تغییری در مقادیر فیلد موجب میشود که struct-ها برابر نباشند.
با این حال، مقادیر struct نمیتوانند با استفاده از عملگرهای ترتیببندی مقایسه شوند. از این رو کد زیر کامپایل نخواهد شد.
1func main() {
2 p1 := struct {a string; b int}{"left", 4}
3 p2 := struct {a string; b int32}{a: "left", b: 4}
4 if p1 > p2 {
5 fmt.Println("Same position")
6 }
7}
8// compilation error
9// invalid operation: p1 > p2 (operator > not defined on struct)
آرایهها
مقادیر آرایهای با مقایسه عناصر با انواع تعریفشده برای برابری تست میشوند. آرایهها تنها در صورتی برابر هستند که مقادیر متناظرشان برابر باشند.
1func main() {
2 pair1 := [2]int {4, 2}
3 pair2 := [2]int {2, 4}
4 if pair1 != pair2 {
5 fmt.Println("different pair")
6 }
7}
همانند مقادیر struct آرایهها نمیتوانند با استفاده از عملگرهای ترتیببندی <، <=، > و >= مقایسه شوند. هر تلاشی در این جهت موجب بروز خطای کامپایل خواهد شد.
اشارهگرها
مقادیر اشارهگر میتوانند برای برابری مقایسه شوند، اما این امکان برای ترتیببندی وجود ندارد. دو مقدار اشارهگر تنها در صورتی برابر تصور میشوند که به مقدار یکسانی در حافظه اشاره کنند (و یا تهی باشند). برای نمونه در قطعه کد زیر &pair با ptr2 برابر است، اما &pair با ptr برابر نیست.
1func main() {
2 pair := [2]int {4, 2}
3 ptr := &[2]int {4, 2}
4 ptr2 := &pair
5
6 if &pair != ptr {
7 fmt.Println("pointing different")
8 }
9 if &pair == ptr2 {
10 fmt.Println("pointing the same")
11 }
12}
به خاطر داشته باشید که یک اشارهگر به یک نوع در واقع همان خود نوع نیست. از این رو تلاش برای مقایسه این دو یک خطای عدم تطبیق کامپایل ایجاد خواهد کرد.
اینترفیسها
مقادیر اینترفیس از این جهت جالب هستند که میتوانند با موارد زیر مقایسه شوند:
- با اینترفیسهای دیگر
- با مقادیری که نوعشان، اینترفیس را پیادهسازی میکند
دو مقدار اینترفیس تنها در صورتی برابر تصور میشوند که نوعهای تشکیلدهنده آنها و مقادیرشان مقایسه پذیر و برابر باشند و یا این که هر دو اینترفیس تهی باشند.
برای نمونه در قطعه کد زیر، مقادیر اینترفیس r0 و r2 برابر هستند، زیرا نوع تشکیلدهنده یکسانی را با مقادیر یکسان rectangle{l:3, w:6} پیادهسازی میکنند. از سوی دیگر اگر چه مقادیر اینترفیس r0 و r1 نوع اینترفیس یکسانی را پیادهسازی کردهاند، اما میتوان مشاهده کرد که برابر نیستند، زیرا مقادیرشان به صورت rectangle{3, 6} و rectangle{6, 3} بوده و متفاوت است. به طور مشابه متغیرهای اینترفیس r1 و s0 برابر نیستند، زیرا مقادیر دینامیک متفاوتی دارند، گرچه اینترفیس یکسانی را پیادهسازی میکنند.
1type shape interface {
2 area() int
3}
4type rectangle struct {
5 l int
6 w int
7}
8func (r rectangle) area() int {
9 return r.l * r.w
10}
11type square struct {
12 l int
13}
14func (s square) area() int {
15 return s.l * s.l
16}
17func main() {
18 var r0 shape = rectangle{3, 6}
19 var r1 shape = rectangle{6, 3}
20 var r2 shape = rectangle{3, 6}
21 var s0 shape = square{5}
22
23 if r0 == r2 {
24 fmt.Println("r0 and r2 same shapes")
25 }
26
27 fmt.Println("r1 and s0 equal", (r1 == s0))
28}
لازم به اشاره است که اگر انواع تشکیلدهنده و مقادیر اینترفیس مقایسه پذیر نباشند، هر تلاشی برای مقایسه کردن آنها موجب بروز مشکلاتی در زمان اجرا میشود.
کانالها
مقادیر کانال را تنها میتوان در مورد برابری مقایسه کرد. دو مقدار کانال تنها در صورتی برابر تصور میشوند که از فراخوانی make یکسانی نشأت گرفته باشند، یعنی به مقدار کانال یکسانی در حافظه اشاره کنند.
برای نمونه در مثال زیر ch0 با ch1 برابر نیستند هر چند نوع یکسانی دارد. اما ch1 با ch2 برابر هستند زیرا هر دو آنها به مقدار کانال یکسانی اشاره دارند.
1func main() {
2 ch0 := make(chan int)
3 ch1 := make(chan int)
4 ch2 := ch1
5
6 fmt.Println(“ch0 == ch1”, (ch0==ch1))
7 fmt.Println(“ch1 == ch2”, (ch1==ch2))
8}