(一)Flutter学习之Dart变量和类型系统

前言

前些日子在公司尝试着使用 Flutter 开发一些功能。要想写好 Flutter 程序,首先 Dart 语言是基础。

当然,在实际开发中,可能由于时间的关系,不用了解到 Dart 的方方面面才开始 Flutter 的开发,可以边学边用。

为了形成一个完整的体系,我想先系统的讲解下 Dart 语言,然后在开始 Flutter 相关的介绍。

Dart 相对于 Java 来说有更强的变现力,甚至我觉得比 Kotlin 还要有表现力。Dart 不仅可以用来开发客户端,还可以用来开发Web前端和后端开发。

所以呢,我觉得 Dart 还是一门值得学习的语言,下面就开始我们的 Dart 学习之旅吧

本文是 Dart 相关的第一篇,主要介绍 Dart 语言的变量的定义和类型体系

Hello World

按照惯例,学习一门语言都是从 Hello World 开始的。下面我们看下 Dart 的 Hello World 程序:

main(){

  var str = "Hello world!";

  print(str);
  
}
  • 首先我们定义了顶级的 main 函数,这个程序的入口,如果没有返回值可以省略函数的返回类型
  • 然后我们通过 var 关键字定义了一个字符串,通过类型推导,str是一个字符串类型的变量
  • 最后通过系统的 print 函数输出 str 变量

变量的定义

上面我们提到了 Dart 是支持类型推导的,所以在定义变量的时候可以通过 var 关键字来定义

当然也可以显示的指定 变量的类型,如:

// 通过 var 关键字定义变量,省略具体的类型
var str = "Hello world!";

// 显式指定变量类型
String str = "Hello World!"

在 Dart 中万物皆对象,如 null、函数、数字 都是对象

所以我们的定义的变量如果没有给初始值,那么这个变量的默认是就是 null

int lineCount;
assert(lineCount == null);

定义变量的时候,可以使用 final 关键字来修饰,如

final String name = "chiclaim";

name = "johnny"; // Error

final定义的变量,在定义的时候就要初始化,并且只能赋值一次

定义变量的时候还可以使用 const 关键字来修饰:

const name = "chiclaim"

name = "johnny"; // Error

const 关键字修饰变量,说明这个变量是 编译时常量,如数字、字符串字面量都是 编译时常量

constfinal 区别是 final 表示这个变量只能被赋值一次,const 表示该变量不仅只能被赋值一次,并且该变量是一个常量

const 不仅可以修饰变量,还可以修饰变量的值,表示该值不能修改:

main(){

  // 通过 const 关键字修饰foo变量的值
  var foo = const [];
  // 由于 foo 的值被 const 修饰,所以不能修改里面的值
  // 运行时错误,Unsupported operation: Cannot modify an unmodifiable list
  foo[0] = 0;
  // 可以赋值,因为 foo 变量没有被 final 或 const 修饰
  foo = [1,2,3];
  // 由于foo变量被重新赋值,所以可以修改里面的值
  foo[0] = 0;
  
  print(foo);

}

Dart类型系统

Dart 类型系统如下所示:

  • numbers(数字如整型,浮点型等)
  • strings(字符串)
  • booleans (布尔值)
  • lists (集合框架和数组)
  • sets (Set集合)
  • maps (Map集合)
  • runes (表达Unicode字符)
  • symbols (一般用不到)

numbers

不像 C/C++/Java 等语言,在 Dart 表示数字的类型只有两个:int 和 double

  • int :表示没有小数点的整型,最大值不大于64-bit(-2^63 ~ 2^63-1)底层依赖的系统最大值也不同,如果编译后用于JavaScript,则最大值为 -2^53 ~ 2^53-1

  • double 表示双精度的浮点型

int age = 17;

double weight = 65.5;

double waistline = 30; // 相当于 30.0

字符串和数字类型之间互转:

// 字符串转化成整型
int num = int.parse("2");

// 字符串转化成浮点型
double pi = double.parse("3.14");

// 浮点型转成字符串
String piStr = 3.14.toString();

// 浮点型转成用小数表示的字符串(可以选择保留几位小数)
String piAsStringFix = 3.14159.toStringAsFixed(2);

string 字符串

字符串的创建

一个 Dart 字符串是个 UTF-16 码元(code units)序列,可以使用单引号或双引号来创建一个字符串

// 双引号创建一个字符串字面量
var s1 = 'Single quotes work well for string literals.';
// 单引号创建一个字符串字面量
var s2 = "Double quotes work just as well.";
//  单引号创建字符串字面量(需要转义符)
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

创建原生(raw)字符串,在字符串前面加上前缀 r

// 一般 \n 是换行符,如果我们想把 \n 当做字符串输出时候,也可以使用创建原生字符串的方式
var raw = r'In a raw string, not even \n gets special treatment.';

我们还可以将表达式(${expression})嵌入字符串中:

const name = "chiclaim";
// 将表达式${expression}嵌入字符串中
var info = "My name is ${name}";
// 如果表达式是一个标识符,可以省略花括号
var info2 = "My name is $name";

字符串的拼接:

// 1. 通过 + 号来拼接字符串
var s2 = 'The + operator ' + 'works, as well.';

// 2. 毗邻的字符串字面量
var s1 = 'String '
    'concatenation'
    " works even over line breaks.";

// 3. 通过多行字符串(使用三引号)
var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

字符串常用的函数

1. 字符串查询相关
  • contains(str) 字符串中是否包含某个字符串
  • startsWith(str) 字符串是否以某个字符串开头
  • endsWith(str) 字符串是否以某个字符串结尾
  • indexOf(str) 字符串在目标字符串中的位置
// Check whether a string contains another string.
assert('Never odd or even'.contains('odd'));

// Does a string start with another string?
assert('Never odd or even'.startsWith('Never'));

// Does a string end with another string?
assert('Never odd or even'.endsWith('even'));

// Find the location of a string inside a string.
assert('Never odd or even'.indexOf('odd') == 6);
2. 字符串提取
  • substring(startIndex,endIndex) 字符串截取
  • split(Pattern) 字符串分割
  • length 字符串长度
  • [index] 通过索引获取字符串中UTF-16编码单元
  • codeUnits 返回字符串的UTF-16编码单元的不可修改的List
// 字符串截取
assert('Never odd or even'.substring(6, 9) == 'odd');

// 使用正则表达式分割字符串,返回字符串集合
var parts = 'structured web apps'.split(' ');

// 通过索引获取字符串的UTF-16编码单元
assert('Never odd or even'[0] == 'N');

// 循环打印字符串中的字符串
for (var char in 'hello'.split('')) {
    print(char);
}

// 获取字符串中的所有UTF-16码元
var codeUnitList ='Never odd or even'.codeUnits.toList();

// N 的 ASCII 编码的十进制就是 78
assert(codeUnitList[0] == 78);
3. 大小写转换
  • toUpperCase 将字符串全部转成大写
  • toLowerCase 将字符串全部转成小写
// 转成大写
assert('structured web apps'.toUpperCase() == 'STRUCTURED WEB APPS');

// 转成小写
assert('STRUCTURED WEB APPS'.toLowerCase() ==  'structured web apps');
4. 字符串的头尾空格的移除和空字符串
  • trim 去除头和尾的空格
  • isEmpty 字符串是否为空
  • isNotEmpty 字符串是否不为空
// 去除头尾的空格
print('  hello  '.trim() == 'hello');

// 字符串是否为空
print(''.isEmpty);

// 空格不是空字符串
print('  '.isNotEmpty);

// 空格trim后,就变成空字符串
print('  '.trim().isNotEmpty);
5. 字符串部分替换
  • replaceAll

字符串是不可变的,替换函数不是修改原字符串,而是会产生一个新的字符串

var greetingTemplate = 'Hello, NAME!';
var greeting =  greetingTemplate.replaceAll(RegExp('NAME'), 'Bob');

// greetingTemplate didn't change.
assert(greeting != greetingTemplate);
6. 字符串构建

通过编程的方式生成一个字符串,可以使用 StringBuffer 来实现,直到调用 StringBuffer.toString() 函数才会创建字符串

var sb = StringBuffer();

// 通过 .. 实现链式调用
sb
  ..write('Use a StringBuffer for ')
  // 写入一个字符串列表,以空格分割
  ..writeAll(['efficient', 'string', 'creation'], ' ')
  ..write('.');

// 创建字符串
var fullString = sb.toString();

assert(fullString ==
    'Use a StringBuffer for efficient string creation.');

Boolean

在 Dart 中通过 bool 关键字来描述布尔,布尔有两个值:true和false。 true 和 false 都是编译器常量

bool isSuccess = true;
bool isFailed = false;

List

List不仅用来表示List集合(有序的数据集合),也用来表示数组

下面来看下如何创建一个List 字面量

var list = [1, 2, 3]; // 类型推导出list是 List<int>

介绍 const 关键字的时候,也用 const 修饰 List 的值 如:

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Error.

Dart2.3 提供了展开操作符(... 和 ...?) 让开发者更加方便的将多个值插入到集合中

var list = [1, 2, 3];
// 展开操作符(...) spread operator
var list2 = [0, ...list];


var list;
// 展开操作符(...?) null-aware spread operator
var list2 = [0, ...?list];

Dart2.3 除了提供了展开操作符,还有 collection ifcollection for

// collection if
var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

// collection for
var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

collection if/for 在开发Flutter的时候很有用,可以简化很多代码

Set

Set 表示一个无序的集合,下面看下如何创建一个 Set 字面量

// halogens is Set<String>
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

创建一个空 Set :

// 通过var和泛型推导出Set类型
var names = <String>{};

// 显式指定是Set类型
Set<String> names = {}; 

需要注意的是下面是创建了一个Map
var names = {}; 

类似地,展开操作符collection if/for 也可以用到 Set 中

Map

Map 是一个 键值对 集合,下面来看下如何创建 Map 字面量:

// gifts is Map<String, String>
var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

// nobleGases is Map<int, String>
var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

需要注意的时候,Key只能出现一次,否则会被后面的替换掉

通过中括号 [] 来添加、修改、获取Map中的元素:

var gifts = Map();
//  添加元素
gifts['first'] = 'partridge';

var gifts = {'first': 'partridge'};
// 获取通过Key获取Map中的元素
assert(gifts['first'] == 'partridge');

var gifts = {'first': 'partridge'};
// 修改Map中的元素
gifts['fourth'] = 'calling birds';

类似地,展开操作符collection if/for 也可以用到 Set 中

关于 Dart 的 展开操作符 以及 collection if/for 更多内容可以查看:

(二)Flutter学习之Dart展开操作符 和 Control Flow Collections

Rune

在 Dart 中,Rune类型是一个字符串的UTF-32的码点(code points),我们在介绍 String 类型的时候,提到了 code units

那么在介绍 Rune 之前,我们先来看下 code points 和 code units 是什么?

Unicode 编码为世界上所有的字母、数字、符号定义了对应的数字来表示

  • 码点:Unicode是属于编码字符集的范围。它所做的事情就是将我们需要表示的字符表中的每个字符映射成一个数字,这个数字被称为相应字符的码点(code points)
  • 码元:就是码点多少个字节能存储,比如 UTF-8 码元就是 8 bit 也就是 1 byte,UTF-16 码元就是 2 byte

因为 Dart 字符串是 UTF-16 码元的序列,描述 32-bit Unitcode 的字符串需要特殊的语法

通常地, Unicode使用 \uXXXX 描述码点 ,这里的 XXXX 是一个4位十六进制的值,例如描述心形的符号使用 \u2665 表示

如果多于或者少于 4 个十六进制,则需要加上花括号,例如笑的表情使用 \u{1f600}

String 类有几个属性可以提取字符串的 rune 信息 (例如属性 runes),codeUnitAtcodeUnit 属性返回 16 bit的 码元(code units)

main() {
  var clapping = '\u{1f44f}';
  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));
}

Object 和 dynamic

在 Dart 所有的类都是继承自 Object,和Java类似。dynamic 和 Object 允许所有的值赋值给它:

dynamic name;
Object email;

main() {
  name = "chiclaim";
  email = "chiclaim@gmail.com";
}

但是他们是两个完全不同的概念。你甚至可以把 dynamic 不要当做一个类型看待,dynamic 顾名思义就是动态的意思,它只是告诉编译器不要做类型检查

dynamic name;
Object email;

main() {
  // 将 String 赋值给 name
  name = "chiclaim";
  // 打印 name 的长度
  print(name.length);
  
  // 将 int 赋值给name
  name = 1;
  
  // 编译器并不会报错(编译时不做类型检查),运行时才会报错
  // NoSuchMethodError: Class 'int' has no instance getter 'length'.
  print(name.length);
  
  email = "chiclaim@gmail.com";
  // 编译器报错,因为 Object 没有 length 属性
  print(email.length);
}

通过上面的代码示例,相信你对 dynamic 和 Object 的区别有了比较清楚的理解

Dart 在官网的最佳实践中建议开发者,如果你想表达意思是任何对象都可以,使用 Object 代替 dynamic

如果你允许类型推导失败,一般推到失败,编译器会默认给dynamic,但是建议显式地写上 dynamic ,因为代码的阅读者不知道你是忘记了写类型还是:

// 建议
dynamic mergeJson(dynamic original, dynamic changes){
}

// 不建议
mergeJson(original, changes){
}

Dart几个重要的概念

  • 万物接对象:null、函数、数字
  • Dart有类型推导功能,定义变量是可以不指定变量类型,如果不想指定类型,可以使用 dynamic 类型
  • Dart支持泛型,如 List<Int>、List<dynamic>
  • Dart支持 top-level 函数,也支持成员函数和静态函数,同时也支持函数的嵌套
  • Dart没有 public、private、protected 关键字来控制访问权限,Dart 通过下划线来控制访问权限,如果以下划线开头,则表示只能在 library 内可见

Reference

关于 Dart 变量和类型系统 就讲到这里, 更多的关于 Android 学习资料可以查看我的GitHub: https://github.com/chiclaim/AndroidAll

https://dart.dev/guides/language/language-tour
https://github.com/acmerfight/insight_python/blob/master/Unicode_and_Character_Sets.md

更多

所有关于 Retrofit 的使用案例都在我的 AndroidAll GitHub 仓库中。该仓库除了 Retrofit,还有其他 Android 其他常用的开源库源码分析,如「RxJava」「Glide」「LeakCanary」「Dagger2」「Retrofit」「OkHttp」「ButterKnife」「Router」等。除此之外,还有完整的 Android 程序员所需要的技术栈思维导图,欢迎享用。

下面是我的公众号,干货文章不错过,有需要的可以关注下,有任何问题可以联系我:

公众号: chiclaim
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容