NIM学习笔记
学习资料
关键词(了解)
addr and as asm bind block break case cast concept const continue converter defer discard distinct div do elif else end enum except export finally for from func if import in include interface is isnot iterator let macro method mixin mod nil not notin object of or out proc ptr raise ref return shl shr static template try tuple type using var when while xor yield
Nim 模糊变量名称的唯一小缺点出现在使用Grep等工具或编辑器的搜索功能时。为了解决这个问题,Nim 提供了一个名为nimgrep的工具,它可以进行不区分大小写和样式的搜索。您的编辑器也可能支持这种类型的搜索。
您可以通过使用命令行参数–styleCheck:error或–styleCheck:hint调用编译器来强制实施一致的命名方案。
not关键字是一元操作符:
a not b 解析成 a(not b), 不是 (a) not (b)
变量和常量
var 可变量
Nim使用关键字 var 支持可变变量。如果您打算更改变量的值,请使用var关键字。
var # 可重复分配的可变变量 letter: char = 'n' # 非强制的类型声明 lang = "N" & "im" nLength: int = len(lang) boat: float truth: bool = false
let 不可变量
Nim使用关键字 let来设置不能被修改的不可变量名
let # 变量只能赋值一次 legs = 400 arms = 2_000 aboutPi = 3.14 a: int16 = 42 b = 42'i8 c = 1'f32 d = 1.0e19 let input = readLine(stdin) # 正确,只是赋值的变量input为不可变量
- 'iX,其中X是有符号整数的大小,'uX,其中X是无符号整数的大小
浮点数有两种类型,可用的浮点类型:
- 'f32 for float32 和'f64 for float64
Nim支持不需要转义序列的原始字符串文字。原始字符串文字是一个以r开头的字符串文字:
var f = openFile(r"C:\texts\text.txt") # 原始字符串, 所以 ``\t`` 不是制表符。
也可以使用三引号字符串文字指定多行字符串:
let multiLine = """foo bar baz """ echo multiLine
const 常量
const 与 let 区别: const 赋值的内容必须在编译时就能获取,性能佳,let 语句只保证变量不会被重新赋值
const debug = true compileBadCode = false
代码格式化
nimpretty --indent:2 tst1.nim
数据结构
数据结构非常有用。
常用基础类型
- 不可变数组 array
- 可变列表 seq
- 元组 tuple
高级数据类型
- 集合 set
- 字典 talbe
- 枚举 enum
array 数组
数组是值类型,就像int,float和许多其他类,这意味着它们在堆栈中被分配。数组的大小是静态&不可变,因此一旦声明数组,它就不能改变它的大小。这就是为什么当您尝试访问其边界之外的索引时,编译器可能会给您一个错误。在C中,不会检查索引边界,因此可以访问超出数组边界的内存。
var array0 = [1,3,5,7] array1 = ["My", "name", "is", "Dominik"] array2 : array[8,int] # 定长为8个int元素 array2[1] = 1 array2[array2.high] = 9 for i in array1: stdout.write i," " # 不换行输出 stdout.write "\n" for i in array1.low .. array1.high: echo array1[i] for i in 0 .. array1.high: echo array1[i] for i in countup(1,array2.high,2): echo array2[i]
自定义下标
var list: array[-10 .. -9, int] list[-10] = 1 list[-9] = 2
seq 序列
因为数组array的大小是静态的,你不能给他们添加更多的东西; 您只能使用新数据覆盖现有项目。
这就是Nim seq序列的用武之地。seq的大小是动态的,可以根据需要增长到更多元素(取决于内存大小)。
@符号用于转换成新序列。匿名过程的语法有点麻烦。值得庆幸的是,Nim支持一些用于定义匿名过程和过程类型的语法糖。语法糖不是语言的一部分,而是在标准库中定义,因此要使用它,您必须导入未来的模块。
import sequtils, future let numbers = @[1, 2, 3, 4, 5, 6] let odd = filter(numbers, (x: int) -> bool => x mod 2 != 0) assert odd == @[1, 3, 5] echo numbers.filter do(x:int)->bool: x>5
使用序列构造函数语法时,必须注意指定序列的类型。
var list = seq[int]
或者,因为编译器无法知道要定义的序列类型。当你构造一个非空序列时:
var list = @[4,8,15,16,23,42]
newSeq过程提供了预分配序列的另一种方法。newSeq还提供了一个重要的优化 - 建议你在提前了解序列的大小时使用它。
var drinks = newSeq[string](3) drinks[0] = "water" drinks[1] = "juice" drinks[2] = "chocolate" drinks.add("beer") if "beer" in drinks: echo "We have water and ", drinks.len - 1, " other drinks" let myDrink = drinks[2]
tuple元组
元组(tuple)适用于在同一个复合类型中放入不同类型的元素,元组还定义了元素的顺序 。
var child: tuple[name: string, age: int] today: tuple[sun: string, temp: float] child = (name: "Rudiger", age: 2) today.sun = "Overcast" today.temp = 70.1
type Person = tuple[name: string, age: int] let person1: Person = (name: "Peter", age: 30) person2: Person = ("Mark", 40) echo person1 echo person2
set 集合
set使用{}构造集合。在大括号内指定值列表,并用逗号分隔项。
集合不关注它存储的项目的顺序,因此您无法通过索引访问其中的项目。
set类型是值类型,因此不需要初始化。
var collection: set[char]
对于要检查集合中特定值的存在的情况,集合非常有用 - 使用 in 关键字
let collection = {'a', 'x', 'r'}
assert 'a' in collection
可以做集合处理:交集,合集,差集
let collection = {'a', 'T', 'z'}
let isAllLowerCase = {'A' .. 'Z'} * collection == {}
assert(not isAllLowerCase)
table字典
enum枚举
type Day = enum Mon=1, Tue, Wed, Thu, Fri, Sat, Sun=7 Colors {.pure.} = enum Red = "FF0000", Green = (1, "00FF00"), Blue = "0000FF" Signals = enum sigOK, sigFail=-1, sigQuit = 3, sigAbort = 6, sigKill = 9
type声明
type 关键字开头的声明包含多个类型定义。像 objects 或 enums 这样的标称类型只能在 type 部分中定义。
type Color = enum cRed, cBlue, cGreen Direction = enum # 另一种形式的写法 dNorth dWest dEast dSouth var orient = dNorth pixel = cGreen
type DieFaces = range[1..20] # 只有1..20之间的数字才是有效值 var my_roll: DieFaces = 13
type Name = string # 类型定义将类型给个别名 Age = int # 但更具描述性 Person = tuple[name: Name, age: Age] # 使用别名来声明类型 var john: Person = (name: "John B.", age: 17)
type AnotherSyntax = tuple # 另一种元组的写法 fieldOne: string secondField: int var test: AnotherSyntax test.fieldOne = "hello" test.secondField = 10 let (b,c) = test
类型定义可以是递归的,甚至可以是相互递归的。(相互递归类型只能在单个 type 部分中使用)
type # 演示相互递归类型的示例 Node = ref object # 垃圾收集器管理的对象(ref) le, ri: Node # 左右子树 sym: ref Sym # 叶节点含有Sym的引用 Sym = object # 一个符号 name: string # 符号名 line: int # 声明符号的行 code: Node # 符号的抽象语法树
与众不同的面向对象
Nim 中的对象类似于C中的结构体。
与Java中的类不同,Nim对象仅包含字段(有时也称为成员变量),但不包含过程、函数或方法,也不包含 cpp 中的初始化程序或析构函数。
在Nim中,我们将数据对象与过程、函数、方法以及与这些数据对象一起使用的可选初始化程序和析构函数分开。
Nim避免了这种严格的类概念,而其通用方法调用语法允许我们对所有数据类型使用类似类的语法。
例如,要获取字符串变量的长度,我们可以用经典的过程符号编写len(myString),或者我们可以使用方法调用语法myString.len()或仅使用myString.len。
type User = object proc say(self: User) = echo "say hello" proc eat(self: User) = echo "eat something " let shaohy = User() shaohy.say() shaohy.eat()
流程控制
if..elif..else
let ageDesc = if age < 18: "Non-Adult" else: "Adult" # 类似C的三目运算符 max=(a>b)?a:b
if age > 0 and age <= 10: echo("You're still a child") elif age > 10 and age < 18: echo("You're a teenager") else: echo("You're an adult")
- 因为每种情况都会被编译器评估,所以其必须是个常量表达式。
- 分支中的语句不会开启新作用域。
- 编译器会检查语义并且只为第一个评估为 true 的情况生成代码。
与C语言中的#ifdef相似,when语句在编写针对特定平台的代码时十分有用
when sizeof(int) == 2: var intSize = 2 echo "running on a 16-bit system!" elif sizeof(int) == 4: var intSize = 4 echo "running on a 32-bit system!" elif sizeof(int) == 8: var intSize = 8 echo "running on a 64-bit system!" else: echo "cannot happen!" echo intSize # variable is visible here!
while
您还可以嵌套循环语句,您可能想知道如何一次性打破多个循环。这可以通过为break关键字指定标签来解决。标签必须由block关键字定义,并且打破该标签将导致执行突破该块内的每个循环。
block label: var i = 0 while true: while i < 5: if i > 3: break label i.inc
while猜数字
import strutils, rdstdin, random randomize() let answer = rand(10) while true: let guess = parseInt( readLineFromStdin("猜一个0到10之间数字:")) if guess < answer: echo "小了,again" elif guess > answer: echo "大了,again" else: echo "正确!" break
for
import os for i in walkFiles("*.nim"): echo i
for i in @[1,2,3,4,5]: echo(i) for i,value in @[1,2,3,4,5]: echo("idx: " ,i," value: ",value)
case...of
case toLower(readLineFromStdin("are you ok? yes/no ")): of "no": echo "Sorry." of "yes": echo "Great." else: echo "Orz..."
import os case system.hostOS: of "linux": echo "Linux" of "macosx", "MacOS": echo "Macos" else: echo "Unknown"
在某些方面,Nim的定义比其他语言更迂腐(德国风格)。
func函数更接近于纯数学的函数式编程,本质上,它们是设置了额外限制的过程:
- 它们不能访问全局状态(const 除外)且不能产生副作用。
- 基本上是用 {.noSideEffects.} 标记的 proc 的别名
- 函数仍然可以更改它们被标记为 var 的可变参数,以及任何 ref 对象
method方法与过程不同,它是动态分派的, 是一个与继承和面向对象编程密切相关的概念, 方法依赖于从 RootObj 继承的对象。
过程(proc)
在本节中,我们将探讨Nim中的procedures(过程)。在其他编程语言中,过程可以被称为函数,方法或子例程。每种编程语言都对这些术语有不同的含义,Nim也不例外。可以使用proc关键字定义Nim中的过程,然后是过程的名称,参数,可选的返回类型,=和过程体。
也可以让编译器为您推断出程序的返回类型。为此,您需要使用auto自动类型:
proc genHello(name: string, surname = "Doe"): auto =
"Hello " & name & " " & surname
assert genHello("Peter") == "Hello Peter Doe"
assert genHello("Peter", "Smith") == "Hello Peter Smith"
虽然这很方便,但您应尽可能明确指定类型。这样做可以让您和其他人更容易确定过程的返回类型,而无需了解过程的主体。
快速排序
import strutils, strformat var arrNum = @[3,1,4,2,7,5,6,9,8,0] proc quickSort(arr: var seq[int], st, en: int) = if st == en: return var sep = st for i in st+1..<en: echo &"st: {st} i : {i}, sep: {sep} " if arr[i] > arr[st]: inc sep stdout.write fmt" {arr[i]} <-> {arr[sep]} => " swap(arr[i],arr[sep]) stdout.write arr echo "" swap(arr[st], arr[sep]) quickSort(arr, st, sep) quickSort(arr, sep+1, en) quick_sort(arrNum,0,arrNum.len) echo arrNum.join(" ")
斐波那契数列
递归函数
proc fib(n: int): int = if n < 2: result = n else: result = fib(n-1) + fib(n-2) echo fib(6)
自构建数列
proc fib2(n: int) : int {. noSideEffect .}= var a = 0 b = 1 c: int m = 1 while m < n: c = a a = b b = c + b inc m return b echo fib2(6)
迭代器
自定义一个浮点数半增的计数器
iterator countTo(n: float): float = var i:float = 0 while i <= n: yield i i += 0.5 for i in countTo(5): echo i
泛型函数
泛型函数就像CPP的模板一样,并允许与模板相同的静态检查
import math proc `**`(a,b : float):float = pow(a,b) echo 2 ** 5 proc `*`[T](a: T, b: int): T = result = "" for i in 0..<b: result &= a echo "a"*10
调用系统命令
import osproc for line in lines "./ip.txt": var cmd = "ping -f -c3 " & line echo execCmd(cmd)
import osproc import streams for line in lines "./ip.txt": var cmd = "ping -f -c3 " & line let p = startProcess(cmd, options={poEvalCommand}) let s = p.outputStream() echo s.readAll()
引用和指针
使用 ref 关键字声明追踪引用,使用 ptr 关键字声明未追踪引用。 通常, ptr T 可以隐式转换为 pointer 类型。
多线程和线程池
nim中的『multi-thread』线程模块是隐式的导入的一部分系统模块,因此您无需显式导入它。
#import locks # not necessary import osproc proc Ping(ip: string) {.thread.} = var cmd = "ping -f -c3 " & ip discard execCmd(cmd) for line in lines "./ip.txt": var thread: Thread[string] createThread[string](thread, Ping, line) joinThread(thread)
nim中的线程池模块需要显式导入它(异步最快)
import threadpool import osproc proc Ping(ip: string) {.thread.} = var cmd = "ping -f -c3 " & ip discard execCmd(cmd) for line in lines "./ip.txt": spawn Ping(line) sync()
NIM编程实例
最简单webserver
import asynchttpserver,asyncdispatch proc conn(req: Request) {.async} = await req.respond(Http200,"Hello world, Nim!") asyncCheck newAsynchttpserver().serve(Port(8080),conn) runForever()
Server Software: Server Hostname: 192.168.13.181 Server Port: 8080 Document Path: / Document Length: 17 bytes Concurrency Level: 100 Time taken for tests: 11.016 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 560000 bytes HTML transferred: 170000 bytes Requests per second: 907.80 [#/sec] (mean) Time per request: 110.157 [ms] (mean) Time per request: 1.102 [ms] (mean, across all concurrent requests) Transfer rate: 49.65 [Kbytes/sec] received
NIM读写excel
import xlsx, random proc processExcel(inputFile: string, outputFile: string) = # Load the workbook and select the first sheet let wb = loadWorkbook(inputFile) let ws = wb[0] # Create a new workbook for output var newWb = newWorkbook() let newWs = newWb.addSheet("Processed Data") # Iterate through the rows and columns for row in ws.rows: var newRow = newWs.addRow() for cell in row.cells: if cell.hasValue: # Check if the cell contains a number if cell.value.isNumber: # Generate a random coefficient between 0.9 and 0.95 let coefficient = 0.9 + random.uniform(0.0, 0.05) # Multiply the cell value by the coefficient let newValue = cell.value.float * coefficient newRow.addCell(newValue) else: newRow.addCell(cell.value) # Keep non-numeric values unchanged # Save the new workbook newWb.save(outputFile) # Main program proc main() = let inputFile = "input.xlsx" # Replace with your input file path let outputFile = "output.xlsx" # Replace with your desired output file path processExcel(inputFile, outputFile) main()
NIM写jwt认证
import jwt, times, json, tables var secret = "upyun123" proc sign(userId: string): string = var token = toJWT(%*{ "header": { "alg": "HS256", "typ": "JWT" }, "claims": { "userId": userId, "exp": (getTime() + 1.days).toUnix() } }) token.sign(secret) result = $token proc verify(token: string): bool = try: let jwtToken = token.toJWT() result = jwtToken.verify(secret, HS256) except InvalidToken: result = false proc decode(token: string): string = let jwt = token.toJWT() result = $jwt.claims["userId"].node.str echo sign("shaohaiyang@gmail.com")
Nim 写游戏
Nim Blog写博客
NIM 与 python 的天衣无缝
nimpy
import nimpy proc fib(n: int): int {.exportpy.} = if n <= 2: return 1 else: return fib(n - 1) + fib(n - 2)
如果没有nimpy模块,使用
nimble install nimpy
然后导出c库函数,源文件名和库函数名要一致:
nim c -d:release --app:lib --threads:on --out:fib_nimpy.so fib_nimpy.nim
nimporter
import nimporter from test01 import fib # 直接调用test01.nim中的函数,第一次会编译运行 if __name__ == "__main__": print(fib(44))
pylib
import pylib var n = int(input("♚ :请输入一个数字: ")) print(f"计算 {n} 的斐波那契数:{fib(n)}")
NIM 与 C 交互
nim与C做数据结构转化(file)
# file /usr/bin/ls /usr/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, .... #ldd /usr/bin/file linux-vdso.so.1 => (0x00007ffedefd3000) libmagic.so.1 => /usr/lib64/libmagic.so.1 (0x00007f7f1919d000) libz.so.1 => /usr/lib64/libz.so.1 (0x00007f7f18f87000) libc.so.6 => /usr/lib64/libc.so.6 (0x00007f7f18bb9000) /lib64/ld-linux-x86-64.so.2 (0x00007f7f193ba000)
from os import fileExists, expandFilename const libName* = "libmagic.so.1" const MAGIC_NONE* = 0x000000 # No flags const MAGIC_DEBUG* = 0x000001 # Turn on debugging const MAGIC_SYMLINK* = 0x000002 # Follow symlinks const MAGIC_COMPRESS* = 0x000004 # Check inside compressed files const MAGIC_DEVICES* = 0x000008 # Look at the contents of devices const MAGIC_MIME_TYPE* = 0x000010 # Return only the MIME type const MAGIC_CONTINUE* = 0x000020 # Return all matches const MAGIC_CHECK* = 0x000040 # Print warnings to stderr const MAGIC_PRESERVE_ATIME* = 0x000080 # Restore access time on exit const MAGIC_RAW* = 0x000100 # Don't translate unprint chars const MAGIC_ERROR* = 0x000200 # Handle ENOENT etc as real errors const MAGIC_MIME_ENCODING* = 0x000400 # Return only the MIME encoding const MAGIC_NO_CHECK_COMPRESS* = 0x001000 # Don't check for compressed files const MAGIC_NO_CHECK_TAR* = 0x002000 # Don't check for tar files const MAGIC_NO_CHECK_SOFT* = 0x004000 # Don't check magic entries const MAGIC_NO_CHECK_APPTYPE* = 0x008000 # Don't check application type const MAGIC_NO_CHECK_ELF* = 0x010000 # Don't check for elf details const MAGIC_NO_CHECK_ASCII* = 0x020000 # Don't check for ascii files const MAGIC_NO_CHECK_TOKENS* = 0x100000 # Don't check ascii/tokens type Magic = object type MagicPtr* = ptr Magic # magic_t magic_open(int); proc magic_open(i:cint) : MagicPtr {.importc, dynlib:libName.} # void magic_close(magic_t); proc magic_close(p:MagicPtr): void {.importc, dynlib:libName.} #int magic_load(magic_t, const char *); proc magic_load(p:MagicPtr, s:cstring) : cint {.importc, dynlib: libName.} #int magic_errno(magic_t); proc magic_error(p: MagicPtr) : cstring {.importc, dynlib:libName.} #const char *magic_file(magic_t, const char *); proc magic_file(p:MagicPtr, filepath: cstring): cstring {.importc, dynlib: libName.} proc guessFile*(filepath: string, flags: cint = MAGIC_NONE): string = var mt : MagicPtr mt = magic_open(flags) discard magic_load(mt, nil) if fileExists(expandFilename(filepath)): result = $magic_file(mt, cstring(filepath)) magic_close(mt) echo guessFile("/usr/bin/ls")
nim编译成c动态库
proc fib*(a: int): int {.exportc, dynlib.} = if a <= 2: result = 1 else: result = fib(a - 1) + fib(a - 2) [root@ops-shaohy-rds myNim]# nim c --app:lib nim2_dynlib.nim -> libnim2_dynlib.so
nim调用c动态库
proc fib(n: int): int{.importc, dynlib: "./libnim2_dynlib.so".} echo fib(10)
nim调用标准库
proc strcmp(a, b: cstring): cint {.importc: "strcmp", nodecl.} echo strcmp("Nim", "Nim")
- 给编译器提供一个声明,但不需要实际的实现。
- 实现是通过其他方式提供的,比如外部库函数等
nim调用非标准库
#include <stdio.h> /* gcc -shared -nostartfiles -fPIC fakeimglib.c -o fakeimglib.so */ int openimage(const char *s) { static int handle = 100; fprintf(stderr, "opening %s\n", s); return handle++; } # nim code proc openimage(s: cstring): cint {.importc, dynlib: "./fakeimglib.so".} echo openimage("foo") echo openimage("bar")

