golang入门(一)

本文是Go语言入门教程,介绍了GOPATH的作用,如何编写和运行第一个Hello World程序,以及变量和函数的声明。通过示例展示了Go语言中变量声明、函数声明、指针和数组切片的基本用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GOPATH的作用

一个Go语言的工作空间可能(通常)是表示成这样的文件结构:
其中,该目录的根目录就是$GOPATH

-bin/
    hello.exe                          # 可执行命令
    outyet.exe                         # 可执行命令
-pkg/
    windows_amd64/
        github.com/golang/example/
            stringutil.a           # package对象
-src/
    github.com/golang/example/
        .git/                      # Git仓库数据
        hello/
            hello.go               # 源代码
        outyet/
            main.go                # 源代码
            main_test.go           # 测试源代码
        stringutil/
            reverse.go             # package源代码
            reverse_test.go        # 测试源代码

要编译上面的项目,可以在$GOPATH/src/github.com/golang/example/hello目录下,命令行输入

go build

或者

go install

而如果配置了GOPATH环境变量,则可以在任何地方打开cmd,输入

go build hello

或者

go install hello

这就体现了GOPATH的作用

第一个hello world的go程序

下面开始尝试编写我们的第一个go程序
在我们的工作空间下,或者不使用GOPATH进行编译则可以在任何地方,新建文件夹src,用来存放我们的源代码。
在src中,新建文件夹hello,存放我们的主程序源代码hello.go
在hello.go中输入

package main

import ("fmt")

func main() {
    fmt.Printf("Hello World.\n")
}

这时候,我们的文件结构是这样的

src/
    hello/
        hello.go

接下来假设我们不使用GOPATH,直接在src/hello目录下输入命令。
我们有3种方法运行我们的hello.go

  • 第一种方法
go run hello.go

会直接编译运行打印出Hello World.
go run并不会在任何地方生成可执行文件,因此,使用go run后我们的文件结构没有任何变化

  • 第二种方法
go build

会在src/hello目录下生成可执行文件hello.exe
接着在cmd中输入

hello

就可以打印出Hello World.
这时,我们的文件结构变为

src/
    hello/
        hello.exe
        hello.go
  • 第三种方法
go install

会在bin目录下(如果没有bin目录则会自动创建该目录),创建可执行文件hello.exe
接着,直接在当前目录下(src/hello/)或者在bin/目录下运行命令

hello

就会打印出Hello World.
src/hello/目录下运行hello,如果当前目录下没有hello.exe,就会自动寻找bin/目录。
这时,我们的文件结构变为

bin/
    hello.exe
src/
    hello/
        hello.go

到此,我们完成了第一个hello world程序

第二个hello world的go程序(初识pkg)

接着,我们尝试编写我们的第二个hello world程序。
首先,我们先来看看第一个程序的源代码,每一行具体是什么意思,第一个hello world程序源代码:

package main

import ("fmt")

func main() {
    fmt.Printf("Hello World.\n")
}

第一行的package main表明编译hello.go会生成可执行文件。
第二行的import ("fmt")表示引入包fmt
下面的main()函数是生成的可执行文件运行时的入口函数。

下面我们尝试编写我们自己的一个包,然后在hello.go把它引入并使用。

src/下新建文件夹world,在src/world/里面新建文件world.go,输入

package world

import "fmt"

func World() {
    fmt.Printf("hello, world\n")
}

这里,我们把world.go打包成world,我们把包的名字和目录的名字设为一样,这样可以避免后续导入包时引起歧义。在后续导入包时,import "world"后接的"world"是目录的名字,然后会自动导入子目录$GOPATH/src/world中定义的包。而使用包的时候,则要使用包的名字,比如world.World()中的world指的是包的名字。而我们只要把目录名字和包的名字设为一样,就能统一在import和具体使用包的时候使用同一个名字。

对于一个包,若变量名或函数名以大写开头,则意味着这个变量或函数会自动导出,后续导入包后就可以使用这些导出的变量或函数。而以小写开头的变量和函数则只能在包内互相访问和使用,同一个包的不同文件也可以访问这些小写开头的变量或函数。

接着我们修改src/hello/hello.go

package main

import ("world")

func main() {
    world.World()
}

这时候,我们的文件结构变为

src/
    hello/
        hello.go
    world/
        world.go

要运行我们的程序,可以使用第一个hello world程序的三种方法。文件结构的改变也是相似的。

但是,我们使用上述三种方法运行了我们的程序,但是目录下并未生成pkg文件夹,要使得world.go能被编译出来,以便在后续编译hello.go时不会重复编译world.go,我们可以在src/world/目录下,输入命令

go install

这样,我们就会在我们的工作空间下生成pkg\windows_amd64\world.a
我们的文件结构变为了(假设也进行了hello.go的编译):

bin/
    hello.exe
pkg/
    wondows_amd64/
        world.a
src/
    hello/
        hello.go
    world/
        world.go

变量和函数声明

变量声明

go语言中的基本变量包括

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128

go的变量声明与C/C++有很大区别,假设我们要声明2个int类型和1个bool类型的变量。在C/C++中,我们会这么做:

int x = 1, y = 2;
bool c;

而在go中,则要这样声明:

var x, y int = 1, 2
var c bool

这样声明的好处在于代码的可读性增强了,尤其是在声明函数的时候,至于为什么,可以详细看这篇解释Go的声明语法

注意变量的声明如果没有显式给定初始值,则会使用默认初始值初始化变量,比如上述的变量xy会相应初始为12,而c则会初始化为false

也可以使用自动推导来声明一个变量,但只能在函数内部使用,因此不建议使用。比如

x, y, z := 1, "string", false

go语言的类型转换只能显式转换,比如

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// or in a function block
i := 42
f := float64(i)
u := uint(f)
// if you try
// var i int = 42
// var f float64 = i
// you will get an error

go语言的常量声明需要使用const代替var,而且不可以使用:=,常量声明可以在任意地方,函数内外均可,比如

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")
}

函数声明

一个简单的两个数相加的函数在go里面是这样声明的

func add(x int, y int) int {
    return x + y
}

finc表明这是一个函数,add则表示函数的名字,(x int, y int)表示函数的参数列表,也可以写成(x, y int),后面跟着的int表示返回类型,如果有多个返回值则用括号括起来,比如func add(x, y int) (int, int) {...}

关于返回值还有一个特殊的规则,但是不建议使用,因为会降低代码的可读性。我们可以对返回值也命名,最后“裸返回”,就可以隐式地返回函数中出现的命名的返回值。如下面的程序最后会返回xy

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}

为什么要这样声明

比如,x, y int可以从左往右读成,变量xy都是bool类型。
又比如,f func(func(int,int) int, int) int可以从左往右读成,变量f是一个函数,这个函数参数为一个以2个int作为参数的函数,以及2个int,这个函数返回一个int。尽管读起来很奇怪,但是直观感受起来会比较简单。
而使用这样的声明规则,还可以简单实现函数闭包:

sum := func(a, b int) int { return a+b } (3, 4)

for, if, switch

go语言的forif也与传统的C/C++有很大区别。

for

  • 第一种情况,可以简单把go中的for理解为不带括号的C/C++的for,比如
for i := 0; i < 10; i++ {
    sum += i
}
// or
sum := 1
for ; sum < 10; {
    sum += sum
}
  • 第二种情况,for等同于没有括号的C/C++的while,比如
sum := 1
for sum < 10 {
    sum += sum
}
  • 第三种情况,要写死循环,即对应C/C++中的while(1),可以这样写
for {
    // ...
}

if

与C/C++相比,go中的if的条件除了没有括号外,与for类似,在条件前可以加一个简单的初始化语句,但是要注意,这个初始化语句初始化的变量,作用域只有这个if子句和后续的else语句等,即作用域只是整一个if块。比如

if v := somefunc(); v < lim {
    return v
} else {
    return lim + v
}
// you can not access v here

switch

与C/C++相比,go语言的switch在执行完一个case分支后,会自动跳出整个switch,而不像C/C++会继续往下执行其他的case。此外,go语言的switch中,case可以是函数而非常量。

// it is equal to 
// if i != 0 {
//    f()
// }
switch i {
case 0:
case f():
}

defer

defer的意思是推迟,在go语言中,使用defer关键字可以让defer后的语句延迟执行,具体延迟到该语句所在的语境结束后才执行。还是看个例子比较好懂:

func main() {
    fmt.Printf("counting ")

    for i := 0; i < 10; i++ {
        defer fmt.Printf("%v ", i)
    }

    fmt.Printf("done ")
}

该程序的输出为:

counting done 9 8 7 6 5 4 3 2 1 0

可见,defer后的语句,相当于被推进了一个栈,在main函数结束前,栈内的语句又一条一条被弹出并执行。

指针

普通指针

go的指针和C/C++的类似,根据声明规则可以这样声明:

var p *int

即声明变量p,他是一个指针,指向一个int类型,注意指针的默认值都为nil
要给该指针赋值,则要用到取地址符&

i := 2
p = &i
fmt.Println(*p)
*p = 3 // this would change i to 3

结构体的指针

go的结构体和C/C++的类似,但和C/C++不同的是,C/C++中,指向结构体的指针访问结构体内的成员,需要这样:p->a或者(*p).a,而在go中,则可以直接p.a这样访问结构体成员。

数组

go中,声明一个数组可以这样

var a [10]int

即声明变量a,它是一个大小为10的数组,类型是int。

数组切片

也可以使用:=声明数组,数组还可以切片:

a := [4]int{1, 2, 3, 4}
var b []int = a[1:2] // b = {2, 3}

要特别注意的是,数组切片相当于数组的引用(指针),任何改变切片b的操作,都会改变原来的数组a。或者说,任何数组间的直接赋值,只是对指针的赋值,并没有深拷贝一个新的数组。

数组切片有两个变量,长度len和容量cap,分别表示数组切片本身的长度,和原数组的长度,比如

a := [6]int{2, 3, 5, 7, 11, 13}
b := a[:3] // len(b) = 3, cap(b) = 6

数组切片还可以用make函数来创建,该函数第一个参数是类型,第二个参数是长度,第三个参数是容量,比如

b := make([]int, 0, 5) // len(b)=0, cap(b)=5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值