如何将markdown转换为wxml

话说我要为技术博客写一个小程序版,我的博客解决方案是 hexo + github-page,格式当然是技术控们喜欢的 markdown 了 。但小程序使用的却是独有的模版语言 WXML。我总不能把之前的文章手动转换成小程序的 wxml 格式吧,而网上也没完善的转换库,还是自己写个解析器吧。

解析器最核心的部分就是字符串模式匹配,既然涉及到字符串匹配,那么就离不开正则表达式。幸好,正则表达式是我的优势之一。

正则表达式

JavaScript中的正则表达式

解析器涉及到的 JavaScript 正则表达式知识

RegExp 构造函数属性,其中lastMatch,rightContent在字符串截取时非常有用

长属性名短属性名替换标志说明
input$_最近一次要匹配的字符串。Opera未实现此属性
lastMatch$&$&最近一次的匹配项。Opera未实现此属性
lastParen$+最近一次匹配的捕获组。Opera未实现此属性
leftContext`       | `input字符串中lastMatch之前的文本
rightContext$'$'Input字符串中lastMatch之后的文本
multiline$*布尔值,表示是否所有表达式都使用多行模式。IE和Opera未实现此属性
$n$n分组
$$转义$
  1. test 方法 和 RegExp 构造函数

    test 方法调用后,上面的属性就会出现在 RegExp 中,不推荐使用短属性名,因为会造成代码可读性的问题,下面就是样例

    var text = "this has been a short summer";
    var pattern = /(.)hort/g;if (pattern.test(text)){alert(RegExp.input);         // this has been a short summeralert(RegExp.leftContext);   // this has been aalert(RegExp.rightContext);  // summeralert(RegExp.lastMatch);     // shortalert(RegExp.lastParen);     // salert(RegExp.multiline);     // false
    }//长属性名都可以用相应的短属性名来代替。不过由于这些短属性名大都不是有效的ECMAScript标识符,因此必须通过方括号语法来访问它们
    if (pattern.test(text)){alert(RegExp.$_);alert(RegExp["$`"]);alert(RegExp["$'"]);alert(RegExp["$&"]);alert(RegExp["$+"]);alert(RegExp["$*"]);
    }
    复制代码
  2. replace 方法

    一般使用的是没有回调函数的简单版本,而回调函数版本则是个大杀器,及其强大

    //简单替换, replace默认只进行一次替换, 如设定全局模式,  将会对符合条件的子字符串进行多次替换,最后返回经过多次替换的结果字符串.
    var regex = /(\d{4})-(\d{2})-(\d{2})/;
    "2011-11-11".replace(regex, "$2/$3/$1");//replace 使用回调函数自定义替换,必须启用全局模式g,因为要不断向前匹配,直到匹配完整个字符串
    //match为当前匹配到的字符串,index为当前匹配结果在字符串中的位置,sourceStr表示原字符串,
    //如果有分组,则中间多了匹配到的分组内容,match,group1(分组1)...groupN(分组n),index,sourceStr
    "one two three".replace(/\bt[a-zA-Z]+\b/g, function (match,index,str) { //将非开头的单词大写console.log(match,index,str);return match.toUpperCase(); 
    });
    复制代码
  3. match 方法

    全局模式和非全局模式有显著的区别,全局模式和 exec 方法类似。

    // 如果参数中传入的是子字符串或是没有进行全局匹配的正则表达式,那么match()方法会从开始位置执行一次匹配,如果没有匹配到结果,则返回null.否则则会返回一个数组,该数组的第0个元素存放的是匹配文本,返回的数组还含有两个对象属性index和input,分别表示匹配文本的起始字符索引和原字符串,还有分组属性
    var str = '1a2b3c4d5e';
    console.log(str.match(/b/)); //返回["b", index: 3, input: "1a2b3c4d5e"]//如果参数传入的是具有全局匹配的正则表达式,那么match()从开始位置进行多次匹配,直到最后.如果没有匹配到结果,则返回null.否则则会返回一个数组,数组中存放所有符合要求的子字符串,但没有index和input属性,也没有分组属性
    var str = '1a2b3c4d5e';
    str.match(/h/g); //返回null
    str.match(/\d/g); //返回["1", "2", "3", "4", "5"]var pattern = /\d{4}-\d{2}-\d{2}/g;
    var str ="2010-11-10 2012-12-12";
    var matchArray = str.match(pattern);
    for(vari = 0; i < matchArray.length; i++) {console.log(matchArray[i]);
    }
    复制代码
  4. exec 方法

    与全局模式下的 match 类似,但 exec 更强大,因为返回结果包含各种匹配信息,而match全局模式是不包含具体匹配信息的。

    //逐步提取,捕获分组匹配文本,必须使用全局模式g, 成功则返回数组(包含匹配的分组信息), 否则为null
    //Regex每次匹配成功后,会把匹配结束位置更新到lastIndex,下次从lastIndex开始匹配
    //如果不指定全局模式,使用while循环,会造成无穷循环
    var pattern = /(\d{4})-(\d{2})-(\d{2})/g;
    var str2 = "2011-11-11 2013-13-13" ;
    while ((matchArray = pattern.exec(str2)) != null) {console.log( "date: " + matchArray[0]+"start at:" + matchArray.index+" ends at:"+ 		pattern.lastIndex);console.log( ",year: " + matchArray[1]);console.log( ",month: " + matchArray[2]);console.log( ",day: " + matchArray[3]);
    }
    复制代码
  5. searchsplit 这两个比较简单的方法则不再介绍

正则表达式高级概念

正常情况下正则是从左向右进行单字符匹配,每匹配到一个字符, 就后移位置, 直到最终消耗完整个字符串, 这就是正则表达式的字符串匹配过程,也就是它会匹配字符,占用字符。相关的基本概念不再讲解,这里要讲的和字符匹配不同的概念 - 断言。

  1. 断言

    正则中大多数结构都是匹配字符,而断言则不同,它不匹配字符,不占用字符,而只在某个位置判断左/右侧的文本是否符合要求。这类匹配位置的元素,可以称为 "锚点",主要分为三类:单词边界,开始结束位置,环视。

    单词边界 \b 是这样的位置,一边是单词字符,一边不是单词字符,如下字符串样例所示

    \brow\b   //row
    \brow     //row, rowdy
    row\b     //row, tomorow
    复制代码

    ^ 行开头,多行模式下亦匹配每个换行符后的位置,即行首
    $ 行结束,多行模式下亦匹配每个换行符前的位置,即行尾

    //js 中的 $ 只能匹配字符串的结束位置,不会匹配末尾换行符之前的换行符。但开启多行模式(m)后,^ 和 $ 则可以匹配中间的换行符。 如下例子可验证:// 默认全局模式下,^ 和 $ 直接匹配到了文本最开头和末尾,忽略了中间的换行符
    'hello\nword'.replace(/^|$/g,'<p>')
    "<p>hello
    word<p>"// 多行模式下,同时能匹配到结束符中间的换行符
    'hello\nword\nhi'.replace(/^|$/mg,'<p>')
    "<p>hello<p>
    <p>word<p>
    <p>hi<p>"
    复制代码
  2. 环视

    环视是断言中最强的存在,同样不占用字符也不提取任何字符,只匹配文本中的特定位置,与\b, ^ $ 边界符号相似;但环视更加强大,因为它可以指定位置和在指定位置处添加向前或向后验证的条件。

    而环视主要体现在它的不占位(不消耗匹配字符), 因此又被称为零宽断言。所谓不占宽度,可以这样理解:

    • 环视的匹配结果不纳入数据结果;

    • 环视它匹配过的地方,下次还能用它继续匹配。

    环视包括顺序环视和逆序环视,javascript 在 ES 2018 才开始支持逆序环视

    • (?=) 顺序肯定环视 匹配右边
    • (?!) 顺序否定环视
    • (?<=) 逆序肯定环视 匹配左边
    • (?<!) 逆序否定环视

    来看一下具体的样例

    // 获取.exe后缀的文件名,不使用分组捕获,能使捕获结果不包含.exe后缀,充分利用了环视匹配结果同时不占位的特性
    'asd.exe'.match(/.+(?=\.exe)/)
    => ["asd", index: 0, input: "asd.exe", groups: undefined]// 变种否定顺序环视,排除特定标签p/a/img,匹配html标签
    </?(?!p|a|img)([^> /]+)[^>]*/?> //常规逆序环视,同样利用了环视匹配不占位的特性
    /(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill')  // ["100",index: 29,...]
    /(?<!\$)\d+/.exec('its is worth about90')                // ["90", index: 21,...] // 利用环视占位但不匹配的特性
    '12345678'.replace(/\B(?=(\d{3})+$)/g , ',') 
    => "12,345,678" //分割数字
    复制代码

解析器的编写

正则表达式相关写得有点多,但磨刀不误砍柴工,开始进入主题

markdown格式

hexo 生成的 markdwon 文件格式如下,解析器就是要把它解析成json格式的输出结果,供小程序输出 wxml

---
title: Haskell学习-functor
date: 2018-08-15 21:27:15
tags: [haskell]
categories: 技术
banner: https://upload-images.jianshu.io/upload_images/127924-be9013350ffc4b88.jpg
---
<!-- 原文地址:[Haskell学习-functor](https://edwardzhong.github.io/2018/08/15/haskellc/) -->
## 什么是Functor
**functor** 就是可以执行map操作的对象,functor就像是附加了语义的表达式,可以用盒子进行比喻。**functor** 的定义可以这样理解:给出a映射到b的函数和装了a的盒子,结果会返回装了b的盒子。**fmap** 可以看作是一个接受一个function 和一个 **functor** 的函数,它把function 应用到 **functor** 的每一个元素(映射)。```haskell
-- Functor的定义
class Functor f wherefmap :: (a -> b) -> f a -> f b
```
<!-- more -->
复制代码

入口

使用node进行文件操作,然后调用解析器生成json文件

const { readdirSync, readFileSync, writeFile } = require("fs");
const path = require("path");
const parse = require("./parse");const files = readdirSync(path.join(__dirname, "posts"));
for (let p of files) {let md = readFileSync(path.join(__dirname, "posts", p));const objs = parse(md);writeFile(path.join(__dirname, "json", p.replace('.md','.json')), JSON.stringify(objs), function( err ){err && console.log(err);});
}
复制代码

来看一下解析器入口部分,主要分为:summary 部分,code代码部分,markdown文本部分。将文本内容的注释和空格过滤掉,但是代码部分的注释要保留。

module.exports = function analyze(str) {let ret = { summary: {}, lines: [] };while (str) {// 空格if (/^([\s\t\r\n]+)/.test(str)) {str = RegExp.rightContext;}// summary 内容块if (/^(\-{3})[\r\n]?([\s\S]+?)\1[\r\n]?/.test(str)) {str = RegExp.rightContext;ret.summary = summaryParse(RegExp.$2);ret.num = new Date(ret.summary.date).getTime();}// codeif (/^`{3}(\w+)?([\s\S]+?)`{3}/.test(str)) {const codeStr = RegExp.$2 || RegExp.$1;const fn = (RegExp.$2 && codeParse[RegExp.$1]) ? codeParse[RegExp.$1] : codeParse.javascript;str = RegExp.rightContext;ret.lines.push({ type: "code", child: fn(codeStr) });}// 注释行if (/^<!--[\s\S]*?-->/.test(str)) {str = RegExp.rightContext;}// 提取每行字符串, 利用 . 不匹配换行符的特性if (/^(.+)[\r\n]?/.test(str)) {str = RegExp.rightContext;ret.lines.push(textParse(RegExp.$1));}}return ret;
};
复制代码

文本内容提取

summary 内容块的提取比较简单,不讲叙。还是看 markdown 文本内容的解析吧。这里匹配 markdown 常用类型,比如列表,标题h,链接a,图片img等。而返回结果的数据结构就是一个列表,列表里面可以嵌套子列表。但基本就是正则表达式提取内容,最终消耗完字符行。

function textParse(s) {const trans = /^\\(\S)/; //转义字符const italy = /^(\*)(.+?)\1/; //倾斜const bold = /^(\*{2})(.+?)\1/; //加粗const italyBold = /^(\*{3})(.+?)\1/; //倾斜和加粗const headLine = /^(\#{1,6})\s+/; //h1-6const unsortList = /^([*\-+])\s+/; //无序列表const sortList = /^(\d+)\.\s+/; //有序列表const link = /^\*?\[(.+)\]\(([^()]+)\)\*?/; //链接const img = /^(?:!\[([^\]]+)\]\(([^)]+)\)|<img(\s+)src="([^"]+)")/; //图片const text =/^[^\\\s*]+/; //普通文本if (headLine.test(s)) return { type: "h" + RegExp.$1.length, text: RegExp.rightContext };if (sortList.test(s)) return { type: "sl", num: RegExp.$1, child: lineParse(RegExp.rightContext) };if (unsortList.test(s)) return { type: "ul", num: RegExp.$1, child: lineParse(RegExp.rightContext) };if (img.test(s)) return { type: "img", src: RegExp.$2||RegExp.$4, alt: RegExp.$1||RegExp.$3 };if (link.test(s)) return { type: "link", href: RegExp.$2, text: RegExp.$1 };return { type: "text", child: lineParse(s) };function lineParse(line) {let ws = [];while (line) {if (/^[\s]+/.test(line)) {ws.push({ type: "text", text: "&nbsp;" });line = RegExp.rightContext;}if (trans.test(line)) {ws.push({ type: "text", text: RegExp.$1 });line = RegExp.rightContext;}if (sortList.test(line)) {return { child: lineParse(RegExp.rightContext) };}if (unsortList.test(line)) {return { child: lineParse(RegExp.rightContext) };}if (link.test(line)) {ws.push({ type: "link", href: RegExp.$2, text: RegExp.$1 });line = RegExp.rightContext;}if (italyBold.test(line)) {ws.push({ type: "italybold", text: RegExp.$2 });line = RegExp.rightContext;}if (bold.test(line)) {ws.push({ type: "bold", text: RegExp.$2 });line = RegExp.rightContext;}if (italy.test(line)) {ws.push({ type: "italy", text: RegExp.$2 });line = RegExp.rightContext;}if (text.test(line)) {ws.push({ type: "text", text: RegExp.lastMatch });line = RegExp.rightContext;}}return ws;}
}复制代码

代码块显示

如果只是解析文本内容,还是非常简单的,但是技术博客嘛,代码块是少不了的。为了代码关键字符的颜色显示效果,为了方便阅读,还得继续解析。我博客目前使用到的语言,基本写了对应的解析器,其实有些解析器是可以共用的,比如 style方法不仅可应用到 css 上, 还可以应用到类似的预解析器上比如:scsslesshtml也一样可应用到类似的标记语言上。

const codeParse = {haskell(str){},javascript(str){},html:html,css:style
};
复制代码

来看一下比较有代表性的 JavaScript 解析器,这里没有使用根据换行符(\n)将文本内容切割成字符串数组的方式,因为有些类型需要跨行进行联合推断,比如解析块,方法名称判断就是如此。只能将一整块文本用正则表达式慢慢匹配消耗完。最终的结果类似上面的文本匹配结果 - 嵌套列表,类型就是语法关键字,常用内置方法,字符串,数字,特殊符号等。

其实根据这个解析器可以进一步扩展和抽象一下,将它作为类 C 语言族的基本框架。然后只要传递 对应语言的正则表达式规则,就能解析出不同语言的结果出来,比如 C#javaC++GO

javascript(str) {const comReg = /^\/{2,}.*/;const keyReg = /^(import|from|extends|new|var|let|const|return|if|else|switch|case|break|continue|of|for|in|Array|Object|Number|Boolean|String|RegExp|Date|Error|undefined|null|true|false|this|alert|console)(?=([\s.,;(]|$))/;const typeReg = /^(window|document|location|sessionStorage|localStorage|Math|this)(?=[,.;\s])/;const regReg = /^\/\S+\/[gimuys]?/;const sysfunReg = /^(forEach|map|filter|reduce|some|every|splice|slice|split|shift|unshift|push|pop|substr|substring|call|apply|bind|match|exec|test|search|replace)(?=[\s\(])/;const funReg = /^(function|class)\s+(\w+)(?=[\s({])/;const methodReg = /^(\w+?)\s*?(\([^()]*\)\s*?{)/;const symbolReg = /^([!><?|\^$&~%*/+\-]+)/;const strReg = /^([`'"])([^\1]*?)\1/;const numReg = /^(\d+\.\d+|\d+)(?!\w)/;const parseComment = s => {const ret = [];const lines = s.split(/[\r\n]/g);for (let line of lines) {ret.push({ type: "comm", text: line });}return ret;};let ret = [];while (str) {if (/^\s*\/\*([\s\S]+?)\*\//.test(str)) {str = RegExp.rightContext;const coms = parseComment(RegExp.lastMatch);ret = ret.concat(coms);}if (/^(?!\/\*).+/.test(str)) {str = RegExp.rightContext;ret.push({ type: "text", child:lineParse(RegExp.lastMatch) });}if(/^[\r\n]+/.test(str)){str=RegExp.rightContext;ret.push({type:'text',text:RegExp.lastMatch});}}return ret;function lineParse(line) {let ws = [];while (line) {if (/^([\s\t\r\n]+)/.test(line)) {ws.push({ type: "text", text: RegExp.$1 });line = RegExp.rightContext;}if (comReg.test(line)) {ws.push({ type: "comm", text: line });break;}if (regReg.test(line)) {ws.push({ type: "fun", text: RegExp.lastMatch });line = RegExp.rightContext;}if (symbolReg.test(line)) {ws.push({ type: "keyword", text: RegExp.$1 });line = RegExp.rightContext;}if (keyReg.test(line)) {ws.push({ type: "keyword", text: RegExp.$1 });line = RegExp.rightContext;}if (funReg.test(line)) {ws.push({ type: "keyword", text: RegExp.$1 });ws.push({ type: "text", text: "&nbsp;" });ws.push({ type: "fun", text: RegExp.$2 });line = RegExp.rightContext;}if (methodReg.test(line)) {ws.push({ type: "fun", text: RegExp.$1 });ws.push({ type: "text", text: "&nbsp;" });ws.push({ type: "text", text: RegExp.$2 });line = RegExp.rightContext;}if (typeReg.test(line)) {ws.push({ type: "fun", text: RegExp.$1 });line = RegExp.rightContext;}if (sysfunReg.test(line)) {ws.push({ type: "var", text: RegExp.$1 });line = RegExp.rightContext;}if (strReg.test(line)) {ws.push({ type: "var", text: RegExp.$1 + RegExp.$2 + RegExp.$1 });line = RegExp.rightContext;}if (numReg.test(line)) {ws.push({ type: "var", text: RegExp.$1 });line = RegExp.rightContext;}if (/^\w+/.test(line)) {ws.push({ type: "text", text: RegExp.lastMatch });line = RegExp.rightContext;}if (/^[^`'"!><?|\^$&~%*/+\-\w]+/.test(line)) {ws.push({ type: "text", text: RegExp.lastMatch });line = RegExp.rightContext;}}return ws;}
}
复制代码

显示WXML

最后只要运行解析器,就能生成 markdown 对应的 json 文件了,然后把json加载到微信小程序的云数据库里面,剩下的显示就交由小程序完成。下面就是使用 taro 编写 jsx 显示部分

<View className='article'>{lines.map(l => (<Block><View className='line'>{l.type.search("h") == 0 && ( <Text className={l.type}>{l.text}</Text> )}{l.type == "link" && ( <Navigator className='link' url={l.href}> {l.text} </Navigator> )}{l.type == "img" && ( <Image className='pic' mode='widthFix' src={l.src} /> )}{l.type == "sl" && ( <Block> <Text decode className='num'> {l.num}.{" "} </Text><TextChild list={l.child} /></Block>)}{l.type == "ul" && ( <Block> <Text decode className='num'> {" "} &bull;{" "} </Text><TextChild list={l.child} /></Block>)}{l.type == "text" && l.child.length && ( <TextChild list={l.child} /> )}</View>{l.type == "code" && (<View className='code'>{l.child.map(c => (<View className='code-line'>{c.type == 'comm' && <Text decode className='comm'> {c.text} </Text>}{c.type == 'text' && c.child.map(i => (<Block>{i.type == "comm" && ( <Text decode className='comm'> {i.text} </Text> )}{i.type == "keyword" && ( <Text decode className='keyword'> {i.text} </Text> )}{i.type == "var" && ( <Text decode className='var'> {i.text} </Text> )}{i.type == "fun" && ( <Text decode className='fun'> {i.text} </Text> )}{i.type == "text" && ( <Text decode className='text'> {i.text} </Text> )}</Block>))}</View>))}</View>)}</Block>))}
</View>
复制代码

后记

经过这个项目的磨练,我的正则表达式的能力又上了一个台阶, 连 环视 都已经是信手拈来了?

小程序预览

转载于:https://juejin.im/post/5cc2d703e51d453f38191d9f

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/448913.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

巧妙喝水打败多种疾病

喝水&#xff0c;我们每天都会做的一件事&#xff0c;殊不知&#xff0c;喝水得当能打败多种疾病问题! 方法/步骤 一、很多人都听说过早晨喝一杯水对身体有好处&#xff0c;有人喝盐水?有人喝蜂蜜水?还有人为了美白喝柠檬水?到底喝什么水最好呢?人体经过了一宿的代谢&…

git 几个commit点合并成一个commit点

在用git做版本控制器的时候&#xff0c;经常会遇到以下情况&#xff1a; 1、在做1个功能的时候&#xff0c;你自己觉得代码没问题了&#xff0c;就本地commit&#xff0c;然后提交代码&#xff0c;在gitlab上发起和并请求&#xff0c;老大看完之后&#xff0c;觉得你还有修改的…

深入浅出 消息队列 ActiveMQ

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 一、 概述与介绍 ActiveMQ 是Apache出品&#xff0c;最流行的、功能强大的即时通讯和集成模式的开源服务器。ActiveMQ 是一个完全支持JM…

2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018) - 4.28

赛后补了几道 赛中我就写了两个... A - Altruistic AmphibiansGym - 101933A 看了眼榜没几个人做。就没看。 最后发现就是一个DP&#xff08;但是我觉得复杂度有点迷&#xff09; 题意&#xff1a;$n$只青蛙有参数$l,w,h$分别表示弹跳力&#xff0c;体重&#xff0c;身高&#…

消息队列技术介绍 : ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ

一、 消息队列概述 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合、异步消息、流量削锋等问题。实现高性能、高可…

python爬虫学习之页面登陆

爬虫学习的一点心得 登陆主要有3种方法&#xff1a;使用selenium&#xff0c;cookies&#xff0c;模拟表单登陆 个人对于一般情况使用cookies登陆 可以实现一次手动&#xff0c;长期自动&#xff0c;可以绕过登陆&#xff08;登陆的相关信息密码&#xff0c;账号等会存于cookie…

消息队列 应用场景 解析

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 另外腾讯云-云社区还有一文不允许转载&#xff0c;但内容挺好的&#xff1a;https://cloud.tencent.com/developer/article/1006035 分布…

利用memcached实现CAS单点登录集群部署

前言&#xff1a;利用memcached实现CAS单点登录集群部署 负载均衡&#xff1a;将接口请求的有状态性变成无状态性。是我们在实现负载均衡时必要要解决的问题。以应用接口的session状态为例&#xff0c;一般解决方法都是将session数据和应用进行剥离&#xff0c;session数据统一…

分布式开放消息系统 ( RocketMQ ) 的原理与实践

分布式消息系统作为实现分布式系统可扩展、可伸缩性的关键组件&#xff0c;需要具有高吞吐量、高可用等特点。而谈到消息系统的设计&#xff0c;就回避不了两个问题&#xff1a; 消息的顺序问题消息的重复问题RocketMQ作为阿里开源的一款高性能、高吞吐量的消息中间件&#xff…

数据结构02-链表

说明&#xff1a;由于该数据结构是由java并且是原生实现&#xff0c;所以与C有一些出入&#xff0c;不过原理是相同的 1.链表的定义 为了表示线性表元素a与a1的逻辑关系&#xff0c;存储数据时&#xff0c;除了存储元素本身的信息之外&#xff0c;还存储了直接后继元素的位置信…

洛谷P2347 砝码称重 某一年noip提高组原题

可以转化为01背包求方案数的问题&#xff0c;dp数组f[][]表示第几个砝码能称出的重量,可压缩至一维 转移方程为f(i,j)f(i-1,j-w[i]) 当前我们可以称出的重量必定是由之前的砝码重量转移过来的 #include<bits/stdc.h> using namespace std; const int N550; const int max…

linux 下载、安装 maven

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 创建maven的文件夹并下载maven的tar包到此文件夹中 //进入一个目录 cd /usr/local//创建一个文件夹 mkdir maven//下载maven的tar包…

c# 编程学习(二)

2019独角兽企业重金招聘Python工程师标准>>> 标识符是对程序中的各个元素进行标识的名称。  只能使用字母(大写和小写)、数字和下划线  标识符必须以字母或下划线开头 变量是容纳值的存储位置。可将变量想象成容纳临时信息的容器 命名变量的建议&#xff1a; …

深入理解C++ 虚函数表

目录 深入理解C 虚函数表虚函数表概述单继承下的虚函数表派生类未覆盖基类虚函数派生类覆盖基类虚函数多继承下的虚函数表无虚函数覆盖派生类覆盖基类虚函数钻石型虚继承总结几个原则安全性问题深入理解C 虚函数表 ​ C中的虚函数的作用主要是实现了多态的机制。关于多态&#…

Bootstrap简介

1.使用准备 1.1 Bootstrap的下载 http://www.bootcss.com&#xff0c;下载用于生产环境的Bootstrap即可。 1.2 Bootstrap包含的内容 ● 全局CSS&#xff1a;基本的 HTML 元素均可以通过 class 设置样式并得到增强效果&#xff1b;还有先进的栅格系统。 ● 组件&#xff1a;无数…

tomcat需要设置环境变量吗

tomcat是一款轻量级web应用服务器&#xff0c;安装的时候我们都是直接解压zip包&#xff0c;然后在bin目录下双击startup.bat就可以启动了&#xff08;当然&#xff0c;前提是本地要安装jdk并配置JAVA_HOME环境变量&#xff09; 所以我一直认为tomcat是不用配置环境变量的 但是…

钱荒下银行理财收益率角逐:邮储银行垫底

21世纪资管研究员松壑 由于银行理财的收益定价机制为设定预期收益率的“先行定价”&#xff0c;而银行对产品本金收益又保有或明或暗的兑付要求&#xff0c;其业绩往往在理财产品发行前就已决定。 因此&#xff0c;本次榜单根据已披露最高预期收益率&#xff08;下称收益率&a…

数据结构7.3_图的遍历

我们希望从图中某一顶点出发访遍图中其余顶点&#xff0c;且使每一个顶点仅被访问一次。 这一过程就叫做图的遍历。 图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。 然而&#xff0c;图的遍历要比树的遍历复杂得多。 因为图的任一顶点都可能和其余的顶…

HCL实验四

PC端配置&#xff1a;配置ip地址 配置网关 交换机配置&#xff1a;①创建VLAN system-view vlan 10 vlan 20 ②配置PC端接口 interface vlan-interface 10 ip add 192.168.10.254 24 interface vlan-interface 20 ip add 192.168.20.254 24 转载于:https://www.cnblogs.com/zy5…

程序员/设计师能用上的 75 份速查表

本文由 伯乐在线 - 黄利民 翻译自 designzum。欢迎加入 技术翻译小组。转载请参见文章末尾处的要求。75 份速查表&#xff0c;由 vikas 收集整理&#xff0c;包括&#xff1a;jQuery、HTML、HTML5、CSS、CSS3、JavaScript、Photoshop 、git、Linux、Java、Perl、PHP、Python、…