学习笔记Golang语法学习笔记

Posted 棉花糖灬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习笔记Golang语法学习笔记相关的知识,希望对你有一定的参考价值。

一、入门

go是编译型的语言,代码风格类似于C语言,其最大特点是支持并发编程,go文件后缀名为.go

在命令行通过go run helloworld.go来运行,或先通过go build helloworld.go编译,然后./helloworld执行,在windows下编译生成的是.exe文件。go mod tidy命令拉取缺少的模块,移除不用的模块

go语言代码的第一行使用package声明包,名为main的包比较特殊,它定义的是一个独立的可执行程序,而不是库。在 main 里的 main 函数也很特殊,它是整个程序执行时的入口。使用import导入使用的包,import必须跟在package声明之后。缺少或多了不需要的包,编译都不会通过。

go语言不需要用分号结尾,除非多个语句或声明出现在同一行。实际上在特定符号后面的换行符会被转换为分号,所以在什么地方换行会影响对go代码的解析,例如必须和关键字func在同一行,不能独立成行。

字符串必须是双引号,单行注释是//,多行注释是/**/

package main

import (
	"fmt"
	"os"
)

func main() 
	var s, sep string
	sep = " "
	// os.Args是通过命令行运行代码时传入的各个参数,第0个参数默认是本代码的路径
	for i := 0; i < len(os.Args); i++ 
		s = s + sep + os.Args[i]
	
	fmt.Println(s)

i++i--是语句而非表达式,故j = i++是非法的,且只支持后缀,++i也是非法的。

go语言中只有for循环,for循环有以下几种形式

for initialization; condition; post
	// 语句


// 相当于C语言中的while循环
for condition
    // 语句


// 死循环
for
    // 语句


// rang产生一对值:索引和索引处的元素值
for _,arg := range os.Args[1:]
    // 语句

go语言中不允许存在无用的变量,即声明了但是没用到的变量,所以索引值用不到时不能用普通的变量名代替,而是需要使用空标识符_

定义变量的几种方式,建议使用第一、二种

s := ""
var s string
var s = ""
var a, b string = "aaa", "bbb"

string包中string.Join(os.Args[1:], " ")可以使用空格将os.Args中的元素连接起来

格式化字符:

符号含义
%d十进制整数
%x, %o, %b十六进制,八进制,二进制整数
%f, %g, %e浮点数
%t布尔值
%c字符
%s字符串
%q带双引号的字符串或带单引号的字符
%v变量的自然形式
%T变量的类型
%%字面上的百分号标志

例:找出重复行

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() 
	// make可以创建新的map,map[string]int表示一个键为string类型,值为int类型的map
	counts := make(map[string]int)
	// bufio中的Scanner可以读入输入,以换行分割,以Ctrl+D结束
	input := bufio.NewScanner(os.Stdin)
	for input.Scan() 
		counts[input.Text()]++
	
	for i, n := range counts 
		if n > 1 
			// 格式化输出
			fmt.Printf("%d\\t%s\\n", n, i)
		
	

例:从stdin或指定文件读取并找出重复行

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() 
	counts := make(map[string]int)
	files := os.Args[1:]
	if len(files) == 0 
		countLines(os.Stdin, counts)
	 else 
		for _, arg := range files 
			f, err := os.Open(arg)
			// err为nil时表示成功打开
			if err != nil 
				fmt.Fprintf(os.Stderr, "dup2: %v\\n", err)
				continue
			
			countLines(f, counts)
			f.Close()
		
	
	for i, n := range counts 
		if n > 1 
			fmt.Printf("%d\\t%s\\n", n, i)
		
	


// 函数和其他包级别的实体可以任意次序声明
func countLines(f *os.File, counts map[string]int) 
	input:=bufio.NewScanner(f)
	for input.Scan() 
		// 更改子函数中的counts值会影响到main函数中counts的值
		counts[input.Text()]++
	

输出从url获取的内容

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() 
	for _, url := range os.Args[1:] 
        // 产生一个http请求
		resp, err := http.Get(url)
		if err != nil 
			fmt.Fprintf(os.Stderr, "fetch: %v\\n", err)
			os.Exit(1)
		
        // 读取响应的响应体
		b, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil 
			fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\\n", url, err)
			os.Exit(1)
		
		fmt.Printf("%s\\n", b)
	

并发获取多个url

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

func main() 
	start := time.Now()
	// 创建一个字符串通道,通道是允许某一例程向另一例程传递指定类型的值的通信机制
	ch := make(chan string)
	for _, url := range os.Args[1:] 
		// main函数在一个goroutine中执行,go语句创建额外的goroutine,即一个并发执行的函数
		go fetch(url, ch)
	
	for range os.Args[1:] 
		// <-ch是接收发送的值
		fmt.Println(<-ch)
	
	fmt.Printf("%.2fs elapsed\\n", time.Since(start).Seconds())


func fetch(url string, ch chan<- string) 
	start := time.Now()
	resp, err := http.Get(url)
	if err != nil 
		// 在通道ch上发送一个值
		ch <- fmt.Sprint(err)
		return
	

	// io.Copy获取响应内容,并通过ioutil.Discard输出流进行丢弃
	nbytes, err := io.Copy(ioutil.Discard, resp.Body)
	resp.Body.Close()
	if err != nil 
		ch <- fmt.Sprintf("while reading %s: %v\\n", url, err)
		return
	
	secs := time.Since(start).Seconds()
	ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)

迷你web服务器

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main()  
	// 回声请求调用处理程序
	http.HandleFunc("/",handler)
	log.Fatal(http.ListenAndServe("localhost:8000",nil))


func handler(w http.ResponseWriter,r *http.Request)  
	// 回显请求URL r的路径部分
	fmt.Fprintf(w,"URL.Path = %q\\n",r.URL.Path)

迷你回声和计数服务器

package main

import (
	"fmt"
	"log"
	"net/http"
	"sync"
)

var mu sync.Mutex
var count int

func main() 
	http.HandleFunc("/", handler)
	http.HandleFunc("/count", counter)
	log.Fatal(http.ListenAndServe("localhost:8000", nil))


func handler(w http.ResponseWriter, r *http.Request) 
	// 加锁
	mu.Lock()
	count++
	mu.Unlock()
	fmt.Fprintf(w, "URL.Path = %q\\n", r.URL.Path)


func counter(w http.ResponseWriter, r *http.Request) 
	mu.Lock()
	fmt.Fprintf(w, "Count %d\\n", count)
	mu.Unlock()

使用&操作符获取一个变量的地址,使用*操作符获取指针引用的变量的值。

二、程序结构

1. 名称

go中有25个关键字:

breakdefalutfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

内建常量:

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语言中区分大小写,如果实体(方法或变量)名称以大写字母开头,则它对包外是可见和可访问的,如fmt中的Printf。包名总是由小写字母组成。变量名通常采用驼峰命名

2. 声明

声明的作用是给一个程序实体命名,并设定其部分或全部属性

实体有4个主要的声明:变量(var)、常量(const)、类型(type)和函数(func)

3. 变量

变量声明的方式为:var 变量名 数据类型 = 表达式,类型和表达式可以省略一个,但不可全省略。如果省略类型,则类型由表达式决定;如果表达式省略,则其初始值对应类型的零值,接口和引用类型的零值是nil。

var a int
var b int = 100
var c = 100

包级别的初始化在main开始之前进行

声明多个变量

var a, b int = 100, 200
var c, d = 100, "abcd"
var (
	e int = 100
    f bool = true
)
g, h := 100, "abcd"

(1) 短变量声明

格式为:变量名 := 表达式

在局部变量中主要使用短变量声明

短变量声明至少声明一个新变量,否则会编译错误,如

f, err := os.Open(infile)
// 编译错误,因没有新的变量
f, err := os.Create(outfile)

:= 是声明并赋值,并且系统自动推断类型,不需要var关键字,而=必须先使用var声明

(2) 指针

指向整数变量的指针的数据类型是*int

x := 1
p := &x
// *p是指针指向的变量值
*p = 2
*p++ // *p指向的变量值加1
package main

import "fmt"

func swap(a, b *int) 
	*a, *b = *b, *a


func main() 
	a := 10
	b := 20
	swap(&a, &b)
	fmt.Println("a = ", a, " b = ", b)

	// 一级指针
	var p *int
	p = &a

	// 二级指针
	var pp **int
	pp = &p
	fmt.Println(&p)
	fmt.Println(pp)

(3) new函数和make函数

表达式new(T)创建一的T类型的匿名变量,初始化为T类型的零值,并返回其地址。

p := new(int) // *int(指针)类型的p
fmt.Println(*p)
*p = 2

表达式make(T)创建一个T类型的匿名变量,初始化为T类型的零值,并返回该变量。与new的不同之处在于,new返回的是变量地址,而make返回的是变量本身。
make的形式必须是make(Type, len, cap),且Type的值只能是slicemapchannel三者之一。

make([]int, 2)

make([]int, 2, 4)

make(map[string]string)

make(chan, int)

make(chan, int, 1)

(4) 赋值

x = 1
*p = true
person.name = "bob" // 结构体成员
connt[x] = count[x] * scale // connt[x]为数组或slice或map的元素
// 多重赋值
x, y = y, x
i, j, k = 1, 2, 3
medals := []string"gold", "silver", "bronze"

(5) 类型声明

type声明定义一个新的名称类型,它和某个已有类型使用同样的底层类型。其格式为:type 类型名 底层类型

package tempconv

import "fmt"

type Celsius float64
type Fahrenheit float64

const (
	AbsoluteZeroC Celsius = -273.15
	FreeezingC    Celsius = 0
	BoilingC      Celsius = 100
)

func CToF(c Celsius) Fahrenheit 
    // 强制类型转换
	return Fahrenheit(c*9/5 + 32)


func FToC(f Fahrenheit) Celsius 
	return Celsius((f - 32) * 5 / 9)

其中Celsius和Fahrenheit虽然底层类型都是float,但是它们不能进行比较和合并,Celsius()和Fahrenheit()表示强制类型转换。如果两个类型具有相同的底层类型,或都是指向相同底层类型的指针类型,则二者是可以相互转换的。

(6) 包和文件

通过标出的标识符是否以大写字母开头来管理标识符是否对外可见。

包的初始化按照在程序中导入的顺序来进行,依赖顺序优先,每次初始化一个包。

三、基本数据

1. 整数

rune类型是int32类型的同义词,byte类型是uint8类型的同义词

取模余数正负号总是与被除数一致

运算符优先级从高到低如下:

*  /  %  <<  >>  &  &^
+  -  |  ^
==  !=  <  <=  >  >=
&&
||

其中&^表示位清空(AND NOT),在z = x &^ y中,若y的某位是1,则z的对应位为0,反之为x的对应位。

2. 浮点数

float32和float64

3. 复数

复数有complex64和complex128两种,可以使用real()和imag()分别提取复数的实部和虚部

x := 1 + 2i

4. 布尔值

一个布尔类型的值只有两种:true和false

5. 字符串

字符串是不可变的字节序列,但可以做字符串拼接。

s := "hello,world"
t := "!!!"
fmt.Println(len(s))
fmt.Println(s[0],s[7])
s += t
s[0] = '0' // 报错,不可更改

bytes、strings、strconv、unicode四个标准包对字符串操作特别重要

  • strings包提供了搜索、替换、比较、修整、切分和连接的函数

  • bytes包里也类似,用于操作字节slice

  • strconv提供布尔值、整数、浮点数与字符串类型之间的相互转换函数

  • unicode包有判别文字符号值特性的函数,如IsDigit、IsLetter、IsUpper、IsLower等

6. 常量

const p = 3.14
const(
    e = 2.71828
    pi = 3.1415926
)

// 其中b和d会复用前一项的表达式及其类型
const(
  	a = 1
    b
    c = 2
    d
)

// 常量生成器itoa,从0开始取值,逐项加1
type Weekday int
const(
  	Sunday Weeday = itoa
    Monday
    Tuestday
    Wednesday
    Thursday
    Friday
    Saturday
)

四、复合数据结构

1. 数组

长度固定,且拥有多个相同数据类型的元素。

// 默认初始化为零值
var a [3]int
var b [3]int = [3]int1,2,3
length := len(a) // 内置函数len()可以获取数组大小
// 遍历数组中的元素
for _, v := range b
    fmt.Printf("%d\\n",v)


// 用...表示数组长度由初始化元素个数决定,此时变量类型为数组,而非切片
c := [...]int1,2,3
fmt.Printf("%T\\n",c)

// 如果一个数组的元素是可比较的,则数组是可比较的,如果数组长度不同不可比较
fmt.Println(b == c)
package main

import "fmt"

type Currency int

const (
	ZERO Currency = iota
	ONE
	TWO
	THREE
	FOUR
)

func main() 
	// 索引:值
	num := [...]intZERO: 0, ONE: 1, TWO: 2, THREE: 3, FOUR: 4
	fmt.Println(TWO, num[TWO])

	// 定义了100个元素的数组,其中下标为99的元素为-1,其他默认为0
	a := [...]int99: -1
	fmt.Println(a)

在调用函数时,传入的参数会创建一个副本,函数内对数组的任何修改都仅影响副本,此时可以使用引用传递(传递数组的地址)来解决问题

package main

import "fmt"

// 传入的数组必须是长度为4的
func printArray(myArray [4]int) 
	for index, value := range myArray 
		fmt.Println("index = ", index, ", value = ", value)
	
	// 只是值拷贝,不会影响原数组的值
	myArray[0] = 111


func main() 
	// 固定长度的数组
	var myArray1 [10]int
	myArray2 := [10]int1, 2, 3, 4<

以上是关于学习笔记Golang语法学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

go学习Golang底层学习笔记

Go语言 语法详解笔记(上)

学习笔记Golang之Gorm学习笔记

Java8学习笔记 - 方法引用:Lambda的语法糖

Java8学习笔记 - 方法引用:Lambda的语法糖

Golang学习笔记--unsafe.Pointer和uintptr