您的位置:首页 > 文旅 > 旅游 > 哪里有学市场营销培训班_网络营销实务_东莞做网站哪家好_百度收录查询工具官网

哪里有学市场营销培训班_网络营销实务_东莞做网站哪家好_百度收录查询工具官网

2025/4/24 4:18:46 来源:https://blog.csdn.net/weixin_67448099/article/details/147347919  浏览:    关键词:哪里有学市场营销培训班_网络营销实务_东莞做网站哪家好_百度收录查询工具官网
哪里有学市场营销培训班_网络营销实务_东莞做网站哪家好_百度收录查询工具官网

1. 认识Dart

Google为Flutter选择了Dart语言已经是既定的事实,无论你多么想用你熟悉的语言,比如JavaScript、TypeScript、ArkTS等来开发Flutter,至少目前都是不可以的。

  • Dart 是由谷歌开发的计算机编程语言,它可以被应用于 Web/服务器/移动应用和物联网等领域开发。
  • Dart 也是 Flutter 的基础,Dart 作为 Flutter 应用程序的编程语言,为驱动应用运行提供了环境。
  • 因此, 要学习Flutter, 则首先得会Dart, Dart 目前最新稳定版本:v3.4.0

好消息:如果你会 JavaScriptTypescriptJava 中任何一门语言,你将很快且很容易就能学会Dart的使用。

2. 搭建Dart开发环境

为什么需要安装Dart呢?

事实上如果提前安装了Flutter SDK,它已经内置了Dart了,我们完全可以直接使用Flutter去进行Dart的编写并且运行。

但是,如果想单独学习Dart,并且运行自己的Dart代码,最好去安装一个Dart SDK。

要在本地开发Dart程序, 首先需要安装Dart SDK

官方文档: Get the Dart SDK | Dart , 中文文档: Dart 编程语言主页 | Dart 中文文档 | Dart

无论是什么操作系统,安装方式都是有两种:通过工具安装直接下载SDK,配置环境变量

a.通过工具安装

  • Windows可以通过Chocolatey
  • macOS可以通过homebrew
  • 具体安装操作官网网站有详细的解释

b.直接下载SDK,配置环境变量

  • 下载地址:https://dart.dev/tools/sdk/archive
  • 我采用了这个安装方式。
  • 下载完成后,根据路径配置环境变量即可。

1. Windows环境安装Dart
1.1 下载压缩包, 然后解压放在任意盘符下(注意不要是中文目录下)

1.2 找到bin目录, 复制完整路径, 配置环境变量

1.3 cmd 窗口执行 dart --version 看到dart-sdk的版本代表OK

2. Mac环境安装Dart
  • macOS支持的架构: x64、ARM64

2.1 下载并解压Dart SDK,放到电脑的某个目录下面(不要是中文)

2.2 配置环境变量

打开终端,‌使用文本编辑器(‌如vinano)‌编辑~/.zshrc~/.bash_profile文件。‌在文件的末尾添加以下行,‌将Dart SDK的路径添加到系统的PATH中:‌

在终端中运行vim ~/.zshrc命令

export PATH="/path/to/dart/sdk/bin:$PATH"

注意:

  1. /path/to/dart/sdk/bin替换为你解压Dart SDK的实际路径。‌
  2. 保存并关闭文件:‌保存并关闭文本编辑器(按下esc,输入:wq)
2.3 使环境变量生效

在终端中运行source ~/.zshrc命令,‌使新的环境变量设置立即生效。‌

2.4 验证安装

运行dart --version命令来检查Dart的版本信息,‌如果正确显示版本信息,‌则表示Dart已经成功安装。‌

3. Dart初体验

3.1 VSCode中安装常用插件
  • Dart插件:可以帮助我们在VSCode中更加高效的编写Dart代码
    提供了友好的代码提示,代码高亮,以及代码的重构、运行和重载

  • Flutter插件:为Flutter开发准备的

  • Code Runner:可以点击右上角的按钮快速运行代码

3.2 第一个Dart程序
  1. 创建dart文件
  • dart文件名必须以 .dart 结尾:01-第一个dart程序.dart

  1. 编写dart代码

需求:打印字符串 'hello dart!'

// 程序的入口:main函数
void main() {// 需求:打印字符串 'hello itcast'print('hello itcast'); // hello itcast
}
  1. 执行dart代码
  • 方式一:终端中执行:终端打开dart文件所在目录
dart 01-dart初体验.dart

  • 方式二:
    • VSCode中执行

    • VSCode中查看代码执行结果

小结:

1、Dart语言的入口也是main函数,并且必须显示的进行定义;

2、Dart的入口函数main是没有返回值的;

3、定义字符串的时候,可以使用单引号或双引号;

4、每行语句必须使用分号结尾,很多语言并不需要分号,比如JavaScript;

4. Dart基础语法

4.1 变量和常量(存储数据)
4.1.1 变量(存储并修改数据)

需求:存储一个姓名和年龄并修改年龄?

实现:变量关键字:var

要点:

  1. 声明变量:var 变量名 = 表达式;
  2. 修改变量:变量名 = 新值;
  3. 类型推断:var关键字声明的变量支持类型推断,修改变量时会检查之前存储数据的类型
void main() {// 1. 声明变量:var 变量名 = 表达式;var name = 'itheima';print(name); // itheimavar age = 17;print(age); // 17// 2. 修改变量:变量名 = 新值;age = 18;print(age); // 18// 3. 类型推断:var关键字声明的变量支持类型推断,修改变量时会检查之前存储数据的类型// 报错:A value of type 'String' can't be assigned to a variable of type 'int'.// age = 'itcast';
}
4.1.2 常量(存储不变的数据)

需求:存储不可被修改的数据

实现:常量关键字:constfinal

区别:final是运行时常量,值在运行时赋值;const是编译期常量,值在编译时赋值;

void main() {// 1. const声明常量const num1 = 10;print(num1); // 10// 报错:Constant variables can't be assigned a value.// num1 = 20;// 2. final声明常量final num2 = 30;print(num2); // 30// 报错:The final variable 'num2' can only be set once.// num2 = 40;// 3. const和final的区别:var x = 1;var y = 2;// 3.1 final:运行时常量,值在运行时赋值final ret1 = x + y;print(ret1);// 3.2 const:编译期常量,值在编译时赋值// 报错:Const variables must be initialized with a constant value.// const ret2 = x + y;const ret3 = 1 + 2;
}

注意: const 和 final的区别

final:运行时常量,值在运行时赋值

const:编译期常量,值在编译时赋值

4.2 数据类型(可存储数据类型)
4.2.1 num(数字)

需求:存储并修改整数和小数

实现:关键字:numintdouble

注意点:

num类型的变量既可以存整数也可以存小数

int类型的值可以赋值给double类型的变量,但是double类型的值不能赋值给int类型的变量

void main() {// 1. num类型 (整数、小数)num n1 = 10;print(n1); // 10n1 = 10.1;print(n1); // 10.1// 2. int类型 (整数)int n2 = 20;print(n2); // 20// 测试:将小数赋值给int// 报错:A value of type 'double' can't be assigned to a variable of type 'int'.// n2 = 20.2;// 3. double类型 (小数)double n3 = 30.3;print(n3); // 30.3// 测试:将整数赋值给doublen3 = 30;print(n3); // 30.0
}
4.2.2 String(字符串)

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

需求:声明字符串,修改字符串,拼接字符串

注意:模板字符串支持运算

实现:关键字:String

void main() {// 1. 定义字符串String name = '张三';String gender = "男";// 2. 修改字符串name = '李四';gender = '女';print(name);print(gender);// 3. 字符串拼接double money = 99991.99;String intro = '姓名:$name,性别:$gender,钱包:${money * 100}';print(intro);// 4. 字符串换行String tag = '''通知明天天气不好注意放假''';print(tag);
}
4.2.3 bool(布尔)

需求:存储并修改真或假

实现:关键字:bool

void main() {// 登录成功:truebool isLogin = true;print(isLogin); // true// 登录失败:falseisLogin = false;print(isLogin); // false
}
4.2.4 List(列表)

需求:使用一个变量有序的存储多个值

实现:列表(数组)关键字:List

  • 定义列表 List 变量名 = [元素1, 元素2, ..., 元素n];
    • 1.1 需求:按序存储数字 1 3 5 7
    • 1.2 需求:按序存储字符串 '居家' '美食' '服饰'
    • 1.3 列表中可以存储任意类型的数据
void main() {// 1. 定义列表:List 变量名 = [元素1, 元素2, ..., 元素n];// 1.1 需求:按序存储数字 1 3 5 7List nums = [1, 3, 5, 7];print(nums);// 1.2 需求:按序存储字符串 '居家' '美食' '服饰'List categories = ['居家', '美食', '服饰'];print(categories);// 1.3 列表中可以存储任意类型的数据List ret = [18,18.8,'美食',true,['itcast', 10],{}];print(ret);
}
  • 使用列表:查改增删
    • 查询列表长度
    • 查询指定的元素
    • 修改:列表[索引] = 新值
    • 新增:列表.add(新元素)、列表.addAll(新列表)
    • 指定位置添加:列表.insert(索引, 内容');
    • 删除:使用元素删除、使用索引删除
    • 遍历列表:读取出列表中每一个元素remove,removeAt
// 2. 使用列表:查改增删
// 2.1 查询:
// 查询列表的长度(列表内部元素的个数)
int len = categories.length;
print(len); // 3
// 查询指定的元素: 列表[索引]
String category = categories[1];
print(category); // 美食// 2.2 修改:列表[索引] = 新值
categories[1] = '美食家';
print(categories); // [居家, 美食家, 服饰]// 2.3 新增:
// 一次新增一个元素:列表.add(新元素)
categories.add('母婴');
print(categories); // [居家, 美食家, 服饰, 母婴]
// 一次新增多个元素:列表.addAll(新列表)
categories.addAll(['办公', '生鲜']);
print(categories); // [居家, 美食家, 服饰, 母婴, 办公, 生鲜]
// 在指定位置新增元素
categories.insert(2, '教育');
print(categories); // [居家, 美食家, 教育, 服饰, 母婴, 办公, 生鲜]// 2.4 删除
// 使用元素删除
categories.remove('生鲜');
print(categories); // [居家, 美食家, 教育, 服饰, 母婴, 办公]
// 使用索引删除
categories.removeAt(4);
print(categories); // [居家, 美食家, 教育, 服饰, 办公]
  • 遍历列表:读取出列表中每一个元素
// 3. 遍历列表:读取出列表中每一个元素
// element: 列表中读取出来的每一个元素
categories.forEach((element) {print(element);
});
4.2.5 Map(字典)

需求:声明键值对的集合,并使用与之相关联的键从中获取值(类似JS中的对象)

实现:字典关键字:Map

  1. 存储商品分类的编号 和 名称
  2. 对字典数据进行查改增删

2.1 查询:字典[key]

2.2 修改:字典[key] = 新值

2.3 新增:字典[新key] = 新值 (注意:key必须是当前字典中不存在的key,如果key已存在就是修改)

2.4 删除:remove(key) (注意:如果key不存在,不会报错,也不会执行删除操作)

  1. 遍历字典
  • 定义字典语法
Map 变量名 = {'键1': 值1,'键2': 值2,...,
};
  • 例子:存储商品分类的编号 和 名称
void main() {// 1. 例子:存储商品分类的编号 和 名称Map category = {'id': 1,'name': '居家',};print(category); // {id: 1, name: 居家}
}
  • 使用字典:查改增删
// 2. 使用字典:查改增删
// 2.1 查询:字典[key]
String name = category['name'];
print(name); // 居家// 2.2 修改:字典[key] = 新值
category['name'] = '美食';
print(category); // {id: 1, name: 美食}// 2.3 新增:字典[新key] = 新值
// 注意:key必须是当前字典中不存在的key,如果key已存在就是修改
category['price'] = 199.9;
print(category); // {id: 1, name: 美食, price: 199.9}// 2.4 删除:remove(key)
// 注意:如果key不存在,不会报错,也不会执行删除操作
category.remove('name');
print(category); // {id: 1, price: 199.9}
  • 遍历字典
// 3. 遍历字典
category.forEach((key, value) {print('$key -- $value');// id -- 1// price -- 199.9
});
4.2.6 Dart空安全机制

如何尽早的发现并解决null带来的异常?

Dart提供了健全的空安全机制,默认所有的变量都是非空的,如果某个变量得到了一个null,则代码在编译期就会报错

  1. 无法正常执行的代码:在代码编译期就会报错
  2. 解决办法:使用 ? 显示的指定变量可以为空
  3. 使用可以为空的变量
void main() {// 1. 可以正常执行的代码String name1 = 'itcast';print(name1.length);// 2. 无法正常执行的代码:在代码编译期就会报错// String name2;// The non-nullable local variable 'name2' must be assigned before it can be used.// 报错:Null check operator used on a null value// print(name2.length);// 3. 解决办法:使用 ? 显示的指定变量可以为空String? name3 = null;print(name3);// 4. 使用可以为空的变量// name3? : 表示非空检查,如果name3为空,不去调用属性或方法,如果name3不为空,就去调用属性或方法print(name3?.length);
}
4.3 运算符(数据如何做运算)
4.3.1 算术运算符

如何对数字做加减乘除等运算?

多了取整 ~/

void main() {int n1 = 10;int n2 = 3;// 加 +print(n1 + n2); // 13// 减 -print(n1 - n2); // 7// 乘 *print(n1 * n2); // 30// 除 /print(n1 / n2); // 3.3333333333333335// 取整:取除法结果的整数部分 ~/print(n1 ~/ n2); // 3// 取模:取除法结果的余数 %print(n1 % n2); // 1// 案例:计算购物车商品总价格:商品A一件,每件289.0元;商品B二件,每件39.0元double total = 289.0 + (2 * 39.0);print(total); // 367.0
}
4.3.2 赋值运算符(同TS)

如何对数字做赋值运算?

void main() {// 等于 =int n1 = 10;// 加等于 +=// n1 = n1 + 5;n1 += 5;print(n1); // 15// 减等于 -=n1 -= 5;print(n1); // 10// 乘等于 *=n1 *= 5;print(n1); // 50// 除等于 /=// 注意:double类型的数据才能做除等于的操作// A value of type 'double' can't be assigned to a variable of type 'int'.// n1 /= 5;double n2 = 50;n2 /= 5;print(n2); // 10.0// 取余等于 %=int n3 = 10;n3 %= 3;print(n3); // 1// 自增:在原有数值上加1 ++int a = 10;a++;print(a); // 11// 自减:在原有数值上减1 --int b = 20;b--;print(b); // 19
}
4.3.3 比较运算符

如何比较数字大小?

没有 === 和 !== 运算符

void main() {int n1 = 10;int n2 = 20;// 大于 >print(n1 > n2); // false// 小于 <print(n1 < n2); // true// 大于等于 >=print(n1 >= n2); // false// 小于等于 <=print(n1 <= n2); // true// 等于 ==print(n1 == n2); // falseprint('itcast' == 'itheima'); // false// 不等于 !=print(n1 != n2); // trueprint('itcast' != 'itheima'); // true
}
4.3.4 逻辑运算符(同TS)

如果表示数据之间的逻辑关系?

void main() {// 年龄int age = 33;// 工作年限int years = 10;// 1. 逻辑与:一假则假// 年龄大于28岁,并且工作年限大于4年bool ret1 = age > 35 && years > 4;print(ret1); // false// 2. 逻辑或:一真则真// 年龄大于23岁,或者工作年限大于2年bool ret2 = age > 35 || years > 2;print(ret2); // true// 3. 逻辑非:真变假,假变真print(!true); // falseprint(!false); // true// 工作年限不小于9年bool ret3 = years >= 9;// bool ret3 = !(years < 9);print(ret3); // true
}

4.4 流程控制(选择或重复执行)
4.1.1 if分支语句(同TS)

单分支、双分支、多分支

void main() {// 1. if单分支语句// 准备高考成绩,如果分数大于等于700分,则输出 '恭喜考入黑马程序员'int score1 = 699;if (score1 >= 700) {print('恭喜考入黑马程序员');}// 2. if双分支语句// 准备高考成绩,如果分数大于等于700分,则输出 '恭喜考入黑马程序员',反之,则输出 '继续努力'int score2 = 699;if (score2 >= 700) {print('恭喜考入黑马程序员');} else {print('继续努力');}// 3. if多分支语句// 根据学生分数划分学生成绩等级:// 优秀:分数大于等于90分// 良好:分数小于90分,且大于等于80分// 中等:分数小于80分,且大于等于60分// 不及格:分数小于60分int score3 = 58;if (score3 >= 90) {print('优秀');} else if (score3 >= 80) {print('良好');} else if (score3 >= 60) {print('中等');} else {print('不及格');}
}

4.1.2 三元运算符(同TS)

简化简单的if双分支语句

void main() {// 需求:准备高考成绩,如果分数大于等于700分,则输出 '恭喜考入黑马程序员',反之,则输出 '继续努力'// 思考:以下代码可以简化吗?int score1 = 699;// if (score1 >= 700) {//   print('恭喜考入黑马程序员');// } else {//   print('继续努力');// }// 1. 使用三元运算符简化if双语句:条件表达式 ? 表达式1 : 表达式2score1 >= 700 ? print('恭喜考入黑马程序员') : print('继续努力');// 2. 思考:以下代码适合使用三元运算符改写吗?int score2 = 88;if (score2 >= 90) {print('优秀');} else if (score2 >= 80) {print('良好');} else if (score2 >= 60) {print('中等');} else {print('不及格');}
}

4.1.3 switch case 语句(同TS)

如果分支很多,且条件是判断相等,则switch case 语句性能比 if 分支语句要好

void main() {// 根据订单状态,打印出订单状态描述信息// 订单状态:1为待付款、2为待发货、3为待收货、4为待评价int orderState = 3;switch (orderState) {case 1:print('待付款');break;case 2:print('待发货');break;case 3:print('待收货');break;case 4:print('待评价');break;default:print('其他');}
}

4.1.4 循环语句(同TS)

如何让代码重复执行?

循环语句:while循环for循环

void main() {// 1. while循环// 重复打印10次 '月薪过万'int n = 0;while (n < 10) {print('$n -- 月薪过万');n++;}// 2. for循环// 重复打印5次 '李白姓白'for (var i = 0; i < 5; i++) {print('$i -- 李白姓白');}// 3. 使用循环遍历列表// 3.1 遍历列表:for循环List categories = ['居家', '美食', '服饰'];for (var i = 0; i < categories.length; i++) {String name = categories[i];print(name);}// 3.2 遍历列表:for ... infor (var item in categories) {// item就是遍历出来的元素print(item);}// 4. 终止循环// 4.1 break:中断整个循环for (var i = 0; i < 5; i++) {if (i == 2) {// 吃到第三个苹果发现了虫子,剩下的苹果没胃口都不吃了break;}print('我把第 ${i + 1} 个苹果吃了');}// 4.2 continue:跳过本次循环直接进入下一次循环for (var i = 0; i < 5; i++) {if (i == 2) {// 吃到第三个桃子发现了虫子,第三个桃子不吃了,剩下的桃子接着吃continue;}print('我把第 ${i + 1} 个桃子吃了');}
}
4.5 基础语法 - 综合案例

需求:计算购物车数据中,被勾选商品的总价

void main() {// 准备购物车数据List carts = [{"count": 2, "price": 10.0, "selected": true},{"count": 1, "price": 30.0, "selected": false},{"count": 5, "price": 20.0, "selected": true}];// 记录总金额double totalAmount = 0.0;// 遍历购物车数据carts.forEach((element) {// 读取商品的勾选状态bool selected = element['selected'];// 如果商品被勾选 ,读取该商品的单价和数量,并计算价格小计if (selected) {double amount = element['count'] * element['price'];// 累加价格小计,计算总价totalAmount += amount;}});print(totalAmount);
}

五、函数(复用代码)

5.1 函数的定义
  1. 定义函数:无参数无返回值函数
  2. 定义函数:有参数有返回值函数
  3. 函数的特点:
  • 返回值类型和参数类型是可以省略的
void main() {// 2. 调用无参数无返回值函数func();// 4.调用有参数有返回值函数 int ret = sum(10, 20);print(ret); // 30
}// 1. 定义函数:无参数无返回值函数
void func() {print('这是一个无参数无返回值函数');
}// 3. 定义函数:有参数有返回值函数
// 需求:定义函数,计算任意两个整数的和,并返回计算结果
int sum(int a, int b) {int ret = a + b;return ret;
}
  • 函数的特点:
    • 返回值类型和参数类型是可以省略的
void main() {// 2. 调用无参数无返回值函数// func();// 4. 调用有参数有返回值函数int ret = sum(10, 20);print(ret); // 30
}// 1. 定义函数:无参数无返回值函数
func() {print('这是一个无参数无返回值函数');
}// 3. 定义函数:有参数有返回值函数
// 需求:定义函数,计算任意两个整数的和,并返回计算结果
// 特点2:返回值类型和参数类型是可以省略的
sum(a, b) {int ret = a + b;return ret;
}
5.2 函数的参数

函数的参数可以分为:必传参数(位置参数)、可选参数(关键字参数)

注意点:必传参数不能为空,可选参数可以为空,且参数都可以设置默认值

void main() {printString('张三丰');printString('李四', age: 18);printString('王五', location: '昌平区');printString('赵六', age: 18, location: '海淀区');
}/*** name:必传参数* age:可选参数* location:可选参数,并有默认值*/
void printString(String name, {int? age, String? location = '昌平区'}) {print('$name - $age - $location');
}

5.3 函数对象

函数可以作为对象赋值给其他变量

函数可以作为参数传递给其他函数

void main() {// 1.2 定义一个变量接收函数// var f = funcDemo1;Function f = funcDemo1;f();// 2.2 函数作为参数funcDemo2(funcDemo3);
}// 1.1 函数可以作为对象赋值给其他变量
void funcDemo1() {print('funcDemo1');
}// 2.1 函数可以作为参数传递给其他函数
void funcDemo2(Function func) {// 调用外界传入的函数func();
}// 定义作为参数的函数: 把funcDemo3传入到funcDemo2
void funcDemo3() {print('funcDemo3');
}

5.4 匿名函数
  1. 匿名函数赋值给变量,并调用
  2. 可以作为参数传递给其他函数去调用(回调函数)
void main() {// 匿名函数// 1. 匿名函数赋值给变量,并调用Function f = () {print('这是一个匿名函数');};f();// 2. 可以作为参数传递给其他函数去调用(回调函数)funcDemo(() {print('这个匿名函数是个参数');});
}// 定义一个接收函数作为参数的函数
void funcDemo(Function func) {func();
}

5.5 箭头函数

当函数体只有一行代码时,可以使用箭头函数简写

void main() {int ret1 = sum1(10, 20);print(ret1);int ret2 = sum2(30, 40);print(ret2);
}// 思考:以下代码可以简写吗?
sum1(a, b) {return a + b; // 函数体只有一行代码
}// 箭头函数简写函数体:简写只有一行代码的函数体
sum2(a, b) => a + b;

5.6 函数 - 综合案例

需求:计算购物车中商品是否全选

void main() {// 准备购物车数据List carts = [{"count": 2, "price": 10.0, "selected": true},{"count": 1, "price": 30.0, "selected": false},{"count": 5, "price": 20.0, "selected": true}];// 调用封装的函数bool isSelectedAll = getSelectedState(carts);if (isSelectedAll) {print('全选');} else {print('非全选');}
}// 核心逻辑:只要有任何一个商品是未勾选的,那么就是非全选
bool getSelectedState(List carts) {// 购物车初始的状态:假设默认是全选bool isSelectedAll = true;carts.forEach((element) {bool selected = element['selected'];// 核心代码:只要有任何一个商品是非勾选的,则购物车就是非全选if (selected == false) {isSelectedAll = selected;}});// 返回是否全选结果return isSelectedAll;
}

六、类(面向对象编程)

6.1 类的定义

需求:定义Person类,属性:名字和年龄,方法:吃饭

void main() {// 创建Person对象Person person = Person();// 1.1 属性赋值person.name = 'itheima';person.age = 17;// 1.2 读取属性print(person.name); // itheimaprint(person.age); // 17// 2.1 调用方法person.eat(); // 我是干饭人
}// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {// 1. 属性String? name;int? age;// 2. 方法void eat() {print('我是干饭人');}
}

存在问题: 我们new一个对象时, 不能直接给对象绑定属性?

答案: 借助构造函数

6.2 构造函数
6.2.1 默认构造函数

无参数,默认隐藏

void main() {// 使用默认的构造函数创建对象Person person1 = Person();// 属性赋值person1.name = '张三';person1.age = 18;// 读取属性print(person1.name); // 张三print(person1.age); // 18// 调用方法person1.eat();
}// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {// 默认的构造函数(无参数,默认隐藏)Person() {print('我是默认的构造函数');}// 属性String? name;int? age;// 方法void eat() {print('我是干饭人');}
}

6.2.2 自定义与类同名构造函数

自定义与类同名的构造函数时,可以有参数

注意点:与类同名的构造函数只能有一个,如果自定义了该类名构造函数,那么默认的构造函数就失效

void main() {// 使用自定义与类同名构造函数创建对象Person person2 = Person('李四', 19);// 读取属性print(person2.name); // 李四print(person2.age); // 19// 调用方法person2.eat();
}// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {// 自定义与类同名构造函数:可以有参数// Person(String name, int age) {//   this.name = name;//   this.age = age;// }// 简写自定义与类同名构造函数:自定义与类同名构造函数时,如果函数的参数和类的属性同名可以简写Person(this.name, this.age);// 属性String? name;int? age;// 方法void eat() {print('我是干饭人');}
}

6.2.3 命名构造函数

实际开发中, 经常会发现这么一种写法: 类名.方法(参数...), 然后返回一个实例化对象, 这种写法在Dart中被称为命名构造函数

void main() {// 使用命名构造函数创建对象Person person3 = Person.withInfo('王五', 20);// // 读取属性print(person3.name); // 王五print(person3.age); // 20// 调用方法person3.eat();
}// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {// 定义命名构造函数// Person.withInfo(String name, int age) {//   this.name = name;//   this.age = age;// }// 简写命名构造函数Person.withInfo(this.name, this.age);// 属性String? name;int? age;// 方法void eat() {print('我是干饭人');}
}

6.3 私有属性和方法

公有属性和方法:供类自身或者其他外部文件和类使用的属性和方法

私有属性和方法:仅供自身使用的属性和方法,其他外部文件和类无法访问

class Dog {// 公有属性String? name;// 私有属性int? _age;// 公有方法void eat() {print('dog eat');}// 私有方法void _run() {print('dog run');}
}

其他dart文件中使用:28-类-私有属性和方法2-使用.dart

// 导入Dart文件、库
import 'lib/Dog.dart';void main() {// 创建Dog对象Dog dog = Dog();dog.name = '旺财';print(dog.name);// 私有属性调用失败// print(dog._age);dog.eat();// 私有方法调用失败// dog._run();
}

6.4 继承(extends)
  • [思考]:如下定义的的两个类ManWoman是否有重复的部分,是否可以优化?
void main() {// 创建男人对象Man man = Man('李雷', 13);print(man.name);man.eat();// 创建女人对象Woman woman = Woman('韩梅梅', 14);print(woman.name);woman.eat();
}/// 男人类
class Man {Man(this.name, this.age);String? name;int? age;void eat() {print('$name -- eat');}
}/// 女人类
class Woman {Woman(this.name, this.age);String? name;int? age;void eat() {print('$name -- eat');}
}
  • 使用继承优化代码:继承的基本使用

[思考]:如下定义的的两个类Man和Woman是否有重复的部分,是否可以优化?
[解决]:定义父类Person,用于封装公共的属性和方法,ManWoman类作为子类去继承父类Person的属性和方法

void main() {// 创建男人对象Man man = Man('李雷', 13);print(man.name);man.eat();// 创建女人对象Woman woman = Woman('韩梅梅', 14);print(woman.name);woman.eat();
}/// 人类:父类
class Person {Person(this.name, this.age);String? name;int? age;void eat() {print('$name -- eat');}
}/// 男人类:子类
class Man extends Person {// 定义子类构造函数// Man(String name, int age) : super(name, age);Man(super.name, super.age);
}/// 女人类:子类
class Woman extends Person {// 定义子类构造函数Woman(super.name, super.age);
}
  • 提示:子类中可以重写父类的方法
void main() {// 创建男人对象Man man = Man('李雷', 13);print(man.name);man.eat();// 创建女人对象Woman woman = Woman('韩梅梅', 14);print(woman.name);woman.eat();
}/// 人类:父类
class Person {Person(this.name, this.age);String? name;int? age;void eat() {print('$name -- eat');}
}/// 男人类:子类
class Man extends Person {// 定义子类构造函数// Man(String name, int age) : super(name, age);Man(super.name, super.age);// 提示:子类中可以重写父类的方法,执行子类自己的逻辑@overridevoid eat() {print('我是$name,我爱吃肉');}
}/// 女人类:子类
class Woman extends Person {// 定义子类构造函数Woman(super.name, super.age);// 提示:子类中可以重写父类的方法,执行子类自己的逻辑@overridevoid eat() {print('我是$name,我爱吃蔬菜');}
}

6.5 混入(mixin、with)
  • [思考]:以下代码如何让子类Woman也有唱歌的方法?
void main() {// 创建男人对象Man man = Man('李雷', 13);print(man.name);man.eat();man.sing();// 创建女人对象Woman woman = Woman('韩梅梅', 14);print(woman.name);woman.eat();
}/// 人类
class Person {Person(this.name, this.age);String? name;int? age;void eat() {print('$name -- eat');}
}/// 男人类
class Man extends Person {// 定义子类构造函数Man(super.name, super.age);// 唱歌的方法void sing() {print('$name -- 爱唱歌');}
}/// 女人类
class Woman extends Person {// 定义子类构造函数Woman(super.name, super.age);
}
  • 使用Mixin扩展优化代码:Mixin扩展的基本使用

[思考]:如何让子类Woman也有唱歌的方法?

[解决]:

方式1:将唱歌的方法,定义到子类Woman中(代码冗余)

方式2:将唱歌的方法,定义到父类Person中(代码扩展性不好,唱歌的方法不能被其他类复用)

方式3:使用mixin扩展一个类,扩展类中定义唱歌的方法

[mixin] 表示一个没有构造函数的类,这个类的方法可以组合到其他类中实现代码复用

[mixin] 可以同时对某个类设置多个扩展类,也可以扩展属性

void main() {// 创建男人对象Man man = Man('李雷', 13);// 继承的属性和方法print(man.name);man.eat();// 通过mixin扩展的方法man.sing(man.name);// 创建女人对象Woman woman = Woman('韩梅梅', 14);// 继承的属性和方法print(woman.name);woman.eat();// 通过mixin扩展的方法woman.sing(woman.name);woman.dance(woman.name);woman.danceType = '街舞';print(woman.danceType);
}/// 人类:父类
class Person {Person(this.name, this.age);String? name;int? age;void eat() {print('$name -- eat');}
}// 唱歌方法的扩展类
mixin SingMixin {// 唱歌的方法void sing(name) {print('$name -- 爱唱歌');}
}// 跳舞方法的扩展类
mixin DanceMixin {// 舞种String? danceType;// 跳舞的方法void dance(String? name) {print('$name -- 爱跳舞');}
}/// 男人类:子类
class Man extends Person with SingMixin {// 定义子类构造函数Man(super.name, super.age);
}/// 女人类:子类
class Woman extends Person with SingMixin, DanceMixin {// 定义子类构造函数Woman(super.name, super.age);
}

七、异步编程

7.1 Dart是单线程的
7.1.1 程序中的耗时操作
开发中的耗时操作
  • 在开发中,我们经常会遇到一些耗时的操作需要完成,比如网络请求、文件读取等等;
  • 如果我们的主线程一直在等待这些耗时的操作完成,那么就会进行阻塞,无法响应其它事件,比如用户的点击;
  • 显然,我们不能这么干!!
如何处理耗时的操作呢?
  • 针对如何处理耗时的操作,不同的语言有不同的处理方式。
  • 处理方式一: 多线程,比如Java、C++、鸿蒙中,我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
  • 处理方式二: 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。

接下来, 我们一起来看看在Dart中如何去处理一些耗时的操作吧!

7.2 Future
7.2.1 同步网络请求

我们先来看一个例子吧:

  • 在这个例子中,我使用getNetworkData来模拟了一个网络请求;
  • 该网络请求需要5秒钟的时间,之后返回数据;
import 'dart:io';void main() {print('开始执行main函数');print(getNetworkData());print('这是不能被阻塞的代码');
}String getNetworkData() {sleep(Duration(seconds: 5));return '返回的网络数据';
}

这段代码会运行怎么的结果呢?

  • getNetworkData会阻塞main函数的执行
开始执行main函数
// 等待5秒  
返回的网络数据
这是不能被阻塞的代码

显然,上面的代码不是我们想要的执行效果,因为网络请求阻塞了main函数,那么意味着其后所有的代码都无法正常的继续执行。

7.2.2 Future基本使用

我们来对上面的代码进行改进,代码如下:

  • 和刚才的代码唯一的区别在于使用了Future对象来将耗时的操作放在了其中传入的函数中;
  • 稍后,我们会讲解它具体的一些API,我们就暂时知道我创建了一个Future实例即可;
import 'dart:io';void main() {print('开始执行main函数');print(getNetworkData());print('这是不能被阻塞的代码');
}Future<String> getNetworkData() {return Future<String>(() {sleep(Duration(seconds: 5));return '返回的网络数据';});
}

我们来看一下代码的运行结果:

  • 这一次的代码顺序执行,没有出现任何的阻塞现象;
  • 和之前直接打印结果不同,这次我们打印了一个Future实例;
  • 结论:我们将一个耗时的操作隔离了起来,这个操作不会再影响我们的主线程执行了。
  • 问题:我们如何去拿到最终的结果呢?
开始执行main函数
Instance of 'Future<String>'
这是不能被阻塞的代码

7.2.3 Future链式调用

有了Future之后,如何去获取请求到的结果:通过.then的回调

import 'dart:io';void main() {print('开始执行main函数');// print(getNetworkData());getNetworkData().then((value) {print(value);});print('这是不能被阻塞的代码');
}Future<String> getNetworkData() {return Future<String>(() {sleep(Duration(seconds: 3));return '返回的网络数据';});
}

上面代码的执行结果:

开始执行main函数
这是不能被阻塞的代码
// 3s后执行下面的代码  
返回的网络数据

执行中出现异常

如果调用过程中出现了异常,拿不到结果,如何获取到异常的信息呢?

import 'dart:io';void main() {print('开始执行main函数');getNetworkData().then((value) {print(value);}).catchError((e) {print(e);});print('这是不能被阻塞的代码');
}Future<String> getNetworkData() {return Future<String>(() {sleep(Duration(seconds: 3));// 不再返回结果,而是出现异常// return '返回的网络数据';throw Exception('网络请求出现错误');});
}

上面代码的执行结果:

开始执行main函数
这是不能被阻塞的代码
// 3s后没有拿到结果,但是我们捕获到了异常
Exception: 网络请求出现错误

补充一:上面内容的小结

我们通过一个案例来学习了一些Future的使用过程:

1、创建一个Future(可能是我们创建的,也可能是调用内部API或者第三方API获取到的一个Future,总之你需要获取到一个Future实例,Future通常会对一些异步的操作进行封装);

2、通过.then(成功回调函数)的方式来监听Future内部执行完成时获取到的结果;

3、通过.catchError(失败或异常回调函数)的方式来监听Future内部执行失败或者出现异常时的错误信息;

补充二:Future的两种状态

事实上Future在执行的整个过程中,我们通常把它划分成了两种状态:

状态一:未完成状态(uncompleted)

  • 执行Future内部的操作时(在上面的案例中就是具体的网络请求过程,我们使用了延迟来模拟),我们称这个过程为未完成状态

状态二:完成状态(completed)

  • 当Future内部的操作执行完成,通常会返回一个值,或者抛出一个异常。
  • 这两种情况,我们都称Future为完成状态。

Dart官网有对这两种状态解析,之所以拿出来说是为了区别于Promise的三种状态

7.2.4 Future链式调用小练习

例子:用户先登录,登录成功之后拿到token,然后再保存token到本地

// 用户先登录,登录成功之后拿到token,然后再保存token到本地
import 'dart:io';void main() {print('开始执行main函数');login().then((token) {setToken(token).then((res) {if (res == 'ok') {print('本地存储token成功, token是$token');}}).catchError((e) {print(e);});}).catchError((e) {print(e);});print('这是不能被阻塞的代码');
}// 1. 模拟耗时的登录操作
Future<String> login() {return Future<String>(() {sleep(Duration(seconds: 3));print('假装登录成功');String token = '666888';return token;});
}// 2. 模拟耗时的本地存储操作
Future<String> setToken(String token) {return Future<String>(() {sleep(Duration(seconds: 3));print('假装本地存储token');return 'ok';});
}

打印结果如下:

开始执行main函数
这是不能被阻塞的代码
// 3s后打印  
假装登录成功
// 3s后打印
假装本地存储token
本地存储token成功, token是666888

7.3 async和await

如果你已经完全搞懂了Future,那么学习await、async应该没有什么难度。

await、async是什么呢?

  • 它们是Dart中的关键字
  • 它们可以让我们用同步的代码格式,去实现异步的调用过程
  • 并且,通常一个async的函数会返回一个Future。

我们已经知道,Future可以做到不阻塞我们的线程,让线程继续执行,并且在完成某个操作时改变自己的状态,并且回调then或者errorCatch回调。

如何生成一个Future呢?

  • 1、通过我们前面学习的Future构造函数,或者后面学习的Future其他API都可以。
  • 2、还有一种就是通过async的函数。

通常使用 async await 解决Future链式调用带来的回调地狱的问题

// 用户先登录,登录成功之后拿到token,然后再保存token到本地
import 'dart:io';void main() {print('开始执行main函数');doLogin();print('这是不能被阻塞的代码');
}void doLogin() async {String token = await login();String res = await setToken(token);if (res == 'ok') {print('本地存储token $token 成功');}
}// 1. 模拟耗时的登录操作
Future<String> login() {return Future<String>(() {sleep(Duration(seconds: 3));print('假装登录成功');String token = '666888';return token;});
}// 2. 模拟耗时的本地存储操作
Future<String> setToken(String token) {return Future<String>(() {sleep(Duration(seconds: 3));print('假装本地存储token');return 'ok';});
}

八、泛型

[思考]为什么ListMap中可以存储任意类型的数据?

[原因]Dart在封装List和Map时,使用了泛型去限定了List和Map中数据的类型为dynamic类型

[泛型]可用于限定数据的类型,比如可以限定List和Map中数据的类型

abstract class List<E> implements EfficientLengthIterable<E>

[问题演示]保存商品分类名称时,不应该出现100、true这样类型的数据

List categories = ['居家', '美食', 100, true];

[解决]使用[泛型]限定List或者Map中元素的类型

void main() {// 1. 使用泛型限定List中元素的类型// 1.1 让列表中的元素只能是字符串类型// List categories = ['居家', '美食', 100, true];// List<String> categories = ['居家', '美食'];// 1.2 让列表中的元素只能是数字类型// List<num> nums = [100, 89, 10.99];// 2. 使用泛型限定Map中键和值的类型// 2.1 键和值都可以是任意类型Map categories1 = {1: 2, true: false, 'name': '张三', 'id': 1};print(categories1);// 2.2 键和值都只能是字符串类型Map<String, String> categories2 = {'id': '1','name': '居家',};print(categories2);// 2.3 键字符串类型, 值是任意类型Map<String, dynamic> categories3 = {'id': 1, 'name': '居家', 'isMan': true};print(categories3);
}

[泛型的作用]:在程序设计中提供一种机制,使得代码能够在编写时不指定具体类型,从而实现更灵活、可复用、类型安全的代码结构,能适应多种不同类型的数据处理需求。

比如:List和Map中的元素使用泛型限定为可以是任意类型的,从而不需要单独去封装存储某一种数据类型的List和Map

/*泛型的作用:使用泛型可以减少重复的代码封装函数:接收字符串就返回字符串,接收数字就返回数字,接收bool就返回bool*/
void main() {// 1. 普通封装// String demoString(String str) {//   return str;// }// int demoInt(int a) {//   return a;// }// bool demoBool(bool b) {//   return b;// }// 2. 基于泛型封装T demo<T>(T parm) {return parm;}// 调用String ret1 = demo<String>('itcast');print(ret1);int ret2 = demo<int>(17);print(ret2);bool ret3 = demo<bool>(true);print(ret3);
}

九、异常处理

void main() {// 1. 捕获异常:try catch// try {//   dynamic name = 'itheima';//   name.haha();// } catch (e) {//   print(e);// } finally {//   // 无论是否有异常都会执行这个代码块//   print('finally');// }// 2. 手动抛出异常:判断字符串是否相等,如果不相等手动抛出异常try {String str = 'itcast';if (str == 'yjh') {print('ok');} else {// 手动抛出异常throw Exception('字符串不相等');}} catch (e) {print(e);}
}

📎dart_code.zip

面试参考话术:

  1. 说一下 flutter?flutter 你会吗?

前提:flutter 项目都是比较早了 ,1 年及以前:

  • flutter 是一个跨平台的开发框架,由谷歌推出,一次开发发布多平台,ios,安卓,鸿蒙,web 都是可以适配的。
  • 开发的语言用的是 Dart,和 ArkTS 非常的类似,但是也有不同的地方,我挑几个说一下:
    • 声明变量:
      • dart:var,const,final
      • ArkTS:let,const
    • 类型
      • 基本类型差不多
      • ArkTS 中的 Record 在dart中叫做 map
      • ArkTS 中的 数组在 dart 中叫 list
    • 异步管理
      • ArkTS 中用的是 Promise,可以简写为 async 和 await
      • dart 中是 future,也可以简写为 async 和 await
      • 他俩基本一样
    • 还有一些 运算符,类子类的语法都是一样的,所以当时学习 ArkTS,包括之前学习前端的时候
    • 之前学习 dart 的经验给了我很多的帮助
    • 而且 dart,flutter 现在 ai 的语料已经很充足了,就算现在让我写,也问题不大

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com