VSCode环境搭建
- 在学习Dart语法时,本人使用的是VSCode编辑器,下载官方网址为:https://code.visualstudio.com/
-
其次安装Flutter,Dart,Code Runner三个插件,如下所示:
- 然后创建一个文件夹,用VSCode打开,就可以进行Dart语法演练了;
main函数
- Dart语言的入口是
main函数
,并且必须显示的进行定义; - Dart的入口函数main是没有返回值的;
- 传递给main函数的命令行参数,是通过List<String>完成的;
-
代码展示如下:
声明变量
- Dart中声明变量有两种方式:
- 明确的声明;
- 类型推导(
var
/final
/const
)
- 案例代码如下:
main(List<String> args) {
//1.明确的声明
String name = "zyc";
//2.类型推导(var/final/const)
//2.1 var声明变量
var age = 20;
//2.2 final声明常量
final weight = 2.55;
//2.3 const声明常量
const address = "南京";
//final与const的区别
// const date1 = DateTime.now();
final date2 = DateTime.now();
final p1 = Person("zyc");
final p2 = Person("zyc");
print(identical(p1, p2)); //false
const s1 = Student("123");
const s2 = Student("123");
print(identical(s1, s2)); //true
}
class Person {
String name;
Person(String name) {
this.name = name;
}
}
class Student {
final String name;
const Student(this.name);
}
-
String name = "zyc"
:明确声明初始化变量name; -
var age = 20
:定义变量age并初始化,为整型变量,赋值其他数据类型会报错; -
final weight = 2.55
:定义常量weight并初始化,为浮点型常量,不可再次赋值修改; -
const address = "南京"
:定义常量address并初始化,为String类型,不可再次赋值修改; -
const
必须赋值,常量值(在编译期间需要有一个确定的值); -
final
可以通过计算/函数获取一个值(运行期间来确定一个值),DateTime.now()
是运行时获取当前时间,所以赋值给const常量会报错; - 定义一个Person类,然后创建两个Person对象,
入参相同,用final修饰
,这两个对象不是同一个对象
,identical
函数可用来检测两个对象是否是同一个对象; - 定义一个Student类,然后创建两个Student对象,
入参相同,用const修饰
,这两个对象是同一个对象
;
数据类型
- int,double
- bool
- String
- 集合类型:
List
/Set
/Map
- String类型的代码案例:
main(List<String> args) {
//1.定义字符串
var str1 = 'abc';
var str2 = "abc";
var str3 = """
abc
ass
sss
""";
//2.字符串拼接
var name = "zyc";
var age = 31;
var height = 188;
var message1 = "my name is ${name},age is ${age},height is ${height}";
var message2 = "name is ${name},type is ${name.runtimeType}";
print(message1); //my name is zyc,age is 31,height is 188
print(message2); //name is zyc,type is String
}
- 定义初始化字符串可以使用
单引号
,双引号
,三引号
; - 字符串引用时使用
${字符串变量名}
,这与Shell脚本语言的语法类似,{}
可加可不加,在确定边界时必须加上,否则会出现语法错误; -
name.runtimeType
:是获取字符串变量name的运行时类型
; - 集合类型的代码案例:
main(List<String> args) {
//1.数组List
var names = ["abc", "sss", "asd"];
names.add("fff");
names.remove("sss");
names.removeAt(1);
//2.集合Set
var movies = {"大话西游", "2012", "大白鲨"};
movies.add("长津湖");
//3.映射map
var info = {"name": "zyc", "age": 20, "height": 175};
bool isNameKey = info.containsKey("name");
print(isNameKey);
print(info.keys); //所有key
print(info.values); //所有value
}
函数
函数的定义
- 先上案例代码:
main(List<String> args) {
print(sum(10, 30));
}
//1.返回值类型可以省略,开发中不推荐这么写
int sum(int num1, int num2) {
return num1 + num2;
}
- 函数返回值类型
可以省略
,开发中不推荐这么写;
函数的参数
- Dart中不支持函数的重载;
- 函数的参数可分为两类:
- 必选参数:必须传,不存在默认值;
- 可选参数:位置可选参数 ,命令可选参数,存在默认值;
- 案例代码如下:
main(List<String> args) {
sayHello1("zyc");
sayHello2("zyc", 18, 175.5);
sayHello3("zyc", age: 31, height: 175);
sayHello3("wangwu", height: 172);
sayHello3("zhuliu", age: 33);
sayHello3("nima", height: 144, age: 55);
}
//1.必选参数:必须传
void sayHello1(String name) {
print(name);
}
//Dart中没有函数的重载
//2.可选参数:位置可选参数 / 命名可选参数
//可选参数可以支持有默认值
//2.1.位置可选参数:[int age,double height]
//实参和行参在进行匹配时,是根据位置匹配的
void sayHello2(String name, [int age = 22, double height = 123.4]) {
print(name);
print(age);
print(height);
}
//2.2.命名可选参数 {int age,double height }
void sayHello3(String name, {int age = 32, double height = 56.9}) {
print(name);
print(age);
print(height);
}
-
void sayHello2(String name, [int age = 22, double height = 123.4])
:其中[int age = 22, double height = 123.4]
为位置可选参数
用[]
,参和行参在进行匹配时,是根据位置匹配的,可以设置默认值,这种语法在Shell脚本语言中也存在; -
void sayHello3(String name, {int age = 32, double height = 56.9})
:其中{int age = 32, double height = 56.9}
为命令可选参数
用{}
,可以设置默认值,在进行函数调用时,命令可选参数必须写参数名
,且支持不用传所有的命令可选参数;
函数是一等公民
- 函数是一等公民意思就是说
函数可以作为另一个函数的参数或者返回值
,有点函数式编程的味道; - 案例代码如下:
main(List<String> args) {
//1.函数作为参数
test1(shit);
//2.匿名函数: (参数列表) {函数体}
test2(() {
print("匿名函数被调用");
return 10;
});
//3.箭头函数:函数体只有一行代码
test1(() => print("箭头函数被调用"));
test3((num1, num2) {
return num1 + num2;
});
test4((num1, num2) {
return num1 + num2;
});
var demo1 = demo();
print(demo1(30, 40));
}
//函数可以作为参数
void test1(Function foo) {
foo();
}
void test2(Function foo) {
var result = foo();
print(result);
}
//4.明确函数类型
typedef Calculate = int Function(int num1, int num2);
void test3(int foo(int num1, int num2)) {
var result = foo(20, 30);
print(result);
}
void test4(Calculate cal) {
print(cal(100, 200));
}
//5.函数作为返回值
Calculate demo() {
return (num1, num2) {
return num1 * num2;
};
}
void shit() {
print("shit函数被调用");
}
-
void test1(Function foo)
:test1函数的参数是一个函数Function
,此函数是没有类型的,也就是说任意一个函数都可以作为test1函数的参数传进来; -
void test2(Function foo)
在函数调用是传入参数是一个匿名函数
,即没有函数名称,只有函数实现体,匿名函数
的格式为:(参数列表) {函数体}
,使用匿名函数比较简便,不用我们再去定义一个函数了; -
箭头函数
:函数体只有一行代码,格式为:() => 函数体
-
Function
关键字没有明确函数类型,在大多数情况下我们需要明确函数的类型,void test3(int foo(int num1, int num2))
,test3函数就明确了传参函数的类型int返回值,两个int入参
,test3在调用时传入上述类型的函数实现体; -
void test3(int foo(int num1, int num2))
这种定义函数类型的方式比较麻烦,可使用typedef
关键字来定义函数的类型,譬如typedef Calculate = int Function(int num1, int num2)
,最后传参只要传Calculate
即可 -
Calculate demo()
:demo函数返回是一个Calculate类型的函数;
运算符
- 针对运算我们只介绍Dart中特有的运算符;
赋值运算符
-
??=
- 当原来的变量有值时,那么??=不执行;
- 当原来的变量为null,那么将值赋值给这个变量;
-
??
- ??前面的数据有值,那么就使用??前面的数据;
- ??前面的数据为null,那么就使用??后面的数据;
- 案例代码如下:
main(List<String> args) {
//??=
//当原来的变量有值时,那么??=不执行
//当原来的变量为null,那么将值赋值给这个变量
var name1 = "zyc";
name1 ??= "shit";
print(name1); //zyc
var age = null;
age ??= 100;
print(age); //100
//??
//??前面的数据有值,那么就使用??前面的数据
//??前面的数据为null,那么就使用??后面的数据
var name2 = "SF";
var temp2 = name2 ?? "SFFF";
print(temp2); //SF
var name3 = null;
var temp3 = name2 ?? "SFFF";
print(temp3); //SFFF
}
级联运算符
..
- 案例代码如下:
main(List<String> args) {
var p = Person();
p.name = "SF";
p.run();
p.eat();
//..级联运算符
var p1 = Person()
..name = "SF"
..eat()
..run();
}
class Person {
String name;
void run() {
print("run");
}
void eat() {
print("eat");
}
}
类与对象
- Dart是面向对象的开发语言,所以
类与对象
是相当重要的;
构造函数
- 先看代码案例:
main(List<String> args) {
var p = Person("SF", 27);
var p1 = Person.withNameAgeHeight("zyc", 31, 175.0);
var p2 = Person.fromMap({"name": "zyc", "age": 31, "height": 175.2});
print(p2);
//Object和dynamic的区别
//Object在调用方法,编译时会报错;
//dynamic在调用方法时,编译时不会报错,但是运行时会存在安全隐患
// Object obj1 = "zyc";
// print(obj1.substring(1));
//明确的类型声明
// dynamic obj2 = "zyc";
// print(obj2.substring(1));
//dynamic obj3 = 123;
//print(obj3.substring(1)); //运行时会报错
}
class Person {
String name;
int age;
double height;
//构造函数
// Person(String name, int age) {
// this.name = name;
// this.age = age;
// }
//语法糖
Person(this.name, this.age);
//命名构造函数
Person.withNameAgeHeight(this.name, this.age, this.height);
Person.fromMap(Map<String, dynamic> map) {
this.name = map["name"];
this.age = map["age"];
this.height = map["height"];
}
@override
String toString() {
return "$name $age $height";
}
}
- 创建Person类,默认构造函数为
Person()
,若自定义构造函数Person(String name, int age)
,那么默认构造函数为Person()
就会被覆盖,不能使用了; - 自定义构造函数
Person(String name, int age)
还有一种语法糖写法即Person(this.name, this.age)
- 命名构造函数,在原来基础上新增属性,这时就要用到
命名构造函数
,例如Person.withNameAgeHeight(this.name, this.age, this.height)
和Person.fromMap(Map<String, dynamic> map)
都是自定义的; -
Object
和dynamic
的区别:-
Object
是所有类的基类,其修饰变量,对象在调用方法,编译时会报错; -
dynamic
其修饰变量,对象在调用方法,编译时不会报错,但是运行时会存在安全隐患;
-
初始化列表
- 与C++的语法类似;
- 初始化列表的使用只能在构造函数中;
- 代码案例:
main(List<String> args) {
var p = Person("SF");
print(p.age);
}
class Person {
//被final修饰的变量为常量 只能被赋值一次
final String name;
final int age;
//初始化列表与C++语法类似
//创建对象时,若传入age,那么就使用传入的age,如果没有传入age,那么使用默认值,age为可选参数
Person(this.name, {int age}) : this.age = age ?? 10 {
//在执行此大括号的代码时,对象已经初始化完毕了
//必须保证在执行此大括号的代码之前,final修饰的name与age必须已经初始化
//所以下面代码报错
//this.age = 10;
}
//存在局限性
//Person(this.name, {this.age = 10});
}
-
Person(this.name, {int age}) : this.age = age ?? 10
:初始化列表,在执行大括号中的代码之前,完成对象的初始化;
重定向构造函数
- 在一个构造函数中,去调用另一个构造函数,注意⚠️是在冒号后面使用this进行调用;
- 案例代码如下:
main(List<String> args) {
var p = Person("SF");
print(p.age); //22
}
class Person {
String name;
int age;
//调用自定义构造函数Person._internal
Person(String name) : this._internal(name, 22);
Person._internal(this.name, this.age);
}
常量构造函数
- 在某些情况下,
传入相同值时
,我们希望返回同一个对象
,这个时候,可以使用常量构造方法; - 默认情况下,创建对象时,即使传入相同的参数,创建出来的也不是同一个对象,但是若在构造函数前加上
const
进行修饰,那么可以保证同一个参数,创建出来的对象是相同的,这样的构造函数称之为常量构造函数
- 案例代码如下:
main(List<String> args) {
const p1 = Person("zyc");
const p2 = Person("zyc");
//p1与p2是同一个对象
print(identical(p1, p2));
}
class Person {
final String name;
const Person(this.name);
}
- 注意常量构造函数,只存在一个final属性;
- 拥有常量构造函数的类中,所有的成员变量必须是final修饰的;
工厂构造函数
- Dart提供了
factory
关键字,用于通过工厂去获取对象; - 普通的构造函数,会默认返回创建出来的对象,而工厂构造函数,
需要手动返回一个对象
; - 案例代码:
main(List<String> args) {
final p1 = Person.withName("zyc");
final p2 = Person.withName("zyc");
print(identical(p1, p2)); //true
}
class Person {
//对象属性
String name;
String color;
//类属性
static final Map<String, Person> _nameCache = {};
static final Map<String, Person> _colorCache = {};
//工厂构造函数
//1.根据key值从缓存中获取对象,存在直接返回,不存在
factory Person.withName(String name) {
if (_nameCache.containsKey(name)) {
return _nameCache[name];
} else {
final p = Person(name, "default");
_nameCache[name] = p;
return p;
}
}
factory Person.withColor(String color) {
if (_colorCache.containsKey(color)) {
return _colorCache[color];
} else {
final p = Person("default", color);
_colorCache[color] = p;
return p;
}
}
Person(this.name, this.color);
}
- 用
final
修饰变量,保证变量不被胡乱修改;
setter与getter方法
- 案例代码:
main(List<String> args) {
final p = Person();
//直接访问属性
p.name = "zyc";
print(p.name);
//通过setter与getter访问属性
p.setName = "SF";
print(p.getName);
p.setAge = 31;
print(p.age);
}
class Person {
String name;
int age;
//setter
set setName(String name) {
this.name = name;
}
set setAge(int age) => this.age = age;
//getter
String get getName {
return name;
}
int get getAge => age;
}
- setter与getter方法中使用了
set
与get
关键字,格式如代码所示; - setter与getter方法中可以使用
箭头函数
,更加简便;
类的继承
- Dart中继承使用
extends
关键字,子类中使用super
访问父类; - 父类中的所有成员变量和方法都会被继承,但是
构造方法
除外; - 子类的构造方法在执行前,会隐含调用父类的
无参数默认构造函数
(没有参数且与类同名的构造方法); - 如果父类没有
无参数默认构造函数
,则子类的构造函数必须在初始化列表中
通过super
显式调用父类的某个构造函数; - 案例代码:
main(List<String> args) {}
class Animal {
int age;
Animal(this.age);
}
class Person extends Animal {
String name;
//必须完成父类属性age的初始化 在初始化列表中完成
Person(this.name, int age) : super(age);
}
抽象类
- 用
abstract
关键字修饰的类,称之为抽象类
; - 在Dart中没有具体实现的方法,称之为
抽象方法
; - 抽象方法必须存在于抽象类中;
- 案例代码:
main(List<String> args) {
//抽象类不能实例化
//final s = Shape();
//Map是系统的一个抽象类
//Map能实例化 是因为Map内部实现了一个工厂构造函数 external factory Map()
final map = Map();
print(map.runtimeType);
}
abstract class Shape {
void getArea();
}
//继承抽象类的子类 必须实现抽象类中定义的抽象方法
class Rectanle extends Shape {
@override
void getArea() {
print("画矩形");
}
}
- 抽象类不能实例化;
- 继承抽象类的子类 必须实现抽象类中定义的抽象方法,否则会报错;
external关键字详解
说道抽象类abstract,就不得不说一下external关键字,external关键字估计用到人很少,在看源码的时侯经常可以看到,如下:
class Object {
const Object();
external bool operator ==(other);
external int get hashCode;
external String toString();
@pragma("vm:entry-point")
external dynamic noSuchMethod(Invocation invocation);
external Type get runtimeType;
}
- 可以看到Object类里有很多方法都是用external声明,并且这些方法没有具体实现;
- 但我们看到class不是abstract class,为什么方法可以不用实现呢?这就是external的作用。
Tips:
external只声明方法,声明的方法需要由外部去实现,通常是由底层sdk根据不同平台(vm、web等)实现;若外部没实现,则会返回null;
1、external作用
- external修饰的方法具有一种实现方法声明和实现分离的特性。
关键在于它能实现声明和实现分离,这样就能复用同一套对外API的声明,然后对应不同平台的多套实现;这样不管是dart for web 还是dart for vm,对于上层开发而言都是同一套API; - external声明的方法由底层sdk根据不同平台实现,class不用声明为abstract class,所以class可直接实例化;
2、external声明方法实现
@patch
class 类名 {
...
@patch
external声明的方法名
...
}
external声明的方法,通过@patch注解实现,结构如上
;
比如Object里各种external声明方法的实现如下
:
@patch
class Object {
...
@patch
bool operator ==(Object other) native "Object_equals";
static final _hashCodeRnd = new Random();
static int _objectHashCode(obj) {
var result = _getHash(obj);
if (result == 0) {
// We want the hash to be a Smi value greater than 0.
result = _hashCodeRnd.nextInt(0x40000000);
do {
result = _hashCodeRnd.nextInt(0x40000000);
} while (result == 0);
_setHash(obj, result);
}
return result;
}
@patch
int get hashCode => _objectHashCode(this);
@patch
String toString() native "Object_toString";
@patch
@pragma("vm:exact-result-type", "dart:core#_Type")
Type get runtimeType native "Object_runtimeType";
...
}
隐式接口
- 在Dart中接口比较特殊,没有一个专门的关键字来声明接口;
- 默认情况下,定义的每个类都相当于默认也声明了一个接口,可称之为
隐式接口
,可以由其他类来实现,因为Dart不支持多继承; - 在开发中,我们通常将用于给别人实现的类 声明为抽象类;
- 案例代码:
main(List<String> args) {
}
class Animal {
void eat() {
print("eat");
}
}
class Runnner {
void run() {
print("run");
}
}
class Flyer {
void fly() {
print("fly");
}
}
//当将一个类当作接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法
class Superman extends Animal implements Runnner, Flyer {
@override
void eat() {
// TODO: implement eat
super.eat();
}
@override
void run() {
// TODO: implement run
}
@override
void fly() {
// TODO: implement fly
}
}
- 当将一个类当作接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法;
混入mixin
- 当
当前类
实现 隐式接口类,隐士接口类中的方法已实现,这时当前类
不想再实现隐式接口类中的方法,可使用混入
语法; - 定义可混入的类时,不能用
class
关键字,而是使用mixin
关键字; - 当前类使用
with
进行混入,使用混入时可以使用super
关键字; - 案例代码:
main(List<String> args) {
final sm = Superman();
sm.run();
sm.fly();
sm.eat();
}
class Animal {
void eat() {
print("Animal eat");
}
}
mixin Runnner {
void run() {
print("Runnner run");
}
}
mixin Flyer {
void fly() {
print("Flyer fly");
}
}
//当将一个类当作接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法
class Superman extends Animal with Runnner, Flyer {
@override
void eat() {
// TODO: implement eat
super.eat();
}
@override
void run() {
// TODO: implement run
super.run();
print("Superman run");
}
}
- 使用混入时,Superman可以实现Runnner中的方法,也可以不用实现Runnner中的方法,实现时可以使用
super
关键字,调用Runnner中的实现;
类成员和方法 => 静态static成员和方法
- 上面我们在类中定义的成员与方法都属于
实例对象
的,在开发中,我们也需要定义类级别的成员与方法
; - 类成员与方法的定义声明使用
static
关键字; - 类成员与方法 通过类名来调用;
- 案例代码:
main(List<String> args) {
final p = Person();
p.name = "zyc";
p.eat();
Person.color = "yellow";
print(Person.color);
Person.run();
}
class Person {
//成员属性 对象属性
String name;
//静态成员属性 类属性
static String color;
void eat() {
print("Person eat");
}
static void run() {
print("Person run");
}
}
枚举
- 使用
enum
关键字 定义枚举; - 案例代码:
main(List<String> args) {
final color = Colors.red;
switch (color) {
case Colors.red:
print("红色");
break;
case Colors.green:
print("灰色");
break;
case Colors.blue:
print("蓝色");
break;
default:
}
//获取枚举的所有值
print(Colors.values);
//获取枚举值的index
print(Colors.red.index);
}
enum Colors { red, green, blue }
泛型
- 待补充;
扩展Extension
- 给指定的类扩展方法;
- Extension是在Dart语言2.6.0版本才支持的;
import 'package:Fluter01/day01/shared/SFSizeFit.dart';
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String message = "Hello World";
final result = message.sf_split(" ");
print(result);
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("基础widget")),
body: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
)
);
}
}
extension StringSplit on String {
List<String> sf_split(String split) {
return this.split(split);
}
}
库的使用
在Dart中,你可以导入一个库来使用它所提供的功能;
库的使用可以使代码的重用性得到提高,并且可以更好的组合代码;
Dart中任何一个Dart文件都是一个库,即使你没有使用关键字
library
声明;-
库的导入:
- 系统库的导入;
- 自定义库的导入;
- 第三方库的导入;
案例代码- 系统库的导入:
import 'dart:async';
import 'dart:math';
main(List<String> args) {
final num1 = 10;
final num2 = 30;
//min函数 属于 dart:math系统库
print(min(num1, num2));
}
- 案例代码 - 自定义库的导入:
- 创建的自定义库的代码如下:
import 'Utils/math_utils.dart' as SFUtils;
main(List<String> args) {
//通过库的别名来调用函数方法
print(SFUtils.sum1(10, 20));
}
int sum1(int num1, num2) {
print("...");
return num1 + num2;
}
import 'Utils/math_utils.dart' show sum1;
main(List<String> args) {
//通过库的别名来调用函数方法
print(SFUtils.sum1(10, 20));
}
- 可使用
as
关键字给自定义库取个别名
,主要是解决冲突的问题; - 在默认情况下,导入一个库时,是导入这个库中的所有内容;
-
show
:可指定要导入的内容; -
hide
:隐藏某个要导入的内容,导入其他内容;
-
- 若库文件太多,这样导入的代码写的太多,可以单独创建一个文件A.dart,使用
export
关键字将库文件导入A.dart中,这样只要导入A.dart文件即可,如下所示:
import 'Utils/utils.dart';
main(List<String> args) {
print(dateFormat());
print(sum1(10, 20));
}
- Dart第三方库的网站:https://pub.dev
-
第三库的导入使用,如下所示:
- 第三方库的使用,如下所示:
import 'package:http/http.dart' as http;
main(List<String> args) async {
var url = Uri.https('www.googleapis.com', '/books/v1/volumes', {'q': '{http}'});
var response = await http.get(url);
if (response.statusCode == 200) {
var jsonResponse = convert.jsonDecode(response.body) as Map<String, dynamic>;
var itemCount = jsonResponse['totalItems'];
print('Number of books about http: $itemCount.');
} else {
print('Request failed with status: ${response.statusCode}.');
}
}