基础学习

wxvirus2022年6月19日
大约 9 分钟

输出

在终端将想要的展示的数据显示出来的一个过程。

  • 内置函数
    • print
    • println
  • fmt 包(推荐使用)
    • fmt.Print
    • fmt.Println

扩展:进程里有stdout stdin stderr

案例:

package main

import "fmt"

func main() {
  fmt.Println("hello world")
}

fmt 包扩展:格式化输出

fmt.Printf("无解的%s", "游戏")

用于替换一个字符串

%d 用于替换一个整数
%f 用于替换一个带小数的,也就是浮点数
%s 占位符 "字符串"
百分比:用于百分比则需要在写一个%

保留小数点后几位

fmt.Printf("无解的%.2f", 2221.2121212)

想保留几位写数字几。

百分比案例:

fmt.Printf("包你100%%满意")

// 输出: 包你100%满意

变量

使用var关键字来定义变量

声明和赋值写在同一行

var sd string = "沙雕"
fmt.Println(sd)

var age int = 18
fmt.Println(age)

var flag bool = true
fmt.Println(flag)

先声明一个变量,后赋值

var sd string // 字符串类型的变量
sd = "沙雕" // 后进行赋值
fmt.Println(sd)
// 和上面本质上是相同的

注意,变量声明和定义了,如果不进行使用,则会报错。这是go的一个特点。

变量名要求

  • 变量名必须只包含:字母、数字、下划线

  • 数字不能开头

  • 不能使用 go 语言内置的关键字

    var var string = "无解" // var 是关键字就报错
    

    关键字如下

    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
    
  • 建议

    • 变量名见名知意:name/age/num 都知道这是啥意思
    • 驼峰式命名:myBossName/startDate,第一个单词的字母是小写的,后面的单词的首字母是大写的

变量的简写

原来的写法

  • 声明 + 赋值

    var name string = "wujie"
    
    // 可以简写为
    var name = "wujie" // go可以内部根据你的赋的值推断它为字符串类型
    
    // 或者
    name := "wujie" // 推荐使用此方式简写
    
  • 先声明再赋值

    var name string
    var message string
    var data string
    name = "wujie"
    
    // 简写
    // 创建多个变量的时候
    var name, message, data string // 将声明放在一行
    name = "wujie"
    message = "hello"
    data = "中奖了"
    

因式分解

例如:声明 5 个变量,分别有字符串、整型,有的是赋值的,有的是不赋值的

var (
	name = "wujie"
  age = 18
  gender string // 只声明,不赋值,有一个默认值:""
  length int 		// 只声明,不赋值,有一个默认值:0
  sb bool 		  // 只声明,不赋值,哟一个默认值:false
  hobby = "大保健"
  salary = 100000
)

作用域

如果我们定义了大括号,我们在里面定义了变量

  • 不能被它的上级使用
  • 可以在同级使用
if true {
		age3 := 12
		fmt.Println(age3)
	}

	fmt.Println(age3) // 报错 undefined: age3
  • 可以在子级别使用
  • 父级和子级有一个同名称的变量,大括号内的变量如果定义了,不和上级的变量进行冲突,互相独立
  • 优先会从自己的级别的开始找,慢慢往上找,最终都找不到就会报错

全局变量和局部变量

  • 全局变量:在一个go文件不在函数里定义的变量,是项目中寻找变量的最后一环
  • 局部变量:在一个函数里,大括号里的都叫局部变量,可以使用任意方式简化

全局变量里,不可以使用简写方式:=,另外两种方式都可以,也可以基于因式分解方式。

赋值及内存相关

name := "wujie" // name的变量指向了内存里存储wujie的位置

nickname := name // 会再重新拷贝一份数据,让nickname指向拷贝后的地址

这一点与 Python 不同

输出下内存地址来对比一下

package main

import "fmt"

func main() {
	name := "wujie"
	nickname := name

	fmt.Println(name, &name)
	fmt.Println(nickname, &nickname)
}

// 输出结果
wujie 0x14000104220
wujie 0x14000104230

两者的内存地址是不一样的


name := "wujie"
nickname := name
name = "666"

name 原先指向的地址不变,但是其地址对应的值会被覆盖

package main

import "fmt"

func main() {
	name := "wujie"
	nickname := name

	fmt.Println(name, &name)
	fmt.Println(nickname, &nickname)

	name = "666"

	fmt.Println(name, &name)
	fmt.Println(nickname, &nickname)
}

// 输出结果
wujie 0x14000010240
wujie 0x14000010250
666 0x14000010240		// 内存地址不会改,值会进行覆盖
wujie 0x14000010250

注意事项:

使用 int、string、bool 这三种数据类型时,如果遇到变量的赋值则会拷贝一份。【值类型】

常量

不可修改的值

关键字const

const age int = 98 // 定义常量,不能被修改

// 或者
const age = 98 // 没有冒号的简写方式

因式分解

const (
	age = 18
  v1 = 123
  v2 = "dqwdwq"
  v3 int // 错误,要求在定义常量的时候必须把值赋上
)

常量基本都放在全局。

iota

可有可无,可以当做在声明常量时的一个计数器

const (
	v1 = 1
	v2 = 3
	v3 = 4
	v4 = 5
)

简写方式

const (
	v1 = iota
	v2
	v3
	v4
)

默认会从 0 开始计数,下面+1

const (
	v1 = iota + 1 // 会从1开始往下计数
	v2
	v3
	v4
)

隔断一个值

const (
	v1 = iota + 2
  _ // 下划线
	v2
	v3
	v4
)

// 输出: 2 4 5 6 7
const (
	v1 = iota
	v2
	v3
	v4
)

const (
	n1 = iota // 不会和上一个iota继续,会重新从0开始
  n2
  n3
)

输入

用户输入数据,完成项目交互。

  • fmt.Scan
  • fmt.Scanln(用的比较多)
  • fmt.Scanf

示例 1:

package main

import "fmt"

func main() {
	var name string
	fmt.Println("请输入用户名:")

	fmt.Scan(&name) // 把name变量的内存地址放进来,给内存地址指向的空间赋值的过程
	fmt.Println(name)
}

请输入用户名:
wujie
wujie


示例 2:

在编辑器里会清楚地看到 Scan 泛黄,其实 Scan 还有 2 个返回值

当使用 Scan 时,会提示用户输入,用户输入完之后,会得到两个值:

  • count,用户输入了几个值
  • err,当用户输入的过程中出现错误了,就包含了错误信息
package main

import "fmt"

func main() {
	var name string
	fmt.Println("请输入用户名:")

	count, err := fmt.Scan(&name) // 把name变量的内存地址放进来,给内存地址指向的空间赋值的过程
	fmt.Println(count, err)
	fmt.Println(name)
}

请输入用户名:
ddqwd
1 <nil>
ddqwd

nil 代表没有错误

package main

import "fmt"

func main() {
	var name string
	fmt.Println("请输入用户名:")

	//count, err := fmt.Scan(&name) // 把name变量的内存地址放进来,给内存地址指向的空间赋值的过程
	_, err := fmt.Scan(&name) // 使用下划线来代替不使用的变量
	if err == nil { // 错误信息为空,代表没错,这里容易被单词误解
		fmt.Println(name)
	} else {
		fmt.Println("用户输入错误", err)
	}
}

特别说明:fmt.Scan 要求你输入 2 个值,就必须输入 2 个值,否则会一直等待


var name string
_, err := fmt.Scanln(&name)
fmt.Println(err)
fmt.Println(name)

效果上和 Scan 差不多

差别:Scan 必须都输完,Scanln 等待回车,只要一按下回车,就算你输入完了;其他全都是一样的。


Scanf 就是格式化输入,第一个输入一个带占位符的格式,第二个和上面一样;支持有模板的方式让用户输入。

var name string
fmt.Println("请输入用户名")
fmt.Scanf("我叫%s", &name)
fmt.Println(name)
var name string
fmt.Println("请输入用户名")
fmt.Scanf("我叫%s今年18岁", &name)
fmt.Println(name)

// 如果输入:我叫XXX今年18岁
// 输出:xxx今年18岁 此时name = xxx今年18岁
// go官方让我们使用空格隔开进行输入
// 输入:我叫xxx 今年18岁

var age int
fmt.Scanf("我叫%s 今年%d 岁", &name, &age)
// 如果%d后面也有数字,会将数字也带着输出,所以这里都主动加上括号

如果返回的两个变量都不想要

var name string
_, _ = fmt.Scanln(&name) // 这里直接使用 = ,不用 := ,因为:= 会有相当于声明了2个同名的变量的错误

无法解决的问题

var message string

fmt.Scanln(&message)
fmt.Println(message)

假如你输入:带我去多无群多的去 "空格手动描述" 带我带我群多前端去

此时,就只会输出空格之前的内容

**解决办法,使用os.Stdin**获取终端输入,获取包含空格的内容。

package main

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

func main() {
  reader := bufio.NewReader(os.Stdin)
  // reader默认一次能读4096个字节 (4096 / 3)个汉字,如果一次读完了,isPrefix = false;读不完,先读一部分,isPrefix=true,再去读一行,如果读完了,isPrefix=false
	// line, isPrefix, err := reader.ReadLine() // 读一行
  line, _, _ := reader.ReadLine() // 所有的都一样才需要去掉冒号
	fmt.Println(line, isPrefix, err)
  data := string(line) // 转换成功后的字符串
  fmt.Println(data)
}

  • line:从 stdin 中读取的一行的数据(字节集合,可以转化成为字符串)
  • isPrefix: 只有一次读完了,isPrefix 才为 false,读不完都是 true

条件语句

最基本

if 条件 {
  成立后代码执行
} else {
  不成立,此代码执行
}

for 循环

package main

import "fmt"

// for 循环
func main() {
	// 1. 基本for循环
	//for i := 0; i < 10; i++ {
	//	fmt.Println(i)
	//}

	// 2. 省略初始语句,必须保留初始语句后面的分号
	//var i = 0
	//for ; i < 10; i++ {
	//	fmt.Println(i)
	//}

	// 3. 省略初始语句和结束语句
	//var i = 10
	//for i > 0 {
	//	fmt.Println(i)
	//	i--
	//}

	// 4. 死循环
	//for {
	//	fmt.Println("hello world")
	//}

	// 5. break跳出循环
	for i := 0; i < 5; i++ {

		if i == 3 {
			//break
			continue // 继续下一次循环
		}
		fmt.Println(i)
	}
}

对 for 进行打标签,然后通过 break 和 continue 就可以实现多层循环的跳出和终止。

f1:
		for i := 0; i < 3; i++ {
			for j := 1; j < 5; j++ {
				if j == 3 {
					continue f1
				}
				fmt.Println(i, j)
			}
		}

>>> 输出:
0 1
0 2
1 1
1 2
2 1
2 2
f1:
		for i := 0; i < 3; i++ {
			for j := 1; j < 5; j++ {
				if j == 3 {
					break f1
				}
				fmt.Println(i, j)
			}
		}


>>> 输出:
1 1
1 2

数值类型范围

范围

有符号位

int8  // 1个字节	-2^7 ~ 2^7-1
int16  // 2个字节	-2^15 ~ 2^15-1
int32 // 4个字节	-2^31 ~ 2^31-1
int64 // 8个字节	-2^63 ~ 2^63-1

无符号位

uint8 	// 1
uint16	// 2
uint32	// 4
uint64	// 8

提示

快速记忆,一个字节占 8 位,就按照第一个 int8 的来进行记忆,后面int16就使用 16/8 的方式来算占多少个字节。这个范围,也是按照第一个来算,找规律。

无符号的范围,都是从 0 开始,到对应的 2^多少次方-1uint多少就是多少次方。

打印数据类型的方法

fmt.Printf("变量名: %T\n", 变量)

打印占用空间大小的方法

fmt.Println(unsafe.Sizeof(变量))
Loading...