golang 整型、位运算、浮点数、布尔型、字符串、常量

整型

Go语言同时提供了有符号和无符号类型的整数运算:
1. int8、 int16、 int32和int64四种有符号整数类型,分别对应8、 16、 32、 64bit大小的有符号整数
2. uint8、 uint16、 uint32和uint64四种无符号整数类型
3. int和uint,这两种是对应特定CPU平台机器字大小的有符号和无符号整数;

其中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位。数的值域为-2^{n-1} 到 2^{n-1}-1; 无符号整数的所有bit位都用于表示非负数,值域是0到2^{n}-1。例如: int8类型整数的值域是从-128到127(-2^7, 2^7 - 1)而uint8类型整数的值域是从0到255(0, 2^8 - 1)。


在Go语言中, %取模运算符的符号和被取模数的符号总是一致的, 因此 -5%3 和 -5%-3 结果都是-2。 除法运算符 / 的行为则依赖于操作数是否为全为整数, 比如 5.0/4.0 的结果是1.25, 但是5/4的结果是1, 因为整数除法会向着0方向截断余数。

位运算

& 位运算 AND            都为1才是1
| 位运算 OR             只要有一个是1 那么就是1
^ 位运算 XOR            有一个为1则就是1;两个1 或者两个0则全是 0
&^ 位清空 (AND NOT)     a &^ b  =  (a^b) & b 
<< 左移                 可以看做是 乘以2的几次方
>> 右移                 可以看做是 除以2的几次方
func main()  {
    var x uint8 = 1 << 1 | 1 << 5   //00100010
    var y uint8 = 1 << 1 | 1 << 2   //00000110

    fmt.Printf("%08b \n", x)
    fmt.Printf("%08b \n", y)


    fmt.Printf("%08b \n", x&y)  //00000010
    fmt.Printf("%08b \n", x|y)  //00100110
    fmt.Printf("%08b \n", x^y)  //00100100 
    fmt.Printf("%08b \n", x&^y) //00100000
}
在 x<<nx>>n 移位运算中, 决定了移位操作bit数部分必须是无符号数; 算术上, 一个 x<<n 左移运算等价于乘以2^n, 一个 x>>n 右移运算等价于除以2^n。

func main()  {
    x := int64(0xdeadbeef)
    fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
}

请注意fmt的两个使用技巧:
1. %之后的 [1] 副词告诉Printf函数再次使用第一个操作数
2. %后的 # 副词告诉Printf在用%o、%x或%X输出时生成0、 0x或0X前缀

浮点数

Go语言提供了两种精度的浮点数, float32和float64。一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度; 通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散。

很小或很大的数最好用科学计数法书写, 通过e或E来指定指数部分:

const Avogadro = 6.02214129e23 // 阿伏伽德罗常数
const Planck = 6.62606957e-34  // 普朗克常数
func main() {
    fmt.Printf("%f\n", Avogadro)
    fmt.Printf("%f\n", Planck)
}

正无穷大和负无穷大, 分别用于表示太大溢出的数字和除零的结果:

func main() {
    var z float64
    fmt.Println(1/z) //+Inf
    fmt.Println(-1/z) //-Inf
}

复数

Go语言提供了两种精度的复数类型: complex64和complex128, 分别对应float32和float64两种浮点数精度。

布尔型

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

布尔值可以和&&( AND) 和||( OR)操作符结合,并且有短路行为:如果运算符左边值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值。

因为 && 的优先级比 || 高( 助记: && 对应逻辑乘法, || 对应逻辑加法,乘法比加法优先级要高),下面形式的布尔表达式是不需要加小括弧:

if 'a' <= c && c <= 'z' ||
    'A' <= c && c <= 'Z' ||
    '0' <= c && c <= '9' {
    // ...ASCII letter or digit...
}

字符串

一个字符串是一个不可改变的字节序列。文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列内置。

len函数可以返回一个字符串中的字节数目,索引操作s[i]返回第i个字节的字节值。子字符串操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节( 并不包含j本身) 生成一个新字符串。 生成的新字符串将包含j-i个字节。

Unicode、UTF-8

它收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应GO语言中的rune(int32等价)整数类型。

UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码使用1到4个字节来表示每个Unicode码点。 ASCII部分字符只使用1个字节, 常用字符部分使用2或3个字节表示。

每个符号编码后第一个字节的高端bit位用于表示总共有多少编码个字节。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符。如果第一个字节的高端bit为0, 则表示对应7bit的ASCII字符;如果第一个字节的高端bit是110, 则说明需要2个字节;后续的每个高端bit都以10开头。

0xxxxxxx                            runes 0-127 (ASCII)
110xxxxx 10xxxxxx                   128-2047 
1110xxxx 10xxxxxx 10xxxxxx          2048-65535 
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff

GO语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符,有两种形式:
1. \uhhhh对应16bit的码点值
2. \Uhhhhhhhh对应32bit的码点值
其中h是一个十六进制的某个数字,一般很少需要使用32bit码点值。下面的字符串面值表示相同值:

"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"

处理中文+英文的字符串

func main() {
    s := "hello, 世界"
    fmt.Println(len(s)) //13
    fmt.Println(utf8.RuneCountInString(s)) //9
}

字符串包含13个字节, 以UTF8形式编码, 但是只对应9个Unicode字符。为了处理这些真实的字符, 我们需要一个UTF8解码器:

func main() {
    s := "hello, 世界"

    for i :=0; i< len(s);{
        r, size := utf8.DecodeRuneInString(s[i:])
        fmt.Printf("%c \t", r)
        i += size
    }
}

每一次调用DecodeRuneInString函数都返回一个r和size, r对应字符本身, siez是r采用UTF8编码后的编码字节长度。

下面是对上面代码的优化, Go语言的range循环在处理字符串的时候, 会自动隐式解码UTF8字符串:

func main() {
    s := "hello, 世界"

    for _, r := range s{
        fmt.Printf("%c \t", r)
    }
}

rune类型

UTF8字符串作为交换格式是非常方便的, 但是在程序内部采用rune序列可能更方便,因为rune大小一致, 支持数组索引和方便切割。
将[]rune类型转换应用到UTF8编码的字符串, 将返回字符串编码的Unicode码点序列。

func main() {
    s := "你好, 世界"
    r := []rune(s)
    fmt.Printf("%x \n", r) //[4f60 597d 2c 20 4e16 754c] 
}

如果是将一个[]rune类型的Unicode字符slice或数组转为string, 则对它们进行UTF8编码:

fmt.Println(string(r))

将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串:

fmt.Println(string(65)) // "A", not "65"
fmt.Println(string(0x4eac)) // "京"

常量

常量表达式的值在编译期计算, 而不是在运行期。

批量声明多个常量:

const (
    e = 2.71828182845904523536028747135266249775724709369995957496696763
    pi = 3.14159265358979323846264338327950288419716939937510582097494459
)

一个常量的声明也可以包含一个类型和一个值, 但是如果没有显式指明类型, 那么将从右边的表达式推断类型。

const noDelay time.Duration = 0

如果是批量声明的常量, 除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式, 对应的常量类型也一样的。

const (
    a = 1
    b 
    c = 2
    d
)

iota 常量生成器

它用于生成一组以相似规则初始化的常量, 但是不用每行都写一遍初始化表达式。 在一个const声明语句中, 在第一个声明的常量所在的行,
iota将会被置为0, 然后在每一个有常量声明的行加一。

下面是来自time包的例子:

type Weekday int
const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

func main() {
    fmt.Println(Sunday, Monday, Tuesday, Saturday)
}

它首先定义了一个Weekday命名类型, 然后为一周的每天定义了一个常量, 从周日0开始。这种类型一般被称为枚举类型。


下面是一个更复杂的例子, 每个常量都是1024的幂:

const (
    _ = 1 << (10 * iota)
    KiB
    MiB
    GiB
    TiB
)

func main() {
    fmt.Println(KiB, MiB, GiB, TiB)
}

猜你喜欢

转载自blog.csdn.net/andybegin/article/details/80942270