zl程序教程

您现在的位置是:首页 >  其他

当前栏目

[GO语言基础] 三.变量声明、数据类型、标识符及编程练习

2023-04-18 14:25:40 时间

作为网络安全初学者,会遇到采用Go语言开发的恶意样本。因此从今天开始从零讲解Golang编程语言,一方面是督促自己不断前行且学习新知识;另一方面是分享与读者,希望大家一起进步。前文介绍了Go的编译运行、语法规范、注释转义及API标准库知识;这篇文章将介绍Golang的变量、数据类型和标识符知识,并通过12道编程练习进行提升。这系列文章入门部分将参考“尚硅谷”韩顺平老师的视频和书籍《GO高级编程》,详见参考文献,并结合作者多年的编程经验进行学习和丰富,且看且珍惜吧!后续会结合网络安全进行GO语言实战深入,加油~

这些年我学过各种编程语言,从最早的C语言到C++,再到C#、PHP、JAVA,再到IOS开发、Python,到最新的GO语言,学得是真的杂。有时候觉得编程语言恰恰是最简单的,而通过一门编程语言能够解决实际问题或深入底层才是其价值所在,并且当我们学好一门编程语言后,其他编程语言都非常类似,殊途同归,学起来也很迅速。

源码下载:

  • https://github.com/eastmountyxz/Go-learning

前文参考:

文章目录:

  • 一.变量 1.什么是变量 2.变量的声明 3.变量的注意事项
  • 二.数据类型 1.整型 2.浮点型 3.字符类型 4.布尔型 5.字符串类型 6.基本数据类型的默认值 7.基本数据类型转换 8.基本数据类型和string转换
  • 三.指针 1.基本介绍 2.指针类型 3.获取指针类型所指向的值 4.指针修改值 5.值类型和引用类型
  • 四.标识符和关键字 1.标识符 2.关键字
  • 五.Golang编程练习 1.题目 2.解答

一.变量

1.什么是变量

为什么需要变量呢? 一个程序就是一个世界,不论使用哪种高级程序语言编写代码,变量都是其程序的基本组成单位。如下图所示的sum和sub都是变量。

变量的定义: 变量相当于内存中一个数据存储空间的表示,可以将变量看作是一个房间的门牌号,通过门牌号能找到房间;通过变量名可以访问到变量的值。变量使用的常见三个步骤:

  • 声明变量或定义变量
  • 变量赋值
  • 变量使用

变量入门示例: 变量表示内存中的一个存储区域,该区域有自己的变量名和数据类型。下面是一个简单案例:

package main
import "fmt"

func main() {
	//定义变量
	var n int
	//赋值变量
	n = 10
	//使用变量
	fmt.Println("n =", n)
}

2.变量的声明

Go语言变量使用的三种方式:

  • (1) 指定变量类型,声明后若不复制,使用默认值,如int的默认值是0; var i int fmt.Println(“i =”, i)
  • (2) 根据值自行判定变量类型(类型推导); var num = 3.14 fmt.Println(“num =”, num)
  • (3) 省略var,=是赋值, :=是声明变量并赋值。注意 :=左侧变量不应该是已经声明过的,否则会编译错误。 name := “eastmount” fmt.Println(“name =”, name) 等价于 var name string name = “eastmount”
package main
import "fmt"

func main() {
	//方法一:指定变量类型 int默认值为0
	var i int
	fmt.Println("i =", i)

	//方法二:根据值自行判定变量类型
	var num = 3.14
	fmt.Println("num =", num)

	//方法三:省略var  :=声明变量并赋值
	name := "eastmount"
	fmt.Println("name =", name)
}

输出结果如下图所示:

多变量声明: 在编程中,通常会遇到一次性声明多个变量的情况。Golang同样提供了对应的功能,如下所示:

  • var n1, n2, n3 int
  • var n1, name, n3 = 100, “csdn”, 3.14
  • n1, name, n3 := 100, “csdn”, 3.14

代码如下,注意不要重复定义变量,否则会报错。

package main
import "fmt"

func main() {
	//方法一:指定变量类型 int默认值为0
	var n1, n2, n3 int
	fmt.Println("n1 =", n1, "n2 =", n2, "n3 =", n3)

	//方法二:根据值自行判定变量类型
	var m1, name, m3 = 100, "csdn", 3.14
	fmt.Println("m1 =", m1, "name =", name, "m3 =", m3)

	//方法三:省略var  :=声明变量并赋值
	k1, k2, k3 := 100, "yxz", 3.14
	fmt.Println("k1 =", k1, "k2 =", k2, "k3 =", k3)
}

输出结果如下图所示:

那么,如何一次性声明多个全局变量呢?

  • 在Go中函数外部定义的变量就是全局变量
  • 全局变量如果只定义不使用,不会报错
  • 一次性声明
package main
import "fmt"

//定义全局变量
var n = 100
var m = 200
var k = "eastmount"

//等价于一次性声明
var (
	o = 300
	p = 400
	q = "yxz"
)

func main() {
	//输出全局变量
	fmt.Println("n =", n, "m =", m, "k =", k)
}

3.变量的注意事项

注意事项:

  • 变量在某个区域的数据值可以在同一类型范围内不断变化,但不能改变数据类型(强类型转换)
  • 变量在同一作用域(在一个函数或代码块)内不能重名
  • 变量=变量名+值+数据类型,这一点大家需要注意,变量的三要素
  • Golang的变量如果没有赋初值, 编译器会使用默认值,比如int为0,string为空串等

变量知识总结:

  • 声明变量 基本语法:var 变量名 数据类型 比如“var n int”表示声明一个变量,变量名为n;“var num float32”表示声明一个单精度小数类型的变量
  • 初始化变量 在声明变量时赋初值,比如“var n int = 12”,如果声明时就直接赋值,可省略数据类型,比如“var m = 30”
  • 变量赋值 先声明变量“var num int”,此时默认值为0,再给变量赋值“num=78”

加号用法: - 当左右两边都是数值型,做加法运算 - 当左右两边都是字符串,做字符串拼接

package main
import "fmt"

func main() {
	//加法运算
	var i, j = 2, 3
	var res = i + j
	fmt.Println("res =", res)

	//字符串拼接
	var str1 = "hello"
	var str2 = "world"
	var str3 = str1 + str2
	fmt.Println("str3 =", str3)
}

二.数据类型

每一种数据都定义了明确的数据类型,并在内存中分配不同大小的内存空间。常用的数据类型分为基本数据类型和派生/复杂数据类型。

  • 基本数据类型 数值型(整型、浮点型)、字符型(单个字符)、布尔型、字符串(Go中归属为基本数据类型)、复数类型(complex64、complex128)、rune
  • 派生/复杂数据类型 指针、数组、结构体、管道、函数、切片、接口、map

1.整型

整数类型就是用于存放整数值,比如0、-2、23等。

(1) 整形的各个类型 注意,一个字节为8位(bit),计算机采用二进制(0或1),即2^8表数范围。

  • 有符号的int8表示为-128 ~ 127。下图bit7为符号位,最大值为bit0至bit6均为1,故为2^7- 1,即127;由于存在+0和-0,因此将 -0位赋值给负数,因此最小值为-128。
  • 无符号uint8表示为0 ~ 255。无符号最大值为bit0至bit7均为1,故为2^8-1,即255。

案例如下:

package main
import "fmt"

func main() {
	var i int = 1
	fmt.Println("i =", i)

	var j int8 = 127
	fmt.Println("j =", j)
}

输出结果如下图所示:

(2) 无符号整形

案例如下,如果uint8赋值为256则会提示边界溢出“. ype03_01.go:14:6: constant 256 overflows uint8”。

(3) 其他int类型

案例如下:

package main
import "fmt"

func main() {
	var i int = -20
	fmt.Println("i =", i)

	var j uint = 127
	fmt.Println("j =", j)

	var k rune = 1024
	fmt.Println("k =", k)

	var m byte = 255
	fmt.Println("m =", m)
}

输出结果如下:

(4) 整形的注意事项

  • Golang各证书类型分为有符号和无符号,int、uint的大小和系统有关
  • Golang的整型默认声明为int型
  • 查看某个变量的字节大小(unsafe.Sizeof)和数据类型(fmt.Printf->%T)
  • Golang程序中整型变量使用时,遵守保小不保大的原则,即:在保证程序正确运行下,尽量使用占用空间小的数据类型,比如年龄
  • bit是计算机中最小存储单位,byte是计算机中基本存储单元(1 byte=8 bit)
package main
import "fmt"
import "unsafe"

func main() {
	//查看变量的数据类型
	//fmt.Printf() 用于格式化输出
	var num int = 1024
	fmt.Println("num =", num)
	fmt.Printf("num 的数据类型是 %T 
", num)

	//查看变量占用的空间大小
	var n int64 = 10
	var name = "eastmount"
	fmt.Printf("n 的数据类型是 %T; 占用字节数是 %d 
", n, unsafe.Sizeof(n))
	fmt.Printf("name 的数据类型是 %T; 占用字节数是 %d 
", name, unsafe.Sizeof(name))

	//Golang程序中整型变量使用尽量遵守保小原则
	//在保证程序正确运行下,尽量使用空间小的数据类型
	var age byte = 28
	fmt.Printf("n 的数据类型是 %T; 占用字节数是 %d 
", age, unsafe.Sizeof(age))
}

输出结果如下图所示:

2.浮点型

浮点型用于存放小数,比如3.14、-1.9等,两种类型如下(没有double类型)。

  • 单精度float32:4字节
  • 双精度float64:8字节

浮点数都是有符号的,浮点数在机器中存放形式为:

  • 浮点数=符号位+指数位+尾数位

浮点数的尾数部分可能丢失,造成精度损失。

  • float64的精度要比float32准确,如果我们要保存一个高精度数据,则应该选用float64;软件开发中也推荐使用float64
package main
import "fmt"

func main() {
	//浮点数定义
	var price float32 = 89.12
	fmt.Println("price =", price)

	var num1 float32 = -0.00081
	var num2 float64 = -78942.00912
	fmt.Println("num1 =", num1, "num2 =", num2)
	
	//精度损失
	var num3 float32 = -123.0000203
	var num4 float64 = -123.0000203
	fmt.Println("
精度损失")
	fmt.Println("num3 =", num3, "num4 =", num4)
}

输出结果如下图所示,可以看到float32精度损失。

浮点数的注意事项

  • Golang浮点类型包括float32和float64两种,不存在double类型
  • Golang浮点类型有固定的范围和字段长度,不受具体操作系统的影响
  • Golang的浮点型默认声明为float64类型
  • 浮点型常量有两种表示 – 十进制数形式,如:3.14、.1234,必须有小数点 – 科学技术法形式,如:5.1234e2=5.12*10的2次方,5.12E-2=5.12/10的2次方
  • 通常情况推荐使用float64,它比float32更精确
package main
import "fmt"
import "unsafe"

func main() {
	//浮点数默认声明为 float64类型
	var num = 89.12
	fmt.Printf("num的数据类型是 %T; 占用字节数是 %d 
", num, unsafe.Sizeof(num))

	//十进制数形式
	num1 := 3.14
	num2 := .123
	fmt.Println("num1 =", num1, "num2 =", num2)

	//科学计数法形式
	num3 := 1.234e2      //1.234 * 10的2次方
	num4 := 1.234E2      //1.234 * 10的2次方
	num5 := 1.234E-2     //1.234 / 10的2次方
	fmt.Println("num3 =", num3, "num4 =", num4, "num5 =", num5)
}

输出结果如下图所示:

3.字符类型

Golang中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存(ASCII码表)。注意,是单个字母,而汉字是3个字节。

字符串是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的,也就是说传统的字符串是由字符组成的,而Go的字符串不同,它由字节组成。

举个简单示例:

  • 如果保存的字符在ASCII表中,比如 [0-9, a-z, A-Z, …] 直接可以保存到byte
  • 如果保存的字符对应码值大于255,这是可以考虑使用int类型保存
  • 如果需要按照字符的方式输出,这是需要格式化输出,即fmt.Printf("%c", num)
package main
import "fmt"

func main() {
	//定义字符类型
	var c1 byte = 'a'
	var c2 byte = '6'

	//当我们直接输出byte值 即输出对应字符的ASCII码值
	fmt.Println("c1 =", c1)
	fmt.Println("c2 =", c2)

	//如果需要输出对应字符 需要使用格式化输出
	fmt.Printf("c1=%c c2=%c
", c1, c2)

	//var c3 byte = '杨'
	//overflow溢出
	var c3 int = '杨'
	fmt.Printf("c3=%c c3对应码值=%d
", c3, c3)
}

输出结果如下图所示,比如“杨”对应Unicode编码10进制为“26472”。由于ASCII码只能存储127个字符,英文字母和字符可以,但是中文字符或其他国家字符很多,故衍生出UTF-8其他编码方式。

字符型的注意事项

  • 字符常量是用单引号(’)括起来的单个字符,例如var c1 byte=‘a’,var c2 int=‘中’
  • Go中允许使用转义字符 ’ 来讲其后的字符转变为特殊字符型常量,比如var c3 char = ‘ ’,表示换行符
  • Go语言的字符使用UTF-8编码,英文字母1个字节,汉字3个字节。如果想查询字对应的utf8编码,使用网址: – http://www.mytju.com/classcode/tools/encode_utf8.asp
  • 在Go中,字符的本质是一个整数,直接输出时,是该符对应的UTF-8编码的码字
  • 可以直接给某个变量赋一个数字,然后按格式化输出%c,会输出该数字对应的unicode字符
  • 字符类型可以进行运算,相当于一个整数,因为它都对应有Unicode编码
package main
import "fmt"

func main() {
	//直接赋值数字 格式化输出字符
	var c1 int = 22269   // 22269->'国'  120->'x'
	fmt.Printf("c1=%c
", c1)

	//字符类型可以运算 相当于一个整数 运算时按码值运行
	var c2  = 10 + 'a'
	fmt.Printf("c3=%c c3对应码值=%d
", c2, c2)
}

输出结果如下图所示:

字符类型本质探讨

  • 字符型存储到计算机中,需要将字符对应的码值(整数)找出来 – 存储:字符 -> 对应码值 -> 二进制 -> 存储 – 读取:二进制 -> 码值 -> 字符 -> 读取
  • 字符和码值的对应关系是通过字符编码表决定的,事先规定好的
  • Go语言的编码都统一成utf-8,从而解决各种乱码问题

4.布尔型

布尔(bool)类型数据只允许取值true和false,用于表示真和假。它仅占1个字节,常用于逻辑运算,适用于程序流程控制(后续详细介绍)。

  • if条件控制语句
  • for循环控制语句

下面举个简单的案例:

package main
import "fmt"
import "unsafe"

func main() {
	//定义数据类型
	var num = false
	fmt.Println("num =", num)

	//注意bool类型占用1个字节 只能取true或false
	fmt.Println("占用空间 =", unsafe.Sizeof(num))
}

输出结果如下:

注意:Go语言中bool类型不可以使用0或非0的整数替代false或true,这和C语言不同。

5.字符串类型

字符串是一串固定长度的字符连接起来的字符序列。Go字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本。

package main
import "fmt"

func main() {
	//定义字符串类型
	var name string = "我的名字叫Eastmount!"
	fmt.Println(name)
}

输出结果如下图所示:

字符串的注意事项

  • Go语言字符串的字符使用UTF-8编码标识Unicode文本,这样Golang统一使用UTF-8编码,中文乱码问题不在困扰我们。编码问题一直是C语言、Java、Python2常见问题
  • 字符串一旦被复制,字符串就不能修改,即Go中字符串是不可变的(原子性)

字符串两种表示形式

  • 双引号:会识别转移字符
  • 反引号:以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果
package main
import "fmt"

func main() {
	//双引号字符串
	str1 := "Eastmount
CSDN"
	fmt.Println(str1)

	//反引号字符串 ``
	str2 := `
	package main
	import "fmt"

	func main() {
		//定义字符串类型
		var name string = "我的名字叫Eastmount!"
		fmt.Println(name)
	}
	`
	fmt.Println(str2)
}

输出结果如下图所示:

字符串拼接

  • var str = “hello” + “world”
  • str += “yangxiuzhang”

当一行字符串太长时,需要使用到多行字符串,可以进行如下处理:

6.基本数据类型的默认值

在Golang中,数据类型都有一个默认值,当程序员没有赋初值时,它就会保留默认值(或零值)。常见默认值如下图所示:

举例如下:

7.基本数据类型转换

Golang和Java、C不同,Go在不同类型的变量之间赋值时需要显示转换。换句话说,Golang中数据类型不能自动转换。

强制转换基本语法

  • 表达式 T(v) 将值 v 转换为类型 T
  • T:数据类型,比如int32、int64、float64等
  • v:需要转换的变量
package main
import "fmt"

func main() {
	//数据类型转换
	var a int32 = 100

	//整型->浮点型
	var b float32 = float32(a)
	var c int8 = int8(a)

	//低精度->高精度
	var d int64 = int64(a)
	fmt.Printf("a=%v b=%v c=%v d=%v
", a, b, c, d)
	fmt.Printf("a=%T b=%T c=%T d=%T
", a, b, c, d)

	//浮点型->整型
	var e float32 = 3.14
	var f int32 = int32(e)
	fmt.Printf("e=%v f=%v
", e, f)
	fmt.Printf("e=%T f=%T
", e, f)
}

输出结果如下图所示:

注意事项:

  • Go中数据类型的转换可以从表示范围小到表示范围大,也可以从范围大到范围小
  • 被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化
  • 在转换中,比如将 int64 转换成 int8(-128到127),编译时不会报错,只是转换的结果按溢出处理,和期待的结果不一样。因此在转换时,需要考虑范围
  • 数据类型不一致的运算错误,建议转换成相同的数据类型。

由于n3是int8类型,赋值语句含128所以编译不通过;而n4也是int8类型,但编译能通过,结果溢出处理。

8.基本数据类型和string转换

在程序开发中,经常将基本数据类型转换成string或将string转换成基本数据类型。

(1) 基本数据类型转换成string

  • 方法1:fmt.Sprintf("%参数", 表达式) Sprintf根据format参数生成格式化的字符串并返回该字符串,参数需要和表达式的数据类型匹配。

代码如下:

package main
import "fmt"

func main() {
	//变量定义
	var num1 int = 99
	var num2 float64 = 3.14
	var b bool = true
	var char byte = 'h'
	var str string
	
	//fmt.Sprintf转换
	str = fmt.Sprintf("%d", num1)
	fmt.Printf("str type %T str=%q
", str, str)
	
	str = fmt.Sprintf("%f", num2)
	fmt.Printf("str type %T str=%q
", str, str)

	str = fmt.Sprintf("%t", b)
	fmt.Printf("str type %T str=%q
", str, str)

	str = fmt.Sprintf("%c", char)
	fmt.Printf("str type %T str=%q
", str, str)
}

输出结果如下图所示:

  • 方法2:使用strconv包的函数 func FormatBool(b bool) string func FormatFloat(f float64, fmt byte, prec, bitSize int) string func FormatInt(i int64, base int) string func FormatUint(i uint64, base int) string func Itoa(int(num))
package main
import "fmt"
import "strconv"

func main() {
	//变量定义
	var num1 int = 99
	var num2 float64 = 3.14
	var b bool = true
	var num3 int64 = 4567
	var str string
	
	//strconv.FormatInt 10进制
	str = strconv.FormatInt(int64(num1), 10)
	fmt.Printf("str type %T str=%q
", str, str)

	//strconv.FormatFloat(num2, 'f', 10, 64)
	//说明:'f'格式 10表示小数位保留10位 64表示小数float64
	str = strconv.FormatFloat(num2, 'f', 10, 64)
	fmt.Printf("str type %T str=%q
", str, str)

	str = strconv.FormatBool(b)
	fmt.Printf("str type %T str=%q
", str, str)

	str = strconv.Itoa(int(num3))
	fmt.Printf("str type %T str=%q
", str, str)
}

输出结果如下图所示:

(2) string类型转基本数据类型 使用strconv包的函数:

  • func ParseBool(str string) (value bool, err error)
  • func ParseFloat(s string, bitSize int) (f float64, err error)
  • func ParseInt(s string, base int, bitSize int) (i int64, err error)
  • func ParseUint(s string, b int, bitSize int) (n uint64, err error)

需要说明,因为返回的是int64或float64,如希望得到int32、float32,则需要调用 int32(num)处理。

注意:在将String类型转换成基本数据类型时,要确保String类型能够转成有效的数据。比如把“123”转成一个整数,但不能把“hello”转成一个整数;如果这样Go直接将其转成0,其他类型也是同样的道理,float转成0、bool转成false。

三.指针

1.基本介绍

由于后续很多内容(如引用)都会涉及到指针,C语言中它也是一个难点,因此这里我们先介绍指针的基础知识,更好地帮助我们学习Golang。

对于基本数据类型来说,变量存的就是值,也叫值类型。获取变量的地址使用“&”,比如:

  • var num int
  • 获取num的地址是&num
package main
import "fmt"

func main() {
	//基本数据类型在内存布局
	var i int = 10
	//i的地址 &i
	fmt.Println("i的地址=", &i)
	fmt.Println("i的值=", i)
}

输出结果:

i的地址= 0xc0000100a0
i的值= 10

2.指针类型

指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:

  • var ptr *int = &num

举例说明指针在内存的布局。

package main
import "fmt"

func main() {
	//基本数据类型在内存布局
	var i int = 10
	//i的地址 &i
	fmt.Println("i的地址=", &i)
	fmt.Println("i的值=", i)

	/*
	 var ptr *int = &i
	   1.ptr是一个指针变量
	   2.ptr的类型是*int
	   3.ptr本身的值是&i
	*/
	var ptr *int = &i
	fmt.Printf("ptr=%v
", ptr)
}

输出结果如下图所示:

3.获取指针类型所指向的值

使用 * 实现,比如 var ptr *int,使用ptr获取ptr指向的值。

package main
import "fmt"

func main() {
	//基本数据类型在内存布局
	var i int = 10
	//i的地址 &i
	fmt.Println("i的地址=", &i)
	fmt.Println("i的值=", i)

	/*
	 var ptr *int = &i
	   1.ptr是一个指针变量
	   2.ptr的类型是*int
	   3.ptr本身的值是&i
	*/
	var ptr *int = &i
	fmt.Printf("ptr=%v
", ptr)

	//获取指针类型指向的值
	fmt.Printf("ptr的地址=%v
", &ptr)
	fmt.Printf("ptr指向的值=%v
", *ptr)
}

输出结果如下所示:

  • i的地址= 0xc0000100a0
  • i的值= 10
  • ptr=0xc0000100a0
  • ptr的地址=0xc000006030
  • ptr指向的值=10

举例说明:

4.指针修改值

编写一个程序,获取一个int变量num的地址并显示终端;再将num的地址赋值给指针ptr,通过ptr去修改num的值。

package main
import "fmt"

func main() {
	//基本数据类型
	var num int = 10
	fmt.Printf("num的地址=%v 原始值=%v
", &num, num)

	//指针
	var ptr *int
	ptr = &num
	fmt.Printf("ptr的地址=%v 指向的值为=%v 自身=%v
", &ptr, *ptr, ptr)

	//修改值
	*ptr = 666
	fmt.Printf("num修改后的值=%v
", num)
}

输出结果如下图所示:

下面这三个练习也推荐大家尝试。

5.值类型和引用类型

(1) 值类型

  • 值类型:有对应的指针类型,形式为“ *数据类型 ”,比如int对应的指针就是“*int”,float32对应的指针类型就是“*float32”,以此类推
  • 值类型的基本数据类型包括:int、float、bool、string、数组和结构体struct
  • 值类型:变量直接存储值,内存通常在栈中分配

(2) 引用类型

  • 变量存储的一个地址,这个地址对应的空间才是真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾空间,由GC来回收。
  • 引用类型的基本数据类型包括:指针、slice切片、map、管道、interface等

内存的栈区和堆区示意图如下:

四.标识符和关键字

1.标识符

Golang对各种变量、方法和函数等命名时使用的字符序列称为标识符。凡是自己可以起名字的地方也都叫标识符。其命名规则如下:

  • 由26个英文字母大小写、0-9、下划线(_)组成
  • 数字不可以开头,比如正确的“var num int”、错误的“var 3num int”
  • Golang中严格区分大小写,Name和name是两个不同的变量
  • 标识符不能包含空格
  • 下划线(_)本身在Go中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但是它对应的值会被忽略,所以仅能作为占位符使用,不能作为标识符使用
  • 不能以系统保留关键字作为标识符(一共有25个),比如break、if等

标识符命名注意事项:

  • 包名:保持package的名字和目录尽量保持一致,建议采取有意义的包名,不要和标准库冲突(大家可以去Go开源代码学习下包名)。
  • 变量、函数、常量名建议采用驼峰法命名 比如:var stuName string = “csdn”,形如xxxYyyyZzzz…
  • 如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用( 首字母大写是公开的,首字母小写是私有的 ),在Golang中没有public、private等关键字,这也是Go与其他语言的区别

举例说明,首先在utils.go中定义一个变量;然后在main.go中使用该变量。

untils.go

package model

//定义一个变量
var HeroName string = "武松"

main.go

package main
import (
	"fmt"
	//导入utils.go文件的变量或函数 引入该model包
	"go_code/chapter03/datatype/model"
)

func main() {
	//该区域的数据可以在同一类型范围内变化
	var n int = 10
	n = 30
	n = 50
	fmt.Println("n=", n)

	//使用utils.go的HeroName变量 包名.标志符
	fmt.Println(model.HeroName)
}

输出结果如下图所示:

如果变量名定义时是小写“heroNam”则会报错

2.关键字

在Go中,为简化代码编译过程中对代码的解析,系统仅保留25个关键字,如下表所示:

除了保留关键字外,Go还提供了36个预定的标志符,包括基础数据类型和系统内嵌函数。

五.GO编程练习

1.题目

(1) 分别定义常见的数据类型(整型、浮点型、字符型、布尔型、字符串型)变量,输出对应结果并查看变量的空间大小、数据类型。

变量名称        数据类型        占用空间        对应的值
num1     int     8       -12
num2     uint8   1       127
num3     int64   8       12345
num4     float32         4       3.14
num5     float64         8       314.15
num6     int     8       26472
num7     bool    1       false
num8     string          16      Eastmount

(2) 判断数字9的奇偶性输出它是奇数或偶数。

num = 9
这是一个奇数

(3) 有人用温度计测量出华氏法表示的温度(如69°F),先要求把它转换为以摄氏法表示的温度(如20°C),输入值为69。

华氏度 f= 69
摄氏度 c= 20.555555555555557
摄氏度取整 c= 20

(4) 通过两种方法(调用函数和循环)实现计算字符串“Eastmount”长度。

The length of "Eastmount" is 9.
E a s t m o u n t
The length of "Eastmount" is 9.

(5) 循环依次输出“East 秀璋”字符串的所有字符。

Unicode遍历字符串
Unicode: E  69
Unicode: a  97
Unicode: s  115
Unicode: t  116
Unicode:    32
Unicode: 秀  31168
Unicode: 璋  29835

utf-8遍历字符串
Unicode: E  69
Unicode: a  97
Unicode: s  115
Unicode: t  116
Unicode:    32
Unicode: ç  231
Unicode: §  167
Unicode: €  128
Unicode: ç  231
Unicode: ’  146
Unicode: ‹  139

(6) 实现字符串循环拼接,将变量str拼接成“a”到“z”并输出。

abcdefghijklmnopqrstuvwxyz

(7) 从键盘上输入整数、浮点数和字符,然后赋值给变量并输出结果。

Eastmount
28
60.2
我的名字是: Eastmount
我的年龄是: 28
我的体重是: 60.2

(8) 任意输入一个字母,实现大小写自动转换输出。

请输入任意字母:
A
对应的ASCII码值: 65
A => a

请输入任意字母:
h
对应的ASCII码值: 104
h => H

(9) 实现多种数据类型转换(int和float转换、float和string转换)。

a=100 int32, b=100 float32
c=3.14 float32, d=3 int32
e=3.14 float64, f=3.140000 string
e=123.456 string, f=123.456 float64

(10) 指针基本概念,定义变量i,然后指针ptr指向该值,输出对应值及地址。

i的地址= 0xc0000100a0
i的值= 10
ptr=0xc0000100a0
ptr的地址=0xc000006030
ptr指向的值=10

(11) 编写一个程序,获取一个int变量num的地址并显示终端;再将num的地址赋值给指针ptr,通过ptr去修改num的值。

666
num的地址= 0xc0000100a0
num的值= 54
ptr的地址=0xc000006030
ptr指向的值=54

修改值后: num=512 0xc0000100a0
修改值后: ptr=512 0xc000006030

(12) 输入a和b两个整数,调用指针按从大到小的顺序输出a和b。

20 88
a=88, b=20
max=88, min=20    //p1和p2

2.解答

注意:程序实现的方法由千万种,作者更多是提供一个场景让你独立思考,独立解决问题。希望你喜欢这些题目,不要觉得枯燥,很多题目后续都会结合实际项目及经验进行介绍。

(1) 分别定义常见的数据类型(整型、浮点型、字符型、布尔型、字符串型)变量,输出对应结果并查看变量的空间大小、数据类型。

  • 查看某个变量的字节大小(unsafe.Sizeof)和数据类型(fmt.Printf->%T)
package main
import "fmt"
import "unsafe"

func main() {
	var num1 int = -12
	fmt.Println("变量名称	数据类型	占用空间	对应的值")
	fmt.Printf("num1 	 %T 	 %d 	 %v 
", num1, unsafe.Sizeof(num1), num1)

	var num2 byte = 127
	fmt.Printf("num2 	 %T 	 %d 	 %v 
", num2, unsafe.Sizeof(num2), num2)

	var num3 int64 = 12345
	fmt.Printf("num3 	 %T 	 %d 	 %v 
", num3, unsafe.Sizeof(num3), num3)

	var num4 float32 = 3.14
	fmt.Printf("num4 	 %T 	 %d 	 %v 
", num4, unsafe.Sizeof(num4), num4)

	var num5 float64 = 3.1415e2
	fmt.Printf("num5 	 %T 	 %d 	 %v 
", num5, unsafe.Sizeof(num5), num5)

	var num6 int = '杨'
	fmt.Printf("num6 	 %T 	 %d 	 %v 
", num6, unsafe.Sizeof(num6), num6)

	var num7 bool = false
	fmt.Printf("num7 	 %T 	 %d 	 %v 
", num7, unsafe.Sizeof(num7), num7)

	var num8 string = "Eastmount"
	fmt.Printf("num8 	 %T 	 %d 	 %v 
", num8, unsafe.Sizeof(num8), num8)
}

输出结果如下图所示:

(2) 判断数字9的奇偶性输出它是奇数或偶数。

package main
import "fmt"

func main() {
	var num int = 9
	fmt.Println("num =", num)

	//判断奇偶性
	if num % 2 == 0 {
		fmt.Println("这是一个偶数")
	} else {
		fmt.Println("这是一个奇数")
	}
}

输出结果如下图所示:

(3) 有人用温度计测量出华氏法表示的温度(如69°F),先要求把它转换为以摄氏法表示的温度(如20°C),输入值为69。

package main
import "fmt"

func main() {
	var f float64
	var c float64

	//温度转换
	f = 69.0
	c = (5.0 / 9) * (f - 32)
	fmt.Println("华氏度 f=", f)
	fmt.Println("摄氏度 c=", c)
	fmt.Println("摄氏度整数 c=", int64(c))
}

输出结果如下图所示:

(4) 通过两种方法(调用函数和循环)实现计算字符串“Eastmount”长度。

package main
import "fmt"
 
func main() {
    var str string
	str = "Eastmount"

	//计算字符串长度
	fmt.Printf("The length of "%s" is %d. 
", str, len(str))
	
	//循环计算字符串长度
	var num int = 0
	for _, s := range str {
		fmt.Printf("%c ", s)
		num += 1
	}
	fmt.Printf("
The length of "%s" is %d. 
", str, num)
}

输出结果如下图所示:

注意,当字符串中包含多字节字符时,要用到标准库utf8中的RuneCountInString函数来获取字符串的长度。代码如下:

package main
import (
    "fmt"
    "unicode/utf8"
)
 
func main() {
	//多字节字符
	test := "Hello, 世界"
	fmt.Println("bytes =", len(test))                      //bytes = 13
    fmt.Println("runes =", utf8.RuneCountInString(test))   //runes = 9
}

(5) 循环依次输出“East 秀璋”字符串的所有字符。

package main
import "fmt"
 
func main() {
	str := "East 秀璋"

	//方法1:Unicode遍历字符串
	fmt.Printf("Unicode遍历字符串
")
	for _, s := range str {
		fmt.Printf("Unicode: %c  %d
", s, s)
	}
	

	//方法2:utf-8遍历字符串
	fmt.Printf("utf-8遍历字符串
")
	for i := 0; i < len(str); i++ {
		ch := str[i]
		fmt.Printf("Unicode: %c  %d
", ch, ch)
	}
}

输出结果如下图所示:

(6) 实现字符串循环拼接,将变量str拼接成“a”到“z”并输出。

package main
import "fmt"
 
func main() {
	var str string
	var tt string
	var ch byte = 'a'

	for i := 0; i < 26; i++ {
		tt = fmt.Sprintf("%c", ch)
		str += tt
		ch += 1
	}
	fmt.Println(str)
}

上述代码注意类型转换,输出结果如下图所示:

(7) 从键盘上输入整数、浮点数和字符,然后赋值给变量并输出结果。

  • 第一种: fmt.Scan(地址列表) 参数传入地址列表。输入变量之间可以采用空格或者换行
  • 第二种: fmt.Scanln(地址列表) 与Scan不同在于自带换行,因此输入变量间不能采用换行
  • 第三种:fmt.Scanf(“格式化字符串”, 地址列表)格式化输入 限制固定的输入格式
package main
import "fmt"

func main() {
	var (
		name string
		age int
		weight float32
	)

	//从终端获取用户的输入内容
	fmt.Scan(&name, &age, &weight)
	fmt.Println("我的名字是:", name)
	fmt.Println("我的年龄是:", age)
	fmt.Println("我的体重是:", weight)
}

输出结果如下图所示:

(8) 任意输入一个字母,实现大小写自动转换输出。

  • A的ASCII码值为65,a的ASCII码值为97
package main
import "fmt"

func main() {
	var ch byte
	var re byte

	//从终端获取用户的输入内容
	fmt.Println("请输入任意字母:")
	fmt.Scanf("%c", &ch)
	fmt.Println("对应的ASCII码值:", ch)

	//大小写转换
	if ch >= 'A' && ch <= 'Z' {
		re = ch + 32
	} else if ch >= 'a' && ch <= 'z' {
		re = ch - 32
	}
	fmt.Printf("%c => %c", ch, re)
}

输出结果如下图所示:

(9) 实现多种数据类型转换(int和float转换、float和string转换)。

package main
import "fmt"
import "strconv"

func main() {
	//整型->浮点型
	var a int32 = 100
	var b float32 = float32(a)
	fmt.Printf("a=%v %T, b=%v %T
", a, a, b, b)

	//浮点型->整型
	var c float32 = 3.14
	var d int32 = int32(c)
	fmt.Printf("c=%v %T, d=%v %T
", c, c, d, d)

	//其他类型->string
	var e float64 = 3.14
	var f string
	f = fmt.Sprintf("%f", e)
	fmt.Printf("e=%v %T, f=%v %T
", e, e, f, f)

	//string->其他类型
	var g string = "123.456"
	var h float64
	h, _ = strconv.ParseFloat(g, 64)
	fmt.Printf("e=%v %T, f=%v %T
", g, g, h, h)
}

输出结果如下图所示:

(10) 指针基本概念,定义变量i,然后指针ptr指向该值,输出对应值及地址。

package main
import "fmt"

func main() {
	//基本数据类型在内存布局
	var i int = 10
	//i的地址 &i
	fmt.Println("i的地址=", &i)
	fmt.Println("i的值=", i)

	/*
	 var ptr *int = &i
	   1.ptr是一个指针变量
	   2.ptr的类型是*int
	   3.ptr本身的值是&i
	*/
	var ptr *int = &i
	fmt.Printf("ptr=%v
", ptr)

	//获取指针类型指向的值
	fmt.Printf("ptr的地址=%v
", &ptr)
	fmt.Printf("ptr指向的值=%v
", *ptr)
}

输出结果如下图所示:

(11) 编写一个程序,获取一个int变量num的地址并显示终端;再将num的地址赋值给指针ptr,通过ptr去修改num的值。

package main
import "fmt"

func main() {
	//获取一个int变量num的地址并显示终端
	var num int
	fmt.Scanf("%c", &num)
	fmt.Println("num的地址=", &num)
	fmt.Println("num的值=", num)

	//定义ptr指针变量
	var ptr *int = &num
	fmt.Printf("ptr的地址=%v
", &ptr)
	fmt.Printf("ptr指向的值=%v
", *ptr)

	//通过ptr去修改num的值
	*ptr = 512
	fmt.Printf("修改值后: num=%d %v
", num, &num)
	fmt.Printf("修改值后: ptr=%d %v
", *ptr, &ptr)
}

输出结果如下图所示:

(12) 输入a和b两个整数,调用指针按从大到小的顺序输出a和b。

  • 解题:用指针方法来处理,不交换整型变量的值,而是交换两个指针变量的值
package main
import "fmt"

func main() {
	//变量定义
	var a int
	var b int
	
	//输入两个整数
	fmt.Println("请输入两个整数:")
	fmt.Scanf("%d %d", &a, &b)

	//p1指向a p2指向b
	var p1 *int = &a
	var p2 *int = &b
	var p *int

	//如果a<b则交换p1与p2的值 a存储大值
	if a < b {
		p = p1
		p1 = p2
		p2 = p
	}

	//从大到小输出a、b结果
	fmt.Printf("a=%v, b=%v
", a, b)

	//输出p1和p2所指向变量的值
	fmt.Printf("max=%v, min=%v
", *p1, *p2)
}

输出结果如下图所示,但遗憾的是a和b值并没有交换,而p1确实指向较大值,p2指向较小值。

这与C语言也存在一些差异,C语言代码如下,请大家下来思考具体原因。

六.总结

写到这里,这篇基础性Golang文章介绍完毕,希望您喜欢!

  • 一.变量1.什么是变量 2.变量的声明 3.变量的注意事项
  • 二.数据类型 1.整型 2.浮点型 3.字符类型 4.布尔型 5.字符串类型 6.基本数据类型的默认值 7.基本数据类型转换 8.基本数据类型和string转换
  • 三.指针 1.基本介绍 2.指针类型 3.获取指针类型所指向的值 4.指针修改值 5.值类型和引用类型
  • 四.标识符和关键字 1.标识符 2.关键字
  • 五.GO编程练习 1.题目 2.解答

Go基本概念和数据类型了解后,后面的文章将详细介绍Go语言的运算、条件语句和循环语句知识,并结合案例进行普及。希望这篇基础性文章对您有帮助,写得不好的地方还请海涵。同时非常感谢参考文献中的大佬们的文章分享,尤其是韩顺平老师,深知自己很菜,得努力前行。也希望自己能深入下去,未来四年好好研究Go编程语言,做更多实际工程,写更好的文章,共勉!

源代码下载地址:

  • https://github.com/eastmountyxz/Go-learning

2020年8月18新开的“娜璋AI安全之家”,主要围绕Python大数据分析、网络空间安全、人工智能、Web渗透及攻防技术进行讲解,同时分享论文的算法实现。娜璋之家会更加系统,并重构作者的所有文章,从零讲解Python和安全,写了近十年文章,真心想把自己所学所感所做分享出来,还请各位多多指教,谢谢。

参考文献:

  • Go官网:https://golang.org/
  • https://www.bilibili.com/video/BV1pt41127FZ
  • https://www.runoob.com/go/go-tutorial.html
  • https://studygolang.com/pkgdoc