内容简介:install B 时刻:Go本质就是用C语言编写的一门高级编程语言所以江哥前面教你C语言就是为了今天能让你看懂Go的实现代码,做到知其然知其所以然
Go语言数据类型
- C语言各数据类型占用内存空间
类型 | 32位编译器 | 64位编译器 |
---|---|---|
char | 1 | 1 |
int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
short | 2 | 2 |
long | 4 | 8 |
long long | 8 | 8 |
void* | 4 | 8 |
- Go语言各数据类型占用内存空间
类型 | 32位编译器 | 64位编译器 | 本质 |
---|---|---|---|
int8/uint8 | 1 | 1 | signed char/unsigned char |
int16/uint16 | 2 | 2 | signed short/unsigned short |
int32/uint32 | 4 | 4 | signed int/unsigned int |
int64/uint64 | 8 | 8 | signed long long int/unsigned long long int |
byte | 1 | 1 | uint8/unsigned char |
rune | 4 | 4 | int32/signed int |
int | 4 | 8 | 根据机器位数决定长度 |
uintptr | 4 | 8 | 根据机器位数决定长度 uint32/uint64 |
float32 | 4 | 4 | float |
float64 | 8 | 8 | double |
true | 1 | 1 | char类型的整型 |
false | 1 | 1 | char类型的整型 |
- 和C语言一样,Go语言也提供了Sizeof计算变量的内存空间
- 1.导入import "unsafe"包
- 2.通过unsafe.Sizeof()计算变量内存空间
package main import ( "fmt" "unsafe" ) func main() { fmt.Println("int size = ", unsafe.Sizeof(int(0))) fmt.Println("int8 size = ", unsafe.Sizeof(int8(0))) fmt.Println("int16 size = ", unsafe.Sizeof(int16(0))) fmt.Println("int32 size = ", unsafe.Sizeof(int32(0))) fmt.Println("int64 size = ", unsafe.Sizeof(int64(0))) fmt.Println("uint size = ", unsafe.Sizeof(uint(0))) fmt.Println("uint8 size = ", unsafe.Sizeof(uint8(0))) fmt.Println("uint16 size = ", unsafe.Sizeof(uint16(0))) fmt.Println("uint32 size = ", unsafe.Sizeof(uint32(0))) fmt.Println("uint64 size = ", unsafe.Sizeof(uint64(0))) fmt.Println("uintptr size = ", unsafe.Sizeof(uintptr(0))) fmt.Println("byte size = ", unsafe.Sizeof(byte(0))) fmt.Println("rune size = ", unsafe.Sizeof(rune(0))) fmt.Println("float32 size = ", unsafe.Sizeof(float32(0))) fmt.Println("float64 size = ", unsafe.Sizeof(float64(0))) fmt.Println("true size = ", unsafe.Sizeof(true)) fmt.Println("false size = ", unsafe.Sizeof(false)) }
- Go语言基本数据类型内部实现
- golang官方网站下载go1.4版本源代码
- 越老版本的代码越纯粹,越适合新手学习
- 随着代码的更新迭代会逐步变得非常复杂, 所以此处建议下载1.4版本
- 解压后打开路径:
go\src\runtime\runtime.h
-
- 得到如下实现代码
- golang官方网站下载go1.4版本源代码
// 第8行到35行 typedef signed char int8; typedef unsigned char uint8; typedef signed short int16; typedef unsigned short uint16; typedef signed int int32; typedef unsigned int uint32; typedef signed long long int int64; typedef unsigned long long int uint64; typedef float float32; typedef double float64; #ifdef _64BIT typedef uint64 uintptr; typedef int64 intptr; typedef int64 intgo; // Go's int typedef uint64 uintgo; // Go's uint #else typedef uint32 uintptr; typedef int32 intptr; typedef int32 intgo; // Go's int typedef uint32 uintgo; // Go's uint #endif #ifdef _64BITREG typedef uint64 uintreg; #else typedef uint32 uintreg; #endif // 第153行到157行 enum { true = 1, false = 0, };
install B 时刻:
Go本质就是用C语言编写的一门高级编程语言
所以江哥前面教你C语言就是为了今天能让你看懂Go的实现代码,做到知其然知其所以然
注意点: 企业开发中一般使用int, 因为int会根据你当前的操作系统自动转换为int32和int64
Go语言变量
- Go语言中变量的概念和C语言中也一样, 所以我们直接来看下如何定义和使用变量即可
- C语言中定义变量的格式
数据类型 变量名称; 数据类型 变量名称1, 变量名称2;
#include <stdio.h> int main(int argc, const char * argv[]) { int num1; // 先定义 num1 = 10; // 后初始化 printf("num1 = %d\n", num1); int num2 = 20; // 定义的同时初始化 printf("num2 = %d\n", num2); // 注意: 同时定义多个变量,不支持定义时初始化, 只能先定义后初始化 int num3, num4; //同时定义多个变量 num3 = 30; num4 = 40; printf("num3 = %d\n", num3); printf("num4 = %d\n", num4); return 0; }
- Go语言中定义变量有三种格式
// 标准格式 var 变量名称 数据类型 = 值; // 自动推到类型格式 var 变量名称 = 值; // 简短格式(golang官方推荐格式) 变量名称 := 值;
package main import "fmt" func main() { var num1 int // 先定义 num1 = 10 // 后赋值 fmt.Println("num1 = ", num1) var num2 int = 20 // 定义的同时赋值 fmt.Println("num2 = ", num2) var num3 = 30 // 定义的同时赋值, 并省略数据类型 fmt.Println("num3 = ", num3) num4 := 40 // 定义的同时赋值, 并省略关键字和数据类型 /* num4 := 40 等价于 var num4 int num4 = 40 */ fmt.Println("num4 = ", num4) }
- 和C语言一样,除了可以定义单个变量以外,还支持一次性定义多个变量
- 方式一, 连续定义
package main import "fmt" func main() { var num1, num2 int // 先定义 num1 = 10 // 后赋值 num2 = 20 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) var num3, num4 int = 30, 40 // 定义的同时赋值 fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) var num5, num6 = 50, 60 // 定义的同时赋值, 并省略数据类型 fmt.Println("num5 = ", num5) fmt.Println("num6 = ", num6) num7, num8 := 70, 80 // 定义的同时赋值, 并省略关键字和数据类型 fmt.Println("num7 = ", num7) fmt.Println("num8 = ", num8) }
- 方式二, 变量组
package main import "fmt" func main() { var( // 先定义 num1 int num2 float32 ) num1 = 10 // 后赋值 num2 = 3.14 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) var( // 定义的同时赋值 num3 int = 30 num4 float32 = 6.66 ) fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) var( // 定义的同时赋值, 并省略数据类型 num5 = 50 num6 = 7.77 ) fmt.Println("num5 = ", num5) fmt.Println("num6 = ", num6) var( // 一行定义多个 num7, num8 = 70, 80 num9, num10 = 9.99, 100 ) fmt.Println("num7 = ", num7) fmt.Println("num8 = ", num8) fmt.Println("num9 = ", num9) fmt.Println("num10 = ", num10) }
Go语言变量定义注意点
- 简短模式的含义是定义的同时初始化
package main import "fmt" func main() { num := 10 num := 20 // 编译报错, 重复定义 fmt.Println("num = ", num) }
- 一定不要把:=当做赋值运算符来使用
package main import "fmt" var num = 10 // 定义一个全局变量 func main() { num := 20 // 定义一个局部变量 fmt.Println("num = ", num) test() } func test() { fmt.Println("num = ", num) // 还是输出10 }
- :=只能用于定义局部变量,不能用于定义全局变量
package main import "fmt" num := 10 // 编译报错 func main() { fmt.Println("num = ", num) }
- 使用:=定义变量时,不能指定var关键字和数据类型
package main import "fmt" func main() { //var num int := 10 // 编译报错 //var num := 10 // 编译报错 num int := 10 // 编译报错 fmt.Println("num = ", num) fmt.Println("num = ", num) }
- 变量组中不能够使用:=
package main import "fmt" func main() { var( num := 10 // 编译报错 ) fmt.Println("num = ", num) }
- 通过:=同时定义多个变量, 必须给所有变量初始化
package main import "fmt" func main() { //num1, num2 := 666, 888 // 正确 num1, num2 := 666 // 报错 fmt.Printf("%d, %d\n", num1, num2) }
- 通过:=同时定义多个变量, 只要任意一个变量没有定义过,都会做退化赋值操作
package main import "fmt" func main() { // 定义一个变量num1 num1 := 10 // 同时定义两个变量num1和num2, 由于num2从来没有定义过, // 所以对于num1来说:=退化为赋值运算符, 而对于num2来说:=仍然是定义+赋值 num1, num2 := 20, 30 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) }
package main import "fmt" func main() { num1 := 10 num2 := 20 // 报错, 因为num1,和num2都已经被定义过 // 至少要有任意一个变量没有被定义过,才会退化赋值 num1, num2 := 30, 40 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) }
- 定义的局部变量或者导入的包没有被使用, 那么编译器会报错,无法编译运行,但是定义的全局变量没有被使用,编译器不会报错, 可以编译运行
局部变量和全局变量
-
和C语言一样,按照变量的作用域,我们可以把变量划分为局部变量和全局变量
-
Go语言中局部变量的概念以及全局变量的概念和C语言一模一样
-
局部变量:
- 定义在函数内部的变量以及函数的形参称为局部变量
- 作用域:从定义哪一行开始直到与其所在的代码块结束
- 生命周期:从程序运行到定义哪一行开始分配存储空间到程序离开该变量所在的作用域
-
全局变量:
- 定义在函数外面的变量称为全局变量
- 作用域范围:从定义哪行开始直到文件结尾
- 生命周期:程序一启动就会分配存储空间,直到程序结束
-
和C语言不同的是, C语言中可以定义相同名称的全局变量, 而Go语言中无论全局变量还是局部变量, 只要作用域相同都不能出现同名的变量
package main import "fmt" //var num1 int //var num1 int // 报错, 重复定义 var num3 int func main() { //var num2 //var num2 // 报错, 重复定义 var num3 int // 不报错, 因为作用域不同 fmt.Println("num3 = ", num3) }
- C语言中全局变量没有赋值,那么默认初始值为0, 局部变量没有赋值,那么默认初始值是随机值
- Go语言中无论是全局变量还是局部变量,只要定义了一个变量都有默认的0值
- int/int8/int16/int32/int64/uint/uint8/uint16/uint32/uint64/byte/rune/uintptr的默认值是0
+float32/float64的默认值是0.0 - bool的默认值是false
- string的默认值是""
- pointer/function/interface/slice/channel/map/error的默认值是nil
- 其它复合类型array/struct默认值是内部数据类型的默认值
- int/int8/int16/int32/int64/uint/uint8/uint16/uint32/uint64/byte/rune/uintptr的默认值是0
package main import "fmt" func main() { var intV int // 整型变量 var floatV float32 // 实型变量 var boolV bool // 布尔型变量 var stringV string // 字符串变量 var pointerV *int // 指针变量 var funcV func(int, int)int // function变量 var interfaceV interface{} // 接口变量 var sliceV []int // 切片变量 var channelV chan int // channel变量 var mapV map[string]string // map变量 var errorV error // error变量 fmt.Println("int = ", intV) // 0 fmt.Println("float = ", floatV) // 0 fmt.Println("bool = ", boolV) // false fmt.Println("string = ", stringV) // "" fmt.Println("pointer = ", pointerV) // nil fmt.Println("func = ", funcV) // nil fmt.Println("interface = ", interfaceV) // nil fmt.Println("slice = ", sliceV) // [] fmt.Println("slice = ", sliceV == nil) // true fmt.Println("channel = ", channelV) // nil fmt.Println("map = ", mapV) // map[] fmt.Println("map = ", mapV == nil) // true fmt.Println("error = ", errorV) // nil var arraryV [3]int // 数组变量 type Person struct{ name string age int } var structV Person // 结构体变量 fmt.Println("arrary = ", arraryV) // [0, 0, 0] fmt.Println("struct = ", structV) // {"" 0} }
数据类型转换
只能显示转换
#include <stdio.h> int main(){ // 隐式转换:自动将实型10.6转换为整型后保存 int a = 10.6; // 自动类型提升: 运算时会自动将小类型转换为大类型后运算 double b = 1.0 / 2; // 等价于1.0 / 2.0 }
- C语言显示转换(强制转换)
#include <stdio.h> int main(){ // 显示转换:强制将实型10.6转换为整型后保存 int a = (int)10.5; }
- Go语言数值类型之间转换
数据类型(需要转换的数据)
package main import "fmt" func main() { var num0 int = 10 var num1 int8 = 20 var num2 int16 //num2 = num0 // 编译报错, 不同长度的int之间也需要显示转换 //num2 = num1 // 编译报错, 不同长度的int之间也需要显示转换 num2 = int16(num0) num2 = int16(num1) fmt.Println(num2) var num3 float32 = 3.14 var num4 float64 //num4 = num3 // 编译报错, 不同长度的float之间也需要显示转换 num4 = float64(num3) fmt.Println(num4) var num5 byte = 11 var num6 uint8 // 这里不是隐式转换, 不报错的原因是byte的本质就是uint8 num6 = num5 fmt.Println(num6) var num7 rune = 11 var num8 int32 num8 = num7 // 这里不是隐式转换, 不报错的原因是byte的本质就是int32 fmt.Println(num8) }
数值类型和字符串类型之间转换
- Go语言中不能通过 数据类型(变量)的格式将数值类型转换为字符串, 也不能通过 数据类型(变量)的格式将字符串转换为数值类型
package main import "fmt" func main() { var num1 int32 = 65 // 可以将整型强制转换, 但是会按照ASCII码表来转换 // 但是不推荐这样使用 var str1 string = string(num1) fmt.Println(str1) var num2 float32 = 3.14 // 不能将其它基本类型强制转换为字符串类型 var str2 string = string(num2) fmt.Println(str2) var str3 string = "97" // 不能强制转换, cannot convert str2 (type string) to type int var num3 int = int(str3) fmt.Println(num3) }
- 数值类型转字符串类型
strconv..FormatXxx()
package main import "fmt" func main() { var num1 int32 = 10 // 第一个参数: 需要被转换的整型,必须是int64类型 // 第二个参数: 转换为几进制, 必须在2到36之间 // 将32位十进制整型变量10转换为字符串,并继续保留10进制格式 str1 := strconv.FormatInt(int64(num1), 10) fmt.Println(str1) // 10 // 将32位十进制整型变量10转换为字符串,并转换为2进制格式 str2 := strconv.FormatInt(int64(num1), 2) fmt.Println(str2) // 1010 var num5 float64 = 3.1234567890123456789 // 第一个参数: 需要转换的实型, 必须是float64类型 // 第二个参数: 转换为什么格式,f小数格式, e指数格式 // 第三个参数: 转换之后保留多少位小数, 传入-1按照指定类型有效位保留 // 第四个参数: 被转换数据的实际位数,float32就传32, float64就传64 // 将float64位实型,按照小数格式并保留默认有效位转换为字符串 str3 := strconv.FormatFloat(num5, 'f', -1, 64) fmt.Println(str3) // 3.1234567 str4 := strconv.FormatFloat(num5, 'f', -1, 64) fmt.Println(str4) // 3.1234567890123457 // 将float64位实型,按照小数格式并保留2位有效位转换为字符串 str5 := strconv.FormatFloat(num5, 'f', 2, 64) fmt.Println(str5) // 3.12 // 将float64位实型,按照指数格式并保留2位有效位转换为字符串 str6 := strconv.FormatFloat(num5, 'e', 2, 64) fmt.Println(str6) // 3.12 var num6 bool = true str7 := strconv.FormatBool(num6) fmt.Println(str7) // true }
- 字符串类型转数值类型
strconv.ParseXxx()
package main import "fmt" func main() { var str1 string = "125" // 第一个参数: 需要转换的数据 // 第二个参数: 转换为几进制 // 第三个参数: 转换为多少位整型 // 注意点: ParseInt函数会返回两个值, 一个是转换后的结果, 一个是错误 // 如果被转换的数据转换之后没有超出指定的范围或者不能被转换时, // 那么错误为nil, 否则错误不为nil // 将字符串"125"转换为10进制的int8 num1, err := strconv.ParseInt(str1, 10, 8) if err != nil { fmt.Println(err) } fmt.Println(num1) var str2 string = "150" // 将字符串"150"转换为10进制的int8 // 由于int8的取值范围是-128~127, 所以转换之后超出了指定的范围, error不为nil num2, err := strconv.ParseInt(str2, 10, 8) if err != nil { fmt.Println(err) } fmt.Println(num2) var str3 string = "3.1234567890123456789" // 第一个参数: 需要转换的数据 // 第二个参数: 转换为多少位小数, 32 or 64 // ParseFloat同样有两个返回值, 如果能够正常转换则错误为nil, 否则不为nil num3, err := strconv.ParseFloat(str3, 32) if err != nil { // 例如: 把字符串"3.14abc"转换为小数就会报错, 因为"3.14abc"不是一个小数 fmt.Println(err) } fmt.Println(num3) var str4 string = "true" // 第一个参数: 需要转换的数据 // ParseBool同样有两个返回值, 如果能够正常转换则错误为nil, 否则不为nil num4, _ := strconv.ParseBool(str4) fmt.Println(num4) }
- 字符串类型转换为数值类型时,如果不能转换除了返回error以外,还会返回对应类型的默认值
package main import "fmt" func main() { var str1 string = "abc" num1, _ := strconv.ParseInt(str1, 10, 32) fmt.Println(num1) // 0 num2, _ := strconv.ParseFloat(str1, 32) fmt.Println(num2) // 0 num3, _ := strconv.ParseBool(str1) fmt.Println(num3) // false }
- 看完上面的代码有没有种想打人的感觉? 如果有那么请继续往下看
- 字符串类型和整型快速转换
package main import "fmt" func main() { var num1 int32 = 110 // 快速将整型转换为字符串类型 // 注意:Itoa方法只能接受int类型 var str1 string = strconv.Itoa(int(num1)) fmt.Println(str1) var str2 string = "666" // 快速将字符串类型转换为整型 // 注意: Atoi方法返回两个值, 一个值是int,一个值是error // 如果字符串能被转换为int,那么error为nil, 否则不为nil num2, err := strconv.Atoi(str2) if err != nil{ fmt.Println(err) } fmt.Println(num2) }
- 数值类型转字符串类型其它方式
package main import "fmt" func main() { var num1 int32 = 110 // Sprintf函数和Printf函数很像, 只不过不是输出而将格式化的字符串返回给我们 var str1 string = fmt.Sprintf("%d", num1) fmt.Println(str1) var num2 float32 = 3.14 var str2 string = fmt.Sprintf("%f", num2) fmt.Println(str2) var num3 bool = true var str3 string = fmt.Sprintf("%t", num3) fmt.Println(str3) }
Go语言常量
-
和C语言一样Go语言中的常量也分为
整型常量
、实型常量
、字符常量
、字符串常量
、自定义常量
-
自定义常量
- C语言自定义常量:
const 数据类型 常量名称 = 值;
#include <stdio.h> int main(int argc, const char * argv[]) { const float PI = 998; PI = 110; // 报错 printf("PI = %d\n", PI ); return 0; }
- Go语言自定义常量:
const 常量名称 数据类型 = 值
orconst 常量名称 = 值
package main import "fmt" func main() { //const PI float32 = 3.14 //PI = 110 // 报错 //fmt.Println("PI = ", PI ) const PI = 3.14 PI = 110 // 报错 fmt.Println("PI = ", PI ) }
- 除此之外Go语言还支持
一次性定义多个常量
package main import "fmt" func main() { // 多重赋值方式 const num1, num2 int = 100, 200 fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) // 常量组方式 const ( num3 = 100 num4 = 200 ) fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) // 常量组+多重赋值 const ( num5, num6 = 100, 200 num7 = 300 ) fmt.Println("num5 = ", num5) fmt.Println("num6 = ", num6) fmt.Println("num7 = ", num7) }
- C语言自定义常量:
- Go语言自定义常量注意点
- 定义的局部变量或者导入的包没有被使用, 那么编译器会报错,无法编译运行
- 但是定义的常量没有被使用,编译器不会报错, 可以编译运行
package main import "fmt" func main() { // 可以编译运行 const PI float32 = 3.14 }
- 在常量组中, 如果上一行常量有初始值,但是下一行没有初始值, 那么下一行的值就是上一行的值
package main import "fmt" func main() { const ( num1 = 998 num2 // 和上一行的值一样 num3 = 666 num4 // 和上一行的值一样 num5 // 和上一行的值一样 ) fmt.Println("num1 = ", num1) // 998 fmt.Println("num2 = ", num2) // 998 fmt.Println("num3 = ", num3) // 666 fmt.Println("num4 = ", num4) // 666 fmt.Println("num5 = ", num5) // 666 const ( num1, num2 = 100, 200 num3, num4 // 和上一行的值一样, 注意变量个数必须也和上一行一样 ) fmt.Println("num1 = ", num1) fmt.Println("num2 = ", num2) fmt.Println("num3 = ", num3) fmt.Println("num4 = ", num4) }
- 枚举常量
- C语言中枚举类型的本质就是整型常量
- Go语言中没有C语言中明确意义上的enum定义, 但是可以借助iota标识符来实现枚举类型
- C语言枚举格式:
enum 枚举名 { 枚举元素1, 枚举元素2, … … };
-
- C语言枚举中,如果没有指定初始值,那么从0开始递增
#include <stdio.h> int main(int argc, const char * argv[]) { enum Gender{ male, female, yao, }; // enum Gender g = male; // printf("%d\n", g); // 0 // enum Gender g = female; // printf("%d\n", g); // 1 enum Gender g = yao; printf("%d\n", g); // 2 return 0; }
- C语言枚举中, 如果指定了初始值,那么从指定的数开始递增
#include <stdio.h> int main(int argc, const char * argv[]) { enum Gender{ male = 5, female, yao, }; // enum Gender g = male; // printf("%d\n", g); // 5 // enum Gender g = female; // printf("%d\n", g); // 6 enum Gender g = yao; printf("%d\n", g); // 7 return 0; }
- Go语言实现枚举格式
const( 枚举元素1 = iota 枚举元素2 = iota ... ... )
-
- 利用iota标识符标识符实现从0开始递增的枚举
package main import "fmt" func main() { const ( male = iota female = iota yao = iota ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 1 fmt.Println("male = ", yao) // 2 }
- iota注意点:
每一行递增1
package main import "fmt" func main() { const ( male = iota // 这里出现了iota female // 这里会自动递增 yao ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 1 fmt.Println("male = ", yao) // 2 }
- 在同一个常量组中,如果iota被中断, 那么必须显示恢复
package main import "fmt" func main() { const ( male = iota female = 666 // 这里被中断, 如果没有显示恢复, 那么下面没有赋值的常量都和上一行一样 yao ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 666 fmt.Println("male = ", yao) // 666 }
package main import "fmt" func main() { const ( male = iota female = 666 // 这里被中断 yao = iota // 这里显示恢复, 会从当前常量组第一次出现iota的地方开始,每一行递增1, 当前是第3行,所以值就是2 ) fmt.Println("male = ", male) // 0 fmt.Println("male = ", female) // 666 fmt.Println("male = ", yao) // 2 }
- iota也支持常量组+多重赋值, 在同一行的iota值相同
package main import "fmt" func main() { const ( a, b = iota, iota c, d = iota, iota ) fmt.Println("a = ", a) // 0 fmt.Println("b = ", b) // 0 fmt.Println("c = ", c) // 1 fmt.Println("d = ", d) // 1 }
- iota自增默认数据类型为int类型, 也可以显示指定类型
package main import "fmt" func main() { const ( male float32 = iota // 显示指定类型,后续自增都会按照指定类型自增 female yao ) fmt.Printf("%f\n", male) // 0.0 fmt.Printf("%f\n", female) // 1.0 fmt.Printf("%f\n", yao) // 2.0 fmt.Println("male = ", reflect.TypeOf(female)) // float32 }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The CS Detective: An Algorithmic Tale of Crime, Conspiracy, and
Jeremy Kubica / No Starch Press / 2016-8-15 / USD 13.74
Meet Frank Runtime. Disgraced ex-detective. Hard-boiled private eye. Search expert.When a robbery hits police headquarters, it's up to Frank Runtime and his extensive search skills to catch the culpri......一起来看看 《The CS Detective: An Algorithmic Tale of Crime, Conspiracy, and 》 这本书的介绍吧!