Flutter (二) Dart入门 一篇就够了

Dart 中文官网

学习Flutterr入个门,应该不难,问题的关键是有个新语言Dart。要不是Flutter启用了,怕是没多少开发人员用吧。

VsCode配置 Dart
  • Dart插件,昨天配置flutter自带了
  • Code Runner (文件右击 run code)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
虽然乱码,但应该是说不识别dart命令,关机重启下就好了

在这里插入图片描述

Dart 概念
  • 在变量中可以放置的所有东西都是对象,而每个对象都是类的实例。无论数字、函数和null都是对象。所有对象都继承自[Object]类。
  • 尽管Dart是强类型的,但类型声明是可选的,因为Dart可以推断类型。如果要明确说明不需要任何类型,请使用[特殊类型dynamic]。
  • Dart支持通用类型,如List(整数列表)或List(任何类型的对象列表)。
  • Dart支持顶级函数(如main()),以及绑定到类或对象(分别是静态方法(static)和实例(instance)方法)的函数。您还可以在函数(嵌套或局部函数)中创建函数。
  • Dart工具可以报告两种问题:警告和错误。警告只是表明您的代码可能不工作,但它们不会阻止您的程序执行。错误可以是编译时错误,也可以是运行时错误。编译时错误阻止了代码的执行;运行时错误导致代码执行时引发异常。
一、变量

写Dart,不像js,不能省去“ ;” , 因为Vscode自带提示报错,我就截代码片段会更形象。

1.声明变量

在js中,声明var,可以是任意类型,也可以重新定义类型。但在Dart中不行,它会类型推断:
在这里插入图片描述
如果对象不限于单一类型,请按照[设计指导]原则指定对象 (Object)或动态(dynamic)类型。
在这里插入图片描述
当然也可以像Java一样,显式声明要推断的类型:

String name = 'Bob';
2.默认值

未初始化的变量的初始值为null。甚至具有数字类型的变量最初也是null,因为数字——就像dart中的其他东西一样——是对象。

int lineCount;
print(lineCount == null);  // true
3.Final 和 const 修饰符
  • 如果您从未打算更改一个变量,请使用final或const修饰他,而不是使用var或其他变量类型。
  • 一个 final 变量只能被初始化一次; const变量是一个编译时常量,(Const变量是隐式的final)。

在这里插入图片描述

  • 被final修饰的顶级变量或类变量在第一次声明的时候就需要初始化。
    在这里插入图片描述
    在这里插入图片描述
  • 被final或者const修饰的变量,变量类型可以省略,建议指定数据类型。
 //可以省略String这个类型声明
 final name = "Bob";
 final String name1  = "张三";
 
 const name2 = "alex";
 const String name3 = "李四";
  • flnal 或者 const 不能和 var 同时使用

在这里插入图片描述

  • const表示编译时常量,即在代码还没有运行时我们就知道它声明变量的值是什么;而final不仅有const的编译时常量的特性,最重要的它是运行时常量,并且final是惰性初始化,即在运行时第一次使用前才初始化。

在这里插入图片描述
在这里插入图片描述

二、内建对象
Number
1.init

根据平台的不同,整数值不大于64位。在Dart VM上,值可以从-263到263 - 1。编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253 - 1。

2.double

64位(双精度)浮点数,由IEEE 754标准指定。既可以整型也可以浮点型。

// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111

String

Dart字符串是UTF-16编码单元的序列。您可以使用单引号或双引号创建一个字符串:

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";

您可以使用${expression}将表达式的值放入字符串中。如果表达式是一个标识符,可以跳过{}。为了获得与对象对应的字符串,Dart调用对象的toString()方法。

var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, ' +
        'which is very handy.');
assert('That deserves all caps. ' +
        '${s.toUpperCase()} is very handy!' ==
    'That deserves all caps. ' +
        'STRING INTERPOLATION is very handy!'); 

另一种创建多行字符串的方法:使用带有单引号或双引号的三重引号:别到时候看见很惊讶

var s1 = '''
You can create
multi-line strings like this one.
''';
Booleans

为了表示布尔值,Dart有一个名为bool的类型。只有两个对象具有bool类型:布尔字面量true和false,它们都是编译时常量。

// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
Lists

在Dart中,数组是列表对象,所以大多数人把它们叫做列表。
列表使用基于0的索引,其中0是第一个元素和列表的索引。[长度- 1]是最后一个元素的索引。您可以获取列表的长度,并引用列表元素,就像在JavaScript中那样:

var list = [1, 2, 3];
print(list.length == 3);
print(list[1] == 2);

list[1] = 1;
print(list[1] == 1);

var list2 = new List<String>()

---------------------------------------------------
常见属性:
      List mylist = ['香蕉','苹果','梨子'];
      print(mylist.length); //3
      print(mylist.isEmpty); //fale
      print(mylist.isNotEmpty); //true
      print(mylist.reversed); //倒序  (梨子, 苹果, 香蕉)
      var list1 = (mylist.reversed).toList(); 
      print(list1); //[梨子, 苹果, 香蕉]

常见方法:
      List mylist = ['香蕉','苹果','梨子'];
      mylist.add('橘子');//仅一次加一次
      mylist.addAll(['桃子','葡萄']); //用于拼接数组
      print(mylist);
      print(mylist.indexOf('桃子')); //下标
      mylist.remove('香蕉');
      print(mylist);  //[苹果, 梨子, 橘子, 桃子, 葡萄]
      mylist.removeAt(0); 
      print(mylist); //[梨子, 橘子, 桃子, 葡萄]

      List mylist2 = ['香蕉','苹果','梨子'];
      mylist2.fillRange(1, 3,'葡萄'); // 修改值,下表 含头不含尾
      print(mylist2); // [香蕉, 葡萄, 葡萄]
      mylist2.insert(1, '甘蔗'); //插入一个
      print(mylist2); //[香蕉, 甘蔗, 葡萄, 葡萄]
      mylist2.insertAll(1, ['橘子,西瓜']); //[香蕉, 橘子,西瓜, 甘蔗, 葡萄, 葡萄]
      print(mylist2);


      List mylist3 = ['香蕉','苹果','梨子'];
      var str = mylist3.join('|'); //转换成字符串
      print(str); //香蕉|苹果|梨子
      var list1 = str.split('|');
      print(list1); //[香蕉, 苹果, 梨子]
      
	-------------------
	
	  List mylist = [1,2,3,5];
	  var newlist = mylist.map((value){
      	return value*2;
 	 	});
 	 print(newlist); //(2, 4, 6, 10)

	----------------------------------

	List mylist = [1,2,3,5,7,4,2,8];
    var newlist = mylist.where((value){
      return value>4;
    });
    print(newlist); //(5, 7, 8)

	---------------------------------

  List mylist = [1,2,3,5,7,4,2,8];
  var f = mylist.any((value){  //只要有一个满足
    return value>5;
  });
  print(f); //true
  var d = mylist.every((value){  //每一个都满足
    return value>5;
  });
  print(d); // false

要创建一个编译时常量列表,请在列表字面量之前添加const:(const 就说明不能改变)

在这里插入图片描述
列表类型有许多便于操作列表的方法。有关列表的更多信息,请参见[泛型] (Generics )和[集合] (Collections)。

还有一个特殊的Set ,Set是没有顺序的且不能重复的集合,不能通过索引取值。(es6)

Maps

map是一个关联键和值的对象。键和值都可以是任何类型的对象。每个键只出现一次,但是您可以多次使用相同的值。Dart对map的支持是通过map字面量和map类型来提供的。

这里有两个简单的Dart map类型,使用map字面量创建:

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};
var p = new Map()

注意:解析器推断gifts的类型为Map<String, String>,nobleGases的类型为Map<int, String>。如果您试图向map添加错误类型的值,则分析器或运行时将引发错误。其他都跟js中map差不多一样。

var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

常用属性:
  Map person = {
    'name':'张三',
    'age':20,
    'sex':'男',
  };
  print(person.keys.toList()); //[name, age, sex]
  print(person.values.toList());//[张三, 20, 男]
  print(person.isEmpty); //fale
  print(person.isNotEmpty); //true

---------------------------------------
常用方法:
  Map person = {
    'name':'张三',
    'age':20,
    'sex':'男',
  };
  person.addAll({
    'work':['敲代码','送外卖'],
    'height':180,
  });
  print(person); // {name: 张三, age: 20, sex: 男, work: [敲代码, 送外卖], height: 180}
  person.remove('sex');
  print(person); //{name: 张三, age: 20, work: [敲代码, 送外卖], height: 180}
  print(person.containsValue('sex')); //false
  -------------------------------------------------------------
  

要创建一个编译时常量的map需要在map的字面量前加const关键字:

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};
Runes

字符是字符串的UTF-32编码点。
Unicode为世界上所有的书写系统中使用的每个字母、数字和符号定义一个唯一的数值。因为Dart字符串是UTF-16代码单元的序列,所以在字符串中表示32位的Unicode值需要特殊的语法。

表示Unicode码点的常用方法是\uXXXX,其中XXXX是4位数的十六进制值。例如,心型字符(♥)的编码为\ u2665。要指定大于或小于4位十六进制数字,请将值放在花括号中。例如笑脸表情(?)的编码\u{1f600}。

main() {
  var clapping = '\u{1f600}';
  print(clapping);
  print(clapping.codeUnits);
  print(clapping.runes.toList());

  Runes input = new Runes(
      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print(new String.fromCharCodes(input));
}

//运行效果如下
?
[55357, 56832]
[128512]
♥  ?  ?  ?  ?  ?

Process finished with exit code 0
Symbols

符号对象表示在Dart程序中声明的操作符或标识符。您可能永远不需要使用符号,但是对于按名称引用标识符的api来说,它们是非常重要的,因为缩小改变了标识符名称而不是标识符符号。

要获取标识符的符号,请使用符号文字,符号文字仅为#,后面跟着标识符:

#radix
#bar
三、运算 条件判断 类型转换

跟js相差不大,就忽略了,除了一些特殊的

1.Dart算数运算符(+、-、*、/、%、~/)

~/ 取整

2.Dart关系运算符( ==、!=、>=、<=、>、<)
3.Dart逻辑运算符 (!、|| 、&&)
4.Dart条件运算符(if、switch case、三目、??)
	var a;
	var b = a ?? 10;
	print(b)  //10
	------------------
	var a = 22;
	var b = a ?? 10;
	print(b)  //22
5.Dart类型转换

a.Number 与 String 之间的转换

try–catch 抛出错误类型

void main(){
	// var num1 = 12;
	// var str = num1.toString();
	// print(str is String)

  // String str = '123456';
  // var myNum =double.parse(str);
  // print(myNum is double); //true  
  // //str 为'',会报错
  String str = '';
  try{

    var myNum = double.parse(str);

    print(myNum);

  }catch(err){

    print(0);

  };
}

b.其他类型转换Boolean 类型
isEmpty:判断字符串为空

  var str = '';
  if(str.isEmpty){
    print('str为空');
  }else{
    print('str不为空');
  }

  var  mynum;
  if(mynum == null){
    print('空');
  }else{
    print('非空');
  };

  var myNum = 0/0;
  print(myNum); // NaN
  if(myNum.isNaN){
    print('NaN');
  }
6.Dart 循环语句

a.自增和自减 js是一样的

  var a = 10;
  var b = a--;
  print(a); //9
  print(b); //10

b.for

  for(int i = 0;i <= 10;i++){
    print(i);
  }
  //声明 i ; 判断 i<=10 ; print(i) ;i++
  ----------------------------
	// 50以内偶数
  for(int i = 0;i <= 50;i++){
    if(i%2==0){
      print(i);
    }
  }
  	//1+2+3+4+。。。+100
  var sum =0;
  for(var i=0;i<= 100;i++){
    sum+=i;
  }
  print(sum);
---------------------------
  List list = ['张三','李四','王五'];

  for(var i=0;i<list.length;i++){
    print(list[i]);
  }
----------------------------------------
// 二维
  List list = [
    {
      'cate':'国内',
      'news':[
        {'title':'国内新闻1'},
        {'title':'国内新闻2'},
        {'title':'国内新闻3'},
        {'title':'国内新闻4'},
        {'title':'国内新闻5'},
        {'title':'国内新闻6'}
      ]
    },
    {
      'cate':'国际',
      'news':[
        {'title':'国际新闻1'},
        {'title':'国际新闻2'},
        {'title':'国际新闻3'},
        {'title':'国际新闻4'},
        {'title':'国际新闻5'},
        {'title':'国际新闻6'}
      ]
    },
    {
      'cate':'英国',
      'news':[
        {'title':'英国新闻1'},
        {'title':'英国新闻2'},
        {'title':'英国新闻3'},
        {'title':'英国新闻4'},
        {'title':'英国新闻5'},
        {'title':'英国新闻6'}
      ]
    }
  ];

  for(var i=0;i<list.length;i++){
    print('-------------');
    for(var j=0;j<list[i]['news'].length;j++){
      print(list[i]['news'][j]['title']);
    }
  }

c. do while while 与js差不多

    int i=1;
    while(i<=10){
    // i++
      print(i);
    } //死循环
    ---------------------------
    // 10以内和
        int i=1;
    var sum=0;
    while(i<=10){
      sum+=i;
      print(i);
      i++;
    }
    print(sum);
    ----------------------------
        int i=1;
    var sum=0;
    do{
      sum+=i;
      // print(i);
      i++;
    }
    while(i<=10);
    print(sum);
// 第一次循环不成立时,do while 比 while 多执行一次

d.break continue 与js差不多

    for(var i=1;i<=10;i++){
      if(i==4){
        continue; // 跳过当前循环,继续循环后面
      }
     print(i);  // 没有4
    };
    for(var i=1;i<=10;i++){
      if(i==4){
        break; // 跳过当前循环,循环结束,只跳出一层
      }
      print(i);  // 1 2 3
    }
    ------------------------------------
      for(var i=0;i<5;i++){
    print('外层----$i');
    for(var j=0;j<4;j++){
      if(j==2){
        break;
      }
      print('里层----$j');
    }
  }
在这里插入代码片
四、函数

先是综合案例,后面都是官网上的解答。毕竟代码什么的还是打出来才算,概念还是理解性记忆。大部分跟js一样,主要是有类型限制;

函数声明 调用:
void printInfo() {
  print('我是一个自定义的方法');
}

int getNum() {
  var myNum = 1;
  return myNum;
}
String printUser(){
  return 'str';
}
void main() {
  printInfo();
  print('调用系统内置的方法');

  var n = getNum();
  print(n);
  print(printUser());

  void j(){
    k(){
      print('sss');
      print(getNum());
      print('aaa');
    }
    k();//局部作用域
  }
  j();
}
--------------------------------------------------

  int sumNum(int n) {
    int sum = 0;
    for (var i = 0; i <= n; i++) {
      sum += i;
    }
    return sum;
  }
  var n1 = sumNum(10);
  print(n1);
  var n2 = sumNum(30);
  print(n2);
---------------------------------------------------
传参:
  String printUserInfo(String username,int age){
    return '姓名:$username ======= 年龄:$age';
  }
  print(printUserInfo('Jack',20));
--------------------------------------------------
可选参数:
  String printUserInfo(String username, [int age]) {
    if (age != null) {
      return '姓名:$username ======= 年龄:$age';
    } else {
      return '姓名:$username ======= 年龄保密';
    }
  }

  print(printUserInfo('Jack', 20)); //姓名:Jack ======= 年龄:20
  print(printUserInfo('Jack')); //姓名:Jack ======= 年龄保密

------------------------------------------------------------------
默认参数:
  String printUserInfo(String username, [int age ,String sex='男']) {
    if (age != null) {
      return '姓名:$username =======性别:$sex==== 年龄:$age';//姓名:Jack =======性别:男==== 年龄:20
    } else {
      return '姓名:$username =======性别:$sex====  年龄保密';//姓名:Jack =======性别:男====  年龄保密
    }
  }
  print(printUserInfo('Jack', 20)); //姓名:Jack ======= 年龄:20
  print(printUserInfo('Jack')); //姓名:Jack ======= 年龄保密
  print(printUserInfo('Li Mei', 20,'女')); //姓名:Li Mei =======性别:女==== 年龄:20
}

---------------------------------------------------------------
命名参数:
  String printUserInfo(String username, {int age ,String sex='男'}){
    if (age != null) {
      return '姓名:$username =======性别:$sex==== 年龄:$age';
    } else {
      return '姓名:$username =======性别:$sex====  年龄保密';
    }
  };
  print(printUserInfo('zhang san',age:20,sex:'未知')); //姓名:zhang san =======性别:未知==== 年龄:20
  -----------------------------------------------------------------
  函数作参数:
    fn1(){
    print('sss');
  }
  fn2(fn){
    fn();
  }
  fn2(fn1); //sss

--------------------------------------------

自执行:
((int n){
  print('dart $n');
})(12);
--------------------------------------------

递归:
    var sum = 1;
    fn(n){
      sum*=n;
      if(n==1){
        return;
      }
      fn(n-1);
    }
    fn(5);
    print(sum);
-------------------------------------------------
局部变量:
  printInfo(){
    var myNum = 124;
    myNum++;
    print(myNum);
  }
  printInfo(); //125
  printInfo(); //125
  printInfo(); //125

----------------------------------------------
闭包:
常驻类型,又不污染全局
  fn(){
    var a=123;
    return(){
      a++;
      print(a);
    };
  }
  var b = fn();
  b(); //124
  b(); //125
  b(); //126


Dart是一种真正的面向对象语言,所以即使函数也是对象,具有类型和功能。这意味着函数可以分配给变量或作为参数传递给其他函数。您还可以像调用函数一样调用Dart类的实例。有关详细信息,请参见[可调用类]。

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

尽管Effective Dart建议对公共api使用类型注释,但是如果您省略了类型,这个函数仍然可以工作:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

函数可以有两种类型的参数:必需的和可选的。首先列出必须的参数,后边是任何可选参数。命名可选参数也可以标记为@required。

可选参数

可选参数可以是位置参数,也可以是命名参数,但不能两者都是。

1.可选的命名参数

在调用函数时,可以使用paramName: value来指定命名参数。例如:

enableFlags(bold: true, hidden: false);

在定义函数时,使用{param1, param2,…}来指定命名参数:

void enableFlags({bool bold, bool hidden}) {...}

Flutter实例创建表达式可能会变得复杂,因此小部件构造函数只使用命名参数。这使得实例创建表达式更容易阅读。

您可以在任何Dart代码(不仅仅是Flutter)中注释一个已命名的参数,并使用@required说明它是一个必传的参数。例如:

const Scrollbar({Key key, @required Widget child})

当构造Scrollbar时,分析器在没有子参数时报错。Required在元包中被定义。或者直接使用import package:meta/meta.dart导入或者导入其他包含meta导出的包,例如Flutter的包:Flutter /material.dart。

2.可选的位置参数

在[]中包装一组函数参数,标记为可选的位置参数:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}
默认参数值

您的函数可以使用 = 来定义命名和位置参数的默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为null。

String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');

您还可以将列表或map集合作为默认值。下面的示例定义一个函数doStuff(),该函数指定列表参数的默认列表和礼品参数的默认map集合。

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}
main () 函数

每个应用程序都必须有一个顶级的main()函数,它作为应用程序的入口点。main()函数返回void,并有一个可选的列表参数作为参数。

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

下面是一个命令行应用程序的main()函数示例,它接受参数:

// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}
函数是一等公民

函数作为参数传递给另一个函数

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);

你也可以为变量分配一个函数,例如:

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
词法作用域

Dart是一种在词法上确定范围的语言,这意味着变量的范围是静态的,仅仅是通过代码的布局来决定的。您可以“跟随花括号向外”以查看变量是否在范围内。

这里有一个嵌套函数的例子,每个作用域级别上都有变量:

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}
词法闭包

闭包是一个函数对象,它可以访问其词法范围内的变量,即使函数在其原始范围之外使用。
函数可以关闭周围作用域中定义的变量。在下面的示例中,makeAdder()捕获变量addBy。无论返回的函数到哪里,它都会记住addBy。

Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}
判断函数相等
void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}
返回值

所有函数都返回一个值。如果没有指定返回值,则语句返回null;隐式附加到函数体。

foo() {}
assert(foo() == null);
五、类

Dart是一种面向对象的语言,具有类和基于mixin的继承。每个对象都是一个类的实例,所有的类都是Object的子类。基于mixin的继承意味着,尽管每个类(除了Object)都只有一个超类,但类主体可以在多个类层次结构中重用。

创建类 实例:
class Person{
  String name='张三';
  int age=20;
  void getInfo(){
    print('$name-----$age');
    print('${this.name}------${this.age}');
  }
  void setInfo(int age){
    this.age = age;
  }
}


main(){
  //实例化
  // var p1 = new Person();
  // print(p1.name);
  // p1.getInfo();

  Person p2 = new Person();
  p2.setInfo(26);
  p2.getInfo(); //张三------26

---------------------------------------
构造函数:
class Person{
  String name;
  int age;
  //默认构造函数
  Person(String name ,int age){
    print('这是构造函数里面的内容,这个方法在实例化的时候触发');
    this.name = name;
    this.age = age;
  }
  void printInfo(){
    print("${this.name}===========${this.age}");
  }
}

void main(){
  Person p1 = new Person('张三', 20);
  p1.printInfo();
  Person p2 = new Person('李四', 40);
  p1.printInfo();
}
//这是构造函数里面的内容,这个方法在实例化的时候触发
//张三===========20
//这是构造函数里面的内容,这个方法在实例化的时候触发
//张三===========20

---------------------------------------------------------

命名构造函数:
class Person{
  String name;
  int age;
  //默认构造函数 一个
  Person(this.name,this.age);
  //命名构造函数可以多个
  Person.now(){
    print('我是命名构造函数');
  }
  void printInfo(){
    print("${this.name}===========${this.age}");
  }
}

void main(){
  Person p1 = new Person.now();
}

--------------------------------------
声明类单独放在一个文件下,调用文件 import,再将私有属性 加_,才可以
class Person{
  String _name;
  int age;
  //默认构造函数 一个
  Person(this._name,this.age);
  //命名构造函数可以多个
  Person.now(){
    print('我是命名构造函数');
  }
  void printInfo(){
    print("${this._name}===========${this.age}");
  }
  //可以在类里面声明公用方法,返回私有属性  之后调用的时候调用公用方法就行了
	//  String getName(){
   /// return this._name;
//  }
}


import 'lib/class.dart';
void main(){
  Person p1 = new Person('张三',30);
  print(p1._name); //不能访问
  //  print(p1.getName());
}

-----------------------------------------
GET SET
class Rect{
  num height;
  num width;
  Rect(this.height,this.width);
  get area{
    return this.height*this.width;
  }
  set areaHeight(value){
    this.height=value;
  }
  // area(){
  //   return this.height*this.width;
  // }
}

void main(){
  Rect R = new Rect(10, 4);

  R.areaHeight =40;
  
  print('面积 ${R.area}'); // get 直接以属性的形式获取
  // print('面积 ${R.area()}');
}

----------------------------------------------------
默认值:
Rect():height=11,width=2{
}
-------------------------------------------------
// 1.使用static 关键字来实现类级别的变量和函数
// 2.静态方法不能访问非静态方法,非静态方法可以访问静态方法
// 3.非静态属性/方法只能实例化后才能访问

// 非静态权限大,可以访问非静态成员和静态成员,静态权限小,只能访问静态成员

class Person{
  static String name = '张三';
  static void show(){
    print(name);
  }
  int age =20;
  void printInfo(){ //非静态方法可以访问静态和非静态成员
    print(name); // 访问静态属性
    print(this.age); // 访问非静态属性  this 实例(new Person)
    show();// 访问静态方法
  }
  static void printUserInfo(){
    print(name);
    show();
   // print(age); // 报错
  }
}


void main(){
  Person.show(); // 4.静态方法类名调用
  var p=new Person();
  p.printInfo();
}
-------------------------------------------------------
/**
 * Dart中对象操作符
 * ? 条件运算符 (了解)
 * as 类型转换
 * is 类型判断
 * ..  级联操作(连缀)
*/

class Person {
  String name;
  num age;
  Person(this.name,this.age);
  void printInfo(){
    print('${this.name}------------------${this.age}');
  }
}



main(){
  Person p;
  p?.printInfo();
  Person p1 = new Person('张三', 20);
  p1?.printInfo(); //防止p空

  print(p1 is Person);
  //  (p as Person).printInfo();

  p1..name='李四'
    ..age = 29
    ..printInfo();
}
-----------------------------------------------------
/**
 * 面向对象的三大特性 封装、继承、多态
 * 
 * 继承
 * 1.子类使用extends关键字继承父类
 * 2.子类会继承父类里面可见的属性和方法,但是不会继承构造函数
 * 3.子类能复写父类的getter setter
 */

class Person{
  String name = '张三';
  num age = 20;
  void printInfo(){
    print("${this.name}-----------------${this.age}");
  }
}

class Web extends Person{

}

main(){
  Web w = new Web();
  print(w.name);
  w.printInfo();
}
--------------------
class Person{
  String name;
  num age;
  Person(this.name,this.age);
  void printInfo(){
    print("${this.name}-----------------${this.age}");
  }
}

class Web extends Person{
  Web(String name, num age) : super(name, age); //在子类构造函数执行前,把 name,age初始化给父类构造函数

}
----------------------------------
// super具体使用 继承属性自定义属性
class Person{
  String name;
  num age;
  Person(this.name,this.age);
  void printInfo(){
    print("${this.name}-----------------${this.age}");
  }
}

class Web extends Person{
  String sex;

  Web(String name, num age,String sex) : super(name, age){
    this.sex=sex;
  }
  run(){
    print('${this.name}------${this.age}-----------${this.sex}');
  }
}
// 命名构造函数
class Person{
  String name;
  num age;
  Person(this.name,this.age);
  Person.dd(this.name,this.age);
  void printInfo(){
    print("${this.name}-----------------${this.age}");
  }
}

class Web extends Person{
  String sex;

  Web(String name, num age,String sex) : super.dd(name, age){
    this.sex=sex;
  }
  run(){
    print('${this.name}------${this.age}-----------${this.sex}');
  }
}
--------------------------------------------------------
// 重写
class Person{
  String name;
  num age;
  Person(this.name,this.age);
  void printInfo(){
    print("${this.name}-----------------${this.age}");
  }
  work(){
    print('我在工作');
  }
}

class Web extends Person{
  Web(String name, num age) : super(name, age);
  run(){
    print('run');
    super.work(); //调用父类方法
  }
  //覆写
  @override  //可写可不写 建议写
  void printInfo(){
    print("姓名 ${this.name}-----------------年龄 ${this.age}");
  }
  work(){
      print('${this.name}的工作是xxx');
  }
}

main(){
  Web w = new Web('李四', 18);
  w.printInfo();
  w.work();
  w.run();
}

-------------------------------------------------
/**
 * Dart中抽象类:Dart抽象类用于定义标准,子类可以继承抽象类,也可以实现抽象接口
 * 
 * 1.抽象类通过abstract关键词来定义
 * 2.Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法
 * 3.如果子类继承抽象类必须得实现里面的抽象方法
 * 4.如果把抽象类当做接口实现的话必须得实现抽象类里定义的所有属性和方法
 * 5.抽象类不能被实例化,只有继承它的子类可以
 * 
 * 
 * extents抽象类 和 implements的区别
 * 1.如果要服用抽象类的方法,并且要抽象的方法约束类的话用extends继承抽象类
 * 2.如果只是把抽象类当做标准的话,我们就用implements实现抽象类
 * 
 * */
 // 案例 定义一个Animal类,要求它的子类必须包含eat方法。如果抽象类里面有多个方法,也都要全部继承声明覆盖

 abstract class Animal {
   eat();   //抽象方法
   run();
   printInfo(){
     print('我是抽象类里的普通方法');
   }
 }

class Dog extends Animal{
  @override
  eat() {
    print('小狗在吃骨头');
  }

  @override
  run() {
    print('小狗在跑');
  }
}
class Cat extends Animal{
  @override
  eat() {
    print('小猫在吃老鼠');
  }

  @override
  run() {
    print('小猫在跑');
  }

}
 main(){
   Dog dog = new Dog();
   dog.eat();
   dog.printInfo();
   Cat cat = new Cat();
   cat.eat();
   dog.printInfo();
 }
 ---------------------------------------
/**
 * Dart中的多态
 * 允许子类类型的指针赋给父类的指针,同一个函数调用会有不同的执行效果
 * 
 * 子类的实例赋值给父类的引用
 * 
 * 多态就是父类定义一个方法不去实现,让继承它的子类去实现,每个子类的不同的表现
 * 
 */
 abstract class Animal {
   eat();   //抽象方法
 }

class Dog extends Animal{
  @override
  eat() {
    print('小狗在吃骨头');
  }
  run(){
    print('dog');
  }
}

class Cat extends Animal{
  @override
  eat() {
    print('小猫在吃老鼠');
  }

}

 main(){
   Dog dog = new Dog();
   dog.run(); // 可以运行

   Animal dog2 = new Dog(); //子类的实例赋给父类的引用
   dog2.run() // 不能运行
 }

-----------------------------------------------------------


/**
 * 与Java相比
 * 首先,dart的接口没有interface关键字定义接口,而是普通类和抽象类作为接口来实现;
 * 
 * 同样使用implement关键字进行实现
 * 
 * 但是dart的接口有些奇怪,如果实现的类是普通的类,会将普通类和抽象类的属性的方法全部覆写一遍
 * 
 * 而因为抽象类可以定义抽象方法、普通类不可以,使用一般如果要实现像Jave的接口,一般会使用抽象类
 * 
 * 建议使用抽象类定义接口
 * 
 * 
 */

// 定义一个db库 支持mysql mssql,mongodb
// mysql mssql,mongodb都有公用的方法

abstract class Db {  // 当前接口 定义规范
  String url; 
  add(String data);
  save();
  delete();
}

class Mysql implements Db{
  @override
  String url;

  Mysql(this.url);

  @override
  add(data) {
    print('这是mysql的add方法'+data);
  }

  @override
  delete() {
    // TODO: implement delete
    return null;
  }

  @override
  save() {
    // TODO: implement save
    return null;
  }
  
}

class MsSql implements Db{
  @override
  String url;

  @override
  add(data) {
    print('这是mssql的add方法'+data);
  }

  @override
  delete() {
    // TODO: implement delete
    return null;
  }

  @override
  save() {
    // TODO: implement save
    return null;
  }

}

class MongoDb implements Db{
  @override
  String url;

  @override
  add(data) {
    print('这是mongodb的add方法'+data);
  }

  @override
  delete() {
    // TODO: implement delete
    return null;
  }

  @override
  save() {
    // TODO: implement save
    return null;
  }
}

main(){
  Mysql mysql =new Mysql('xxxxxxx');

  mysql.add('123456789');
}
------------------------------------------------------
/**
 * Dart 中一个类实现多个接口
 */

abstract class A {
  String name; 
  printA();
}

abstract class B {
  
  printB();
}


class C implements A,B{
  @override
  String name;

  @override
  printA() {
    // TODO: implement printA
    print('printA');
  }

  @override
  printB() {
    // TODO: implement printB
    return null;
  }
}

void main(){
  C c =new C();
  c.printA();
}

----------------------------------------------

/**
 * mixins 的中文意思混入,就是在类中混入其他功能
 * 
 * 在Dart中可以使用mixins实现类似多继承的功能
 * 
 * 因为mixins使用的条件,随着Dart版本一直在变,这里对应的版本是Dart2.x中使用mixins的条件
 * 
 * 1.作为mixins的类只能继承Object,不能继承其他类
 * 
 * 2.作为mixins的类不能有构造函数
 * 
 * 3.一个类可以mixins多个mixins类
 * 
 * 4.mixins绝不是继承,也不是借口,而是一种全新的特性
 */

class Person {
  String name;
  num age;
  Person(this.name,this.age);
  printInfo(){
    print("我是一个Person 类$age");
  }
}

class A {
  String info ='ssss';
  void printA(){
    print('A');
  }
  void run(){
    print('a');
  }
}

class B{
  void printB(){
    print('B');
  }
  void run(){
    print('b');
  }
}

// class C with Person, A,B{   

// }
class C extends Person with A,B{
  C(String name, num age) : super(name, age);   

}

void main(){
  C c=new C('张三',20);
  c.printA();
  print(c.info);
  c.run();  //后面的把前面顶掉
}
--------------------------------------------------
/**
 * mixins的类型就是其超类的子类型
 */

class A {
  String info = 'this is A';
  void printA(){
    print('A');
  }
}

class B {
  void printB(){
    print('B');
  }
}

class C with A,B{

}

void main(){
  var c = new C();
  print(c is C); //true
  print(c is A);//true
  print(c is B);//true
}
1.类成员

对象具有由函数和数据(分别是方法和实例变量)组成的成员。当您调用一个方法时,您在一个对象上调用它:该方式可以访问该对象的函数和数据。
使用点号(.)引用实例变量或方法:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;   //这边有问题, final y

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

为避免最左操作数为空时出现异常,使用 ?.代替 .来使用:

// If p is non-null, set its y value to 4.
p?.y = 4;
2.构造函数

通过创建一个与类同名的函数来声明构造函数(另外,还可以像[命名构造函数]中描述的一样选择一个附加标识符)。构造函数最常见的应用形式是使用构造函数生成一个类的新实例:

class Point {
  num x, y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

this关键字是指当前实例。只有在名称冲突时才使用它。否则,Dart的代码风格需要省略this

1.命名的构造函数

使用命名构造函数可以在一个类中定义多个构造函数,或者让一个类的作用对于开发人员来说更清晰:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

一定要记住构造函数是不会从父类继承的,这意味着父类的命名构造函数子类也不会继承。如果你希望使用在超类中定义的命名构造函数来创建子类,则必须在子类中实现该构造函数。

2.调用非默认的超类构造函数

默认情况下,子类中的构造函数调用父类的未命名的无参数构造函数。父类的构造函数在构造函数体的开始处被调用。如果类中有使用初始化列表,初始化列表将在调用超类之前执行。综上所述,执行顺序如下:

  • 初始化列表
  • 超类中的无参数构造函数
  • main类中的无参数构造函数

如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。在冒号(:)之后,在构造函数体(如果有的话)之前指定超类构造函数。
在下例中,Employee类的构造函数中调用了他的超类Person中的命名构造函数。

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

///结果输出为
in Person
in Employee

因为父类构造函数的参数是在调用构造函数之前执行的,所以参数可以是表达式,比如函数调用:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}

警告:在超类的构造函数的参数中不能使用this关键字。例如,参数可以调用static方法但是不能调用实例方法。

3.初始化列表

除了调用超类构造函数之外,还可以在构造函数主体运行之前初始化实例变量。初始值设定项用逗号分开。

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

在开发期间,可以通过在初始化列表中使用assert来验证输入。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

初始化列表在设置final字段时很方便。下面的示例初始化初始化列表中的三个final字段:

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}

///运行结果
3.605551275463989
4.重定向构造函数

有时,构造函数的唯一目的是重定向到同一个类中的另一个构造函数。重定向构造函数的主体为空,构造函数调用出现在冒号(:)之后。

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}
5.常量构造函数

如果您的类生成的对象不会改变,您可以使这些对象成为编译时常量。为此,定义一个const构造函数,并确保所有实例变量都是final的。

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}
6.工厂构造函数

在实现构造函数时使用factory关键字,该构造函数并不总是创建类的新实例。例如,工厂构造函数可以从缓存返回实例,也可以返回子类型的实例。

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

注意:工厂构造函数不能访问this关键字。

var logger = Logger('UI');
logger.log('Button clicked');
6.实例方法

对象上的实例方法可以访问实例变量。下面示例中的distanceTo()方法是一个实例方法的示例:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
7.Getter 和 Setter

getter和setter是对对象属性的读写访问的特殊方法。回想一下,每个实例变量都有一个隐式的getter,如果需要的话还可以加上一个setter。使用get和set关键字来实现getter和setter方法可以来读写其他属性:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

使用getter和setter,你可以使用方法包装实例变量,而无需改动业务代码。

8.抽象方法

实例方法、getter和setter方法可以是抽象方法,之定义一个接口但是将具体实现留给其他类。抽象方法只能存在于抽象类中,抽象方法是没有方法体的。

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}
六、泛型
1.为何要使用泛型

写出严谨高质量的代码是很有用的:

  • 适当地指定泛型类型可以生成更好的代码
  • 您可以使用泛型来减少代码重复

如果您想要一个列表只包含字符串,您可以将它声明为list (读作“String of String”)。这样,您和其他程序员,以及您的工具就可以检测到将一个非字符串分配到列表中可能是一个错误。

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

另一个原因是减少代码重复。泛型允许您在许多类型之间共享一个接口和实现,同时仍然利用静态分析。例如,假设您创建了一个用于缓存对象的接口:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

您发现您想要这个接口的特定字符串版本,所以您创建了另一个接口:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

如果稍后你想要获取这个接口的一个数字特征的版本。
泛型类型可以省去创建所有这些接口的麻烦。相反,您可以创建一个具有类型参数的接口:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

在这段代码中,T是替代类型。它是一个占位符,您可以将其视为开发人员稍后将定义的类型。

2.使用集合字面量

List和map字面量可以被参数化。参数化字面量和你已经认识的所有字面量一样,仅仅是在字面量的开始括号之前添加(对于list类型来说)或者添加<keyType, valueType>(对于map类型来说)。

var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};
3.构造函数的参数化类型

要在使用构造函数时指定一个或多个类型,请将类型放在类名后面的尖括号(<…>)中。例如:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);

下面的代码创建了一个具有整数键和视图类型值的map映射:

var views = Map<int, View>();
4.泛型集合及其包含的类型

Dart通用类型被具体化,这意味着它们在运行时携带它们的类型信息。例如,您可以测试集合的类型:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
5.限制参数化类型

在实现泛型类型时,您可能希望限制其参数的类型。你可以使用extends。

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

可以使用SomeBaseClass 或它的任何子类作为泛型参数:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

也可以不指定泛型参数:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'

指定任何非somebaseclass类型都会导致错误:

var foo = Foo<Object>();
6. 使用泛型方法

最初,Dart仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数。

T first<T>(List<T> ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}

在这里,first上的泛型参数()允许你在很多地方使用类型参数T:

  • 在函数的返回中返回类型(T)
  • 在参数的类型中使用(List)
  • 在局部变量的类型中(T tmp)
七、库和可见性

import和library指令可以帮助您创建模块化和可共享的代码库。库不仅提供api,而且包含隐私部分:以下划线(_)开头的标识符仅在库中可见。每个Dart应用程序都是一个库,即使它不使用库指令。

1.使用库

使用import来指定如何在另一个库的范围中使用来自一个库的命名空间。
例如,Dart web应用程序通常使用Dart:html库,它们可以这样导入:

import 'dart:html';

对于内置库,URI具有特定的形式(dart:scheme)。对于其他库,可以使用文件路径或者包:scheme的形式。

import 'package:test/test.dart';
2.指定一个库前缀

如果您导入两个具有冲突标识符的库,那么您可以为一个或两个库指定一个前缀。例如,如果library1和library2都有一个Element类,那么你可以用以下的方法:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
3.只导入库的一部分
// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
4.懒加载库

延迟加载(也称为懒加载)允许应用程序在需要时按需加载库。以下是一些您可能使用延迟加载的情况:

  • 减少应用程序的初始启动时间。
  • 要执行A/B测试——尝试算法的其他实现。
  • 加载很少使用的功能,如可选屏幕和对话框

要延迟加载库,必须首先使用deferred as进行导入。

import 'package:greetings/hello.dart' deferred as hello;

当您需要库时,使用库的标识符调用loadLibrary()。

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值