基础语法补充
Hello World!
package main
import (
"fmt"
)
func main(){
fmt.Println("Hello World!")
}
与C/C++不同,go文件开头需要声明入口文件,在上述代码中声明了main包为入口包,即该文件下程序为main包的一部分。而后才是与C/C++ “#include<>” 类似的导入库操作,在go文件中导入了“fmt”包。接下来是main函数,其中调用了fmt包中函数,可以发现fmt包主要起着格式化输入输出的作用。 从上述代码中可以总结出该go文件的语言结构,有以下部分:
- 包声明
- 引入包
- 函数
运算符
运算符优先级与C/C++类似
变量与常量
Go语言是强类型语言。 Go语言按类别划分有以下几种数据类型:
- bool(布尔型)
- 数字类型 --整型int --浮点型float32、float64
- 字符串类型
- 其他派生类型
对于强类型和弱类型语言,个人更喜欢强类型语言,强类型语言尽管在速度上不如弱类型语言,但强类型带来的严谨性使得程序更加安全。弱类型语言如js在编写代码过程中很容易因为类型而导致编写随意,使得后续在复审中花费更多的时间。ts作为静态类型也是为了解决这个毛病。
变量声明
- 使用var关键字声明变量,会自动推导类型
var a = "Hello"
- 显式声明变量类型
var a string = "Hello"
var f float64
- 使用 := 声明变量
b := 123
常量声明
- 使用const关键字声明常量
显式类型定义const s string = "World"
隐式类型定义const s = "World"
if else
if 520 % 99 == 0 {
fmt.Println("You love me")
} else {
fmt.Println("I love you")
}
与C/C++区别在于,if后面的条件不需要添加小括号,但必须添加大括号,大括号中语句不得与if在同一行。虽然但是,C/C++中我也不习惯将语句写在同一行,个人觉得这极其影响程序的可读性。
for循环
go语言中没有while、do while循环,仅有for循环
for {
fmt.Println("Loop")
break
}
for j := 5; j > 0; j-- {
fmt.Printf("%d\n", j)
}
i:=1
for i<5 {
fmt.Printf("%d", i)
i++
}
由上述代码可以看出在Go语言中尽管没有while等其他循环结构,但是for循环已经提供了while循环的功能。在学习C/C++的时候我就发现while循环有时挺鸡肋的明明可以全部使用for循环实现,在Go语言中验证了我的想法。
switch
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
default:
fmt.Println("other")
}
除了swtich与C/C++的区别在于,在Go语言中的switch不需要在每个case后添加break语句,默认仅执行一段case。那么有时确实需要执行下一条case呢该如何编写。此处提到fallthrough,fallthrough会强制执行后面的case语句,且不判断下一条语句表达式结果是否为true。 同时,在Go语言中case后接表达式可以很好地替代多重if-else if
进阶语法补充
Go语言递归函数
-
递归函数是指一个函数不断地调用自己,从而解决某个问题或某个算法的过程。在Go语言中,递归函数与其他函数并没有本质区别,只是在函数执行过程中自己调用自己。递归函数需要有终止条件,否则会导致无限递归,耗尽内存。
-
以下是几个递归函数的简单示例
- 用于计算斐波那契数列的第n个数字(n≥0):
func fibonac(n int) int { if n == 0||n == 1{ return n } return fibonac(n-1) + fibonac(n-2) }在上述函数定义中,我们首先检查n的值是否等于0或1,如果是则返回n。否则,我们递归地调用本函数两次,从而计算第n个斐波那契数。
- 用于计算阶乘
func factor(n int) int { if (n > 0) { result = n * Factorial(n-1) return result } return 1 }
在递归函数调用时,每次都会产生一个新的函数执行环境,因此递归函数的性能相对较,而且当递归深度过大时,可能会导致栈溢出等问题。因此,在使用递归时需要谨慎处理,可以采用尾递归和循环等结构优化递归函数。
递归函数可以用于解决很多问题,比如树形结构的遍历、计算阶乘、斐波那契数列等。递归函数的实现需要较高的逻辑思维能力(我每次写递归函数都需要经过一定时间的思考和反复的debug),需要清楚的确定递归的终止条件,以及每次迭代过程中需要做哪些操作。在编写递归方法时,需要注意性能和错误处理,防止优化不足或者导致程序崩溃等问题。
Go语言类型转换
- Go语言类型转换基本格式
type_name(expresion)
数值类型转换
var a int = 10
var b float64 = float64(a)
字符串类型转换
var str string = "10"
var num int
num, _ = strconv.Atoi(str)
上述strconv.Atoi函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误
Go语言的strconv(string conversion)包提供了字符串和基本数据类型之间的转换功能,是Go语言中常用的一个包。在程序中,经常需要将字符串和数字之间进行相互转换,strconv包提供了Int、ParseBool、Atoi等函数来完成这些功能,下面对一些常用函数进行介绍:
- Atoi:将字符串类型的数字转换为整型
func Atoi(s string) (int, error)- Itoa:将整型转换为字符串类型的数字
func Itoa(i int) string- ParseBool:将字符串类型的布尔值转换为bool型
func ParseBool(str string) (bool, error)- ParseInt:将字符串类型的数字转换为整型(必须指定进制)
func ParseInt(s string, base int, bitSize int) (i int64, err error)- FormatInt:将整型转换为字符串类型的数字(必须指定进制)
func FormatInt(i int64, base int) string- parseFloat:将字符串类型的数字转换为浮点型(可以指定位数)
func ParseFloat(s string, bitSize int) (float64, error)- FormatFloat:将浮点型数字转换为字符串类型的数字
func FormatFloat(f float64, fmt byte, prec, bitSize int) string通过strconv包中提供的函数,我们可以方便地进行不同类型的转换。需要注意的是,在进行转换时,应先判断转换是否成功,以避免因为不合法的字符串或者类型不匹配等原因导致程序发生异常。
Go语言接口
- 接口(interface)是一种抽象类型,它规定了一个对象应该执行哪些操作。接口定义了一个或多个方法,具体实现由实现接口的类型负责。
在Go语言中,任何实现了接口规定的全部方法的类型都被看作是实现了该接口,而不需要进行显式地声明。这样就在接口和实现之间实现了解耦,扩展性更强。
- 以下是一段示例程序:
package main
import (
"fmt"
"math"
)
//定义一个几何体的接口
type Geometry interface {
Area() float64
Perimeter() float64
}
//实现接口的两个方法,计算矩形的面积和周长
type Rectangle struct {
width, height float64
}
func (r Rectangle) Area() float64 {
return r.width * r.height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.width + r.height)
}
//实现接口的两个方法,计算圆形的面积和周长
type Circle struct {
x, y, radius float
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.radius
}
这段示例程序演示了在Go语言中如何使用接口来实现多态性。首先,我们定义了一个名为Geometry的接口,该接口定义了两个方法:Area()和Perimeter()。然后,我们创建了两个结构体类型Rectangle和Circle,并分别实现了Geometry接口中定义的方法。这样,Rectangle和Circle类型就被视为实现了Geometry接口。
也许这样还不足以看出Go接口设计的优雅,我们继续看他们在调用时的场景:
//定义一个打印几何体面积和周长的函数
func printGeometry(g Geometry) {
fmt.Println("面积:", g.Area())
fmt.Println("周长:", g.Perimeter())
}
func main() {
r := Rectangle{width: 3, height: 4}
c := Circle{x: 0, y: 0, radius: 5}
printGeometry(r)
printGeometry(c)
}
我们定义了一个名为printGeometry的函数,该函数接收一个Geometry类型的参数。该函数内部会调用传入参数的Area()和Perimeter()方法,并在屏幕上打印对应的结果。
需要注意的是,在main函数中,我们会将Rectangle类型和Circle类型的变量分别传给printGeometry函数作为参数,而不需要区分具体是哪一种类型。这正是接口设计的优美之处,通过接口我们可以将不同实现类型的对象看做单一类型来进行处理,从而实现复用和灵活性的提高。