C#从入门到精通(三)

C#从入门到精通(三)

1.面向对象的概念

(1)我们将一些具有相同属性和相同方法的对象进行进一步的封装,抽象出来类的这个概念。类就是一个模子,确定了对象应该具有的属性和方法。
(2)对象是根据类创建出来的。
(3)类就是一个盖大楼的图纸,对象就是盖出来的大楼。

2.类

语法:

[public] class 类名
{
	字段;
	属性;
	方法;
}

写好一个类之后,我们需要创建这个类的对象,
那么,我们管创建这个类的对象过程称之为类的实例化。
使用关键字 new。

this表示当前这个类的对象;
类是不占内存的,而对象是占内存的。

结构与类的区别:结构不具有多肽。

3.属性 (重要

属性的作用就是保护字段、对字段的 赋值和取值进行限定。
属性的本质就是两个方法,一个叫get(),一个叫set()。

        private string _name;
        //写一个属性去保护字段(field),限制非法的输入
        public string Name
        {
            //当你输出属性的值的时候 会执行get的方法
            get{ return _name; }
            //当你给属性赋值的时候 首先会执行set方法
            set { _name = value; }
        }

既有get()也有set()我们称之为可读可写属性。
只有get()没有set()我们称之为只读属性。
没有get()只有set()我们称之为只读属性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Person
    {
        private string _name;
        //写一个属性去保护字段(field),限制非法的输入
        public string Name
        {
            //当你输出属性的值的时候 会执行get的方法
            get{ return _name; }
            //当你给属性赋值的时候 首先会执行set方法
            set
            {
                _name = value;
            }
        }

        private int _age;
        public int Age
        {
            get
            {
                if (_age > 100 || _age < 0)
                {
                    _age = 0;
                }
                return _age;
            }
            set
            {
                if (value > 100 || value < 0)
                {
                    value = 0;
                }
                _age = value;
            }
        }

        private char _gender;
        public char Gender
        {
            get
            {
                if(_gender!='男' && _gender!='女')
                {
                   return _gender = '男';
                }
                return _gender;
            }
            set { _gender = value; }
        }

        //如果输出的时字段而不是属性,就不会访问get()方法
        public void CHLSS()
        {
            Console.WriteLine("我叫{0},今年{1}岁了,性别{2},我可以吃喝拉撒睡",this.Name,this.Age,this.Gender);
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            Person huaHua = new Person();
            huaHua.Name = "花花";
            huaHua.Age = -23;
            huaHua.Gender = '男';

            huaHua.CHLSS();
            Console.ReadKey();
        }
    }
}

4.对象的初始化

当我们创建好一个对象之后,需要给这个对象的每个属性进行赋值。我们莞这个过程称之为对象的实例化。

5.静态和非静态的区别

(1)在非静态类中,既可以有实例成员,也可以有静态成员。
(2)在调用实例成员的时候,需要使用对象名.实例成员;
在调用静态成员的时候,需要使用类名.静态成员名;
(3)总结:
①静态成员必须使用类名去调用,而实例成员使用对象名调用。
②静态函数中,只能访问静态成员,不允许访问实例成员。
③实例函数中,既可以使用静态成员,也可以使用实例成员。
④静态类中只允许有静态成员,不允许出现实例成员。
(4)使用:
①如果你想要你的类当做一个“工具类”去使用,这个时候可以考虑将类写成静态的;
②静态类在整个项目中资源共享。
(5)释放资源。

堆 栈 静态存储区域

只有在程序全部结束之后,静态类才回释放资源。

GC:Garbage Collecttion(垃圾回收器)

6.构造函数

作用:帮助我们初始化对象(给对象的每个属性依次的赋值)
构造函数时一个特殊的方法:
(1)构造函数没有返回值,连void也不用写。
(2)构造函数的名称必须跟类名一样。

注意:
①创建对象的时候会执行构造函数。
②构造函数可以实现重载
③类当中有一个默认的无参数的构造函数,当你写一个新的构造函数之后,不管是不是带参数的,那个默认的无参数的构造函数都会被干掉。

7.new关键字

Person p = new Person();

new帮我们做了三件事情:
①在内存中开辟一块空间;
②在开辟的空间中创建对象;
③调用对象的构造函数进行初始化对象。

8.this关键字

(1)代表当前类的对象;
(2)在类当中显示地调用本类的构造函数;
调用方法:

:this

9.析构函数

调用析构函数能够立即帮助我们释放内存,如果不调用的话,系统会自用GC帮助我们释放内存。
GC:Garbage Collection

~Student()
{
	Console.Writeline("我是析构函数")}

10.命名空间

(1)命名空间
可以认为类时属于命名空间的。
如果在当前项目中没有这个类的命名空间,需要我们手动的导入这个类所在的命名空间。
①用鼠标点击类名左下角添加;
②alt+shift+F10;
③记住命名空间,手动的去引用。

(2)在一个项目中引用另一个项目的类
①添加引用
②引用命名空间
在这里插入图片描述
在这里插入图片描述

11.值类型和引用类型

区别:
1、值类型和引用类型在内存上存储的地方不一样。
2、在传递值类型和传递引用类型的时候,传递的方式不一样。
注意:值类型我们称之为值传递,引用类型我们称之为引用传递。
3.我们学的值类型和引用类型:

值类型:int double bool char decimal struct enum
引用类型:string 自定义类 数组

4.存储:
值类型的值是存储在内存的栈当中;
引用类型的值是存储在内存的堆当中。
在这里插入图片描述

12.字符串

(1)字符串的不可变性。
当你给一个字符串重新赋值之后,老值并没有销毁,而是重新开辟了一块内存空间存储新的值。
在这里插入图片描述
当程序结束之后,GC扫描整个内存,如果发现有的空间没有被指向,则立即将它销毁。

(2)我们可以将字符串看作是char类型的一个只读数组。(只读不改)
举个例子:
ToCharArray();将字符串转换为char数组。
new string(char[] c):能够将char数组转换成为字符串。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            //可以将string类型 看作是char类型的一个只读数组
            string s = "abcdefg";
            //s[0] = 'b';(这样做是不可以的,因为只是只读的)
            //首先将字符串转换成char类型的数组
            char[] c = s.ToCharArray();
            c[0] = 'b';
            //将字符数组转换成为我们的字符数组
            s = new string(c); 

            //既然可以将string看做是char类型的只读数组,所以可以通过下标去访问字符串中的某个元素
            Console.WriteLine(c[0]);
            Console.WriteLine(s);
            Console.ReadKey();
        }
    }
}

            StringBuilder sb = new StringBuilder();
            string str = null;
            //时间:00:00:01.3013044
            //时间:00:00:00.0070914
            //创建一个定时器,用来记录程序运行的时间
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 50000; i++)
            {
                //str += i;
                sb.Append(i);
            }
            sw.Stop();

            Console.WriteLine(sb.ToString());
            Console.WriteLine(sw.Elapsed);
            Console.ReadKey();

(3)字符串提供了各种方法:
①Length:获得当前字符串中的字符数组的个数
②ToUpper():将字符串转换成大写形式
③ToLower():将字符串转换成小写形式
④Equals(string _,StringComparison.OrdinalIgnoreCase):比较两个字符串,忽略大小写。
⑤Sprlit():字符串分割,返回字符串的类型的数组。
⑥Substring():截取字符串。在截取的时候包含要截取的那个位置。
⑦Indexof():判断某个字符在字符串中第一次出现的位置,如果没有就返回-1。
⑧LastIndexof():判断某个字符在字符串中最后一次出现的位置,如果没有就返回-1。
⑨StartsWith():判断以…开始。
⑩EndWith():判断以…结束。
11.Replace():将字符串中某个字符串替换成一个新的字符串。
12.Contains():判断某个字符串是否包含指定的字符串。
13.Trim():去掉字符串中前后的空格。
14.TrimEnd():去掉字符串中结尾的空格。
15.TrimStart():去掉字符串中前面的空格。
16.string.IsNullOrEmpty():判断一个字符串是否为空或者为null。
17.string.Join():将数组按照指定的字符串连接,返回一个字符串。
(4)字符串练习:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //string str = "abcdefgefefefefef";
            //int index = str.IndexOf('e');
            //Console.WriteLine("第一次出现e的位置是{0}", index);
            //int count = 1;
            //for (int i = 0; i < str.Length; i++)
            //{
            //    count++;
            //    while (index != -1)
            //    {
            //        index = str.IndexOf('e', index + 1);
            //        if (index == -1)
            //        {
            //            break;
            //        }
            //        Console.WriteLine("第{0}次出现e的位置是{1}", count, index);
            //    }
            //}
            string[] str1 = { "a", "b", "c", "d" };
            string strNew1 = string.Join('|' , str1);
            Console.WriteLine(strNew1);
            string[] str2 = strNew1.Split(new char[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < str2.Length; i++)
            {
                Console.Write(str2[i]);
            }
        }
    }
}

12.继承

主要作用是解决类中的代码冗余。
子类可以继承父类的属性、方法和函数。 子类默认调用父类无参数的构造函数。
在这里插入图片描述

我们可能会在一些类中,写一些重复的成员,我们可以将这些重复的成员单独地封装到一个类当中,作为这些类的父类。
首先,子类继承了父类的属性和方法,但是子类并没有继承父类的私有字段。
非常重要:

问:子类有没有继承父类的构造函数?
答:子类并没有继承父类的构造函数。但是,子类会默认的调用父类无参数的构造函数,创建父类对象,让子类可以使用父类中的成员。
所以,如果在父类中重新写了一个有参数的构造函数之后,那个无参数的就被干掉了,
子类就调用不到了,所以子类会报错。
解决办法:
方法一:在父类中重新写一个无参数的构造函数;
方法二:在子类中显示的调用父类的构造函数,使用关键字:base ()

13.继承的特性

(1)继承的单根性:一个子类只能继承一个父类。
(2)继承的传递性:爷爷穿给父亲,父亲传给儿子。

14.object是所有类的基类

15.new关键字

(1)创建对象
(2)隐藏从父类哪里继承过来的同名成员。
隐藏的后果就是子类调用不到父类的成员。

16.里氏转换(非常重要)

1.里氏转换
(1)子类可以赋值给父类
(2)如果父类中装的是子类对象,那么可以将这个父类强转换为子类对象。

Person p = new Student();
Student ss = (Student)p;

(3)子类对象可以调用父类的成员,但是父类对象永远都只能调用自己的成员。

(4)新知识(重要)

is:表示类型转换,如果能够转换成功,则返回一个true,否则返回一个falseas:表示类型转换,如果能够转换则返回对应的对象,否则就返回一个null

is的用法:

            //子类可以转换为父类
            Person p = new Student();
            //如果父类中装的是子类对象,那么可以将这个父类强转换成为子类对象
            if(p is Student)
            {
                Student s = (Student)p;
                s.StudentSayHello();
            }
            else
            {
                Console.WriteLine("转换失败");
            }
            Console.ReadKey();
using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Person[] p = new Person[10];
            Random r = new Random();

            for (int i = 0; i < p.Length; i++)
            {
                int number = r.Next(1, 4);
                switch(number)
                {
                    case 1:
                        p[i] = new Student();
                        break;
                    case 2:
                        p[i] = new Teacher();
                        break;
                    case 3:
                        p[i] = new Person();
                        break;
                }
            }

            for (int i = 0; i < p.Length; i++)
            {
                if(p[i] is Student)
                {
                    //Student s = (Student)p[i];
                    //s.StudentSayHello();
                    ((Student)p[i]).StudentSayHello();
                }
                else if(p[i] is  Teacher)
                {
                    //Teacher t = (Teacher)p[i];
                    //t.TeacherSayHello();
                    ((Teacher)p[i]).TeacherSayHello();
                }
                else
                {
                    p[i].PersonSayHello();
                }
            }

            Console.WriteLine();
        }
    }
}

17 .ArrayList集合方法
(1)各种方法请看下面:

//创建一个集合对象
ArrayList list = new ArrayList();
//集合:很多数据的一个集合
//数组:长度不可变、类型单一
//集合的好处:长度可以任意改变 类型随便

list.Add('g');//添加单个元素

list.AddRange(new int[] { 1,2,3,4,5,6});//添加一个集合

list.Clear();//清空所有元素

list.Remove(true);//删除单个元素 写谁就删掉谁

list.RemoveAt(0);//根据下标去删除元素

list.RemoveRange(0, 3);//根据下标去移除一定范围的元素

list.Sort();//升序排序

list.Reverse();//逆转

list.Insert(1, "cha");//根据下标插入单个元素

list.InsertRange(0, new string[] { "张三", "李四" });//根据下标在指定的位置插入棉签

bool b = list.Contains(1);//判断是否包含某个指定的元素

(2)集合长度的问题
每次集合中实际包含的元素个数(count)超过了可以包含的元素个数(capcity)的时候
集合就会向内存中申请多开辟一倍的空间,来保证集合的长度一直够用。

17.HashTable键值对集合

(1)var:根据值能够推断出来类型
①c#是一门强类型的语言:在代码当中,必须对每一个变量的类型有一个明确的定义。
在这里插入图片描述

②js是一门弱类型语言。

(2)

Hashtable ht = new Hashtable();
ht.Add(1, "zhangsan");
ht.Add(2, true);
ht.Add(3, '男');
ht.Add(false, "错误的");

Console.WriteLine(ht[1]);
Console.WriteLine(ht[2]);
Console.WriteLine(ht[3]);
Console.WriteLine(ht[false]);

foreach (var item in ht.Keys)
{
	Console.WriteLine(ht[item]);
}

(3)在键值对集合当中,我们是根据键去找值的。
键值对对象[键]=值。
键值对集合当中,键必须是唯一的,而值是可以重复的。

18.简繁转换

以前的方法:

            string str = @"C:\Users\小鱼仔\Desktop\aaa\hua.wav";
            int index = str.LastIndexOf("\\");
            str = str.Substring(index + 1);
            Console.WriteLine(str);
            Console.ReadKey();

现在的方法:

Console.WriteLine(Path.GetFileName(str));

19.Path就是用来文件

在这里插入图片描述

20.File类

            //创建一个文件
            File.Create(@"C:\Users\小鱼仔\Desktop\aaa\new.txt");
            Console.WriteLine("创建成功");
            

            //删除一个文件
            File.Delete(@"C:\Users\小鱼仔\Desktop\aaa\new.txt");
            Console.WriteLine("删除成功");

            //复制一个文件
            File.Copy(@"C:\Users\小鱼仔\Desktop\aaa\new.txt", @"C:\Users\小鱼仔\Desktop\aaa\new2.txt");
            Console.WriteLine("复制成功");

在这里插入图片描述

21.编码简介

编码:将字符串以怎样的形式保存为二进制
乱码:产生乱码的原因主要是因为,你保存的这个文件所采用的编码,跟你打开这个文件所采用的编码格式不一样。

byte[] buffer =  File.ReadAllBytes(@"C:\Users\小鱼仔\Desktop\aaa\new.txt");
//将字节数组中的每个元素都按照我们指定的编码格式解码成为字符串
//常见的编码格式:UTF-8 GB2312 GBK ASCII Unicode
string s = Encoding.Default.GetString(buffer);
Console.WriteLine(s);
Console.ReadKey();
string str = "哲楷昨天被叫去守夜了!";
byte[] buffer = Encoding.Default.GetBytes(str);

File.WriteAllBytes(@"C:\Users\小鱼仔\Desktop\aaa\new2.txt",buffer);
Console.WriteLine("写入成功");
Console.ReadKey();
            string[] contents =  File.ReadAllLines(@"C:\Users\小鱼仔\Desktop\aaa\new2.txt", Encoding.Default);
            foreach (var item in contents)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();

22.绝对路径和相对路径

绝对路径:通过给定的这个路径直接能在我的电脑中找到这个文件。
相对路径:文件相对应于应用程序的路径。
(我们在开发当中尽可能地使用向对路径)

23.装箱和拆箱

装箱:将值类型转换为引用类型。
拆箱:将引用类型转换成为值类型。
看两种类型是否发生了装箱或者拆箱,要看这两种类型是否存在继承关系。

24. 创建泛型集合对象

有确定的类型

List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);

25.字典集合(对应HashTable键值对集合 )

自己定义键,自己定义值

            Dictionary<char, int> dic1 = new Dictionary<char, int>();
            dic1.Add('a', 1);
            dic1.Add('b', 2);
            string str = "welcome to China";
            Dictionary<char, int> dic = new Dictionary<char,int>();
            for (int i = 0; i < str.Length; i++)
            {
                if(str[i] == ' ')
                {
                    continue;
                }
                else if(dic.ContainsKey(str[i]))//如果dic已经包含了当前循环到的这个键
                {
                    dic[str[i]]++;//值就加一
                }
                else
                {
                    dic[str[i]] = 1;
                }
            }

            foreach (KeyValuePair<char,int> kv in dic)
            {
                Console.WriteLine("字母{0}出现了{1}次", kv.Key, kv.Value);
            }

26.使用FileStream来读写文件

(1)FileStream是操作字节的,它可以操作任何类型的文件。

using System;
using System.IO;
using System.Text;

namespace _FileStream
{
    class Program
    {
        static void Main(string[] args)
        {
            //FileStream 操作字节的
            //举个例子:是同FileStream来读取文件数据
            //1.创建FileStream对象
            FileStream fs = new FileStream(@"C:\Users\小鱼仔\Desktop\aaa\new.txt",FileMode.OpenOrCreate,FileAccess.Read);
            byte[] buffer = new byte[1024 * 1024 * 5];
            //返回本次实际读取到的有效字节数
            int r =  fs.Read(buffer, 0, buffer.Length);
            //将字节数组中每一个元素按照指定的编码格式解码成为字符串
            string s = Encoding.Default.GetString(buffer,0,r);
            //关闭流
            fs.Close();
            //释放六所占用的资源
            fs.Dispose();
            Console.WriteLine(s);


            //StreamReader和StreamWriter 操作字符的
        }
    }
}

(2)将创建文件流对象的过程写在using当中,会自动的帮助我们释放流随占用的资源。

            //使用FileStream来写入数据
            using (FileStream fs = new FileStream(@"C:\Users\小鱼仔\Desktop\aaa\new.txt",FileMode.OpenOrCreate,FileAccess.Read))
            {
                string str = "kankanshishenme1xiaoguo";
                byte[] buffer = Encoding.Default.GetBytes(str);
                fs.Write(buffer, 0, buffer.Length);
            }
            Console.WriteLine("写入ok");
            Console.ReadKey();

27.使用FileStream实现多媒体文档的复制

思路:就是先将要复制的多媒体文件读取出来,然后再写入到你指定的位置

            string source = @"C:\Users\小鱼仔\Desktop\aaa\new.txt";
            string target = @"C:\Users\小鱼仔\Desktop\aaa\new3.txt";
            CopyFile(source, target);
            Console.WriteLine("输出成功!");
            Console.ReadKey();
public static void CopyFile(string soucre,string target)
        {
            //创建一个负责读取的流
            using (FileStream fsRead = new FileStream(soucre,FileMode.Open,FileAccess.Read))
            {
                //创建一个负责写入的流
                using (FileStream fsWrite = new FileStream(target,FileMode.OpenOrCreate,FileAccess.Write))
                {
                    byte[] buffer = new byte[1024 * 1024 * 5];
                    //因为文件可能比较大,读取时应该通过循环去读取
                    while(true)
                    {
                        //返回本次读取到的字节数
                        int r = fsRead.Read(buffer, 0, buffer.Length);
                        //如果返回0就意味着读取结束
                        if(r == 0)
                        {
                            break;
                        }
                        fsWrite.Write(buffer, 0, r);
                    }
                    
                }

            }
        }

StreamReader和StreamWriter 操作字符的,只能用来写文本文件。

28.多肽

概念:让一个对象能够表现出多种的状态(类型)
实现多态的3种手段:①虚方法 ②抽象类 ③接口
(1)虚方法
将父类的方法标记为虚方法,使用关键字virtual,这个函数可以被子类重新写一遍;
父类写上virtual,子类写上override。

        public abstract class Animal
        { 
            public virtual void Barka()
            {
                Console.WriteLine("动物都会叫");
            }

        }

        public class Dog:Animal
        {
            public override void Barka()
            {
                Console.WriteLine("动物汪汪叫");
            }
        }

(2)抽象类
当父类中的方法不知道如何去实现的时候,可以考虑将父类写成抽象类,将方法写成抽象方法。
抽象方法不能有方法体。
它与虚方法的区别:虚方法在父类中有实现,抽象类在父类没有实现(没有方法体)

        public abstract class Animal
        {
            public abstract void Bark();
        }

在这里插入图片描述

29.c#中的访问修饰符

(1)public :公开的公共的
(2)private:私有的,只能在当前类的内部访问
(3)protected:受保护的,只能在当前类的内部以及该类的子类中访问。
(4)intesnal:只能在当前项目中访问。在同一个项目中,internal和public的权限是一 样。
(5)protected internal:protected+internal

1)、 能够修饰类的访问修饰符只有两个: public、 internal
2)、可访问性不一致。
子类的访问权限不能高于父类的访问权限,会暴露父类的成员。

30.设计模式

(1)设计这个项目的一种方式。
(2)简单工厂设计模式。

31.值传递和引用传递

//值类型:int doubile char decimal bool enum struct(存在于栈)
//引用类型:string 数组 自定义类 集合 object 接口(存在于堆)

值类型在复制的时候,传递的是这个值的本身;
引用类型在复制的时候,传递的是对这个对象的引用(地址)。
在这里插入图片描述
注意:字符串很特殊,主要是因为它的不可变性。

32. 序列化和反序列化

序列化:就是将对象转换为二进制。
反序列化:就是将二进制转换为对象。
作用:传输数据。
(1)序列化步骤:
①将这个类标记为可以被序列化的。

33.部分类和密封类

(1)部分类(partial):两个类同名,可以共用字段属性。
(2)密封类(sealed):不能被继承,但是可以继承别人。

34.接口简介

接口就是一个规范、能力。(interface)

[public] interface I...able
{
	成员;
}
    public interface IFlyable
    {
        void Fly();
        string Test();

        string Name
        {
            get;
            set;
        }
    }

注意:
①接口中的成员不允许添加访问修饰符,默认就是public;
②接口类的成员不能有任何实现,不允许写具有方法体的函数;
③只要一个类继承了一个接口,这个类就必须实现这个接口中所有的成员。
④为了多肽,接口不能被实例化,因为接口的函数是没有函数体的,实例化调用的东西无意义。
在这里插入图片描述
⑤显示调用接口就是为了解决方法的重名问题

using System;

namespace ppp
{
    class Program
    {
        static void Main(string[] args)
        {
            IFlyable fly = new Bird();
            fly.Fly();
            Bird bird = new Bird();
            bird.Fly();
        }
    }
    
    public class Bird:IFlyable
    {
        public void Fly()
        {
            Console.WriteLine("鸟会飞!");
        }

        //显示地实现接口
        void IFlyable.Fly()
        {
            Console.WriteLine("我是接口的飞!");
        }
    }

    public interface IFlyable
    {
        void Fly();
    }
}

35.自动属性和普通属性

普通属性:我们平时常用的那些;
自动属性:会帮我们自动地生成一个私有的字段。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

红狐狸的北北记

红狐狸背着行囊上路,感谢支持!

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

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

打赏作者

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

抵扣说明:

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

余额充值