语言元素
指令和程序
计算机的硬件系统通常由五大部件构成,包括:运算器、控制器、存储器、输入设备和输出设备。其中,运算器和控制器放在一起就是我们通常所说的中央处理器,它的功能是执行各种运算和控制指令以及处理计算机软件中的数据。我们通常所说的程序实际上就是指令的集合,我们程序就是将一系列的指令按照某种方式组织到一起,然后通过这些指令去控制计算机做我们想让它做的事情。今天我们大多数时候使用的计算机,虽然它们的元器件做工越来越精密,处理能力越来越强大,但究其本质来说仍然属于“冯·诺依曼结构”的计算机。“冯·诺依曼结构”有两个关键点,一是指出要将存储设备与中央处理器分开,二是提出了将数据以二进制方式编码。二进制是一种“逢二进一”的计数法,跟我们人类使用的“逢十进一”的计数法没有实质性的区别,人类因为有十根手指所以使用了十进制(因为在数数时十根手指用完之后就只能进位了,当然凡事都有例外,玛雅人可能是因为长年光着脚的原因把脚趾头也算上了,于是他们使用了二十进制的计数法,在这种计数法的指导下玛雅人的历法就与我们平常使用的历法不一样,而按照玛雅人的历法,2012年是上一个所谓的“太阳纪”的最后一年,而2013年则是新的“太阳纪”的开始,后来这件事情被以讹传讹的方式误传为”2012年是玛雅人预言的世界末日“这种荒诞的说法,今天我们可以大胆的猜测,玛雅文明之所以发展缓慢估计也与使用了二十进制有关)。对于计算机来说,二进制在物理器件上来说是最容易实现的(高电压表示1,低电压表示0),于是在“冯·诺依曼结构”的计算机都使用了二进制。虽然我们并不需要每个程序员都能够使用二进制的思维方式来工作,但是了解二进制以及它与我们生活中的十进制之间的转换关系,以及二进制与八进制和十六进制的转换关系还是有必要的。如果你对这一点不熟悉,可以自行使用维基百科或者百度百科科普一下。
变量和类型
在程序设计中,变量是一种存储数据的载体。计算机中的变量是实际存在的数据或者说是存储器中存储数据的一块内存空间,变量的值可以被读取和修改,这是所有计算和控制的基础。计算机能处理的数据有很多种类型,除了数值之外还可以处理文本、图形、音频、视频等各种各样的数据,那么不同的数据就需要定义不同的存储类型。golang中的数据类型很多,而且也允许自定义新的数据类型(许多编程语言都有这一功能),我们先介绍几种常用的数据类型。
- 整型:golang中的整型有多种,不同种类拥有不同的取值范围。
- int8:-128 到 127
- uint8(byte):0 到 255
- int16:-32768 到 32767
- uint16:0 到 65535
- int32(rune):-2147483648 到 2147483647
- uint32:0 到 4294967295
- int64:-9223372036854775808 到 9223372036854775807
- uint64:0 到 18446744073709551615
- int:这是根据平台而定的,32位系统上通常是int32,64位系统上通常是int64
- uint:这也是根据平台而定的,32位系统上通常是uint32,64位系统上通常是uint64
- uintptr:这是一个无符号整型,其大小足以容纳一个指针
- 浮点型:浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如
123.456
)之外还支持科学计数法(如1.23456e2
)。golang分为2种类型- float32:大约 ±1.18e-38 到 ±3.4e+38
- float64:大约 ±2.23e-308 到 ±1.8e+308
- 字符串型:字符串是以单引号或双引号括起来的任意文本,比如
'hello'
和"hello"
,字符串还有原始字符串表示法、字节字符串表示法、Unicode字符串表示法,而且可以书写成多行的形式(用三个单引号或三个双引号开头,三个单引号或三个双引号结尾)。 - 布尔型:布尔值只有
True
、False
两种值,要么是True
,要么是False
,在golang中,通过bool
来声明(请注意大小写),也可以通过布尔运算计算出来(例如3 < 5
会产生布尔值True
,而2 == 1
会产生布尔值False
)。 - 字符串型:形如“test”,表示一个字符串,用
string
来声明。
变量命名
对于每个变量我们需要给它取一个名字,就如同我们每个人都有属于自己的响亮的名字一样。在多数编程语言中,变量命名需要遵循以下这些必须遵守硬性规则和强烈建议遵守的非硬性规则。
- 硬性规则:
- 变量名由字母(广义的Unicode字符,不包括特殊字符)、数字和下划线构成,数字不能开头。
- 大小写敏感(大写的
a
和小写的A
是两个不同的变量)。 - 不要跟关键字(有特殊含义的单词,后面会讲到)和系统保留字(如函数、模块等的名字)冲突。
- 避免未使用变量的出现,golang要求每个变量都被使用
- 建议遵守要求:
- 用小写字母拼写,多个单词用下划线连接。
- 私有的实例属性用下划线开头(后面会讲到)。
当然,作为一个专业的程序员,给变量(事实上应该是所有的标识符)命名时做到见名知意也是非常重要的。
变量定义
golang的变量定义有多种形式
- 使用
var
关键字,标准的变量定义方式,适用于任何作用域 - 简短声明,简短声明使用
:=
运算符,它可以在声明变量的同时为其赋予初始值,仅适用于函数内部。编译器会自动推断变量类型 - 使用
new
函数,new
是golang语言的内建函数,它用于分配内存并返回指向该内存的指针,主要用于创建并初始化指针类型变量
var x int = 10 // 定义并初始化整型变量 x
var y string // 定义字符串变量 y,未初始化
var z bool = true // 定义并初始化布尔型变量 z
func main() {
s := "Hello, Go!" // 定义并初始化字符串变量 s
n := 42 // 定义并初始化整型变量 n
// 在同一作用域内重复使用 := 会报错,除非变量之前未声明
s2 := "Another string" // 如果之前没有声明过 s2,则此处合法
p := new(int) // 创建一个指向整型零值(0)的指针
fmt.Println(*p) // 输出:0
s3 := new(string) // 创建一个指向字符串零值("")的指针
fmt.Println(*s3) // 输出: ""
}
变量的使用
下面通过几个例子来说明变量的类型和变量使用。
package main
import(
fmt
)
/*
使用变量保存数据并进行加减乘除运算
Version: 1.0
Author: 长柳
*/
a := 300
b := 10
fmt.Println(a + b) // 310
fmt.Println(a - b) // 290
fmt.Println(a * b) // 3000
fmt.Println(a / b) // 30
在golang中可以使用%T
或者函数reflect.TypeOf
对变量的类型进行检查。程序设计中函数的概念跟数学上函数的概念是一致的,数学上的函数相信大家并不陌生,它包括了函数名、自变量和因变量。如果暂时不理解这个概念也不要紧,我们会在后续的章节中专门讲解函数的定义和使用。
/*
使用type()检查变量的类型
Version: 0.1
Author: charles
*/
a := 100
b := 12.345
c := "Hello, world"
d := True
fmt.Println("Type of myVar is: %T", a) //int
fmt.Println("Type of myVar is: %T", b) //float
fmt.Println("Type of myVar is: %T", c) //str
fmt.Println("Type of myVar is: %T", d) //bool
说明:Println中使用了格式化符
T%
,还有占位符的概念,不过Println并不支持占位符,占位符下面会介绍
可以使用golang中内置的函数对变量类型进行转换。
int()
:将一个数值或字符串转换成整数。string()
: 转换为字符串float32()/float64()
:将一个字符串转换成浮点数。[]byte()
:将字符串转换为字节切片(一个字符)。
下面的代码通过键盘输入两个整数来实现对两个整数的算术运算。
/*
使用fmt.Scan()函数获取键盘输入(字符串)
使用int()函数将输入的字符串转换成整数
Version: 1.0
Author: 长柳
*/
var a int
var b string
fmt.Scan(&a, &b) // 读取整数和字符串,如 "123 hello"
fmt.Sprintf("a+b is d%",a+int(b))
说明:上面的Sprintf函数中输出的字符串使用了占位符语法,其中
%d
是整数的占位符,还有%f
是小数的占位符,字符串之后的%
后面跟的变量值会替换掉占位符然后输出到终端中,运行上面的程序,看看程序执行结果就明白啦。
运算符
golang支持多种运算符,下表大致按照优先级从高到低的顺序列出了所有的运算符,运算符的优先级指的是多个运算符同时出现时,先做什么运算然后再做什么运算。除了我们之前已经用过的赋值运算符和算术运算符,我们稍后会陆续讲到其他运算符的使用。
运算符 | 描述 |
---|---|
[] [:] | 下标,切片 |
** | 指数 |
~ + - | 按位取反, 正负号 |
* / % // | 乘,除,模,整除 |
+ - | 加,减 |
>> << | 右移,左移 |
& | 按位与 |
^ | | 按位异或,按位或 |
<= < > >= | 小于等于,小于,大于,大于等于 |
== != | 等于,不等于 |
&& || ^ | 逻辑运算符 |
= += -= *= /= %= //= **= &= |= ^= >>= <<= | (复合)赋值运算符 |
说明: 在实际开发中,如果搞不清楚运算符的优先级,可以使用括号来确保运算的执行顺序。
赋值运算符
赋值运算符应该是最为常见的运算符,它的作用是将右边的值赋给左边的变量。下面的例子演示了赋值运算符和复合赋值运算符的使用。
/*
赋值运算符和复合赋值运算符
Version: 1.0
Author: 长柳
*/
a := 10
b := 3
a += b //相当于:a = a + b
a *= a + 2 //相当于:a = a * (a + 2)
fmt.Println(a) //算一下这里会输出什么
比较运算符和逻辑运算符
比较运算符有的地方也称为关系运算符,包括==
、!=
、<
、>
、<=
、>=
,我相信没有什么好解释的,大家一看就能懂,唯一需要提醒的是比较相等用的是==
,请注意这个地方是两个等号,因为=
是赋值运算符,我们在上面刚刚讲到过,==
才是比较相等的比较运算符。比较运算符会产生布尔值,要么是True
要么是False
。
逻辑运算符有三个,分别是&&
(and)、||
(or)和^
(not)。and
字面意思是“而且”,所以&&
运算符会连接两个布尔值,如果两个布尔值都是True
,那么运算的结果就是True
;左右两边的布尔值有一个是False
,最终的运算结果就是False
。相信大家已经想到了,如果&&
左边的布尔值是False
,不管右边的布尔值是什么,最终的结果都是False
,所以在做运算的时候右边的值会被跳过(短路处理),这也就意味着在&&
运算符左边为False
的情况下,右边的表达式根本不会执行。||
字面意思是“或者”,所以||
运算符也会连接两个布尔值,如果两个布尔值有任意一个是True
,那么最终的结果就是True
。当然,||
运算符也是有短路功能的,在它左边的布尔值为True
的情况下,右边的表达式根本不会执行。^
运算符的后面会跟上一个布尔值,它的作用是得到与该布尔值相反的值,也就是说,后面的布尔值如果是True
运算结果就是False
,而后面的布尔值如果是False
则运算结果就是True
。
/*
比较运算符和逻辑运算符的使用
Version: 1.0
Author: 长柳
*/
flag0 := 1 == 1
flag1 := 3 > 2
flag2 := 2 < 1
flag3 := flag1 && flag2
flag4 := flag1 || flag2
flag5 := !(1 != 2)
fmt.Println("flag0 =", flag0) // flag0 = True
fmt.Println("flag1 =", flag1) // flag1 = True
fmt.Println("flag2 =", flag2) // flag2 = False
fmt.Println("flag3 =", flag3) // flag3 = False
fmt.Println("flag4 =", flag4) // flag4 = True
fmt.Println("flag5 =", flag5) // flag5 = False
说明:比较运算符的优先级高于赋值运算符,所以
flag0 = 1 == 1
先做1 == 1
产生布尔值True
,再将这个值赋值给变量flag0
。Println
函数可以输出多个值,多个值之间可以用,
进行分隔,输出的内容之间默认以空格分开。
练习
练习1:华氏温度转换为摄氏温度。
提示:华氏温度到摄氏温度的转换公式为: C = ( F − 32 ) ÷ 1.8 C=(F - 32) \div 1.8 C=(F−32)÷1.8。F为华氏,C为摄氏
参考答案:
/*
将华氏温度转换为摄氏温度
Version: 1.0
Author: 长柳
*/
var f float64
fmt.Scan(&f)
c := (f - 32) / 1.8
fmt.Sprintf("%.1f华氏度 = %.1f摄氏度" f,c)
说明:在使用
Sprintf
函数输出时,也可以对字符串内容进行格式化处理,上面Sprintf
函数中的字符串%.1f
是一个占位符,稍后会由一个float
类型的变量值替换掉它。同理,如果字符串中有%d
,后面可以用一个int
类型的变量值替换掉它,而%s
会被字符串的值替换掉。
练习2:输入年份判断是不是奥运年。
示例:
/*
输入年份 如果是奥运年输出True 否则输出False
Version: 0.1
Author: 骆昊
*/
var year int
fmt.Scan(&year)
var is_aoyun bool
/*
在此处写答案
is_aoyun = (year%4==0)
*/
is_aoyun =
print(is_leap)
说明:比较运算符会产生布尔值,而逻辑运算符
and
和or
会对这些布尔值进行组合,最终也是得到一个布尔值,闰年输出True
,平年输出False
。