C#零基础学习笔记

1.1 C#语言入门

C#(读作C sharp)是一种面向对象的编程语言,主要用于开发运行在.NET平台上的应用程序,C#的语言体系都是构建在.NET框架上。

1.1.1 C#语言的特点

  • 语法简洁,不允许直接操作内存,去掉了指针操作。
  • 彻底的面向对象设计,C#具有了面向对象语言所应有的一切特性:封装、继承和多态。
  • 与web紧密结合,C#支持绝大多数的web标准,例如HTML、XML、SOAP等。
  • 强大的安全性机制,可以消除软件开发中常见的错误,.NET提供的垃圾回收器还能够帮助开发者有效的管理内存资源。
  • 兼容性较好,因为C#遵守.NEt的公共语言规范(CLS),从而保证能够与其他语言的开发组件兼容。
  • 完善的错误、异常处理机制,C#提供了完善的错误和异常处理机制,使程序在交付应用时能够更加健壮。

1.1.2 认识 .NET Framework

.NET Framework又称.NET框架,他是微软公司推出的全面面向对象的软件开发和运行平台,它有俩个重要组件:公共语言运行时(CLR)和类库

  • 公共语言运行时:简称CLR,负责管理和执行由.NET编译器编译产出的中间语言代码(如下图所示)。在公共语言运行时中包含了俩部分内容,分别是CLS和CTS,其中,CLS表示公共语言规范,它是许多应用程序所需要的一套基本语言功能;而CTS表示通用类型系统,它定义了可以在中间语言中使用的预定义数据类型,所有面向 .NET Framework的语言最终都可以生成这些类型的编译代码。

在这里插入图片描述

  • 类库里有很多编译好的类,可以直接使用,例如进行多线程操作时,可以直接使用类库里面的Thread类,进行文件操作时·,可以直接使用类库中的IO类等,类库实际上相当于一个仓库,这个仓库里面装满了各种工具,可以让开发人员直接使用。

1.1.3 C# 和 .Net Framework

.Net Framework是微软公司推出的一个全新的开发平台,而C#是专门为微软公司的.Net Framework一起使用而设计的一种编程语言,在.Net Framework平台上开发时,可以使用多种开发语言,比如C#、VB.NET、VS++、F#等,而C#只是其中的一种。
运行C#开发的程序时,必须要安装.Net Framework,.Net Framework可以随着vs的开发环境一起安装到计算机上。

1.1.4 C#的应用领域

  • 游戏软件开发
  • 桌面应用系统开发
  • 智能手机程序开发
  • 多媒体系统开发
  • 网络系统开发
  • RIA应用程序开发
  • 操作系统平台开发
  • WEB应用开发

1.2 Visual Studio 的安装与卸载

1.2.1 安装Visual Studio的必要条件

名称 说明
处理器 2.0GHz双核处理器
RAM 建议使用8G以上
可用硬盘 系统盘上最少10G的可用空间
显示器 1024×768,增强色16位
操作系统及所需补丁 WIndows7以上

1.2.2安装 Visual Studio

下载路径:https://visualstudio.microsoft.com/zh-hans/?rr=https://www.microsoft.com/zh-cn/
社区版为免费版,下载后安装,等待程序加载完成后,自动跳转到安装选择项界面,该页面主要将通用Windows平台开发.NET桌面开发ASP.NET和Web开发,进行安装。下载时,一定要保证好网络状态。

2,踏上C#开发的征程

2.1编写第一个程序

  • 在vs中新建-项目
  • 选择控制台应用程序
  • 应用程序创建完成后,会打开Program.cs文件,输入代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    
    
    internal class Program
    {
    
    
        static void Main(string[] args)//Main方法,程序的主入口方法
        {
    
    
            Console.WriteLine("Hello word");//输出Hello Word
            Console.ReadLine();//固定控制台
        }
    }
}

using是自动生成的代码,用来引用默认的命名空间。
Main方法,用来作为程序的入口方法,每一个C#程序都必须有一个Main方法。

2.2 C#程序结构预览

一个C#程序总体可以分为命名空间、类、关键字、标识符、Main方法、C#语句和注释等。

2.2.1 命名空间

在开发环境中创建项目时,会自动生成一个与项目名称相同的命名空间,语法如下

namespace 名称
命名空间既可以用作程序的内部组织系统,也用作向“外部”公开的组织系统(即一种向其他程序公开自己拥有的程序元素的方法)。如果要调用某个命名空间中的类或者方法,需要先使用using指令引入命名空间,这样,可以直接使用该命名空间中包含的成员(包括类及类中的属性、方法等)。

C#中的命名空间就好像一个储存了不同类型物品的仓库,而using指令就像一把钥匙,命名空间的名称就好比仓库的名称,用户可以通过钥匙打开指定的名称的仓库,然后便可以从仓库中获取所需的物品。

在使用命名空间中的类时,如果不想用using指令引用命名空间,可以在代码证使用命名空间调用其中的类,例如,直接使用Demo命名空间调用其中的Operation类,代码如下:
Demo.Operation.oper = new Demo.Operation();

扫描二维码关注公众号,回复: 17330749 查看本文章

2.2.2 类

C#程序的主要功能代码都是在类中实现的,类是一种数据结构,他可以封装数据成员、方法成员和其他的类。因此,类是C#语言的核心和基本构成模块。C#支持自定义类,使用C#编程就是编写自己的类来描述实际需要解决的问题。

如果把命名空间比作一个医院,类就相当于该医院的各个科室,如内科、骨科、眼科等等等,在各个科室都有自己的工作方法,相当于在类中定义的变量、方法等。这命名空间与类的关系。

在使用类之前都必须先进行声明,一个类一旦被声明,就可以作为一个新的类型来使用,在C#中,通过使用class来声明类

class 类名
{
    
    
	代码
}

声明类时,还可以指定类的修饰符和其要继承的基类或者接口等信息。

2.2.3 关键字和标识符

关键字

关键字是C#语言中已经被赋予特定意义的一些单词,开发过程中,不能把这些关键字作为命名空间、类名、方法和属性等来使用。
例如,int、short、this、for、new等等等。

标识符

标识符可以简单理解为一个名字,比如每个人都有自己的名字,它主要用来标识类名、变量名、方法名、属性名、数组名等各种成员。
标准如下:

  • 由任意顺序的字母、下划线和数字组成
  • 第一个字符不能是数字
  • 不能是C#保留的关键字
  • 可以使用汉字,运行时不会报错,但不建议。
  • 不能包含#、%、或者$等特殊字符
  • 一旦一个语句块中定义了一个变量名,那么在变量的作用域都不能再定义同名的变量。

在C#中,标识符中的字母是严格区分大小写的,来个相同的单词,如果大小写格式不一样,所代表的意义完全不一样。

2.2.4 Main方法

在vs中创建控制台应用程序后,会自动生成Program.cs文件,该文件里有一个默认Main方法。
每个C#程序中都必须包含一个Mian方法,它是类体中的主方法,也叫入口方法,可以说是激活整个程序的开关。static和void分别是Main方法的静态修饰符和返回修饰符,C#的Main方法必须声明为static,并且区分大小写。

	 static void Main(string[] args)//Main方法,程序的主入口方法
        {
    
    
           
        }

Main方法一般是自动生成,如果需要修改,需要注意:

  • Main方法在类和结构内声明,它必须是静态(static)的,而不能是公用(public)的
  • Main的返回类型有俩种:void和int
  • Main方法可以包含命令行参数string[] args,也可以不包括。

通常Main方法中不写集体逻辑代码,只用作类实体化和方法调用。整个过程就好手机来电话了,只需要按“接通”键就可以接电话,而不需要考虑手机通过怎样的信号转换电磁信号转化成声音。这样代码简单明了,容易维护。养成良好的编码习惯,可以让程序员的工作事半功倍。

2.2.5 C#语句

语句是构造所有C#程序的基本单位,使用C#语句可以声明变量、常量、调用方法、创建对象或执行任何逻辑操作,C#以分号终止。

            Console.WriteLine("Hello word");//输出Hello Word
            Console.ReadLine();//固定控制台

这俩行代码是C#最基本的C#语句,用来在控制台窗口输出和读取内容,他们都用到了Console类。
Console类中与输入输出相关的方法:

方法 说明
Read 从标准输出流读取下一个字符 (返回值为int,只能记录int类型的值)
ReadLine 从标准输出流读取下一个字符(返回值类型为string)
Write 将指定的值写入标准输出流
WriteLine 将当前行终止符写入标准输出流

开发控制台应用程序时,经常使用Console.Read和Console.ReadLine来固定控制台窗体。

2.2.6注释

注释是在编译程序时不执行的代码和文字,其主要功能数对某行或某段代码进行说明,方便代码的维护和理解,或者在调试程序时,将某行或某段代码设置为无效代码,常用的有单行注释和多行注释

  • 单行以"//"开头
  • 多行/* 内容 */

注释可以出现在代码任意位置,但是不能分割关键字和标识符。

2.3 程序编写规范

遵循一定的代码编写规则和密码规范可以使代码更加规范化,对代码的理解和维护起到了至关重要的作用。

2.3.1 代码编写规则

代码编写规则通常对应用程序的功能没有影响,但他们对改善源代码的理解是有帮助的,养成良好的习惯对于软件的开发都是很有益的,列举常用的代码编写规则:

  • 编写C#程序时,统一代码缩进的样式,比如统一缩进俩个字符或者四个字符位置
  • 每编写一行C#代码后都应该换行编写下一行代码
  • 在编写C#代码时,应该合理使用空格,以便使用代码结构更加清晰。
  • 尽量使用接口,然后使用类实现接口,以提高程序的灵活性
  • 关键的语句必须要写注释(包括声明关键的变量)
  • 建议局部变量在最接近使用它的地方声明
  • 不要使用goto系列语句,除非是用在跳出深层循环时。
  • 避免编写超过五个参数的方法,如果要使用多个参数,则使用结构
  • 避免书写代码量过大的try-catch语句块
  • 避免在同一个文件中编写多个类
  • 生成和构建一个长的字符串时,一定要使用StringBuilder类型,而不是string类型
  • 对于if语句,应该使用一对大括号“{}”把语句块括起来
  • Switch语句一定要有default语句来处理意外情况

2.3.2 命名规范

命名规范在编写代码中起到很重要的作用,虽然不遵守命名规范程序也可以运行,但是使用命名规范可以更加直接地了解代码所代表的含义。

俩种命名方法

在C#中,最常用的有俩种命名方法,分别是Pascal命名法和Camel命名法

  • 用Pascal命名法来命名方法和类型,首字母必须为大写,且后面连接词的首字母均为大写,
  • 用Camel命名法来命名局部变量和方法的参数,Camel命名法是指名称中第一个单词首字母小写。
  • Camel命名法又称为驼峰命名法。
程序中的命名规范
  • 命名项目名称时,可以使用公司域名+产品名称,或者直接使用产品名称
  • 用有意义的名字来定义命名空间
  • 接口名称的前缀加“I”
  • 类的命名最好能够体验出类的功能和操作
  • 方法的命名:一般将其命名为动宾短语
  • 定义成员变量时最好前缀加"_"

3必须要学会的C#语法

3.1 为什么要使用变量

变量关系到数据的存储,计算机是使用内存来存储计算时所使用的数据,那么内存是如何存储数据的呢?通过生活常识知道数据是各种各样的,比如整数、小数、字符串等。那么在内存中存储这些数据时,就需要根据数据的需求(即类型)为它申请一块合适的空间,然后在这个空间中存储相应的值。
在内存中为数据分配一定的空间之后,如果要使用定义的这个数据,由于内存中的数据是以二进制格式进行的存储,而这些二进制数据都相对应相应的内存地址,因此,必须通过一种技术使用户能够方便地访问到二进制数据的内存地址,这种技术就是变量。

3.2

变量主要用来存储特定类型的数据,用户可以根据需要随时改变变量中所存储的数据值,变量具有名称、类型和值,其中变量名是变量在程序源代码中的标识,类型用来确定变量所代表的内存的大小和类型,变量值是指它所代表的内存块的数据。在执行过程中,变量的值可以发生变化,使用变量之前必须先声明变量,即指定变量的类型和名称。

3.3 变量的声明和初始化

好比一个新生儿必须有一个名字一样,使用变量时,也需要首先对变量进行命名,对变量命名的过程,其实就是声明一个变量。变量在使用之前,必须进行声明并初始化。

3.3.1声明变量

声明变量就是指定变量的名称和类型,变量的声明非常重要,未经声明的变量本身并不合法,也无法在程序中使用。在C#中,声明一个变量是由一个类型和跟在后面的一个或者多个变量名组成,多个变量一逗号分开,声明变量以分号结束。

再声明变量时,要注意变量的命名规则。C#的变量名是一种标识符,应该符合标识符的命名规则。另外,需要注意的一点是:C#中的变量名是区分大小写的,比如num和Num是俩个不同的变量,在程序中使用时是有区别的。

3.3.2简单数据类型

C#中的数据类型根据其定义可以分为两种:一种值类型,一种引用类型,从概念上看,值类型是直接存储值,而引用类型存储的对值的引用。
C#的数据类型结构

整数类型

整数类型用来存储整数数值,既没有小数部分的数组。可以是正数,也可以是负数。整数数据在C#中有三种表示形式,分别是十进制、八进制和十六进制、

  • 十进制:表现形式就是我们生活中使用的数据,如:120、0、-213(不能以0开头,0除外)
  • 八进制:以0开头的数,如0123、-0123(必须以0开头)
  • 十六进制:以0x或0X开头的数,如0x25(转换成十进制为37),0Xb0le(转换成十进制为45086)

C#内置整数类型

类型 说明(8位相当于1字节) 范围
sbyte 8位有符号整数 -128~127
short 16位有符号整数 -32768~32767
int 32位有符号整数 -2147483648~-2147483647
long 64位有符号整数 -9223372036854775808~9223372036854775807
byte 8位无符号整数 0~255
ushort 16位无符号整数 0~65535
uint 32位无符号整数 0~4294967295
ulong 64位无符号整数 0~18446744073709551615

浮点类型

浮点类型变量主要用于处理含有小数的数据,浮点类型主要包含float和double俩种类型。
浮点类型及描述

类型 说明 范围
float 精确到7位数 ±1.510245 ~ 3.41038
double 精确到15~16位 ±5.010-2324 ~ ±1.710308

需要使用float类型变量时,必须在数值的后面跟随f或者F,或者编译器会直接将其作为double类型处理,另外,也可以在double类型的之值前面加上(float),对其进行强制转。
浮点类型变量的默认值是0,而不是0.0。

decimal类型

decimal类型表示128位数据类型,它是一种精度更高的浮点类型,其精度可以达到28位,取值范围是 ±1.010-28 ~ ±7.91028
由于decimal类型的高精度特性,它更适合于财务和货币计算。
如果希望一个小数被当做为decimal类型,需要使用后缀m或M。

bool类型

bool类型又称为布尔类型,主要用来表示true或者false值,C#中定义布尔类型时,需要使用bool关键字bool。
布尔类型通常被使用在流程控制语句中作为判断条件。
布尔类型的默认值为false

字符类型

字符类型在C#中使用Char表示,该类主要用来存储单个字符,它占用16位(俩个字节)的内存空间。在定义字符型变量时,要以单引号(‘’)表示,如‘a’表示一个字符,而“a”表示一个字符串,因为虽然只有一个字符,但由于使用了双引号,所以它仍然表示字符串,而不是字符。
Char类只能定义一个Unicode字符,Unicode字符是目前计算机中通用的字符编码,它为针对不同语言中的每一个字符设定了统一的二进制编码,用于满足跨语言、跨平台的文本转换和处理要求。
Char类的常用方法及说明

方法 说明
IsDigit 指示某个Unicode字符是否属于十进制数字类别
IsLetter 指示某个Unicode字符是否属于字母类别
IsLetterOrDigit 指示某个Unicode字符是属于字母类型还是属于十进制数字类别
IsLower 指示某个Unicode字符是否属于小写字母类别
IsNumber 指示某个Unicode字符是否属于数字类别
IsPunctuation 指示某个Unicode字符是否属于标点符号类别
IsSeparator 指示某个Unicode字符是否属于分隔符类别
IsUpper 指示某个Unicode字符是否属于大写字母类别
IsWhiteSpace 指示某个Unicode字符是否属于空白类别
Parse 将指定的字符串的值转换为它的等效Unicode字符
ToLower 将Unicode字符的值转换为它的小写等效项
ToString 将字符的值转换为其等效的字符串表示
ToUpper 将Unicode字符的值转换为它的大写等效项
TryParse 将指定字符串的值转换为它的等效Unicode字符
转义字符

前面说到字符只能存储单个字符,但在vs中编写

char ch = '\';

会报错,从代码上看,反斜线是一个字符,但为什么会报错呢,这就引出了转义符的概念。
转义字符就是一个特殊的字符变量,以反斜杠“\” 开头,后跟一个或多个字符,也就说,在C#中“\”是一个转义字符,不能单独使用,如果要在C#中使用反斜线,要使用这样的代码:

Char ch = "\\";

转义字符就相当于一个电源转换器,电源转换器就是通过一定的手段获取所需要的电源形式。例如交流变成直流,高电压变成低电压,低频变高频。转义符也是如此,它是将字符转换成另外一种操作形式,或将无法使用的字符进行组合。
转义符只针对后面紧跟着的单个字符进行操作。
转义字符及其作用

转义字符 说明
\n 回车换行
|横向跳到下一制表位置
\ " 双引号
\b 退格
\r 回车
\f 换页
\ \ 反斜线
单引号
\uxxxx 4位十六进制所表示的字符,如\u0052

3.3.3 变量的初始化

在程序中使用变量时,一定要对其赋值,也就是初始化,如何才可以使用,初始化变量有三种方法,分别是单独初始化变量,声明时初始化变量,同时初始化多个变量

单独初始化变量

在C#中,使用赋值运算符“=”对变量进行初始化,即将等号右边的值赋值给左边的变量。
在对变量进行初始化时,等号右边也可以是一个已经被赋值的变量。

int a;
a=1;
声明时初始化变量

声明变量时可以对变量进行初始化,即在每个变量名后面加上给变量赋初始值的指令。

int a = 1;
同时初始化多个变量

在对多个同类型的变量赋同一个值时,为了节省代码行数,可以同时对多个变量进行初始化。

int a,b,c,d;
a = b = c = d =0;

变量的作用域

由于变量被定义后,只是暂时存储在内存中,等程序执行到某一个点后,该变量会被释放掉,也就说说变量有它的生命周期。因此,变量的作用域是指程序代码能够访问该变量的区域,如果超出该区域,则在编译时会出现错误,在程序中,一般会根据变量的“有效范围”将变量分为“成员变量”和“局部变量”。

成员变量

在类体中定义的变量被称为成员变量,成员变量在整个类中都又效,类的成员变量又可以分为俩种,即静态变量和实例变量。
如果在成员变量的类型前面加上关键字static,这样的成员变量称为静态变量。静态变量的有效范围可以跨类,甚至可达到整个应用程序之内。对于静态变量,除了能在他定义的类内存取,还能直接以“类名.静态变量”的方式在其他类内使用。

局部变量

在类的方法体中定义的变量(定义方法的{与}之间的区域)称为局部变量,局部变量只在当前代码块中有效。
在类的方法中声明的变量,包括方法的参数,都属于局部变量,局部变量只有在当前定义的方法内有效,不能用于类的其他方法中,局部变量的生命周期取决于方法,当方法被调用时,C#编译器为方法中的局部变量分配内存空间,当该方法的调用结束后,则会释放方法中局部变量占用的内存空间,局部变量也将会销毁。

3.4 常量

3.4.1 常量是什么

常量就是程序运行过程中,值不能改变的量,比如显示生活中居民身份证号码、数学运算中的π值等,这些都是不会发生改变的,我们都可以定义为常量。

3.4.2 常量的分类

常量主要有俩种,分别是const常量和readonly常量

const常量

在C#中提到常量,通常是指const常量。const常量也叫做静态常量,他在编译时就已经确定了值。const常量的值必须在声明时就进行初始化,而且之后不能更改。

readonly常量

readonly常量是一种特殊的常量,也称为动态常量,从字面理解上看,readonly常量可以进行动态赋值,但需要注意的是,这里的动态赋值是有条件的,他只能在构造函数中进行赋值。

cosnt常量和readonly常量的区别
  • const常量必须在声明时进行初始化,而readonly常量则可以延迟到构造函数中初始化
  • const常量在编译时就被解析,即将常量的值替换成了初始化的值,而readonly常量的值需要在运行时确定
  • const常量可以在类中或者方法体中定义,而readonly常量只能在类中定义。

3.5运算符

运算符是具有运算功能的符号,根据使用操作数的个数,可以将运算符分为单目运算符、双目运算符和三目运算符,其中单目运算符是作用在一个操作数上的运算符,如+等;双目运算符是作用在俩个操作数上的运算符,如+,*等;三目运算符是作用在3个操作数上的运算符,C#中唯一的三目运算符就是条件运算符(?:)。

3.5.1 算术运算符

运算符 说明 实例 结果
+ 12.45f+15 27.45
- 4.56-0.16 4.4
* 5L*12.45F 62.25
/ 7/2 3
% 求余 12%10 2

3.5.2 自增自减运算符

自增和自减运算符,分别用++和–表示。自增和自减运算符是单目运算符,在使用时有俩种形式。

            int a = 0;
            a++;
            a--;//先赋值,后自增减
            ++a;
            --a;//先自增减,后赋值

自增和自减运算符只能作用于变量,下面形式不合法:

	3++;//不合法,因为3是一个常量
	(i+j)++;//不合法,因为i+j是一个表达式

3.5.3 赋值运算符

赋值运算符主要用来为变量等赋值,它是双目运算符。C#中的赋值运算符分为简单赋值运算符和复合赋值运算符。

简单赋值运算符

J简单赋值运算符以符号“=”表示,其功能是将右操作数所含的值赋值给左操作数,例如:

int a = 100;
复合赋值运算符

复合赋值运算符的说明及运算规则:

名称 运算符 运算规则 意义
加赋值 += x+=y x=x+y
减赋值 -= x-=y x=x-y
除赋值 /= x/=y x=x/y
乘赋值 *= x*=y x=x*y
模赋值 %= x%=y x=x%y
位与赋值 &= x&=y x=x&y
位或赋值 I= xI=y x=xIy
右移赋值 >>= x>>=y x=x>>y
左移赋值 <<= x<<=y x=x<<y
异或赋值 ^= x^=y x=x^y

3.5.4 关系运算符

关系运算符是双目运算符,它用于程序中的变量之间以及其他类型的对象之间的比较,他返回一个代表运算结果的布尔值。当运算符对应的关系成立时,运算结果为true,否则为false。关系运算符通常在条件语句中作为判断的依据。
关系运算符:

运算符 作用 举例 操作数据 结果
> 大于 a>b 整数、浮点型、字符型 false
< 小于 a<b 整数、浮点型、字符型 true
== 等于 a==b 基本数据类型、引用型 ftrue
!= 不等于 a!=b 基本数据类型、引用型 false
>= 大于或等于 a>=b 整数、浮点型、字符型 false
>= 小于或等于 a>=b 整数、浮点型、字符型 false

不等于预算法(!=)是与等于运算符相反的运算符,它与!(a==b)是等效的

3.5.5 逻辑运算符

逻辑运算符是对真和假这俩种布尔值进行计算的,运算结果y依然是一个布尔值。在C#中,逻辑运算符主要包括&(&&)(逻辑与)、|(||)(逻辑或)、!(逻辑非)。在逻辑运算符中,除了“!”是单目运算符之外,其他都是双目运算符。
逻辑运算符:

运算符 含义 用法 结合方向
&&、& 逻辑与 a&&b 从左到右
II、I 逻辑与 aIIb 从左到右
逻辑非 !a 从右到左

使用逻辑运算符进行逻辑运算

表达式1 表达式2 表达式1&&表达式2 表达式1 II 表达式2 !表达式1
true true true true false
true false false true false
false false false false true
false true false true true

逻辑运算符&&和&都是表示“逻辑与”,那么他们之间的区别是?从表格中可以看出,当俩个都表示true时,逻辑与的结果会是true。使用&会判断俩个表达式;而&&是针对bool类型的数据进行判断,当第一个返回false时,而不去判断第二个表达式,而直接俗称结果2,从而节省计算机判断的次数。通常将这种在逻辑表达式中从左端的表达式可推断出整个表达式的值称为短路,而那些始终执行逻辑运算符俩边的表达式称为“非短路”。&&属于短路运算符,而&则属于非短路运算符,“||”和“|”的区别跟&&和&的区别一样。

位运算符

位运算符的操作数类型是整数,可以是有符号也可以无符号的。

未完待续,小白路漫漫,让我们一起加油!!!

猜你喜欢

转载自blog.csdn.net/weixin_52473844/article/details/130554482