因为基础部分的东西很简单,java这里很多基本的东西的写法与C/C++没有太大区别,所以比较相似的部分会直接跳过不再阐述,仅说明与C/C++比较不同的地方。

一.数据类型与变量

在java中,数据类型主要分为两类,一类为基本数据类型,另一类为引用数据类型。

1.1基本数据类型

基本数据类型有四类八种:

  1. 四类:整型、浮点型、字符型、布尔型
  2. 八种
数据类型 关键字 内存占用 范围
字节型 byte 1 字节 -128 ~ 127
短整型 short 2 字节 -32768 ~ 32767
整型 int 4 字节 -2147483648 ~ 2147483647
长整型 long 8 字节 -9223372036854775808 ~ 9223372036854775807
单精度浮点数 float 4 字节 有范围,一般不关注
双精度浮点数 double 8 字节 有范围,一般不关注
字符型 char 2 字节 0 ~ 65535
布尔型 boolean 没有明确规定 truefalse
  • 不论是在16位系统还是32位系统,int 都占用 4个字节long 都占用 8个字节
  • 整型和浮点型都是带有符号的
  • 整型默认为 int 型,浮点型默认为 double 型。
  • 字符串属于引用类型,该类型后续介绍。

除此之外,每个基本类型都对应着一个包装器类型:

基本类型 包装器类型
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
  • 包装器类型位于 java.lang 包中,无需额外导入。
  • 从 Java 5 开始支持自动装箱(基本类型 → 包装器)和自动拆箱(包装器 → 基本类型)。
  • 包装器类提供了实用的静态方法,如 Integer.parseInt()Double.valueOf() 等。

这些我们后面都会大量的使用到,这里先做个认识即可。

1.2类型转换

需要注意,在java中,不管是显示还是隐式类型转换,不相关的类型无法进行互相转换!
比如:

boolean a = false;
a = (boolean) 100;

1.1关于隐式类型转换

像我们在使用C/C++时,有很多类型之间是可以直接相互转换的,无论是宽类型到窄类型还是反过来,甚至不相干的类型之间也可以进行转换,比如bool与整型类型之间的转换。总之C/C++中的类型转换是非常宽松的,当然也会因此引发很多问题,比如为了解决枚举引发的污染,C++11提出了一个强类型枚举。

但是,在Java中,只有从小范围类型向大范围类型转换时才能自动完成;而大范围向小范围转换时,无论实际数据是否在目标范围内,都必须显式强制转换,否则编译报错。

比如:

int a = 10;
long b = 10l;
a = b;//报错

1.2关于强制类型转换

强制类型转换:当进行操作时,代码需要经过一定的格式处理,不能自动完成。
特点:数据范围大的 → 数据范围小的

int a = 10;
long b = 100L;
b = a; // int-->long,数据范围由小到大,隐式转换
a = (int)b; // long-->int, 数据范围由大到小,需要强转,否则编译失败
float f = 3.14F;
double d = 5.12;
d = f; // float-->double,数据范围由小到大,隐式转换
f = (float)d; // double-->float, 数据范围由大到小,需要强转,否则编译失败
a = d; // 报错,类型不兼容
a = (int)d; // int没有double表示的数据范围大,需要强转,小数点之后全部丢弃
byte b1 = 100; // 100默认为int,没有超过byte范围,隐式转换
byte b2 = (byte)257; // 257默认为int,超过byte范围,需要显示转换,否则报错
boolean flag = true;
a = flag; // 编译失败:类型不兼容
flag = a; // 编译失败:类型不兼容

注意事项:

  1. 不同数字类型的变量之间赋值,表示范围更小的类型能隐式转换成范围较大的类型
  2. 如果需要把范围大的类型赋值给范围小的,需要强制类型转换,但是可能精度丢失
  3. 将一个字面值常量进行赋值时,Java 会自动针对数字范围进行检查。
  4. 强制类型转换不一定能成功再次强调,不相干的类型不能互相转换!

1.3关于类型提升

java与C++一样,当小类型与大类型相加的时候,小类型会自动提升为大类型之后与大类型进行相加。当然此时的结果就是一个大类型,如果想要把这个大类型强制塞回给小类型,那就需要显示类型转换:

public static void main(String[] args)
{
   int a = 100;
   long b = 100;
   int c = (int)(a + b);//必须显示强转否则报错
   System.out.println();
}
特殊情况:byte类型与byte类型的运算

byte 与 byte 的运算

byte a = 10;
byte b = 20;
byte c = a + b;   // 编译报错:不兼容的类型,从 int 转换到 byte 可能会有损失
System.out.println(c);
Test.java:5: 错误: 不兼容的类型: 从int转换到byte可能会有损失
byte c = a + b;

结论bytebyte 都是相同类型,但是出现编译报错。

原因是,虽然 ab 都是 byte,但是计算 a + b 会先将 ab提升成 int,再进行计算,得到的结果也是 int,这时赋给 cbyte 类型),就会出现上述错误。

根本原因:由于计算机的 CPU 通常是按照 4 个字节为单位从内存中读写数据。为了硬件上实现方便,诸如 byteshort 这种低于 4 个字节的类型,会先提升成 int,再参与计算。

正确的写法

byte a = 10;
byte b = 20;
byte c = (byte)(a + b);   // 将 int 类型的结果强转为 byte
System.out.println(c);

所以总的来说关于类型提升就两个注意点:
类型提升规则:

  1. 不同类型的数据混合运算,范围小的会提升成范围大的
  2. 对于 shortbyte 这种比 4 个字节小的类型,会先提升成 4 个字节的 int,再运算。

二.运算符

java中的运算符和C/C++中的极为相似,我们简单的看下就ok了:

  1. 算术运算符
运算符 描述 示例
+ 加法 a + b
- 减法 a - b
* 乘法 a * b
/ 除法 a / b
% 取余(模运算) a % b
  1. 赋值运算符
运算符 描述 示例 等价于
= 赋值 a = b
+= 加后赋值 a += b a = a + b
-= 减后赋值 a -= b a = a - b
*= 乘后赋值 a *= b a = a * b
/= 除后赋值 a /= b a = a / b
%= 取余后赋值 a %= b a = a % b
  1. 关系(比较)运算符
运算符 描述 示例
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b
  1. 逻辑运算符
运算符 描述 示例
&& 逻辑与(短路与) a && b
|| 逻辑或(短路或) a || b
! 逻辑非 !a
& 逻辑与(非短路) a & b
| 逻辑或(非短路) a | b
^ 逻辑异或 a ^ b
  1. 位运算符
运算符 描述 示例
& 按位与 a & b
| 按位或 a | b
^ 按位异或 a ^ b
~ 按位取反 ~a
<< 左移 a << 2
>> 右移(带符号) a >> 2
>>> 无符号右移 a >>> 2
  1. 一元运算符
运算符 描述 示例
+ 正号 +a
- 负号 -a
++ 自增(前置/后置) ++aa++
-- 自减(前置/后置) --aa--
  1. 条件(三元)运算符
运算符 描述 示例
? : 三元条件运算符 a > b ? a : b

说到运算符的优先级,当然不需要我们去记忆,用到了时候查下,和C/C++一样。
这里主要说一下比C/C++多出来的一个运算符>>>
你像我们一般说负数的第一个字节位是1,那么>>去右移一个负数时,空出来的位置用1填充,而>>>右移则直接用0进行填充:

public static void main(String[] args)
{
    int a = -1;
    a = a >>> 1;
    System.out.println(a);//输出值位INT_MAX
}

三.逻辑控制语句

这里也和C/C++极为相似,就简单的列举下,用法与C/C++基本相同:

3.1 条件判断语句

3.1.1 if 语句

语法格式 描述 示例
if (条件) { 代码块 } 条件为 true 时执行代码块 if (a > b) { max = a; }
if (条件) { 代码块 } else { 代码块 } 条件为 true 执行 if 块,否则执行 else 块 if (a > b) { max = a; } else { max = b; }
if (条件1) { 代码块 } else if (条件2) { 代码块 } else { 代码块 } 多条件分支判断 if (score >= 90) { grade = 'A'; } else if (score >= 60) { grade = 'B'; } else { grade = 'C'; }

3.1.2 switch 语句

语法格式 描述 示例
switch (表达式) { case 值1: 代码块; break; case 值2: 代码块; break; default: 代码块; } 根据表达式的值匹配对应的 case 执行 switch (day) { case 1: System.out.println("周一"); break; case 2: System.out.println("周二"); break; default: System.out.println("其他"); }

支持的数据类型: byteshortcharintString(Java 7+)、enum 枚举


3.2 循环语句

3.2.1 for 循环

语法格式 描述 示例
for (初始化; 条件; 迭代) { 代码块 } 标准 for 循环,先初始化,条件为 true 时执行循环体,执行迭代 for (int i = 0; i < 10; i++) { sum += i; }
for (变量类型 变量名 : 数组/集合) { 代码块 } 增强 for 循环(foreach),遍历数组或集合 for (int num : numbers) { System.out.println(num); }
for (;;) { 代码块 } 无限循环(需要 break 退出) for (;;) { if (condition) break; }

3.2.2 while 循环

语法格式 描述 示例
while (条件) { 代码块 } 先判断条件,条件为 true 时执行循环体 while (i < 10) { sum += i; i++; }
while (true) { 代码块 } 无限循环(需要 break 退出) while (true) { if (condition) break; }

3.2.3 do-while 循环

语法格式 描述 示例
do { 代码块 } while (条件); 先执行一次循环体,再判断条件,条件为 true 时继续执行 do { sum += i; i++; } while (i < 10);

四.在控制台进行输入输出

4.1 输出到控制台

基本语法

System.out.println(msg);  // 输出一个字符串,带换行
System.out.print(msg);    // 输出一个字符串,不带换行
System.out.printf(format, msg);  // 格式化输出
  • println 输出的内容自带 \nprint 不带 \n
  • printf 的格式化输出方式和 C 语言的 printf 基本一致

代码示例

System.out.println("hello world");
int x = 10;
System.out.printf("x = %d\n", x);

格式化字符串

转换符 类型 示例 输出
d 十进制整数 ("%d", 100) 100
x 十六进制整数 ("%x", 100) 64
o 八进制整数 ("%o", 100) 144
f 定点浮点数 ("%f", 100f) 100.000000
e 指数浮点数 ("%e", 100f) 1.000000e+02
g 通用浮点数 ("%g", 100f) 100.000
a 十六进制浮点数 ("%a", 100) 0x1.9p6
s 字符串 ("%s", 100) 100
c 字符 ("%c", '1') 1
b 布尔值 ("%b", 100) true
h 散列码 ("%h", 100) 64
% 百分号 ("%.2f%%", 2/7f) 0.29%

提示:这个表格没必要记住,用到的时候根据需要查一下就行了。


4.2 从键盘输入

使用 Scanner 读取字符串/整数/浮点数

import java.util.Scanner;  // 需要导入 util 包

Scanner sc = new Scanner(System.in);

System.out.println("请输入你的姓名:");
String name = sc.nextLine();

System.out.println("请输入你的年龄:");
int age = sc.nextInt();

System.out.println("请输入你的工资:");
float salary = sc.nextFloat();

System.out.println("你的信息如下:");
System.out.println("姓名: " + name + "\n" + "年龄:" + age + "\n" + "工资:" + salary);

sc.close();  // 注意,要记得调用关闭方法

执行结果:

请输入你的姓名:
张三
请输入你的年龄:
18
请输入你的工资:
1000
你的信息如下:
姓名: 张三
年龄:18
工资:1000.0

使用 Scanner 循环读取 N 个数字,并求取其平均值

Scanner sc = new Scanner(System.in);
int sum = 0;
int num = 0;

while (sc.hasNextInt()) {
    int tmp = sc.nextInt();
    sum += tmp;
    num++;
}

System.out.println("sum = " + sum);
System.out.println("avg = " + sum / num);
sc.close();

执行结果:

10
40
50
^Z
sum = 100
avg = 33

注意事项:当循环输入多个数据的时候,使用 Ctrl + Z 来结束输入(Windows 上使用 Ctrl + Z,Linux / Mac 上使用 Ctrl + D)。

五.方法(就是C/C++中的函数)

定义一个方法的基本语法如下:

// 方法定义
修饰符 返回值类型 方法名称([参数类型 形参 ...]){
	方法体代码;
	[return 返回值];
}

方法定义注意事项

序号 注意事项 说明
1 修饰符 现阶段直接使用 public static 固定搭配
2 返回值类型 如果方法有返回值,返回值类型必须与返回的实体类型一致;如果没有返回值,必须写成 void
3 方法名字 采用小驼峰命名(如 calcSum
4 参数列表 如果方法没有参数,() 中什么都不写;如果有参数,需指定参数类型,多个参数之间使用逗号隔开
5 方法体 方法内部要执行的语句
6 定义位置 在 Java 中,方法必须写在类当中
7 嵌套定义 在 Java 中,方法不能嵌套定义
8 方法声明 在 Java 中,没有方法声明一说(即不需要像 C 语言那样先声明再实现)

比如我们来写一个判断是否为闰年的方法并调用:

public class HelloWorld {
    public static boolean isLeapYear(int year){
        if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){
            return true;
        }else{
            return false;
        }
    }
    public static void main(String[] args)
    {
        System.out.println(isLeapYear(400));
    }
}

5.1实参和形参的关系

方法的形参相当于数学函数中的自变量。
比如1 + 2 + 3 + … + n的公式为sum(n) = ((1 + n) * n) / 2
Java 中方法的形参就相当于 sum 函数中的自变量 n,用来接收 sum 函数在调用时传递的值。形参的名字可以随意取,对方法都没有任何影响,形参只是方法在定义时需要借助的一个变量,用来保存方法在调用时传递过来的值。

注意在 Java 中,实参的值永远都是拷贝到形参中,形参和实参本质是两个实体。
也就是说它没有像C/C++那样传值/传指针/传引用这一说法。

5.2方法重载(就是C/C++中的函数重载)

同一个类中,允许存在多个方法名相同参数列表不同的方法。就叫方法重载。
方法重载的三大规则:

  1. 方法名必须相同
  2. 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序不同)
  3. 与返回值类型是否相同无关

规则上也与C/C++别无二致。

六.数组的定义与使用

6.1 数组的创建及初始化

数组的创建

T[] 数组名 = new T[N];
  • T:表示数组中存放元素的类型
  • T[]:表示数组的类型
  • N:表示数组的长度
int[] array1 = new int[10];        // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5];   // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new String[3];   // 创建一个可以容纳3个字符串元素的数组

数组的初始化

数组的初始化主要分为动态初始化静态初始化

1. 动态初始化:在创建数组时,直接指定数组中元素的个数

int[] array = new int[10];

2. 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定

语法格式:

T[] 数组名称 = {data1, data2, data3, ..., datan};
int[] array1 = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hell", "Java", "!!!"};

注意事项:

  • 静态初始化虽然没有指定数组的长度,编译器在编译时会根据 {} 中元素个数来确定数组的长度。
  • 静态初始化时,{} 中数据类型必须与 [] 前数据类型一致。
  • 静态初始化可以简写,省去后面的 new T[]
// 简写形式
int[] array1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = {"hell", "Java", "!!!"};

6.2关于引用类型的说明

6.2.1java程序运行时的内存分布

我们先来认识下java程序在运行时的内存分布:
在这里插入图片描述
JVM 运行时数据区域

区域 说明
程序计数器 (PC Register) 只是一个很小的空间,保存下一条执行的指令的地址
虚拟机栈 (JVM Stack) 与方法调用相关的一些信息。每个方法在执行时,都会先创建一个栈帧,栈帧中包含:局部变量表、操作数栈、动态链接、返回地址等。保存的都是与方法执行时相关的信息(如局部变量)。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
本地方法栈 (Native Method Stack) 与虚拟机栈的作用类似,只不过保存的内容是 Native 方法的局部变量。在有些版本的 JVM 实现中(如 HotSpot),本地方法栈和虚拟机栈是一起的
堆 (Heap) JVM 所管理的最大内存区域。使用 new 创建的对象都是在堆上保存(例如 new int[]{1, 2, 3})。堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁
方法区 (Method Area) 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的字节码就是保存在这个区域。

现阶段重点关注:堆 和 虚拟机栈 这两块空间,后续 JVM 中还会更详细介绍。

6.2.2什么是引用类型

我们都知道,在C++中,无论是左值引用还是右值引用,都必须在引用变量定义时初始化,引用变量并不拥有指向的资源。
java这里也是,但是并不需要定义时就必须初始化。比如窝可以这样写:

int[] arr1;
int[] arr2 = null;
arr1 = new int[]{1,2,3,4};
arr2 = {1,2,3,4};//错误!注意初始为空的时候,如果想要对其进行初始化时必须动态初始化

上面两种方式都是一样的。
需要额外注意的是,在我们定义一个引用类型比如数组时,它在栈(准确来说是虚拟机栈)上开辟空间存储此引用类型变量,同时在堆上开空间去存储实际数据:
在这里插入图片描述
从上图可以看到,引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。有点类似 C 语言中的指针,但是 Java 中引用要比指针的操作更简单

我们再来看下面这样一个例子:

public static void func() {
    int[] array1 = new int[3];
    array1[0] = 10;
    array1[1] = 20;
    array1[2] = 30;
    int[] array2 = new int[]{1,2,3,4,5};
    array2[0] = 100;
    array2[1] = 200;
    array1 = array2;
    array1[2] = 300;
    array1[3] = 400;
    array2[4] = 500;
    for (int i = 0; i < array2.length; i++) {
        System.out.println(array2[i]);
    }
}

实际上整个func执行时内存空间中出现了这样的变化:
在这里插入图片描述
在这里插入图片描述
除此之外,java中还有二维数组的概念,与C/C++中也大差不差。二维数组的用法和一维数组并没有明显差别, 因此我们不再赘述.
同理, 还存在 “三维数组”, “四维数组” 等更复杂的数组, 只不过出现频率都很低.

更多推荐