当前位置: 首页 > news >正文

【HarmonyOS】HarmonyOS NEXT学习日记:二、ArkTs语法

【HarmonyOS】HarmonyOS NEXT学习日记:二、ArkTs语法

众所周知TS是JS的超集,而ArkTs则可以理解为是Ts的超集。他们的基础都基于JS,所以学习之前最好就JS基础。我的学习重点也是放在ArkTs和JS的不同点上。

文章主要跟着官方文档学习,跳过了一些和js相同的地方,保留了一些js和ts不同的地方,同时在一些概念上添加了额外的解读和代码注释。方便ts基础不太扎实或者还没接触过的同学阅读。

在这里插入图片描述

声明

声明变量关键字和js一样,let、const

ArkTS是一种静态类型语言,所有数据的类型都必须在编译时确定,但是定义时有初始值则可以显式指定其类型。

let str: string = 'hello';
let str2= 'hello, world';

上面两种都是定义了string类型的变量。

类型

基本类型和js差不多,主要看看声明类型,和一些特殊的类型用法

String

字符串

let str: string = 'hello';
let str2:string= `The result is ${a}`;

Number

数字

let num: number= 1;

Boolean

布尔类型

let flag: boolean = false;

Void

void类型用于指定函数没有返回值。可以用于泛型类型参数。泛型是什么我们后面会提到。

Object

所有引用类型的基类型

Array

let values: number[] = [1, 2, 3];

Enum

枚举值

enum ColorSet { Red, Green, Blue }
let c: ColorSet = ColorSet.Green;
console.log(c)//2
enum ColorSet { White = '0xFF', Grey = '0x7F', Black = '0x00' }
let c: ColorSet = ColorSet.Black;
console.log(c)//0x00

Union类型

联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型。
比如我想要一个数组既可以储存string也可以储存number就可以像下面这样:

type x = string | number;
let arr: x[] = [1, '2'];

用type定义一个名为x的联合类型,然后使用他声明arr的类型即可。

Aliases

Aliases类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。

比如我希望把两层的数组类型多处复用,给这种类型命名Matrix,则可以像下面这样

type Matrix = (number|string)[][]
let arr:Matrix = [[1,2,3],['4','5','6']]

运算符

和js基本一致,略过。想要详细了解的同学可以查看官方文档

语句

什么if else switch之类的用法和js一致,略过。想要详细了解的同学可以查看官方文档

函数

函数声明

函数声明引入一个函数,包含其名称、参数列表、返回类型和函数体。

以下示例是一个简单的函数,包含两个string类型的参数,返回类型为string:

function add(x: string, y: string): string {let z: string = `${x} ${y}`;return z;
}

可选参数

可选参数的格式可为name?: Type

function hello(name?: string) {if (name == undefined) {console.log('undefined');} else {console.log(name);}
}

当然也可以设置参数缺省值

function hello(name: string = 'me') {console.log(name);
}
hello()//输出me

Rest参数

用法与js相同
Rest就是为解决传入的参数数量不一定, rest parameter(Rest 参数) 本身就是数组,数组的相关的方法都可以用。
函数的最后一个参数可以是rest参数。

function sum(...numbers: number[]): number {let res = 0;for (let n of numbers)res += n;return res;
}sum() // 返回0
sum(1, 2, 3) // 返回6

返回类型

如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型。

// 显式指定返回类型
function foo(): string { return 'foo'; }// 推断返回类型为string
function goo() { return 'goo'; }

不需要返回值的函数的返回类型可以显式指定为void或省略标注。这类函数不需要返回语句。

function hi1() { console.log('hi'); }
function hi2(): void { console.log('hi'); }

以上两种均可。

函数类型

函数类型通常用于定义回调

type trigFunc = (x: number) => number // 这是一个函数类型function do_action(f: trigFunc) {f(3.141592653589); // 调用函数
}do_action(Math.sin); // 将函数作为参数传入

箭头函数

let sum = (x: number, y: number): number => {return x + y;
}

返回类型可以省略;省略时,返回类型通过函数体推断

函数重载

function foo(x: number): void;            /* 第一个函数定义 */
function foo(x: string): void;            /* 第二个函数定义 */
function foo(x: number | string): void {  /* 函数实现 */
}foo(123);     //  OK,使用第一个定义
foo('aa'); // OK,使用第二个定义

通过 class 关键字来定义一个类, 使用 constructor 定义构造函数。

class Person {name: string = ''surname: string = ''constructor (n: string, sn: string) {this.name = n;this.surname = sn;}fullName(): string {return this.name + ' ' + this.surname;}
}

定义类后,可以使用关键字new创建实例

let p = new Person('John', 'Smith');
console.log(p.fullName());

或者,可以使用对象字面量创建实例:

class Point {x: number = 0y: number = 0
}
let p: Point = {x: 42, y: 42};

字段

字段是直接在类中声明的某种类型的变量。

类可以具有实例字段或者静态字段。

使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。

ArkTS要求所有字段在声明时或者构造函数中显式初始化.

class Person {static numberOfPersons = 0constructor() {Person.numberOfPersons++;}
}
let me = new Person()
let you = new Person()
console.log(Person.numberOfPersons+'' )//2

getter和setter

setter和getter可用于提供对对象属性的受控访问。

class Person {name: string = ''private _age: number = 0get age(): number { return this._age; }set age(x: number) {if (x < 0) {throw Error('Invalid age argument');}this._age = x;}
}let p = new Person();
p.age; // 输出0
p.age = -42; // 设置无效age值会抛出错误

方法

方法属于类。类可以定义实例方法或者静态方法。静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。

!!!所谓的静态方法和静态字段,都属于这个类,而非实例。所以实例是无法调用的,要调用只能通过这个类来调用。!!!

构造函数

类声明可以包含构造函数,使用constructor声明,所谓构造函数就是用来初始化对象状态的
如果未定义构造函数,则会自动创建具有空参数列表的默认构造函数

构造函数重载签名

可以通过编写重载签名,指定构造函数的不同调用方式。具体方法为,为同一个构造函数写入多个同名但签名不同的构造函数头,构造函数实现紧随其后。

class C {constructor(x: number)             /* 第一个签名 */constructor(x: string)             /* 第二个签名 */constructor(x: number | string) {  /* 实现签名 */}
}
let c1 = new C(123);      // OK,使用第一个签名
let c2 = new C('abc');    // OK,使用第二个签名

继承

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。

  • 子类继承父类后子类的实例就拥有了父类中的属性和方法,但不继承构造函数
  • 子类自己特殊逻辑放在子类中重写父类的逻辑
  • super 可以调用父类上的方法和属性
class Person {name: string = ''private _age = 0get age(): number {return this._age;}
}
class Employee extends Person {salary: number = 0calculateTaxes(): number {return this.salary * 0.42;}
}

也可以用implements实现对接口的继承,但是包含implements子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。

interface DateInterface {now(): string;
}
class MyDate implements DateInterface {now(): string {// 在此实现return 'now is now';}
}

父类访问

关键字super可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口。

我们刚刚说过继承来的子类无法继承构造函数,如果要用的话就可以通过super实现

class RectangleSize {protected height: number = 0protected width: number = 0constructor (h: number, w: number) {this.height = h;this.width = w;}draw() {/* 绘制边界 */}
}
class FilledRectangle extends RectangleSize {color = ''constructor (h: number, w: number, c: string) {super(h, w); // 父类构造函数的调用this.color = c;}draw() {super.draw(); // 父类方法的调用// super.height -可在此处使用/* 填充矩形 */}
}

方法重写

子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。

方法重载签名

通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后

class C {foo(x: number): void;            /* 第一个签名 */foo(x: string): void;            /* 第二个签名 */foo(x: number | string): void {  /* 实现签名 */}
}
let c = new C();
c.foo(123);     // OK,使用第一个签名
c.foo('aa'); // OK,使用第二个签名

可见性修饰符

类的方法和属性都可以使用可见性修饰符。
可见性修饰符包括:private、protected和public。默认可见性为public。

  • public 自己、自己的子类 和其他类都可以访问 (默认值)
  • protected 受保护的 自己和自己的子类能访问, 其他类不能访问
  • private 私有的 只能自己访问,自己的子类不能访问,其他类更不能访问
Private
class C {public x: string = ''private y: string = ''set_y (new_y: string) {this.y = new_y; // OK,因为y在类本身中可以访问}
}
let c = new C();
c.x = 'a'; // OK,该字段是公有的
c.y = 'b'; // 编译时错误:'y'不可见
Protected
class Base {protected x: string = ''private y: string = ''
}
class Derived extends Base {foo() {this.x = 'a'; // OK,访问受保护成员this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的}
}

对象字面量

对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。

class C {n: number = 0s: string = ''
}let c: C = {n: 42, s: 'foo'};

对象字面量只能在可以推导出该字面量类型的上下文中使用

Record类型的对象字面量

泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值

let map: Record<string, number> = {'John': 25,'Mary': 21,
}map['John']; // 25

类型K可以是字符串类型或数值类型,而V可以是任何类型.

interface PersonInfo {age: numbersalary: number
}
let map: Record<string, PersonInfo> = {'John': { age: 25, salary: 10},'Mary': { age: 21, salary: 20}
}

接口

接口声明引入新类型。接口是定义代码协定的常见方式。

任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。
所谓多态,就是一个类只能继承一个父类,但是可以继承多个接口从而实现多态。

接口使用interface关键字声明

interface Style {color: string // 属性
}
interface AreaSize {calculateAreaSize(): number // 方法的声明someMethod(): void;     // 方法的声明
}

类继承接口使用implements

// 接口:
interface AreaSize {calculateAreaSize(): number // 方法的声明someMethod(): void;     // 方法的声明
}// 实现:
class RectangleSize implements AreaSize {private width: number = 0private height: number = 0someMethod(): void {console.log('someMethod called');}calculateAreaSize(): number {this.someMethod(); // 调用另一个方法并返回结果return this.width * this.height;}
}

接口继承

接口也可以使用extends 实现继承

interface Style {color: string
}interface ExtendedStyle extends Style {width: number
}

泛型类型和函数

泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。
即将数据类型也作为参数传递

class CustomStack<Element> {//这里定义了一个Element形参,作为类型public push(e: Element):void {//内部的push方法接受的参数使用Element为类型// ...}
}
let s = new CustomStack<string>();//在实例化的时候,传入Element的实参string用来当做push方法的入参类型
s.push('hello');

泛型约束

泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法。

interface Hashable {hash(): number
}
class MyHashMap<Key extends Hashable, Value> {public set(k: Key, v: Value) {let h = k.hash();// ...其他代码...}
}

泛型函数

使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:

function last(x: number[]): number {return x[x.length - 1];
}
last([1, 2, 3]); // 3

以上方法只能用于number数组,
如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:

function last<T>(x: T[]): T {return x[x.length - 1];
}

现在,该函数可以与任何数组一起使用。

// 显式设置的类型实参
last<string>(['aa', 'bb']);
last<number>([1, 2, 3]);// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
last([1, 2, 3]);

泛型默认值

泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。

class SomeType {}
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
class Derived1 extends Base implements Interface { }
// Derived1在语义上等价于Derived2
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }function foo<T = number>(): T {// ...
}
foo();
// 此函数在语义上等价于下面的调用
foo<number>();

相关文章:

【HarmonyOS】HarmonyOS NEXT学习日记:二、ArkTs语法

【HarmonyOS】HarmonyOS NEXT学习日记&#xff1a;二、ArkTs语法 众所周知TS是JS的超集,而ArkTs则可以理解为是Ts的超集。他们的基础都基于JS&#xff0c;所以学习之前最好就JS基础。我的学习重点也是放在ArkTs和JS的不同点上。 文章主要跟着官方文档学习&#xff0c;跳过了一…...

Web前端-Web开发CSS基础2-选择器

一. 基础 1. 选中所有的<p>标签&#xff1b; 2. 选中所有的<ol>标签&#xff1b; 3. 选中所有的<ul>标签&#xff1b; 4. 选中所有id为happy的标签&#xff1b; 5. 选中所有id为sad的标签&#xff1b; 6. 选中所有id为angry的标签&#xff1b; 7. 选中所有类…...

Mongodb数组字段索引之多键索引

学习mongodb&#xff0c;体会mongodb的每一个使用细节&#xff0c;欢迎阅读威赞的文章。这是威赞发布的第92篇mongodb技术文章&#xff0c;欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题&#xff0c;欢迎在文章下面点个赞&#xff0c;或者关…...

[Spring] Spring Web MVC案例实战

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

大模型“重构”教育:解构学习奥秘,推动教育普惠

大模型“重构”千行百业系列选题 生成式人工智能的热潮&#xff0c;为AI领域的发展注入新的活力&#xff0c;而“赋能千行百业”已经成为人们普遍对于人工智能和大模型的全新理解。 人工智能和大模型技术的迅猛发展正在以前所未有的速度深刻改变着各个行业。正如专家所预测&a…...

HCNA VRP基础

交换机可以隔离冲突域&#xff0c;路由器可以隔离广播域&#xff0c;这两种设备在企业网络中应用越来越广泛。随着越来越多的终端接入到网络中&#xff0c;网络设备的负担也越来越重&#xff0c;这时网络设备可以通过专有的VRP系统来提升运行效率。通过路由平台VRP是华为公司数…...

单片机外围设备-EEPROM

eeprom用iic通信。eeprom有几个特点需要关注&#xff1a; 1、可以单字节读写 2、eeprom按页划分存储&#xff0c;不同型号的eeprom的页大小不一致&#xff0c;往eeprom写数据时&#xff0c;如果写到了该页的末尾&#xff0c;会自动从该页的开头继续写&#xff0c;把之前的数据…...

YOLO--置信度(超详细解读)

YOLO&#xff08;You Only Look Once&#xff09;算法中的置信度&#xff08;Confidence&#xff09;是一个关键概念&#xff0c;用于评估模型对预测框内存在目标对象的信心程度以及预测框对目标对象位置的准确性。 一、置信度的定义 数值范围&#xff1a;置信度是一个介于0和…...

“解锁物流新纪元:深入探索‘沂路畅通‘分布式协作平台“

"解锁物流新纪元&#xff1a;深入探索沂路畅通分布式协作平台" 在21世纪的数字浪潮中&#xff0c;物流行业作为连接生产与消费的关键纽带&#xff0c;其重要性不言而喻。然而&#xff0c;随着市场规模的持续扩大和消费者需求的日益多样化&#xff0c;传统物流模式已…...

昇思25天学习打卡营第六天|应用实践/计算机视觉/Vision Transformer图像分类

心得 运行模型似乎有点靠天意&#xff1f;每次跑模型之前先来个焚香沐浴&#xff1f;总之今天是机器视觉的最后一课了&#xff0c;尽管课程里强调模型跑得慢&#xff0c;可是我的这次运行&#xff0c;居然很快的就看到结果了。 如果一直看我这个系列文章的小伙伴&#xff0c;…...

vxe-table合并行数据

场景&#xff1a; 混批名称相同合并混批名称&#xff0c;在混批名称相同条件下合并相同的混批类型&#xff1b;在混混批类型相同条件下合并相同的混批值&#xff1b;在混批值相同条件下合并相同的单位 实现根据四个不同的key值&#xff0c;当四个key值对应相等时&#xff0c;合…...

LabVIEW异步和同步通信详细分析及比较

1. 基本原理 异步通信&#xff1a; 原理&#xff1a;异步通信&#xff08;Asynchronous Communication&#xff09;是一种数据传输方式&#xff0c;其中数据发送和接收操作在独立的时间进行&#xff0c;不需要在特定时刻对齐。发送方在任何时刻可以发送数据&#xff0c;而接收…...

【多模态学习笔记二】MINIGPT-4论文阅读

MINIGPT-4:ENHANCING VISION-LANGUAGE UNDERSTANDING WITH ADVANCED LARGE LANGUAGE MODELS 提出的MiniGPT-4使用一个投影层,将冻结的视觉编码器与冻结的先进的LLM Vicuna对齐。我们的工作首次揭示,将视觉特征与先进的大型语言模型正确对齐可以具有GPT-4所展示的许多先进的多…...

Docker基本讲解及演示

Docker安装教程 Docker安装教程 1、Docker介绍 Docker是一个开源的应用容器引擎&#xff0c;允许开发者将应用程序及其依赖项打包成一个轻量级、可移植的容器&#xff0c;然后发布到任何支持 Docker 的环境中运行&#xff0c;无论是开发机、测试机还是生产环境。 Docker基于…...

各类专业技术的pdf电子书

从业多年&#xff0c;收集了海量的pdf电子书籍&#xff0c;感兴趣的私聊。...

【Linux】多线程_9

文章目录 九、多线程10. 线程池 未完待续 九、多线程 10. 线程池 这里我没实现一些 懒汉单例模式 的线程池&#xff0c;并且包含 日志打印 的线程池&#xff1a; Makefile&#xff1a; threadpool:Main.ccg -o $ $^ -stdc11 -lpthread .PHONY:clean clean:rm -f threadpoolT…...

LabVIEW设备检修信息管理系统

开发了基于LabVIEW设计平台开发的设备检修信息管理系统。该系统应用于各种设备的检修基地&#xff0c;通过与基地管理信息系统的连接和数据交换&#xff0c;实现了本地检修工位数据的远程自动化管理&#xff0c;提高了设备的检修效率和安全性。 项目背景 现代设备运维过程中信…...

python爬虫基础:使用lxml库进行HTML解析和数据提取的实践指南

使用lxml库进行HTML解析和数据提取的实践指南 在Python编程中&#xff0c;网页抓取和数据提取是一项常见任务。lxml库因其高效性和强大的XPath支持&#xff0c;成为了处理HTML和XML文档的优选工具。本文将带你了解如何使用lxml来解析HTML文档并提取所需数据。 1. 安装lxml库 …...

大语言模型系列:Transformer

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Transformer模型自2017年由Vaswani等人在论文《Attention Is All You Need》中提出以来&#xff0c;已成为最具影响力的技术之一。这种模型设计的核心是自注意力机制&#xff0c;它允许模型在处理序列数据时&#xf…...

宠物健康新守护:智能听诊器引领科技突破

在宠物护理领域&#xff0c;一项令人瞩目的科技创新正逐渐兴起&#xff0c;那便是智能听诊器。这款革命性的设备以前所未有的准确性和便利性&#xff0c;为宠物主人提供了一种全新的健康监测体验。 只需将智能听诊器轻轻放置在爱宠的身上&#xff0c;它便立即开始工作&#xf…...

KITTI 3D 数据可视化

引言 KITTI 视觉基准测试套件&#xff08;KITTI Vision Benchmark Suite&#xff09;提供了大量用于理解自动驾驶场景的工具。尤其是3D数据可视化在分析和解释传感器&#xff08;如激光雷达&#xff09;与环境的复杂交互中起到了至关重要的作用。本文将详细探讨KITTI数据集中3…...

旅游数据可视化:免费工具让复杂数据变得简单易懂

随着旅游业的蓬勃发展&#xff0c;海量的数据如同繁星点点&#xff0c;记录着每一位旅者的足迹与偏好。然而&#xff0c;如何将这些复杂的数据转化为直观、易懂的信息&#xff0c;为旅游企业精准决策、为消费者提供更加个性化的服务&#xff0c;成为了行业内外共同关注的焦点。…...

数据结构进阶:使用链表实现栈和队列详解与示例(C, C#, C++)

文章目录 1、 栈与队列简介栈&#xff08;Stack&#xff09;队列&#xff08;Queue&#xff09; 2、使用链表实现栈C语言实现C#语言实现C语言实现 3、使用链表实现队列C语言实现C#语言实现C语言实现 4、链表实现栈和队列的性能分析时间复杂度空间复杂度性能特点与其他实现的比较…...

【线程系列之五】线程池介绍C语言

一、基本概念 1.1 概念 线程池&#xff08;Thread Pool&#xff09;是一种基于池化技术管理线程的机制&#xff0c;旨在减少线程创建和销毁的开销&#xff0c;提高系统资源的利用率&#xff0c;以及更好地控制系统中同时运行的线程数量。线程池通过预先创建一定数量的线程&am…...

【学习css3】使用flex和grid实现等高元素布局

过往的实现方法是使用浮动加计算布局来实现&#xff0c;当flex和grid问世时&#xff0c;这一切将变得简单起来 一、简单的两列实现 1、先看页面效果 2、css代码 .container {padding: 10px;width: 100ch;margin: 0 auto;box-shadow: inset 0 0 0 2px #ccc;}.column {margin: 2…...

如何防止Eclipse格式化程序在行注释开头插入空格

格式化前&#xff1a; //foo bar 格式化后&#xff1a; // foo bar 这种看着不是很舒服。如果不让格式化时自动在注释符后面插入空格呢&#xff1f; 要在Eclipse中进行代码格式化时防止在行注释&#xff08;‌//&#xff09;‌后面自动增加空格&#xff0c;‌可以通过调整…...

Nextjs 调用组件内的方法

在 Next.js 中&#xff0c;如果你想从一个组件外部调用组件内部的方法&#xff0c;可以使用 React 的 useRef 钩子来引用组件实例并调用其方法。这种方法主要适用于类组件&#xff0c;但也可以用于函数组件&#xff0c;通过将方法暴露在 ref 对象上。 以下是一个示例&#xff…...

ip地址是电脑还是网线决定的

在数字化时代的浪潮中&#xff0c;网络已经成为了我们日常生活和工作不可或缺的一部分。当我们谈论网络时&#xff0c;IP地址无疑是一个核心的概念。然而&#xff0c;关于IP地址的分配和决定因素&#xff0c;很多人可能存在误解。有些人认为IP地址是由电脑决定的&#xff0c;而…...

Hadoop中HDFS、Hive 和 HBase三者之间的关系

HDFS&#xff08;Hadoop Distributed File System&#xff09;、Hive 和 HBase 是 Hadoop 生态系统中三个重要的组件&#xff0c;它们各自解决了大数据存储和处理的不同层面的问题。我们用大白话来解释这三个组件之间的关系&#xff1a; HDFS - 数据的仓库&#xff1a; HDFS 是…...

opencv—常用函数学习_“干货“_10

目录 二七、离散余弦变换 执行离散余弦变换 (dct) 和逆变换 (idct) 解释 实际应用 JPEG压缩示例&#xff08;简化版&#xff09; 二八、图像几何变换 仿射变换 (warpAffine 和 getAffineTransform) 透视变换 (warpPerspective 和 getPerspectiveTransform) 旋转变换 (g…...