1.多值返回
在C/C++里面如果需要返回多值,一般是在函数传入指针或者引用,比如
fun(int *a,int *b,int *c),但在go里面,如果需要返回多值,只需要把函数写成这样
1 func test_func()(int,int,int){ 2 a := 1; 3 b := 2; 4 c := 3; 5 6 return a,b,c; 7 }
最后函数会依次返回a,b,c
这个其实在lua中早就有了,所以实际上在go里面也算不上什么新的东西,go里面还有一个返回值命名的特性
func read_file()(read_count int,err int){
....//代码需要做的事情
return ;
}
这样就可以让函数的代码更加清晰和更让人理解,看函数声明就可以知道返回值是干什么的
2.零值初始化,自动推导类型
C++ 11新增了一个auto,在go里面也有:=符号可以自动推导,比如a := 1,a会自动被推导成为int类型,还有另外一个特性就是灵芝初始化,我想这个是大部分C程序员喜闻乐见的,因为这样我们可以少写几行代码跑去初始化一个变量(在我的大部分代码中也都是用0来初始化变量),如果C/C++有这个特性,实际上很多错误都可以很容易找到,我看很多代码都是因为没有初始化造成的
3.垃圾回收
这个不用说了,绝对是个利器,资源管理一直是C/C++头疼的问题,当然自动垃圾回收肯定会带来性能下降,甚至会内存泄露(据说java也会有内存泄露的问题,python是有的,虽然这些都有垃圾回收的机制)
4.slices
说白了slices就是指向一个array底层的指针,不过和array不同,slices在元素加入的时候可以增加长度,并且slices有点类似与指针,一旦修改了slices,那么slices指向的数据也会改变,go里面没有把数组退化成指针的做法,如果你传入的是个数组,那么go会复制整个数组的副本,也就是如果有100个元素,那么go就会复制100个元素的副本
5:defer
defer就是在函数退出的时候会自动调用我们用defer生命的代码段,这个是为了防止我们忘记关闭文件句柄或者释放资源,C/C++程序员经常犯的错误
1 f,_ = os.Open(file_name); 2 3 defer f.Close()
函数在打开文件后并不会立即执行f.Close,而是会在函数执行完毕后才执行f.Close()
6:接口,自定义类型与方法
1 class Graph 2 { 3 public: 4 int GetWidth(); 5 6 int GetHeight(); 7 8 virtual void Draw(); 9 private: 10 int width; 11 int height; 12 }
我一直不喜欢C++的这种的方式,因为把一大堆的函数和数据放在一起,这样当代码多了以后将会变得很混乱,而且因为虚函数的存在,在进行初始化的时候不能直接用memset或者memcpy,如果一个类中有几百个变量,那么我们需要一个个去手动初始化,不像C语言里面,数据结构都是原生的值,可以直接memset初始化,go里面则是自动帮我们零值初始化
实际上有了解C++的应该知道,上面的这个类编译器在生成代码的时候还是帮我们进行了分开,比如GetAge()会变成GetAge(Person &person),在go里面则是将一个类分成三个部分,数据,方法与接口
1 type Graph struct{ 2 width int; 3 height int; 4 } 5 6 7 func (g *Graph)GetWidth()(int){ 8 return g.width; 9 } 10 11 func(g *Graph)GetHeight()(int){ 12 return g.height; 13 } 14 15 func(g *Graph)Draw(){ 16 fmt.Printf("graph draw"); 17 } 18 19 type graph_interface interface{ 20 Draw() 21 } 22 23 func Draw(g graph_interface){ 24 g.Draw(); 25 }
interface就是声明了一个接口,就是类似与虚函数的vptr,可以把type graph_interface interface这句理解成某个把函数加入虚函数表,使用这个接口就可以调用传入的参数的Draw这个函数(C++虚函数的实现原理也是利用这个方法)
在函数声明前面加上(g *Graph)就可以把类的数据与这些方法绑定在一起,其他也没什么好说的了,公有和私有数据或者函数都是利用大小写来区分的,不过go里面跟C++不同,C++如果是private的话其他类就不能访问这个变量或者函数,而go则是其他文件不能访问,本文件还是可以访问,有点类似于C的static
实际上go的C++内部的这些实现原理我估计都是差不多的,只是展现出来的语法的不同而已,当然到现在为止我更喜欢go的语法,我想go的设计思想更符合linus的说法
“烂程序员关心的是代码。好程序员关心的是数据结构和它们之间的关系。”
git的设计其实非常的简单,它的数据结构很稳定,并且有丰富的文档描述。事实上,我非常的赞同应该围绕我们的数据结构来设计代码,而不是依据其它的,我认为这也是git之所以成功的原因之一[...]依我的观点,好程序员和烂程序员之间的差别就在于他们认为是代码更重要还是数据结构更重要。
我想C++的程序员要看看这篇文章:http://www.aqee.net/torvalds-quote-about-good-programmer/
将数据结构和这些方法分开将会更有助于程序员去理清数据结构与函数的关系
7.goroutine
据说这个是go的最大的"卖点",网上吹捧的文章也很多,不过lua很早就有这个东西了,所以我觉得没什么可以吹捧的,就是在线程内再模拟单核的电脑去运行代码段,当然这样很节省资源,不用进行线程的切换
go func_name(),这样就把一个函数当作goroutine来运行,当然routine并不是并发的,只是并行的,所以说goroutine是在线程内模拟单核的电脑去运行代码(并发和并行的区别)
go routine进行通讯也很简单,就是利用channel,有用过unix shell的应该很熟悉这个东西了,不多说了
go的缺点:
1.无法与C结合,无法编译成lib,dll
总之看了这么多,我觉得go还是很好用的,但是到目前为止,没发现有什么可以将go嵌入到c语言里面,只能把C嵌入到go里面,这个我想会导致很多公司不愿意用go,我本来想将go弄进一个项目里面,但是发现go无法嵌入到C里面,也无法编译成dll就放弃了
2.并不是像网上说的那样是为了并发而设计的,只是改进的C++
应该能取代C++或者java,但估计不能取代C,网上有人拿go和erlang比较,我还是觉得go无法跟erlang比较,go顶多算是改进的C++,和erlang这种天生为了并行运算的而生的无法比较,go还是沿用了C/C++的面向对象,面向过程,而erlang则是天生面向进程,一切都是进程,甚至线程调度代码都是自己写的而不是直接将操作系统的API封装一层,据说go的线程调度代码只有几百行,而erlang有几万行,这里就能看出差别了,甚至我一直觉得erlang完全可以自己单独出来做操作系统
3.强制性的编码风格
go也有缺点,比如强制性的一些编码手段,如果你一个变量不使用,不会给你一个警告,而是会给你一个错误,无法通过编译,据说是为了增加编译速度,这个让我很诧异,因为我在编程中经常需要预留接口或者变量,这个对于以后更改很有好处,在这种地方节省编译时间有效吗??我估计是go的设计者为了强迫大家写出好看的代码,这个在很多无法通过编译的选项就可以看出,go对程序员的编程风格强制要求让我很不适应...况且我们工程十多万行代码,整个项目重编,10分钟和20分钟有区别吗??我想大部分人都是切出去做其他的事情
4.诡异的语法
var n int;这个是不是很诡异??反正我非常不习惯这个声明变量的方式,还有大括号
1 if true { 2 //code 3 }else if false{ 4 //code 5 }else 6 { 7 //code 8 }
需要强制把大括号跟条件语句在一起,不知道go的设计师是为了标新立异还是懒得写语法分析??这样代码反而没有c的把大括号另起一行来的好看,不知道为什么要强制程序员这样写,不过让我想起当初python的缩进也引起很多争议...当初在论坛上还跟人争辩过这个
5.编译真的比C/C++快??
网上有人说go的编译速度比C和C++快,go为了增加编译速度强制程序员不得在代码中有不使用的变量,import的包如果不使用也会被提示编译错误,但这真的能解让go编译速度比C++快??C++因为语法复杂对于编译器的设计者一直是个噩梦,但是我想go没有lib或者obj的概念(我没找到生成的临时文件在哪里,还是说我的生成方式不对??),那我们在build一个项目的时候是不是要把所有的被import的文件重新编译一遍??如果项目大起来编译速度真的会比C++还快??如果C++的整个项目不重编的话go的编译速度会比C++更有优势??我记得网上看有人说C++的编译方式会慢是因为一个文件一旦被include 46次,那么他也需要被打开46次,但我想这个问题go应该也存在,或者已经解决了??