理解String 类

理解String 类

引言

我们都知道在C语言中是没有字符串类型的,C语言中,最常用的就是将字符串放进一个数组中,之后,对数组进行一些操作。而在 Java / C++ 中,有直接表示字符串的类型。在Java 中,String 类型就是表示字符串类型,同时它也是引用类型。比方说:

String str = "abcd";

没错,在 Java 中,字符串就是上述这样定义并初始化的,而 abcd 的末尾没有像 C语言那样有 \0,在 Java 中,里面就是 abcd.
在上面的一行代码中,被双引号引起来的 abcd 就叫做字符串,它是有a、b、c、d 这四个字符组成的,而 abcd 又属于字面值常量,其本质是常量,不可以被更改。

一、创建字符串的方式

1. 直接赋值

程序清单1:

public class Test1 {
    public static void main(String[] args) {
        String str1 = "hello";
		System.out.println(str1); 
		//输出结果:hello        
    }
}

2. 通过使用构造方法

程序清单2:

public class Test2 {
    public static void main(String[] args) {
        String str2 = new String("world");
        System.out.println(str2); 
        //输出结果:world
    }
}

3. 通过使用字符数组转换成字符串

程序清单3:

public class Test3 {
    public static void main(String[] args) {
        char[] chars = {'x','y','z'};
        String str3 = new String(chars);      
        System.out.println(str3); 
        //输出结果:xyz
    }
}

二、字符串的引用变量比较相等

1. 情况一

在程序清单4中,例如(str1 == str2)的这种形式,比较的其实是两个引用的地址,而不是字符串的内容 " hello ",这点需要理解,请往下看:

程序清单4:

public class Test4 {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
    }
}

输出结果:
out
图解上述代码:
分析:
String 类的设计使用了共享设计模式:
如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中。如果下次继续使用直接赋值的模式声明 String 类对象,此时对象池之中如若有指定内容,将直接进行引用。
如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。

2. 情况二

程序清单5:

public class Test5 {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(str1 == str2);
    }
}

输出结果:
out
图解上述代码:
分析

3. 情况三

程序清单6:

public class Test6 {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = new String("hello");
        System.out.println(str1 == str2);
    }
}

输出结果:
out
图解上述代码:
分析

总结:

  • 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。

  • 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern( ) 方法手工入池。

常量池的一些概念:

运行时常量池:当程序把编译好的字节码文件,加载到JVM中后,会生成一个运行时常量池(方法区),实际上是 Class 文件常量池。
字符串常量池:主要存放字符串常量,本质上是一个哈希表,StringTable,双引号引起来的字符串常量。

三、理解字符串不可变

String 类的内部实现是基于 char[ ] 来实现的,但是 String 类并没有提供 set 方法之类的来修改内部的字符数组。

程序清单7:

public class Test7 {
    public static void main(String[] args) {
        String str1 = "abc";
        System.out.println(str1);
        str1 = "xyz";
        System.out.println(str1);
    }
}

输出结果:
out

注意:在这里,我们并不是通过把字符串的内容修改了,而是将 [ str1 引用 ] 引用了新的对象。

分析

程序清单8:

public class Test8 {
    public static void main(String[] args) {
        String str = "hello" ;
        str = str + " world" ;
        str += " !!!" ;
        System.out.println(str);
    }
}

输出结果:
out

同样地,在程序清单8中,我们并不是将 " hello " 中的内容修改了,而是先通过 " hello " 和 " world " 创建了一个新的对象 " hello world ",之后又通过 " hello world " 和 " !!! " 创建了另一个新的对象 “hello world !!!”。对于这样的操作,相当于每次都要通过关键字 new 实例化一个对象。所以,程序清单8是一个不好的示范,对于程序员来说,不应该这么做。

四、字符、字节、字符串、数组

1. 将字符数组转换成字符串

程序清单9:

public class Test9 {
    public static void main(String[] args) {
        char[] chars = {'a','b','c','d'};
        String str = new String(chars);
        System.out.println(str);
    }
}
//输出结果:abcd

2. 拿到字符串中的连续字符

程序清单10:

public class Test10 {
    public static void main(String[] args) {
        char[] chars = {'a','b','c','d'};
        String str = new String(chars, 1, 2);
        System.out.println(str);
    }
}
//输出结果:bc
String str1 = new String(char[] chars,int offset, int count);

在 String 构造方法中,其中 offset 表示偏移量,count 表示偏移个数,若 offset 为 1,count 为 2,那么,实现 str1 就从数组下标1开始,往后拿2个元素。当然,我们应该注意不能越界哦。

3. charAt( ) 方法 [ 常用 ]

程序清单11:

public class Test11 {
    public static void main(String[] args) {
        String str = "abcde";
        char ch = str.charAt(2);
        System.out.println(ch);
    }
}
//输出结果:c
charAt(int index)

index 表示指定位置的索引,字符串第一个字符索引为 0。
同样地,我们应该注意不能越界哦。

4. 将字符串转变成字符数组 [ 常用 ]

程序清单12:

public class Test12 {
    public static void main(String[] args) {
        String str = "abcde";
        char[] chars = str.toCharArray();
        System.out.println(Arrays.toString(chars));
    }
}
//输出结果:[a, b, c, d, e]

上面四个程序清单说明的是字符数组与字符串之间的一些操作,如果把字符数组换成字节数组,整型数组等等…其对应的思想是一样的。感兴趣的小伙伴可以自己试一下字节与字符串之间的关系。

5. 判断字符串是是由字符构成还是由数字构成

判断一个字符串是否是由字符构成

思路:我们创建一个 judge( ) 方法来判定每个字符是否由字母组成即可,所以我们遍历整个字符串的长度,然后通过下面两行代码来验证每一个字符:

char ch = str.charAt(i);
boolean sign = Character.isLetter(ch);

程序清单13:

public class Test13 {
    public static boolean judge(String str){
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            boolean sign = Character.isLetter(ch);
            if(sign == false){
                return false;
            }
        }
        return true;
    }
    public static void main(String[] args) {
        String str = "abcde";
        System.out.println(judge(str));
    }
}
//输出结果:true

当然,我们也可以判定是否每个字符是数字,只不过逻辑需要改变一下,代码如下:

char ch = str.charAt(i);
boolean sign = Character.isDigit(ch);

五、字符串比较

1. equals( ) 方法

查看底层代码,通过 equals( ) 方法比较字符串的内容,返回类型是布尔类型。

底层

程序清单14:

public class Test14 {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = new String("hello");
        System.out.println(str1.equals(str2));

        String str3 = null;
        String str4 = "hello";
        //System.out.println(str3.equals(str4)); //空指针异常
        System.out.println(str4.equals(str3));
    }
}

输出结果:

out

注意,在程序清单14中,str3 和 str4 不能互换位置,因为 str3 这个引用本身不指向任何对象,如果继续使用 str3 的话,会造成空指针异常,我已经通过注释标明出来了。

在程序清单15中,我们对程序清单14做出了一些改变,当我们忽视大小写的时候,可以使用 equalsIgnoreCase( ) 方法。

程序清单15:

public class Test15 {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "HeLLo";
        System.out.println(str1.equalsIgnoreCase(str2));
    }
}

输出结果:

out

2. compareTo( ) 方法

当查看底层代码,我们发现 String 类型实现了 Comparable接口,那么String 类就会重写 compareTo( ) 方法,返回值是整型。
底层1

底层2

接下来,我们通过程序清单16来演示一下 compareTo( ) 方法是怎么使用的。

程序清单16:

public class Test16 {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "ABC";
        int ret1 = str1.compareTo(str2);
        System.out.println(ret1);
        System.out.println("--------------");

        String str3 = "abc";
        String str4 = "acb";
        int ret2 = str3.compareTo(str4);
        System.out.println(ret2);
        System.out.println("--------------");

        String str5 = "abcde";
        String str6 = "ab";
        int ret3 = str5.compareTo(str6);
        System.out.println(ret3);
    }
}

输出结果:
out

在程序清单16中,compareTo( ) 方法是通过对比字符串中的字符一个一个进行比较的,字符对应的 Unicode 编码之差就是返回值( ASCII 码 )。如果两个字符串长度不等,返回的就是字符串长度之差。

六、字符串查找

1. contains( ) 方法

主串通过 contains( ) 方法 调用子串

程序清单17:

public class Test17 {
    public static void main(String[] args) {
        String str1 = "abcdef";
        String str2 = "cde";
        System.out.println(str1.contains(str2));
    }
}

输出结果:

out

2. indexOf( ) 方法

主串通过 indexOf( ) 方法 调用子串

如果主串中有子串的数据,那么就返回主串中的索引,即子串对应的第一个字符的位置,如果主串中没有子串的数据,则返回 -1.

程序清单18:

public class Test18 {
    public static void main(String[] args) {
        String str1 = "abcdef";
        String str2 = "cde";
        String str3 = "dec";
        System.out.println(str1.indexOf(str2));
        System.out.println(str1.indexOf(str3));
    }
}

输出结果:

out

3. lastIndexOf( ) 方法

indexOf( ) 方法是在主串中从前往后找子串,lastIndexOf( ) 方法是在主串中从后往前找子串。虽然 lastIndexOf( ) 方法是从后往前找,但是输出的索引下标依然是从前往后算的。只不过,如果两个方法面对重复的字符,会出现不同的结果。 如下所示:

程序清单19:

public class Test19 {
    public static void main(String[] args) {
        String str1 = "{abcd}}";
        String str2 = "}";
        System.out.println(str1.lastIndexOf(str2));
        System.out.println(str1.indexOf(str2));
    }
}

输出结果:

2

拓展:indexOf( ) 方法 和 lastIndexOf( ) 方法中可以添加第二个整型参数 : ( fromIndex ),表示从某个位置开始找,感兴趣的小伙伴可以自己测试一下。

4. startsWith( ) 方法

字符串通过调用 startsWith( ) 方法,来判断主串的头部是否是我们要输入的数据。传参的时候可以是一个参数,也可以是两个参数。第一个参数可以是字符,也可以是字符串;第二个参数表示的是偏移量,旨在通过某个位置开始判断。

代码如下:

程序清单20:

public class Test20 {
    public static void main(String[] args) {
        String str1 = "abcdef";
        System.out.println(str1.startsWith("a"));
        System.out.println(str1.startsWith("bcd"));
        System.out.println(str1.startsWith("c",2));
        //表示从偏移位置2 往后判断
    }
}

输出结果:

out

5. endsWith( ) 方法

startsWith( ) 方法是从头部开始判断,而 endsWith( ) 方法是从尾部开始判断,思想是相同的,但是 endsWith( ) 方法 在传参的时候,只能传一个参数,即我们要判断的字符或字符串。

程序清单21:

public class Test21 {
    public static void main(String[] args) {
        String str1 = "abcdef";
        String str2 = "cde";
        System.out.println(str1.endsWith("f"));
        System.out.println(str1.endsWith("bcd"));
    }
}

输出结果:

out

七、字符串替换

1. replace( ) 方法

我们看到下图 replace( ) 方法,在底层的代码,当替换成功的时候,返回的是一个新创建的对象,而需要替换的字符的内容和输入字符的内容相同的时候,说明无须替换,那么这个时候,就返回原来的引用。

这就说明一个问题,在上文中的目录,我提到了一个关键点:字符串的内容不可变,也就是说,当我们表面上好像改变了原先字符串的数据,但实际上底层操作的时候,为我们 new 了一个新的对象,所以说我们看到被替换后的字符串,实际意义上是一个完完整整的一个新串,与原先的字符串没有半点关系。

底层

程序清单22:

public class Test22 {
    public static void main(String[] args) {
        String str = "abcabeabdabxy";
        String ret1 = str.replace('a','z');
        String ret2 = str.replace("ab","zz");
        String ret3 = str.replace("ab","zzz");
        String ret4 = str.replaceFirst("ab","zz");
        System.out.println(ret1);
        System.out.println(ret2);
        System.out.println(ret3);
        System.out.println(ret4);
    }
}

输出结果:

out

八、字符串拆分

1. split( ) 方法

我们看到 split( ) 方法底层代码的返回类型是 String[ ] 类型,即一个字符串数组的类型。
底层

程序清单23:

public class Test23 {
    public static void main(String[] args) {
        String str = "name=Jack&age=23";
        String[] strings = str.split("&");
        for (String s:strings) {
            System.out.println(s);
        }
    }
}

输出结果:

out
在我们通过 IDEA 编译器调试之后,我们可以发现字符串数组中下标 0 和 1 放了两个字符串。
调试
我们需要注意的是:如果我们单纯地调用 split( ) 方法,只会分割一次,因为底层代码就是这么实现的,在后面我会提到多次分割。

程序清单24:

public class Test24 {
    public static void main(String[] args) {
        String str = "name=Jack&age=23";
        String[] strings = str.split("=", 2);
        for (String s:strings) {
            System.out.println(s);
        }
    }
}

输出结果:

out
这里我们需要注意下面的代码:在 split( “=”, 2 ) 中的参数2,代表的是按 " = " 分割成两组,而不是均匀分割成两组。

String[] strings = str.split("=", 2);

2. 使用 split( ) 方法 进行二次分割

程序清单25:

public class Test25 {
    public static void main(String[] args) {
        String str = "name=Jack&age=23";
        String[] strings1 = str.split("&");
        for (String s:strings1) {
            String[] strings2 = s.split("=");
            for (String ss: strings2) {
                System.out.println(ss);
            }
        }
    }
}

输出结果:

out
在这里我就不进行调试了,调试的过程中是动态的,感兴趣的小伙伴可以自己她是过后观察。

3. 字符串拆分的特殊情况

对于一些转义字符的特殊情况,我们在给 split( ) 方法传参的时候,需要考虑进去。比方说:在程序清单26中,我们通过字符 " . " 来分割字符串,然而我们不能直接就这么传参了,我们需要通过 ’ \ ’ 转义才能达到我们的目的。
此外,字符 " | ‘’," * " , " + " 等等都得加上转义字符。

程序清单26:

public class Test26 {
    public static void main(String[] args) {
        String str = "192.168.1.1" ;
        String[] strings = str.split("\\.") ;
        for(String s: strings) {
            System.out.println(s);
        }
    }
}

输出结果:

out

4. 按连接符实现多次拆分

在程序清单26中,我们可以通过连接符 ’ | ’ 实现不同分隔符之间的拆分。

程序清单26:

public class Test26 {
    public static void main(String[] args) {
        String str = "name=Jack&age=23";
        String[] strings = str.split("=|&|=");
        for (String s:strings) {
            System.out.println(s);
        }
    }
}

输出结果:

out

九、字符串截取

1. subString( ) 方法

subString( ) 方法的底层代码,可以看到其返回类型是 String 类型。
并且,返回时创建了新的对象。
底层
我们先来展示程序清单27,再来分析代码是如何实现的。

程序清单27:

public class Test27 {
    public static void main(String[] args) {
        String str1 = "abcdef";
        String str2 = str1.substring(2);
        String str3 = str1.substring(0);
        String str4 = str1.substring(2,5);
        System.out.println(str2);
        System.out.println(str3);
        System.out.println(str4);
    }
}

输出结果:

out
分析程序清单27的代码:

输出 str2 :是从字符串下标索引为2的字符开始往后截取(包括2下标),底层代码 new 了一个新对象。

输出 str3 :字符串下标索引为0的字符开始往后截取,等于拿到了原先的对象,这时候并没有重新创建对象。

输出 str4 :subString( ) 方法遵循左闭右开,左边下标索引为2可以取到字符,右边下标索引为5取不到字符。同时,底层代码 new 了一个新对象。

十、其他的一些字符串操作

1. trim( ) 方法

trim( ) 方法 用来除去字符串两边的空格,代码如下:

程序清单28:

public class Test28 {
    public static void main(String[] args) {
        String str1 = "    abc  xyz    ";
        System.out.print(str1);
        System.out.println("------------");
        String str2 = str1.trim();
        System.out.print(str2);
        System.out.println("------------");
    }
}

输出结果:

out

2. 将字符串中的字符转换成大写 / 小写 [ 常用 ]

toUpperCase( ) 方法 和 toLowerCase( ) 方法

程序清单29:

public class Test29 {
    public static void main(String[] args) {
        String str1 = "abcdWXYZ123";
        String str2 = str1.toUpperCase();
        String str3 = str1.toLowerCase();
        System.out.println(str2);
        System.out.println(str3);
    }
}

输出结果:

out

3. 拼接字符串

程序清单30:

public class Test30 {
    public static void main(String[] args) {
        String str1 = "abcd";
        String str2 = "wxyz";
        String str3 = str1.concat(str2);
        System.out.println(str3);
    }
}

输出结果:
out

4. 求字符串长度 [ 常用 ]

求字符串长度并不难,而我想说的是,这里需要区分:字符串求长度的格式 str.length( ) 和 数组求长度的个数的格式 arr.length。注意点:一个有括号,一个没括号。

程序清单31:

public class Test31 {
    public static void main(String[] args) {
        String str = "abcde";
        System.out.println(str.length());
        int[] arr = {1,2,3,4,5};
        System.out.println(arr.length);
    }
}

输出结果:

out

5. 判断字符串是否为空

程序清单32:

public class Test32 {
    public static void main(String[] args) {
        String str1 = "abcd";
        String str2 = " ";
        String str3 = "";
        System.out.println(str1.isEmpty());
        System.out.println(str2.isEmpty());
        System.out.println(str3.isEmpty());
    }
}

输出结果:

out

十一、理解引用

程序清单33:

public class Test {

    public void change(String s, char[] ch){
        s = "world";
        ch[0] = 'g';
    }

    String str = new String("hello");
    char[] chars = {'a', 'b', 'c'};

    public static void main(String args[]){
        Test test = new Test();
        test.change(test.str, test.chars);
        System.out.println(test.str);
        System.out.println(test.chars);
    }
}

输出结果:
out
这一题在我第一次做的时候,自己认为输出结果会是下面代码:

//错误答案
world
gbc

后来,我才想明白,当我们传参的时候,传了一个是 String 类型,一个是 char[ ] 类型,这两者在传参的时候传的其实都是引用类型。(这其实就是实参和形参之间对应的关系,通过改变形参并不影响传入的实参状态

说白了,就是把对象的地址传过去了。那么在 change( ) 方法接收的时候,等于拿到了两者的地址。后来,我们改变了引用变量 s ,让它指向了 " world “,那么就不再指向原先的” hello ",所以最终引用 s 与主函数中的引用 str 没有半毛钱关系。而引用 ch 就不同了,它是通过 ch[0] 直接访问到原先数组,通过改变数组下标为 0 的元素值,这会破坏原先数组的结构,所以最终数组元素变成了 [ gbc ]。 下图辅助理解:

分析

十二、StringBuilder 类 和 StringBuffer 类

我们查看底层代码,发现 StringBuilder 类 在底层实现的时候,调用了 append( ) 方法,在 append( ) 方法中返回的是原先的对象,并没有重新 new 一个新的对象。append( ) 方法在底层代码实现的作用就是:可以拼接字符串,然后一次一次地将字符串放入原先的对象中。

out
底层

底层
我们查看底层代码,发现 StringBuffer 类 在底层实现的时候,和上面的 StringBuilder 类很相似,唯一不同的就是 StringBuffer 类多了一个限定符 synchronized,这表示 StringBuffer 采用了同步处理,属于线程安全操作,会使程序执行起来更加安全,当然也会使程序执行起来相对 StringBuilder 于更慢一点。

底层

底层

底层

在介绍过 StringBuilder 类 和 StringBuffer 类 两者对应的底层代码之后,因为两者很多地方是相似的,那么我直接以 StringBuilder 类 来演示一些操作字符串的方法。

1. append( ) 方法

append( ) 方法可以用来拼接很多不同类型的变量放入一个 StringBuilder 类的变量中,我们先来看一下编译器中 append( ) 方法,可以拼接的东西应有尽有!
图

程序清单34:

public class Test34 {
    public static void main(String[] args) {
        StringBuilder str = new StringBuilder("abc");
        System.out.println(str);
        System.out.println("----------------");
        str.append("opq");
        System.out.println(str);
        System.out.println("----------------");
        str.append("xyz");
        System.out.println(str);
    }
}

输出结果:
out
在程序清单34中,我们可以发现 append( ) 方法在放字符串的时候,每次都放入了 str 中,那么实际上被改变的就是原先对象中的字符串内容。

2. 字符串逆置

程序清单35:

public class Test35 {
    public static void main(String[] args) {
        StringBuilder str = new StringBuilder("abcde");
        System.out.println(str);
        System.out.println(str.reverse());
    }
}

输出结果:
out

3. StringBuilder 类 和 StringBuffer 类 的其他一些方法

在程序清单36中,通过 delete( ) 方法,我们删除对应索引下标的字符,遵循左闭右开原则。

程序清单36:

public class Test36 {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("abcdefg");
        System.out.println(str);
        System.out.println(str.delete(1,4));
    }
}

输出结果:
out

在程序清单37中,通过 insert( ) 方法,我们在对应的索引下标处添加对应的数据。

程序清单37:

public class Test37 {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("abcde");
        System.out.println(str);
        System.out.println(str.insert(0, "你好"));
        System.out.println(str.insert(2, "hello"));
    }
}

输出结果:
out

4. String、StringBuffer、StringBuilder 三者的区别

① String的内容不可修改,StringBuffer与StringBuilder的内容可以修改

② StringBuffer与StringBuilder大部分功能是相似的

③ StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作

5. String 和 StringBuffer、StringBuilder 之间的转换

在这里,我们需要理解一点:String 表示 String 类,而 StringBuilder 是另一种类,StringBuffer 同样也是另一种类,这三者性质不同,但是可以互相转换。

在程序清单38中,我演示了两种转换形式:

StringBuilder / StringBuffer=String 类
调用 toString( ) 方法

② String=StringBuilder / StringBuffer 类
利用 StringBuilder / StringBuffer 的构造方法

程序清单38:

public class Test {
    public static String transform1(){
        StringBuilder strb = new StringBuilder();
        strb.append(111);
        strb.append("hello");
        return strb.toString();
        //return strb; //error
    }
    public static StringBuilder transform2(){
        String str = "world";
        return new StringBuilder(str);
        //return str; //error
    }
    public static void main(String[] args) {
        System.out.println(transform1());
        System.out.println(transform2());
    }
}

输出结果:

out

十三、其他类型与字符串类型之间的转换

1. 整型转字符串

程序清单39:

public class Test {
    public static String transform1(){
        return Integer.toString(123);
    }
    public static String transform2(){
        return Character.toString('c');
    }
    public static void main(String[] args) {
        System.out.println(transform1());
        System.out.println(transform2());
    }
}

输出结果:
out
在上面,我演示了 toString( ) 方法
当然,我们也可以使用 String.valueOf ( ) 方法,此外,我们可以进行转换的类型有很多很多。

分析

2. 字符串转整型

在下面的程序中,我先将整个字符串分割为一个数组元素中的三个元素,我将数组的第一个元素转换成了整数,使用了【 Integer.parseInt() 】方法,后两个元素直接赋值给了字符串。后来我又将刚刚数组的第一个元素转换成了字符串,通过给其加上一个空字符即可。

程序清单40:

public class Test {
    public static void main(String[] args) {
        String str = "123;opq;xyz";
        String[] result = str.split(";");
        for (String s : result) {
            System.out.print(s +" ");
        }
        int a = Integer.parseInt(result[0]);
        String b = result[1];
        String c = result[2];

        System.out.print("\n"+a +" ");
        System.out.print(b +" ");
        System.out.print(c +" \n");
        System.out.println("------------------");
        String str2 = result[0] + "";
        System.out.println(str2);
    }
}

输出结果:

1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十七ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值