Go语言的入坑之旅就要从这里开始了。入坑之前,推荐先看看这篇博客,Go语言fmt包Printf方法,在调试时是非常用用的。传送门http://waliblog.com/go/2018/02/28/fmtPrintf.html

Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句

1.命名

  ●一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线

  ●区分大小写

  ●Go语言程序员推荐使用 驼峰式 命名

2.关键字

预定义的名字,主要对应内建的常量、类型和函数

01.内建常量

02.内建类型

03.内建函数

3.声明

Go语言主要有四种类型的声明语句:varconsttypefunc,分别对应变量常量类型函数实体对象的声明

4.常量

常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string或数字。

一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改。

const filename = 'test.txt'
const a,b = 3,4
//或者
const (
   filename = 'test.txt'
   a,b = 3,4
)
//如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的
const (
	a = 1
	b
	c = 2
	d
)

fmt.Println(a,b,c,d)  //1,1,2,2

01.特殊常量(枚举)

Go语言是没有枚举这个类型,不过用const关键字可以定义一组数据来当枚举使用。常量声明可以使用iota常量生成器初始化。

type Weekday int

const (
    sunday  Weekday = iota  //iota将会被置为0,然后在每一个有常量声明的行加一
	monday
	tuesday
	wednesday
	thursday
	firday
	saturday
)

fmt.Println(sunday,monday,tuesday,wednesday,thursday,firday,saturday)   //0 1 2 3 4 5 6

const (
	_ = << (10 * iota)    // _
	Kib  //1024
	Mib  //1048576
	Gib  //1073741824
	Tib  //1099511627776   (exceeds 1 << 32)
	Pib  //1125899906842624
	EiB  // 1152921504606846976
	ZiB  // 1180591620717411303424    (exceeds 1 << 64)
	YiB // 1208925819614629174706176
)

5.变量

变量分为普通变量指针变量

变量声明后必须使用

语法 var 变量名字 类型 = 表达式

其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。

var num int = 1
var a,b int = 2,3

01.省略表达式

类型对应的零值
数值0
布尔false
字符串空字符串
接口或引用类型包括(slice、指针、map、chan和函数)nil
数组或结构体等聚合类型每个元素或字段都是对应该类型的零值
var a int   
fmt.println(a)   //输出:0

var bool Boolean
fmt.println(bool) //输出: false

var str String
fmt.println(str)  //输出: ""

//省略类型,类型由初始化表达式推导

var a = 1
fmt.Printf("%T = %d",a,a)  //int = 1

var str = "string"
fmt.Printf("%T = %s",str,str) //string = string

02.简短变量声明

简短变量声明被广泛用于大部分的局部变量的声明和初始化。var形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方

a,b := 1,2
fmt.Printf("%d,%d",a,b)

简短变量声明左边的变量可能并不是全部都是刚刚声明的。如果有一些已经在相同的词法域声明过了,那么简短变量声明语句对这些已经声明过的变量就只有赋值行为了

in,err := os.Open(infile)  //err是声明
//...
out,err := os.Create(outfile)  //err是赋值,因为之前声明过了

//简短变量声明语句中必须至少要声明一个新的变量,下面的代码将不能编译通过:
f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // compile error: no new variables

6.new函数

另一个创建变量的方法是调用用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T

p := new(int)
fmt.Println(*p)

*p = 2
fmt.Println(*p)

7.变量声明周期

  ●变量的生命周期指的是在程序运行期间变量有效存在的时间间隔

  ●对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的

  ●局部变量的声明周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收

  ●函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建,调用完后被销毁

8.作用域

  ●一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代码中可以有效使用这个名字的范围

  ●不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念

  ●句法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧包裹的内容一样。句法块内部声明的名字是无法被外部块访问的。这个块决定了内部声明的名字的作用域范围

  ●词法块声明在代码中并未显式地使用花括号包裹起来

  ●全局词法块对全局的源代码来说,存在一个整体的词法块,称为全局词法块。对于每个包;每个for、if和switch语句,也都对应词法块;每个switch或select的分支也有独立的语法块;当然也包括显式书写的词法块(花括弧包含的语句)

  ●声明语句对应的词法域决定了作用域范围的大小,对于内置的类型函数和常量,比如int、len和true等是在全局作用域的,因此可以在整个程序中直接使用。任何在在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。还有许多声明语句,比如tempconv.CToF函数中的变量c,则是局部作用域的,它只能在函数内部(甚至只能是局部的某些部分)访问

  ●控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域

  ●一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。

  ●如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问:

func f() {}
var g = "g"
func main(){
  f := "f"
  fmt.Println(f)    //"f";变量f屏蔽了func f
  fmt.Println(g)    //"g";包级别
  fmt.Println(h)    //未定义,报错
}

示例一

func main() {
    x := "hello!"    //函数级别作用域  
    for i := 0; i < len(x); i++ {   //for声明了隐式的词法域部分的作用域,相当于for(i := 0; i < len(x); i++) 只不过GO中的for是不需要加花括号的
       x := x[i]     //在for中声明了x,和for外面的x不同,只不过名称相同
       if x != '!' {  // 相当于if( x != '!') x是for中x
          x := x + 'A' - 'a'   //在if中声明x,与for外x,for中声明x不同
          fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
       }
    }
}

示例二

if x := 3; x > 5{ //if(x :=3; x>5)
   fmt.Println(x)
}else if y := 2; x == y{
   fmt.Printf("y=%d,x=%d",y,x)
}else{
   fmt.Println(x,y)  //3,2
}

fmt.Println(x,y)  //报错,x,y未定义,不能访问

第一个if,显示声明了x作用域属于第一个if的。else if中fmt.Printf("y=%d,x=%d",y,x)是可以访问到x,只不过这里条件不成立。else 中可以访问x,y,else后面的语句也是属于if作用域的。最后一个fmt输出不能输出,原因就是x,y声明是在if内,局部作用域,外部是不能访问的。

示例三

if f, err := os.Open(fname); err != nil { // compile error: unused: f
   return err
}

f.ReadByte() // compile error: undefined f
f.Close()    // compile error: undefined f

//同样f声明的变量的作用域是局部的,只能作用域if,可以这样做修改

f,err := os.Open(fname)
if err != nil  return err

f.ReadByte()
f.Close()

9.赋值

var x int
var flag bool
var str string

x = 1
flag = true
str = "hello world"

//二元算术运算符和赋值语句的复合操作有一个简洁形式
x += 1

//数值变量也可以支持++递增和--递减语句(译注:自增和自减是语句,而不是表达式,因此x = i++之类的表达式是错误的):
var w int
v := 1

w = v++ //(错误)
v++
w = v  //(正确)

//元组赋值
x,y := 1,2
x, y = y, x

10.条件语句

条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 的情况在执行另外的语句。

01.if语句

a := 10
if a >5{
	fmt.Println("a大于5")
}else{
    fmt.Println("a小于5")
}
//输出a大于5,也可这样写 
if a := 10; a > 5 {
 fmt.Println("a大于5")
}else{
 fmt.Println("a小于5")
}

if语句隐式作用域,条件里可以赋值,但作用域就在if语句中包括else,if之外是不能访问变量。

02.switch语句

switch 语句用于基于不同条件执行不同动作

func eval(a,b int, op string) int {
    var result int
    switch op {
    case "+":
        result = a + b
    case "-":
        result = a - b
    case "*":
        result = a * b
    case "/":
        result = a / b
    default:
        panic("没有该操作符" + op)
    }
    return result
}

s := eval(4,5,"+")
fmt.Println(s)  //9

  ●switch会自动break,除非使用fallthrough

  ●switch后面可以没有表达式

func grad(score int) string  {
    g := ""
    switch {
    case score < 60:
        g = "F"
    case score < 70:
        g = "E"
    case score < 80:
        g = "D"
    case score < 90:
        g = "C"
    case score < 100:
        g = "B"
    case score == 100:
        g = "A"
    default:
        panic(fmt.Sprintf("出错:%d",score))
    }
    return  g
}

fmt.Println(grad(100))  //A

03.select语句

select是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。select随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

select语法

select {
    case communication clause  :
       statement(s)
    case communication clause  :
       statement(s) 
    //你可以定义任意数量的 case
    default : //可选
       statement(s)
}

  ●每个case都必须是一个通信

  ●所有channel表达式都会被求值

  ●所有被发送的表达式都会被求值

  ●如果任意某个通信可以进行,它就执行;其他被忽略。

  ●如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。否则

    1.如果有default子句,则执行该语句。

    2.如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

11.for语句

  ●for的条件里不需要括号

  ●for的条件里可以省略初始条件,结束条件,递增表达式

sum := 0
for i := 1; i < 100; i++{
  sum += i
}

fmt.Println(sum)  //5050

for写十进制转二进制

func convertToBin(n int) string{
  result := ""
  for ; n > 0; n /= 2{
    lsb := n % 2
	result = strconv.Itoa(lsb) + result
  }
  return result
}

01.无限循环

for {
 fmt.Println("1")
}

02.range变量for

//变量字符串
str := "hello world!"
for _,ch := range str{
  fmt.Printf("%c",ch)
}
//变量数组
slice := []int{1,2,3,4,5,6,7,8}
for k,v := range slice{
  fmt.Printf("slice[%d] = %d\n",k,v)
}

12.指针变量

Go 语言的取地址符是&,放到一个变量前使用就会返回相应变量的内存地址。

var a int = 5
fmt.Println(&a)  //0xc042058080是a变量在内存中的一块地址,每台电脑的值可能会不一样

指针变量:一个指针变量可以指向任何一个值的内存地址它指向那个值的内存地址

访问指针变量的值:在指针类型前面加上 * 号(前缀)来获取指针所指向的内容

var a int = 5
var ptr *int
ptr = &a
*ptr = 10

fmt.Println(a)  //10
fmt.Println(*ptr)  //10