辉夜的博客

繁花似锦,辉夜如昼

0%

软件构造-Java学习笔记(1)

主要介绍java和c语言的一些区别,参考课程浙江大学翁恺b站java语言基础

第一个java程序

java的开发环境基本上还是依赖ide的,因为java有比较复杂的依赖关系(至少对我而言比较复杂)。教学视频使用的是目前最广泛使用的eclipse,官网下载可以直接安装(2022年不再需要提前安装jre),汉化方式可以自行搜索。

java与C语言的第一个区别就在于”类”,和任何编程语言一样,java程序也有自己的执行入口,不过不是主函数,而是主类。我们在主类下编写的方法会被依次调用。一个类就是一个抽象数据类型,包含了数据和对数据的操作。这样的封装使得java和C++等面向对象的编程语言拥有与C语言截然不同的编程风格,会看到大量的标识符和”.”,取成员运算符。java中有许多自带的类,比如马上就会遇到的System类,执行java函数的时候如果跟进是可以直接跟进到System里的方法的,很有趣(另外很想吐槽类名不能自动补全实在是太糟糕了。)

同样java中的特色还有字符串类String,以及字符串对加号运算符的重载。所谓运算符的重载就是在一定的作用域内,把运算符替换为某个函数的执行。比如原来的加法可以看成有两个算数类型参数返回一个算数类型的add函数,而java中字符串类对加号的重载可以看成把加号对应的函数替换成了strcat函数,也就是连接两个字符串。这种情况下,如果想要保留算数运算就需要在算数变量被替换为字符串变量之前进行算数运算,最简单的方法就是在算术表达式周围加一个括号。

可以看到,类和运算符重载的概念赋予了面向对象语言极大的灵活和自由性。对于类的使用者来说,完全不需要关心类的背后发生了什么。System类背后对输入输出流的复杂处理被掩盖起来,String类进行的字符串连接也被简单的加号替代。这样的封装使得面向对象语言天然的适合用来描述某种问题(我目前并不能很好的概括出是哪种问题)。

C语言的函数也有类似的封装,然而,C语言并没有提供作用域层面的封装。我们可以在函数内部进行封装,但函数之间的数据传输必须通过清晰的传参返回来完成。C语言对访问类型(static/global)的控制可以说提供了封装的一种方案,也就是以编译单元为单位进行模块化的编程设计。不过,函数之间的数据依靠传参管理降低了函数之间的最大聚合程度,更会在系统框架变更时带来巨大的麻烦,运算符重载这种特技更是无从谈起。不过,C语言同样有其独特的好处:面向过程的控制流是清晰可见且高度解耦的,由于没有层层抽象,它还可以生成效率更高的代码。

java中的数组

java拥有美好的内存管理机制,我们可以使用new关键字分配一片地址,并且这片地址的大小是可变的。(不确定,但是new初始化的默认值全为0是很明显的静态存储特点)java中没有指针类型,而是用[]起到和指针类似的效果,于是我们会写出:

1
int[] a = new int[3];

这是一个数组的定义,也就是声明并且分配存储空间。事实上a相当于一个指针。当我们把两个数组类型的变量做比较时,我们实际上在比较他们是否指向同一个地址。如果要比较他们的内容,则要使用数组自带的成员方法equals().
数组自带许多的成员,我也不清楚这些成员是哪里来的。一个很有用的成员变量是a.length,它是a的长度,于是我们遍历数组时可以用:

1
2
3
4
for (int i = 0; i < a.length; i++)
{
System.out.println(a[i]);
}

这和C语言是完全类似的。这个成员变量被声明为final,类似于C中的const,因此它不能被随意修改,而是在创建数组时就确定了。。Java允许我们对可枚举的类型写出for循环的另外一种形式,被称为for-each循环,是全新的:

1
2
3
4
for (var i: a)
{
System.out.println(i);
}

这里的i是a的一个浅拷贝,刚刚给出的例子和一般的for循环的例子是完全等价的。不过需要注意的是,如果要改变数组中的元素的值,那么不能用for-each循环,因为for-each的循环相当于依次把a中元素的值赋给了i,不像a[i]是数组元素自身的标识符。可以想到,改变i的值的效果会在下一次循环时被清空。
事实上,for-each循环可以改写为:

1
2
3
4
5
6
7
/*for (var i: a)*/

int index = 0;
for(i = a[0] ; index < a.length;index++, i = a[index])
{
...
}

二维数组的形式和操作与C语言都是类似的,二维数组就是数组的数组。二维数组自身持有一个length成员,它的每一个元素作为一个数组也持有自己的成员。如果要遍历二维数组,可以写:

1
2
3
4
5
for(int i = 0; i < a.length; i++) {
for(int j = 0; j < a[i].length; j++) {
a[i][j] ...
}
}

更进一步地,如果探究

java中的字符与字符串

java中使用Unicode来表示字符,并且转义字符’\uxxxx’表示用十六进制表示的unicode字符码。除此之外关于字符的运算都是和C语言完全类似的。
字符串是java中一个很常用的类。创建字符串变量的方法据说有11种…让我们先看看比较常用的两种。

1
2
3
String a;
a = new String("qaq");
a = "qaq";

它提供了许多的方法,以及一种运算符重载。当其他类型和String类型进行运算时会被转换成String类型,所以要想避开加号重载就要在转换之前进行运算。常见的C语言函数都可以在java中找到对应的方法:

1
2
3
4
strcat -> +
strlen -> string.length();
strcmp -> string.compareto();
strstr -> string.indexOf();

此外还有许多方法。不同方法之间可以配合产生出很惊艳的效果。java提供的修改字符串方法都可以看成持有一个结构体指针,返回一个结构体的函数。这意味着方法本身的返回值是一个全新的对象。对于java中的查找或匹配字符串方法,也就是在C语言中会返回字符指针的那些函数(strstr),现在会返回一个整数下标。因为这更多的是实践性的问题,这里就不再举更多例子,需要时可以查阅手册。

java中的包裹类型

基本类型对应的包裹类型,是具有一些常用成员的类。他们之间的对应是这样的:

1
2
3
4
int     -> Integer
boolean -> Boolean
double -> Double
char -> Character

包裹类型会含有一些该类型的信息,比如Integer的成员MAX_VALUE就是C语言中的T_MAX,此外还有一些其他的方法,比如Character具有isdigit,isalpha方法。这些都和c语言是完全类似的。