c语言 有趣简单编程
Java是我专业学习和使用的第一门语言。 大约十年半以来,这一直是我的生计。 但是,这并不是我一整年以来一直学习或使用的唯一语言:例如,很久以前,我不得不开发JavaScript代码来实现动态用户界面。 那时,它被称为DHTML ....几年前,我还自学了Kotlin,从未停止使用它。 去年,在一家新公司工作时,我尝试了Clojure-尽管效果不佳。
在所有情况下,Java仍然代表着我学习和判断其他语言的基础。 这是语言的一些有趣特征,我发现这些特征来自Java背景,颇具挑战性。
JavaScript:原型
JavaScript是我必须与Java一起使用的第一语言。 尽管JavaScript在过去的这些年里一直在发展,但其中一个共同的功能却非常奇怪地实现了:新对象的实例化。
在Java中,首先创建一个类 :
publicclassPerson{
privatefinalStringname;
privatefinalLocalDatebirthdate;
publicPerson(Stringname,LocalDatebirthdate){
this.name=name;
this.birthdate=birthdate;
}
publicStringgetName(){
returnname;
}
publicLocalDategetBirthdate(){
returnbirthdate;
}
}
然后,可以继续创建该类的实例 :
varperson1=newPerson("John Doe",LocalDate.now());
varperson2=newPerson("Jane Doe",LocalDate.now());
JavaScript与Java语法非常相似:
classPerson{
constructor(name,birthdate){
this.name=name;
this.birthdate=birthdate;
}
}
letperson1=newPerson("John Doe",Date.now());
letperson2=newPerson("Jane Doe",Date.now());
平行线在那里停止。 得益于JavaScript动态的特性,可以向现有实例添加属性和函数。
person1.debug=function(){
console.debug(this);
}
person1.debug();
但是,这些仅添加到实例。 其他实例缺少这些补充:
person2.debug();// Throws TypeError: person2.debug is not a function
为了向现在和将来的所有实例添加函数(或属性),需要利用原型的概念:
Person.prototype.debug=function(){
console.debug(this);
}
person1.debug();
person2.debug();
letperson3=newPerson("Nicolas",Date.now());
person3.debug();
Kotlin:扩展功能/属性
几年前,我尝试自学Android。 我发现这种体验并不十分适合开发人员:当然,我了解目标之一是尽可能减小占用空间,但这是以非常简洁的API为代价的。
我记得必须调用带有很多参数的方法,其中大多数为null 。 我试图找到一种方法来解决这个问题...并发现Kotlin的扩展属性-具有默认参数。 我停止了学习Android的道路,但继续使用Kotlin。
我爱Kotlin。 许多人赞扬Kotlin的零安全方法。 我喜欢它,但对我来说,最高的价值在于其他地方。
假设我们经常需要大写字符串。 在Java中实现该目标的方法是使用静态方法创建一个类:
publicclassStringUtils{
publicstaticStringcapitalize(Stringstring){
varcharacter=string.substring(0,1).toUpperCase();
varrest=string.substring(1,string.length()-1).toLowerCase();
returncharacter+rest;
}
}
在早期,没有一个项目没有StringUtils
和DateUtils
类。 幸运的是,现在,现有的库提供了最需要的功能, 例如 Apache Commons Lang和Guava 。 但是,它们遵循基于静态方法的相同设计原理。 令人遗憾的是,因为Java被认为是一种OOP语言。 不幸的是,静态方法不是面向对象的。
Kotlin允许在扩展函数和属性的帮助下向现有类分别添加行为。 语法非常简单,并且与面向对象的方法完全兼容:
funString.capitalize():String{
valcharacter=substring(0,1).toUpperCase()
valrest=substring(1,length-1).toLowerCase()
returncharacter+rest
}
在编写Kotlin代码时,我经常使用它。
在幕后,Kotlin编译器生成的字节码类似于Java代码中的字节码。 它只是“唯一”的语法糖,但是从设计的角度来看,与Java代码相比,这是一个巨大的改进!
转到:隐式接口实现
在大多数OOP语言(Java,Scala,Kotlin等)中,类可以遵守合同,也称为接口 。 这样,客户端代码可以引用该接口,而不关心任何特定的实现。
publicinterfaceShape{
floatarea();
floatperimeter();
defaultvoiddisplay(){
System.out.println(this);
System.out.println(perimeter());
System.out.println(area());
}
}
publicclassRectangleimplementsShape{
publicfinalfloatwidth;
publicfinalfloatheight;
publicRectangle(floatwidth,floatheight){
this.width=width;
this.height=height;
}
@Override
publicfloatarea(){
returnwidth*height; (1)
}
@Override
publicfloatperimeter(){
return2*width+2*height; (1)
}
publicstaticvoidmain(String...args){
varrect=newRectangle(2.0f,3.0f);
rect.display();
}
}
- 一个人应该使用
BigDecimal
来达到精确的目的-但这不是重点
重要的一点是:由于Rectangle
实现Shape
,因此可以在Rectangle
任何实例上调用Shape
上定义的display()
方法。
Go不是一种OOP语言:它没有类的概念。 它提供结构 ,并且功能可以与这种结构相关联。 它还提供了结构可以实现的接口 。
但是,Java实现接口的方法是明确的 : Rectangle
类声明其实现Shape
。 相反,Go的方法是隐式的 。 实现接口所有功能的结构隐式实现此接口。
这将转换为以下代码:
packagemain
import(
"fmt"
)
typeshapeinterface{ (1)
area()float32
perimeter()float32
}
typerectanglestruct{ (2)
widthfloat32
heightfloat32
}
func(rectrectangle)area()float32{ (3)
returnrect.width*rect.height
}
func(rectrectangle)perimeter()float32{ (3)
return2*rect.width+2*rect.height
}
funcdisplay(shapeshape){ (4)
fmt.Println(shape)
fmt.Println(shape.perimeter())
fmt.Println(shape.area())
}
funcmain(){
rect:=rectangle{width:2,height:3}
display(rect) (5)
}
- 定义
shape
界面 - 定义
rectangle
结构 - 将两个
shape
函数都添加到rectangle
-
display()
函数仅接受shape
- 因为
rectangle
实现了shape
所有功能,并且由于隐式实现,所以rect
也是shape
。 因此,调用display()
函数并将rect
作为参数传递是完全合法的
Clojure:“依赖类型”
我以前的公司在Clojure投入了很多资金。 因此,我尝试学习该语言,甚至写了几篇文章来总结我对语言的理解。
Clojure在很大程度上受到LISP的启发。 因此,表达式用括号括起来,并且要执行的功能首先位于它们的内部。 另外,Clojure是一种动态类型化的语言:虽然有类型,但没有声明它们。
一方面,该语言提供了基于合同的编程。 可以指定前置条件和后置条件:在运行时对它们进行评估。 这样的条件可以进行类型检查, 例如,参数是字符串,布尔值等吗? -甚至可以更进一步,类似于_dependent类型 :
在计算机科学和逻辑中,从属类型是其定义取决于值的类型。 “整数对”是一种类型。 由于对值的依赖性,“第二对大于第一对的整数对”是从属类型。
https://zh.wikipedia.org/wiki/Dependent_type
它是在运行时强制执行的,因此不能真正称为依赖类型。 但是,这是我所涉猎的语言中最接近的一种。
长生不老药:模式匹配
一些语言自称为提供模式匹配功能。 通常,模式匹配用于评估变量, 例如在Kotlin中:
varstatusCode:Int
valerrorMessage=when(statusCode){
401->"Unauthorized"
403->"Forbidden"
500->"Internal Server Error"
else->"Unrecognized Status Code"
}
此用法是类固醇的转换语句。 但是,通常,模式匹配的应用范围更加广泛。 在以下代码段中,将检查第一个常规的HTTP状态错误代码,如果未找到,则默认为更通用的错误消息:
valerrorMessage=when{
statusCode==401->"Unauthorized"
statusCode==403->"Forbidden"
statusCode-400<100->"Client Error"
statusCode==500->"Internal Server Error"
statusCode-500<100->"Server Error"
else->"Unrecognized Status Code"
}
不过,它是有限的。
Elixir是一种在Erlang OTP上运行的动态类型化语言,将模式匹配提升到了一个全新的水平。 Elixir的模式匹配可用于简单的变量解构:
{a,b,c}={:hello,"world",42}
将为a
分配:hello
,向b
分配“ world”,向c
分配42。
它也可以用于集合的更高级的分解:
[head|tail]=[1,2,3]
head
将被分配为1,而tail
将被分配为[2, 3]
。
然而,对于函数重载甚至更是如此。 作为一种功能语言,Elixir没有用于循环的关键字( for
或while
):循环需要使用递归实现。
例如,让我们使用递归来计算List
的大小。 在Java中,这很容易,因为有一个size()
方法,但是Elixir API没有提供这种功能。 让我们以Elixir方式伪代码实现它-使用递归。
publicintlengthOf(List<?>item){
returnlengthOf(0,items);
}
privateintlengthOf(intsize,List<?>items){
if(items.isEmpty()){
returnsize;
}else{
returnlengthOf(size+1,items.remove(0));
}
}
这可以几乎逐行转换为Elixir:
deflength_of(list),do:length_of(0,list)
defplength_of(size,list)do
if[]==listdo
size
else
[_|tail]=list (1)
length_of(size+1,tail)
end
end
- 模式匹配与可变解构。 头值分配给
_
变量,这意味着以后无法引用它-因为它没有用。
但是,如前所述,Elixir模式匹配也适用于函数重载。 因此,写Elixir的名义方法是:
deflist_len(list),do:list_len(0,list)
defplist_len(size,[]),do:size (1)
defplist_len(size,list)do (2)
[_|tail]=list
list_len(size+1,tail)
end
- 如果列表为空,则调用此函数
- 否则调用此函数
请注意,模式是按照声明的顺序进行评估的:在上面的代码段中,Elixir首先评估具有空列表的函数,并且仅在不匹配的情况下评估第二个函数, 即列表不为空。 如果要以相反的顺序声明函数,则每次都会在非空列表上进行匹配。
Python:理解
Python是一种动态类型的趋势语言。 就像Java中一样,Python通过for
关键字提供循环。 以下片段循环遍历集合中的所有项目,并逐一打印它们。
fornin[1,2,3,4,5]:
print(n)
要收集新集合中的所有项目,可以创建一个空集合,然后将每个项目添加到循环中:
numbers=[]
fornin[1,2,3,4,5]:
numbers.append(n)
print(numbers)
但是,可以使用漂亮的Python功能: 进行理解 。 尽管它与标准循环使用相同的for
关键字,但是for comprehension是实现相同结果的功能构造。
numbers=[nfornin[1,2,3,4,5]]
print(numbers)
上一个代码段的输出为[1, 2, 3, 4, 5]
。
也可以变换每个项目。 例如,以下代码片段将计算每个项目的平方:
numbers=[n**2fornin[1,2,3,4,5]]
print(numbers)
输出[1, 4, 9, 16, 25]
。
理解的好处是可以使用条件句。 例如,以下代码段将仅过滤偶数项,然后将它们平方:
numbers=[n**2fornin[1,2,3,4,5]ifn%2==0]
print(numbers)
输出为[4, 16]
。
最后,为了理解,允许使用笛卡尔积。
numbers=[a:nfornin[1,2,3]forain['a','b']]
print(numbers)
将会输出[('a', 1), ('b', 1), ('a', 2), ('b', 2), ('a', 3), ('b', 3)]
。
上面的理解也称为列表理解,因为它们旨在创建新列表。 地图理解非常相似,旨在创建...地图。
结论
尽管Java是一门不断发展的语言,但这是一件好事。 但是,其他语言中发现的方法也值得研究。 请记住,一种语言构成了人们对问题的思考方式以及解决方案的设计方式。 学习或至少熟悉其他语言是考虑其他观点的好方法。
翻译自: https://blog.frankel.ch/six-interesting-features-programming-languages/
c语言 有趣简单编程