网站开发工程师分析,山东省建设工程 评估中心网站,门户网站简称,购物网站销售管理一、函数
函数的基本语法#xff1a;
func 函数名#xff08;形参列表#xff09;#xff08;返回值列表#xff09; {执行语句...return 返回值列表
}
1.形参列表#xff1a;表示函数的输入
2.函数中的语句#xff1a;表示为了实现某一功能的代码块
3.函数可以有返回…一、函数
函数的基本语法
func 函数名形参列表返回值列表 {执行语句...return 返回值列表
}
1.形参列表表示函数的输入
2.函数中的语句表示为了实现某一功能的代码块
3.函数可以有返回值,也可以没有函数的调用
func main() {sum max(1, 2)fmt.Printf(最大值是%d\n, sum)
}
//一个返回值不用加括号
func max(num1, num2 int) int {var result intif num1 num2 {result num1 } else {result num2}return result
}函数首字母大写该函数可以被本文件包和其他包文件使用类似public;首字母小写只能被本包文件使用其他包文件不能被使用类似privateGo函数不支持函数重载
func text(a int){fmt.Println(a)
}
//Go语言不支持传统的函数重载会报函数重复定义
func **text**(a int , b int) {}在Go中函数也是一种数据类型可以赋值给一个变量则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
func getSum(n1 int, n2 int) int {return n1 n2
}func main() {a : getSum //将函数赋值给一个变量此时变量a是函数类型fmt.Printf(a的类型%T, getSum类型是%T\n, a, getSum)// a的类型func(int, int) int, getSum类型是func(int, int) intres : a(10, 40) // 等价 res : getSum(10, 40)fmt.Println(res, res) //res 50}函数参数
传递参数
基本数据类型和数组默认都是值传递的即进行值拷贝。在函数内修改不会影响到原来的值。
func test01(n1 int) {n1 n1 10fmt.Println(test01() n1 , n1) //test01() n1 30
}func main() {num : 20test01(num)fmt.Println(main() num , num) //main() num 20
}
如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型)可以传入变量的地址函数内以指针的方式操作变量。从效果上看类似引用。
// n1 就是 *int 类型
func test02(n1 *int) {fmt.Printf(n1的值%v\n, n1) //n1的值0xc04200e0b0*n1 *n1 10fmt.Println(test02() n1 , *n1) // test02() n1 30
}func main() {num 20fmt.Printf(num的地址%v\n, num) //num的地址0xc04200e0b0test02(num)fmt.Println(main() num , num) // main() num 30
}
值类型和引用类型 值类型:基本数据类型int系列, float 系列, boo1, string 、数组和结构体struct 引用类型:指针、slice切片、map、管道chan、interface 等都是引用类型不管是指针、引用类型还是其他类型参数都是值拷贝传递。区别只是拷贝目标对象还是拷贝指针而已。
可变参数
//支持o到多个参数
func sum(args... int) sum int {}
//支持1到多个参数
func sum(n1 int, args... int) sum int {
}
可变参数本质就是一个切片args[index]可以访间到各个值只能接收一个到多个同类型参数且必须放在列表尾部
func sum(n1 int, args... int) int {sum : n1 //遍历args for i : 0; i len(args); i {sum args[i] //args[0] 表示取出args切片的第一个元素值其它依次类推}return sum
}func main() {res4 : sum(10, 90, 10,100)fmt.Println(res4, res4) //res4 210
}
将切片作为变参时需进行展开操作如果是数组先将其转换为切片
func test(a ...int) {fmt.Println(a) //[10 20 30]
}func main() {a : []int{10, 20, 30} //先将数组转成slicetest(a...) //将slice展开
}
函数作为另一个函数的参数
函数既然是一种数据类型因此在Go中函数可以作为形参并且调用
func getSum(n1 int, n2 int) int {return n1 n2
}//函数既然是一种数据类型因此在Go中函数可以作为形参并且调用
func myFun(funvar func(int, int) int, num1 int, num2 int ) int {return funvar(num1, num2)
}func main() {//看案例res2 : myFun(getSum, 50, 60) //将getSum函数作为myFun函数的参数fmt.Println(res2, res2)
}
返回值
返回值列表也可以是多个
func swap(x, y string) (string, string) {return y, x
}func main() {a, b : swap(Google, Runoob)fmt.Println(a, b)
}
使用_标识符忽略返回值
func cal(n1 int, n2 int) (int, int) {sum : n1 n2sub : n1 - n2return sum, sub
}func main() {res1, _ : cal(10, 20) //忽略第二个返回值fmt.Printf(res1%d\n, res1) //res130
}
命名返回值支持对函数返回值命名优缺点共存
命名返回值和参数一样可当作函数局部变量使用最后由return隐式返回
//支持对函数返回值命名
func getSumAndSub(n1 int, n2 int) (sum int, sub int){sub n1 - n2sum n1 n2return
}
func main() {a1, b1 : getSumAndSub(1, 2)fmt.Printf(a%v b%v\n, a1, b1) //a13 b1-1
}
匿名函数
Go支持匿名函数匿名函数就是没有名字的函数如果我们某个函数只是希望使用一次可以考虑使用匿名函数匿名函数也可以实现多次调用。
在定义匿名函数时就直接调用这种方式匿名函数只能调用一次。
func main() {//在定义匿名函数时就直接调用这种方式匿名函数只能调用一次res1 : func (n1 int, n2 int) int {return n1 n2}(10, 20)fmt.Println(res1, res1) //res1 30
}
将匿名函数赋给一个变量(函数变量)再通过该变量来调用匿名函数
func main() {//将匿名函数func (n1 int, n2 int) int赋给 a变量//则a 的数据类型就是函数类型 此时,我们可以通过a完成调用a : func (n1 int, n2 int) int {return n1 - n2}res2 : a(10, 30)fmt.Println(res2, res2) //res2 -20res3 : a(90, 30)fmt.Println(res3, res3) //res3 60
}
闭包
闭包就是一个函数和其他的相关的引用环境组合的一个整体实体
//累加器
func AddUpper() func (int) int {var n int 10 return func (x int) int {n n xreturn n}
}func main() {//使用前面的代码f : AddUpper()fmt.Println(f(1))// 11 fmt.Println(f(2))// 13fmt.Println(f(3))// 16
}
返回的是一个匿名函数但是这个匿名函数引用到函数外的n ,因此这个匿名函数就和n形成一个整体构成闭包。
延迟处理defer
在函数中程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等)为了在函数执行完毕后及时的释放资源Go的设计者提供defer (延时机制)。
当go执行到一个defer时不会立即执行defer后的语句而是将defer后的语句压入到一个栈中然后继续执行函数下一个语句。当函数执行完毕后在从defer栈中依次从栈顶取出语句执行(注:遵守栈先入后出的机制)。在defer将语句放入到栈时也会将相关的值拷贝同时入栈
func sum(n1 int, n2 int) int {//当执行到defer时暂时不执行会将defer后面的语句压入到独立的栈(defer栈)//当函数执行完毕后再从defer栈按照先入后出的方式出栈执行defer fmt.Println(ok1 n1, n1) //defer 3. ok1 n1 10defer fmt.Println(ok2 n2, n2) //defer 2. ok2 n2 20//增加一句话n1 // n1 11n2 // n2 21res : n1 n2 // res 32fmt.Println(ok3 res, res) // 1. ok3 res 32return res
}func main() {res : sum(10, 20)fmt.Println(res, res) // 4. res 32
}
最佳实践当函数执行完毕后可以及时的释放函数创建的资源
func test() {//关闭文件资源file openfile(文件名)defer file.close()//其他代码
}
错误处理
在Go语言中错误被认为是一种可以预期的结果而异常则是一种非预期的结果发生异常可能表示程序中存在BUG或发生了其它不可控的问题。
错误 Go语言中错误被认为是一种可以预期的结果而异常则是一种非预期的结果发生异常可能表示程序中存在BUG或发生了其它不可控的问题。
Go中的错误类型error
type error interface {Error() string
}函数通常可在最后一个返回值中返回错误信息自定义错误errors.New(“错误说明”)会返回一个error类型的值表示一个错误
func myF(f float64) (float64, error) {if f 0 {return 0, errors.New(Not legal input )}// 实现return 0.0, nil
}func main() {_, e : myF(-1)_, e2 : myF(2)fmt.Println(e) // Not legal inputfmt.Println(e2) // nil
}
异常处理
Go语言追求简洁优雅所以Go语言不支持传统的 try…catch…finally 这种处理 Go中引入的处理方式为: defer, panic, recover。Go中可以抛出一个panic 的异常然后在defer中通过recover捕获这个异常然后正常处理 defer是Go提供的一种延迟执行机制每次执行 defer都会将对应的函数压入栈中。在函数返回或者 panic 异常结束时Go 会依次从栈中取出延迟函数执行。 panic用于主动抛出程序执行的异常会终止其后将要执行的代码并依次逆序执行 panic 所在函数可能存在的 defer 函数列表。panic 内置函数 ,接收一个interface{}类型的值也就是任何值了作为参数。可以接收error类型的变量输出错误信息并退出程序. recover 关键字主要用于捕获异常将程序状态从严重的错误中恢复到正常状态。 必须在 defer 函数中才能生效。
deferpanicrecover的代码样例
func my(i int) int {defer func() {if err : recover(); err ! nil {fmt.Println(发生了异常, err)}}()if i ! 5 {return i} else {panic(panic)}return -1
}
func main() {for i : 0; i 10; i {a : my(i)fmt.Println(a)}
}
运行结果
0
1
2
发生了异常 panic
0
4
使用deferrecover来处理错误
func test() {//使用defer recover 来捕获和处理异常defer func() {err : recover() // recover()内置函数可以捕获到异常if err ! nil { // 说明捕获到错误fmt.Println(err, err)//这里就可以将错误信息发送给管理员....fmt.Println(发送邮件给adminsohu.com~)}}()num1 : 10num2 : 0res : num1 / num2fmt.Println(res, res)
}
func main() {test()
} 内置函数
Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作例如len、cap 和 append或必须用于系统级的操作
append -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close -- 主要用来关闭channel
delete -- 从map中删除key对应的value
panic -- 停止常规的goroutine panic和recover用来做错误处理
recover -- 允许程序定义goroutine的panic动作
real -- 返回complex的实部 complex、real imag用于创建和操作复数
imag -- 返回complex的虚部
make -- 用来分配内存返回Type本身(只能应用于slice, map, channel)
new -- 用来分配内存主要用来分配值类型比如int、struct。返回指向Type的指针
cap -- capacity是容量的意思用于返回某个类型的最大容量只能用于切片和 map
copy -- 用于复制和连接slice返回复制的数目
len -- 来求长度比如string、array、slice、map、channel 返回长度
print、println -- 底层打印函数在部署环境中建议使用 fmt 包
new的使用
func main() {num1 : 100fmt.Printf(num1的类型%T , num1的值%v , num1的地址%v\n, num1, num1, num1)num2 : new(int) // *int//num2的类型%T *int//num2的值 地址 0xc04204c098 这个地址是系统分配//num2的地址%v 地址 0xc04206a020 (这个地址是系统分配)//num2指向的值 100*num2 100fmt.Printf(num2的类型%T , num2的值%v , num2的地址%v\n num2这个指针指向的值%v, num2, num2, num2, *num2)
} 二、方法
2.1 方法简介 方法是与指定的数据类型绑定的特殊函数
go方法的声明
func (t type) methodName (参数列表) (返回值列表){方法体return 返回值
}
// t type 表示这个方法和type这个类型进行绑定t为type的一个实例
func (p Person) methodName (参数列表) (返回值列表){}t表示哪个Person变量调用这个p就是它的副本。举例说明
type Person struct {Name stringAge int Hometown string score map[string]int
}
func (p Person) test() {p.Age 1p.score[China] 1 //对于引用数据类型会修改其值
}func main() {m0 : make(map[string]int)m0[China] 80person0 : Person{szc, 23, Henan Anyang, m0}person2 : new (Person)(*person2).Name Jason(*person2).Age 24m2 : make(map[string]int)m2[Math] 90(*person2).score m2person0.test()fmt.Println(person0)(*person2).test()fmt.Println(*person2)
}方法的调用和传参机制
方法的调用和传参机制和函数基本一样。不一样的地方时变量调用方法时该变量本身也会作为一个参数传递到方法(如果变量是值类型则进行值拷贝如果变量是引用类型则进行地质拷贝)
方法的注意事项
如果希望修改结构体变量的值可以通过结构体指针的方式来处理
type Circle struct {radius float64
}//为了提高效率通常我们方法和结构体的指针类型绑定
func (c *Circle) area2() float64 {//因为 c是指针因此我们标准的访问其字段的方式是 (*c).radius//return 3.14 * (*c).radius * (*c).radius// (*c).radius 等价 c.radius fmt.Printf(c 是 *Circle 指向的地址%p\n, c)c.radius 10return 3.14 * c.radius * c.radius
}func main() {//创建一个Circle 变量var c Circle fmt.Printf(main c 结构体变量地址 %p\n, c)c.radius 7.0//res2 : (c).area2()//编译器底层做了优化 (c).area2() 等价 c.area()//因为编译器会自动的给加上 cres2 : c.area2()fmt.Println(面积, res2)fmt.Println(c.radius , c.radius) //10
} Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定)因此自定义类型都可以有方法而不仅仅是struct比如int , float32等都可以有方法。
func (i integer) print() {fmt.Println(i, i)
}
//编写一个方法可以改变i的值
func (i *integer) change() {*i *i 1
}func main() {var i integer 10i.print()i.change()fmt.Println(i, i)
}
如果一个类型实现了String()这个方法那么fint.Println默认会调用这个变量的String()进行输出
如果String()方法绑定的是结构体指针那么输出时要传入地址否则会按照原来的方式输出
type Student struct {Name stringAge int
}//给*Student实现方法String()
func (stu *Student) String() string {str : fmt.Sprintf(Name[%v] Age[%v], stu.Name, stu.Age)return str
}func main() {//定义一个Student变量stu : Student{Name : tom,Age : 20,}//如果你实现了 *Student 类型的 String方法就会自动调用fmt.Println(stu)
}
对于方法如 struct的方法)接收者为值类型时可以直接用指针类型的变量调用方法反过来同样也可以对于普通函数接收者为值类型时不能将指针类型的数据直接传递反之亦然
type Person struct {Name string
} //函数
//对于普通函数接收者为值类型时不能将指针类型的数据直接传递反之亦然func test01(p Person) {fmt.Println(p.Name)
}func test02(p *Person) {fmt.Println(p.Name)
}//对于方法如struct的方法
//接收者为值类型时可以直接用指针类型的变量调用方法反过来同样也可以func (p Person) test03() {p.Name jackfmt.Println(test03() , p.Name) // jack
}func (p *Person) test04() {p.Name maryfmt.Println(test03() , p.Name) // mary
}func main() {p : Person{tom}test01(p)test02(p)p.test03()fmt.Println(main() p.name, p.Name) // tom(p).test03() // 从形式上是传入地址但是本质仍然是值拷贝fmt.Println(main() p.name, p.Name) // tom(p).test04()fmt.Println(main() p.name, p.Name) // maryp.test04() // 等价 (p).test04 , 从形式上是传入值类型但是本质仍然是地址拷贝}
*不管调用形式如何真正决定是值拷贝还是地址拷贝看这个方法是和哪个类型绑定。如果是值类型比如(p Person)则是值拷贝如果和指针类型比如是( Person)则是地址拷贝。 通过方法封装
封装的实现步骤
将结构体、字段的首字母小写给结构体所在的包提供一个工厂模式的函数首字母大写类似一个构造函数提供一个首字母大写的 Set 方法类似其它语言的 public用于对属性判断并赋值提供一个首字母大写的 Get 方法类似其它语言的 public用于获取属性的值。
type person struct {Name stringage int //其它包不能直接访问..sal float64
}//写一个工厂模式的函数相当于构造函数
func NewPerson(name string) *person {return person{Name : name,}
}//为了访问age 和 sal 我们编写一对SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {if age 0 age 150 {p.age age} else {fmt.Println(年龄范围不正确..)//给程序员给一个默认值}
}
func (p *person) GetAge() int {return p.age
}func (p *person) SetSal(sal float64) {if sal 3000 sal 30000 {p.sal sal} else {fmt.Println(薪水范围不正确..)}
}func (p *person) GetSal() float64 {return p.sal
}func main() {var p *person NewPerson(smith)p.SetAge(18)p.SetSal(5000)fmt.Println(*p)fmt.Println(p.Name, age , p.GetAge(), sal , p.GetSal())
}
三、接口
接口简介 interface 类型可以定义一组方法但是这些不需要实现并且 interface不能包含任何变量。 Go接口实现机制很简洁只要目标类型方法集内包含接口声明的全部方法就被视为实现了该接口无须做显式声明 接口可以嵌入其他接口类型 接口只能声明方法不能实现 一个自定义类型可以实现多个接口 接口通常以 er 作为名称后缀 只要是自定义数据类型就可以实现接口不仅仅是结构体类型。
type integer int func (i integer) Say() {fmt.Println(inter Say i , i )
}var i integer 10 var b AInterface ib.Say()// integer Say i 10代码示例
//声明/定义一个接口
type Usber interface {//声明了两个没有实现的方法Start() Stop()
}type Phone struct {} //让Phone 实现 Usb接口的方法就实现了Usb接口
func (p Phone) Start() {fmt.Println(手机开始工作。。。)
}
func (p Phone) Stop() {fmt.Println(手机停止工作。。。)
}//计算机
type Computer struct {}//编写一个方法Working 方法接收一个Usb接口类型变量
//只要是实现了 Usb接口 所谓实现Usb接口就是指实现了 Usb接口声明所有方法
func (c Computer) Working(usb Usber) {//通过usb接口变量来调用Start和Stop方法usb.Start()usb.Stop()
}func main() {//测试//先创建结构体变量computer : Computer{}phone : Phone{}//关键点computer.Working(phone)
} 类型转换
类型转换可将接口变量还原为原始类型或用来判断是否实现了某个更具体的接口类型
//类型断言的其它案例
var x interface{}
var b2 float32 1.1
x b2 //空接口可以接收任意类型
// xfloat32 [使用类型断言]
y : x.(float32)
fmt.Printf(y 的类型是 %T 值是%v, y, y) //y 的类型是 float32 值是1.1
待检测机制的类型断言
type Point struct {x inty int
}func main() {var a interface{}var point Point Point{1, 2}a point //okvar b Point//类型断言(带检测的)如果成功返回trueb, ok : a.(Point)if ok {fmt.Println(convert success)fmt.Printf(y 的类型是 %T 值是%v, b, b)} else {fmt.Println(convert fail)}
}