Fork me on GitHub

函数递归

调用自己的函数,就叫做递归函数。

典型例子:数学中一个数n的阶乘,表示为n!,它的值定义是这样的:

1
2
0! = 1
n! = (n-1)*n

0的阶乘是1,n的阶乘的值是n-1的阶乘的值乘以n,所以这个定义是一个递归的定义。

为求n的值,需先求n-1的值,直到0,然后一次往回退。

求阶乘的递归函数代码如下:

1
2
3
4
5
6
7
public static long factorial(int n) {
if(n==0){
return 1;
}else{
return n*factorial(n-1);
}
}

递归是有开销的,而且使用不当,可能会出现意外的错误。

函数重载

同一个类中函数名字相同但参数不同的现象,一般称为函数重载。

为什么需要函数重载呢?一般是因为函数想表达的意义是一样的,但函数个数或类型不一样。

比如,求两个数的最大值,在java的Math库中就定义了四个函数:

Math库的四个max函数.png

函数定义

函数的基本语法结构:

1
2
3
4
修饰符 返回值类型 函数名字(参数类型 参数名字,...) {
操作 ...
return 返回值;
}

定义函数就是定义了一段有着明确功能的子程序,但定义函数本身不会执行任何代码,函数要被执行,需要被调用。

java中,任何函数都需要放在一个类中。函数放在类中,类中包含多个函数。

函数一般叫做方法。

一个类里面可以定义多个函数,可以定义一个叫做main的函数,形式如下:

1
2
3
public static void main(String[] args) {
...
}

main函数表示程序的入口,String[] args表示从控制台接收到的参数。

java中运行一个程序的时候,需要指定一个定义了main函数的类,java会寻找main函数,并从main函数开始执行。

对于需要重复执行的代码,可以定义函数,然后在需要的地方调用,这样可以减少重复代码。

数组参数

数组作为参数与基本类型不同,基本类型不会对调用者中的变量造成任何影响(不懂这句= =),但数组不是,在函数内修改数组中的元素会修改调用者中的数组内容。

1
2
3
4
5
6
7
8
9
10
11
12
public static void reset(int[] arr){
for(int i=0;i<arr.length;i++){
arr[i] = i;
}
}
public static void main(String[] args){
int[] arr = {10,20,30,40};
reset(arr);
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}

在reset函数内给参数数组元素赋值,在main函数中数组arr的值也会变。

可变长度的参数

有的时候,参数个数不是固定的,数量是未知的。

java支持可变长度的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static int max(int min,int... a) {
int max = min;
for(int i=0;i<a.length;i++) {
if(max<a[i]) {
max = a[i];
}
}
return max;
}

public static void main(String[] args) {
System.out.println(max(0));
System.out.println(max(0,2));
System.out.println(max(0,2,4));
System.out.println(max(0,2,4,5));
}

max函数接受一个最小值,以及可变长度的若干参数,返回其中的最大值。

可变长度参数的语法是在数据类型后面加三个点…,在函数内,可变长度参数可以看作是数组,可变长度参数必须是参数列表中的最后一个参数,一个函数也只能有一个可变长度的参数。

可变长度参数实际上会转换为数组参数,函数声明max(int min,int… a)实际上会转换为max(int min,int[] a)。在main函数调用max(0,2,4,5)的时候,实际上会转换为调用max(0,new int[]{2,4,5}),使用可变长度参数主要是简化了代码书写。

函数的返回值最多只能有一个,void情况下没有返回值。

同一个类里,函数可以重名,但是参数不能一样。一样是指参数个数相同,每个位置的参数类型也一样。但参数的名字不算,返回值类型也不算。

换句话说,函数的唯一性标示是:类名_函数名_参数2类型_…参数n类型

四种循环

在java中,循环有四种形式,分别是while,do/while,for,foreach。

while

1
2
3
4
5
6
7
8
9
10
Scanner reader = new Scanner(System.in);
System.out.println("please input password");
int num = reader.nextInt();
int password = 12345;
while(num!=password) {
System.out.println("please input password");
num = reader.nextInt();
}
System.out.println("correct");
reader.close();

上述代码的功能:使用类型为Scanner的reader变量从屏幕控制台接收数字,reader.nextInt()从屏幕接收一个数字。如果数字不是12345,就一直提示输入;数字是12345,就跳出循环。

do/while

不管语句条件是什么,代码块都会至少执行一次。

先执行代码块,然后再判断条件语句。如果成立,则继续循环,否则退出循环。

1
2
3
4
5
6
7
8
9
Scanner reader = new Scanner(System.in);
int password = 6789;
int num = 0;
do {
System.out.println("please input password");
num = reader.nextInt();
}while(num!=password); //注意加分号
System.out.println("correct");
reader.close();

for

for(初始化语句;循环条件;步进操作){

​ 循环体
}

分号不能省略。

foreach

for (元素类型 元素名称 : 遍历数组/集合/能进行迭代的实例) {

​ 语句

}

1
2
3
4
int[] arr = {1,2,3,4};
for(int element : arr) {
System.out.println(element);
}

foreach使用冒号:,冒号前面是循环中的每个元素,包括数据类型和变量名称。

冒号后面是要遍历的数组或集合。

每次循环中element都会自动更新。

对于不需要使用索引变量、只是简单遍历的情况,foreach语法上更为简洁。

**循环控制 - break **

使用break关键字可以中断循环。

**循环控制 - continue **

continue语句会跳过循环体中剩下的代码,然后执行步进操作。

char的含义

char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编码,用于表示Unicode编号对应的字符。

由于char固定占用两个字符,char只能表示Unicode编号在65536以内的字符(无论是英文字符还是中文字符),而不能表示超出范围的字符。

由于char本质是一个整数,可以进行整数的一些运算。

在进行运算时会被看做int,但由于char占两个字节,运算结果不能直接赋值给char类型,而是需要进行强制类型转换。

char类型的比较,就是Unicode编号的比较。

大小写转换:大写A-Z的编号是65-90,小写a-z的编号是97-122,正好相差32,所以大写转小写要加32,小写转大写要减32。

乱码

ASCII编码:美国规定了128个字符的二进制表示法,即ASCII(美国信息互换标准代码)。

数字32~126表示的字符都是可打印字符。

ASCII:32~126

0~31和127表示的这些字符都是不可打印字符,它们一般用于控制目的。

ASCII:0~31,127

美国的编码不够其他国家使用,其他国家陆续发明了各种各样的编码方式。在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312,GBK,GB18030和Big5。

美国和西欧都是用一个字节(8位)就够了,但中文是两个字节(16位)

Ascii码是基础,一个字节表示,最高位设为0,其他7位表示128个字符。其他编码都是兼容Ascii的,最高位使用1来进行区分。

西欧主要使用Windows-1252,使用一个字节,增加了额外128个字符。

中文大陆地区的三个主要编码GB2312,GBK,GB18030,有时间先后关系,表示的字符数越来越多,且后面的兼容前面的,GB2312和GBK都是用两个字节表示,而GB18030则使用两个或四个字节表示。

最高位用1的编码,除了GB2312/GBK/GB18030外,都是不兼容的。比如Windows-1252和GBK就不兼容。

Unicode:将世界上所有的字符统一编码,给世界上所有字符都分配了一个唯一的数字编号。

其中UTF-8使用1~4个字节表示,兼容ASCII编码。英文字符使用1个字节,中文字符大多使用3个字节。

如何恢复乱码?

答:先将A编码的字符映射为二进制,再通过二进制映射B编码的字符。

位运算

位运算是将数据看作二进制,进行位级别的操作。
Java不能单独表示一个位,但是可以用byte表示8位,也可以用16进制来写二进制常量。

位运算有移位运算和逻辑运算。

移位运算:

  • 左移:操作符为<<,向左移动,右边的低位补0,高位的舍弃掉。左移1位相当于乘以2。
  • 无符号右移:操作符为>>>,向右移动,右边的舍弃掉,左边补0。
  • 有符号右移:操作符为>>,向右移动,右边的舍弃掉,左边补什么取决于原来最高位是什么,原来是0就补0,原来是1就补1。有符号右移相当于除以2。

十六进制

在数字前面加0x,代表十六进制

比如十进制的123,等于十六进制的0x7B

java中不支持直接写二进制常量,可以用十六进制来表示

可以是用Long和Integer的方法,查看二进制和十六进制的表示:

1
2
3
4
5
int a =25;
System.out.println(Integer.toBinaryString(a)); //二进制
System.out.println(Integer.toHexString(a)); //十六进制
System.out.println(Long.toBinaryString(a)); //二进制
System.out.println(Long.toHexString(a)); //十六进制

负数的二进制表示

正数的表示法:原码表示法

负数的表示法:补码表示法。补码=原码取反然后加1

例如(byte类型):

-1:1的原码是00000001,取反是11111110,然后再加1,就是11111111

如何计算11111111等于多少?

解:先取反,得00000000,再加1,得00000001,可知该负数绝对值为1,实际值为-1

对负数进行补码运算就可以得到原码,如同十进制的负负得正。

数组存储

基本类型的数组有三种赋值形式:

1
2
3
int[] arr = {1,2,3}; //给定初始值
int[] arr = new int[]{1,2,3}; //给定初始值
int[] arr = new int[3]; //3代表长度

不能在给定初始值的同时还给定长度。

一个基本类型变量,内存中只会有一块对应的内存空间;

一个数组,内存中有两块对应的内存空间,一块用于存储数组内容本身,另一块用于存储内容的位置。

代码 内存地址 内存数据
int a = 100; 1000 100
int[] arr = {1,2,3}; 2000 2500
2500 1
2504 2
2508 3

如上面表格,int a 的内存地址是1000,该地址(1000)存储的值就是值100。int[] arr的内存地址是2000,该地址存储的值是的地址2500,而从2500的地址开始才实际存储1、2、3。

  • Copyrights © 2021 Silver Shaded
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信