你好-groovy

索引


当前主要看了Language Specification章节,但是Semantics小节里面的类型检查和静态编译基本算跳过


文档地址: http://groovy-lang.org/documentation.html

基本类型之间的转化: http://groovy-lang.org/differences.html#_conversions
基本类型数学计算之间的转化:http://groovy-lang.org/syntax.html#_math_operations

关键词:http://groovy-lang.org/syntax.html#_keywords
转义字符:http://groovy-lang.org/syntax.html#_escaping_special_characters

所有操作符优先级 http://groovy-lang.org/operators.html#_operator_precedence
(这里的 >>> 和 >>= >>>= 在文档里没有提到啊)

操作符重载:http://groovy-lang.org/operators.html#Operator-Overloading

http://groovy-lang.org/semantics.html 这个文档下面的type check和static compile瞄了两眼跳了
类型检查规则:http://groovy-lang.org/semantics.html#static-type-checking
静态编译:http://groovy-lang.org/semantics.html#_static_compilation

使用Tuple注解快捷创建构造器 : http://docs.groovy-lang.org/2.5.4/html/gapi/index.html?groovy/transform/TupleConstructor.html
使用Map注解快捷创建构造器 : http://docs.groovy-lang.org/2.5.4/html/gapi/index.html?groovy/transform/MapConstructor.html

多注解出现的冲突解决: http://groovy-lang.org/objectorientation.html#handling_duplicate_annotations

http://docs.groovy-lang.org/latest/html/documentation/core-domain-specific-languages.html
http://docs.groovy-lang.org/latest/html/documentation/core-semantics.html#closure-coercion

方法指针:
http://docs.groovy-lang.org/latest/html/documentation/core-operators.html#method-pointer-operator

语法

鉴于groovy和java的相似性,大概贴出需要额外记录的部分

//shebang,必须在第一行
#!/usr/bin/env groovy

基本


定义

def 或者 显示类型声明,或者强转(as 或者 类型),如:
def 在Groovy里就是 Object

char c1 = 'A'
def c2 = 'B' as char
def c3 = (char)'C'
`cx`.asType(char)
groovy中的true

非false,引用非null,size非0,大小非0
可以通过重写asBoolean()来重新计算

常用
//操作
assert  2 ** 3 == 8    
//spaceship  
assert (1 <=> 1) == 0
assert (3 <=> 1) == 1
assert ('a' <=> 'z') == -1

//属性调用  
//field调用,感觉第一种方式拒绝了mName的命名方式啊  
person.name  //默认调用person.getName()   
person.@name  //调用person的可被访问field : name  

//elvis 操作符  
displayName = user.name ? user.name : 'Anonymous'
displayName = user.name ? : 'Anonymous'

//判空  
person?.name  

//数组
//需要显示定义
String[] arrStr = ['Ananas', 'Banana', 'Kiwi']

//列表
//等同于list.append('e')
list << 'e'  
//索引  
letters[-1]
letters[1, 3]  
letters[2..4]  

//字典
//可以用(key)来将key的值作为键而不是`key`
person = [(key): 'Guillaume']

//基本数字类型
byte, char, short, int, long, BigInteger, Double, BigDecimal   

二进制:0b 开头; 八进制:0;16进制:0x      
后缀:i,f,l,d,g(big) ; 科学计数法:5e-1

//属性定义
//可以直接用字符串拼接,支持所有类型  
map."any name-is ok" = "ALLOWED"   
快捷操作
//iterable
//遍历
// *. 获取cars的所有实例,并一一获取make后添加到一个list 等同于 cars.collect{ it.make }
// 所有实现Iterable 接口的类都可以使用 *.  
// 嵌套使用效果更佳,但是collection建议使用 cars.collectNested{ it.model }
def cars = [
       new Car(make: 'Peugeot', model: '508'),
       new Car(make: 'Renault', model: 'Clio')]       
def makes = cars*.make                                
assert makes == ['Peugeot', 'Renault']

//引用
args = [4]
assert function(*args,5,6) == 15

def m1 = [c:3, d:4]
assert [a:1, b:2, *:m1] == [a:1, b:2, c:3, d:4]   

//range
//可以对任何实现了Comaprable(具有next和previous方法)的对象使用  
assert (0..5).collect() == [0, 1, 2, 3, 4, 5]       
assert (0..<5).collect() == [0, 1, 2, 3, 4]         
assert (0..5) instanceof List

import

import 支持 * (叫做star import)
支持static,并且允许导入不同参数的同名方法(同参也可以,但是优先使用import的)

//单例简写
import static Calendar.getInstance as now
assert now().class == Calendar.getInstance().class  

//别名
import java.util.Date
import java.sql.Date as SQLDate

这些包都是默认导入的:

java.io.*;
java.lang.*;  
java.math.BigDecimal;  
java.math.BigInteger;  
java.net.*;  
java.util.*;  
groovy.lang.*;  
groovy.util.*   

正则

//以 ~ 方式快速创建,支持任何形式的字符串
def p = ~/foo/
assert p instanceof Pattern

//用 =~ 构建Matcher  
def text = "some text to match"
def m = text =~ /match/                                           
assert m instanceof Matcher                                       
if (!m) {                 //对Matcher做比较时,等同于调用m.find()                                          
    throw new RuntimeException("Oops, text not found!")
}

//快速返回boolean结果  
m = text ==~ /match/

as


as 对应在 asType() 方法的实现,如果不是同一个类型,会创建新对象

//类内部实现方法
def asType(Class target) {
    if (Cartesian==target) {
        return new Cartesian(x: r*cos(phi), y: r*sin(phi))
    }
}
//类外部实现  
Polar.metaClass.asType = { Class target ->
    if (Cartesian==target) {
        return new Cartesian(x: r*cos(phi), y: r*sin(phi))
    }
}

使用反射时注意

greeter = { println 'Hello, Groovy!' }.asType(clazz)

闭包


{ w -> w << 3}
{ -> number }

//方法指针(pointer)  
def fun = str.&toUpperCase                                                          
assert fun() == str.toUpperCase()  

这里的pointer是闭包类型

as可以把闭包改成任意类型
甚至类里面已定义了的方法都可以被覆盖
注意:这里的替换是把闭包里的内容,全部放入方法体,如果有两个方法,两个方法都会被覆盖。

那怎么对多个覆盖呢。有map:

def map
map = [
  i: 10,
  hasNext: { map.i > 0 },
  next: { map.i-- },
]
def iter = map as Iterator

closure是 一个开放的,匿名的,块状的代码,是groovy.lang.Closure的实例,可以接受参数,返回值并被赋值给一个变量

def variable = { [closureParameters -> ] statements }  
variable()  
variable.call()

隐式it参数

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
//为了杜绝无用调用,因为你可能没有使用it参数  
def magicNumber = { -> 42 }
this,owner,delegate

闭包中使用 getThisObject() 和 this 效果一样,都是获取最近的outer class实例
getOwner()和owner的效果一样,owner则是获取闭包的最直接持有者

getDelegate()和delegate一样,日常作用和owner一样,但是可以被赋值

你可以通过设置delegate实现代理模式:

class Person {
    String name
}
def p = new Person(name:'Igor')

//正常模式,即你也可以定义一个变量代替delegate实现相同效果  
def upperCasedName = { delegate.name.toUpperCase() }
upperCasedName.delegate = p
assert upperCasedName() == 'IGOR'

甚至delegate都可以省略,省略的时候可以选择策略

p.pretty.resolveStrategy = Closure.DELEGATE_FIRST  

(因为默认情况下delegate和owner一样;
如果delegate省略,系统自动补全,而补全的时候,可以先/只补全owner的属性,或者delegate,或者TO_SELF
TO_SELF只是在自定义Closure的时候有意义)

//简写模式
def cl = { name.toUpperCase() }                 
cl.delegate = p                                 
assert cl() == 'IGOR'
GString中
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 1'

当重新赋值x = 2时,实际上是产生了一个新的x2,而不是原先的那个x1了.对于 x 而 言 , 实 际 上 是 {x}而言,实际上是 xx的表达式,即x1的引用
如果要不变,可以用这种方法:"x = ${-> x}",或者让x成为一个引用变量是一样的效果,这样就会获取现在的x2

curry

在groovy中,闭包允许设置一个最左或者最右参数

//你可以选择传入n,或者str,或者两者
def nCopies = { int n, String str -> str*n }    
def twice = nCopies.curry(2)                    
assert twice('bla') == 'blabla'  

def blah = nCopies.rcurry('bla')                
assert blah(2) == 'blabla'
assert twice('bla') == nCopies(2, 'bla')

也允许设置索引参数

def volume = { double l, double w, double h -> l*w*h }      
def fixedWidthVolume = volume.ncurry(1, 2d)                 
assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d)       
def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d)          
assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d)   
Memoization
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }.memoize()
Composition
def plus2  = { it + 2 }
def times3 = { it * 3 }

def times3plus2 = plus2 << times3
assert times3plus2(3) == 11
assert times3plus2(4) == plus2(times3(4))

def plus2times3 = times3 << plus2
assert plus2times3(3) == 15
assert plus2times3(5) == times3(plus2(5))

// reverse composition
assert times3plus2(3) == (times3 >> plus2)(3)
Trampoline

常规的递归调用,可能会造成堆栈溢出,而Trampoline可以避免这种情况
理解成平行方法调用,而不是垂直栈

$


$ 符号可以用来标记引用,后文直接称为$引用
直接变量和闭包 使用${}包裹;带.的变量 直接用$作为前缀

def firstname = "Homer"
"${firstname}"
//闭包
${ w -> w << 3}
${ -> number }

def person = [name: 'Guillaume', age: 36]
"$person.name"

字符串


根据包裹的符号和实际内容,被解释成多种类型:

  • 单引号:常规String
  • 双引号:如果字符串包含$,会被解释成GString。GString 支持$引用和闭包
  • 三个单引号: 三个单引号的字符串直接可以换行、缩进,不需要对应的符号显式表示
def strippedFirstNewline = '''\    //这里的'\'可以去除默认的第一行换行
line one
'''
  • 三个双引号:综合了 双引号 和 三个单引号 的特性
  • Slashy: 用 /包裹,类似 三个双引号,好处是不用转义\。适合用于正则

因为用于正则,所以可以特别使用 $()。

  • Dollar slashy:功能类似slashy, 但是用$转义。转义有点没看懂(好像是不用转,但是连续两个会被处理)
def dollarSlashy = $/
    $ dollar sign
    $$ escaped dollar sign
    \ backslash
    / forward slash
    $/ escaped forward slash
    $$$/ escaped opening dollar slashy
    $/$$ escaped closing dollar slashy
/$

assert [
    '$ dollar sign',
    '$ escaped dollar sign',
    '\\ backslash',
    '/ forward slash',
    '/ escaped forward slash',
    '$/ escaped opening dollar slashy',
    '/$ escaped closing dollar slashy'
].every { dollarSlashy.contains(it) }

控制语句

switch

case支持闭包和列表:

case 12..30:
        result = "range"
        break
case ~/fo*/: // toString() representation of x matches the pattern?
        result = "foo regex"
        break

case { it < 0 }: // or { x < 0 }
        result = "negative"
        break
for循环

支持java的fori,for-each,也支持in:

for ( i in 0..9 ) {
    x += i
}
assert:
assert [left expression] == [right expression] : (optional message)  
assert calc(x,y) == z*z : 'Incorrect computation result'

enum

在常规情况和switch情况,Enum和String之间会自动转化
但是作为方法参数的时候,还是需要as 转化下

enum State {
    up,
    down
}  
State st = 'up'  
assert st == State.up  

def val = "up"
State st = "${val}"
assert st == State.up

SAM(single abstract method)类型:

是一种只定义了一个抽象方法的类型,包括功能型接口,和抽象类
而所有闭包都可以用as操作符转化成sam类型,理解起来有点像直接把闭包赋值给方法作为方法体

interface Predicate<T> {
    boolean accept(T obj)
}
abstract class Greeter {
    abstract String getName()
    void greet() {
        println "Hello, $name"
    }
}
//2.2.0 之后可以直接省略 as
Predicate filter = { it.contains 'G' } as Predicate
assert filter.accept('Groovy') == true

Greeter greeter = { 'Groovy' } as Greeter
greeter.greet()

类和方法,无可访问性修饰符的时候,默认为Public
field,无可访问性修饰符的时候,默认具有getter,setter
文件名和类名可以不同,但是推荐相同,因为脚本执行用文件名作为类名

获取类的所有属性: obj.properties.keySet()

方法

构造器和方法在定义和使用上别无二致,

//要随意顺序传入map的参数,需要args是方法的第一个参数。如果不是第一个参数,必须对应传入Map类型参数
//默认参数必须放在参数序列的最后面  
def foo(Map args, Integer number = 2) { "${args.name}: ${args.age}, and the number is ${number}" }
foo(name: 'Marie', age: 1, 23)  
foo(23, name: 'Marie', age: 1)  

都支持positinal parameter和named parameter

//构造器  
class PersonConstructor {
    String name
    Integer age

    PersonConstructor(name, age) {          
        this.name = name
        this.age = age
    }
}
//position式使用(参数叫positional parameter)  
def person1 = new PersonConstructor('Marie', 1)    
def person2 = ['Marie', 2] as PersonConstructor    
PersonConstructor person3 = ['Marie', 3]  

//如果没有指定构造器,可以map式调用(参数叫named parameter)       
//使用map,需要第一个就是map式,后面随意,可以和position式混用  
def person7 = new PersonWOConstructor(name: 'Marie', age: 2)    

类可以向外赋值

def coordinates = new Coordinates(latitude: 43.23, longitude: 3.67)
def (la, lo) = coordinates                                          

assert la == 43.23                                                  
assert lo == 3.67

其他

  • GString 的hashcode和常规的String的不同。禁止把GString 作为map的键
  • in 对应 isCase() 方法,在List中对应 contains() 方法
  • 只要类实现call方法,调用类默认方法,相当于调用call

def mc = new MyCallable(); mc(2) == mc().call(2)

  • lable, 用来增加可读性和break跳转,后者不是一个好的编码风格;可以被AST转化
  • GPath:解析POJO或xml的时候,都是使用.作为分割点,如a.@href
  • 如果方法有参数,且无混淆项,调用时括号可以被省略
  • 末尾分号可被省略
  • 方法、闭包结尾语句,可省略return。最后一句的结果被返回
  • public可省略
  • 方法返回参数也是可选的,但是如果不写,需要写上修饰符,以区分方法调用和声明
  • 大括号{ }用来作为闭包语法,不能用作创建数组(中括号[]可以)
  • 忽略field的(使用范围)修饰词会认为是一个private field,同时有getter,setter;
    package-private对应需要用@PackageScope修饰
  • 万物皆object。所以groovy中的基本变量其实都会被转化成包装类(wrapper class)
  • 如果你定义了一个getter或setter,那么对应的属性,即使没有显式声明也可以读取或者设置
class Foo {
  static int i
}

assert Foo.class.getDeclaredField('i').type == int.class
assert Foo.i.class != int.class && Foo.i.class == Integer.class  

闭包注解

//定义时,把value定义成class类型
@Retention(RetentionPolicy.RUNTIME)
@interface OnlyIf {
    Class value()                    
}
//注解使用
    @OnlyIf({ jdk>=7 && windows })
    void requiresJDK7AndWindows() {
        result << 'JDK 7 Windows'
    }

//注解生效代码示例,上述requiresJDK...方法定义在Task类中
class Runner {
    static <T> T run(Class<T> taskClass) {
        def tasks = taskClass.newInstance()                                         
        def params = [jdk:6, windows: false]                                        
        tasks.class.declaredMethods.each { m ->                                     
            if (Modifier.isPublic(m.modifiers) && m.parameterTypes.length == 0) {   
                def onlyIf = m.getAnnotation(OnlyIf)                                
                if (onlyIf) {
                    Closure cl = onlyIf.value().newInstance(tasks,tasks)            
                    cl.delegate = params                                            
                    if (cl()) {                                                     
                        m.invoke(tasks)                                             
                    }
                } else {
                    m.invoke(tasks)                                                 
                }
            }
        }
        tasks                                                                       
    }
}

def tasks = Runner.run(Tasks)
assert tasks.result == [1, 'JDK 6'] as Set

meta注解

可以理解成注解别名,一般代表了一个或多个其他注解,主要为了减少代码量。

//service和transactional是其他注解
import groovy.transform.AnnotationCollector

@Service                                        
@Transactional                                  
@AnnotationCollector                            
@interface TransactionalService {
}

因为多注解就可能造成冲突,此时需要修改处理器模式或者自定义处理器

Traits

用来实现
composition of behaviors、runtime implementation of interfaces、behavior overriding、compatibility with static type checking/compilation

使用类似接口

[@SelfType(ChildClassType)]  
trait Temp {    
    public String name

    //只有private或public
    private String greetingMessage() {                      
        'Hello from a private method!'
    }
    abstract String name()                       
    String fly() { "I'm flying!" }          
}

//调用时,属性名前面增加__
obj.Temp__name
//包名变更,所有点变成_
my_package_Temp__name

虽然概念上A implement trait可以理解成A继承trait,但是实际上trait是直接被放入类A里的,所以trait里的方法返回this实际上是返回A实例
但是如果trait implement interface , 此时trait就有了实例

这段话没看懂

While it would likely be considered bad style to inherit and override or multiply inherit methods with the same signature but a mix of final and non-final variants, Groovy doesn’t prohibit this scenario. Normal method selection applies and the modifier used will be determined from the resulting method. You might consider creating a base class which implements the desired trait(s) if you want trait implementation methods that can’t be overridden.

trait 单继承trait 用extend,多继承用implement

动态代码,可以调用quack(),而quack不在trait中声明

trait SpeakingDuck {
    private Map props = [:]
    String speak() { quack() }      
    def propertyMissing(String prop) {
       props[prop]
    }                
}
class Duck implements SpeakingDuck {
    String methodMissing(String name, args) {
        "${name.capitalize()}!"                     
    }
}
def d = new Duck()
assert d.speak() == 'Quack!'  //因为没有quack方法,所以根据methodMissing输出他的名字

多继承冲突
如果两个方法冲突,默认取后者,可以通过重写对方法:A.super.conflictMethod()

动态实现:new Something() as A、new St().withTraits A, B 这时候要注意,产生的实例已经不是最开始的那个

//三个都是trait
class Handler implements Parent, Child1, Child2 {}
def h = new Handler()
h.on('foo', [:])

//这里child1,child2都继承parent,on是Parent的方法
//调用h.on的时候,会先去看child2里对on的处理,如果调用了super.on,会去child1里看,如果调用了super.on,会去Parent里看  
//如果child2和parent无关,那么super.on会调用handler.super(这个特性可以用来修饰final类的方法)  

如果只有一个抽象方法(SAM),可以直接闭包创建:

trait Greeter {
    String greet() { "Hello $name" }        
    abstract String getName()               
}

Greeter greeter = { 'Alice' }  

官方不支持AST(Abstract syntax tree)转化
trait的变量不被继承,但是可以用getX,getY变相获取子类的变量;同样的原因,不能使用x++,可以使用 x += 1

mixin

混搭。。trait时,会改变o的instanceOf,而这个方法不会

class A { String methodFromA() { 'A' } }        
class B { String methodFromB() { 'B' } }        
A.metaClass.mixin B                             
def o = new A()
assert o.methodFromA() == 'A'                   
assert o.methodFromB() == 'B'                   
assert o instanceof A                           
assert !(o instanceof B)
static method,property,field

http://groovy-lang.org/objectorientation.html#_static_methods_properties_and_fields
静态成员的支持还是实验性的,只支持2.5.4, 略过

执行

命令
groovy -version
groovysh
groovyConsole
groovy <scriptName>
脚本和类的理解
//常规类写法
class Main {                                    
  static void main(String... args) {          
      println 'Groovy world!'                 
  }
}
//常规脚本写法。  
println 'Hello'                                 
int power(int n) { 2**n }     
println "2^6==${power(6)}"                  
//实际上是一种简写,编译器处理后结果  
import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {
    int power(int n) { 2** n}                   
    def run() {
        println 'Hello'                         
        println "2^6==${power(6)}"              
    }
    static void main(String[] args) {
        InvokerHelper.runScript(Main, args)
    }
}

脚本(script)会被编译成类(class),这里的run需要有一个返回值,而且脚本的名字来源于文件名

在脚本中定义变量,int x = 1 会限制其作用于在run方法内
而 x = 1,会将x绑定到整个脚本,甚至可以和交互的应用共享数据。
如果你只是想把范围扩大到class,而不要绑定,可以使用@Field标签

和java 运行、语法区别

  • 因为类型在运行的时候才知道,所以方法的选择会在运行时进行。这种方式叫做 运行时分发(runtime dispatch) 或 多方法(multi-methods)
//这里 result = 1
//因为在运行时会忽略定义时的Object;然后“object”是String类型的
int method(String arg) {
    return 1;
}
int method(Object arg) {
    return 2;
}
Object o = "Object";
int result = method(o);
  • 不支持java7的ARM(Automatic resource manage)语法,但是下面写法有类似效果:
new File('/path/to/file').eachLine('UTF-8') {
   println it
}  
new File('/path/to/file').withReader('UTF-8') { reader ->
   reader.eachLine {
       println it
   }
}
  • 匿名函数和java表现的类似,实际实现和groovy.lang.closure相近,总是有些不同:
//非静态内部类的调用
//groovy支持调用单参数函数时不传递参数,这时参数为null;构造器也会有这个属性,可能造成问题(但是还没有更好的方式解决)
public class Y {
    public class X {}
    public X foo() {
        return new X()
    }
    public static X createX(Y y) {
        return new X(y)
    }
}
  • lambdas
//groovy不支持这种语法,但是用闭包有相近的部分
Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)
  • java中的==和 groovy的is一样,而groovy的==被解释成 a.compareTo(b) == 0
  • 接口暂时不支持java8 的default,可以用traits实现类似效果
  • 不需要显式try/catch,或者throws
  • 不支持java8 的TYPE_PARAMETER, TYPE_USE
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、下载 4使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.m或d论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 、1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值