go语言入门

go语言入门

go简介

go语言,golang,是谷歌公司开发的,是编译型语言。

编译型语言,需要将go代码编译成可执行文件,然后就可以在相应的系统上跑了,而开发环境中,我们需要下载go sdk,这个是go管理代码资源的工具,我们可以通过go build命令来编译go代码,go run来编译+运行go代码(编译的代码即用即删)。

hello world

package main // 声明这个go文件属于哪个包,main有特殊含义
// 一个项目要运行必须要有main包,运行的入口是main包下的main函数

// 包导入,不使用,会报错,编译不过,导入后必须要使用
import "fmt"

// func 定义一个函数 ,main 是主函数,没有参数,没有返回值,整个项目入口
func main() {
	// 在控制台输出hello world
	fmt.Println("hello world") 
}

如果实在goland里面运行,那么可以用比较快捷的方式,直接右击文件编译运行。不过一般来说,go的结构会基于包,一个项目里只有一个main包,程序的入口也就是这个main包下的main函数。

我们学习代码可以采取单个go文件编译执行。

在上述的hello world程序中,go语言会要求程序导入的包必须使用,否则编译报错,这样实际上可以节约我们不必要的内存和效率开支,不引用的包就应该不导入。goland编辑器编辑时,针对这一点,做了一些处理,我引用到包时,它会自动帮我在顶部导入,在我代码中删除对这个包的引用时,它也会检测到并把导入那一句代码给删掉,提升了开发的效率。

变量命名规范

规则

可以由字母(unicode)、下划线、数字组成,不能由数字开头,区分大小写,关键字和保留字不推荐作为变量名

25个关键字:

break      default       func     interface   select
case       defer         go       map         struct
chan       else          goto     package     switch
const      fallthrough   if       range       type
continue   for           import   return      var

37个保留字:

内置常量: true false iota nil
内置类型:  int int8 int16 int32 int64
          uint uint8 uint16 uint32 uint64 uintptr
          float32 float64 complex128 complex64
          bool byte rune string error
内置函数: make len cap new append copy close delete
          complex real imag
          panic recover

规范

go的命名习惯是:变量、函数名用小驼峰,文件名用下划线格式

对比python:全下划线

对比java:全驼峰

变量声明

注意:go中的变量被声明后,必须被使用,否则会编译报错,所以goland编辑器有时会飘红提示,我们需要后文使用变量后飘红提示才会消失。

三种基本的声明语法:

// 完全定义 var关键字 变量名 变量类型 = 变量值
var name string = "leethon"  // go的字符串必须是双引号

// 类型推导 var关键字 变量名 = 变量值  (根据变量名确认这个变量的类型)
var name = "leethon"  // 变量声明后,其数据类型不会再变了,因为go是静态语言

// 简短声明 变量名 := 变量值 (省略了var关键字)
name := "leethon"

一次性声明多个变量:

// 完整定义多个变量
var name, age, hobby string = "leethon", "19", "篮球"  // 完整定义则类型指定必须一致
var (
	name string = "leethon"
    age int = 19	// 这种写法可以单独声明变量类型
    hobby = "篮球"  // 也可以不指定类型,让其自己推导
)

// 类型推导
var name, age, hobby = "leethon", 19, "basketball"  // 类型推导的多个变量类型可不一致

// 简略声明
name, age, hobby := "leethon", 19, "basketball" 

变量也可以先声明后赋值,如果采取这种方式,则必须使用完整定义的语法,且实际上被声明的变量会有其类型的默认值

var name string  // 默认为空字符串
var age int // 默认值为0

数据类型

查看变量的类型,%T是占位符,最终会将变量的类型放入字符串

fmt.Printf("a的值是:%v,类型是:%T", a, a)

数字类型

// 整型
int8   只占8bit,可以表示 -128~127 的数字范围
int16  占两个字节  可以表示 -2^15~2^15-1的数字
int32 int64 依次类推
int   32位机器是 int32 ,64位机器是 int64
// 无符号整型(只表示自然数)
uint8  只占8bit,可以表示 0~255 的数字范围
uint16  占两个字节  可以表示 0~2^16-1的数字
uint32 uint64 依次类推
uint   32位机器是 uint32 ,64位机器是 uint64

// 浮点型:表示小数
float32 float64

// 复数类型:实部和虚部
complex64 complex128

// rune byte
byte 是 uint8 的别名
rune 是 int32 的别名

字符类型

// string
"lee"   "hello world"  这些都是字符串,不能用单引号包裹
`反引号包裹
可以写多行的字符串`

// 单引号
'c'  '7'  '好' 使用单引号包裹的不是字符串类型,它对应字符编码的整型数字
var a, b, c = 'a', '2', '三'
fmt.Printf("%T %v,%T %v,%T %v", a, a, b, b, c, c)
// int32 97,int32 50,int32 19977

布尔类型

true 
false

自由数据类型

any:声明变量或形式参数时,可以用any来表示:实际的变量类型由传入的数据值来决定

常量

go中,常量用关键字const进行声明,常量一经声明,不能再更改它的值

// 完整定义
const 常量名 数据类型 = 数据值
// 类型推导
const 常量名 = 数据值
// 定义多个
const (
	male = 1
    female = 2
    unknown = 0
)
// 依赖规则,如果下方常量声明时不赋值,那么和上方常量保持一致
const (
	a = 10
    b   		// 10
)

iota

iota是一个特殊常量,是一个可以被编译器修改的常量,我们在代码层面不会修改,而在编译过程中会根据一定的规则变化。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota经常用作枚举值:

const (
	unknown = iota   // 0
    male					// 1
    female					// 2
)

iota结合上文提到的依赖规则,在声明多个常量时,可能会出现以下情况:

const (
    a = iota  	 //0
    b          		//1
    c          		//2
    d = "ha"   //独立值,iota += 1
    e          		//"ha"   iota += 1
    f = 100    //iota +=1
    g          		//100  iota +=1
    h = iota   //7,恢复计数
    i          		//8
)
// iota递增,且中途有其他常量不使用其值也不影响,重新出现iota时再重新引用

我们还可以结合位运算做一些有趣的枚举:

const (
    KB = 1<<(iota * 10)  // 1左移10位
    MB								// 1左移20位
    GB
)

函数

主函数main

main函数是程序的入口,也可以说是工程的主体,每一个go程序必须有一个main函数才能运行,main函数没有参数,也没有返回值。

函数定义与调用

// 定义无参函数
func test() {
	fmt.Println("无参函数")
}

// 定义有参函数,括号里面放入形参,必须指定参数类型
func test1(a int, b string) {  
	fmt.Println(a)
	fmt.Println(b)
}

// 如果参数类型一致的变量,则可以简写
func test2(a, b int, c string){
    fmt.Println(a, b, c)
}

// 定义有返回值的函数,括号后面需要指定返回值的数据类型
func test3(a, b int) int {
	return a + b
}

// 一个函数可以有多个返回值,多个返回值都要指定数据类型,格式如下
func test4(a, b int) (int, int) {
	return b, a
}

// 函数可以在很多地方调用,主函数和函数中都可以对其他或自己进行调用
func main(){
    test()  // 调用无参函数
    test1(11, "leethon") // 调用有参函数,数据类型和位置必须相对应
	var a int = test3(1, 2) // 调用有参函数,且接收返回值
    fmt.Println(a)
    
    b, c := test4(3, 4)  // 必须有相应个数的变量去接收返回值
    fmt.Println(b, c)
    b, d :=test4(b, c)  // :=前面只要有未声明的变量即可,也可含声明过的变量
    fmt.Println(b, c, d)
    b, c = test4(b, c)  // =就是单纯的赋值符号,b和c已在前文定义
    fmt.Println(b, c)
    // 总结,可以直接定义变量去接收返回值,也可以用声明过的变量去接收,重点是变量与返回值的个数对应上
    
    _, c = test4(1, 2)  // _表示忽略该位置的返回值,在go中_不能当做变量使用
}

函数总结:

  1. 定义函数使用func关键字,基本格式func(){}
  2. 函数形参和返回值也需要严格定义数据类型,形式参数变量仅在函数内部有效
  3. go语言有很多类型,函数也是一种,且它的形参数据类型、返回值数据类型都函数类型的一部分(形参数据类型不同的函数是两种类型)
  4. 函数的返回值必须有对应个变量接收,可以用_来接收后面用不到的值,来防止编译报错。_在go中叫作空白符,它的用法在go中主要就是处理调用后接收变量不用的情况。

匿名函数

// 定义匿名函数
func() {}
// 定义传参和有返回值的匿名函数
func(a int) string{
    return "ok"
}
// 匿名函数定义完直接调用(虽然没用,但是支持这么做)
func(){
    fmt.Println("立即执行!")
}()

函数是一等公民

匿名函数没有名字,但是它可以被赋值给变量,也可以传参,实际上函数在go语言中属于一等公民。

所谓一等公民,是指支持所有操作的实体, 这些操作通常包括作为参数传递,从函数返回,修改并分配给变量等。

///// 来看看一等公民意味着什么
// 1.直接将匿名变有(分配给变量)
f := func(){
    fmt.Println("我有名了!")
}

// 验证f变量的数据类型
fmt.Printf("%T", f)  // func()

// 2.当参数传入
// 定义一个需要函数当形参的函数:
func test(f func()){
    f()  // 调用传入的函数实参
}

// 3.函数的形参和返回值类型都是函数类型的一部分
也就是说函数类型不只有 func()
还有 func(int) int /  func(int string) / func(string)(int int) 等等,所以函数类型也是很复杂的

// 4.闭包函数,装饰器也是可以实现的
func outer(f func()) func(){  // 这里实际上就表名了能装饰的函数类型只有无参函数,且结果为无参函数
    inner := func(){
        f()  // 局部作用域引用外部作用域的变量,就叫闭包
        fmt.Println("加点额外的东西")
    }
    return inner // 装饰器
}

变量空间作用域

作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。

Go 语言中变量可以在三个地方声明:

  • 局部变量:函数内定义的变量称为局部变量
  • 全局变量:函数外定义的变量称为全局变量
  • 形式参数:函数定义中的变量称为形式参数

局部变量:

package main

func main(){
    var a int = 10
    b := a + 20
    // a 和 b 都是局部变量
}

全局变量:

package main

import "fmt"

var a int = 10  // a是全局变量,任何函数中都可以对其进行引用

func main(){
    fmt.Println("main", a) // main 10
    test()
    fmt.Println("main", a) // main 10
}

func test(){
    a := 20  // 局部如果重新声明a,则在函数中,这个a使用的是局部的,且不会影响全局
    fmt.Println("test", a) // test 20
}

形式参数:

package main

import "fmt"

func main() {
   /* main 函数中声明局部变量 */
   var a int = 10
   var b int = 20
   var c int

   c = sum(a, b);
   fmt.Printf("main()函数中 c = %d\n",  c);
}

/* 函数定义-两数相加 */
func sum(a, b int) int {
   fmt.Printf("sum() 函数中 a = %d\n",  a);
   fmt.Printf("sum() 函数中 b = %d\n",  b);
	// var a = 10  这种方式是错的,形式参数不能再被重复声明
   return a + b;
}

注意:

go的全局是以包为单位的,也就是说在同一个文件夹下的多个go文件,顶级代码的变量和函数的名字都是不能重复声明的;但是对于测试代码而言,也许会使用单个go文件直接编译,就不用考虑这个问题;这也是为什么一个包下只能有一个main函数,main也作为主函数这一特殊含义存在,如果将包编译执行,则main函数就是入口。

类型重命名

type MyFunc func(int, int) int   // 创建一个类型,其实际类型为两参一返的函数
type MyType = int          // 给类型起了个别名
/*两者的区别在于
第一种是创造,它和原本的类型不是一种类型
第二种是命名,它和原本的类型视为同一类型
*/
// 举个栗子
func test(a int) {}
var a Mytype = 10
test(a)  // 上文中Mytype与int是同种类型,所以test这里是可以传入的
// 但如果使用type MyType int 那这里就判定传参的数据类型不一样,就报错
posted @ 2023-04-25 17:23  leethon  阅读(22)  评论(0编辑  收藏  举报