java基础

#java

安装jdk(window10)

在Oracle官网下载最新的稳定版jdk(我的是15)

  1. 环境变量设置 :找到java文件bin文件所在地址,将地址复制(D:/JAVA/bin),在控制面板中找到环境变量设置(window10直接左下角搜索框搜索环境变量),在系统变量中找到PATH 选中编辑 添加刚刚的地址。
  2. 测试
    Ctrl/Command + R
    打开命令行,输入Java查看相应信息。有则安装正确。

java程序运行

  1. 打开文本编辑器输入代码,保存为.java文件(不推荐)
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}
  1. 打开命令行,进入.java文件所在文件夹,输入
    java+空格+java文件名。注意文件名与CLASS名称一致。(javac 生成class文件)

顺序:先javac 再java
即先用Javac把.java文件编译成.class文件。再用java+类名(或.class)来运行。

PS: java:这个可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码;
javac:这是Java的编译器,它用于把Java源码文件(以.java后缀结尾)编译为Java字节码文件(以.class后缀结尾);
jar:用于把一组.class文件打包成一个.jar文件,便于发布;
javadoc:用于从Java源码中自动提取注释并生成文档;
jdb:Java调试器,用于开发阶段的运行调试。

ide

Eclipse是由IBM开发并捐赠给开源社区的一个IDE,也是目前应用最广泛的IDE。Eclipse的特点是它本身是Java开发的,并且基于插件结构,即使是对Java开发的支持也是通过插件JDT实现的。
具体设置参考这里

选用eclipse即可。

代码说明

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

这段代码包含一个一个java的基本框架:
类–方法–方法定义;(java本身的关键字要小写,定义的名字对大小写敏感)

类的定义中,我们定义了一个名为main的方法。
()括起来的是方法参数;
花括号{}中间的就是方法的代码。
public、static用来修饰方法,表示它是一个公开的静态方法。
注意与C语言一样,程序应该从main函数开始。
缩进不是必须(跟python不一样),但最好加上。

程序基础

基本结构

1.Java是面向对象的语言,一个程序的基本单位就是class(类),class是关键字。
类名必须以英文字母开头,后接字母,数字和下划线的组合;习惯以大写字母开头。
类前面的public写了可以从命令行直接java 类名执行该段程序。
2.类内部可以有多个方法,public也可以修饰方法。而上面main方法前关键字static是另一个修饰符.(Java入口程序规定的方法必须是静态方法,方法名必须为main,括号内的参数必须是String数组。);方法名也有命名规则,命名和class一样,但是首字母小写。{}里面的是真正的代码,每一行以分数结束。
3.注释
三种方法:1)//注释 2)/*
注释
*/
3)以/*开头,以/结束。这种特殊的多行注释需要写在类和方法的定义处,可以用于自动创建文档。
Eclipse IDE提供了快捷键Ctrl+Shift+F

变量和数据类型(默认有c的基础)

在Java中,变量分为两种:基本类型的变量和引用类型的变量。
1.基本类型很好理解
int x = 1; 基本与c一样。可重新幅值,但重新幅值的时候不用指定变量。注意存储时的结构:只有定义新变量时才会给一个存储单元。
基本数据类型包括:

整数类型:byte,short,int,long
**对于float类型,需要加上f后缀。**
***char类型使用单引号',且仅有一个字符,要和双引号"的字符串类型区分开。**
浮点数类型:float,double

字符类型:char

布尔类型:boolean

2.引用类型
我认为最主要的区别在存储这一块,类似于C语言中的指针。

3.常量
定义变量的时候,如果加上final修饰符,这个变量就变成了常量。(有的编程语言用的const)
4.var关键字
有些时候,类型的名字太长,写起来比较麻烦。例如:

StringBuilder sb = new StringBuilder();
var sb = new StringBuilder();

var自动推断类型符号。
变量的定义遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域,并且,不要重复使用变量名。

整数运算

整数运算和c基本一样。
溢出当最高位计算结果为1,因此,加法结果变成了一个负数。可换成位数更多的变量类型。
简写运算符 +=,-=,*=,/=
自增 ++和–
移位
<< >> 左移会改变符号
<<< >>> 移位空出来的地方会直接补0(一般右移改变符号)
位运算

& | ~ ^ 可直接用于两个整数间的运算。

关于这几个运算优先级的问题建议大家直接加括号。
如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。 也可以在前面加个括号,括号里面放你要要的类型名称,强制转型。(强制类型可能出错)

浮点数运算

在计算机中浮点数运算一般不准确,因为转成二进制时可能是无限的。
由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数。
需要特别注意,在一个复杂的四则运算中,两个整数的运算不会出现自动提升的情况。

布尔运算

布尔运算是一种关系运算,包括以下几类:(与位运算的不同在于更多的是用于逻辑判断)

比较运算符:>,>=,<,<=,==,!=
与运算 &&
或运算 ||
非运算 !

布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。(注意理解,是不考虑后面的计算了,不代表后面的式子是正确的)。

三元运算符

b ? x : y

例如:

int x = n >= 0 ? n : -n;

if b是正确的 这个式子的值就取前面的。否则取后面。

字符类型

因为Java在内存中总是使用Unicode表示字符,所以,一个英文字符和一个中文字符都用一个char类型表示。

如char c1 = ‘A’;

要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可。
\u+Unicode编码来表示一个字符:

字符串类型

String 一个字符串可以存储0个到任意个字符。用双引号"…"表示开始和结束。里面含有双引号可用转义字符/。
常见的转义字符包括:

\" 表示字符"
\' 表示字符'
\\ 表示字符\
\n 表示换行符
\r 表示回车符
\t 表示Tab
\u#### 表示一个Unicode编码的字符

连接用“+”即可
多行字符串:如


 String s = """
                   SELECT * FROM
                     users
                   WHERE id > 100
                   ORDER BY name DESC
            """;
                   

际上是5行,在最后一个DESC后面还有一个\n。如果我们不想在字符串末尾加一个\n,就需把最后的引号提上一行。
指针特性:变量名在这里只相当于钥匙,而不是之前那些变量充当的是门牌号。
引用类型的变量可以指向一个空值null,它表示不存在,即该变量不指向任何对象。

数组

定义结构如下

int[] ns = new int[5];
int[] ns = new int[] { 68, 79, 91, 85, 62 };

数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false;
大小不可重新改变。(个数)
索引从0开始
与字符串类似 都是引用类型(钥匙)。每一次有新元素,钥匙就去开那个们。
字符串数组
注意理解。

基本语句结构

输入、输出语句

输出

System.out.println(输出内容)

格式化–同c语言一样。 用在语句输出时最好。
用占位符代替。(连续两个%%表示一个%字符本身)

输入(比较复杂)
1.导入

import java.util.Scanner;

2.创建对象

Scanner scanner = new Scanner(System.in);

3.读取

String 名字 = scanner.nextLine() //读文字
String 名字=scanner.nextint()//读一个数

编译、运行(java\javac)

选择

结构:

		if (n > 90) {
            System.out.println("优秀");
        } else if (n >= 60) {
            System.out.println("及格了");
        } else {
            System.out.println("挂科了");
        }

条件判断的时候。
相等用 ==
引用类型 相等用 equals (s.equals(n))

switch
代码示例

 		switch (option) {
        case 1:
            System.out.println("Selected 1");
            break;
        case 2:
            System.out.println("Selected 2");
            break;
        case 3:
            System.out.println("Selected 3");
            break;
        default:
            System.out.println("Not selected");
            break;
        }

java12开始可采用模式匹配,这样做可以不用管break的遗漏情况。

 		String fruit = "apple";
        switch (fruit) {
        case "apple" -> System.out.println("Selected apple");
        case "pear" -> System.out.println("Selected pear");
        case "mango" -> {
            System.out.println("Selected mango");
            System.out.println("Good choice!");
        }
        default -> System.out.println("No fruit selected");
        }

这种方法可以直接赋值。

int n=switch(a){

case “…” ->1
}

循环

1.while
示例:

while (条件表达式) {
    循环语句
}

2.do while
示例:

do {
          循环语句
        } while (条件表达式);

注意上面两种结构的不同之处,一个是先判断,一个是先执行。

3.for
示例:

for (int i=1; i<=100; i++) {
            sum = sum + i;
        }

和c语言一致;循环时,千万不要在循环体内修改计数器。

for each
参考代码
n是定义的变量;ns是之前定义的整型数组。

		int[] ns = { 1, 4, 9, 16, 25 };
        for (int n : ns) {
            System.out.println(n);
        }

注意:break会跳出当前循环,也就是整个循环都不会执行了。而continue则是提前结束本次循环,直接继续执行下次循环。
与c语言一致;

一般循环可用于处理一维及多维数组的问题。
2维数组输出的时候可用java带的一个库。

import java.util.Arrays;
System.out.println(Arrays.deepToString(数组名));

命令行参数

Java程序的入口为main方法,而main方法的参数中接受一个命令行参数,它是一个String[]数组。
这个参数是用虚拟机接受用户输入并传给它的。(这里采用的是for循环)
例子:

	public class Main {
    public static void main(String[] args) {
        for (String arg : args) {
            if ("-version".equals(arg)) {
                System.out.println("v 1.0");
                break;
            }
        }
    }
}

面向对象编程基础(OOP)

(以有vb基础为前提)

就是定义对象,并在定义的对象里面赋予其一些方法。调用时采用对象.方法即可运行对应程序。
对象基础:
类、实例、方法。。。
实现方式:继承和多态(多态可能难理解一点)
Java核心类:
字符串、包装类型、JavaBean、 枚举、常用工具类。

实例的定义

class somebody {
    public String name;
    public int age;
}

可以看到一个实例可以包含多个字段(特征:name、age)(数据封装)。

创建

somebody lei = new somebody();

方法

lei在这里是变量,指向这个实例。
调用

lei.age=21;

但这样用(实例.字段)的时候会破坏其封装性。
措施:在类里面把字段的类型改成private,同时增加方法。
示例:

class Person {
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0 || age > 100) {
            throw new IllegalArgumentException("invalid age value");
        }
        this.age = age;
    }
}

这可以看出来,在类里面可以对参数值进行判断。

lei.setName(“Xiao Ming”);

方法定义:

修饰符 方法返回类型 方法名(方法参数列表) {
    若干方法语句;
    return 方法返回值;
}

返回类型选void时可以没有return;

而示例代码里面方法用的是public,当然方法也可以用private。
这表明只能在类内部的方法调用改私有方法。同时getage()是一个实时计算的对外接口。

this

在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段(如果没有命名冲突,可以省略)。
实际就是有时候方法定义的形参名和类的字段名重合时,要注意一下。

可变参数

处理之后没有参数个数定义一致的限制。

	public void setNames(String... names) {
        this.names = names;
    }

注意中间三个省略号。
可以把可变参数改写为String[]类型。(但这种方法不太好,要先自己构造数组

g.setNames(new String[] {“Xiao Ming”, “Xiao Hong”, “Xiao Jun”});

)
而且可以传入null,但前一个方法没有这种困扰。

参数绑定

引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。
基本参数是分开的。

构造方法

实际就是一个与类名相同的方法,一般有一个空的默认构造方法。
在构造方法里面,可以直接传入初值。这样创建一个类的时候可以直接赋初值。

多构造方法

参数个数的不同,对应的幅值不同。

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this.name = name;
        this.age = 12;
    }

    public Person() {
    }
}

方法重载

与多构造方法类似,只是这里用的是普通方法,而不是针对类名。

继承

继承是针对面向对象编程的基础知识。主要是可以解决重复定义的问题。

class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

class Student extends Person {
    // 不要重复name和age字段/方法,
    // 只需要定义新增score字段/方法:
    private int score;

    public int getScore() {}
    public void setScore(int score) {}
}

关键字extend就是实现继承的关键点。继承是面向对象编程中非常强大的一种机制,我们只需在对应类中添加新的字段或者方法。

没有用extend时,实际继承的是Object(就是通常我们说的继承树)

protected是为了解决父类private定义的字段和方法。private定义的字段,在子类中不能调用,但用了protected就可以解决private的问题。

super
super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName,或者this.name,或者name,效果都是一样的。编译器会自动定位到父类的name字段。

但是对于构造函数,必须要调用父类存在的某个构造方法。因为子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

super(name, age);

阻止继承

没有final修饰,任何类都可以从该类继承。(放在class前)(对于方法也可以加上final,放在public后)

用permits写出能从该类继承的子类名称。

public sealed class Shape permits Rect, Circle, Triangle {
    ...
}

向上转型

把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。 —安全的(没有啥条件)

向下转型

把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。
需要先经过向上转型才行
为避免出错,用instanceof判断。

if (n instanceof people) {
    // 只有判断成功才会向下转型:
    people s = (Student) n; // 一定会成功
}

区分继承和组合:
具有has关系不应该使用继承,而是使用组合。即在类里面新建另一个类(相当于所有关系。)

多态

继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。(方法名、方法参数、方法返回值等均相同)
加上@Override可以让编译器帮助检查是否进行了正确的覆写。希望进行覆写,但是不小心写错了方法签名,编译器会报错。
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。而这个就称之为多态 指针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。

super调用

如果要调用父类的被覆写的方法,可以通过super来调用。

super.方法名();

抽象类

为了解决父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:

abstract class Person {
    public abstract void run();
}

方法和类必须同时声明为抽象类。抽象类无法实例化,只能用于被继承。(子类必须实现覆写其定义的抽象方法)

类似

public class Main {
    public static void main(String[] args) {
        Person p = new Student();
        p.run();
    }
}

abstract class Person {
    public abstract void run();
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

好处:我们对其进行方法调用,并不关心Person类型变量的具体子类型。(但我还没感受到有啥方便很多的地方)

接口

抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。
可以把该抽象类直接改写为接口:interface

interface Person {
    void run();
    String getName();
}

接口定义的所有方法默认都是public abstract。
当一个具体的class去实现一个interface时,需要使用implements关键字。

class Student implements Person {…} //接口里面定义过的方法需要覆写。

同时一个类可以实现继承多个interface,普通的类是不可以的,普通的类只有一个爸爸。
接口当然也能继承接口。

什么叫签名方法? 我个人理解就是前面那些自定义了名字但没有具体命令的指令。

default

default方法用在接口定义中,如果接口中新加一个default方法,子类不需要重新定义方法。但是如果是普通方法,子类中需要重新修改。

静态字段和静态方法

static修饰。(在public/privated后)
对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例。(就是定义多个实例时,对任何一个静态字段修改值,所有实例中的该值都会跟着一起修改。)
静态方法内部不能访问this.(因为它不属于实例),它只能访问静态字段。

class Person {
    public static int number;

    public static void setNumber(int value) {
        number = value;
    }
}

编程时实例调用静态方法是因为编译器自动帮我们把实例改成类名而已。
接口是纯抽象类,不能定义实例字段,但可以可有静态字段,且静态字段必须为final类型。(public static final可省去)

public static final int n=1;

Java定义了一种名字空间,称之为包:package。一个类总是属于某个包,类名(比如Person)只是一个简写,真正的完整类名是包名.类名
在定义class的时候,在第一行声明这个class属于哪个包。

package 包名;

在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。(包跟包之间没有package)

package_sample
└─ bin
├─ hong
│ └─ Person.class
│─ ming
│ └─ Person.class
└─ mr
└─ jun
└─ Arrays.class
位于同一个包的类,可以访问包作用域内其他类的字段和方法(创建类)。
引用的时候可以有import方法

import mr.lei.*;(与python类似)----可以直接调用该包类所有class

作用域的问题大家要注意。

内部类

Java的内部类可分为Inner Class、Anonymous Class和Static Nested Class三种:

Inner Class和Anonymous Class本质上是相同的,都必须依附于Outer Class的实例,即隐含地持有Outer.this实例,并拥有Outer Class的private访问权限;

Static Nested Class是独立类,但拥有Outer Class的private访问权限。

参考

classpath

classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。()

在启动JVM时设置classpath才是推荐的做法。实际上就是给java命令传入-classpath或-cp参数
java -classpath .;文件夹名;文件夹名 abc.xyz.Hello

模块(需要时自学吧?哈哈)

.class是JVM看到的最小可执行文件,很多个它堆在一起(大型文件)的时候很不好管理,这时可以用一个容器jar进行管理(具体方法百度),但jar有个问题是它只是用来存放class,不关心他们之间的依赖。claspath如果有某个类漏掉了,会出现错误。
如果a.jar必须依赖另一个b.jar才能运行,那我们应该给a.jar加点说明啥的,让程序在编译和运行的时候能自动定位到b.jar,这种自带“依赖关系”的class容器就是模块。以.jmod结尾。

模块进一步隔离了代码的访问权限。

java 核心类

String

Java编译器对String有特殊处理,可以用**"…"**直接表示,实际上在其内部也是由一个char[]数组表示的。注意在Java中字符串的比较不能用==,而是用equls()函数。忽略大小写的比较函数可以用equalsIgnoreCase().
常用方法:

"Hello".contains("ll");
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true
"Hello".substring(2); // "llo"--索引从0开始。
"Hello".substring(2, 4); "ll"
"  \tHello\r\n ".trim(); // "Hello"--	去除空白字

isEmpty和isBlank的区别是一个是判断是否为空,一个是判断是否为空字符。

s.replace("ll", "~~"); // "he~~o",所有子串"ll"被替换为"~~"

也可以通过正则表达式去除。
分割

String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}

拼接

String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C

格式化字符串:
java里面有formatted和format()两种,前者为s.formatted(参数1,参数2…),后者为string.format(字符串,参数1,参数2…)

类型转换

String.valueOf(123); // “123”
int n1 = Integer.parseInt(“123”); // 123
int n2 = Integer.parseInt(“ff”, 16); // 按十六进制转换,255
lean b1 = Boolean.parseBoolean(“true”); // true
Integer有个getInteger(String)方法,它不是将字符串转换为int,而是把该字符串对应的系统变量转换为Integer:
Integer.getInteger(“java.version”); // 版本号,11

和char[]互相转化

char[] cs = “Hello”.toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String

并且转化后的string相当于是复制了一份char[],和前面的char[]没有关系了就.(java里面有一个clone函数)

字符编码

ANSI:
GB2312:汉字占两个字节,第一个字节的最高位是1
Unicode:需要两个或更多字节,英文前直接加00(统一)
UTF-8:变长编码,英文可以只用1个字节,中文编码

byte[] b1 = "Hello".getBytes(); // 按系统默认编码转换,不推荐
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换

注意是byte().在早期版本中是char[].

字符串的拼接可以直接用+
this

StringBuilder

直接用+会创建新字符串,丢掉旧的.这时就可以用StringBuilder

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',');
    sb.append(i);
}
String s = sb.toString();

链式操作:

public class Main {
    public static void main(String[] args) {
        var sb = new StringBuilder(1024);
        sb.append("Mr ")
          .append("Bob")
          .append("!")
          .insert(0, "Hello, ");
        System.out.println(sb.toString());
    }
}

###StringJoiner
一般用于有分割符且需要加开头和结尾的字符串时.

public class Main {
    public static void main(String[] args) {
        String[] names = {"Bob", "Alice", "Grace"};
        var sj = new StringJoiner(", ", "Hello ", "!");
        for (String name : names) {
            sj.add(name);
        }
        System.out.println(sj.toString());
    }
}

前面提到的Sj.join()内置了这个方法,一般用于没有开头和结尾时.

包装类型

Java的数据类型一般包含两种:
基本类型:int short float等
引用类型:class对象 interface等
基本类型不能为null
如想把int变成一个引用类型就要用包装类的概念:就是包含一个实例字段int的Integer类.

在Java中基本上每一个基本类型都有对应的包装类,可以通过下面的方法进行相互转换.

int i = 100;
Integer n1 = new Integer(i);
Integer n2 = Integer.valueOf(i);
Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()

这其中自动装箱就是把int变成Integer,反过来就是自动拆箱.—这只是在编译时自动变换的!
包装类都是不变类(final)
要注意的是,在比较大小时用equls()函数.—用Integer n2 = Integer.valueOf(i)的方式创建.

转换:

int x1 = Integer.parseInt(“100”); // 100
Integer.toHexString(100)

JavaBean

若干private实例字段;
通过public方法来读写实例字段。
如果读写方法符合以下这种命名规范
// 读方法:
public Type getXyz()
// 写方法:
public void  setXyz(Type value)

这样的对象就是JavaBean.
boolean的读方法为is开头.
JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,主要用在图形界面的可视化设计中。

要枚举一个JavaBean的所有属性,可以直接使用Java核心库提供的Introspector.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值