Giter Club home page Giter Club logo

blog.sc's People

Contributors

suhao avatar

Stargazers

soulmountain avatar nil avatar Mass avatar

Watchers

James Cloos avatar  avatar

blog.sc's Issues

React.js

React.js

一个用于创建可复用、可聚合的web组件的js库,只提供mvc中的v层。

  1. 组件化: 不是写一大堆的HTML模板
  2. JSX:js逻辑与HTML标签紧密相连,且极易理解
  3. 单项数据流:数据更新,直接更新整个App
  4. 虚拟DOM-Tree

一、JSX

Javascript的xml语法扩展,React将jsx编译成React构造器的方法。支持嵌套的子组件,方便直观。

Python算法概要

搜索(线性和二分查找)、排序(冒泡、选择排序)、递归函数(阶乘、斐波那契数列)、时间复杂度(线性、二次和常量)

效率工具:代码格式化之clang-format

团队编写代码需要保持统一的代码格式,但是每个人总有各自的习惯,通过工具会更加方便管理。clang-format是基于clang的一个命令行工具,能够自动格式化C/C++/ObjectC代码,支持多种代码风格:Google、Chromium、LLVM、Mozilla、Webkit等,也支持自定义风格(通过编写clang-format文件)。

FFI-在JavaScript中调用C++动态链接库

如何在JavaScript中调用C++动态链接库

什么是ffi

ffi 是Foreign Function Interface的简称,是一款nodejs的addon。

可以让大家在nodejs中调用c风格的c++动态链接库

ffi调用起来非常简单,可以看下官网的这个例子

var ffi = require('ffi-napi');

var libm = ffi.Library('libm', {
  'ceil': [ 'double', [ 'double' ] ]
});
libm.ceil(1.5); // 2

// You can also access just functions in the current process by passing a null
var current = ffi.Library(null, {
  'atoi': [ 'int', [ 'string' ] ]
});
current.atoi('1234'); // 1234

只要传入dll的路径,方法名称,返回值和参数类型,就可以得到一个关于dll中对应方法的代理对象。

接着在这个代理对象上执行对应的方法即可。

ffi原理

node-ffi的原理可以参见下图

image

实际上node-ffi是在libffi之上套了一层壳,将libffi的方法封装成了nodejs的addon

而libffi又调用了系统的API,打开特定的dll(POSIX下dlopen方法/Winodws下LoadLibraryEx方法),并获取对应的方法的地址(POSIX下dlsym方法/Winodws下GetProcAddress方法)。

获得方法对象之后,就可以封装对应的参数,在dll中执行,并获得对应的返回值
image

ffi问题

ffi相较于单独编写nodeaddon的方式简便了不少。

但是也会存在一定的问题。

  • ffi只支持c风格的dll
  • ffi通过对应的字符串进行动态加载,没有编译优化,相较于addon会有一定性能损失

因此ffi也不是银弹,需要大家在项目中根据实际情况进行考虑

参考文档:

[博客] 我最终选择用Github issues来写博客

image
作为一个IT从业者,一直想挑选一个合适的平台来写博客,希望有一个高度定制化的界面,展示自己独一无二的个性和工作、技能等。折腾过WordPress、hexo,自行搭建服务等一系列尝试后,间断性的码字总是被服务搭建到期而终止。最终发现,基于issuess的方式就是我想要的博客平台。

为什么是Github Issues?

作为全球最大的代码托管平台,又被微软收入麾下,其可靠程度是非常高的,基本不用担心存放在里面的数据会丢失。

Github issues 提供了非常方便快捷的编辑能力,尤其是贴图。它支持通过拖拽、粘贴、选择的方式上传图片,图片会存放在 user-images.githubusercontent.com 这个地方,且支持外链——这也意味着我们可以很方便地把 issue 的内容转载到其他的平台。

在 Github issues 里面,可以为某条 issue 添加点赞、爱心等互动标签(Reactions),也可以设置分类标签(Labels),更可以给 issue 添加评论(Comment)。

Github 提供了一套满足了绝大部分需求的 API,囊括了 REST 和 GraphQL 的调用方式,这才是 Github 能够成为我们博客平台的大杀器。

作为一个博客平台,它满足了我的以下需求:

  1. 先有Github,再有Issues:免费,安全;免费,安全;免费,安全
  2. 个人介绍:github自带展示个人技能的平台属性
  3. 文章的撰写与展示:对一个博客来说,最重要的就是它的内容,也就是里面的文章。一个好用的博客平台应该具备方便的撰写文章的能力,让够让用户毫无负担地撰写、编辑自己的文章。此外,还必须能够文章的信息,比如展示标题、节选、封面,创建/修改时间,评论点赞数等等
  4. 归档能力:一篇文章的撰写时间、内容标签/分类等都是不同的,如何按照不同的要求对这些文章进行归档整理,也是考验博客平台的能力之一。再者,当文章数量较多的时候,添加一个搜索的功能也能大大方便读者对博客的浏览
  5. 博主与读者互动的能力:仅仅只有博主一个人自嗨可能难以激发写作的动力,如果博客能够提供博主与读者互动的能力,将能有效激励博主持续创作,更能提升文章的传播度——点赞和评论功能则是互动能力中最重要的功能之一

前后端分离

使用 Github issues 作为博客平台,也就是相当于管理后端。我们在管理后端里面撰写文章,设置标签,回复评论,然后通过 API 调用把数据传送给客户端。

管理后端直接用现成的 Github issues 页面,那么客户端则使用 Github 为开发者免费提供的静态页面部署服务 Github pages。要使用这个服务,只需要开通一个仓库,然后在仓库的 Settings 里面找到 Github pages 并打开即可,默认会以 Master 分支的根目录作为静态资源目录,我们只需要把客户端的静态资源直接放置在这里就好。

开通了 Github pages 以后,便可以通过其提供的 URL 直接在浏览器里访问到博客了,而博客的数据则完全加载自 Github API。通过已授权的接口,还允许提交评论等功能。

结语

总结一下,Github issues 提供了一个博客平台所需的的各项基本能力,与 Github 的可靠性, API 的全面性,Github pages 的便捷性结合在一起,都非常适合作为一个博客平台来使用。

下一步,我将一步一步学习如何基于Issues搭建个性化的博客平台。欢迎一起来学习、交流。

CSS的布局方式

前端开发,使用CSS进行布局是工作的核心。在做ReactNativeDesktop的实践中,逐步学习了CSS的布局方式。本文将汇总相关的基础知识,以供回顾和总结。

一、盒模型布局方式

传统的布局方式是通过盒模型,使用display(文档流布局)+position(定位布局)+float(浮动布局)来控制。

1. 文档流布局

按照文档的顺序一个个显示,块元素独占一行,行内元素共享一行。

2. 浮动布局

使元素脱离文档流,浮动起来。

3. 定位布局

通过position属性来定位。

二、Flexbox布局

通过上述盒模型的三种布局方式有一些缺陷,比如我们不能只使用一个属性来实现垂直居中布局,所以就产生了第四种布局方式:flex 布局。可以简便、完整、响应式地实现各种页面布局,Flex即Flexible Box的缩写,意为弹性布局,为盒模型提供最大的灵活性。任何一个容器都可以指定为flex布局,行内元素也可以通过inline-flex属性值来使用flex布局。inline-flex 和 inline-block 一样,对设置了该属性值的元素的子元素来说是个 display:flex 的容器,对外部元素来说是个 inline 的块。

在 flex 中,最核心的概念就是容器和轴,所有的属性都是围绕容器和轴设置的。其中,容器分为父容器和子容器。轴分为主轴和交叉轴(主轴默认为水平方向,方向向右,交叉轴为主轴顺时针旋转 90°)

Flexbox布局

在使用 flex 的元素中,默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis),主轴开始的位置称为 main start,主轴结束的位置称为 main end。同理,交叉轴开始的位置称为 cross start,交叉轴结束的位置称为 cross end。在使用 flex 的子元素中,占据的主轴空间叫做 main size,占据的交叉轴空间叫做 cross size。

这里需要强调,不能先入为主认为宽度就是 main size,高度就是 cross size,这个还要取决于你主轴的方向,如果你垂直方向是主轴,那么项目的高度就是 main size。

实现 flex 布局需要先指定一个容器,任何一个容器都可以被置顶为 flex 布局,这样容器内部的元素就可以使用 flex 来进行布局。简单说来,如果你使用块元素如 div,你就可以使用 flex,而如果你使用行内元素,你可以使用 inline-flex。

需要注意的是:当时设置 flex 布局之后,子元素的 float、clear、vertical-align 的属性将会失效。

1. Flex容器

设置了 flex 属性的容器可以通过设置其属性值来设置容器的子元素的排列规则:

  • flex-direction:设置子元素的排列方向,row(默认值,主轴为水平方向,起点为左端)、row-reverse(主轴为水平方向,起点为右端)、colum(主轴为垂直方向,起点在上沿)、colum-reverse(主轴为垂直方向,起点在下沿)

  • flex-wrap:设置是否换行,让弹性盒子元素必要时换行显示,不设置默认为不换行(如果容器装不下子元素会相应的收缩);nowrap(默认不换行)、wrap(换行第一行在上)、wrap-reverse(换行第一行在下)

  • flex-flow:flex-direction 和 flex-wrap 属性的复合属性;默认为row nowrap

  • justify-content:设置子元素在横轴上的排列对齐方式;flex-start(默认值,左对齐)、flex-end(右对齐)、center(居中)、space-between(两端对齐,子元素间隔相等)

  • align-items:设置子元素在纵轴上的排列,flex-start(交叉轴的起点对齐)、flex-end(交叉轴的终点对齐)、center(交叉轴的中心点对齐)、baseline(子元素的第一行文字的基线对齐)、stretch(默认值;子元素未设置高度height或设置为auto将占满整个容器高度;如果设置则此属性值无效)

  • align-content:当有多行子元素时每行子元素之间的排列方式;当只有一行子元素时即flex-wrap属性值为no-wrap时,该属性没有效果;stretch(默认值;子元素默认排列,相当于只设置了flex-wrap: wrap,每行子元素间有一定的间隔)、flex-start(子元素从纵轴的起点开始排列,且行间没有间距)、flex-end(与纵轴的终点对齐,且行间没有间距)、center(与纵轴的中点对齐,行间无间距)、space-between(与纵轴两端对齐,轴线之间的间隔平均分布)、space-around(每行子元素的间隔都相等,且每行子元素之间的间隔比子元素到父元素的距离大一倍)

2. Flex子元素

  • order:设置某个子元素的排序先后顺序,数值越小,排列越靠前,默认为0

  • flex-grow:设置子元素的放大比例,默认为0;0时即使存在剩余空间也不放大;1时等分剩余空间;即按照所有子元素的值求和后等分,然后乘以各自的系数值

  • flex-shrink:子元素缩小比例,默认为1;1时即默认是如果父元素装不下子元素,如果不换行的话那么子元素都按照一样的比例缩小;该属性取负值无效;如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小;如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小

  • flex-basis:子元素的理想宽度,即子容器在不伸缩情况下的原始尺寸,主轴为横向时代表宽度,主轴为纵向时代表高度;默认值为auto即子元素的本来大小;跟长度width差不多意义,可以设为一个长度值,定义了子元素初始占据的宽度;设置子元素在被放进一个flex容器之前的大小,也就是子元素理想或假设的大小,但是flex-basis并不能保证其实际呈现的大小;但当父元素装不下子元素时,子元素会按照flex-shrink来进行相应比例的缩小,如果没有设置flex-shrink默认情况下每个子元素的压缩率都是一样的;当父元素有空余空间时,设置了flex-basis的子元素并不会自动扩大,而是保持flex-basis的属性值设置的大小

  • flex:flex-grow、flex-shrink和flex-basis的组合;默认值为0 1 auto;有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)

  • aligin-self:设置单个子元素的纵轴排列方式;允许单个项目有与其他项目不一样的纵轴排列方式,可覆盖align-items属性;默认值为auto,即跟父元素设置的 align-items 的值指定的纵轴排列方式一样;可能取6个值,设置的值可覆盖掉父元素设置的align-items指定的排列方式;

三、Grid布局

网格布局,实现二维布局方式。可以让我们摆脱现在布局中存在的文档流限制,换句话说,你的结构不需要根据设计稿从上往上布置了。这也意味着您可以自由地更改页面元素位置。这最适合你在不同的断点位置实现你最需要的布局,而不再需要为响应你的设计而担心HTML结构的问题。

和 table 布局不同的是,grid 布局不需要在 HTML 中使用特定的标签布局,所有的布局都是在 CSS 中完成的,你可以随意定义你的 grid 网格。

没有 HTML 结构的网格布局有助于使用流体、调整顺序等技术管理或更改布局。通过结合 CSS 的媒体查询属性,可以控制网格布局容器和他们的子元素,使用页面的布局根据不同的设备和可用空间调整元素的显示风格与定位,而不需要去改变文档结构的本质内容。

1. 网格线:Grid Lines

网格线组成了网格,他是网格的水平和垂直的分界线。一个网格线存在行或列的两侧。我们可以引用它的数目或者定义的网格线名称

GridLines

2. 网格轨道:Grid Track

网格轨道是就是相邻两条网格线之间的空间,就好比表格中行或列。在网格中其分为grid column和grid row。每个网格轨道可以设置一个大小,用来控制宽度或高度。

GridTrack

3. 网格单元格:Grid Cell

网格单元格是指四条网格线之间的空间。所以它是最小的单位,就像表格中的单元格。

GridCell

4. 网格区域:Grid Area

网格区域是由任意四条网格线组成的空间,所以他可能包含一个或多个单元格。相当于表格中的合并单元格之后的区域。

GridArea

5. Grid布局

使用 grid 布局很简单,通过display属性设置属性值为 grid 或 inline-grid 或者是 subgrid(该元素父元素为网格,继承父元素的行和列的大小) 就可以了。

网格容器中的所有子元素就会自动变成网格项目(grid item),然后设置列(grid-template-columns)和 行(grid-template-rows)的大小,设置 grid-template-columns 有多少个参数生成的 grid 列表就有多少个列。

注:当元素设置了网格布局,column、float、clear、vertical-align属性无效。

如果没有设置 grid-template-columns,那么默认只有一列,宽度为父元素的 100%。设置了 grid-template-columns 的话,设置了几个参数,就有几列(不超过 grid item 的个数),然后设置的 grid-template-row 参数就是每一列的高度(超出列数的高度无效)。

例如:grid-template-columns: 1fr 1fr 2fr; 生成3列

css fr 单位是一个自适应单位,fr单位被用于在一系列长度值中分配剩余空间,如果多个已指定了多个部分,则剩下的空间根据各自的数字按比例分配。fr 是基于网格容器可用空间来计算的(flex 也是一样),所以我们可以和其他单位混合使用,如果需要的话。

  • 行或列最小和最大尺寸:minmax() 函数来创建行或列的最小或最大尺寸,第一个参数定义网格轨道的最小值,第二个参数定义网格轨道的最大值。可以接受任何长度值,也接受 auto 值。auto 值允许网格轨道基于内容的尺寸拉伸或挤压。布局时,会先判断总高度是小于第一列高度的最大值和第二列高度的最大值之和的,如果大于最大值之和,那么第一列和第二列的高度都为设置的最大值,如果是小于最小值之和的话,那么第一列和第二列的高度都为设置的最小值。

例如:grid-template-rows: minmax(100px,200px) minmax(50px,200px),将第一行的高度设置为 minmax(100px,200px),第二行的高度设置为minmax(50px,200px)

如果容器总高度设置为 300px,这时每一列的高度要怎么算呢?总高度是小于第一列高度的最大值和第二列高度的最大值之和的,这样就是先用 总高度 300px - 第一列最小高度 100px - 第二列最小高度 50px = 150px。第一列高度:第一列最小高度 100px + 150px/2 = 175px;第二列高度:第一列最小高度 50px + 150px/2 = 125px

  • 重复行或者列:repeat() 可以创建重复的网格轨道。这个适用于创建相等尺寸的网格项目和多个网格项目。repeat接受两个参数:第一个参数定义网格轨道应该重复的次数,第二个参数定义每个轨道的尺寸。

  • 间距:grid-column-gap创建列与列之间的距离;grid-row-gap创建行与行之间的距离;grid-gap 是 grid-row-gap 和 grid-column-gap两个属性的缩写

6. 通过网格线定位 grid item

我们可以通过表格线行或者列来定位 grid item,grid-row 是 grid-row-start 和 grid-row-end 的简写。grid-column 是 grid-column-start 和 grid-column-end 的简写。如果只提供一个值,指定了 grid-row-start 和 grid-column-start 的值。如果提供两个值,第一个值是 grid-row-start 或者 grid-column-start 的值,第二个值是 grid-row-end 或者 grid-column-end 的值,两者之间必须要用/隔开。这四个值可以用 grid-area 缩写,分别对应 grid-row-start、grid-column-start、grid-row-end、grid-column-end。

7. 合并单元行与合并单元列

和 excel 中的合并单元行/列是相同的(这个需要设置在 grid item 中),grid-column-start、grid-column-end、grid-row-start、grid-row-end,也可以使用 grid-row 和 grid-column 简写的形式,关键词 span 后面紧随数字,表示合并多少个列或行,/ 前面是从第几行/列开始。

8. 自定义网格线名称

在 grid 中,是可以自定义网格线的名称的,然后使用定义好的网格线来进行布局,[col1-start] 网格线名称一定要使用 [] 括住

9. 通过网格区域命名和定位网格项目

什么是网格区域? 网格区域(grid-area)是一个逻辑空间,主要用来放置一个或多个网格单元格(Grid Cell)。他是由四条网格线(Grid line),网格区域每边一条,四边相交组织的网格轨道(Grid Track)。简单点理解,网格区域是有四条网格线交织组成的网格空间,这个空间中可能是一个网格单元格,也可能是多个网格单元格。

定义网格区域 在CSS Grid Layout中定义网格区域有两种方式,一种是通过网格线来定义,另一种是通过grid-template-areas来定义。接下来看看两种定义网格区域的方法在具体使用过程中有何不同。

  • 网格线定义网格区域:使用网格线定义网格区域的方法非常的简单,首先依赖于 grid-template-columns 和 grid-template-rows 显式定义网格线,甚至是由浏览器隐式创建网格线,然后通过 grid-area 属性通过取网格线,组成网格线交织区域,那么这个区域就是所讲的网格区域。在使用 grid-area 属性调用网格线,其遵循的规则是 grid-area: row-start/ column-start / row-end / column-end。

  • grid-template-areas 定义网格区域:在 CSS Grid Layout 中还可以通过 grid-template-areas 属性来定义网格区域的名称,然后需要放在对应网格区域的元素,可以通过 grid-area 属性来指定。而且重复区域可以使用同一个名称来实现跨区域。另外对于空的轨道区域,可以使用点号 . 来代表

四、常用的CSS布局

1. 水平垂直居中

垂直居中真的是已经被讲烂了,这个方式是有很多的,但就是看的太多会导致最后一个都没有记住,所以最好是每一种情况只记住一个最佳实践。

对于居中,不需要背什么“x 种方式实现 xx”这样的例子,我们只需要了解其原理即可写出符合要求的 css。

水平、垂直居中,比较喜欢用绝对定位的方法实现,其次就是使用 table 布局,因为自带垂直居中。如果是单行的行内元素使用 line-height 等于 height,对于多行元素的垂直居中,大部分都是使用 table 元素(求推荐更好的布局),当然还有 flex 和 grid 布局

  • 水平居中

一般水平居中还是比较容易的,一般都是先看子元素是固定宽度还是宽度未知;如果是固定宽度,这种方式是绝对定位居中,除了使用 margin,我们还可以使用 transform;宽度未知,将子元素设置为行内元素,然后父元素设置 text-align: center。

多个块状元素,上面的方式即使子元素不止一个也想实现水平居中也是有效的,(宽度固定不固定都可,不固定的话就不需要设置宽度,会被自动撑开,但是要考虑到撑爆的情况)。当然也可以使用我们刚刚介绍的 flex,我们只需要让子元素在主轴上的对齐方式设置为居中就可以。

  • 垂直居中

单行行内元素,只需要将子元素的行高等于高度就可以了。多行的行内元素,因为给每一个子元素都设置了 line-height,但是试了很多方法,要不是没有效果,要不然就是又局限性,提到最多的是使用 table-cell 的方式(但是貌似这个方法也有一点弊端,那就是其子元素的表现形式和行内元素类似,子元素不能独占一行);还有一个方法是设置一个空的行内元素,使其 height:100%,display:inline-block,vertical-align: middle; 并且 font-size:0。但是这样方式的原理我还不是很清楚,只是知道要设置一个空元素,高度和父元素相等,并且设置垂直居中的属性。但是,这只是用与所有的行内元素的宽度和不超过父元素的宽度的情况。另一个一劳永逸的方法就是 flex。

  • 图片和文字垂直居中

经常有看到设计稿是图片和文字垂直居中的,那么怎么才能让图片和文字垂直居中呢?只需要给图片一个 vertical-align: middle; 属性就可以。

  • 总结一下居中的原理

我们要实现水平或者垂直居中,应该从两方面下手:元素自带居中的效果或者强制让其显示在中间。

所以我们先考虑,哪些元素有自带的居中效果,最先想到的应该就是 text-align:center 了,但是这个只对行内元素有效,所以我们要使用 text-align:center 就必须将子元素设置为 display: inline; 或者 display: inline-block;;

接下来我们可能会想既然有 text-align 那么会不会对应也有自带垂直居中的呢,答案是有的 vertical-align:,我一直不是很喜欢使用这个属性,因为十次用,9.9 次都没有垂直居中,一度让我怀疑人生。现在貌似也搞得不是很清楚,看了 张鑫旭的文章 居然看得也不是很懂,笑哭。目前就在 table 中设置有效,因为 table 元素 的特性,打娘胎里面带的就是好用。还有一种可以有效的方式是前面提到的空元素的方式,不过感觉多设置一个元素还不如使用 table。

还有一只设置垂直居中的是将行内元素的 line-height 和 height 设置为相同(只适用于单行行内元素)

固定宽度或者固定高度的情况个人认为设置水平垂直居最简单,可以直接使用绝对定位。使用绝对定位就是子元素相对于父元素的位置,所以将父元素设置 position:reletive 对应的子元素要设置 position:absolute,然后使用 top:50%;left:50%,将子元素的左上角和父元素的中点对齐,之后再设置偏移 margin-top: 1/2 子元素高度;margin-left: 1/2 子元素宽度;。这种方式也很好理解。

上面的绝对定位方法只要将 margin 改为 transform 就可以实现宽度和高度未知的居中(兼容性啊兄弟们!(ಥ_ಥ))transformX:50%;transformY:50%;

2. 圣杯布局

其实我还真是第一次听说圣杯布局这种称呼,看了下这个名字的由来,貌似和布局并没有什么关系,圣杯布局倒是挺常见的三栏式布局。两边顶宽,中间自适应的三栏布局。

这个布局方式的关键是怎么样才能使得在伸缩浏览器窗口的时候让中间的子元素宽度改变。可以适应浏览器的宽度变化使用百分比设置宽度再合适不过,所以我们要将中间子元素的宽度设置为 100%,左边和右边的子元素设置为固定的宽度。

这里我们要注意的是,中间栏要在放在文档流前面以优先渲染。

将其三个元素的宽度和高度设置好,然后都设置为 float:left。

  • 使子元素在同一行显示

我们可以看出,现在三个子元素是在一排显示的,因为我们给中间的子元素设置的宽度是 100%,并且中间的子元素在文档流的最前面,最先被渲染。
那么我们要使得三个元素在同一排显示。接下来我们要将 .left 和 .right 向上提。实际上我们是使用 margin-left 为 负值来实现的,我们将 .left 的 margin-left 设置为 -100%(负的中间子元素的宽度),这样,左边的元素就会被“提升”到上一层。
然后就是右边子元素了,只需要设置 margin-left 设置为负的自身的宽度。

  • 使得中间子元素不被遮盖

现在中间的子元素被遮挡了,只要使得中间的子元素显示的宽度刚好为左边元素和右边元素显示中间的宽度就可以。同时我们还必须保证是使用的半分比的布局方式。

这样的话有一种方式可以即使中间的宽度减少,又可以使中间的宽度仍然使用 100%,那就是设置父元素的 padding 值,将父元素的 padding-left 设置为左边子元素的宽度,将父元素的 padding-right 设置为右边子元素的宽度。

  • 将左边和右边的子元素像两边移动

中间的子元素确实是在中间了,那么我们只需要设置相对位置,将左边的子元素和右边的子元素向两边移动就好。

3. 双飞翼布局

双飞翼布局是为了解决圣杯布局的弊端提出的,如果你跟我一起将上面的圣杯布局的代码敲了一遍,你就会发现一个问题,当你将浏览器宽度缩短到一定程度的时候,会使得中间子元素的宽度比左右子元素宽度小的时候,这时候布局就会出现问题。所以首先,这提示了我们在使用圣杯布局的时候一定要设置整个容器的最小宽度。

圣杯布局和双飞翼布局解决问题的方案在前一半是相同的,也就是三栏全部float浮动,但左右两栏加上负margin让其跟中间栏div并排,以形成三栏布局。

不同在于解决”中间栏div内容不被遮挡“问题的思路不一样:圣杯布局,为了中间div内容不被遮挡,将中间div设置了左右padding-left和padding-right后,将左右两个div用相对布局position: relative并分别配合right和left属性,以便左右两栏div移动后不遮挡中间div。

双飞翼布局,为了中间div内容不被遮挡,直接在中间div内部创建子div用于放置内容,在该子div里用margin-left和margin-right为左右两栏div留出位置。

所以只是一个小小的改动,在我们将中间元素宽度调到比两边元素小的时候,也是可以正常显示,但是如果总宽度小于左边元素或者右边元素的时候,还是会有问题。

参考文档

JavaScript菜鸟教程

JavaScript菜鸟教程

JavaScript是web的编程语言,定义了网页的行为(HTML定义网页内容,css描述网页布局)。

JavaScript 是一种轻量级的脚本编程语言,可以插入HTML页面编码。ECMA-262 是 JavaScript 标准的官方名称。

JavaScript是世界上最流行的脚本语言,因为你在电脑、手机、平板上浏览的所有的网页,以及无数基于HTML5的手机App,交互逻辑都是由JavaScript驱动的。

JavaScript是一种运行在浏览器中的解释型的编程语言。

1. JavaScript用法

HTML 中的脚本必须位于 <script> 与 </script> 标签之间。脚本可被放置在 HTML 页面的 和 部分中。您可以在 HTML 文档中放入不限数量的脚本。通常的做法是把函数放入 部分中,或者放在页面底部。这样就可以把它们安置到同一处位置,不会干扰页面的内容。

也可以把脚本保存到外部文件中。外部文件通常包含被多个网页使用的代码。外部 JavaScript 文件的文件扩展名是 .js。使用外部文件,可在 <script> 标签的 "src" 属性中设置该 .js 文件。外部脚本不能包含 <script> 标签

2. JavaScript输出

JavaScript 没有任何打印或者输出的函数,但是可以使用window.alert() 弹出警告框、document.write() 方法将内容写到 HTML 文档中、 innerHTML 写入到 HTML 元素、console.log() 写入到浏览器的控制台。

一、JavaScript语法

JavaScript是字母大小写敏感的,使用unicode字符集,涵盖了所有字符包括标点符号等。常用驼峰命名规则。

1. 注释

JavaScript不会执行注释,但是可以提高代码可读性。可以类似C++的单行注释或者多行注释。

2. 字面量

固定值,如3.14;数字字面量可以是整数或者小数或者科学计数;字符串字面量可以使用单引号或者双引号;表达式字面量用于计算;数组字面量定义一个数组;对象字面量定义一个对象;函数字面量定义一个函数;通常恒定不可变

3. 变量

用于存储数值;使用var定义变量,使用等号为变量赋值;变量通过变量名访问,通常可变。

  • 必须字母开头,下划线和美元符不推荐
  • 大小写敏感
  • 鼓励在代码开始处,统一对变量进行声明和赋初值
  • 虽然支持一个语句多个变量,但是不推荐
  • 未赋初值的变量,值为undefined
  • 重新声明变量,变量值不会丢失
  • 动态类型:相同变量可以用作不同类型
  • 可以使用new关键字来声明变量类型

变量均为对象,声明一个变量时,即创建一个新的对象

4. 操作符

  • 赋值运算符:=、+=、-=、*=、/=、%=
  • 算术运算符:=-*/%、++、--
  • 位运算符
  • 条件运算符:?:
  • 比较运算符:==、===、!=、!==、>、<、>=、<=
  • 逻辑运算符:&&、||、!

5. 语句

使用分号分隔,用于发送命令,告诉宿主环境执行任务。语句通常以一个语句标识符开始,并执行该语句。语句标识符作为保留关键字,不能作为变量名使用。例如function、return等。

  • if...else:条件语句
  • switch:条件语句
  • for、for...in:循环语句
  • while、do...while:循环语句

标签:break+标签可以跳出任何代码块,break+不带标签、continue用于循环中使用

6. 函数

函数是可以反复调用执行的语句块,多由事件驱动。使用function,包裹在花括号中。可以使用return增加返回值。函数内部定义的变量为局部变量,在函数运行以后被删除。

7. 数据类型

数字、字符串、数组、对象等

  • 值类型:基本类型,如字符串、数字、布尔、对空、未定义、Symbol
  • 引用数据类型:对象、数组、函数
  • 对象:拥有属性和方法的数据,可以认为是属性变量或者键值对的容器;键值对在对象中称为对象属性
  • 字符串:用于存储和处理文本,可以使用索引(从0开始)访问字符串中字符,内置属性length定义长度;特殊字符使用转义符;
  • typeof:检测变量的数据类型,数组是一种特殊的对象类型
  • null:表示一个空对象引用,typeof检测返回object,可以使用null来清空对象
  • undefined:表示变量不含有值,任何变量可以使用undefined清空
  • null与undefined值相等,类型不等
  • NaN:number
  • Array:object
  • Date:object
  • constructor属性:返回变量的构造函数
function isArray(myArray) {
    return myArray.constructor.toString().indexOf("Array") > -1;
}
function isDate(myDate) {
    return myDate.constructor.toString().indexOf("Date") > -1;
}

8. 作用域

可访问变量、对象、函数的集合,变量在作用域内保持生命周期。

  • 局部作用域
  • 全局作用域: 网页中的所有脚本和函数均可访问和使用

二、JavaScript事件

HTML 事件是发生在 HTML 元素上的事情。当在 HTML 页面中使用 JavaScript 时, JavaScript 可以触发这些事件。HTML 事件可以是浏览器行为,也可以是用户行为,如HTML加载完成、按钮被点击等。

1. 常见的HTML事件

  • onchange
  • onclick
  • onmouseover
  • onmouseout
  • onkeydown
  • onload

2. 可以做什么?

事件可以用于处理表单验证,用户输入,用户行为及浏览器动作:

  • 页面加载时触发事件
  • 页面关闭时触发事件
  • 用户点击按钮执行动作
  • 验证用户输入内容的合法性
  • 等等 ...

可以使用多种方法来执行 JavaScript 事件代码:

  • HTML 事件属性可以直接执行 JavaScript 代码
  • HTML 事件属性可以调用 JavaScript 函数
  • 你可以为 HTML 元素指定自己的事件处理程序
  • 你可以阻止事件的发生。
  • 等等 ...

三、JavaScript技巧

1. 正则表达式

使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式。搜索模式可用于文本搜索和文本替换。

/正则表达式主体/修饰符(可选)

在 JavaScript 中,正则表达式通常用于两个字符串方法 : search() 和 replace()

  • search:用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串,并返回子串的起始位置
  • replace:用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串

正则表达式修饰符:

  • i:执行对大小写不敏感的匹配
  • g:执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)
  • m:执行多行匹配

正则表达式模式:方括号用于查找某个范围内的字符

  • [abc]:查找方括号之间的任何字符
  • [0-9]:查找任何从 0 至 9 的数字
  • (x|y):查找任何以 | 分隔的选项

2. 错误处理

  • try...catch...finally
  • throw

3. 变量提升:hoisting,函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部

JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。JavaScript 初始化不会提升,只有声明的变量会提升,初始化的不会。

为了避免这些问题,通常我们在每个作用域开始前声明这些变量,这也是正常的 JavaScript 解析步骤,易于我们理解。

4. JavaScript 严格模式(use strict)

JavaScript 严格模式(strict mode)即在严格的条件下运行。"use strict" 指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。它不是一条语句,但是是一个字面量表达式,在 JavaScript 旧版本中会被忽略。"use strict" 的目的是指定代码在严格条件下执行。严格模式下你不能使用未声明的变量。

严格模式通过在脚本或函数的头部添加 "use strict"; 表达式来声明。

  • 消除代码运行的一些不安全之处,保证代码运行的安全
  • 提高编译器效率,增加运行速度
  • 为未来新版本的Javascript做好铺垫

"严格模式"体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。掌握这些内容,有助于更细致深入地理解Javascript,让你变成一个更好的程序员。

5. JavaScript 使用误区

  • 赋值运算符应用错误:在 JavaScript 程序中如果你在 if 条件语句中使用赋值运算符的等号 (=) 将会产生一个错误结果, 正确的方法是使用比较运算符的两个等号 (==)
  • 比较运算符常见错误:在常规的比较中,数据类型是被忽略的;在严格的比较运算中,=== 为恒等计算符,同时检查表达式的值与类型;switch 语句会使用恒等计算符(===)进行比较
  • 加法与连接注意事项:加法是两个数字相加。连接是两个字符串连接。JavaScript 的加法和连接都使用 + 运算符。
  • 浮点型数据使用注意事项:avaScript 中的所有数据都是以 64 位浮点型数据(float) 来存储;
  • 不能对 return 语句进行断行
  • JavaScript 不支持使用名字来索引数组,只允许使用数字索引
  • 在 JavaScript 中, 对象 使用 名字作为索引。如果你使用名字作为索引,当访问数组时,JavaScript 会把数组重新定义为标准对象。执行这样操作后,数组的方法及属性将不能再使用,否则会产生错误
  • 定义数组元素,最后不能添加逗号:数组最后一个值的后面添加逗号虽然语法没有问题,但是在不同的浏览器可能得到不同的结果
  • 定义对象,最后不能添加逗号
  • Undefined 不是 Null:在 JavaScript 中, null 用于对象, undefined 用于变量,属性和方法。对象只有被定义才有可能为 null,否则为 undefined。如果我们想测试对象是否存在,在对象还没定义时将会抛出一个错误。
  • 程序块作用域:在每个代码块中 JavaScript 不会创建一个新的作用域,一般各个代码块的作用域都是全局的

所有的编程语言,包括 JavaScript,对浮点型数据的精确度都很难确定。

UWP开发指南

UWP开发指南

现有工程是基于C++开发的,这里主要讨论C++库,以及基于C++的UWP移植和方案处理。

UWP是微软近些年大力打造的一个支持多设备、多CPU架构、多语言等可以运行在Win10系统的一个通用平台。在这个平台上微软加强了安全性、软件规范性,同时软件必须要经过微软商店的评审,很大程度上保证了软件的质量。

在此平台上,微软摈弃了历史悠久的Win32运行时库,转而提供一个全新的Windows运行时库,被称之为WinRT。WinRT的强大之处在于,它并不限制某一种语言,就目前来说它支持的语言有以下几种

  • C#
  • C++/CX(除C++支持外,微软还进行了扩展,以方便基于WinRT的开发)
  • JavaScript
  • C++/WinRT(微软19年新推出一种是用C++开发的方案)

同时,我们还可以使用多种语言来创建我们自己的WinRT库,来满足软件模块复用的需求。对于C++来说,共有三种模块可选择

  • UWP模块(通用DLL,使用CX语言来调用WinRT运行时,该项目的产出可以用在UWP程序中,但只限于C++直接使用,通常讲不支持多语言(C#也可通过加载C风格函数来调用))
  • C++/CX WinRT运行时模块(可被多语言使用,但是导出类必须使用CX的语法)
  • C++/WinRT运行时模块(可被多语言使用,但是需要IDL文件)

C++/WinRT,该方案支持完全以C++17的语法来编写代码以及调用WinRT的API,但是如果想要创建一个新的WinRT运行时组件,那么就需要把Lyra的导出类和接口都定义为一个IDL文件,然后由必要的WinRT工具生产相应的实现类来进行导出功能。工程量依然巨大和复杂。C++/CX WinRT,该方案则需要使用CX的语法来将功能导出,同样工程量不小。

UWP模块,该方案可以直接使用现有的C++代码,导出方式也和传统的Win32DLL一致,需要注意的是,某些Win32函数将不再支持,因此,移植的工作就是查看哪些函数不能在UWP环境下运行,然后在WinRT中找到替代方案来代替它。

开发实践

1. 安装包的自定义配置

DISM全称是Deployment Image Servicing and Management,一个命令行的部署映像服务和管理工具。针对特殊的渠道版本,需要附带自定义配置信息,可以利用Preinstall Apps Using DISM中的Use custom data files来实现。

DISM会在App的安装目录下生成的Custom.data文件,文件的格式可以自定义,文件名和内容生成无法修改,后续用户通过商店升级APP后,这个文件也不会丢失。直到用户卸载App时被一起移除,开发者可以将文件放到Appx/microsoft.system.package.metadata目录下(microsoft.system.package.metadata为隐藏目录),来调试自定义配置的定制版本功能。

2. 与网页的交互

参考:https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/web-view

可以通过使用InvokeScriptAsync来调用或注入脚本到webview,通过ScriptNotify 事件从 webview获取信息。

  • InvokeScriptAsync 只能传递 & 返回string值
  • 可以使用eval 方法来注入内容到网页: string functionString = String.Format("document.getElementById('nameDiv').innerText = 'Hello, {0}';", nameTextBox.Text); await webView1.InvokeScriptAsync("eval", new string[] { functionString });
  • 网页内容通过使用 window.external.notify 和 一个 string 参数来发送信息到app,注册 ScriptNotify 就能收到这些信息。为了在调用 window.external.notify 时能发出 ScriptNotify 事件,app必须在 manifest 里包含页面的 URI 到 ApplicationContentUriRules 里,这些 URI 必须使用 https ,可以使用通配符指定子域名,比如:https://*.microsoft.com。对于 app 包内的内容(比如将网页文件放在包内),并无此要求,可以直接使用 ms-local-stream:// URI 即可。

3. UWP开发的Visual studio插件推荐

UWP开发涉及很多UI布局文件,为便于协作和防止xaml文件的冲突,建议安装xaml styler插件,统一xaml格式,减少文件修改的冲突发生。安装后修改XAML Styler配置,Keep first attribute on same line设置为true,Format XAML on save改为false

4. 资源管理

C/C++编译系统与自动化

一、编译系统与编译自动化

1. 编译系统

  • CMake
  • GNU Autotool
  • MSBuild
  • Meson
  • QMake
  • KBuild
  • build2
  • xmake
  • Bazel
  • Make

2. 包管理

  • Conan
  • vcpkg
  • Buckaroo
  • QPM
  • cget
  • Hunter
  • Cppan
  • Build2
  • CPM
  • Spack
  • Nuget

包管理器的选择注意事项:

  • 具有CMake集成的最成熟管理器是Conan和vcpkg
  • Vcpkg 的软件包数量最多,但是,无法安装同一包的多个版本或托管本地包存储库
  • Conan 文档记录更多,并且对包版本提供了更多控制。它具有去集中存储库,还允许用户托管本地或 Intranet 专用存储库。柯南的一个小问题是 Python 安装要求,但通过将柯南转换为单个本机可执行文件,通过将柯南 Python 模块与 Python 解释器打包为 Windows 的 Py2Exe 工具,可以快速解决此问题

包管理器的一些合理功能集

  • CMake 集成
  • 默认存储库、去集中存储库和本地存储库。
  • 交叉平台
  • 无需调用任何外部命令即可更轻松地使用 CMake,应将要安装的包定义在 CMakelists.txt 文件中。
  • 命令行工具
  • 从命令行或 IDE(如 .net 的 Nuget 或 Python 的点)浏览包元数据。
  • 与国际发展部集成
  • 独立不依赖于任何解释器或外部依赖项,或完全挤满了解释器,便于安装和部署

Visual Studio 2022 Preview启动打开项目就崩溃

最近Visual Studio 2022 Preview更新后,启动打开项目后,莫名奇妙的直接崩溃,不显示任何异常提示

排查步骤

  1. 打开两个VisualStudio实例,使用其中一个实例Attach To Process来Debug另外一个实例
  2. 打开项目解决方案
  3. 获取异常崩溃信息

Crashing on start with "System.AccessViolationException"

解决方案是增加环境变量如下:

  • VSDebug_DisableManagedReturnValue=1

[博客]博客主题

为避免将来的博客内容杂乱无章,首先限定博客的主题**,以将编程、生活日记、工具wiki等分离拆开。

本博客主要记录编程相关的内容,比如:

  • 有趣的小实验
  • 小项目
  • 优美的算法
  • 数学等

这将是未来的工作技能名片。

Flag:博客十年

  • A weekly blog post
  • Learn a language or technique quarterly
  • An open source project

Python特性概要与范式

一、Python特性概要

1.1 python是一门解释型语言

python修改文件后,仅需要保存,执行 python demo.py 即可。而C/C++修改文件后,需要执行 gcc demo.c -o demo 编译生成方可

print "hello world!"

1.2. 特性总结:字节码、动态语义、缩进

我们来分析下python的执行流程,以 python demo.py 为例:

  • python先将脚本文件编译成字节码(pyc,pyo)
  • python虚拟机解释并运行字节码文件

而编译型语言会先将代码文件编译成机器码,生成可执行文件;所以脚本解释型语言速度会慢一点。

不同于C/C++的强类型,python支持赋值时确定数据类型的动态语义。这种方式更加符合人类的直观思维,所见即所得(WYSIWYG原来也可以在语言中体现)。

num = 4
print num

python中以缩进来决定语句的层次关系和逻辑块。

a = 1
b = 2
if a == b
    print 'hello world!'

二、Python代码范式

python具体而分,包含模块、语句、表达式、和对象构成。程序由模块组成,模块包含语句,语句包含表达式,表达式建立并处理对象。

# regex:coding[:=]\s*([-\w.]+)
#coding=utf-8

"标准模块脚本的写作范式,作为脚本的文档"

# 引入模块
import time
print time.__doc__

# 单行注释
desc = "全局变量"    #后备注释

def hello():
    """
    多行注释
    """
   return "hello world"

# main
if __name__ == "__main__":
    print hello()

2.1 变量命名

类似C++,不能包含python的关键字;大小写敏感

2.2 赋值

  • 动态特性:a = "a"
  • 多重赋值: a,b,c = "a", "b", 6
  • 删除:del a,b,c

2.3 万能钥匙

python内置万能钥匙,这些彩蛋可以方便的让我们去学习和掌握了解各个模块。

  • type:
  • dir:返回对象的所有属性列表
  • help:PyDoc

如何保护前端JS代码?

Web前端JS代码需要保护吗?

这得具体情况具体分析。
1、如果只是写一段web页面图片轮播,或是跑马灯效果等等之类简单的功能。那不需要保护。
2、如果是精心设计一个绚丽的特效,如果想要保护这段自己付诸幸苦实现的特效代码不被他人随意拿去使用,那应该保护这段JS代码!
3、如果页面上有重要的功能是用JS代码管控的,比如交易逻辑、帐号密码信息、个人隐私、甚至有与远程服务器或数据库的通信等等,那么相关的js代码非常应该被保护、应该做JS代码防盗保护!
否则可能引起被黑客分析、攻击等严重问题。安全相关的事情,从来都要防患于未然、不可心存侥幸。除非它对你毫不重要。

如何保护Web前端JS代码?

1、打包&压缩
有人认为打包、压缩就是对JS代码的保护。确实,打包在一定程度上可以起码些许保护作用,好像看起来是如此。但打包、压缩的目的并不是为了保护JS代码,而是为了使用方便、减小代码体积,方便使用、便于传输。比如模块化的编程可能产生200个JS文件,如果使用时逐一用“script src”进行引用……这是种折磨,不管是对于代发人员,还是网络加载(浏览器也会生气!x_x)。
类似Webpacket、Gulp进行打包,可以将这些多个JS合成到一个文件,并且可能会进行回车、换行、空格的删除,以实现代码压缩,也有一些简单混淆操作:把长变量名改为统一风格的短变量名等。然后,最终生成的一个文件。代码总量减小了、可读性差了、使用方便了。同时让有些人认为这也实现了JS代码保护。其实、实际上、当然的代码并没有被保护:可读性依旧,只是代码量大了一些而已,只要稍稍耐心的读代码,会发现,代码依然是很易理解的,没有多少安全性可言。
2、混淆&加密
前端JS代码的保护,必需要混淆和加密共用。
单独的JS源代码加密,是行不通的,更不可能有所谓的JS不可逆加密。因为代码在浏览器端执行时,必须转解密还原成原始代码,才能被浏览器的JS引擎识别和运行。在解密后,会存在完整的原始JS代码。这是非常不安全的存在,有多种方法可将原始的JS代码显示出来。

JS代码混淆被不少开发人员认为是不够高端的JS代码保护方式,听起来不如JS源码加密更具安全性。事实上,混淆也有多个级别。比如比较低端的字符搜索和串替换、随机插入伪僵尸代码、字符串十六进制化等等。而也有高端的手法,会先进行语法分析、词法分析,重建语法树,相当于已经实现了一个JS引擎,在引擎中处理代码,那么,就可以在其中任意一步进行自由度极高的操作,比如在语法树中插入新的语法结构、比如可以将字符串全部提取并进行加密、可以对变量进行整体有规则化的重定义使无意义化等等。这样就可以实现真正的代码重建。这样重建的JS代码安全性,将会有一个质的提升。

当正真的混淆和加密联合使用,可以实现真正的JS代码安全保护。JS混淆中融入JS加密,JS加密中又嵌入JS混淆。这样保护后的代码,即使在客户端执行环境中被逆向还原,得到的也是大量含义不明的函数、代码、字符串。特别重点是:代码已经经过了重建,这时逆向得到的也是分离后的重建的无意义JS代码、大量的僵尸代码、混淆的字符串、不明含意的变量。可读性与原始代码相比……天壤之别。

固执的人或许还会说:没有破解不了的保护方案,只要我认真、用心、用时的分析,还是能分析出原始代码含意的,确实,可能如此。

但是,原本的代码,可能只需要读10分钟,而从这样保护后的JS代码读取原始含意,可能需要……10个月。而这时候,我们的JS代码可能已经更新到下一版了。

JS代码保护的目的已经达到了,不是吗?

Python菜鸟教程

2020年第二季度将开始学习python相关的语法和基础应用,尝试编写一些小demo。

image

毫无疑问,Python 是当下最火的编程语言之一。使用C/C++做开发将近10年,尝试下学习其他语言,来体验不一样的开发体验。

一、Python核心技术

Python的相关核心概念可以提前了解一下,然后在整个学习过程中反复品味和更新。从而掌握一门语言最核心的通用原理,万般皆通。

二、Python教程

Python系列学习将从如下几个方面进行学习和讲解:

  1. Python特性概要与范式
  2. Python基本数据类型
  3. Python数据结构
  4. Python面向对象编程OOP
  5. Python算法概要

三、Python实战

  1. Python编程实战
  2. Python框架剖析

四、总结

前端教程

HTML

超文本标记语言

超文本:使用html编写的文档,文件格式.html

HTML文档结构

  • 文档类型

  • 文档根元素

    • 文档头部

    • 文档主题

HTML元素,标签与属性

  • 双标签与单标签

  • 标题:h1~h6

  • 段落:p

  • 链接:a

  • 图像:img

  • 列表:ul+li,ol+li

  • 表格:table+thead+tbody+tr+td

  • 表单:form+label+input+button

  • 框架:iframe

  • 通用:div/span

HTML5语义化标签

  • 容器:header/nav/main/article/section/footer

  • header

  • nav

  • main

    • article

    • section

    • aside

  • footer

CSS层叠样式表

设置HTML元素在文档中的布局和显示方式(外观)

  • 元素属性: style="color: red"

  • 元素标签:<style>p{color:red;} </style>

  • 外部资源:

CSS基本语法

样式规则:

  • 选择器:标签选择器、类选择器

  • 样式声明

样式选择器:

  • id选择器:id是唯一的,#id

  • class选择器:.class

  • 标签选择器:p

  • 优先级:标签<class<id<javascript

盒子模型

一切皆盒子,一切布局的基础,页面上的一切可见元素均可看作盒子。

盒子默认都是块级元素:独占一行,支持宽高设置。

盒子模型可以设置六个样式:宽高、背景、内外边距、边框

  • width:水平方向

  • height:垂直方向

  • background-color:背景,默认透明

  • margin:外边距,当前盒子与其他盒子之间的位置与关系,默认透明,只有宽高属性

  • border:边框,位于内外边距之间,是可见元素,不透明,除宽度外,还可以设置样式与前景色

  • padding:内边距,内容与边框之间的填充区域,默认透明,只有宽高属性

其中margin与padding的设置,属性按顺时针上右下左排列,可设方式为:上-右-下-左,上-左右-下,上下-左右,上下左右。

根据可见性可以分为两类:

  • 可见:width,height,border

  • 透明:background,padding,margin

样式选择器

  • id选择器:#id{...}

  • class选择器:.class{...}

  • 标签选择器:标签{}

  • 属性选择器:选择器[id=""]{...}

  • 群组选择器:地位平等,同时设置多个选择器样式;选择器1,选择器2 {...}

  • 相邻选择器:选择器+* {...},加号标识选中相邻,*作为通配符,命中相邻的一个

  • 兄弟选择器:相邻兄弟选择器,平级的所有元素;选择器~ * {....}

  • 伪类选择器:使用冒号

    • 子元素选择器:根据位置进行选择;选择器 空格 : first-child/last-child/nth-child/nth-last-child(元素索引,从1开始) {...}, 空格也可以改为具体的选择器;

    • 类型选择器:选择器 类型: first-of-type/last-of-type/nth-of-type {...},只要选中类型,就要使用带有type的

    • 关注点是位数,用nth-child

    • 既关注位置,又关注类型,用nth-of-type

背景设置

  • background-color:背景色,覆盖边框区域

  • background-image:背景图,url(image-path),默认会水平垂直重复

  • background-repeat:重复方向,no-repeat(不重复),repeat-x(水平重复),repeat-y

  • background-position:背景定位,left center,30px 50px,10% 20%

  • background-attachment:滚动方式,scroll,fixed(固定)

  • background-img:多背景图设置,css3新增

  • background-size:设置背景图尺寸,css3新增

  • background-clip:设置背景图绘制区域,css3新增

  • background:简写,顺序为:背景色 背景图 重复方向 背景定位 滚动方式

布局

调皮的内边距:padding

  • padding会撑大盒子

  • 宽度分离:增加中间层

  • box-sizing:计算盒子宽高计算方式,改为边框+padding;box-sizing: border-box;content-box则恢复默认计算方式

琢磨不定的外边距:margin

  • 同级塌陷:垂直方向同级排列时,上边盒子margin-bottom与下边盒子margin-top会重叠

  • 嵌套传递:子元素的margin会向父元素传递;子元素的margin转为父元素的padding来解决

  • 自动挤压:margin-left: auto,自动定位到右侧

浮动元素:float

浏览器交出页面布局的权限,交给用户,即将元素从文档流中脱离出来。脱离后必然是一个块,可以设置宽高等。

流动布局:又称文档流/标准流,元素排列顺序与元素在html文档中的编写顺序一致的,从上到下,从左往右

脱离文档流的手段:

  • 浮动:将元素在水平方向上自由移动,垂直仍在文档流中

    • float:left,right;clear:both/left/right

    • 浮动元素:让元素脱离文档流,在垂直方向仍然在文档流中,在水平方向可以向左右两边自由浮动

    • 浮动元素对前面的元素无影响,只会影响后面的元素

    • 浮动元素之间可以理解为在水平方向组成了一个文档流,按照规则进行排列

    • 子元素(区块)与父级包含块(区块):

      • 子元素浮动,父区块塌陷,高度丢失:浮动后脱离文档流,父区块无法再包裹住子元素

        • 给父区块设置高度:可以但是pass,太不灵活

        • 父区块跟着一块浮动:pass,需要很多级别均设置float

        • 子元素同级增加一个元素:设置 clear: both父元素增加一个overflow,专用来清浮动:overflow:hidden

        • 伪元素:老教材中有,pass

      • 清空浮动以overflow来处理

  • 定位:将元素在页面重新排列

    • 静态定位:static,文档流定位,流动布局

    • 相对定位:relative,元素仍在文档流

      • 只是相对它原来的位置发生偏移

      • {position: relative; left:30px;right:20px}

      • 后边元素依旧认为它在原来位置,然后进行后续布局

    • 绝对定位:absolute,元素脱离文档流

      • 参照物:相对于离他最近的,具有定位属性的父级元素进行定位;循环后的最顶级的参照物为body

      • {position: absolute; left:0;right:0}

    • 固定定位:fixed,不在文档流中

      • 参照物:始终相对于浏览器窗口进行定位,body/html

布局常识

网页拆分为上中下(通用头部/底部+内容区),然后内容区拆分为左中右。

  • 头部:链接+导航

  • 底部:

主体的布局,常用方式是双飞翼布局、圣杯布局。

  • 双飞翼布局:左右固定,中间自适应;优先展示中间,以提高渲染优先级

    • DOM结构

      • 主体:放到wrap容器中实现宽度分离,以规避padding撑大盒子;默认大小由内容决定

      • 左侧

      • 右侧

  • 圣杯布局:杯体+两个耳朵把手

    • DOM结构

      • 主题:不需要wrap,盒子大小改为边框计算方式{box-sizing: border-box}

      • 左侧

      • 右侧

HTML中的表格: 表格布局

原生表格的DOM结构如下:

  • table:表格

  • caption:标题

  • thead:表头,tr+th

  • tbody:主体,tr+td

  • tfoot:页脚,tr+td

  • rowspan/colspan:合并单元格

CSS控制表格的样式:

  • box-shadow:阴影

  • tfoot尽量直接写在tbody中

  • hack方式的伪元素:通过css向页面添加元素

    • table:after {}

    • table:before {}

表格布局=>定位(position)=>浮动(float)=>弹性(flex)=>网格(Grid)

Flex布局

布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。

  • Flex 是 Flexible Box的缩写, 意思是"弹性布局", 用来为盒状模型提供最大的布局灵活性

  • 任何一个容器都可以设置为Flex布局模式

/* 块元素可以设置为Flex容器 */
.container {
 display: flex;
}

/*内联元素也可以设置为Flex*/
span {
 display: inline-flex;
}

/* WebKit内核浏览器,如Safari, 需要加前缀*/
div {
 display: -webkit-flex; /*Safari*/
 display: lfex;
}
  • 一旦设置为Flex容器, 则容器内子元素的float, clear, vertical-align属性全部失去意义, 没有效果了
  • 由此可见, Flex布局的目标明确, 就是要把Float布局拍死在沙滩上

Flex基本概念

  • Flex容器:简称容器,采用flex布局的元素

  • Flex项目:简称项目

    • Flex容器中的所有成员(子元素)会自动成为该容器的成员,称为flex项目

    • flex项目都支持宽高设置, 哪怕它之前是内联元素,类似于浮动元素

  • 主轴:水平轴,横轴,x轴

    • main start: 起始位置

    • main end: 结束位置

    • main size: 单个项目占据的主轴空间

  • 交叉轴: 垂直轴,坚轴,y轴

    • cross start: 起始位置
    • cross end: 结束位置
    • cross size: 单个项目占据的交叉轴空间

Flex容器

  • flex-direction:主轴方向,row | row-reverse | column | column-reverse
  • flex-wrap:换行,nowrap | wrap | wrap-reverse
  • flex-flow:direction和wrap的简写,[flex-direction] || [flex-wrap]
  • justify-content:项目在主轴上对齐方式,flex-start | flex-end | center | space-between | space-around
  • align-items:项目在交叉轴上的对齐方式,flex-start | flex-end | center | baseline | stretch
  • align-content:项目在多根轴线上的对齐方式,只有一根轴线无效,用于让每一个单行的容器居中,而不是让整个容器居中;只适用于多行的flex容器,且交叉轴上有多余的空间使得flex容器内的flex项目对齐

Flex项目

  • order:定义项目排列顺序,索引越小越靠前,默认为0

  • flex-grow:定义项目的放大比例,默认为0不放大,即有剩余空间时也不放大项目

  • flex-shrink:项目缩小比例,默认为1,即如果空间不足,则自动缩小项目来填充

  • flex-basis:定义项目在主轴占据的空间,默认为auto,即项目原始大小

  • flex:是flex-grow,flex-shrink,flex-basis的简写,默认为0 1 auto,后两个属性可选

  • align-self:个性化定制某个项目的对齐方式,可以覆盖容器的aligin-items属性,默认为auto

Grid布局:网格布局

Grid是第一个专门为解决布局问题而生的CSS模块

  • 简单直观,具备表格布局和弹性布局的所有优点
  • Grid是CSS中最强大的布局系统
  • Grid是一个二维系统,可以同时处理行和列,而flexbox主要是一维系统
  • 可以将规则应用于父元素成为网格容器,子元素成为网格元素
  • 网格布局时一个二维的基于网格的布局系统,目的在于设计基于网格的用户界面方式
  • 与flexbox类似,子元素的原始顺序不重要,可以在css中以任意顺序放置他们,使得媒体查询重新排列网格变得非常容易

Grid布局的基础知识

  • 布局从table转向浮动、定位以及inline-block,但是本质上都是Hack的方式,遗漏了很多重要的功能,例如垂直居中;flexbox在一定程度上解决了部分问题,但是它的目的是为了更简单的一维布局,而不是复杂的二维布局。flex和grid在一起将工作的更好
display: grid
  • grid-template-columns/grid-template-rows:设置行列大小

  • grid-column/grid-row:将它的子元素放入网格

  • Grid Container:网格容器,设置了display:grid的元素,是所有grid item的直接父项

  • Grid Item:网格元素,容器的直接子元素,间接的子元素不是哦

  • Grid Line:网格线,分界线组成网格结构,可以是垂直的,也可以是水平的,并位于列或行的任意一侧

  • Grid Track:网格轨道,两个相邻网格线之间的空间,可以想象成网格的列或者行

  • Grid Cell:网格单元,两个相邻的行和相邻的列网格线之间的空间,是网格的一个单元

  • Grid Area:网格区域,四个网格线包围的总空间,网格区域可以由任意数量的网格单元组成

Grid容器

  • display:将元素定义为grid容器,并为其内容建立新的网格格式化上下文

    • grid:生成一个块级网格

    • inline-grid:生成一个行级网格

    • subgrid:如果容器本身就说一个grid项目(即嵌套网络),可以使用这个属性表示想从父节点获取它的行/列大小,而不是指定自己的大小

    • column/float/clear/vertical-align对容器没有影响

  • grid-template-columns/grid-template-rows:使用以空格分隔的多个值来定义网格的行和列,表示轨道的大小,空格代表网格线

  • grid-template-areas:通过引用grid-area属性指定的网格区域的名称来定义网格模板,重复网格区域的名称导致内容扩展到这些单元格。点号代表一个空单元格,提供了网格结构的可视化

  • grid-template:定义了grid-template-rows/columns/areas的简写

  • grid-column-gap/grid-row-gap:指定网格线的大小,即设置列/行之间的间距宽度

  • grid-gap:grid-row-gap和grid-column-gap的缩写

  • justify-items:沿着横轴对齐网格内的元素

  • align-items:沿着纵轴对齐

  • justify-content:网格的总大小可能小于容器的大小,如果所有项目都使用像素值这样的非弹性单位设置大小,则可能出现此种情况。此时可以设置网格容器内的网格的对齐方式,沿着横轴对齐网格

  • aligin-content:同上,验证纵轴对齐网格

  • grid-auto-columns/grid-auto-rows:指定自动生成的网格轨道(即隐式网格轨道)的大小。

  • grid-auto-flow:如果你存在没有显示指明放置在网格上的 grid item,则自动放置算法会自动放置这些项目。 而该属性则用于控制自动布局算法的工作方式。

  • grid:在单个属性中设置所有以下属性的简写:grid-template-rows,grid-template-columns,grid-template-areas,grid-auto-rows,grid-auto-columns和grid-auto-flow。 它同时也将 sets grid-column-gap 和 grid-row-gap 设置为它们的初始值,即使它们不能被此属性显示设置。

Grid Items

  • grid-column-start/grid-column-end/grid-row-start/grid-row-end:指定元素网格线的起始/终止位置

  • grid-column/grid-row:上述属性的简写

  • grid-area:给网格进行命名,也可以作为grid-row-start + grid-column-start + grid-row-end + grid-column-end 的简写形式

  • justify-self:沿着行轴对齐grid item 里的内容(与之对应的是 align-self, 即沿列轴对齐)。 此属性对单个网格项内的内容生效。

  • align-self:沿着列轴对齐grid item 里的内容(与之对应的是 justify-self, 即沿行轴对齐)。 此属性对单个网格项内的内容生效。

经典的三列布局

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
      <link rel="stylesheet" href="style.css">
    <title>简单的三列布局</title>
</head>
<body>
<header class="header">头部</header>
<aside class="left">左边栏</aside>
<main class="content">主体内容区</main>
<aside class="right">右边栏</aside>
<footer class="footer">底部</footer>
</body>
</html>
/* style.css */
body {
  /*设置body元素采用网格布局*/
  display: grid;

  /*行模板: 第一行60px,第二行750px, 第三行60px*/
  grid-template-rows: 60px 750px 60px;

  /*列模板: 第一列200px,第2列自动扩展, 第3列200px*/
  grid-template-columns: 200px auto 200px;

  /*设置行间距:10px*/
  grid-row-gap: 10px;

  /*设置列间距: 10px*/
  grid-column-gap: 10px;
}

.header {
  /*网格区域命名*/
  grid-area: my-header;

  /*参考背景色*/
  background-color: lightgreen;
}
.footer {
  /*网格区域命名*/
  grid-area: my-footer;

  /*参考背景色*/
  background-color: lightgreen;
}

.left {
  /*网格区域命名*/
  grid-area: my-left;

  /*参考背景色*/
  background-color: lightblue;
}

.right {
  /*网格区域命名*/
  grid-area: my-right;

  /*参考背景色*/
  background-color: lightblue;
}

.content {
  /*网格区域命名*/
  grid-area: my-content;

  /*参考背景色*/
  background-color: coral;
}


/*设置网格区域: 非常直观*/
body {
  /*网格区域名称相同, 意味着合并*/
  grid-template-areas:
    "my-header  my-header   my-header"
    "my-left    my-content    my-right"
    "my-footer  my-footer   my-footer"
}

媒体查询

@media (max-width: 1050px) {

}

DMCA

Copyright claims(DMCA)

Submit a DMCA takedown for any pages.

Please commit a comment to submit a DMCA takedown notice. We will receive emails during office hours and delete the information on your submission request. You also can send an mail to [email protected] for more help.

Thanks to everyone in the open source community for giving us so much knowledge and learning opportunities.

Thank you very much!

Yours sincerely, Ant Team.

版权举报

版权报告, 请在这里提交DMCA删除要求。

请在下方留言提交一个DMCA版权举报通知,我们将时刻接收您的举报邮件,并尽快到达办公室后第一时间删除您举报的信息。您也可以发邮件到[email protected]通知删除。

感谢开源社区的每一个人,给了我们如此丰富多彩的知识,提供了如此有价值的学习机会。

非常感谢~~~

此致,蚂蚁团队。

鸣谢!

libuv简述

libuv简述

Introduction

libuv是一个高性能的事件驱动的I/O库,提供了跨平台的API。libuv是node.js的官方御用库,拥有卓越的系统编程性能,已被更多的如Rust语言等所使用。libuv提供了一个跨平台的抽象,实现不同平台的内核事件通知机制,如FreeBSD-kqueue、Linux-(e)poll、Windows-IOCP。

libuv使用异步的事件驱动编程风格,核心是提供一个event-loop,以及基于I/O和其他事件的通知回调函数。同时提供了一些核心工具,如定时器、非阻塞的网络支持、异步文件系统、子进程等。

异步编程

异步编程在现代编程语言中越来越占主流地位,例如界面编程通过异步来实现非阻塞的交互体验,网络请求的异步处理等。最直观的理解方式就是观察者模式,我们关注每个事件,并对事件做出反应。libuv负责将来自系统的事件收集起来,或者监视其他来源的事件,用户注册相关的事件回调后就可以在事件发生时收到回调。

系统编程中最经常处理的一般是输入和输出,而不是一大堆的数据处理。传统的输入/输出函数(例如read,fprintf)都是阻塞式的,一个read操作返回前程序什么也做不了。例如文件写入数据,网络读取数据等,这些操作耗费的时间相比于cpu的处理时间差得太多。对于高性能的的程序来说,将无法忍受。

一个标准的解决方案是多线程,每一个阻塞的I/O操作都被分配到各个子线程中(或者是使用线程池),当某个线程一旦阻塞,处理器就可以调度处理其他需要cpu资源的线程。

libuv使用异步非阻塞的方案,大多数现代的操作系统都提供了基于事件通知的子系统。例如一个socket的read调用会发生阻塞,但是我们可以请求系统监视socket事件的到来,并将事件放到事件队列中,这样通过程序检查事件来及时获取数据并回调。异步是相对于时间或者空间来讲的,非阻塞的方式下我们无需等待数据瞬间返回而自由的处理我们的其他事情。例如我们使用微信,发给A一个消息后可以看个新闻,A回复消息后会提醒我们,我们再来查看。

#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

int main() {
  auto loop = malloc(sizeof(uv_loop_t));
  uv_loop_init(loop);
  uv_run(loop, UV_RUN_DEFAULT);
  uv_loop_close(loop);
  free(loop);
}
  1. 错误处理

同步阻塞式编程,在执行返回后可以返回代表结果的错误码(成功为0我们也统称为错误码的一种)。但是对于异步执行,会在执行失败后,给回调函数传递一个状态参数。libuv的错误码信息被定义为UV_E常量。通常小于0代表出现了错误,但是UV_EOF指示读取到文件等的末端,需要特殊处理。

  1. Handles & Requests

libuv工作在我们监听了相关的特定事件,通常需要创建对应的I/O设备、定时器、进程等handle来实现。handle是不透明的数据结构,通过其uv_type_t的type来指定handle的使用目的。

初始化handle:

uv_TYPE_init(uv_loop_t *, uv_TYPE_t *)
/* Handle types. */
typedef struct uv_loop_s uv_loop_t;
typedef struct uv_handle_s uv_handle_t;
typedef struct uv_stream_s uv_stream_t;
typedef struct uv_tcp_s uv_tcp_t;
typedef struct uv_udp_s uv_udp_t;
typedef struct uv_pipe_s uv_pipe_t;
typedef struct uv_tty_s uv_tty_t;
typedef struct uv_poll_s uv_poll_t;
typedef struct uv_timer_s uv_timer_t;
typedef struct uv_prepare_s uv_prepare_t;
typedef struct uv_check_s uv_check_t;
typedef struct uv_idle_s uv_idle_t;
typedef struct uv_async_s uv_async_t;
typedef struct uv_process_s uv_process_t;
typedef struct uv_fs_event_s uv_fs_event_t;
typedef struct uv_fs_poll_s uv_fs_poll_t;
typedef struct uv_signal_s uv_signal_t;

在异步操作中,handle上有许多与之关联的request,request是短暂性的对象,通常只维持在一个回调函数的时间,对应着handle上的一个IO操作。request用来在初始函数和回调函数直接进行上下文的传递,例如uv_udp_t代表一个udp的socket,对每一个向socket的写入操作,都会新建和回调一个uv_udp_send_t。

/* Request types. */
typedef struct uv_req_s uv_req_t;
typedef struct uv_getaddrinfo_s uv_getaddrinfo_t;
typedef struct uv_getnameinfo_s uv_getnameinfo_t;
typedef struct uv_shutdown_s uv_shutdown_t;
typedef struct uv_write_s uv_write_t;
typedef struct uv_connect_s uv_connect_t;
typedef struct uv_udp_send_s uv_udp_send_t;
typedef struct uv_fs_s uv_fs_t;
typedef struct uv_work_s uv_work_t;

我们来思考下,为何要创建多个request? 是的,同一个request的话我们需要保证request的读写是线程安全的、原子安全的等等等。在使用asio的过程中,我尝试过使用一个request,但是我是将所有数据处理完成后才返回的,并保证了序列性从而保证request数据的安全性。对于libuv这样一个通用性的库,显而易见需要更加灵活的方式来返回数据,所以就需要多个request来保证数据的安全。

  1. IDLING:空转的handle
#include <stdio.h>
#include <uv.h>

int64_t counter = 0;

void wait_for_a_while(uv_idle_t* handle) {
    counter++;

    if (counter >= 10e6)
        uv_idle_stop(handle);
}

int main() {
    uv_idle_t idler;
    uv_idle_init(uv_default_loop(), &idler);
    uv_idle_start(&idler, wait_for_a_while);
    uv_run(uv_default_loop(), UV_RUN_DEFAULT);
    uv_loop_close(uv_default_loop());
}
  1. Storing context

基于回调函数的异步编程,我们需要在调用处和回调函数之间传递一些上下文等特定的信息。所有的handle和request都有一个data域,用来存储信息并传递。uv_loop_t也有一个相似的data域。

传递一个上下文特定信息,这是一个c语言库中很常见的模式。想象一下如何实现C和C++的对象直接的互调操作? 最简单的是把C++的对象当作上下文,在所有的相关操作中附加带上。

文件系统

  1. 异步文件I/O

文件读写可以通过uv_fs_*函数族和结构体完成,在线程池中调用系统的阻塞函数,在程序交互时通知在事件循环中注册的监视器。如果监视器callback为null,则自动执行阻塞同步。open和close是一次性执行的采用了同步处理,对任务和多路I/O的快速I/O采用异步来提升性能。

  • uv_fs_open:
  • uv_fs_close:关闭文件描述符,同步执行
  • callback:void ()(uv_fs_t req),文件读取到末尾EOF时,req->result为0
  • uv_fs_read:读取函数,必须传递一个已经初始化的缓冲区;回调被触发时,缓冲区被写入数据
  • uv_fs_write:
  • uv_fs_req_cleanup:在文件系统操作结束后必须要被调用,用来回收在读写中分配的内存

由于文件系统和磁盘的调度策略,写入成功的数据并不一定在磁盘上。

关于更多的文件操作API,可以查阅官方文档,这里就不再详细赘述,使用方式都是类似的。

  1. 数据流

讲到文件,就不得不提流。熟悉Asio的同学,可能对其中的流处理比较印象深刻。在libuv中,最基础的I/O操作是流uv_stream_t。TCP套接字、UDP套接字、管道对文件I/O和IPC来讲都可以看作是stream的子类。

  • uv_read_start:一旦调用,libuv会保持持续读取数据,直到stop被调用
  • uv_read_stop
  • uv_write:

数据的离散单元式uv_buffer_t,包含了指向数据的开始地址指针和长度,我们需要管理的式实际数据,需要自己分配和回收内存。类似asio的stream需要我们附加到自己的string上。nodejs使用自己的内存分配Smalloc,将buffer与v8的对象关联起来。

  1. 事件

现代操作系统会提供相关的API来监视文件夹、文件的变化,例如inotify-linux、fsevents-darwin、kqueue-bsd、ReadDirectoryChanges-windows、eventPorts-solaris等。libuv包括了类似的文件监视库:uv_fs_event_start。具体可以参考API文档。

网络

libuv的网络编程接口比BSD的socket便捷很多,因为都是非阻塞的,且原理都是一样的。libuv提供了覆盖了烦人啰嗦的底层任务的抽象函数。

  1. TCP: 面向连接的字节流协议

libuv基于stream实现tcp:

  • server:uv_tcp_init建立tcp句柄,uv_tcp_bind绑定,uv_listen建立监听,新连接到来时激活回调函数,uv_accept接收连接,然后使用stream处理与客户端通信
  • client:uv_tcp_connect即可,在回调函数中处理数据
  1. UDP:用户数据报协议,无连接的不可靠网络通信协议

libuv未提供stream形式的实现,而是提供了一个uv_udp_t句柄接收和uv_udp_send_t句柄发送。

  1. DNS

libuv提供了一个异步的DNS解决方案,提供自己的uv_getaddrinfo,在回调函数中可以像使用正常的socket操作一样。

  1. 网络接口信息

uv_interface_addresses可以获得系统的网络接口信息,在服务器准备绑定IP地址时很有用,可以查看哪些端口是否被占用等。

线程

虽然做web编程,了解事件循环的方法即可。但是在nodejs实现前后端的统一后,我们还是有必要了解下处理器完成任务的单元:线程。线程更多是在内部使用,用来在执行系统调用时伪造异步的假象。libuv使用线程使得程序可以异步的执行一个阻塞的任务,使用线程池来保证大量阻塞API的调用。

libuv的线程API与pthread-POSIX API使用方法和语义上近似,在不同的系统平台上由于句法和语义表现都不太相似,libuv支持了有限数量的线程API。

只有一个主线程,主线程只有一个event-loop。不会有其他与主线程交互的线程,除非使用uv_async_send。

  • uv_thread_create:开始一个线程
  • uv_thread_join:等待线程结束
  1. mutex

libuv的互斥量与pthread存在一一映射。递归调用互斥量函数在某些系统平台上支持,但是BSD上会报错,例如:

uv_mutex_lock(mut);
uv_thread_create(threadid, entry, (void*)mut);
uv_mutex_lock(mut);

可以用来等待其他线程初始化一些变量然后释放mut锁。

  1. Lock

读写锁是更细粒度的实现机制,两个线程可以同时从共享区中读取数据。以读模式占用读写锁时,无法再以写模式拥有。以写模式占用锁时,其他读写都不可再拥有。

进程

libuv提供了很多的子进程管理函数,跨平台支持stream/pipe完成进程间通信。在unix中的共识是一个进程只做一件事情并做到最好。因此进程通常通过创建子进程来完成不同的任务。一个多进程通过消息通信的模型,比多线程共享内存的模式会比较容易理解很多。

spawning child processes:uv_spawn,进程的命令行参数argv按照惯例,要比实际参数多一个,最后一个设置为NULL。

更多进程相关的内容,请搜索相关的文章来学习,这里只简单提一下。在真正需要时再行查找即可。

事件循环

libuv提供了非常多的控制event-loop的方法,你能通过使用多loop来实现很多有趣的功能。你还可以将libuv的event loop嵌入到其它基于event-loop的库中。比如,想象着一个基于Qt的UI,然后Qt的event-loop是由libuv驱动的,做着加强级的系统任务。

  • uv_stop:关闭一个event-loop

工具类

  1. Timers:libuv的定时器可以设定为定时多次执行
uv_timer_t timer_req;
uv_timer_init(loop, &timer_req);
uv_timer_start(&timer_req, callback, 5000, 2000);

我们可以使用uv_timer_stop(&timer_req)来停止定时器,且在回调函数中可以安全使用。

  1. Idler pattern:空转回调会在每一次的event-loop循环激发一次

可以用于执行一些优先级低的任务,例如可以向开发者发送程序的性能情况,以便于分析。

  1. passing data to worker thread

在使用uv_queue_work的时候,你通常需要给工作线程传递复杂的数据。解决方案是自定义struct,然后使用uv_work_t.data指向它。一个稍微的不同是必须让uv_work_t作为这个自定义struct的成员之一(把这叫做接力棒)。这么做就可以使得,同时回收数据和uv_wortk_t。

  1. External I/O with polling

通常在使用第三方库的时候,需要应对他们自己的IO,还有保持监视他们的socket和内部文件。在此情形下,不可能使用标准的IO流操作,但第三方库仍然能整合进event-loop中。所有这些需要的就是,第三方库就必须允许你访问它的底层文件描述符,并且提供可以处理有用户定义的细微任务的函数。但是一些第三库并不允许你这么做,他们只提供了一个标准的阻塞IO函数,此函数会完成所有的工作并返回。在event-loop的线程直接使用它们是不明智的,而是应该使用libuv的工作线程。当然,这也意味着失去了对第三方库的颗粒化控制。

libuv的uv_poll简单地监视了使用了操作系统的监控机制的文件描述符。从某方面说,libuv实现的所有的IO操作,的背后均有uv_poll的支持。无论操作系统何时监视到文件描述符的改变,libuv都会调用响应的回调函数。

  1. Loading libraries

libuv提供了一个跨平台的API来加载共享库,用来实现插件、扩展、模块系统。

  1. TTY

文字终端长期支持非常标准化的控制序列。它经常被用来增强终端输出的可读性。例如grep --colour。libuv提供了跨平台的,uv_tty_t抽象(stream)和相关的处理ANSI escape codes 的函数。这也就是说,libuv同样在Windows上实现了对等的ANSI codes,并且提供了获取终端信息的函数。

未完待续......

Win32 C++项目移植到UWP

概述

场景:将现有Win32平台Dll移植到UWP平台,供采用C#开发的Win Phone APP使用,而该DLL还依赖其他C++静态、动态库。

在Visual Studio中新建项目,模板选择Visual C++/Windows/通用页面,包含如下几个工程类型:

  • 空白应用(通用 Windows)
  • DLL(通用 Windwos)
  • 静态库(通用 Windows)
  • Windows 运行时组件(通用 Windows)

其中Dll和静态库可以被空白应用和运行时组件使用,并且是语言相关的,不能跨语言调用。运行时组件可以被空白应用使用,与语言无关,不管是C++还是C#应用均可调用。我们可以通过如下步骤来实现Win32到UWP的移植:

  1. 工程转换
  2. 编译问题
  3. 磁盘操作
  4. 数据类型转换
  5. 接口封装处理

请下载Universal Windows Platform (UWP) app samples,参考微软官方方案的示例。

一、工程转换

win32工程依赖关系为:app 依赖a.dll,a.dll 链接c++.lib,a.dll 依赖c.dll。

整体的工程关系转换为:a.dll -> a_rt.lib,c++.lib -> c++_rt.lib,c.dll -> c_rt.lib。其中通用Window版组件加_rt以作区分,Windows 运行时组件(通用 Windows)外壳定义为 shell_rt.dll。

新的依赖关系:app 依赖shell_rt.dll,shell_rt.dll 链接a_rt.lib、c++_rt.lib、c_rt.lib, 并且shell_rt.dll 负责重新封装a.dll的接口。app 可由 C++ 或 C# 开发。

注意: 创建 Windows 运行时组件(通用 Windows) 工程时,必须保证工程内的最外层命名空间名字和最终生成的dll名字(包括winmd文件)完全一致,这也是官方的要求。

通过阅读 官方文档 得知在不重新创建工程的情况下将现有工程转换为UWP工程的方法,如下:

  1. 打开DLL项目的项目属性

  2. 配置改为所有配置

  3. 打开C/C++常规选项,将使用Windows运行时扩展设置为是(/Zw),这将启用组件扩展(C++/CX);注意此步骤只能用于C++项目,C语言项目需将此设置为否;如果C++项目中包含C文件,可以单独将C文件设置为否

  4. 解决方案资源管理器中,选择项目节点,打开快捷菜单,然后选择重定向SDK版本目标,点击确定

  5. 解决方案资源管理器中,选择项目节点,打开快捷菜单,然后选择卸载项目

  6. 解决方案资源管理器中,选择卸载的项目节点,然后选择编辑项目文件,找到WindowsTargetPlatformVersion元素并将其替换为如下元素,然后关闭vcxproj文件,然后选择重新加载项目

<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<WindowsTargetPlatformVersion>10.0.10156.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.10156.0</WindowsTargetPlatformMinVersion>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>

经过以上步骤处理,项目将被标识为通用Windows项目。

总结来讲,就是修改平台工具集、使用Windows运行扩展、运行库。

二、编译问题

工程转换完成后,需要处理编译问题。由于编译问题各式各样,哪里有问题就改哪里。

例如可能需要增加如下宏定义,以解决环境问题:

#ifdef _M_ARM
    #define WINAPI_FAMILY WINAPI_FAMILY_PHONE_APP
#endif

三、磁盘操作

UWP 不支持fopen,CreateFile此类操作。用来替换的是CreateFile2,用法和CreateFile类似。但该API只能处理特殊目录,例如程序安装目录、图片、文档、视频等。对于磁盘中任意的目录,都没有操作权限。因此,对于期望可操作任意目录文件的需求,只能放弃使用CreateFile2,改用以下UWP组件中的磁盘操作类:

  • Windows::Storage::StorageFile
  • Windows::Storage::StorageFolder
  • Windows::Storage::Streams::IRandomAccessStream

其它相关类请在类名上按F12打开对象浏览器查看。

看过类里的函数之后可以发现大部分函数都有Asyn后缀,带Asyn后缀的函数均为异步函数,Windows不希望UI线程及其它某些线程因为同步调用导致响应迟钝。C++中异步函数的调用方式大致为:

// 使用 task 和以下命名空间中的类时需开启 /ZW 选项,即开启 C++/CX 支持
#include <collection.h>
#include <ppltasks.h>
 
using namespace concurrency;
using namespace Platform;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
 
// 从一个文件对象获取其目录对象
 
void Test(StorageFile ^file)
{
    create_task(file->GetParentAsync()).then([this, file](StorageFolder ^parentFolder)
    {
        if(parentFolder != nullptr)
        {
            // do something
        }
    });
}

线程A调用Test函数,通过create_task创建一个task对象,并将一个lamda函数(位于then()中,[this, file]中声明的变量可在函数中使用)作为委托传递给task对象的then方法,并继续向下执行并退出Test函数。task中的file->GetParentAsyn()操作实际由线程B调用,待函数返回后,再将结果交由线程A执行委托函数。

^ 这个符号读作hat,这里用来声明句柄对象。String ^str;这里的str就是一个String的句柄类型,初始值或无对象指向时为nullptr,释放时可以使用delete str 也可以让作用域控制自动释放。可以简单的理解为类似智能指针。

异步方法虽然可以避免对线程A的阻塞,但实际使用中并不方便。因为,大部分情况下,我们都会为耗时的网络或磁盘操作专门开启线程处理,而不是直接使用UI线程操作。因此如果都使用这种异步方式,在某些场景下,代码会写的很反人类,例如下面这个比较完整的文件读取操作:

// 头文件、命名空间省略,变量判断、异常处理省略
 
void ReadBytesFromFile(String ^strFilePath)
{
    // 根据文件路径获取文件对象;
    create_task(StorageFile::GetFileFromPathAsync(strFilePath)).then([](StorageFile ^file)
    {
        if (file != nullptr)
        {
            // 以读写的方式打开文件;
            create_task(file->OpenAsync(FileAccessMode::ReadWrite)).then([](IRandomAccessStream ^stream)
            {
                if (stream != nullptr)
                {
                    auto buf = ref new Buffer(10); // 读取10个字节;
                    create_task(stream->ReadAsync(buf, 10, InputStreamOptions::None)).then([buf](IBuffer ^buffer)
                    {
                        // buf 和 buffer 中包含读取到的数据;
                    });
                }
            });
        }
    });
}

昔日Win32的一个CreateFile操作,在这里变的无比繁琐。而且,上面传入一个String路径打开文件的方式因为权限文件,并不可行。

在系统中,除几个个别目录(安装目录、图片目录、视频目录等)在app配置权限后可用于直接操作权限外,app是无法直接使用任意字符串路径进行文件操作的。正确的方式应该是:

  1. 使用FolderPicker或FilePicker获取一个StorageFolder或StorageFile对象
  2. 将对象加入到权限列表中 AccessCache::StorageApplicationPermissions::FutureAccessList->Add(file);
  3. 如果多模块间传递的是String类型,此时可以从StorageFolder或StorageFile对象的Path属性获取String类型路径字符串,之后可以使用该路径字符串转换(见数据类型间的转换)为StorageFolder或StorageFile对象,此时权限仍旧有效。

如果需要在某目录下新建文件,则应该使用FolderPicker获取StorageFolder对象,将对象加入权限列表,再使用该StorageFolder对象创建文件。

考虑到在做代码移植时,调整某些线程的同异步模式将会导致原有框架结构变的混乱,因此,出现了下面的用法:

// 将file文件中偏移5开始的10个字符写入到偏移2开始的位置;
auto taskOpen = create_task(file->OpenAsync(FileAccessMode::ReadWrite));
if(taskOpen.wait() == canceled)
    return false;
IRandomAccessStream ^stream = taskOpen.get();
stream->Seek(5);
auto buffer = ref new Buffer(10);
auto taskRead = create_task(stream->ReadAsync(buffer, 10, InputStreamOptions::None));
if(taskRead.wait() == canceled)
    return false;
 
auto data = ref new Array<byte>(buffer->Length);
auto reader = DataReader::FromBuffer(buffer);
reader->ReadBytes(data);
 
stream->Seek(2);
auto taskWrite = create_task(stream->WriteAsync(buffer));
if(taskWrite.wait() == canceled)
    return false;
auto taskFlush = create_task(stream->FlushAsync());
if(taskFlush.wait() == canceled)
    return false;

这种 task.wait() 的调用方式并不能应用到所有线程。参见ppltask.h文件的task_status _Wait();函数及其中的_IsNonBlockingThread函数内部实现。请自行调试实验各类线程调用wait()中的_IsNonBlockingThread函数时的返回情况。
(经过验证,以上这种写文件的写法效率较低,在频繁调用时尤为明显,包括前面列出的对GetFileFromPathAsync 的调用)

四、数据类型转换

Platform::Array<unsigned char> ^UnsignedChar2Array(unsigned char *pBuffer, unsigned int uSize)
{
    return ref new Platform::Array<unsigned char>(pBuffer, 10);
}
 
std::wstring PlatformString2StdWstring(Platform::String ^str)
{
    return std::wstring(str->Data());
}
 
std::string Unicode2Utf8(Platform::String ^str)
{
    std::wstring wstrTemp(str->Data());
 
    std::string strUtf8;
    int iUtf8Len = ::WideCharToMultiByte(CP_UTF8, 0, wstrTemp.c_str(), wstrTemp.length(), NULL, 0, NULL, NULL);
    if (0 == iUtf8Len)
        return "";
 
    char* pBuf = new char[iUtf8Len + 1];
    memset(pBuf, 0, iUtf8Len + 1);
    ::WideCharToMultiByte(CP_UTF8, 0, wstrTemp.c_str(), wstrTemp.length(), pBuf, iUtf8Len, NULL, NULL);
 
    strUtf8 = pBuf;
    delete[] pBuf;
 
    return strUtf8;
}
 
using namespace Windows::Storage::Streams;
IBuffer ^UnsignedChar2Buffer(unsigned char *pBuffer, unsigned int uSize)
{
    DataWriter writer;
    writer.WriteBytes(Platform::ArrayReference<uint8>(pBuffer, uSize));
    return writer.DetachBuffer();
}
 
void Buffer2UnsignedChar(IBuffer ^buffer, unsigned char **pBuffer, unsigned int *uSize)
{
    DataReader ^reader = DataReader::FromBuffer(buffer);
    *uSize = buffer->Length;
    *pBuffer = new uint8[*uSize];
    reader->ReadBytes(Platform::ArrayReference<uint8>(*pBuffer, *uSize));
}

五、接口封装处理

UWP 组件的接口不同于Win32 DLL 的导出接口,UWP 的接口是一个winmd 文件,包含语言无关类型信息MetaData(元数据)。使用组件时只需要 xxx.dll 和 xxx.winmd 两个文件,不需要头文件。

在导出接口时,首先需要最外层有一个和库文件名相同的命名空间名,导出的类需要声明成如下格式(需带public ref sealed声明 ):

namespace test // 组件名为test.dll
{
    public ref class CInterface sealed
    {
    }
}

因为接口可能被跨语言使用,因此下面这种接口参数的写法就要避免:

void Func(Platform::String ^*pStr);

这种写法只能被C++使用,如果C#调用的话,会出现崩溃。不过,Platform::Array^ *这种写法倒是没有问题。int、int *诸如此类,都是可以的,int *对于C# 的调用,使用out进行修饰。

// C++方式的接口导出函数声明
void Func1(int *pParam);
void Func2(const Platform::Array<unsigned char>^ inArray);
void Func3(Platform::Array<unsigned char>^ *outArray);
 
  
// C#看到的接口声明
// void Func1(out int pParam);
// void Func2(byte[] inArray);
// void Func3(out byte[] outArray);
 
// C#方式的接口调用
Int32 param = 0;
Func1(out param);
 
byte[] inArray;
Func2(inArray);
 
byte[] outArray;
Func3(out outArray);

如果接口需要传递回调函数,需要封装成类,可以从接口导出一个interface 修饰的类:

namespace test
{
    public interface ICallback
    {
    public:
        virtual void func() = 0;
    }
     
    public ref class CInterface sealed
    {
        void RegCallback(ICallback ^callback)
        {
            // 对于callback我做了一层回调封装映射,由此处的ICallback ^ 类型与内部原有的C++ 回调形成映射关系(中间过渡)
            // 避免C++/CX 语法深入内部
        }
    }
}
 
// C# 使用时
class CCallback : test.ICallback
{
    public void func()
    {
        // do something
    }
}
 
CCallback callback = new CCallback();
test.CInterface inter = new test.CInterface();
inter.RegCallback(callback);

六、调研与实战

我们可以将第三方库转为UWP模块项目,工程文件由原Win32的工程文件转换得来。skia项目除外,skia是由gn构建工具生成的。生成工程编译后,我们可以创建一个UWP的demo来调用测试。

  1. google base库的MessagePump移植

在传统的Win32程序中,窗口和消息循环是分割的,我们可以使用 CreateWindow 来创建多个窗口,而多个窗口是共用一个消息循环,由于消息循环的独立性,我们的消息循环的代码通常像下面这样:

MSG msg = { 0 };
while (::GetMessage(&msg, NULL, 0, 0)) {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
}

即使没有Win32的窗口,消息循环也可以正常运行起来。同时,所有窗口共用的消息循环是运行在一个线程中的,也就是常说的主线程(UI线程)。而在UWP程序中,微软对窗口,消息循环进行了整合:

image

上图中,CoreApplication代表着整个UWP的进程,该UWP程序中,可以拥有多个CoreApplicationView,每一个CoreApplicationView是一个窗口和一个消息循环(消息分发)(CoreDispatcher)的结合,而且每一个CoreApplicationView独占一个线程。

也就是说,上图中的两个CoreApplicationView分别跑在不同的线程当中。

因此,UWP程序中,窗口和消息循环是不可分割的,是由UWP框架决定的,我们无法变更。

故而,google base库的MessagePump的Win32的实现模式(消息循环独立),在UWP中将不可复用。

参考SDL2对UWP的支持模式,对于MessagePump,初步设想为:MessagePump使用 CoreApplication.run 来实现,意味着必然会创建一个窗口,对于执行顺序问题,参考SDL2的处理方式,以回调函数的方式把创建窗口的操作延后。

  1. skia的最小绘制单元

skia目前找到的资料以及示例,Dx绘制的最小单元是一个CoreWindow。

  1. Win32的窗口

UWP的窗口是CoreWindow,但是不支持子窗口,目前未知如何将一个CoreWindow设置为另外一个CoreWindow的子窗口方法。通过上边讲述的CoreWindow对应一个消息循环,所以子窗口在UWP是不存在的,不然我们的消息循环就没有问题了。

另外,CoreWindow不支持窗口的标题隐藏,在自绘窗口的情况下,会有移植问题;并且多个CoreWindow会在任务栏显示多个图标,不符合Win32的窗口视觉设计。

所以对Win32的窗口,移植到UWP应该是一个控件,例如为image。但是skia最小绘制单元为CoreWindow,不能对单独一个控件进行画面更新提交,可以通过Dx接口获取到刷新完成后的画面,然后使用UWP方法将图片刷新到image控件中。

UWP原生菜单不支持超出窗口大小,这种情况和Win32的菜单窗口行为是不同的,需要特殊注意。

  1. 事件响应

UWP项目中,xaml后缀问题提供了大量的工具箱控件,但是并不能支持win32的所有事件。需要使用AddHandler来为控件添加事件响应。

例如:Button本来是监听不到鼠标左键的按下和抬起事件。哪怕你监听PointerPressedEvent事件,并在相应的事件处理函数中判断左键也不行,但是用AddHandler函数把PointerPressedEvent再添加一遍就可以监听到。

  1. UWP项目添加skin资源目录

skin文件夹必须在uwp项目所在文件夹内,skin下所有文件必须添加到vs项目中,而且必须作为内容,不然appx包里就不会有当前文件。

image

  1. UWP项目添加依赖库

如果所依赖的库在当前工程中,只需要右击引用添加即可;如果不在工程中,将依赖库以现有项的方式添加到工程中,并将属性页的文件内容改为是。

  1. 应用程序项目配置

开发的前提是dll中不使用windows10已弃用的API,应该程序所需要的所有dll都需要放在appx目录下,debug模式默认目录下,除了configuration\debug\c++uwp_called_win32dll\appx里面有一个exe,在configuration\Debug\c++uwp_called_win32dll下也有一个exe

  • C++通用应用程序调用C++Win32Dll:项目引用添加后,编译时应用程序项目会把被引用的dll拷贝到程序集Appx目录下,需要使用后期生成事件来再次拷贝文件,且dll只要不含win32API则可以在uwp应用项目中正确加载
  • C++通用应用程序调用C++UWPdll:可以添加引用,省去了项目配置属性和拷贝dll,并且vs会检查dll中哪些api在uwp中不能使用,编译自动报错
  • C++通用应用程序调用C++WinRT:可以添加引用,支持上层使用C#
  • C#通用应用程序调用C++Win32Dll:不可添加引用,不可通过项目属性配置,不可隐式链接win32dll,只能使用dllimport显示链接,只支持extenrn C的c函数方式;C++类不能直接使用
  • C#通用应用程序调用C++uwpdll:同调用C++Win32DLL
  • C#通用应用程序调用C++WinRT:可以添加引用,可以跨语言

JavaScript高级程序设计

这个页面我将记录阅读《JavaScript高级程序设计》第三版的点滴记录,既记录基础语法,又包含相关的理解

第一章:Javascript简介

最初的作用是在客户端实现表单的验证等,而今具备了与浏览器窗口及其内容等几乎所有方面的交互能力,并且通过node.js等可以实现更多native的能力。

Javascript已经发展成为一门功能全备的编程语言,能够处理复杂的计算和交互,拥有闭包、匿名函数和元编程等特性。一个完整的实现包含如下部分:

  • 核心ECMAScript:语法、类型、语句、关键字、保留字、操作符、对象等
  • 文档对象模型DOM:针对XML并经扩展的用于HTML的编程接口API,DOM将页面映射为一个多层节点结构,包含DOM视图、DOM事件、DOM样式、DOM遍历和范围等
  • 浏览器对象模型BOM:HTML5将BOM功能标准化,BOM处理浏览器窗口和框架及各种控制能力

第二章:在HTML中使用Javascript

HTML中插入js的方法是使用<script>元素,惯例会将标签放在元素中,现代Web应用为渲染优化会放在元素中。

文档模式我们现在均默认选择标准模式:

<!DOCTYPE html>

第三章:基本概念

语言的核心就是语法、操作符、数据类型、内置功能等,通过这些基础设施来解决现实问题。

3.1 语法

ECMAScript的一切变量、函数名、操作符等均区分大小写,函数名、变量名不能是关键字如typeof,标识符采用驼峰大小写格式。

3.2 变量

可以保存任何类型的数据,仅是一个用于保存值的占位符而已。定义时使用var操作符,未初始化时会存储一个特殊值undefined。

我们可以理解为是一个void*,用于存储任意类型的数据的地址

通过var定义的为局部变量,省略var则定义为全局变量。

3.3 数据类型

6种数据类型:undefined、null、boolean、number、string、object,可以使用typeof操作符来确认类型

函数在ECMAScript中是对象,不是一种数据类型;但是也确实有一些特殊的属性,因此区分函数和其他对象是有必要的

  • null:只有一个值;如果在C++中实现,可以认为指向了一个全局构造的对象地址,如base::EmptyString();null指向一个空对象的指针,所以typeof(null)===object;

  • undefined:只有一个值,使用var声明变量但未对其初始化的值;注意与空对象指针是不一样的,即便undefined继承自null所以null==undefined 为true(注意==操作符为了比较目的,实际做了操作数的转换),但是typeof两者却是不同的

对未声明的变量,只能使用typeof检测其类型,并且会返回undefined;怀疑是否在执行时构造了一个临时变量?
我们应该要尽量做到每一个声明的对象均给与初始化,以便于区分未声明的对象

  • boolean:只有两个字面量true和false,将任何其他类型转换为boolean,可以调用Boolean()函数(特别地如下情况会转换为false:空字符串、数字0、数字NaN、对象null、undefined)

在编程中的条件判断中,建议全部显式转换为boolean,从而保证整个流程的准确无歧义且易于阅读
在编程中,保证一个变量的类型不被修改(虽然是合法的),这样更加便于理解和清晰明了(请原谅我喜欢强类型的编程模式)

  • number:表示整数和浮点数;格式有十进制、八进制(不建议使用)、十六进制;有正0和负0,但是两者其实是相等的;

浮点数值的最高精度是17位小说,但是算术计算精度却有舍入误差,永远不要测试浮点数值

如果计算值超过Number.MAX_VALUE则返回Infinity,小于Number.MIN_VALUE则返回-Infinity,且不能再参与计算,可以通过isFinite()函数检测返回值

NaN非数值是一个特殊的数值,表示本来要返回数值的操作数未返回数值,例如除以0
任何涉及NaN的操作都返回NaN,NaN与任何值都不相等(包括NaN本身);可以通过isNaN()函数检测,任何不能被转换为数值的值都会返回true,如isNaN("blue")=NaN,isNaN(object)时会先调用对象的valueOf()方法,确定其返回值是否可以转换为数值,如果不能则基于返回值调用toString()方法,再测试返回值

数值转换Number():boolean转为0和1,数字值直接返回,null转为0,undefined返回NaN,字符串如果只包含数字转为十进制;字符串忽略前导0,字符串包含有效浮点格式转为对应浮点数值,字符串包含有效十六进制转为相同大小十进制,字符串为空转为0,字符串非上述情况转为NaN;对象调用valueOf()返回值进行Number()调用,若上述返回NaN则再调用toString方法再调用Number()

数值转换parseInt():转换字符串,查看是否符合数值模式,忽略字符串前的空格,找到第一个非空格字符;若第一个字符非数字和负号,返回NaN;对空字符串会返回NaN;解析后续字符直到遇到结束或者遇到非数字字符;注意:小数点不是有效的数字字符哦;可以识别十进制、十六进制,第二个参数指定基数可以传递按照多少进制解析,我们要求明确指定基数

数值转换parseFloat():与parseInt类似,只是增加了对第一个小数点的解析支持;且始终忽略前导0,只解析十进制格式,十六进制字符串会返回0

  • string:表示16位unicode字符组成的字符序列,可用单引号/双引号表示;字符字面量定义了相关的转义序列,如\n、\t、\b、\r、\f、\、\‘、"、\xnn、\unnnn,可以出现在字符串的任意位置且被当作一个字符来解析

length属性:获取字符串长度,返回字符串中16位字符的数目,如果包含双字节字符则不会精确返回字符数目

ECMAScript中的字符串是不可变的,要改变变量保持的字符串需要先销毁原来的字符串然后用另一个包含新值的字符串填充该变量

字符转换toString:数值、布尔、对象、字符串均有toStirng方法,但是null和undefined没有;数值调用时可以传递基数,默认情况以十进制格式返回数值的字符串表示

字符转换String():将任意类型转为字符串,包括null和undefined;如果有toString,调用并返回;null返回"null",undefined返回“undefined”

  • object:一组数据和功能的集合,可通过new操作符+对象类型名称创建;我们可以创建object类型的实例并为其添加属性和方法,从而创建自定义对象;每个实例都有如下属性和方法:Constructor、hasOwnProperty(检查指定属性是否存在于当前对象)、isPrototypeOf(检查对象是否当前对象的原型)、propertyIsEnumerable(属性是否可以使用for-in语句来枚举)、toLocaleString、toString、valueOf

3.4 操作符

  • 一元递增递减:对任何值都适用,且变为数值变量;有效数字字符串先转为数字值再执行增减;不包含有效数字字符时返回NaN;false变为0再执行;true变1再执行;对象先调用valueOf执行增减,如果为NaN执行toString后执行增减

  • 一元加减:加相当于转换为数值,减相当于转为数值的负数

  • 位操作符:按内存中的数值的位来操作数值;数值的位操作是64位的数值转换为32位然后执行位操作后再转换为64位;特殊的NaN和Infinity被当作0来处理;对非数值先使用Number转为数值后再执行位操作,得到一个数值的结果

位非NOT:~,返回数值反码,操作数的负值减1
位与AND:&,位对齐,每一位全为1取1反之取0
位或OR:|,位对齐,每一位全为0取0反之取1
位异或XOR:^,位对齐,每一位只有一个1取1反之取0
左移:<<,所有位向左移动指定位数,以0补足空位;不会影响符号位
有符号的右移:>>,保留符号位
无符合右移:>>>,所有32位右移;负数以补码右移会返回很大的值

  • 布尔操作符:与或非

逻辑非:!,任意类型均返回一个布尔值,对象返回false,空字符串返回true,非空字符串返回false,0返回true,非0和infinity返回false,null返回true,NaN返回true,undefined返回true;两个!!相当于模拟Boolean()
逻辑与:&&,相当于短路操作;有一个操作数不是布尔时,返回就不一定时布尔;第一个操作数是对象,返回第二个操作数;第二个为对象,第一个求值为true时返回第二个对象;两个操作数据均是对象返回第二个;有一个为null返回null;有一个为NaN返回NaN;有一个为undefined返回undefined
逻辑或:||,有一个操作数不是布尔值,就不一定返回布尔;第一个为对象,返回第一个;第一个操作数求值为false返回第二个;两个对象返回第一个;两个都是null返回null;两个都是NaN返回NaN;两个都是undefined返回undefined;短路操作,第一个操作数求值为true就不执行第二个

  • 乘性操作符:乘除求模

const引用和const指针

工作中,我们在某些情况下需要区分(const int *p;) (int const *p;) (int *const p;)三者,那么他们的区别是什么呢?

首先,我们来看下边的两个模板定义:

template <typename R, typename Receive, typename... Args>
bool Invoke(R (Receive::*method)(Args...), Args... args) {
  using Functor = FunctorAdapter<R, Receive, Args...>;
  return Invoke<Functor>(method, args...);
}

template <typename R, typename Receive, typename... Args>
bool Invoke(R (Receive::*method)(const Args...), Args &&... args) {
  using Functor = FunctorAdapter<R, Receive, const Args...>;
  return Invoke<Functor>(method, args...);
}

当method的参数为指针时,需要如何定义才能匹配上述两个模板调用呢? 这时候我们就需要使用 const type* const ,如下:

virtual void Method(const std::int32_t *const &) {}

一、const指针

int* p定义了一个普通的指针,const指针有如下几种情况:

  1. const int * p: 指向常量的指针
  • 等价于int const * p,const修饰的是类型int,在前在后是等价的
  • p指向的内存不能经过p来被修改
*p = 2;            // 错误,实际内存值无法通过p被修改,所以*p不能作为左值,
p = nullptr;     // 正确,p可以被修改
  1. int* const p:指向变量的常量指针,指针为常量
  • 修饰的是指针,指针无法被修改
  • p无法被修改,所以p无法作为左值
*p = nullptr;  // 正确,*p指向的内存可以通过p修改
p = nullptr;   // 错误,p是const的,无法修改,不能作为左值
  1. const int * const p:指向常量的常量指针,
  • 实际内存值无法通过p修改,p也无法修改
*p = nullptr;  // 错误
p = nullptr;   // 错误

const在类型之后:修饰的是const前的类型,即变量的实际值不可变
const在指针之后:修改的是const前的指针,则指针不可变
const在类型之前:等同于在类型之后
不可变,则不能作为左值

二、const引用

  1. const int& b:引用常量
  • 与int const& b等价
  • 可以绑定到常量对象、字面量、表达式
  • 非常量引用不能绑定到表达式和字面量
int i = 0;
const int& r1 = i;         // 正确
const int& r2 = 3;        // 正确
const int& r3 = i * 5;   // 正确
int& r4 = r1 * 2;          // 错误
int& r5 = 3;                // 错误
  1. int &const b:非法的,指针是对象而引用不是,不存在常量引用

术语中我们讲的常量引用,其实指的是引用常量

三、static

static const 和 const static是一样的

参考资料:

Win32转Bridge UWP指南

微软提供了一种将win32程序直接转成UWP的方法--Desktop Bridge

UWP发布过程:Pre-processing --> Certification --> Release --> Publishing

一、基础环境

  1. 系统环境:版本在10.0.14393.0之上,x64 Processor,可以执行systeminfo命令查看
  2. 安装Windows10SDK
  3. 在Windows商店下载安装Desktop App Converter
  4. 下载与当前系统版本匹配的BaseImage:https://aka.ms/converterimages

二、转编

  1. 以管理员权限打开Desktop App Convert
  2. St-ExecutionPolicy bypass
  3. 加载BaseImage:仅加载一次即可,DesktopAppConverter.exe -Setup -BaseImage D:\UWP\BaseImage-15063-UPDATE.wim -Verbose
  4. 制作
DesktopAppConverter.exe -Installer E:\App\AppSetup.exe -InstallerArguments "/S" -Destination D:\App\App -PackageName "0C72C7CD.535318B65018D" -AppId "App" -AppDisplayName "APP" -PackageDisplayName "App" -AppDescription "App" -Publisher "CN=7C12DEB1-3990-4000-BC36-CFF184F703CC" -PackagePublisherDisplayName "App" -Version 3.0.41.0 -MakeAppx -Verbose

DesktopAppConverter.exe -Installer d:\App\win32\AppSetup.exe -InstallerArguments "/S" -Destination D:\App\output -PackageName "0C72C7CD.535318B65018D" -AppId "App" -AppDisplayName "App" -PackageDisplayName "App" -AppDescription "App" -Publisher "CN=7C12DEB1-3990-4000-BC36-CFF184F703CC" -PackagePublisherDisplayName "App" -Version 3.0.41.0 -Verbose
  1. 图片压缩,如果D:\App\output\0C72C7CD.535318B65018D\PackageFiles\Assets\AppLargeTile.scale-400.png文件大小超过200kb,可以使用pngout进行优化

  2. 手动打包:不要再desktop bridge环境里

"C:\Program Files (x86)\Windows Kits\10\bin\x86\makeappx.exe" pack /d D:\app\output\0C72C7CD.535318B65018D\PackageFiles /p D:\app\output\0C72C7CD.535318B65018D_S05.appx /l

三、其他

打开C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools,在cmd运行VsDevCmd.bat,然后执行如下操作

  1. 生成证书
cd "C:\Program Files (x86)\Windows Kits\10\bin\x86"
MakeCert.exe -r -h 0 -n "CN=7C12DEB1-3990-4000-BC36-CFF184F703CC" -eku 1.3.6.1.5.5.7.3.3 -pe -sv my.pvk my.cer 
pvk2pfx.exe -pvk my.pvk -spc my.cer -pfx my.pfx 
  1. 手动签名
cd "C:\Program Files (x86)\Windows Kits\10\bin\x86"
signtool.exe sign -f my.pfx -fd SHA256 -v .\0C72C7CD.535318B65018D_S03.appx
  1. WACK认证
Before you submit your app for certification and listing in the Windows Store, use the Windows App Certification Kit to test your app and make sure it's ready to go.
"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\appcertui.exe"
  • Convert 后,PackageFiles 中Manifest 的DisplayName的名称
  • Appx 安装在本机上生成的菜单栏的名称
  • 最小化任务栏显示名称

PHP菜鸟教程

image

一、学习路线

本文中,我们将要学习PHP;初学者的学习的路线如下:

  1. 熟悉HTML/CSS/JS等网页基本元素,可以自行制作简单网页,熟悉元素属性
  2. 理解动态语言的概念和运作机制,熟悉基本的PHP语法
  3. 学习如何将PHP与HTML结合,完成简单的动态页面
  4. 学习MySQL,开始设计数据库
  5. 巩固PHP语法,熟悉常用函数,理解面向对象,MySQL优化
  6. 掌握一些模板和框架
  7. 完成一个功能齐全的动态网站

学习总是容易疲惫,交叉学习可以保持大脑的清晰。接下来我们来学习下PHP的基础语法,然后利用开源的框架,开发一个企业网站。

二、PHP简介

PHP是一种创建动态交互性网站的强有力的服务器端脚本语言。代码在服务器上执行,以纯html返回给浏览器,代码文件以".php"作为文件扩展名。跨平台和面向对象是其最大最重要的特性。PHP是B/S体系结构,安全性高,内嵌Zend加速引擎性能稳定快速。

PHP被唱衰很长时间了,但是目前以PHP架构的网站依旧占据着互联网的一大部分,并且PHP在不断的更新迭代,相信知识永远不会过时。PHP的扩展库很多,例如对MySql、bzip2等的支持;我们在需要的时候查询相关的文档即可,这里不做列举,以免造成学习的恐惧。

PHP能够做什么?

  • 生成动态页面内容
  • 创建、打开、读取、写入、关闭服务器上的文件
  • 收集表单数据
  • 发送和接收cookies
  • 添加删除修改数据库的数据
  • 权限控制用户访问
  • 加密数据

PHP环境搭建和开发工具

学习环境可以使用AppServ等集成开发环境,生产环境现在也有了宝塔等类似的环境。我们不在环境上浪费过多时间,网上很多。开发工具也根据个人喜好即可,这里我们选择VSCode。

/*index.php*/
<?php
    echo "Hello World!!!";
?>

三、PHP教程

本系列学习将从如下几个方面进行学习和讲解:

  1. PHP基础知识
  2. PHP核心知识
  3. PHP高级应用
  4. PHP项目实战

四、总结

编程的终极捷径就是:coding、coding、coding

Python核心技术

一、Python之禅

python是一种优美的语言

Ptyhon之禅,阐述了Python语言的核心**。我们来品味一下:

import this
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Python之禅 by Tim Peters
优美胜于丑陋(Python 以编写优美的代码为目标)
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
可读性很重要(优美的代码是可读的)
即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)
包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)
当存在多种可能,不要尝试去猜测
而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)
虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )
做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量)
如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)
命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)

二、Python语言核心之对象与引用

简单看了下python的介绍,发现如下两个与C/C++相区分的关键点:对象+引用。如何来理解呢?

  1. 一切数据皆对象

对象是包含了一系列数据和方法的实体

这里的对象可以理解为C++中的对象,python实现时类似于将每一个数据都通过内置对象类型做了一层包装。例如整形的5,在python中是一个interger对象,其值为数字5。

  1. 一切变量皆引用

变量在python中类似于C++中的函数地址,指向了数据的对象。例如 t = 5 ,可以理解为t指向了一个对象,这个对象的值为5,t是这个对象的引用。

当我们后边将要学习python的基础内置数据类型时,会更加深刻的理解这两点。这样做的好处是什么呢?

  • 内存回收:每一个对象都包含一个引用计数,如果连续给t引用不同的对象地址,那么对应的1、2、3等值对应的对象引用计数变为0,则销毁对应的对象内存。很类似与C++的scoped_refptr吧?通过AddRef+Release给对象增加删除引用计数,为0时销毁对象内存
  • 类型判断:type(t),通过对象的内置的方法和特例化即可以返回对象对应的类型
  • 集成了C++对象的所有优点

理解上边阐述的概念,那么我们可以思考下:自己来设计python的数据类型时会怎么做呢?

我们使用C++来实现时,自然而然就想到了引用计数。这也是工作中,我们管理内存的常用方式。

import sys
print sys.getrefcount(5)
  1. 一切皆为指针

通过如上的阐述,我们可以想象到python中一切都可以用C++中的类指针方式实现。

所以,python中不用再考虑指针的问题了。

综上,在现实世界中,一切均可抽象为对象。在微观世界中,我们可以理解为一切皆由最小的元子组成;如果不停的微观化,是不是就可以重塑这个世界? 人抽象为各种元子组成,感情也是元子的一种属性及方法;如果抓住了最核心的元子,造人也许就是真的造人了!!!

回到对象本身,可以明了python的数据组成部分:身份(id查看唯一标识符,可以取到内存地址)、类型(type)、值(属性)

[转]《C++最佳实践》翻译与阅读笔记

转自:https://mp.weixin.qq.com/s/vkOH79E8XBtbxSLo_AGFcg

翻译原文链接在这里:https://zhuanlan.zhihu.com/p/427778091

干货开始:

这本书的副标题是:45ish Simple Rules with Specific Action items for better C++ ,这本书是由公司大佬推荐的, 个人认为有必要掌握一下这45条最佳实践, 可以很大程度上提升代码的可读性和健壮性, 而且这本书也不长, 翻译起来也会比较简单,比很多人推荐的Effective C++ 要容易读的多, 抽了一个十一的尾巴就翻译完了,easy~ 翻译的过程中去掉了欧美人写书时喜欢带上的口水话,从而让文章更加精炼。++我翻译的时候会带上一些自己的理解,这部分我用下划线标出来了, 注意区分++。

1. 导读

我以一个训练者的目标希望各位能够:

  • 学习如何自己做实验;
  • 不要仅仅信我说的话,要亲手实验;
  • 学习到这门语言是如何工作的;
  • 在下轮写代码的时候避免犯错;

2. 有关"最佳实践"

最佳实践实际上就是说:1. 减少常见的错误;2. 能够更快定位错误 3. 提升运行性能

为什么要最佳实践? 因为你的项目并不是一个特殊的项目

如果你或者你同事在用C++编程,他们往往是在乎性能的,否则他们会用一些其它的语言。我经常去一些公司他们都告诉我他们的项目是特殊的,因为他们想要快速完成功能。

警告:他们都在出于相同的原因做着相同的决定。很少有例外, 例外者其实都是那些已经遵循这本书的组织。

3. 使用工具:自动测试

你需要一个单独的指令去跑所有的测试,如果你没有自动测试, 没有人会跑这些测试的:

  • Catch2
  • DocTest
  • GoogleTest
  • Boost.Test

Ctest 是一个Cmake下用来测试的runner, 它可以很好地利用 Cmake的 add_test 特性。你需要很熟悉测试工具、搞清楚他们是怎么做的、并且从他们中选择一个。

没有自动化的测试, 这本书的其它部分就是没有意义的,在重构代码的时候,如果你不能验证你没有破坏现有的代码, 你就不能使用这些最佳实践。

Oleg Rabaev 说过:

==如果一个部分是很难测试的, 那它肯定没被设计好。如果一个组成部分是容易测试的, 那就意味它是有被很好的设计的。反过来说,一个好的设计,应该是一个容易被测试的设计。==

4. 使用工具:持续构建

没有自动化的测试, 那就很难去保证代码的质量。在我生涯中的一些C++ 项目中,我的C++项目支持各种操作系统、各架构的组合。当你开始在不同平台、架构上组合不同的编译器的时候, 那就很有可能出现在一个平台能够使用在另外一个平台上不能使用的情况。为了解决这个问题, 使用带有持续测试的持续构建工具吧。

  • 测试你将支持的所有平台的组合。
  • 将debug和最终发布分离
  • 测试所有的配置项。
  • 测试所有你需要支持的编译器

5. 使用工具:编译器警告

有许多警告你也许都没有在使用, 大多数警告实际上都是有好处的。-Wall 并不是GCC和Clang提供的所有warning, -Wextra仍然只是warning中的冰山一角。

强烈考虑使用 -Wpedantic(GCC/Clang) 以及 /permissive(msvc) , 这些编译选项能够进制语言拓展项,并且让你更加接近于C++ 标准, 你今天开启越多的warning, 你以后就越容易迁移到其它平台。

6. 使用工具:静态分析

静态分析工具可以在不编译和运行你的代码的情况下来分析你的代码, 你的编译器实际上就是这么一个工具并且是你的第一道代码质量防线。许多这种工具都是免费的并且是开源的, CPPCheck 和clang-tidy是两种流行并且免费的工具, 大多数IDE和编辑器都能够支持。

7. 使用工具:sanitizers

sanitizers是集成在gcc clang msvc下的一种运行时分析工具。如果你比较熟悉Valgrind, sanitizers提供了相似的功能但快了好几个数量级。

Address sanitizer, UB Sanitizer, Thread sanitizer 可以找到很多像魔法一样的问题, 在写这本书的时候,msvc正在支持越来越多的sanitizer, gcc和clang 拥有更多对sanitizer的支持。

John Reghr 推荐在开发的时候永远开启ASan( Address Sanitizer) 和UBSan(Undefined Behaviour Sanitizer) 。

当类似于内存溢出的错误发生时, sanitizer 将会给你一个报告告诉你什么情况下会导致程序失败, 通常还会给出修复问题的建议。

你可以用如下的命令开启ASan 和UBSan:

gcc -fsanitize=address,undefined <filetocompile>

8. 慢下来

在C++中,一个问题往往有很多解决方案, 对于哪种方案是最优的有若干个观点存在。从被人的项目里面复制粘贴代码是非常容易的, 使用你觉得最满意的的方案去解决问题也是非常容易的(但这两种都是要避免的), 要注意了:

如果解决方案似乎很复杂很大,停下。这时候是一个很好的时间去走走然后好好思考这个方案。当你结束了散步, 把你的设计方案和同事讨论一下,或者用橡皮鸭思考方法讲出来(++所谓橡皮鸭方法就是你对着一个橡皮鸭一五一十地说出你的思考过程和逻辑,从中可以察觉到自己的思维漏洞等++) 如果你还没有发现一个直接的解决方法?去twitter上面问问吧。最关键的其实是不要盲目地去用你自己觉得满意的方案去解决问题, 要多停一段时间多思考。随着年龄的增长,我花在编程的时间越来越少,但是思考的时间却越来越多, 最后我实现解决方案的速度比以前越来越快,并且越来越简单。

9. C++ 不是魔法

这部分只是提醒你我们可以对C++的各个方面进行推理, 它并不是一个黑盒,也并不是一个魔法。

如果你有问题, 你通常可以很简单地去写个实验代码,它将自己回答你的这些问题。

10. C++ 不是纯面向对象的语言

Bjarne Stroustrup 在"The C++ Programming Language" 的第三版里面讲到:

==C++ 是一个通用的编程语言但是偏向于系统编程, 它比C更好, 支持数据抽象, 支持面向对象, 支持泛型编程。==

你必须明白C++ 是一种多科目语言, 它很好的支持了当下的所有编程范式:

  • 过程式
  • 函数式
  • 面向对象
  • 泛型编程
  • 运行时编程(constexpr 以及模板元变成) 理解什么时候去用这些工具是写好C++的关键, 那些只执着用某一个编程范式的项目往往会错过这个语言的最佳特性。

11. 学习一门其他语言

考虑到C++并不是一个纯面向对象的语言, 你必须掌握一些其它技术才能更好地使用C++,最好学会一门函数式语言比如Haskell.

12. const/constexpr 修饰一切常量

许多大佬说过这很多次了, 让一个对象修饰为const 有两个好处:

它迫使我们去思考这个对象的的初始化和生命周期, 这会影响程序的性能。可以向代码的读者传达这是一个常量的意义 另外,如果它是一个static对象,编译器现在可以自由地将它移到内存的常量区,这可能会影响优化器的行为。

13. constexpr 修饰一切在编译阶段就已知的值

使用# define的日子早就一去不复返了, constexpr应该成为你新的默认选择。很不幸的是, 人们总是过份高估constexpr的复杂程度, 所以就让我们把它拆解成最简单的东西说起吧~

如果你看到像这样的一些东西:

在编译时期,数据就已经知道是static const 对象了: 
static const std::vector<int> angles{-90,-45,0,45,90}

那这种情况就需要把它变成:

static constexpr std::array<int,5> angles{-90,-45,0,45,90}

static constexpr 在这里可以保证对象不会在每次这个函数/声明遇到时都会重新初始化。由于static修饰了这个变量, 它将存在于整个程序的生命周期, 而且我们知道它不会被初始化两次。

这两个代码的区别有三:

  • 这个array的大小我们是能够在编译时期知道的
  • 我们溢出了数组大小动态分配
  • 我们不需要再对访问static 对象付出额外代价(++这点我没明白啥意思,原文是 We no longer pay the cost of accessing a static++) 主要收益来源于前两点。

14. 在大多数情况下使用auto来自动推断类型

我其实并不是一个永远使用auto的人, 但是让我来问你一个问题:std::count 函数返回的类型是什么?

我的回答是:我不在乎。

const auto result = std::count( /* stuff */ );

使用auto 避免没有必要的转换和数据丢失, 同样的场景经常在range-for 循环中出现,再举个栗子:

C++
有可能发生代价很高的转换
const std::string value = get_string_value();

get_string_value()的返回类型是什么?如果它是一个std::string_view或者是一个const char *, 我们将会有一个潜在的高代价的类型转化。

不可能有高代价的性能转化的写法:
// 避免类型转化
const auto value = get_string_value();

另外, auto作为返回值实际上可以大幅度简化泛型代码:

// C++98 template usage
template<typename Arithmetic>
Arithmetic divide(Arithmetic numerator, Arithmetic denominator) {
                    return numerator / denominator;
}

这个代码强制我们分子的类型和分母的类型都是同一种类型,非常难受。但在C++98 里面怎么实现分子分母不同类型呢?

template<typename Numerator, typename Denominator>
/*what's the return type*/
divide(Numerator numerator, Denominator denominator) {
                    return numerator / denominator;
}

可以看到,我们没有办法提供返回值的类型, C++98没有提供这种问题的解决方法,但是C++11通过尾置返回类型可以做到:

// use trailing return type
template<typename Numerator, typename Denominator>
auto divide(Numerator numerator, Denominator denominator)
 -> decltype(numerator / denominator)
{
                    return numerator / denominator;
}

但是在C+14里面, 我们还可以把返回类型给省去,

// use trailing return type
template<typename Numerator, typename Denominator>
auto divide(Numerator numerator, Denominator denominator){
    return numerator / denominator;
}

15. 使用ranged-for循环而不是老的循环方法

我会用几个例子来说明这一点:

当循环时, int和 std::size_t 的问题

for (int i = 0; i < container.size(); ++i) {
    // 糟糕,i 实际上不是int类型,是size_t类型,有类型不一致的问题
}

当循环时, 容器类型不一致的问题:

for (auto itr = container.begin();itr != container2.end();++itr) {
// 哦,我们大多数人都有过这种经历
}
使用ranged-for的例子

for (const auto &element : container) {
    // 消除了以上的两种问题
}

注意:永远不要在用ranged-for的时候修改容器自身

16. 使用ranged-for时配合auto使用

不使用auto会让你更容易不经意间犯一些错误:

意外的类型转换:

for (const int value : container_of_double){
    // 意外的转换,很有可能会报warning
}

意外的继承切片问题

for (const Base value : container_of_Derived){
    // 意外的发生了继承切片问题
}

正确做法

for (const auto &value : container){
    // 不会发生意外的问题
}

优先考虑:

  • 对于内部元素不可变类型的循环时选择 const auto &
  • 对于内部元素可变类型的循环时选择 auto &
  • auto && 当且仅当你需要一些奇怪的类型比如std::vector 时, 或者将元素移出容器时(++这个地方没懂, 原文:auto && only when you have to work with weird types like std::vector, or if moving elements out of the container++)

17. 使用算法而不是循环

算法能够传达更多的含义并且能够符合”const 一切" 的**, 在C++20 中, 我们有ranges, 这会使得算法用起来更加舒服。

使用函数的方式并且配合使用算法, 这会使C++ 的代码读起来更想是一个句子。

比如,检查一个container内是否有一个大于12的数:

const auto has_value = std::any_of(begin(container), end(container), greater_than(12));

在极少数情况,编译器的静态分析工具可以能够提示你有现有的算法能够使用。

18. 不要害怕使用模板

模板能够表现 C++ 中的DRY原则(Dont repeat yourself) , 模板它可能是复杂的,令人生畏的, 并且是图灵完备的, 但是它们也不必如此。15年前, 业界似乎有一个盛行的态度是:“模板就不是给正常人写的"。

幸运的是, 这种观点放在今天越来越不正确了, 现在我们用更多的工具:concepts, generic lanbdas 等等。

我们将在接下来写一个简单的例子。假设我们想要写一个函数它可以除任意两个值:

// 除两个double 类型的数
double divide(double numerator, double denominator){
    return numerator / denominator;
}


// 你不希望分子的类型被提升到double类型:
float divide ( float numerator, float denominator){
    return numerator / denominator;
}


// 当然,你还想两个int类型的相除
int divide ( int numerator, int denominator){
    return numerator / denominator;
}


// template 就是为了这种情况而设计的:
// 最基础的template 
template<typename T>
T divide(T numerator, T denominator){
    return numerator / denominator;
}
// 大多数的例子都用T来表示, 就像我刚才做的那样,但是不要这么做,给你的类型一个有意义的名字:
template<typename Arithmetic>
Arithmetic divide(Arithmetic numerator, Arithmetic denominator){
    return numerator / denominator;
}

19. 不要copy paste代码

如果你发现自己正在选中一块代码并且复制它, 停下!

后退一步并且再看下这些代码:

  • 为什么你要复制它?
  • 这个代码和你的目标代码有多少相似?
  • 构造一个函数有意义吗?
  • 记住, 不要害怕使用模板!

我发现这个简单的条例可以对我的代码质量有着最直接的影响, 如果我们即将在当前的函数中进行粘贴一段代码, 那就考虑使用lambda 。C++14 的lambda 配合上泛型参数(也叫auto),可以让你更加容易写出可复用的代码还不要处理template 语法。

20. 遵循“零法则"

在正确的情况下,没有析构函数总是更好的。空的析构函数会损失一部分性能,并且:

  • 它会让类型不再简单;
  • 没有函数用途;
  • 会影响内联的析构;
  • 不经意间禁止了移动操作。如果你提供了一个自定义的删除行为, std::unique_ptr 可以帮助你遵循0法则

21. 如果你一定手动管理资源, 遵循“五法则”

如果你需要提供一个自定义的析构函数,那你必须同时对其它特殊成员函数进行 =delete, =default,或者实现它们。这个规则一开始叫“三法则”, 在C++11后变成了“五法则”

// 特殊的成员函数
struct S {
    S(); // default constructor
        // does not affect other special member functions

    // If you define any of the following, you must deal with
    // all the others.

    S(const S &); // 拷贝构造

    S(S&&);     // 移动构造 

    S &operator=(const S &); // 拷贝赋值

    S &operator=(S &&); //  移动赋值 
};

当你不知道怎么去处理它们的时候,=delete 对于这些特殊的成员函数来说是一个非常安全的处理方法.

当你在声明带有虚函数的基类时,你也应该遵循”五法则“:

struct Base {

    virtual void do_stuff();


    // because of the virtual function we know this class
    // is intended for polymorphic use, therefore our

    // tools will tell us to define a virtual destructor
    virtual ~Base() = default;


    // and now we need to declare the other special members
    // a good safe bet is to delete them, because properly and safely
     // copying or assigning an object via a reference or pointer
     // to a base class is hard / impossible


    S(S&&) = delete;

    S(const &S) = delete;

    S &operator=(const S &) = delete;
    S &operator=(S &&) = delete;

};


struct Derived : Base {
// We don't need to define any of the special members
// here, they are all inherited from `Base`.    
}

22. 不要调用未定义的行为(UB行为)

现在我们知道有很多未定义的行为是很难追踪的, 在接下来的几节中我将给出一些例子。最重要的事情是你需要理解,未定义行为的存在会破坏你整个程序。

一个符合规范的实现在运行一个格式良好的程序时,应该产生可以观测的行为,相同的程序和相同的输入应该产生与之相对应的行为。
但是, 如果一个程序中包含着一个未定义的行为, 这段代码对执行输入的程序也就没有要求。(甚至对第一个未定义操作之前的操作也没有要求)

如果你有未定义的行为, 整个程序的执行就会变得很诡异。

23. 不要判断this 为nullptr,这是UB行为

int Class::member() {
if (this == nullptr) {
    // removed by the compiler, it would be UB
    // if this were ever null
    return 42;
    } else {
        return 0;
    }
}

严格意义上说,这并不是对未定义行为的校验。但是这个校验不可能会失败, 如果this等于nullptr, 那你将会处于一个UB行为的状态中。人们过去经常经常这么做,但这永远是一个UB行为。你不能从一个对象的生命周期以外去访问一个对象。从理论上来说, this=null的唯一可能就是你在调用一个null对象的成员。

24. 不要判断对象的引用是nullptr,这是UB行为

int get_value(int &thing) {
    if (&thing == nullptr) {
        // removed by compiler
        return 42;
    } else {
        return thing;
    }
}

不要尝试它,这是一个UB行为, 永远认为引用所指向的是一个存在的对象, 在你设计API时可以合理使用这一点。

25. 避免在switch语句中使用default

这个问题可以用以下一系列的例子来阐明, 从这个开始:

enum class Values {
    val1,
    val2
};

std::string_view get_name(Values value) {
    switch (value) {
        case val1: return "val1";
        case val2: return "val2";
    }
}

如果你开启了所有warning, 你将会得到一个"not all code paths return a value" 的警告,这从技术上说是正确的。

我们可以调用 get_name(static_cast(15)) , 这并不违法任何C++的规定, 但函数不返回任何值这一点是个UB行为。

你可以尝试这样修复代码:

enum class Values {
    val1,
    val2
};

std::string_view get_name(Values value) {
    switch (value) {
        case val1: return "val1";
        case val2: return "val2";
        default: return "unknown";
    }
}

但是呢这样会引入一个新的问题:

enum class Values {
    val1,
    val2,
    val3, // 这里增加一个值
};

std::string_view get_name(Values value) {
    switch (value) {
        case val1: return "val1";
        case val2: return "val2";
        default: return "unknown";
    }
    // 编译器不会诊断出来val3没有处理
}

实际上,我们更倾向于这样的代码:

enum class Values {
    val1,
    val2,
    val3, // 这里增加一个值
};

std::string_view get_name(Values value) {
    switch (value) {
        case val1: return "val1";
        case val2: return "val2";
    }  // 没有处理的enum 值这里会有warning
    return "unknown" 
}

26. 使用带作用域的枚举值

C++11 引入了带作用域的枚举值, 目的是为了解决很多从C继承过来的问题。

C++98 enums

enum Choices {
    option1 // value in the global scope
};


enum OtherChoices {
    option2
};


int main() {
    int val = option1;

    val = option2; // no warning
}

enum Choices 和OtherChoices它们俩很容易被搞混, 并且它们引入了全局命名空间下的标识符。

  • enum class Choices;
  • enum class OtherChoices;

在这些枚举类中的值都是带有限定作用域的,并且更加强类型。

C++11 scoped enumeration

enum class Choices {
    option1
};


enum class OtherChoices {
    option2
};


int main() {
    int val = option1;
    int val2 = Choices::option1;
    Choices val = Choices::option1;
    val = OtherChoices::option2;
}

这个带enum class的版本没有花太多功夫就可以让它们不那么容易被搞混, 并且它们的标识符现在是带作用域的,并不是全局的。

enum Struct和 enum class 其实是等价的,只是逻辑上enum struct更加合理, 因为它的成员是public公开的。

27. 使用if constexpr 而不是SFINAE(Substituion Failure Is Not An Error)

SFINAE 是一种很难读懂的代码(++不懂SFINAE的参考知乎的这个文章文章) , if constexpr 没有SFINAE那么灵活,但是当你能用它的时候就尽量用它.

我们来看一下之前在Prefer auto in Many Cases的文章中举的相除的例子:

template<typename Numerator, typename Denominator>

auto divide(Numerator numerator, Denominator denominator)
{
    return numerator / denominator;
}

好, 那当我们要做整数相除的时候, 我们现在想要加一个不同的行为该怎么做呢?在C++17以前, 我们会使用SFINAE("Substitution Failure is not An Error") 这个特性。基本意思就是说, 如果一个函数无法编译, 那它就会从重载解析中删除。举个例子:

SFINAE 版本的删除

#include <stdexcept>#include <type_traits>#include <utility>

template <typename Numerator, typename Denominator,
        std::enable_if_t<std::is_integral_v<Numerator> &&
        std::is_integral_v<Denominator>,int> = 0>
auto divide(Numerator numerator, Denominator denominator) {
    // is integer division
    if (denominator == 0) {
        throw std::runtime_error("divide by 0!");
    }
    return numerator / denominator;
}

template <typename Numerator, typename Denominator,
std::enable_if_t<std::is_floating_point_v<Numerator> ||
                std::is_floating_point_v<Denominator>,
                int> = 0>
auto divide(Numerator numerator, Denominator denominator) {
    // is floating point division
    return numerator / denominator;
}

C++17 的 if constexpr 语法可以简化这个代码:

#include <stdexcept>#include <type_traits>#include <utility>

template <typename Numerator, typename Denominator>
auto divide(Numerator numerator, Denominator denominator) {
if constexpr (std::is_integral_v<Numerator> && std::is_integral_v<Denominator>) {
    // is integral division
    if (denominator == 0) {
        throw std::runtime_error("divide by 0!");
    }
}
   return numerator / denominator;
}

注意,if constexpr块中的代码在语法上仍然必须是正确的. if constexpr 跟#define是不一样的。

28. 用Concepts约束你的模板参数(C++20)

Concepts比起SFINAE会给你带来更好的报错信息以及更快的编译时间, 另外还会比SFINAE有更好的可读性。我们继续构建我们上一章的例子 , 这是上一章用if constexpr 写出来的代码:

#include <stdexcept>#include <type_traits>#include <utility>

template <typename Numerator, typename Denominator>
auto divide(Numerator numerator, Denominator denominator) {
if constexpr (std::is_integral_v<Numerator> && std::is_integral_v<Denominator>) {
    // is integral division
    if (denominator == 0) {
        throw std::runtime_error("divide by 0!");
    }
}
   return numerator / denominator;
}

用concept我们可以把它分解成两个不同的函数。Concepts可以在许多不同的场景下使用, 这个版本在函数声明以后用了一个简单的requires从句:

#include <stdexcept>#include <type_traits>#include <utility>

// overload resolution will pick the most specific version
template <typename Numerator, typename Denominator>
auto divide(Numerator numerator, Denominator denominator) requires
    (std::is_integral_v<Numerator>&& std::is_integral_v<Denominator>) {
    // is integral division
    if (denominator == 0) {
        throw std::runtime_error("divide by 0!");
    }
    return numerator / denominator;
}


template <typename Numerator, typename Denominator>
auto divide(Numerator numerator, Denominator denominator) {
    return numerator / denominator;
}

这个版本用了concepts作函数参数, C++20 甚至还有“auto concept" , 这是个隐式模板函数。

#include <stdexcept>#include <concepts>
auto divide(std::integral auto numerator,
            std::integral auto denominator) {
    // is integer division
    if (denominator == 0) {
        throw std::runtime_error("divide by 0!");
    }
    return numerator / denominator;
}


auto divide(auto numerator, auto denominator){
    return numerator / denominator;
}

29. 将你的泛型代码去模板化

尽可能地将代码移出模板之外, 使用其它函数或者使用基类都可以,编译器仍然可以内联它们。(并不是不用模板,而是模板内的代码尽可能移出去)

去模板化可以提高编译速度并且减少二进制文件的大小,二者都很有用。它还可以消除模板膨胀。

每次函数模板实例化时都会生成一个新lambda函数

template<typename T>
void do_things()
{
    // this lambda must be generated for each
    // template instantiation
    // 这个lambda函数在每个模板实例化的时候都会被生成一次
    auto lambda = [](){ /* some lambda that doesn't capture */ };
    auto value = lambda();
}

与之相对应的是

auto some_function(){ /* do things */ 


template<typename T>
void do_things()
{
    auto value = some_function();
}

现在只编译了一个版本的内部逻辑,编译器决定它们是否应该内联。在基类和模板派生类中会用到相似的技巧。

30. 使用Lippincott 函数

与“去模板化"相同的道理, 这是在异常处理中的一个DRY(Dont't repeat yourself) 原则。如果你有很多异常类型需要处理, 你可能会写出如下代码:

void use_thing() {
    try {
        do_thing();
    } catch (const std::runtime_error &) {
    // handle it
    } catch (const std::exception &) {
    // handle it
    }
}


void use_other_thing() {
    try {
        do_other_thing();
    } catch (const std::runtime_error &) {
        // handle it
    } catch (const std::exception &) {
        // handle it
    }
}

Lippincott 函数提供了一种中心化的异常处理套路:

void handle_exception() {
    try {
        throw; // re-throw exception already in flight
    } catch (const std::runtime_error &) {
    } catch (const std::exception &) { }
}


void use_thing() {
    try {
        do_thing();
    } catch (...) {
        handle_exception();
    }
}


void use_other_thing() {
    try {
        do_other_thing();
    } catch (...) {
        handle_exception();
    }
}

这个小技巧不是啥新东西, 它早在C++98时就可以使用了。

31. 担心全局状态(Global State)

对全局状态进行推理是很难的, 任何非const 的static值或者std::shared_ptr 都可能是一个潜在的全局状态, 你永远不知道谁可能会更新这个值或者它是否是线程安全的。

当一个函数改变了一个全局状态时,它会导致不易察觉的并且很难去追溯的bug, 另一个函数要么会依赖这个变化,要么会受到它的负面影响。

32. 让你的接口很难用错

你的接口是你的第一道防线, 如果你提供了一个很容易用错的接口, 你的用户就会错误地使用它。如果你提供了一个很难用错的接口, 你的用户就会很难去把它用错。但这是C++, 他们总能找到办法的。

设计一个很难用错的接口有时会导致代码的冗长, 你必须选择哪一个是最重要的, 是正确的代码还是短的代码?

33. 考虑如果调用错了API是否会导致UB错误

你是否接受一个空指针?它是否是个可选参数?如果一个nullptr传入你的函数会发生什么?如果一个异常范围的值传入你的函数会发生什么?有些开发者会在内部接口和外部接口里面做个区分, 他们允许一些不安全的API在内部接口中使用。

不过你能保证外部的使用者永远不调用内部的API嘛?你能保证内部使用者永远不误用API嘛?

34. 大量使用[[nodiscard]]

[nodiscard] 是一个C++的特性,它告诉编译器如果返回值被放弃了则需要警告,

它可以用在函数上:

[[nodiscard]] int get_value();

int main()
{
    // warning, [[nodiscard]] value ignored

    get_value();
}

可以用在类型上:

struct [[nodiscard]] ErrorCode{};

ErrorCode get_value();


int main()
{
    // warning, [[nodiscard]] value ignored

    get_value();
}

35. 使用强类型

考虑一下POSIX socket的API

socket(int, int, int);

每个参数代表:

  • 类型
  • 协议
  • 域名 这个设计是有问题的, 但是我们代码里面隐藏了更多比这个更不明显的问题。比如一个构造函数:

Rectangle(int, int, int, int); 这个函数可能是指(x,y,width, height), 也有可能是指(x1, y1, x2, y2), 或者不太可能但是仍然有可能出现的是含义是(width, height, x, y)

那你认为下面这个API怎么样呢?

强类型API

struct Position {、
    int x;
    int y;
};


struct Size {
    int width;
    int height;
};


struct Rectangle {
    Position position;
    Size size;
};  


Rectangle(Position, Size);

这可以延伸出来其它的具有操作符重载的组合语句:

// Return a new rectangle that has been
// moved by the offset amount passed in
Rectangle operator+(Rectangle, Position);

避免bool类型的参数
这章的预发布读者指出, steve Maguire 在他的《编写可靠的代码》的第五章中说过:"要让代码在调用时易于理解“。在C++11中, enum class 提供给了你一种很容易的方法去添加强类型,从而避免布尔类型的参数, 这会使你的API更难用错。

考虑以下代码:

参数顺序不明显

struct Widget {
    // this constructor is easy to use wrong, we
    // can easily transpose the parameters
    Widget(bool visible, bool resizable);
}

与如下代码相比:

强类型带作用域的枚举值

struct Widget {

    enum struct Visible { True, False };
    enum struct Resizable { True, False };


    // still possible to use this wrong, but MUCH harder
    Widget(Visible visible, Resizable resizable);
}

36. 不要返回裸指针

返回一个裸指针会让读者和使用者很难去思考明白它的所有权。选择引用智能指针, 非归属指针包装器(?++没明白,原文是non owning pointer wrapper++) ,或者考虑可选引用(++没明白是啥,原文是optional reference++)。

返回一个裸指针的函数示例

int *get_value();

谁拥有这个返回值?是我吗?当我用完它,我是否需要去delete掉这个指针?

或者考虑一种更坏的情况, 如果这个内存使用malloc来分配的,我是不是需要调用free来释放它?

这是个指向单个int值的指针,还是一个int数组?

这个代码有太多问题了, 甚至用[[no discard]]都不能帮助我们

37. 优先用栈而不是堆

栈对象(非动态分配的作用域在本地的对象)对于优化器更加友好、缓存更加友好、并且可能被优化器完全删除。正如Björn Fahller所言, ”假设任何指针间接指向都是一次cache miss”

用最简单的话来说:

ok,用栈并且这可以被优化

std::string make_string() {
 return "Hello World"; 
}

不好, 使用了堆

std::unique_ptr<std::string> make_string() {
    return std::make_unique<std::string>("Hello World");
}

OK

void use_string() {
    // This string lives on the stack
    std::string value("Hello World");
}

非常糟糕的写法, 用了堆还内存泄露

void use_string() {
    // The string lives on the heap
    std::string *value = new std::string("Hello World");
}

记住,std::string本身也会在内部分配内存, 并且用的是堆, 如果你的目标是不用任何堆, 你需要用一些其它方法来打到。其实我们的目标是不分配不必要的堆。
总体来说, 用new 创建的对象(或者用make_unique或者make_shared创建的对象) 都是堆对象, 并且拥有动态存储周期(Dynamic Storage Duration), 在本地作用域下创建的对象都是栈对象, 并且具有自动存储周期(Automactic Storage Duration)

38. 不要再使用new

你已经避免使用堆并且使用智能指针来管理内存资源了对吧。再进一步, 在少数一些你需要用堆的情况下,请确保使用 std::make_unique<>()(C++14),在很少见的情况下你需要共享对象的所有权,这时候使用std::make_shared<>()(c++11)

39. 了解你的容器

优先以这种顺序选择你的容器:

  • std::array<>
  • std::vector<>

std::array
一个固定大小的分配在栈上的连续容器, 数据的多少必须在编译时期知道, 你必须拥有足够的栈空间去承载数据。这个容器可以帮助我们优先使用栈而不是堆。已知的位置以及内存连续性会让std::array<>是一个“负成本抽象"(negative cost abstraction)", 因为编译器知道数据的大小和位置, 它可以用额外的一系列优化手段来优化。

std::vector
一个动态大小分配在堆上的连续容器, 尽管编译器不知道数据最终会驻留在哪里, 但他知道元素的在内存中是紧密布局的。内存的连续性给了编译器更多的优化空间并且对缓存更加友好。

几乎任何其它事情都需要评论和解释原因, 对于小型的容器, 带有线性搜索的map可能比std::map性能要更好。但是别对这点太痴迷了, 如果你需要kv查找, 用std::map并且评估一下它是否有你想要的性能表现和特性。

40. 避免使用std::bind和std::function

尽管编译器继续提升,优化器也在继续解决这些类型的复杂性, 这些仍然有很有可能去增加编译时间和运行时间的开销。C++14 的lambda, 具有广义的捕获表达式功能, 能够做到和std::bind同样的事情。

用std::bind去改变参数的顺序

#include <functional>

double divide(double numerator, double denominator) {
    return numerator / denominator;

}


auto inverted_divide = std::bind(divide, std::placeholders::_2,std::placeholders::_1);

用lambda去改变参数的顺序

#include <functional>

double divide(double numerator, double denominator) {
    return numerator / denominator;

}


auto inverted_divide = [](const auto numerator, const auto denominator) {
    return divide(denominator/numerator)
}

41. 跳过c++11版本

如果你在正在转向”现代C++", 请跳过Cpp11版本, Cpp14修复了许多Cpp11的漏洞。

其中语言层面的特性包括:

  • C++11 版本的constexpr 隐式地指定了所有成员函数为const(即不能修改this), 这在C++14已经被改变。
  • C++11缺少对函数对auto 返回类型的推导(lambdas有)
  • C++11没有auto或者可变Lambda参数的
  • C++14新增[[deprecated]]特性
  • C++14新增了数字分隔符, 比如1'000'000
  • 在C++14里constexpr 函数可以有多个return

库层面特性包括:

  • std::make_unique 在C++14中加入
  • C++11没有std::exchange
  • C++14新增了对std::array的constexpr支持cbegin, cend, crbegin, 和crend 这些自由函数(free function ,没有入参的函数) 为了和begin 和end这些在C++11加入的标准容器中的自由函数保持一致而被加入了。

42. 对于重要的类型,不要使用initializer_list

Initializer List在C++里面是一个重载项, Initializer Lists被用于直接初始化数值。 initializer_list被用于向函数或者构造器传入一个value list。

接下来举几个例子讲一下initializer_list的一些异常行为(摘自本书作者的youtube上的讲解视频:

auto f(int i, int j, int k){
    return std::initializer_list<int>{i, j, k};
}


int main() {
   int argc = 1;
   for (int i: f(argc+1, argc+2, argc+3)){
       std::cout << i << ",";
   }
}

最终的结果实际上是个UB行为, 因为上面这段代码的函数f等价于

auto f(int i, int j, int k){
    return std::initializer_list<int>{i, j, k};
}

auto f(int i, int j, int k){
    const int __a[] = {i, j, k};
    return std::initializer_list<int>{__a, __a+3}; // pointer local
}

因此同理,下面这段代码是没有办法编译成功的(因为unique_ptr不能转让所有权):

#include <vector>#include <memory>

std::vector<std::unique_ptr<int>> data{
    std::make_unique<int>(40), std::make_unique<int>(2)
};

43. 使用工具:Build Generators

  • CMake
  • Meson
  • Bazel
  • Others

裸make file或者visual studio的项目文件让上面列出的每个东西都很棘手并难以去实现。使用build tool工具去帮助你维护在不同平台和编译器之间的可移植性。对待你的build script就想对待你的其它code一样, 它们也有自己的一套best practise, 并且非常容易就写出一个不易维护的build sciprt, 就像写出一个不可维护的C++代码一样。在使用cmake --build的情况下, Build generators同时也可以帮助抽象和简化你的持续集成环境, 这样无论你在用什么平台开发,都可以做出正确的事情。

44. 使用包管理工具

最近几年开发者对C++的包管理工具表现出了浓厚的兴趣, 有两个成为了其中最著名的包管理工具:

  • Vcpkg
  • Conan

使用一个包管理工具是绝对有好处的, 包管理工具可以提高可以提高可移植性并且降低开发人员的管理成本。

45. 缩短构建时间

对于减少构建时间带来的痛苦,有以下一些很实用的建议:

  • 将你的代码尽可能地去模板化(不是不用模板)
  • 在有意义的地方使用前向声明(所谓前向声明是指:A.h引用B.h, B.h又要引用A.h,这时候解决问题的办法是在B.h里面只声明一下A.h里面的A类)
  • 在你的build system中开启PCH(precompile headers)
  • 使用ccache
  • 了解unity builds
  • 了解外部模板(extern template)能做什么以及它的局限性
  • 使用构建分析工具去看构建时间被花费在了哪里

使用IDE

我所观察到的使用现代IDE最令人惊喜的一个特性就是:IDE对你的代码做了实时分析。实时分析就意味着你在键入代码的时候编译器就知道它是否要编译,因此你会花上更少的时间去等待构建。

46. 使用工具: 支持多编译器

在你的平台上你至少要支持两种编译器。每个编译器都会做不同的分析并且以一种略微不同的方式来实现标准。如果你使用Visual Studio,你应该能够在clang和cl.exe之间灵活切换。你还可以使用WSL并且开启远程linux 构建。

如果你使用linux系统,你应该能够在GCC和Clang之间能够灵活切换。

注意, 在macOS上,确保你在使用的编译器是你想使用的那个,因为gcc 命令很可能是一个苹果公司安装的clang的软连接

47. 模糊测试(Fuzzing)和变异测试(Mutating)

你的想象力限制了你能够构建的测试用例, 你是否有尝试恶意调用API?你有故意去传入一些格式错误的数据给你的输入吗?你是否处理一些未知或者未经信任的数据来源?

在所有可能的情况组合中为所有可能的函数调用生成所有可能的输入是不可能的, 很幸运的是,有工具来为我们解决这些问题。

模糊测试
模糊测试工具能够生成各种长度的随机字符串,这种测试能够约束你用合适的方法去处理这些数据, 模糊测试工具分析那些从你的测试执行过程中生成出来的覆盖率数据并且使用那些信息去删除多余的测试并且产生新且特殊的测试用例。

理论上来说,如果给它足够的时间,模糊测试可以对你需要测试的代码最终达到100%的代码覆盖率。结合AddressSanitizer, 它可以成为寻找你代码中bug的强有力的工具。在一个很有趣的文章里面描述了模糊测试工具和AddressSainitzer的组合是如何在小于6个小时内发现OpenSSL的安全漏洞的。

变异测试
编译测试是通过修改你代码里的条件和变量来进行测试工作的,举个例子:

bool greaterThanFive(const int value) { 
    return value > 5; // comparison
}

void tests() {
 assert(greaterThanFive(6));
 assert(!greaterThanFive(4));
}

编译测试能够修改你的常量5或者>运算符, 所以你的code变成:

bool greaterThanFive(const int value) {
    return value < 5; // mutated
}

任何能够继续通过的测试用例都属于"存活下来的变异测试用例", 这就预示着要么你的代码存在bug要么这是一个有缺陷的测试。

48. 继续你的C++学习

如果你想变得更强你就要持续不断地学习, 世面上有许多你可以在你的C++学习上使用在资源。(++若干年后, 你就会发现,你变禿了,也变强了++)

Python数据结构

本部分我们来详述基础的数据结构,如栈、队列、字典、元组、树、链表等。

风靡全球的创造力培养方法-设计思维 Design Thinking

image

“把学习带到现实中,让孩子用自己的力量创造改变,可以直接提升他们的幸福感和竞争力。”

这是“全球孩童创意行动”的发起人——Kiran Sethi在TED演讲时说的一句话,这个行动旨在引导中小学生主动寻找现实问题,并创造性地解决它。这种能力对于今天的孩子来说,可谓至关重要,世界经济论坛今年发布了“2020年人才市场最看重的10项技能”,就把“Complex Problem Solving(解决复杂问题)”放在了第一位。

image

而世界上所有注重创新教育的国家和地区,也都极其注重训练孩子解决现实问题的能力。具体如何训练呢?欧美学校都钟爱一个名为“设计思维”(Design Thinking)的创造力训练方法。

“设计思维”发源于设计界,后来被各行各业借鉴,斯坦福大学设计学院把它归纳成一套科学方法论后,迅速风靡全球高校和中小学。它一共分为下图这5个步骤,引导孩子们以“人的需求”为中心,通过团队合作解决问题,获得创新。

image

同理心(Empathy):收集对象的真实需求

定义(Define):分析收集到的各种需求,提炼要解决的问题

头脑风暴(Ideate):打开脑洞,创意点子越多越好

原型制作(Phototype):把脑子中的想法动手制作出来

测试(Test):优化解决方案

有人把这种方法类比为一本菜谱,告诉你烧菜的步骤、烧的时间等,虽然每个人用它炒出来的东西都不一样,但只要跟着这本菜谱仔细做,一般都不会做得太难吃。本文将把5大步骤逐一介绍,形成一份详细的“设计思维攻略”,也可称为“创造性解决问题攻略”,值得为孩子收藏。

▋一、学会用“同理心”思考问题,而非“同情心”

设计思维的第一步,是建立“同理心”,这是一种设身处地体会他人感受的思考方式,和“同情心”有本质不同,比如有个人掉到山洞了,有“同情心”的人会说,“你好可怜。”但有“同理心”的人会说,“洞里这么冷,你一定不好受。”

image

一个经典的同理心练习是:对着别人,用你左手的食指和右手的食指,摆一个“人”字。你发现了什么呢?——你得摆一个“入”字。

image

有2个常见方法,可以帮助孩子快速进入“同理心”模式:

方法1:角色扮演

了解一个人最好的方法,就是成为那个人。“角色扮演”让人们得以亲身体验对象的处境,用感同身受,代替主观臆测。这种思考,比先入为主的“我以为”,要更深刻。

举个例子。我们经常鼓励孩子参与公益,帮助社会上有需要的人,却极少引导孩子思考“我给别人的帮助,真的是别人需要的吗?”比如灾区的人们更需要食物和被褥,我们却一厢情愿给人家寄去旧衣服(有些甚至都没有消毒干净),到头来也只是感动了自己。

怎么给特殊群体更好的帮助呢?离我们很近的香港理工大学的学生就曾面临这个问题,他们要为盲人做设计,动手之前,他们做了一个“一小时盲人体验”——蒙上眼睛,户外探索一小时。期间,学生们互相搀扶着上下楼梯,偶尔会碰上树木,在几个空间里探索前行,聆听声音,触摸不同的材质等。

整个过程中,不时有学生发出呼救声,但慢慢地,他们安静下来,把自己当成盲人,去思考一些问题,并决定,“与其设计一些东西帮助他们,不如设计一些东西让他们享受正常生活。”下图是其中一位学生,Kevin Chan设计的“盲人用的跑步机”:提供了一个安全的跑步地带,模仿户外的情景。

image
(“盲人用的跑步机”,上图源自《创意工具》一书)

“亲身体验”的经历,也更能激发孩子们解决问题的动力。

比如印度排名前十的河沿小学的五年级学生,有一堂课叫“儿童权益”,学习这门课时,学生们就被要求制作庙里所用的香,连续工作八个小时,以体验童工的境遇。

当孩子连续工作了两个小时后,他们后背酸痛,连续工作一天后,他们的心态改变了——自发走上街头,用切身感受说服城市里的每一个人:童工制度必须被禁止!他们和企业家、工头诚恳对话,表达自己这一愿景,后来成功说服大量作坊停止采用童工。

image

方法2:采访

采访是记者的基本功,但从小掌握采访的技巧,能让孩子更高效地收集自己想要的信息。而采访最看重的,就是提问的技巧。如何提问呢?最基本的原则是“5个W,1个H”:
image

Who?谁?

Doing What?做什么?

When?什么时候?

Where?在哪儿?

Why?为什么?

How did it look?How did you feel?长什么样子?你什么感受?……

两个常用的技巧是:

寻找不一致,用得体的方式指出人们说话、做事前后不一致的地方;

注意语言之外的信号,比如肢体语言和情绪。

采访的技巧,美国男孩Logan La Plante就玩得很溜。他从小在家上学,但学习面和知识深度比同龄人领先很多,奥秘就是他经常拜访一些网络科技公司,免费在那里工作,采访专业人士;晚上则请一个不同行业的陌生人喝咖啡,接触不同领域的知识……

▋二、学会发现真实问题,并且重新定义它

爱因斯坦曾说,“如果只给我一个小时拯救地球,我会花59分钟找准核心问题,然后用1分钟解决它。(If I were given one hour to save the planet, I would spend 59 minutes defining the problem and one minute resolving it.)”设计思维的第二步,就是要“定义正确的问题”。

它的意思是,明确问题到底出在哪里,并用一句很精简的话告诉别人,“你想如何解决什么问题”。有一个常用句式——“How might we……”(我们该如何……),下图就是个很好的例子:

image

我们该(如何),为(谁),做点(什么),好解决(什么问题)

我们该(如何),为(会员),填平他们在文化和技术上的差距,好(促进大家的交流)

再举个例子。美国“天赋教育”(相当于我们的尖子班)有一道经典的领导力训练题:

“假设在2097年,你和你的宇宙飞船机组成员正执行一次百年宇宙飞行的使命。着陆时,却遇到了问题:你们降落在错误的着陆点上。在测量设备全毁的情况下,你将如何率领团队走出绝境?”

image

套用“How might we”句式,问题就从“我如何率领团队走出绝境”,聚焦成了“我如何收集尽可能多资料,尽快确定地理位置,联系救援”。

▋三、学会提出更多更有创意的解决方法

上面两步——“同理心——定义”是“发现问题”,从“头脑风暴”开始,孩子进入“解决问题”阶段。

所谓“头脑风暴”,要求孩子尽可能多地写下脑海中一闪而过的创意点子,不拒绝任何疯狂的想法。但它可不是让孩子坐在那里苦思冥想,下面是一些常见的练习,能让我们打破惯有思维的局限。

方法1:黄金60秒

看看下图左上角这坨蓝色,你会联想到什么呢?云朵?水杯图案?水渍?……给自己60秒,然后写下你想到的所有可能。
image

“头脑风暴”经常会设置一个很短的时间,给人们紧迫感。少年商学院曾受新加坡国家设计中心邀请,带领**孩子前往参加设计思维工作坊,期间,学生们就被要求,根据采访的收获,在30分钟内想出100个方法,解决他们锁定的对象正在面临的问题……头脑风暴后,又要快速筛选创意,几十个创意经过小组讨论和筛选,只留下了1-2个,成为设计方案的核心。
image

方法2:自由联想

我们曾分享过现在很流行的思维工具——思维导图的使用指南(点此获取),其中有一张“树状图”就特别适合用来收集发散的想法,比如,设置中心词为“月圆”,你能想到什么呢?写在旁边。
image

方法3:强制类比

和“自由联想”不同,“强制类比”选中的事物往往看上去八竿子打不着,但越是天差地别,摆到一起还一定要找出相同点时,结果越是有趣。思维导图中的“双重气泡图”就很适合用来做这类梳理,比如下图,把灯泡和球鞋放在了一起:

image

强制类比能给我们很多灵感,比如锯子借鉴了茅草边的锯齿,雨伞借鉴了香菇的形状。

▋四、学会试错:把脑子里的想法“拿”出来试错优化

最后两个步骤——“原型制作”和“测试”,经常是结伴出现的。

所谓“原型制作”,就是动手把脑子中的想法制作成一个看得见摸得着的实体模型。不用拘泥于哪种特定的工具,只要孩子喜欢,乐高、橡皮泥、现正流行的一款游戏“Minecraft(我的世界)”都可以。

众所周知,欧美学校非常注重培养孩子动手的习惯和能力,这不仅为让孩子检验自己构想的方案是不是具有真实的可操作性,更能帮助孩子打破“完美主义”,在实践中找到优化解决方案的新思路,而不是纸上谈兵。

image
(斯坦福设计学院的工具墙)

image
(今年暑假,少年商学院受到斯坦福设计学院姊妹院校——德国波茨坦大学设计学院的邀请,带领**孩子前往参加工作坊。上图为学院里的工具墙)

它的目的,是做“测试”,看看对方的问题是不是真的得到了解决。这不可能一蹴而就,不断试错的过程,也在培养孩子的耐心和抗挫折能力。

▋收获创造力,更收获创造力自信

这5个步骤是一个环环相扣的紧密的循环。比如下面这6个初中生,就运用设计思维,用6天5晚的时间,改造了ATM(自动取款机):

1、同理心:收集需求

他们首先请教了银行行长,了解“客户怎么使用ATM?他们最经常投诉的问题是什么?”、“ATM盗窃最经常发生在哪个国家?”这类专业问题;再到ATM生产基地实地考察,询问专业设计师,“互联网金融这么发达,支付宝早就代替了银行,对ATM业务会有影响吗?”
image
然后走上街头,采访刚使用完ATM的路人,但并不顺利,每5个人,只有1个人愿意接受采访。

image

2、定义:找准问题

根据上一阶段收集来的需求,他们列表区分开“我们认为他们需要的”和“他们实际需要的”。
image

然后再筛选出这些具体的核心问题。

image

3、头脑风暴:发现解决方案

对于孩子来说,用创意写满一页纸,其实不难。但有时创意没了极限,他们就请设计师给自己“泼冷水”,再次反思——“这些是你们喜欢的功能,还是用户真正需要的功能?”
image

4、原型制作:把想法从脑子里“拿”出来
image

5、公开展示:收集优化建议

最后,6人小组带着自己做好的模型站上了演讲台,向坐在台下的ATM设计师、银行高管演示自己的方案:他们的ATM穿了一层自动感应的光电玻璃,人操作的时候,透明玻璃变为磨砂,保护隐私;玻璃外墙还可以卖广告位,不仅让排队的人不无聊,还能为银行带来二次收入……

这6个学生是少年商学院在2014年于广州开展的“设计思维工作坊之ATM大作战”的同学。工作坊开展之前,他们大都连银行卡都没用过,更不了解ATM了,但最后他们却通过设计思维流程,非常有同理心地提出了改造ATM机的方案,让ATM制造业上市公司首席设计师和银行高管都为其点赞。其中一位同学后来把这段学习经历分享给了她申请美国高中的面试官,她现在已在心仪的高中就读。还有一位同学说,工作坊结束后回到家,“看见什么都想改造一下,很多东西都存在问题,但大人们习以为常了。”

就像文章开头说的,让孩子主动发现并解决现实中的问题,能让孩子收获“幸福感”。非常幸运的是,少年商学院获全球两大设计思维泰斗—— 美国斯坦福大学设计学院(d.school)创办人和德国波茨坦大学d.school创办人的授权与认可,向**少年普及原汁原味的设计思维方法与课程。

image

(少年商学院教研总监Evan与斯坦福大学d.school创办人David Kelley)

而这个过程给我们的最大的体会就是——**孩子从不缺乏创造力,缺乏的只是展示创造力的机会和自信。在这方面的提升上,“设计思维”是绝佳工具。

这个世界原本属于一群高喊“知识就是力量”的理性思考族群,但现在,它将属于一些有高的感性能力的人——他们富有创造力、具有同理心、能观察趋势以及讲述故事的动情表达能力。

——Daniel Pink,纽约时报撰稿人、美国前副总统戈尔演讲撰稿人

Lua菜鸟教程

Lua教程

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的,该小组成员有:Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。

其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。其特性如下:

  • 轻量级:它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里
  • 扩展性:Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样
  • 自动内存管理:只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象
  • 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持
  • 支持面向过程(procedure-oriented)编程和函数式编程(functional programming)
  • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等

Lua基本语法

lua标识符与其他C++类似,区分大小写,保留字及lua内部全局变量多以下划线+大写字母组成(如_VERSION)。

1 true false nil
2 while for repeat
3 do end util return
4 if else elseif then
5 break goto
6 and not or
7 function in local

全局变量

默认情况下,变量总是全局的。不需要声明,赋值即创建;访问未初始化全局变量也不会出错,只是结果为 nil ;删除全局变量只需要将其赋值为nil即可。

数据类型

Lua是动态类型,变量不需要类型定义,只需要变量赋值。值可以存储在变量中,作为参数传递或者结果返回。

lua中有8种基本类型:nil、boolean、number、string、userdata、function、thread、table。

数据类型 描述
nil 一个无效值,如一个没有赋值的变量
对于全局变量和table,执行删除作用
做比较时需要加上双引号
boolean true或者false
nil看作是false,数字0为true
number 双精度实浮点数,double
默认类型可以修改luaconf.h里的定义
string 字符串,单双引号标识
可以使用[[]]表示一块多行字符串,如赋值为xml等时很有用
字符串进行算术操作,会尝试转为一个数字
字符串连接使用两个点".."
#用来计算字符串长度,放在字符串前
function 由c或者lua编写的函数
函数被看作第一类值(First-Class value)
可以以匿名函数的方式通过参数传递
userdata 用户自定义数据,存储在变量种,代表任意应用程序或者C/C++语言库所创建的数据结构及类型,可以将任意C/C++的任意数据类型的数据(通常是struct或者指针)存储在lua变量种
thread 执行的独立线路,执行协同程序
在lua中最主要的线程是协同程序(coroutine),和线程差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
table 表,关联数组,索引可以是数字、字符串或者表类型,通过构造表达式创建,如{}创建一个空表
lua的表默认初始化索引从1开始,不同于C++从0开始
不固定长度大小,新增数据自动增加长度,未初始化的table都是nil
支持[]和.点号两种索引方式

可以使用type函数来测试变量的类型和值的类型:

print(type(10.1*5))
print(type(print))

线程和协程的区别:线程可以同时多个运行,协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起时才能暂停。

变量

使用前必须代码中声明,创建变量。编译程序执行代码前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。

lua有三种变量类型:全局、局部、表中的域。语句块或者函数中的均为全局变量,local显式声明方为局部变量,局部作用域从声明位置开始到所在语句块结束。应尽可能使用局部变量,以避免命名冲突,且访问局部变量速度更快。

赋值时改变一个变量的值和改变表域的最基本方法,可以多变量同时赋值,用逗号隔开依次给左侧变量赋值。先计算右侧的值然后赋给左侧变量。

循环

循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。能否继续重复,决定循环的终止条件。循环语句是由循环体及循环的终止条件两部分组成的。

image

循环类型 描述
while循环 条件为true时重复执行,先检查后执行
for循环 重复执行次数在for中控制
repeat-until 重复执行,直到指定条件为真
循环嵌套 while do end; for do end; repeat until

流程控制

流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。

image

语句 描述
if语句 一个布尔表达式作为条件判断,后紧跟其他语句
if-else语句 if假执行else
if嵌套 在if-else-if中使用if-else-if

函数

函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。Lua 提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。

Lua 编程语言函数定义格式如下:

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

lua支持多返回值,用逗号隔开;可变参数类似C语言三个点号,{...}表示可变长参数组成的数组表,也可以用select("#", ...)来获取可变参个数;select(n, ...) 返回n到select(“#”, ...)的参数。

运算符

序号 类型 操作符 描述
1 算术运算符 +、-、*、/、%、-(负号)、^(乘幂, A^2=100)
2 关系运算符 ==、~=、>、<、>=、<=
3 逻辑运算符 and、or、not
4 其他运算符 两个点号(..连接字符串),#一元操作符返回字符串长度

字符串

字符串由数字、字母、下划线组成。可以使用单引号、双引号、[[]]三种形式定义。不能直接显示的字符,可以使用\转义。

  • upper:转大写
  • lower:转小写
  • gsub:替换
  • find:搜索
  • reverse:反转
  • format:类print的格式化字符串, %c数字ASCII表字符
  • char:将整形数字转成字符串并连接
  • byte:字符转整数值
  • len:长度
  • rep:返回字符串的n个拷贝
  • ..两个点号:链接
  • gmatch:返回符合pattern的字串
  • match:寻找第一个pattern匹配

格式字符串可能包含以下的转义码:

  • %c - 接受一个数字, 并将其转化为ASCII码表中对应的字符
  • %d, %i - 接受一个数字并将其转化为有符号的整数格式
  • %o - 接受一个数字并将其转化为八进制数格式
  • %u - 接受一个数字并将其转化为无符号整数格式
  • %x - 接受一个数字并将其转化为十六进制数格式, 使用小写字母
  • %X - 接受一个数字并将其转化为十六进制数格式, 使用大写字母
  • %e - 接受一个数字并将其转化为科学记数法格式, 使用小写字母e
  • %E - 接受一个数字并将其转化为科学记数法格式, 使用大写字母E
  • %f - 接受一个数字并将其转化为浮点数格式
  • %g(%G) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式
  • %q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
  • %s - 接受一个字符串并按照给定的参数格式化该字符串

为进一步细化格式, 可以在%号后添加参数. 参数将以如下的顺序读入:

  • (1) 符号: 一个+号表示其后的数字转义符将让正数显示正号. 默认情况下只有负数显示符号.
  • (2) 占位符: 一个0, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格.
  • (3) 对齐标识: 在指定了字串宽度时, 默认为右对齐, 增加-号可以改为左对齐.
  • (4) 宽度数值
  • (5) 小数位数/字串裁切: 在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位.

数组

数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。

迭代器

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。

  • 泛型 for 迭代器:自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量
  • 无状态的迭代器:迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素
  • 多状态的迭代器:迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量

表table

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。Lua table 是不固定大小的,你可以根据自己需要进行扩容。Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。

  • concat:列出参数中指定的数组部分从开始到结束位置的所有元素
  • insert:插入
  • maxn:key为整数的最大key值
  • remove:删除
  • sort:升序排序

模块与包

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
 
-- 定义一个常量
module.constant = "这是一个常量"
 
-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end
 
local function func2()
    print("这是一个私有函数!")
end
 
function module.func3()
    func2()
end
 
return module

Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
 
print(module.constant)

module.func3()

对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。

require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。

当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"
#更新环境变量参数,使之立即生效
source ~/.profile

如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。

Lua和C是很容易结合的,使用 C 为 Lua 写包。与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。

元表(Metatable)

在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。

有两个很重要的函数来处理元表:

  • setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败
  • getmetatable(table): 返回对象的元表(metatable)

__index 元方法是 metatable 最常用的键。当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。

Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

    1. 在表中查找,如果找到,返回该元素,找不到则继续
    1. 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
    1. 判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

__newindex 元方法用来对表更新,__index则用来对表访问 。当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。

协同程序

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

-- coroutine_test.lua 文件
co = coroutine.create(
    function(i)
        print(i);
    end
)
 
coroutine.resume(co, 1)   -- 1
print(coroutine.status(co))  -- dead
 
print("----------")
 
co = coroutine.wrap(
    function(i)
        print(i);
    end
)
 
co(1)
 
print("----------")
 
co2 = coroutine.create(
    function()
        for i=1,10 do
            print(i)
            if i == 3 then
                print(coroutine.status(co2))  --running
                print(coroutine.running()) --thread:XXXXXX
            end
            coroutine.yield()
        end
    end
)
 
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
 
print(coroutine.status(co2))   -- suspended
print(coroutine.running())
 
print("----------")

coroutine在底层实现就是一个线程;当create一个coroutine的时候就是在新线程中注册了一个事件;
当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件;coroutine.running() 返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用running的时候,就是返回一个 corouting 的线程号。

文件IO

Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式:

  • 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作
  • 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法

错误处理

程序运行中错误处理是必要的,在我们进行文件操作,数据转移及web service 调用过程中都会出现不可预期的错误。如果不注重错误信息的处理,就会造成信息泄露,程序无法运行等情况。

  • 语法错误:通常是由于对程序的组件(如运算符、表达式)使用不当引起的
  • 运行错误:运行错误是程序可以正常执行,但是会输出报错信息

我们可以使用两个函数:assert 和 error 来处理错误。assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出;error函数终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回),error会附加一些错误位置的信息到message头部。

Lua中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。

pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。

if pcall(function_name, ….) then
-- 没有错误
else
-- 一些错误
end

pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但pcall返回时,它已经销毁了调用桟的部分内容。Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。

调试

Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试器代码。

  • 命令行调试器:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。
  • 图形界调试器:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit。

垃圾回收

Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。

Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。

垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。

垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。

如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。

面向对象

LUA中最基本的结构是table,所以需要用table来描述对象的属性。lua 中的 function 可以用来表示方法。那么LUA中的类可以通过 table + function 模拟出来。至于继承,可以通过 metetable 模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)。Lua 中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。

-- 元类
Shape = {area = 0}

-- 基础类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end

-- 基础类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)

myshape:printArea()
-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end
-- 基础类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()

Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
  o = o or Shape:new(o,side)
  setmetatable(o, self)
  self.__index = self
  return o
end

-- 派生类方法 printArea
function Square:printArea ()
  print("正方形面积为 ",self.area)
end

-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()

Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
  o = o or Shape:new(o)
  setmetatable(o, self)
  self.__index = self
  self.area = length * breadth
  return o
end

-- 派生类方法 printArea
function Rectangle:printArea ()
  print("矩形面积为 ",self.area)
end

-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()

数据库访问

Lua 数据库的操作库有一个开源的LuaSQL,支持的数据库有:ODBC, ADO, Oracle, MySQL, SQLite 和 PostgreSQL。LuaSQL 可以使用 LuaRocks 来安装可以根据需要安装你需要的数据库驱动。

require "luasql.mysql"

--创建环境对象
env = luasql.mysql()

--连接数据库
conn = env:connect("数据库名","用户名","密码","IP地址",端口)

--设置数据库的编码格式
conn:execute"SET NAMES UTF8"

--执行数据库操作
cur = conn:execute("select * from role")

row = cur:fetch({},"a")

--文件对象的创建
file = io.open("role.txt","w+");

while row do
    var = string.format("%d %s\n", row.id, row.name)

    print(var)

    file:write(var)

    row = cur:fetch(row,"a")
end


file:close()  --关闭文件对象
conn:close()  --关闭数据库连接
env:close()   --关闭数据库环境

5.2 版本之后,require 不再定义全局变量,需要保存其返回值。

Lua参考手册

PHP基础知识

本部分我们将来学习PHP的语言的基础知识。

一、语言基础

<?php
 echo "XML风格";  // 其他标记风格请自行google,这里统一使用此种风格
?>

1. PHP的数据类型

php支持8种原始类型:4种标量类型(boolean、integer、float/double、string),2种复合类型(array、object),2种特殊类型(resource、null)。PHP种数据类型是根据变量使用的上下文运行时决定的。可以强制转换以及判断类型, 如settype, is_bool。

  • 美元$是变量的标识符,所有变量都以$开头,无论声明还是调用变量。
  • 字符串使用单双引号来定义,双引号种若有变量会自动替换为实际值,单引号不会
  • 单引号只需要对单引号转义,双引号注意对双引号、美元符号等字符的使用
  • 界定符:<<<,结束标识符必须单独一行,且不许有空格,且之后不能有其他字符

数组

数组是一组类型相同的变量的集合,数组的每个数据称为一个元素,包括索引和值两部分。索引可以由数字或者字符串组成。

资源

资源是一种特殊变量,又称作句柄,保持外部资源的一个引用,通过专门的函数来创建和使用。

PHP常量

值不变的量,定义后脚本任何地方都不能改变。define,defined

PHP变量

变量通过一个变量名来定义,系统对程序种的每一个变量分配一个存储单元。PHP变量使用前无需声明,变量标识符区分大小写。

2. PHP的运算符

算术运算符

加减乘除、取余、递增递减与C++类似。

字符串运算符

英文句号,连接两个字符串。

赋值运算符

赋值操作从右而左

位运算符

对二进制位从低位到高位对齐后的运算,包含按位与或异或非,左右移位。

逻辑运算符

比较运算符

错误控制运算符

$err = @(5 / 0);

三元运算符:三目运算符

3. PHP的表达式

表达式是PHP的基本元素和重要组成部分,使用分号来区分表达式和语句。

4. PHP函数

函数就是将一些重复使用到的功能写到一个独立的代码块种,需要时单独调用。

function hi() {
}

不同于其他语言的是,包含变量函数和函数的引用。

4. 编码规范

  • 统一使用4个空格缩进
  • 大括号
  • 小括号和关键字空格隔开
  • 小括号和函数紧贴
  • 运算符与变量空格隔开
  • 段落:合理加入空白行
  • 类命名、属性命名、方法命名、形参命名、变量命名、引用变量和引用函数命名、全局变量命名、常量全局常量命名、静态变量命名、函数命名等

Python编程实战

一、编程练习

二、项目实战(两个项目)

  • 简单计算器:熟悉Tkinter
  • 网站:
    • 数据库基础:基本SQL查询、函数、关系数据库、内外连接等
    • 使用python数据库:利用一种数据库框架连接,创建插入数据和读取
    • API:学习json、微服务、表现层应用程序转换应用程序接口(RestAPI)
    • numpy:https://github.com/rougier/numpy-100
    • 网站搭建:django,flask框架
    • 单元测试、日志、调试:pytest

三、Python框架

四、使用集成开发环境:IDE->Github->Hosting->Services

CMake与VisualStudio工程配置映射

本文将迁移至Rapid C++: CMake与VisualStudio工程配置映射

本文整理了工作中常用的CMake与VisualStudio工程配置的映射关系,便于基于现有VS工程迁移到CMake,以及保持开源项目与现有项目的编译兼容性。

工作中的项目工程使用VisualStudio2019进行编译,而开发机已经升至最新的VisualStudio2022,故而会包含相关版本上的兼容讨论。工程配置以VisualStudioCommunity2022Preview版本为参考。

一、CMake概念与配置

参考CMake Wiki.

1. CMakeList.txt:常规CMake配置文件,配置工程名称、生成选项等,是所有生成所必须的

PROJECT(main)
CMAKE_MINIMUM_REQUIRED(VERSION 3.15)
SET(CMAKE_SOURCE_DIR .)
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
AUX_SOURCE_DIRECTORY(. DIR_SRCS)
ADD_EXECUTABLE(main ${DIR_SRCS})

2. CMAKE_BUILD_TYPE:可枚举值为Debug、Release、RelWithDebInfo、MinSizeRel

  • Debug:CMake会使用CMAKE_FLAGS_DEBUG和CMAKE_C_FLAGS_DEBUG中的字符串作为编译选项生成Makefile
  • Release:使用CMAKE_CXX_FLAGS_RELEASE和CMAKE_CFLAGS_RELEASE选项生成Makefile

3. CMAKE_EXE_LINKER_FLAGS:链接器标志

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /machine:x86")

4. add_dependencies:项目引用Reference,添加依赖

5. source_group( header FILES includeme.h ):Put files into folders

二、Visual Studio与CMake配置映射表

参考Windows C++ project property page reference.

Visual Studio Configuration Properties 常用修改配置 CMake 备注
Configuration Debug/Release/RelWithDebInfo/MinSizeRel CMAKE_BUILD_TYPE debug和release版本
General=>General Properties 通用配置
Output Directory 输出路径,$(ROOT)\pub\bin$(Configuration)
Intermediate Directory 临时文件路径,$(ROOT)\intermediate$(Configuration)$(ProjectName)
Target Name 项目名字,$(ProjectName)
Configuration Type image 项目生成类型,exe/dll/lib/utility
Windows SDK Version 10.0(latest installed version)
Platform Toolset Visual Studio 2019(v142)
C++ Language Standard Default(ISO C++ 14 Standard)
C Language Standard Default(Legacy MSVC)
Advanced=>Advanced Properties 高级
Target File Extension 目标文件扩展类型,.dll/.lib/.exe等
Extensions to Delete On Clean 清理时删除的文件扩展类型,如*.obj;.tlog;.pch;.exp;.idb;*.pdb;等
Build Log File 编译日志文件,$(IntDir)$(MSBuildProjectName).log
Preferred Build Tool Architecture 64-bit (x64) 推荐的编译工具架构,推荐使用64位以加快编译速度
Use Debug Libraries No
Enable Unity (JUMBO) Build
Copy Content to OutDir
Copy Project References to OutDir
Copy Project References's Symbols to OutDir
Copy C++ Runtime to OutDir
Use of MFC Use Standard Windows Libraries
Character Set Use Unicode Character Set 字节设置,多字节/单字节
Whole Program Optimization No Whole Program Optimization 全程序优化,目前的基础架构依赖于google开源,推荐使用无全程序优化,以保障编译
MSVC Toolset Version Default,默认无改动
Advanced=>C++/CLI Properties C++/CLI属性配置
Common Language Runtime Support
.Net Target Framework Version
Enable Managed Incremental Build
Debugging=>Debugger to launch: Local Windows Debugger Windows本地调试配置,大部分时候为默认值,仅特殊情况下设置命令行参数进行启动传参调试
Command
Command Arguments
Working Directory
Attach
Debugger Type
Environment
Merge Environment
SQL Debugging
Amp Default Accelerator
VC++ Directories=>General VC++环境路径,虽然工程中修改此处配置但是个人不推荐
Executable Directories
Include Directories 建议配置C/C++中的Additional Include Directories
External Include Directories
Reference Directories
Library Directories 建议配置Linker中的Additional Library Directories
Library WinRT Directories
Source Directories
Exclude Directories
VC++ Directories=>Public Project Content
Public Include Directories
All Header Files are Public
Public C++ Module Directories
All Modules are Public
C/C++=>General C/C++通用配置
Additional Include Directories include_directories 附加包含路径
Additional #using Directories
Additional BMI Directories
Additional Module Dependencies
Additional Header Unit Dependencies
Scan Sources for Module Dependencies
Translate Includes to Imports
Debug Information Format Program Database for Edit And Continue(/Zi) set(CMAKE_C_FLAGS_DEBUG_INIT "/Zi")
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/Zi")
调试信息格式
Support Just My Code Debugging
Common Language RunTime Support set_target_properties( target PROPERTIES COMPILE_FLAGS “/clr”)
set_target_properties( target PROPERTIES COMPILE_FLAGS “/clr:pure”)
set_target_properties( target PROPERTIES COMPILE_FLAGS “/clr:safe”)
set_target_properties( target PROPERTIES COMPILE_FLAGS “/clr:oldSynax”)
公共语言运行时
Consume Windows Runtime Extension
Suppress Startup Banner set_target_properties( target PROPERTIES COMPILE_FLAGS “/nologo” ) 取消启动版权标志和信息
Warning Level Level3(/W3) set_target_properties( target PROPERTIES COMPILE_FLAGS “/W0” )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/W1” )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/W2” )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/W3” )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/W4" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Wall” )
警告级别
Treat Warnings As Errors set_target_properties( target PROPERTIES COMPILE_FLAGS “/WX-" )#No
set_target_properties( target PROPERTIES COMPILE_FLAGS “/WX" ) #Yes
将警告认为是错误,默认设置为No,个别时候设置为True
Warning Version
Diagnostics Format
SDL checks
Multi-processor Compilation set_target_properties( target PROPERTIES COMPILE_FLAGS “/MP" )#Yes
#Don’t set means No
多核心编译
Enable Address Sanitizer
C/C++=>Optimization 优化
Optimization Disabled(/Od) set(CMAKE_C_FLAGS_DEBUG_INIT "/Od")
set(CMAKE_C_FLAGS_MINSIZEREL_INIT "/O1")
set(CMAKE_C_FLAGS_RELEASE_INIT "/O2")
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/O2")
警用优化
Inline Function Expansion set(CMAKE_C_FLAGS_DEBUG_INIT "/D_DEBUG /MTd /Zi /Ob0 /Od /RTC1")
set(CMAKE_C_FLAGS_MINSIZEREL_INIT "/MT /O1 /Ob1 /D NDEBUG")
set(CMAKE_C_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
Enable Intrinsic Functions Yes(/Oi) set_target_properties( target PROPERTIES COMPILE_FLAGS “/Oi" ) #yes
#Don’t set means no
开启内联
Favor Size Or Speed set_target_properties( target PROPERTIES COMPILE_FLAGS “/Os" ) #size
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Ot" ) #speed
#Don’t set means neither
Omit Frame Pointers set_target_properties( target PROPERTIES COMPILE_FLAGS “/Oy-" ) #no
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Oy" ) #yes
Enable Fiber-Safe Optimizations set_target_properties( target PROPERTIES COMPILE_FLAGS “/GT" ) #yes
#not setting means no
Whole Program Optimization No set_target_properties( target PROPERTIES COMPILE_FLAGS “/GL" ) #yes
#not setting means no
C/C++=>Preprocessor 预处理
Preprocessor Definitions _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS;%(PreprocessorDefinitions) set_target_properties( target PROPERTIES COMPILE_DEFINITIONS DEFNAME=DEFVAL )
set_source_files_properties( filename.cpp PROPERTIES COMPILE_DEFINITIONS DEFNAME=DEFVAL )
预定义宏
Undefine Preprocessor Definitions set_target_properties( target PROPERTIES COMPILE_FLAGS “/UDEFNAME" )
Undefine All Preprocessor Definitions set_target_properties( target PROPERTIES COMPILE_FLAGS “/u" )
Ignore Standard Include Paths set_target_properties( target PROPERTIES COMPILE_FLAGS “/X" )
Preprocess to a File set_target_properties( target PROPERTIES COMPILE_FLAGS “/P" )
Preprocess Suppress Line Numbers set_target_properties( target PROPERTIES COMPILE_FLAGS “/EP" )
Keep Comments set_target_properties( target PROPERTIES COMPILE_FLAGS “/C" )
Use Standard Conforming Preprocessor
C/C++=>Code Generation 代码生成
Enable String Pooling set_target_properties( target PROPERTIES COMPILE_FLAGS “/GF" ) #yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/GF-" ) #no
Enable Minimal Rebuild No(/Gm-) set_target_properties( target PROPERTIES COMPILE_FLAGS “/Gm" ) #yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Gm-" )#no
最小化生成
Enable C++ Exceptions Yes(/EHsc) set_target_properties( target PROPERTIES COMPILE_FLAGS “/EHsc" ) #yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/EHa" ) #yes, with SEH exceptions
set_target_properties( target PROPERTIES COMPILE_FLAGS “/EHs" ) #yes, with extern C functions
#not setting means no
Smaller Type Check set_target_properties( target PROPERTIES COMPILE_FLAGS “/RTCc" ) #yes
not setting means no
Basic Runtime Checks set_target_properties( target PROPERTIES COMPILE_FLAGS “/RTCs" ) #Stack frame check
set_target_properties( target PROPERTIES COMPILE_FLAGS “/RTCu" ) #Uninitialized Variable
set_target_properties( target PROPERTIES COMPILE_FLAGS “/TRC1" ) #Both
#not setting means no
Runtime Library Multi-threaded DLL(/MD) set(CMAKE_C_FLAGS_DEBUG_INIT "/MTd")
set(CMAKE_C_FLAGS_MINSIZEREL_INIT "/MT")
set(CMAKE_C_FLAGS_RELEASE_INIT "/MT")
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/MT")
Change the default flags for specific config.
多线程Dll,同进程组件需要保持一致
Struct Member Alignment set_target_properties( target PROPERTIES COMPILE_FLAGS “/Zp1" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Zp2" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Zp4" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Zp8" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Zp16" )
Security Check set_target_properties( target PROPERTIES COMPILE_FLAGS “/GS" ) #yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/GS-" ) #no
Control Flow Guard
Enable Function-Level Linking Yes(/Gy) set_target_properties( target PROPERTIES COMPILE_FLAGS “/Gy" ) #yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Gy-" ) #no
开启函数级别链接
Enable Parallel Code Generation
Enable Enhanced Instruction Set set_target_properties( target PROPERTIES COMPILE_FLAGS “/arch:SSE" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/arch:SSE2" )
Floating Point Model set_target_properties( target PROPERTIES COMPILE_FLAGS “/fp:precise" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/fp:strict" )
set_target_properties( target PROPERTIES COMPILE_FLAGS “/fp:fast" )
Enable Floating Point Exceptions set_target_properties( target PROPERTIES COMPILE_FLAGS “/fp:except" )
Create Hotpatchable Image set_target_properties( target PROPERTIES COMPILE_FLAGS “/hotpatch" )
Spectre Mitigation
Enable Intel JCC Erratum Mitigation
Enable EH Continuation Metadata
Enable Signed Returns
C/C++=>Language 语言
Disable Language Extensions set_target_properties( target PROPERTIES COMPILE_FLAGS “/Za" )
Conformance mode
Treat wchar_t As Built in Type set_target_properties( target PROPERTIES COMPILE_FLAGS “/Zc:wchar_t" )#yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/Zc:wchar_t-" ) #no
Force Conformance in For Loop Scope
Remove unreferenced code and data
Enforce type conversion rules
Enable Run-Time Type Information set_target_properties( target PROPERTIES COMPILE_FLAGS “/GR" ) #yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/GR-" ) #no
Open MP Support set_target_properties( target PROPERTIES COMPILE_FLAGS “/openmp" )#yes
set_target_properties( target PROPERTIES COMPILE_FLAGS “/openmp-" )#no
C++ Language Standard
C Language Standard
Enable Experimental C++ Standard Library Modules
C/C++=>Precompiled Headers 预编译头
Create/Use Precompiled Header Not Using Precompiled Headers set_target_properties( target PROPERTIES COMPILE_FLAGS "/Yc" ) #create
set_target_properties( target PROPERTIES COMPILE_FLAGS "/Yu" ) #use
#not setting means no 关闭预编译头
Precompiled Header File set_target_properties( target PROPERTIES COMPILE_FLAGS "/Ycstdafx.h" )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/Yustdafx.h" )
Precompiled Header Output File set_target_properties( target PROPERTIES COMPILE_FLAGS "/FpPathAndName.pch" )
C/C++=>Output Files 输出文件
Expand Attributed Source set_target_properties( target PROPERTIES COMPILE_FLAGS "/Fx" )
Assembler Output set_target_properties( target PROPERTIES COMPILE_FLAGS "/FA" )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/FAc" )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/FAs" )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/FAcs" )
#not setting means no list
Use Unicode For Assembler Listing set_target_properties( target PROPERTIES COMPILE_FLAGS “/FAu" ) #yes
#Don’t set means no
ASM List Location
Module Output File Name
Module Dependencies File Name
Object File Name set_target_properties( target PROPERTIES COMPILE_FLAGS "/FoName.obj" )
Program Database File Name set_target_properties( target PROPERTIES COMPILE_FLAGS "/FdC:/Debug/good.pdb" )
Generate XML Documentation Files set_target_properties( target PROPERTIES COMPILE_FLAGS "/doc" )
XML Documentation File Name set_target_properties( target PROPERTIES COMPILE_FLAGS "/docDocument.xml" )
Generate Source Dependencies File
Source Dependencies File Name
C/C++=>Browse Information 浏览信息
Enable Browse Information set_target_properties( target PROPERTIES COMPILE_FLAGS "/FR" )
Browse Information File set_target_properties( target PROPERTIES COMPILE_FLAGS "/FRfilename" )
C/C++=>External Includes
Treat Files Included with Angle Brackets as External
External Header Warning Level
Template Diagnostics in External Headers
Disable Code Analysis for External Headers
Analysis Ruleset for External Headers
C/C++=>Advanced 高级
Calling Convention set_target_properties( target PROPERTIES COMPILE_FLAGS "/Gd" ) #_cdecl
set_target_properties( target PROPERTIES COMPILE_FLAGS "/Gr" ) #_fastcall
set_target_properties( target PROPERTIES COMPILE_FLAGS "/Gz" ) #_stdcall
Compile As set_target_properties( target PROPERTIES LINKER_LANGUAGE "CXX" ) #C++
set_target_properties( target PROPERTIES LINKER_LANGUAGE "C" ) #C
or
set_target_properties( target PROPERTIES COMPILE_FLAGS "/TP" ) #CXX
set_target_properties( target PROPERTIES COMPILE_FLAGS "/TC" ) #C
Disable Specific Warnings %(DisableSpecificWarnings) set_target_properties( target PROPERTIES COMPILE_FLAGS "/wd4710" )
Forced Include File set_target_properties( target PROPERTIES COMPILE_FLAGS "/FIinclude.h" )
Forced #using File set_target_properties( target PROPERTIES COMPILE_FLAGS "/FUname" )
Show Includes set_target_properties( target PROPERTIES COMPILE_FLAGS "/showIncludes" )
Use Full Paths set_target_properties( target PROPERTIES COMPILE_FLAGS "/FC" )
Omit Default Library Name set_target_properties( target PROPERTIES COMPILE_FLAGS "/ZI" )
Internal Compiler Error Reporting set_target_properties( target PROPERTIES COMPILE_FLAGS "/errorReport:queue" )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/errorReport:none" )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/errorReport:prompt" )
set_target_properties( target PROPERTIES COMPILE_FLAGS "/errorReport:send" )
Treat Specific Warnings As Errors 4172;%(TreatSpecificWarningsAsErrors)
C/C++=>Command Line 命令行
Additional Options /Zc:threadSafeInit- 线程安全初始化
Librarian=>General 静态库通用设置
Output File
Additional Dependencies
Additional Library Directories
Suppress Startup Banner
Module Definition File Name
Ignore All Default Libraries
Export Named Functions
Force Symbol References
Use Unicode Response Files
Link Library Dependencies
Error Reporting
Treat Lib Warning As Errors
Target Machine
SubSystem
Minimum Required Version
Remove Objects
Verbose
Name
Link Time Code Generation
Librarian=>Command Line 静态库命令行
Additional Options
Linker=>General 动态库链接通用设置
Output File #normal case
set_target_properties( target PROPERTIES OUTPUT_NAME "Helloworld" )
set_target_properties( target PROPERTIES PREFIX "lib" )
set_target_properties( target PROPERTIES SUFFIX "lib" )

#for debug version
set_target_properties( target PROPERTIES DEBUG_OUTPUT_NAME "Helloworld" )
set_target_properties( target PROPERTIES DEBUG_PREFIX "lib" )
set_target_properties( target PROPERTIES DEBUG_SUFFIX "lib" )

#For dlls
set_target_properties( target PROPERTIES OUTPUT_NAME "Helloworld" )
set_target_properties( target PROPERTIES IMPORT_PREFIX "lib" )
set_target_properties( target PROPERTIES IMPORT_SUFFIX "lib" )
set_target_properties( target PROPERTIES PREFIX "bin" )
set_target_properties( target PROPERTIES SUFFIX "dll" )
Show Progress set_target_properties( target PROPERTIES LINK_FLAGS "/VERBOSE" )
set_target_properties( target PROPERTIES LINK_FLAGS "/VERBOSE:Lib" )
set_target_properties( target PROPERTIES LINK_FLAGS "/VERBOSE:ICF" )
set_target_properties( target PROPERTIES LINK_FLAGS "/VERBOSE:REF" )
set_target_properties( target PROPERTIES LINK_FLAGS "/VERBOSE:SAFESEH" )
set_target_properties( target PROPERTIES LINK_FLAGS "/VERBOSE:CLR" )
Version set_target_properties( target PROPERTIES VERSION 0.1.2.3)
Enable Incremental Linking No (/INCREMENTAL:NO) set_target_properties( target PROPERTIES LINK_FLAGS "/INCREMENTAL" )
set_target_properties( target PROPERTIES LINK_FLAGS "/INCREMENTAL:NO" )

set( CMAKE_EXE_LINKER_FLAGS_DEBUG "/INCREMENTAL" )
set( CMAKE_EXE_LINKER_FLAGS_DEBUG "/INCREMENTAL:NO" )
增量链接
Incremental Link Database File
Suppress Startup Banner set_target_properties( target PROPERTIES LINK_FLAGS "/NOLOGO" )
Ignore Import Library
Register Output
Per-user Redirection
Additional Library Directories %(AdditionalLibraryDirectories) link_directories( dir1 dir2 )
set_target_properties( target PROPERTIES LINK_FLAGS "/LIBPATH:dir1 /LIBPATH:dir2" )
Link Library Dependencies
Use Library Dependency Inputs
Link Status set_target_properties( target PROPERTIES LINK_FLAGS "/LTCG:STATUS" )
set_target_properties( target PROPERTIES LINK_FLAGS "/LTCG:NOSTATUS" )
Prevent DLL Binding set_target_properties( target PROPERTIES LINK_FLAGS "/ALLOWBIND:NO" )
set_target_properties( target PROPERTIES LINK_FLAGS "/ALLOWBIND:YES" )
Treat Linker Warning As Errors set_target_properties( target PROPERTIES LINK_FLAGS "/WX" )
Force File Output set_target_properties( target PROPERTIES LINK_FLAGS "/FORCE" )
Create Hot Patchable Image set_target_properties( target PROPERTIES LINK_FLAGS "/FUNCTIONPADMIN" )
set_target_properties( target PROPERTIES LINK_FLAGS "/FUNCTIONPADMIN:16" ) #Itanium only
set_target_properties( target PROPERTIES LINK_FLAGS "/FUNCTIONPADMIN:6" ) #x64 only
set_target_properties( target PROPERTIES LINK_FLAGS "/FUNCTIONPADMIN:5" ) #x86 only
Specify Section Attributes
Linker=>Input 动态库链接输入
Additional Dependencies %(AdditionalDependencies) target_link_libraries( target item1 item2 )
Ignore All Default Libraries set_target_properties( target PROPERTIES LINK_FLAGS "/NODEFAULTLIB" )
Ignore Specific Default Libraries
Module Definition File
Add Module to Assembly
Embed Managed Resource File
Force Symbol References
Delay Loaded DLLs %(DelayLoadDLLs)
Assembly Link Resource
Linker=>Manifest File 动态库链接清单文件
Generate Manifest
Manifest File
Additional Manifest Dependencies
Allow Isolation
Enable User Account Control (UAC)
UAC Execution Level
UAC Bypass UI Protection
Linker=>Debugging 动态库链接调试
Generate Debug Info Generate Debug Information (/DEBUG) 输出调试信息,生成pdb
Generate Program Database File
Strip Private Symbols
Generate Map File Yes (/MAP) 生成map文件
Map File Name
Map Exports
Debuggable Assembly
Linker=>System 动态库链接系统
SubSystem
Minimum Required Version
Heap Reserve Size
Heap Commit Size
Stack Reserve Size
Stack Commit Size
Enable Large Addresses
Terminal Server
Swap Run From CD
Swap Run From Network
Driver
Linker=>Optimization 动态库链接优化
References Yes (/OPT:REF)
Enable COMDAT Folding Yes (/OPT:ICF)
Function Order
Profile Guided Database
Link Time Code Generation
Link Time Code Generation Object File
Linker=>Embedded IDL
MIDL Commands
Ignore Embedded IDL
Merged IDL Base File Name
Type Library
TypeLib Resource ID
Linker=>Windows Metadata 动态库链接元数据
Generate Windows Metadata
Windows Metadata File
Windows Metadata Key File
Windows Metadata Key Container
Windows Metadata Delay Sign
Linker=>Advanced 动态库链接高级
Entry Point
No Entry Point
Set Checksum
Base Address
Randomized Base Address
Fixed Base Address
Data Execution Prevention (DEP)
Turn Off Assembly Generation
Unload delay loaded DLL
Nobind delay loaded DLL
Import Library $(OutDir)$(TargetName).lib
Merge Sections
Target Machine
Profile
CLR Thread Attribute
CLR Image Type
Key File
Key Container
Delay Sign
CLR Unmanaged Code Check
Error Reporting
SectionAlignment
Preserve Last Error Code for PInvoke Calls
CET Shadow Stack Compatible
Image Has Safe Exception Handlers
Linker=>Command Line 动态库链接命令行
Additional Options
Manifest Tool=>General 清单工具通用配置
Suppress Startup Banner
Verbose Output
Assembly Identity
Manifest Tool=>Input and Output 清单工具输入输出
Additional Manifest Files
Input Resource Manifests
Embed Manifest
Output Manifest File
Manifest Resource File
Generate Catalog Files
Generate Manifest From ManagedAssembly
Suppress Dependency Element
Generate Category Tags
DPI Awareness Per Monitor High DPI Aware
Manifest Tool=>Isolated COM
Type Library File
Registrar Script File
Component File Name
Replacements File
Manifest Tool=>Advanced
Update File Hashes
Update File Hashes Search Path
Manifest Tool=>Command Line
Additional Options
Resources=>General
Preprocessor Definitions
Undefine Preprocessor Definitions
Culture
Additional Include Directories
Ignore Standard Include Paths
Show Progress
Suppress Startup Banner
Resource File Name
Null Terminate Strings
Resources=>Command Line
Additional Options
XML Data Generator=>General
Suppress Startup Banner
Additional Document File
Output Document File
Document Library Dependencies
XML Data Generator=>Command Line
Additional Options
Browse Information=>General
Suppress Startup Banner
Output File
Preserve SBR Files
Browse Information=>Command Line
Additional Options
Build Events=>Pre-Build Event
Command Line
Description
Use In Build
Build Events=>Pre-Link Event
Command Line
Description
Use In Build
Build Events=>Post-Build Event
Command Line
Description
Use In Build
Custom Build Step=>General
Command Line
Description
Outputs
Additional Dependencies
Treat Output As Content
Content Root Folder
Execute After
Execute Before

三、工程配置需求的非官方非标准的解决方案

1. 设置Visual Studio的WindowsSDKVersion(WindowsTargetPlatformVersion)值为10.0 (latest installed version)

image

在使用cmake生成的visual studio工程中,WIndowsSDKVersion总是为本机的最新SDK版本号,对于想要控制版本号或者使用最新版本均十分不便。

查阅了现有文档,以及官方文档均无相关配置项。
可参考官方相关讨论:https://gitlab.kitware.com/cmake/cmake/-/issues/21403

在核查cmGlobalVisualStudio14Generator的代码时发现cmake有一个针对sdk版本过滤的逻辑,而visual studio默认未设置版本时是否可以自动设置为10.0呢?是的,VisualStudio会帮我们自动设置为10.0 latest installed version

具体的方式是:
cmake -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM=10.0

但是,这样是否有什么后遗症? 目前还未验证,待调试所有参数完毕后测试验证下编译效果

2. 推荐使用至少3.15版本的CMake

CMAKE_MINIMUM_REQUIRED(VERSION 3.15)

3. 用于执行CMake的bat脚本

使用.bat脚本调用cmake,可以指定比较复杂的cmake.exe命令的参数

:: ${ProjectRoot}/build/vs2017-x64.bat

@echo off

::build directory
:: it should be similar name with cmake generator name
set BUILD_DIR=vs2017-x64

:: platform
:: x86 or x64
set BUILD_PLATFORM=x64

:: cl.exe compiler version
set BUILD_COMPILER=v142

:: create directory if not exist
if not exist %BUILD_DIR% md %BUILD_DIR%
cd %BUILD_DIR%

:: run cmake by specifing:
:: - generator
:: - installation directory
:: - CMakeLists.txt location
cmake -G "Visual Studio 12 2017 Win64" -DCMAKE_INSTALL_PREFIX=D:/target/%BUILD_PLATFORM%/%BUILD_COMPILER%

:: run build by specifying config and target
:: note: this may fail, and please open .sln and do manual compilation and installation
cmake --build . --config Release --target INSTALL

:: go back to old folder
cd ..

:: stuck to show build messages
pause

4. 判断平台:32位、64位

方法1:CMAKE_SIZEOF_VOID_P 表示 void* 的大小(例如为 4 或者 8),可以使用其来判断当前构建为 32 位还是 64 位,CMake官方推荐

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    message(STATUE "64bit")
else()
    message(STATUE "32bit")endif()

方法2:判断**CMAKE_CL_64** 是否为true,CMake官方已废弃,此判断仅且仅当使用cl.exe时有效

if(CMAKE_CL_64)
    message(STATUS "MSVC 64bit")
else()
    message(STATUS "MSVC 32bit")
endif()

5. 判断Visual Studio版本:MSVC_VERSION

6. 判断操作系统:其中WIN32判断的是windows系统,包括32位和64位两种情况

if(WIN32)
    message(STATUS "----- This is Windows.")
elseif(UNIX)
    message(STATUS "----- This is UNIX.") #Linux下输出这个
elseif(APPLE)
    message(STATUS "----- This is APPLE.")
elseif(ANDROID)
    message(STATUS "----- This is ANDROID.")
endif(WIN32)

if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    message(STATUS "----- OS: Windows")
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
    message(STATUS "----- OS: Linux")
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
    message(STATUS "----- OS: MacOS X")
elseif(CMAKE_SYSTEM_NAME MATCHES "Android")
    message(STATUS "----- OS: Android")endif()

测试发现,如果在CMAKE_MINIMUM_VERSION()后立即使用CMAKE_SYSTEM_NAME,Linux下得到结果为空,Android下得到为Android。看起来是Android的toolchain中进行了设定。

7. 判断Debug/Release

  • CMAKE_BUILD_TYPE取值:默认值由编译器决定,调用cmake时可通过-DCMAKE_BUILD_TYPE=Release的形式指定其值
  • Debug:CMAKE_BUILD_TYPE MATCHES "Debug" OR CMAKE_BUILD_TYPE EQUAL "None" OR NOT CMAKE_BUILD_TYPE
  • Release:CMAKE_BUILD_TYPE MATCHES "Release"
  • RelWitchDebInfo:CMAKE_BUILD_TYPE MATCHES "RelWitchDebInfo"
  • MinSizeRel:CMAKE_BUILD_TYPE MATCHES "MinSizeRel"

8. 根据Debug/Release添加不同的库目录

set(PROJECT_LIB_DIR, "path")

在vs平台下,会自动把path和path/$(Configuration)添加到库搜索目录。

9. 设定编译选项

修改CMAKE_C_FLAGS、CMAKE_CXX_FLAGS变量

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /we4013 /we4431")

10. SAFESEH报错

error LNK2026: 模块对于 SAFESEH 映像是不安全的
fatal error LNK1281: 无法生成 SAFESEH 映像

解决办法是:

if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    #message("inside windows")
    # add SAFESEH to Visual Studio. copied from http://www.reactos.org/pipermail/ros-diffs/2010-November/039192.html
    #if(${_MACHINE_ARCH_FLAG} MATCHES X86) # fails
    #message("inside that branch")

    # in VS2013, there is: fatal error LNK1104: cannot open file "LIBC.lib"
    # so, we have to add /NODEFAULTLIB:LIBC.LIB
    # reference: https://stackoverflow.com/questions/6016649/cannot-open-file-libc-lib
    set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB") 
    set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")
    set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")    
    #endif()
endif (CMAKE_SYSTEM_NAME MATCHES "Windows")

11. link_directory但是链接异常

link_directories() 这句话必须在add_executable()之前写 不然找不到库目录

或者先add_executable() 再 target_link_directories(XXX PRIVATE some direcotry)

12. Debug库带“d”后缀

设置debug模式下编译出的库文件,相比于release模式下,多带一个字母"d"作为后缀。

  • 对于单个目录:set_target_properties(TargetName PROPERTIES DEBUG_POSTFIX d)
  • 对于整个CMakeLists.txt:set(CMAKE_DEBUG_POSTFIX d)
  • 在调用cmake时临时指定:-DCMAKE_DEBUG_POSTFIX=d

13. 在cmake中执行目录创建、拷贝文件等脚本:add_custom_command、execute_process

  • 创建目录:file(MAKE_DIRECTORY ${SO_OUTPUT_PATH})

  • 和某个target绑定的文件拷贝,使用add_custom_command:add_custom_command(TARGET your_target PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${MY_SO_NAME} ${SO_OUTPUT_PATH}/)

  • 和target无关的,或者说对于所有target而言都需要做文件拷贝,用execute_process

foreach(lib_name_pth ${LIBS_TO_COPY})
    message(STATUS "--- ${lib_name_pth}")
    execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${lib_name_pth} ${SO_OUTPUT_PATH})
endforeach()

14. 转换相对路径为绝对路径

get_filename_component(SO_OUTPUT_PATH_ABS ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI} ABSOLUTE)

15. 循环处理列表:cmake中的列表也是字符串,不过,通过list(APPEND)得到的列表字符串,可以用foreach来遍历其中每个字符串

foreach(loop_var arg1 arg2 arg3)
    message(STATUS "--- ${loop_var}")
endforeach(loop_var)

foreach(loop_var ${SNPE_LIB_ALL})
    message(STATUS "--- ${loop_var}")
endforeach(loop_var)

16. 设置C/C++编译器

通过设定CMAKE_C_COMPILER和CMAKE_CXX_COMPILER来做到。

注意:project()命令必须在设定编译器之后出现,否则编译器的设定不起作用,将使用系统默认编译器。

if (UNIX)
    message(STATUS "----- This is Linux.")
    set(CMAKE_C_COMPILER "gcc-4.9")
    set(CMAKE_CXX_COMPILER "g++-4.9")
endif()

project(gamma)

注:前一种方法是在单个CMakeLists.txt中设定。对于跨平台编译,则应当避免污染根CMakeLists.txt,应该为每个平台分别使用cmake cache script。而在cache script中需要设定的变量,都应该是缓存变量。

set(CMAKE_C_COMPILER gcc CACHE STRING "C compiler")
set(CMAKE_CXX_COMPILER g++ CACHE STRING "C++ compiler")
set(PLATFORM_NAME "SigmaStar" CACHE STRING "")

17. 设定导入库(IMPORTED)及其属性

如果是自己项目中的源码基于cmake构建,其中利用add_library()创建的库目标,可以直接用来作为可执行目标、动态库或静态库的依赖库直接使用。

而如果是别人直接丢过来的库和头文件、没有用cmake封装一次呢?显然我们不应该在Visual Studio的项目属性中手动添加,手写一个导入库的cmake,在add_library()命令中指定关键字IMPORTED,再用set_target_properties()命令来设定导入库目标的头文件目录、库目录、库文件名字:

add_library(rock SHARED IMPORTED GLOBAL)
set_target_properties(rock PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES "inc"            #PUBLIC头文件目录
    IMPORTED_IMPLIB "rock.lib" #Windows平台上dll库的.lib库所在位置
    IMPORTED_LOCATION "rock.dll"  #dll库的.dll所在位置,或者.so库的位置,或者静态库的位置
)

其中GLOBAL关键字,是为了让全局可见。例如通过add_subdirectory()添加了mpbase库,里面是上述方式添加的库,但是上级CMakeLists.txt要确保能使用这个库,就需要指定GLOBAL关键字。

P.S. 实践发现,如果库文件所在目录很长(超过256个字符),或者添加的导入库对应的库文件有多个,它们的名字会被拼接起来,在CMake+Ninja的NDK开发环境下直接报错说路径太长。因此,导入库并不是一个好的实践。

18. 查看并修改Visual Studio项目属性中的某个设定

问题来自StackOverFlow上某网友的提问:Compile error CMAKE with CUDA on Visual Studio C++

解决步骤:

  1. 用cmake-gui.exe或ccmake加载cmake的cache文件
  2. 查找需要修改的字符串对应的CMake变量
  3. 在CMakeLists.txt中修改、覆盖此变量

19. 添加宏定义

add_definitions(-DUSE_OPENCV) 
add_definitions(-DLANDMARK_VERSION=2.1.33)

相当于传递给C/C++编译器:

#define USE_OPENCV
#define LANDMARK_VERSION 2.1.33

20. 设置fPIE

error: Android 5.0 and later only support position-independent executables (-fPIE)

问题出现在:连接一个静态库到一个可执行程序,并在android6.0上运行

解决办法:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -pie")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fPIE -pie")

21. 设置fPIC

问题出现场景:编译动态库libaisf_bodyattr_processor.so的时候,它依赖于静态库libarcsoft_bsd.a,但是libarcsoft_bsd.a库编译时没有指定fPIC编译选项。

  • 在编译静态库的时候,全局设定:set(CMAKE_POSITION_INDEPENDENT_CODE ON)

  • 在编译静态库的时候,设定

add_library(lib1 lib1.cpp)
set_property(TARGET lib1 PROPERTY POSITION_INDEPENDENT_CODE ON)

22. Linux gcc添加链接库"-lm":target_link_libraries(xxx m)

23. 清空普通变量:unset()

24. 清除缓存变量:unset( CACHE)

25. FindXXX.cmake简单例子

使用场景满足的条件:

  • 使用了很多依赖项;
  • 每个依赖项仅仅提供了.a/.lib/.so库文件和.h/.hpp头文件,没有提供XXX-config.cmake脚本;
  • 每个依赖项的库文件包括debug和release两种

此时如果继续在CMakeLists.txt中“一把梭”,各种设定都写在单个文件中,可以执行就不够高了。每个依赖写成FindXXX.cmake,则后续直接使用find_package(XXX)很方便,定位排查依赖项问题、可移植性都得到了增强。

FindXXX.cmake基本步骤

  • 步骤1:FindXXX.cmake第一行:include(FindPackageHandleStandardArgs)
  • 步骤2:想方设法设定_INCLUDE_DIRS和_LIBRARIES的值,并且避免硬编码。具体又可以包括:
    • 使用set()和list(APPEND )来设定变量的值
    • 使用find_library()来找库文件和头文件(比直接写为固定值要灵活)
  • 步骤3:find_package_handle_standard_args( DEFAULT_MSG _INCLUDE_DIRS _LIBRARIES)
  • 步骤4:根据是否查找成功进行打印

例子1:单个头文件和单个库文件

# provides `milk_LIBRARIES` and `milk_INCLUDE_DIRS` variable
# usage: `find_package(milk)`

include(FindPackageHandleStandardArgs)

set(milk_ROOT_DIR ${PROJECT_SOURCE_DIR}/third_party/milk CACHE PATH "Folder contains milk") 

set(milk_DIR ${milk_ROOT_DIR})

find_path(milk_INCLUDE_DIRS
          NAMES milk.h
          PATHS ${milk_DIR}
          PATH_SUFFIXES include include/x86_64 include/x64
          DOC "milk include" 
          NO_DEFAULT_PATH)

# find milk.libfind_library(milk_LIBRARIES
             NAMES milk
             PATHS ${milk_DIR}
             PATH_SUFFIXES lib lib64 lib/x86_64 lib/x86_64-linux-gnu lib/x64 lib/x86
             DOC "milk library"
             NO_DEFAULT_PATH)

find_package_handle_standard_args(milk DEFAULT_MSG milk_INCLUDE_DIRS milk_LIBRARIES)

if (milk_FOUND)
  if (NOT milk_FIND_QUIETLY)
    message(STATUS "Found milk: ${milk_INCLUDE_DIRS}, ${milk_LIBRARIES}")
  endif ()
  mark_as_advanced(milk_ROOT_DIR milk_INCLUDE_DIRS milk_LIBRARIES)
else ()
  if (milk_FIND_REQUIRED)
    message(FATAL_ERROR "Could not find milk")
  endif ()
endif ()

例子2:同时存在Debug和Release版本的库

希望在调用find_package(xxx)之后,Visual Studio或XCode等IDE能自动切换debug和release的库。则需要为debug库的路径添加debug字段,为release库添加optimized字段。

set(LEMON_LIBRARIES
    debug "${LEMON_DIR}/lib/debug/lemon.lib"
    optimized "${LEMON_DIR}/lib/release/lemon.lib"  )

考虑到硬编码不是一个好的方案,库文件可能放在lib、lib64、lib/Release等目录中,应当先用find_library()进行查找,然后再set库文件变量LEMON_LIBRARIES。

多个库的find_library写法

对于依赖库中的多个库,自然的想法是使用foreach()来处理每个库文件。

考虑到find_library(lemon_lib_name)会产生缓存变量lemon_lib_name,这会导致再次调用find_library(lemon_lib_name)时不再查找。需要unset(${lemon_lib_name} CACHE)该缓存变量来确保查找成功。直接给出完整例子:

include(FindPackageHandleStandardArgs)

set(LEMON_ROOT_DIR ${PROJECT_SOURCE_DIR}/third_party/lemon CACHE PATH "Folder contains lemon")

set(LEMON_DIR ${CEVA_ROOT_DIR})

set(LEMON_DIR ${LEMON_ROOT_DIR})

set(LEMON_LIBRARY_COMPONENTS lemon_core lemon_extra)

foreach(lemon_component ${LEMON_LIBRARY_COMPONENTS})
  unset(LEMON_LIBRARIES_DEBUG CACHE)
  find_library(LEMON_LIBRARIES_DEBUG
            NAMES ${lemon_component}
            PATHS ${LEMON_DIR}
            PATH_SUFFIXES lib lib/debug lib/debug
            DOC "lemon library component ${lemon_component} debug"
            NO_DEFAULT_PATH)
   unset(LEMON_LIBRARIES_RELEASE CACHE)
   find_library(LEMON_LIBRARIES_RELEASE
              NAMES ${lemon_component}
              PATHS ${LEMON_DIR}
              PATH_SUFFIXES lib lib/release lib/Release
              DOC "lemon library component ${lemon_component} release"
              NO_DEFAULT_PATH)

   list(APPEND LEMON_LIBRARIES
    debug ${LEMON_LIBRARIES_DEBUG}
    optimized ${LEMON_LIBRARIES_RELEASE}
  )
endforeach()

find_package_handle_standard_args(LEMON DEFAULT_MSG LEMON_LIBRARIES)

if (LEMON_FOUND)
  if (NOT LEMON_FIND_QUIETLY)
    message(STATUS "Found LEMON: ${LEMON_LIBRARIES}")
  endif ()
  mark_as_advanced(LEMON_ROOT_DIR LEMON_LIBRARIES)
else ()
  if (LEMON_FIND_REQUIRED)
    message(FATAL_ERROR "Could not find lemon")
  endif ()
endif ()

例子3:找dll

注意:CMAKE_FIND_LIBRARY_SUFFIXES的使用:CMake find_library matching behavior?

set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")

实际上,CMAKE_FIND_LIBRARY_SUFFIXES影响最大的就是find_library()命令了。譬如zlib安装目录下,同时存在动态库和静态库,分别是libz.a和libz.so,而find_library()的行为是“找到一个就不再找了”,因此如果没有很好的设定CMAKE_FIND_LIBRARY_SUFFIXES,就会导致找不到想要的库。默认情况下是找到动态库,然而windows下还需要手动拷贝DLL。。。麻烦。

可以通过备份原有的CMAKE_FIND_LIBRARY_SUFFIXES的值,改掉它的值,find_library()之后再改回原来的值,这样就支持了 静态库/动态库 分别查找的设定。。(用于魔改cmake自带的FindZLIB.cmake)

CMake find module to distinguish shared or static library

26. CMake各种编译链接参数的默认值

cmake_minimum_required(VERSION 3.2)

message(STATUS "CMAKE_C_FLAGS = " ${CMAKE_C_FLAGS})
message(STATUS "CMAKE_C_FLAGS_DEBUG = " ${CMAKE_C_FLAGS_DEBUG})
message(STATUS "CMAKE_C_FLAGS_MINSIZEREL = " ${CMAKE_C_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_C_FLAGS_RELEASE = " ${CMAKE_C_FLAGS_RELEASE})
message(STATUS "CMAKE_C_FLAGS_RELWITHDEBINFO = " ${CMAKE_C_FLAGS_RELWITHDEBINFO})

message(STATUS "CMAKE_CXX_FLAGS = " ${CMAKE_CXX_FLAGS})
message(STATUS "CMAKE_CXX_FLAGS_DEBUG = " ${CMAKE_CXX_FLAGS_DEBUG})
message(STATUS "CMAKE_CXX_FLAGS_MINSIZEREL = " ${CMAKE_CXX_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_CXX_FLAGS_RELEASE = " ${CMAKE_CXX_FLAGS_RELEASE})
message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO = " ${CMAKE_CXX_FLAGS_RELWITHDEBINFO})

message(STATUS "CMAKE_EXE_LINKER_FLAGS = " ${CMAKE_EXE_LINKER_FLAGS})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_DEBUG = " ${CMAKE_EXE_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_RELEASE = " ${CMAKE_EXE_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO})

message(STATUS "CMAKE_MODULE_LINKER_FLAGS = " ${CMAKE_MODULE_LINKER_FLAGS})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_DEBUG = " ${CMAKE_MODULE_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_RELEASE = " ${CMAKE_MODULE_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO})

message(STATUS "CMAKE_SHARED_LINKER_FLAGS = " ${CMAKE_SHARED_LINKER_FLAGS})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_DEBUG = " ${CMAKE_SHARED_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_RELEASE = " ${CMAKE_SHARED_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO})

message(STATUS "CMAKE_STATIC_LINKER_FLAGS = " ${CMAKE_STATIC_LINKER_FLAGS})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_DEBUG = " ${CMAKE_STATIC_LINKER_FLAGS_DEBUG})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL = " ${CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_RELEASE = " ${CMAKE_STATIC_LINKER_FLAGS_RELEASE})
message(STATUS "CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO = " ${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}

27. 链接器相关问题

检查链接到的重名函数

场景:A库的代码中定义了函数play(),B库的代码中也定义了函数play(),但是这两个play()函数的实现不同,并且被可执行目标C同时链接。
链接器默认是找到一个符号就不再查找,因此默认能链接并且可以运行,只不过运行结果不是所期待的。

容易查到,Linux下gcc对应的链接器中可以使用--whole-archive和--no-whole-archive参数来包含静态库中的所有符号。

如果是gcc,则使用gcc -Wl --whole-archive someLib --no-whole-archive。

如果是Visual Studio,则需要>=2015 update2的版本中才支持/WHOLEARCHIVE选项,VS2013要哭泣了。

因而,在CMakeLists.txt中,可以设定链接器的全局设定:

if(CMAKE_SYSTEM_NAME MATCHES "Windows")
	set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /WHOLEARCHIVE")
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
	 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--whole-archive")
endif()

缺点:

  • 所有库的符号都进行导入,不能灵活处理单个不需要导入所有符号的库
  • 系统默认导入的库,例如Windows下的USER32.dll和KERNEL32.dll会产生冲突

TODO: 对于单个target,如何设定?

if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    set_target_properties(inter
        PROPERTIES LINK_FLAGS
        "/WHOLEARCHIVE:gender /WHOLEARCHIVE:smile"
    )
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
    # 不起作用
    set_target_properties(inter
        PROPERTIES LINK_FLAGS
        "-Wl,--whole-archive gender
   -Wl,--whole-archive smile"
    )
endif()

或者:

set(MYLIB -Wl,--whole-archive mytest -Wl,--no-whole-archive)
target_link_libraries(main ${MYLIB})

实际上:

  • gcc的链接器ld:通过-Wl, --whole-archive lib_name -Wl, --no-whole-archive能每次分别对一个静态库加载所有的member(函数等

  • Visual Studio的链接器:VS2017(1900)之后才支持-WHOLEARCHIVE来实现同样功能;

  • PC Clang的链接器:使用lld作为链接器(比如Xcode现在用肯定是lld),用-Wl,-force_load ${lib}来做到只导入一个静态库中的所有member

  • NDK的链接器:怎么说现在用的NDK也是17b起步了,默认编译器是Clang,gcc暂时没考虑;虽然编译器很早就(可以)切换到Clang了,但链接器目前还是用的gcc的ld,因此NDK的链接阶段检查重复符号应该用ld的检查方式;即使是用当前(2020-01-26 02:16:34)最新的NDK也就是NDK21,手动传入ANDROID_LD=lld后,NDK切换到的链接器lld和MacOS上与AppleClang搭配的lld也还是不一样,链接阶段查重复符号仍然需要传gcc的ld的那一套参数:-Wl, --whole-archive lib_name -Wl, --no-whole-archive,但好处是报错界面更加友好直观了:
    image

  • gcc和ld 中的参数 --whole-archive 和 --no-whole-archive

  • lld中的-force_load参数功能说明是在邮件列表中找到的,官方文档里还更新出来

  • ndk-20的change log

28. 生成compile_commands.json:用于在VSCode等编辑器/IDE中给C/C++代码做函数定义跳转支持

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

29. 子目录CMakeLists.txt中产生变量给父目录中的CMakeLists.txt使用

set设定变量并且设定PARENT_SCOPE参数。当项目中代码变多,就可能需要分成多个目录存放。每个目录下放一个CMakeLists.txt,写出它要处理的文件列表,然后暴露给外层CMakeLists.txt,使外层CMakeLists.txt保持清爽结构。

set(hello_srcs
    ${CMAKE_CURRENT_SOURCE_DIR}/hello.cpp)
set(hello_private_incs
    ${CMAKE_CURRENT_SOURCE_DIR}/hello.h)

set(hello_srcs ${hello_srcs} PARENT_SCOPE)
set(hello_private_incs ${hello_private_incs} PARENT_SCOPE)

30. 在IDE中将targets分组显示:使用folder

包括两步:

  • 在最顶部的CMakeLists.txt中添加一句:set_property(GLOBAL PROPERTY USE_FOLDERS ON)
  • 在希望出现在文件夹的项目的add_library或add_executable后添加:set_property(TARGET ${prj_target} PROPERTY FOLDER Samples)

image

31. 设置Debug的优化级别参数

set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")

32. cmake生成VS2019的工程

VS2019开始,需要用-A参数指定是32位程序,还是64位程序。以前的用法不行了

cmake -G "Visual Studio 16 2019" -A Win32 ..\..
cmake -G "Visual Studio 16 2019" -A x64 ..\..

33. 不要同时使用include_directories()和target_include_directories()

include_directories("inc" "src")

add_library(rock src/rock.cpp)

target_include_directories(rock PRIVATE "3rdparty/spdlog")

用法有坑,其中target_include_directories()设置的目录不会生效。经验:只使用其中一种设定include目录的方式。

34. NDK开发中出现error: cannot use 'try' with exceptions disabled

这需要编译器的编译选项中开启exception的支持。现在NDK开发,主流的做法是Android Studio + gradle + cmake做构建,需要在build.gradle中设定-fexceptions:

        externalNativeBuild { 
           cmake 
{//                cppFlags '-std=c++11 -fexceptions'
//                arguments '-DANDROID_PLATFORM=android-21',
//                    '-DANDROID_TOOLCHAIN=clang',
//                    '-DCMAKE_BUILD_TYPE="Release',
//                    '-DANDROID_ARM_NEON=ON',
//                    '-DANDROID_STL=c++_shared'
//                cppFlags '-std=c++11'
//                arguments '-DANDROID_TOOLCHAIN=clang',
//                        '-DANDROID_STL=c++_static'
                cppFlags "-std=c++11 -fexceptions"
            }
        }

但有时候,发现上述设定后并不生效;尝试删除.externalBuild目录重新构建,仍然报exception无法处理。后来发现,问题出在CMakeLists.txt加载的.cmake脚本中(用的代码框架是其它部门同事写的,所以不是很熟悉),他给手动设定了这么一句:

SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions -fno-short-enums -Werror=non-virtual-dtor")

去掉-fno-exception即可。

35. cmake-gui中可见(可检索)的变量

目前遇到的有两种:

  • option( )命令定义的变量:option(USE_OPENCV “use opencv?” CACHE PAH)
  • 缓存变量:set(varName "value" CACHE STRING "")

当然,还可以使用mark_as_adances来设定,则默认在cmake-gui中不可见

36. Ninja error: Filename longer than 260 characters

NDK开发中,CMake+Ninja构建,如果文件名超过260个字符会失败。这个限制略蛋疼

ninja: error: Stat(../../deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarc_net_sgl.a;E:/dbg/myy/landmark_demo/deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarcsoft_face_detection.a;E:/dbg/myy/landmark_demo/deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarcsoft_landmark_tracking.a;E:/dbg/myy/landmark_demo/deps/arcsoft_landmark/2.1.12021.33/libs/Android/armeabi-v7a/libarcsoft_videooutline.a): Filename longer than 260 characters

37. cmake判断C/C++编译器

比如我想要定制一些安全的编译选项,发现需要区分msvc,gcc和clang。容易直接想到CMAKE_C_COMPILER和CMAKE_CXX_COMPILER。但考虑到-G Xcode和命令行下分别输出,得到的结果并不都是clang,这就很蛋疼。

使用CMAKE_CXX_COMPILER_ID比较方便,像上面提到的case会做合并输出AppleClang。而如果是NDK-r17c则输出Clang。

常见的平台下对应输出:

  • MSVC
  • Clang
  • GNU
  • AppleClang

更多结果看官方文档 CMAKE__COMPILER_ID

38. cmake字符串追加元素,并且用空格分隔

简单有效的两种方式:

  • set: set(MY_FLAGS "${MY_FLAGS} -Werror=shadow")
  • 封装一个函数
function(afq_list_append __string __element)
    set(${__string} "${${__string}} ${__element}" PARENT_SCOPE)
endfunction()

39. cmake --build的使用:cmake执行编译链接、安装

本质上,当使用cmake ..,或cmake -G "Visual Studio 15 2017 Win64" ../..类似命令时,是执行“pre-make”,相当于是makefile的生成器,可以说对于你的项目代码来说并没执行编译链接,更没有安装。

实际使用经验:cmake生成了这些cache文件后,可以打开Visual Studio编译,或执行make编译(Linux下)。但这些都是native tool。通用的方式则是用cmake包装好的接口:

# 执行编译(如果是可执target,则包括链接过程)
cmake --build . --config Release

# 执行某个target的编译(如果是可执target,则包括链接过程)
cmake --build . --config Release --target xx

# 执行安装
cmake --install . --prefix d:/lib/openblas/clang-cl/x64 -v

40. C/C++和汇编混编

在cmake里混合编译C/C++与汇编代码,通过enable_language(ASM_)可以做到。

例如x86(32位)的MASM(Visual Studio支持的一种汇编语法):enable_language(ASM_MASM)

此外往往还需要给Visual Studio设置SEH:

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /SAFESEH:NO /NODEFAULTLIB:LIBC.LIB")

汇编文件则和C/C++文件一起,正常添加为target的依赖即可:add_executable(run src/main.cpp src/CalcSum_.asm)

41. cmake设置pthread

  • target_link_libraries(xxx pthread):有时候不管用(例如ARM Android平台),则用下面的方式
  • find_package( Threads ) && target_link_libraries( testbed ${CMAKE_THREAD_LIBS_INIT} log)
  • 注意ARM Android(也就是通常说的NDK开发中),需要给cmake传一个选项:-DANDROID_PLATFORM=android-24 ^(我是armv8所以用24)

42. cmake使用pkg-config

有些依赖库只提供.pc文件,甚至已经配置了CMake脚本但是安装后还是只有.pc而没有XXXConfig.cmake或xxx-config.cmake。并且不仅是Linux,Windows上也这样。
这就不得不在CMake中尝试去加载.pc文件。原理是,cmake里面封装了对pkg-config工具的兼容,可以认为是一个插件,用这个插件去加载.pc文件。实际测试发现Linux和Windows都可以用。

  • Linux安装pkg-config:sudo apt install pkg-config
  • Windows安装pkg-config-lite

使用:先找到xx.pc文件,然后分成目录和文件前缀两部分,在cmake中配置

举例1:Ubuntu 16.04下用apt安装openblas并在CMake中用pkg-config方式配置:

cmake_minimum_required(VERSION 3.15)

project(cmake_pkg_config_example)

set(ENV{PKG_CONFIG_PATH} /usr/lib/pkgconfig)
find_package(PkgConfig)
pkg_search_module(OBS REQUIRED blas-openblas)

message(STATUS "=== OBS_LIBRARIES: ${OBS_LIBRARIES}")
message(STATUS "=== OBS_INCLUDE_DIRS: ${OBS_INCLUDE_DIRS}")

举例2:Windows 10下用CMake配置Pangolin安装中配置的zlib

cmake_minimum_required(VERSION 3.15)

project(cmake_pkg_config_example) 

#指定pkg-config.exe绝对路径
set(PKG_CONFIG_EXECUTABLE "D:/soft/pkg-config/bin/pkg-config.exe") 

#指定zlib.pc所在目录
set(ENV{PKG_CONFIG_PATH} "D:/lib/pangolin/share/pkgconfig") 

find_package(PkgConfig)
message(STATUS "--- PKG_CONFIG_FOUND: ${PKG_CONFIG_FOUND}")
message(STATUS "--- PKG_CONFIG_VERSION_STRING: ${PKG_CONFIG_VERSION_STRING}")

pkg_search_module(ZLIB REQUIRED zlib) 

message(STATUS "=== ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}")
message(STATUS "=== ZLIB_INCLUDE_DIRS: ${ZLIB_INCLUDE_DIRS}")

43. cmake多行注释

#[[
第一种注释方式
#]]


#[===============[
第二种注释方式
#]===============]

44. 命令行-D指定参数

在cmake脚本中指定其中任意一种:

  • cmake缓存类型的变量:set(CAFFE_TARGET_VERSION "1.0.0" CACHE STRING "Caffe logical version")
  • option:option(USE_OPENCV "Do we use OpenCV?" ON)

45. include()指令

  • 包含文件,例如include(utils.cmake)
  • 包含模块,在CMAKE_MODULE_PATH->CMAKE MODULE DIRECTORY依次查找。例如include(ExternalModule),会在这两个路径列表中查找ExternalModule.cmake文件并加载

CMAKE_MODULE_PATH是在CMake脚本中用户可以自行修改的变量。
CMAKE MODULE DIRECTORY是什么,官方文档没明确说。其实说的应该是cmake安装后的Modules目录,例如/usr/local/share/cmake/Modules

46. list追加元素,或list首部插入元素

  • 追加(链表尾部插入):list(APPEND CMAKE_MODULE_PATH "cmake/Modules")
  • 首部插入(链表首元素前插入):list(INSERT CMAKE_PREFIX_PATH 0 "$ENV{HOME}/soft/opencv/build")

47. cmake中使用IWYU

IWYU 是 google 的开源项目,用来移除不必要的头文件。

48. target_link_libraries()

cmake 3.13 开始提供的命令。低版本cmake无法使用

49. CMake构建NDK项目提示asm/types.h找不到

用CMake构建NDK项目时,会传入toolchain的cmake脚本文件android.toolchain.cmake给CMake。这个文件中会做若干设定,其中就包括include路径。

我遇到的情况是,自己手动修改CMAKE_C_FLAGS和CMAKE_CXX_FLAGS时,覆盖了它们原有的(android.toolchain.cmake修改后的)值,导致asm/types.h找不到。

# 我的错误设定:
set(CMAKE_C_FLAGS "${MY_CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${MY_CMAKE_CXX_FLAGS}")
# 正确做法应该是追加内容而非修改:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MY_CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MY_CMAKE_CXX_FLAGS}")

P.S. 排查方法:由于我是基于ninja构建的(cmake+ndk的组合下,现在通常用ninja),通过对比”能正常构建的工程“和”提示asm/types.h找不到的工程“之间${CMAKE_BINARY_DIR}目录下的rules.ninja和build.ninja来发现问题所在。

50. windows下创建的共享库,没生成.lib文件

.lib是导入库,里面存访对外可见(暴露)的符号(函数、变量)。.dll应该搭配一个.lib导入库才能使用。

如果是自己的源码生成的dll共享库,则在CMakeLists.txt一开始,添加:set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)则可以导出所有的符号。

CMake linking against shared library on windows: error about not finding .lib file

而如果只想导出一部分符号,则可以为每个函数分别指定导出规则。

51. 拷贝dll

在Windows下,Visual Studio中,如果用了动态库(例如opencv、zlib等),需要把dll放到PATH环境变量中,使得运行时能找到dll。
而其实Windows下的PATH查找,是会在CMAKE_BINARY_DIR目录下查找的。如果不想改PATH环境变量,也不希望每次都要手动拷贝dll,包括清掉build目录后重新构建时也不想手动拷贝,那么可以用cmake命令来搞。

举个例子,调用zlib库执行文本压缩解压,用到了zlib1.dll,其中executable target名字是demo。

zlib的二进制包下载:https://nsis.sourceforge.io/mediawiki/images/b/bb/Zlib-1.2.8-win64-AMD64.zip

zlib的调用示例代码:https://blog.csdn.net/yuhuqiao/article/details/82188963

cmake中拷贝zlib1.dll的写法:

# each time the `demo` target is built, we copy zlib1.dll if it is changed.
add_custom_command(TARGET demo
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
    ${ZLIB_DLL}
    ${CMAKE_BINARY_DIR}/)

52. 压缩或解压.zip/tar.gz

tar命令是从cmake 3.2开始支持的内置命令,以解压doctest.zip到项目根目录为例:

execute_process(
    COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/doctest.zip
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})

https://stackoverflow.com/questions/48891928/cmake-unzip-automaticallywhile-building-when-the-zip-file-changes

53. 列出所有target

有些基于cmake的项目,CMakeLists.txt写的很复杂很庞大。可以列出所有target,帮助理清思路。

列出makefile中的所有target:

cd build
cmake --build . --target help 

实际上,makefile里诸如"all"和"clean"这样的target,并不是我们感兴趣的。还是shell大法拼凑一下吧:

cd ~/work/my_project
mkdir build && cd build && cmake ..
make -j4 > log.txt 2>&1
grep 'Built target' log.txt | awk '{print $4}'

54. 判断文件是否存在

EXISTS可以判断,同时适用于文件和目录。

举例:查找当前目录下是否存在doctest目录,并且检查doctest目录下是否存在doctest_fwd.h和doctest.cpp文件:

if (EXISTS "${CMAKE_SOURCE_DIR}/doctest"
    AND EXISTS "${CMAKE_SOURCE_DIR}/doctest/doctest_fwd.h"
    AND EXISTS "${CMAKE_SOURCE_DIR}/doctest/doctest.cpp")
    message(STATUS "--- doctest source code ready !")
else()
    message(STATUS "--- extracting doctest source code from zip...")
    execute_process(
        COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/doctest.zip
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )
endif()

55. 打印变量

message(STATUS "--- CMAKE_CXX_COMPILER is: ${CMAKE_CXX_COMPILER}")

include(CMakePrintHelpers)
cmake_print_variables(CMAKE_CXX_COMPILER)

56. 判断是否为目录

用IS_DIRECTORY命令判断。例如shadow中判断各个backend,如果是目录,则添加subdirectory,写法如下:

foreach (backend_dir ${backends_dir})
  if (IS_DIRECTORY ${backend_dir})
    add_subdirectory(${backend_dir}) 
 endif()
endforeach()

57. Visual Studio环境下的MT,MTd, MD, MDd的设定

https://stackoverflow.com/a/56490614/2999096

从cmake3.15开始,可以用CMAKE_MSVC_RUNTIME_LIBRARY和MSVC_RUNTIME_LIBRARY设定。

  • 可以全局设定:set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDebug)

  • 针对单个目标设定:

add_executable(foo foo.c)
set_property(TARGET foo PROPERTY
             MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

候选值有4种:

  • MultiThreaded:Compile with -MT or equivalent flag(s) to use a multi-threaded statically-linked runtime library.
  • MultiThreadedDLL:Compile with -MD or equivalent flag(s) to use a multi-threaded dynamically-linked runtime library.
  • MultiThreadedDebug:Compile with -MTd or equivalent flag(s) to use a multi-threaded statically-linked runtime library.
  • MultiThreadedDebugDLL:Compile with -MDd or equivalent flag(s) to use a multi-threaded dynamically-linked runtime library.

58. 设定Address Sanitizer(ASAN)

首先确保有符号信息(例如CMAKE_BUILD_TYPE设定为Debug)。

其次是设定如下几个变量中的其中一个(多个也行但没必要,看你需求):

  • CMAKE_EXE_LINKER_FLAGS
  • CMAKE_EXE_LINKER_FLAGS_DEBUG
  • CMAKE_SHARED_LINKER_FLAGS
  • CMAKE_SHARED_LINKER_FLAGS_DEBUG

例如我编译的是可执行目标,那么:

set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")

对于可执行目标,并且依赖于静态库或动态库,懒人用法:

set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")

注意:ASAN似乎对vector等容器的支持不够好。对于vector,预先分配多少内存,似乎ASAN并不知道,导致vector被clear后再使用,(做下标访问一段时间后)出现的segfault,没被ASAN检测到。

59. Linux下编译32位程序

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")

60. 设置VS的可执行文件生成目录

假设.sln目录在 build/vs2019-x64 下,则默认的可执行文件生成目录是 build/vs2019-x64/Debug 或 build/vs2019-x64/Release;而如果文件放在这一默认生成目录下的话无法被读取到(不加前缀的情况),需要放在 build/vs2019-x64 目录下才能读到。

以至于,代码里经常要根据 _MSC_VER 或者 ANDROID 等平台相关的宏,设定不同的路径,例如:

#if _MSC_VER
    const char* image_path = "E:/share/to_zcx/ncnn_vk_dbg/build/vs2019-x64/ncnn_input_cpu.bmp";
#elif ANDROID
   const char* image_path = "ncnn_input_cpu.bmp";
#endif

这让代码不整洁,也增加了出错的可能。

image
image

可以通过上面两截图中的方式,在项目属性中修改可执行文件的生成目录和启动目录,但仍然不方便;最好的办法还是在 CMakeLists.txt 里设定:

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_SOURCE_DIR}/bin>) 

add_executable(testbed hello.cpp)
set_target_properties(testbed PROPERTIES
    VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/bin")

https://stackoverflow.com/questions/47175912/using-cmake-how-to-stop-the-debug-and-release-subdirectories
https://stackoverflow.com/questions/41864259/how-to-set-working-directory-for-visual-studio-2017-rc-cmake-project

61. find_package等的debug输出

从cmake3.17开始,文档里正式说明支持CMAKE_FIND_DEBUG_MODE这一cmake变量,设定为TRUE则打印find_package/find_program/find_file等函数的打印过程

set(CMAKE_FIND_DEBUG_MODE TRUE)
find_package(...)
set(CMAKE_FIND_DEBUG_MODE FALSE)

实际上据网友反馈,稍早的版本也可以用这一变量,只不过文档里当时没写。

另外,设定CMAKE_FIND_DEBUG_MODE变量为TRUE,等价于调用cmake时候指定--debug-find参数。

如果你是cmake-GUI方式构建,菜单栏也可以选择输出debug信息:

image

62. 命令行方式传入option的坑

场景:命令行方式调用cmake,指定了很多option和cache variable的值,希望把这些option和cache variable放在.txt文件中,然后通过cat option.txt方式传给cmake。
结论:option.txt里的写法,-D之后不能有空格,否则无法生效。

例如,正确写法是:

-DUSE_X1=ON
-DUSE_X2=ON
-DUSE_X3=ON

错误写法是

-D USE_X1=ON
-D USE_X2=ON
-D USE_X3=ON

可以用如下 CMakeLists.txt 验证结论:

cmake_minimum_required(VERSION 3.15)

project(x)

option(USE_X1 "USE X1?" OFF)
option(USE_X2 "USE_X2?" OFF)
option(USE_X3 "USE_X3?" OFF)

if (USE_X1)
    message(STATUS "USE_X1: ON")
else()
    message(STATUS "USE_X1: OFF")
endif()

if (USE_X2)
    message(STATUS "USE_X2: ON")
else()
    message(STATUS "USE_X2: OFF")
endif()

if (USE_X3)
    message(STATUS "USE_X3: ON")
else()
    message(STATUS "USE_X3: OFF")
endif()

63. PowerShell中从txt读内容传给cmake

  • bash里头的做法:
cmake `cat options.txt`
  • powershell里头,不能用``符号,也没有cat命令。作为替代,用的是$(type options.txt),例如:
cmake G:/dev/opencv-build/mock -G "Visual Studio 16 2019" -A x64 -DCMAKE_INSTALL_PREFIX=install $(type G:/dev/opencv-build/mock/options.txt)

powershell是基于ksh语法修改而来(而不是M的别的其他的语法);‘(cat options.txt)`在bash里也能用;
两个``符号,英文正式名字叫做backtick,而不是"reverse quote"。

99. 现代CMake

常用主要有如下几个不响应target的全局设定:

参考

Python环境

  1. Pyenv:不同Python版本的安装器
  • cd $HOME/.pyenv
  • git clone https://github.com/pyenv/pyenv.git ~/.pyenv
  • pyenv versions
  • pyenv version
  • pyenv install --list
  • pyenv install -v 2.7.3
  • pyenv rehash
  • pyenv global 3.4.0
  • pyenv local 2.7.3
  1. pipenv:管理虚拟环境,在项目目录创建项目环境配置
  • pip3 install pipenv
  • pipenv install
  • pipenv --python 3.7

C/C++通用框架和库

本文整理了部分C/C++的框架和库,分享给大家。后续会附加相关库的评测和使用指南,持续关注哦~~~

一、框架

Apache C++ Standard Library : 是一系列算法,容器,迭代器和其他基本组件的集合

ASL : Adobe源代码库提供了同行的评审和可移植的C++源代码库。

Boost : 大量通用C++库的集合。

BDE : 来自于彭博资讯实验室的开发环境。

Cinder : 提供专业品质创造性编码的开源开发社区。

Bxxomfort : 轻量级的,只包含头文件的库,将C++ 11的一些新特性移植到C++03中。

Dlib : 使用契约式编程和现代C++科技设计的通用的跨平台的C++库。

EASTL : EA-STL公共部分

ffead-cpp : 企业应用程序开发框架

Folly : 由Facebook开发和使用的开源C++库。

JUCE : 包罗万象的C++类库,用于开发跨平台软件

libphenom : 用于构建高性能和高度可扩展性系统的事件框架。

LibSourcey : 用于实时的视频流和高性能网络应用程序的C++11 evented IO

LibU : C语言写的多平台工具库

Loki : C++库的设计,包括常见的设计模式和习语的实现。

MiLi : 只含头文件的小型C++库

openFrameworks : 开发C++工具包,用于创意性编码。

Qt : 跨平台的应用程序和用户界面框架

Reason : 跨平台的框架,使开发者能够更容易地使用Java,.Net和Python,同时也满足了他们对C++性能和优势的需求。

ROOT : 具备所有功能的一系列面向对象的框架,能够非常高效地处理和分析大量的数据,为欧洲原子能研究机构所用。

STLport : 是STL具有代表性的版本

STXXL : 用于额外的大型数据集的标准模板库。

Ultimate++ : C++跨平台快速应用程序开发框架

Windows Template Library : 用于开发Windows应用程序和UI组件的C++库

Yomm11 : C++11的开放multi-methods.

二、异步事件循环

Boost.Asio : 用于网络和底层I/O编程的跨平台的C++库。

libev : 功能齐全,高性能的时间循环,轻微地仿效libevent,但是不再像libevent一样有局限性,也修复了它的一些bug。

libevent : 事件通知库

libuv : 跨平台异步I/O。

libco : 协程,微信支持8亿用户同时在线的底层IO库。功能强大

ntyco : 纯c版的协程框架,代码短小精悍,适合嵌入工程。

libgo : golang风格的并发框架,C++11实现协程库

三、网络库

ACE : C++面向对象网络变成工具包

Casablanca : C++ REST SDK

cpp-netlib : 高级网络编程的开源库集合

Dyad.c : C语言的异步网络

libCurl : 多协议文件传输库

Mongoose : 非常轻量级的网络服务器

Muduo : 用于Linux多线程服务器的C++非阻塞网络库

net_skeleton : C/C++的TCP 客户端/服务器库

WAFer : 基于C语言的超轻型软件平台,用于可扩展的服务器端和网络应用。 对于C编程人员,可以考虑node.js

Onion : C语言HTTP服务器库,其设计为轻量级,易使用。

POCO : 用于构建网络和基于互联网应用程序的C++类库,可以运行在桌面,服务器,移动和嵌入式系统。

RakNet : 为游戏开发人员提供的跨平台的开源C++网络引擎。

Tufao : 用于Qt之上的C++构建的异步Web框架。

WebSocket++ : 基于C++/Boost Aiso的websocket 客户端/服务器库

ZeroMQ : 高速,模块化的异步通信库

四、TCP/IP协议栈

f-stack : 腾讯开源的协议栈,基于DPDK的高性能用户态协议栈。

NtyTcp : 单线程的协议栈的,基于netmap,DPDK,rawSocket的实现。

LWIP : 针对 RAM 平台的精简版的 TCP/IP 协议栈实现。

mTCP : 针对多核系统的高可扩展性的用户空间 TCP/IP 协议栈。

4.4BSD : * nix的协议栈是源于4.4BSD的。

五、WEB应用框架

Nginx : 一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。

Lighttpd : 一款开源 Web 服务器软件,安全快速,符合行业标准,适配性强并且针对高配置环境进行了优化。

Libmicrohttpd : GNU软件下的简单c库的Web服务器。API简单,快速。

shttpd : 基于Mongoose的Web服务器框架。

CivetWeb : 提供易于使用,强大的,C/C++嵌入式Web服务器,带有可选的CGI,SSL和Lua支持。

CppCMS : 免费高性能的Web开发框架(不是 CMS).

Crow : 一个C++微型web框架(灵感来自于Python Flask)

Kore : 使用C语言开发的用于web应用程序的超快速和灵活的web服务器/框架。

libOnion : 轻量级的库,帮助你使用C编程语言创建web服务器。

QDjango : 使用C++编写的,基于Qt库的web框架,试图效仿Django API,因此得此名。

Wt : 开发Web应用的C++库。

六、标准库,算法与函数

C++ Standard Library : 是一系列类和函数的集合,使用核心语言编写,也是C++ISO自身标准的一部分。

Standard Template Library : 标准模板库, STL

C POSIX library : POSIX系统的C标准库规范

ISO C++ Standards Committee : C++标准委员会

七、音频库

FMOD : 易于使用的跨平台的音频引擎和音频内容的游戏创作工具。

Maximilian : C++音频和音乐数字信号处理库

OpenAL : 开源音频库—跨平台的音频API

Opus : 一个完全开放的,免版税的,高度通用的音频编解码器

Speex : 免费编解码器,为Opus所废弃

Tonic : C++易用和高效的音频合成

Vorbis : Ogg Vorbis是一种完全开放的,非专有的,免版税的通用压缩音频格式。

八、生态学

lisequence : 用于表示和分析群体遗传学数据的C++库。

SeqAn : 专注于生物数据序列分析的算法和数据结构。

Vcflib : 用于解析和处理VCF文件的C++库

Wham : 直接把联想测试应用到BAM文件的基因结构变异。

九、压缩

bzip2 : 一个完全免费,免费专利和高质量的数据压缩

doboz : 能够快速解压缩的压缩库

PhysicsFS : 对各种归档提供抽象访问的库,主要用于视频游戏,设计灵感部分来自于Quake3的文件子系统。

KArchive : 用于创建,读写和操作文件档案(例如zip和 tar)的库,它通过QIODevice的一系列子类,使用gzip格式,提供了透明的压缩和解压缩的数据。

LZ4 : 非常快速的压缩算法

LZHAM : 无损压缩数据库,压缩比率跟LZMA接近,但是解压缩速度却要快得多。

LZMA : 7z格式默认和通用的压缩方法。

LZMAT : 及其快速的实时无损数据压缩库

miniz : 单一的C源文件,紧缩/膨胀压缩库,使用zlib兼容API,ZIP归档读写,PNG写方式。

Minizip : Zlib最新bug修复,支持PKWARE磁盘跨越,AES加密和IO缓冲。

Snappy : 快速压缩和解压缩

ZLib : 非常紧凑的数据流压缩库

ZZIPlib : 提供ZIP归档的读权限。

十、并发性

Boost.Compute : 用于OpenCL的C++GPU计算库

Bolt : 针对GPU进行优化的C++模板库

C++React : 用于C++11的反应性编程库

Intel TBB : Intel线程构件块

Libclsph : 基于OpenCL的GPU加速SPH流体仿真库

OpenCL : 并行编程的异构系统的开放标准

OpenMP : OpenMP API

Thrust : 类似于C++标准模板库的并行算法库

HPX : 用于任何规模的并行和分布式应用程序的通用C++运行时系统

VexCL : 用于OpenCL/CUDA 的C++向量表达式模板库。

十一、密码学

Bcrypt : 一个跨平台的文件加密工具,加密文件可以移植到所有可支持的操作系统和处理器中。

BeeCrypt : 快速的加密图形库,功能强大,接口方便。

Botan : C++加密库

Crypto++ : 一个有关加密方案的免费的C++库

GnuPG : OpenPGP标准的完整实现

GnuTLS : 实现了SSL,TLS和DTLS协议的安全通信库

Libgcrypt : 基于GnuPG的加密图形库。

Libmcrypt : 线程安全,提供统一的API。

LibreSSL : 免费的SSL/TLS协议,属于2014 OpenSSL的一个分支

LibTomCrypt : 一个非常全面的,模块化的,可移植的加密工具

libsodium : 基于NaCI的加密库,固执己见,容易使用

Nettle : 底层的加密库

OpenSSL : 一个强大的,商用的,功能齐全的,开放源代码的加密库。

Tiny AES128 in C : 用C实现的一个小巧,可移植的实现了AES128ESB的加密算法

十二、数据库

hiberlite : 用于Sqlite3的C++对象关系映射

hiredis : 用于Redis数据库的很简单的C客户端库

LevelDB : 快速键值存储库

LMDB : 符合数据库四大基本元素的嵌入键值存储

MySQL++ : 封装了MySql的C API的C++ 包装器

RocksDB : 来自Facebook的嵌入键值的快速存储

SQLite : 一个完全嵌入式的,功能齐全的关系数据库,只有几百KB,可以正确包含到你的项目中。

Redis : 一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库.

MongoDB : 一个基于分布式文件存储的数据库

十三、调试

Boost.Test : Boost测试库

Catch : 一个很时尚的,C++原生的框架,只包含头文件,用于单元测试,测试驱动开发和行为驱动开发。

CppUnit : 由JUnit移植过来的C++测试框架

CTest : CMake测试驱动程序

GoogleTest : 谷歌C++测试框架

ig-debugheap : 用于跟踪内存错误的多平台调试堆

libtap : 用C语言编写测试

MemTrack : 用于C++跟踪内存分配

MicroProfile : 跨平台的网络试图分析器

minUnit : 使用C写的迷你单元测试框架,只使用了两个宏

Remotery : 用于web视图的单一C文件分析器

UnitTest++ : 轻量级的C++单元测试框架

十四、容器

C++ B-Tree : 基于B树数据结构,实现命令内存容器的模板库

Hashmaps : C++中开放寻址哈希表算法的实现

十五、游戏引擎

Cocos2d-x : 一个跨平台框架,用于构建2D游戏,互动图书,演示和其他图形应用程序。

Grit : 社区项目,用于构建一个免费的游戏引擎,实现开放的世界3D游戏。

lrrlicht : C++语言编写的开源高性能的实时#D引擎

PolyCode : C++实现的用于创建游戏的开源框架(与Lua绑定)。

十六、图形库

bgfx : 跨平台的渲染库

Cairo : 支持多种输出设备的2D图形库

Horde3D : 一个小型的3D渲染和动画引擎

magnum : C++11和OpenGL 2D/3D 图形引擎

Ogre 3D : 用C++编写的一个面向场景,实时,灵活的3D渲染引擎(并非游戏引擎)

OpenSceneGraph : 具有高性能的开源3D图形工具包

Panda3D : 用于3D渲染和游戏开发的框架,用Python和C++编写。

Skia : 用于绘制文字,图形和图像的完整的2D图形库

urho3d : 跨平台的渲染和游戏引擎。

十七、图像处理

Boost.GIL : 通用图像库

CImg : 用于图像处理的小型开源C++工具包

FreeImage : 开源库,支持现在多媒体应用所需的通用图片格式和其他格式。

GDCM : Grassroots DICOM 库

ITK : 跨平台的开源图像分析系统

Magick++ : ImageMagick程序的C++接口

OpenCV : 开源计算机视觉类库

tesseract-ocr : OCR引擎

VIGRA : 用于图像分析通用C++计算机视觉库

VTK : 用于3D计算机图形学,图像处理和可视化的开源免费软件系统。

十八、国际化

gettext : GNU gettext

IBM ICU : 提供Unicode 和全球化支持的C、C++ 和Java库

libiconv : 用于不同字符编码之间的编码转换库

十九、Json库

frozen : C/C++的Jason解析生成器

Jansson : 进行编解码和处理Jason数据的C语言库

jbson : C++14中构建和迭代BSON data,和Json 文档的库

JeayeSON : 非常健全的C++ JSON库,只包含头文件

JSON++ : C++ JSON 解析器

json-parser : 用可移植的ANSI C编写的JSON解析器,占用内存非常少

json11 : 一个迷你的C++11 JSON库

jute : 非常简单的C++ JSON解析器

ibjson : C语言中的JSON解析和打印库,很容易和任何模型集成

libjson : 轻量级的JSON库

PicoJSON : C++中JSON解析序列化,只包含头文件

Qt-Json : 用于JSON数据和 QVariant层次间的相互解析的简单类

QJson : 将JSON数据映射到QVariant对象的基于Qt的库

RepidJSON : 用于C++的快速JSON 解析生成器,包含SAX和DOM两种风格的API

YAJL : C语言中快速流JSON解析库

二十、日志

Boost.Log : 设计非常模块化,并且具有扩展性

easyloggingpp : C++日志库,只包含单一的头文件。

Log4cpp : 一系列C++类库,灵活添加日志到文件,系统日志,IDSA和其他地方。

templog : 轻量级C++库,可以添加日志到你的C++应用程序中

二十一、机器学习,人工智能

btsk : 游戏行为树启动器工具

Evolving Objects : 基于模板的,ANSI C++演化计算库,能够帮助你非常快速地编写出自己的随机优化算法。

Caffe : 快速的神经网络框架

CCV : 以C语言为核心的现代计算机视觉库

mlpack : 可扩展的C++机器学习库

OpenCV : 开源计算机视觉库

Recommender : 使用协同过滤进行产品推荐/建议的C语言库。

SHOGUN : Shogun 机器学习工具

sofia-ml : 用于机器学习的快速增量算法套件

二十二、数学库

Armadillo : 高质量的C++线性代数库,速度和易用性做到了很好的平衡。语法和MatlAB很相似

blaze : 高性能的C++数学库,用于密集和稀疏算法。

ceres-solver : 来自谷歌的C++库,用于建模和解决大型复杂非线性最小平方问题。

CGal : 高效,可靠的集合算法集合

CML : 用于游戏和图形的免费C++数学库

Eigen : 高级C++模板头文件库,包括线性代数,矩阵,向量操作,数值解决和其他相关的算法。

GMTL : 数学图形模板库是一组广泛实现基本图形的工具。

GMP : 用于个高精度计算的C/C++库,处理有符号整数,有理数和浮点数。

二十三、多媒体库

GStreamer : 构建媒体处理组件图形的库

LIVE555 Streaming Media : 使用开放标准协议(RTP/RTCP, RTSP, SIP) 的多媒体流库

libVLC : libVLC (VLC SDK)媒体框架

QtAV : 基于Qt和FFmpeg的多媒体播放框架,能够帮助你轻而易举地编写出一个播放器

SDL : 简单直控媒体层

SFML : 快速,简单的多媒体库

二十四、物理学

Box2D : 2D的游戏物理引擎。

Bullet : 3D的游戏物理引擎。

Chipmunk : 快速,轻量级的2D游戏物理库

LiquidFun : 2D的游戏物理引擎

ODE : 开放动力学引擎-开源,高性能库,模拟刚体动力学。

ofxBox2D : Box2D开源框架包装器。

Simbody : 高性能C++多体动力学/物理库,模拟关节生物力学和机械系统,像车辆,机器人和人体骨骼。

二十五、机器人学

MOOS-Ivp : 一组开源C++模块,提供机器人平台的自主权,尤其是自主的海洋车辆。

MRPT : 移动机器人编程工具包

PCL : 点云库是一个独立的,大规模的开放项目,用于2D/3D图像和点云处理。

Robotics Library : 一个独立的C++库,包括机器人动力学,运动规划和控制。

RobWork : 一组C++库的集合,用于机器人系统的仿真和控制。

ROS : 机器人操作系统,提供了一些库和工具帮助软件开发人员创建机器人应用程序。

二十六、科学计算

FFTW : 用一维或者多维计算DFT的C语言库。

GSL : GNU科学库。

二十七、脚本

ChaiScript : 用于C++的易于使用的嵌入式脚本语言。

Lua : 用于配置文件和基本应用程序脚本的小型快速脚本引擎。

luacxx : 用于创建Lua绑定的C++ 11 API

SWIG : 一个可以让你的C++代码链接到JavaScript,Perl,PHP,Python,Tcl和Ruby的包装器/接口生成器

V7 : 嵌入式的JavaScript 引擎。

V8 : 谷歌的快速JavaScript引擎,可以被嵌入到任何C++应用程序中。

二十八、序列化

Cap'n Proto : 快速数据交换格式和RPC系统。

cereal : C++11 序列化库

FlatBuffers : 内存高效的序列化库

MessagePack : C/C++的高效二进制序列化库,例如 JSON

ProtoBuf : 协议缓冲,谷歌的数据交换格式。

ProtoBuf-c : C语言的协议缓冲实现

SimpleBinaryEncoding : 用于低延迟应用程序的对二进制格式的应用程序信息的编码和解码。

Thrift : 高效的跨语言IPC/RPC,用于C++,Java,Python,PHP,C#和其它多种语言中,最初由Facebook开发。

二十九、视频库

libvpx : VP8/VP9编码解码SDK

FFMpeg : 一个完整的,跨平台的解决方案,用于记录,转换视频和音频流。

libde265 : 开放的h.265视频编解码器的实现。

OpenH264 : 开源H.364 编解码器。

Theora : 免费开源的视频压缩格式。

三十、XML库

Expat : 用C语言编写的xml解析库

LibXml2 : Gnome的xml C解析器和工具包

LibXml++ : C++的xml解析器

PugiXML : 用于C++的,支持XPath的轻量级,简单快速的XML解析器。

RapidXML : 试图创建最快速的XML解析器,同时保持易用性,可移植性和合理的W3C兼容性。

TinyXML : 简单小型的C++XML解析器,可以很容易地集成到其它项目中。

TinyXML2 : 简单快速的C++CML解析器,可以很容易集成到其它项目中。

TinyXML++ : TinyXML的一个全新的接口,使用了C++的许多许多优势,模板,异常和更好的异常处理。

Xerces-C++ : 用可移植的C++的子集编写的XML验证解析器

脚本语言:搭建高性能的混合系统

本文摘自https://time.geekbang.org/column/article/245905,请支持正版付费

C++的高效、灵活和强大本文将不再赘述,使用现代的C++特性,再加上标准库、第三方库,C++几乎无所不能。但是,C++ 也有自己的“阿喀琉斯之踵”,那就是语言复杂、学习曲线陡峭、开发周期长、排错 / 维护成本高。

所以,C++ 不能完全适应现在的快速开发和迭代的节奏,最终只能退到后端、底层等领域。要想充分发挥 C++ 的功力,就要辅助其他的语言搭建混合系统,尽量扬长避短,做好那最关键、最核心的部分,这样才能展现出它应有的价值。由于当前的操作系统、虚拟机、解释器、引擎很多都是用 C 或者 C++ 编写的,所以,使用 C++,可以很容易地编写各种底层模块,为上层的 Java、Go 等语言提供扩展功能。不过,今天我不去说这些大型语言,而是讲两种轻便的脚本语言:Python 和 Lua,看看 C++ 怎么和它们俩实现无缝对接:以 C++ 为底层基础,Python 和 Lua 作为上层建筑,共同搭建起高性能、易维护、可扩展的混合系统。

Python

Python 应该是除了 JavaScript 以外最流行的一种脚本语言了,一直在 TIOBE 榜单里占据前三名的位置。而且,在新兴的大数据、人工智能、科学计算等领域,也都有着广泛的应用。很多大公司都长期招聘 Python 程序员,就是看中了它的高生产率。

Python 本身就有 C 接口,可以用 C 语言编写扩展模块,把一些低效耗时的功能改用 C 实现,有的时候,会把整体性能提升几倍甚至几十倍。但是,使用纯 C 语言写扩展模块非常麻烦,那么,能不能利用 C++ 的那些高级特性来简化这部分的工作呢?很多人都想到了这个问题,于是,就出现了一些专门的 C++/Python 工具,使用 C++ 来开发 Python 扩展。其中,最好的一个就是pybind11

pybind11 借鉴了“前辈”Boost.Python,能够在 C++ 和 Python 之间自由转换,任意翻译两者的语言要素,比如把 C++ 的 vector 转换为 Python 的列表,把 Python 的元组转换为 C++ 的 tuple,既可以在 C++ 里调用 Python 脚本,也可以在 Python 里调用 C++ 的函数、类。

pybind11 名字里的“11”表示它完全基于现代 C++ 开发(C++11 以上),所以没有兼容旧系统的负担。它使用了大量的现代 C++ 特性,不仅代码干净整齐,运行效率也更高。

下面,我们看看怎么用 pybind11,让 C++ 来辅助 Python,提升 Python 的性能。

pybind11 是一个纯头文件的库,但因为必须结合 Python,所以首先要有 Python 的开发库,然后再用 pip 工具安装。

pybind11 支持 Python2.7、Python3 和 PyPy,这里用的是 Python3:


apt-get install python3-dev
apt-get install python3-pip
pip3 install pybind11

pybind11 充分利用了 C++ 预处理和模板元编程,把原本无聊重复的代码都隐藏了起来,展现了“神奇的魔法”——只需要短短几行代码,就可以实现一个 Python 扩展模块。具体怎么实现呢?

实际上,你只要用一个宏“PYBIND11_MODULE”,再给它两个参数,Python 模块名和 C++ 实例对象名,就可以了。


#include <pybind11/pybind11.h>        // pybind11的头文件

PYBIND11_MODULE(pydemo, m)            // 定义Python模块pydemo
{
  m.doc() = "pybind11 demo doc";      // 模块的说明文档
}                                     // Python模块定义结束

代码里的 pydemo 就是 Python 里的模块名,之后在 Python 脚本里必须用这个名字才能 import。

第二个参数“m”其实是 pybind11::module 的一个实例对象,封装了所有的操作,比如这里的 doc() 就是模块的说明文档。它只是个普通的变量,起什么名字都可以,但为了写起来方便,一般都用“m”。

假设这个 C++ 源文件名是“pybind.cpp”,现在你就可以用 g++ 把它编译成在 Python 里调用的模块了,不过编译命令比较复杂:


g++ pybind.cpp               \                  #编译的源文件
   -std=c++11 -shared -fPIC   \                 #编译成动态库
  `python3 -m pybind11 --includes` \            #获得包含路径
  -o pydemo`python3-config --extension-suffix`  #生成的动态库名字

稍微解释一下。第一行是指定编译的源文件,第二行是指定编译成动态库,这两个不用多说。第三行调用了 Python,获得 pybind11 所在的包含路径,让 g++ 能够找得到头文件。第四行最关键,是生成的动态库名字,前面必须是源码里的模块名,而后面那部分则是 Python 要求的后缀名,否则 Python 运行时会找不到模块。

编译完后会生成一个大概这样的文件:pydemo.cpython-35m-x86_64-linux-gnu.so,现在就可以在 Python 里验证了,使用 import 导入,然后用 help 就能查看模块说明:


$ python3
>>> import pydemo
>>> help(pydemo)

刚才的代码非常简单,只是个空模块,里面什么都没有,现在,我们来看看怎么把 C++ 的函数导入 Python。

你需要用的是 def() 函数,传递一个 Python 函数名和 C++ 的函数、函数对象或者是 lambda 表达式,形式上和 Python 的函数也差不多:


namespace py = pybind11;               // 名字空间别名,简化代码

PYBIND11_MODULE(pydemo, m)             // 定义Python模块pydemo
{
  m.def("info",                        // 定义Python函数
    []()                              // 定义一个lambda表达式
    {
      py::print("c++ version =", __cplusplus);  // pybind11自己的打印函数
      py::print("gcc version =", __VERSION__);
      py::print("libstdc++   =", __GLIBCXX__);
    }
  );  

  m.def("add",                       // 定义Python函数
    [](int a, int b)                 // 有参数的lambda表达式
    {
      return a + b;
    }
  );  
}                                     // Python模块定义结束

这样我们就非常轻松地实现了两个 Python 函数,在 Python 里可以验证效果:


import pydemo            # 导入pybind11模块
pydemo.info()            # 调用C++写的函数
x = pydemo.add(1,2)      # 调用C++写的函数

pybind11 也支持函数的参数、返回值使用标准容器,会自动转换成 Python 里的 list、dict,不过你需要额外再包含一个“stl.h”的头文件。下面的示例代码演示了 C++ 的 string、tuple 和 vector 是如何用于 Python 的:


#include <pybind11/stl.h>              // 转换标准容器必须的头文件

PYBIND11_MODULE(pydemo, m)             // 定义Python模块pydemo
{
  m.def("use_str",                     // 定义Python函数
    [](const string& str)              // 入参是string
    {
        py::print(str);
        return str + "!!";            // 返回string
    }
  );  

  m.def("use_tuple",                  // 定义Python函数
    [](tuple<int, int, string> x)     // 入参是tuple
    {
        get<0>(x)++;
        get<1>(x)++;
        get<2>(x)+= "??";
        return x;                     // 返回元组
    }
  );  

  m.def("use_list",                   // 定义Python函数
    [](const vector<int>& v)          // 入参是vector
    {
        auto vv = v;
        py::print("input :", vv);
        vv.push_back(100);
        return vv;                    // 返回列表       
    }
  );
}

因为都是面向对象的编程语言,C++ 里的类也能够等价地转换到 Python 里面调用,这要用到一个特别的模板类 class_,注意,它有意模仿了关键字 class,后面多了一个下划线。

我拿一个简单的 Point 类来举个例子:


class Point final
{
public:
    Point() = default;
    Point(int a);
public:
    int get() const;
    void set(int a);
};

使用 pybind11,你需要在模板参数里写上这个类名,然后在构造函数里指定它在 Python 里的名字。导出成员函数还是调用函数 def(),但它会返回对象自身的引用,所以就可以连续调用,在一句话里导出所有接口:


py::class_<Point>(m, "Point")           // 定义Python类
    .def(py::init())                    // 导出构造函数
    .def(py::init<int>())               // 导出构造函数
    .def("get", &Point::get)            // 导出成员函数
    .def("set", &Point::set)            // 导出成员函数
    ;

对于一般的成员函数来说,定义的方式和普通函数一样,只是你必须加上取地址操作符“&”,把它写成函数指针的形式。而构造函数则比较特殊,必须调用 init() 函数来表示,如果有参数,还需要在 init() 函数的模板参数列表里写清楚。pybind11 的功能非常丰富,我们不可能一下子学完全部的功能,刚才说的这些只是最基本,也是非常实用的功能。除了这些,它还支持异常、枚举、智能指针等很多 C++ 特性,你可以再参考一下它的文档,学习一下具体的方法,挖掘出它的更多价值。如果你在工作中重度使用 Python,那么 pybind11 绝对是你的得力助手,它能够让 C++ 紧密地整合进 Python 应用里,让 Python 跑得更快、更顺畅,建议你有机会就尽量多用。

Lua

接下来我要说的第二个脚本语言是小巧高效的 Lua,号称是“最快的脚本语言”。

你可能对 Lua 不太了解,但你一定听说过《魔兽世界》《愤怒的小鸟》吧,它们就在内部大量使用了 Lua 来编写逻辑。在游戏开发领域,Lua 可以说是一种通用的工作语言。

Lua 与其他语言最大的不同点在于它的设计目标:不追求“大而全”,而是“小而美”。Lua 自身只有很小的语言核心,能做的事情很少。但正是因为它小,才能够很容易地嵌入到其他语言里,为“宿主”添加脚本编程的能力,让“宿主”更容易扩展和定制。

标准的 Lua(PUC-Rio Lua)使用解释器运行,速度虽然很快,但和 C/C++ 比起来还是有差距的。所以,你还可以选择另一个兼容的项目:LuaJIT(https://luajit.org/)。它使用了 JIT(Just in time)技术,能够把 Lua 代码即时编译成机器码,速度几乎可以媲美原生 C/C++ 代码。

不过,LuaJIT 也有一个问题,它是一个个人项目,更新比较慢,最新的 2.1.0-beta3 已经是三年前的事情了。所以,我推荐你改用它的一个非官方分支:OpenResty-LuaJIT(https://github.com/openresty/luajit2)。它由 OpenResty 负责维护,非常活跃,修复了很多小错误。


git clone [email protected]:openresty/luajit2.git
make && make install

和 Python 一样,Lua 也有 C 接口用来编写扩展模块,但因为它比较小众,所以 C++ 项目不是很多。现在我用的是 LuaBridge,虽然它没有用到太多的 C++11 新特性,但也足够好。

LuaBridge 是一个纯头文件的库,只要下载下来,把头文件拷贝到包含路径,就能够直接用:

git clone [email protected]:vinniefalco/LuaBridge.git

我们先来看看在 Lua 里怎么调 C++ 的功能。

和前面说的 pybind11 类似,LuaBridge 也定义了很多的类和方法,可以把 C++ 函数、类注册到 Lua 里,让 Lua 调用。

但我不建议你用这种方式,因为我们现在有 LuaJIT。它内置了一个 ffi 库(Foreign Function Interface),能够在 Lua 脚本里直接声明接口函数、直接调用,不需要任何的注册动作,更加简单方便。而且这种做法还越过了 Lua 传统的栈操作,速度也更快。

使用 ffi 唯一要注意的是,它只能识别纯 C 接口,不认识 C++,所以,写 Lua 扩展模块的时候,内部可以用 C++,但对外的接口必须转换成纯 C 函数。

下面我写了一个简单的 add() 函数,还有一个全局变量,注意里面必须要用 extern "C"声明:


extern "C" {                // 使用纯C语言的对外接口
int num = 10; 
int my_add(int a, int b); 
}

int my_add(int a, int b)    // 一个简单的函数,供Lua调用
{
    return a + b;
}

然后就可以用 g++ 把它编译成动态库,不像 pybind11,它没有什么特别的选项:


g++ lua_shared.cpp -std=c++11 -shared -fPIC -o liblua_shared.so

在 Lua 脚本里,你首先要用 ffi.cdef 声明要调用的接口,再用 ffi.load 加载动态库,这样就会把动态库所有的接口都引进 Lua,然后就能随便使用了:


local ffi = require "ffi"            -- 加载ffi库
local ffi_load = ffi.load            -- 函数别名
local ffi_cdef = ffi.cdef

ffi_cdef[[                           // 声明C接口
int num;
int my_add(int a, int b);
]]

local shared = ffi_load("./liblua_shared.so")    -- 加载动态库 

print(shared.num)                                -- 调用C接口
local x = shared.my_add(1, 2)                    -- 调用C接口

在 ffi 的帮助下,让 Lua 调用 C 接口几乎是零工作量,但这并不能完全发挥出 Lua 的优势。

因为和 Python 不一样,Lua 很少独立运行,大多数情况下都要嵌入在宿主语言里,被宿主调用,然后再“回调”底层接口,利用它的“胶水语言”特性去粘合业务逻辑。

要在 C++ 里嵌入 Lua,首先要调用函数 luaL_newstate(),创建出一个 Lua 虚拟机,所有的 Lua 功能都要在它上面执行。

因为 Lua 是用 C 语言写的,Lua 虚拟机用完之后必须要用函数 lua_close() 关闭,所以最好用 RAII 技术写一个类来自动管理。可惜的是,LuaBridge 没有对此封装,所以只能自己动手了。这里我用了智能指针 shared_ptr,在一个 lambda 表达式里创建虚拟机,顺便再打开 Lua 基本库:


auto make_luavm = []()              // lambda表达式创建虚拟机
{
    std::shared_ptr<lua_State> vm(  // 智能指针
        luaL_newstate(), lua_close  // 创建虚拟机对象,设置删除函数
        );
    luaL_openlibs(vm.get());        // 打开Lua基本库

    return vm; 
};
#define L vm.get()                  // 获取原始指针,宏定义方便使用

在 LuaBridge 里,一切 Lua 数据都被封装成了 LuaRef 类,完全屏蔽了 Lua 底层那难以理解的栈操作。它可以隐式或者显式地转换成对应的数字、字符串等基本类型,如果是表,就可以用“[]”访问成员,如果是函数,也可以直接传参调用,非常直观易懂。

使用 LuaBridge 访问 Lua 数据时,还要注意一点,它只能用函数 getGlobal() 看到全局变量,所以,如果想在 C++ 里调用 Lua 功能,就一定不能加“local”修饰。

给你看一小段代码,它先创建了一个 Lua 虚拟机,然后获取了 Lua 内置的 package 模块,输出里面的默认搜索路径 path 和 cpath:


auto vm = make_luavm();                  // 创建Lua虚拟机
auto package = getGlobal(L, "package");  // 获取内置的package模块

string path  = package["path"];          // 默认的lua脚本搜索路径
string cpath = package["cpath"];         // 默认的动态库搜索路径

你还可以调用 luaL_dostring() 和 luaL_dofile() 这两个函数,直接执行 Lua 代码片段或者外部的脚本文件。注意,luaL_dofile() 每次调用都会从磁盘载入文件,所以效率较低。如果是频繁调用,最好把代码读进内存,存成一个字符串,再用 luaL_dostring() 运行:

luaL_dostring(L, "print('hello lua')");  // 执行Lua代码片段
luaL_dofile(L, "./embedded.lua");        // 执行外部的脚本文件

在 C++ 里嵌入 Lua,还有另外一种方式:提前在脚本里写好一些函数,加载后在 C++ 里逐个调用,这种方式比执行整个脚本更灵活。

具体的做法也很简单,先用 luaL_dostring() 或者 luaL_dofile() 加载脚本,然后调用 getGlobal() 从全局表里获得封装的 LuaRef 对象,就可以像普通函数一样执行了。由于 Lua 是动态语言,变量不需要显式声明类型,所以写起来就像是 C++ 的泛型函数,但却更简单:


string chunk = R"(                    -- Lua代码片段
    function say(s)                   -- Lua函数1
        print(s)
    end
    function add(a, b)                -- Lua函数2
        return a + b
    end
)";

luaL_dostring(L, chunk.c_str());      // 执行Lua代码片段

auto f1 = getGlobal(L, "say");        // 获得Lua函数
f1("say something");                  // 执行Lua函数

auto f2 = getGlobal(L, "add");        // 获得Lua函数
auto v = f2(10, 20);                  // 执行Lua函数

只要掌握了上面的这些基本用法,并合理地划分出 C++ 与 Lua 的职责边界,就可以搭建出“LuaJIT + LuaBridge + C++”的高性能应用,运行效率与开发效率兼得。比如说用 C++ 写底层的框架、引擎,暴露出各种调用接口作为“业务零件”,再用灵活的 Lua 脚本去组合这些“零件”,写上层的业务逻辑。小结

结语

好了,今天讲了怎么基于 C++ 搭建混合系统,介绍了 Python 和 Lua 这两种脚本语言。

Python 很“大众”,但比较复杂、性能不是特别高;而 Lua 比较“小众”,很小巧,有 LuaJIT 让它运行速度极快。你可以结合自己的实际情况来选择,比如语言的熟悉程度、项目的功能 / 性能需求、开发的难易度,等等。

今天的内容也比较多,简单小结一下要点:

  • C++ 高效、灵活,但开发周期长、成本高,在混合系统里可以辅助其他语言,编写各种底层模块提供扩展功能,从而扬长避短;
  • pybind11 是一个优秀的 C++/Python 绑定库,只需要写很简单的代码,就能够把函数、类等 C++ 要素导入 Python;
  • Lua 是另一种小巧快速的脚本语言,它的兼容项目 LuaJIT 速度更快;
  • 使用 LuaBridge 可以导出 C++ 的函数、类,但直接用 LuaJIT 的 ffi 库更好;
  • 使用 LuaBridge 也可以很容易地执行 Lua 脚本、调用 Lua 函数,让 Lua 跑在 C++ 里。

image

[转]NextJS vs. NuxtJS vs. GatsbyJS

NextJS vs. NuxtJS vs. GatsbyJS

由 Kara Eads 发布于 Unsplash

所谓服务端渲染,就是将原先运行在客户端的 JavaScript 框架的渲染过程放在服务器上,渲染出静态的 HTML 和 CSS 的过程。

为什么说它很重要呢?

我们都想提升网站的加载速度 —— 而服务端渲染技术就是这么一个可以加速页面渲染的工具。下面我们来看一看页面渲染的关键步骤:浏览器渲染页面的关键步骤是接受服务端发送的关键数据并渲染成可视化的页面,如果我们能更快地将这些关键资源分发到浏览器,那么页面就可以更快地渲染好并呈现给用户。

服务端渲染的原理

浏览器渲染你的应用的速度,取决于你如何构建它。浏览器收到的第一个东西是 HTML 文档。文档包括了指向各种资源的引用 —— 如图片、CSS 和 JavaScript 代码。在浏览器解析 HTML 文档时,它知道要去获取并下载这些资源。所以,即使浏览器已经下载好了 HTML,也还是会在 CSS 解析完成之后才会开始渲染页面。

一旦 CSS 解析结束,浏览器便进一步地渲染页面。也就是说,只需要 HTML 和 CSS,浏览器就能渲染页面。我们都知道浏览器就擅长这个,所以这个过程是很快的。

现在,最后的一步是 JavaScript。HTML 文档解析结束后,浏览器会下载你的 JavaScript 文件。如果文件很大,或者网络状况很差,那下载时间会很长,而且浏览器还得解析 JavaScript。在硬件配置较差的设备上,这部分的时间消耗会很可观。并且,如果你的首次渲染依赖于 JavaScript,你还会多次地看到慢的加载过程。JavaScript 应被看作 HTML 和 CSS 的一个增强,因为它的加载是可以延迟的。然而,情况并不总是这么简单。有的网站需要一些严重依赖于 JavaScript 的功能 —— 这类网站使用了 JavaScript 框架。

服务端渲染的过程

上面提到的 JavaScript 框架的渲染过程其实也是可以优化的,比如可以在服务端提前完成其渲染过程,再将生成好的 HTML 下发至浏览器。

所以,用户几乎能立刻看到你之前在服务端提前生成好的 HTML 页面,同时 JavaScript 在后台启动执行。这不一定能让你的页面比非服务端渲染的版本加载的要快,但它确实能在 JavaScript 还在后台下载的过程中给用户一些能看到的内容 —— 这挺好的。

调查与统计

在我们进一步讨论之前,让我们来看看一些来自不同网站的统计信息。

JavaScript 的情况

根据一项关于 JavaScript 情况的调查,我们可以看到,Next 排在了 Nuxt 和 Gatsby 之前。在本文中,我们不会考虑 Express 等,因为我们只关注用于服务端渲染的 JavaScript 框架。

图 01: https://2019.stateofjs.com/back-end/

根据下面的统计,我们可以看到用户对与 JavaScript 相关的后端框架的关注度。可以看出,NextJS 比起 NuxtJS 和 GatsbyJS,用户的使用量和兴趣都是最高的。

图 02: https://2019.stateofjs.com/back-end/

GitHub 仓库统计信息

图 03: https://github.com/vercel/next.js/

图 04: https://github.com/nuxt/nuxt.js

图 05: https://github.com/gatsbyjs/gatsby

依据这些 GitHub 仓库,我们可以看到开发者越来越被 NextJS 所吸引 —— 不管是 Watch、Fork 还是 Star 数,NextJS 都来得高一些。但 Gatsby 在开发者之间也差不多受欢迎。

为什么选 NextJS?

Next 是快速成长的 React 框架之一,尤其是在服务端渲染方面。作者把 NextJS 称作一个轻量级框架。我个人认为把它看作一个平台或者是起始模板更合适。

图 06: https://2019.stateofjs.com/back-end/nextjs/

NextJS 的优点

  • 它默认在用户端和服务器端都使用支持热重载的 Webpack。Babel 也默认用诸如 env 或 JSX 的 preset 来编译你的代码。
  • 所有东西默认都是服务端渲染的。
  • 你可以在四分钟内起手写一个略为复杂的 React 应用。另外,学习 Next 也没有任何问题 —— 官方网站有专门的学习页面。你也可以看看它的 GitHub 页面。
  • 你只需要在一个特定的目录下新建 JavaScript 文件,就能创建路由。当然你也可以自定义路由。
  • 服务器端用的是 Node,你可以做任何你想做的事,比如用 Express。
  • 它能基于 import 自动地拆分你的应用 —— 不必要的代码不会被加载。
  • 获取数据极其简单。
  • 想像一下你要学习 NextJS —— 你可以用它做什么?如果你是一个自由开发者,你可以很快开始一个新项目;如果你要构建一个潜在的大项目,NextJS 也会很有用。
  • 你可以配置你需要的任何东西 —— 从页面初始化和路由,到 Webpack 和 Babel 配置。
  • 在任何支持 Node 的环境都可以部署 NextJS 应用。
  • 为你完整地处理了搜索引擎优化(SEO)。
  • 总的来说,NextJS 为你做了大量的事情。

不该用 NextJS 的地方

如果你在构建一个简单的应用,我估计 NextJS 会过犹不及。

如果你打算把一个服务端的应用集成到 NextJS 应用中,我不建议你立即就开始这么做,因为实际上是做不到的 —— 工作量太大了。

为什么选 NuxtJS?

NuxtJS 是在 VueJS 上层构建的高层次框架,能帮助你构建适用于生产环境的 Vue 应用。

图 07: https://2019.stateofjs.com/back-end/nuxt/

NuxtJS 的优点

  • 构建一个可用于生产环境的 Vue 应用是比较复杂的。Nuxt 不仅预先配置好了 Vuex、Vue Router 和 Vue-meta,还聪明地使用基于充分的研究得来的最佳实践,来预设你的项目。 这些,Vue 并没有以开箱即用的方式提供给你。
  • 创建 Nuxt 应用很简单。Nuxt 脚手架会问你要安装哪些库,比如 ESLint ,或者某个要用的 CSS 框架。
  • Vue 的默认项目结构会把资源文件和组件文件放在你的源代码目录,而 Nuxt 把你的应用的页面、视图、路由和其他文件夹放在另外的、基于最佳实践的位置。
  • 由于每样东西都有自己固定的位置,在 Nuxt 应用之间迁移和让开发者尽快上手项目是很简单的。
  • 在大的 Vue 项目中,路由配置会很长。使用 Nuxt,你只需要把你的单文件组件放在页面目录下,Nuxt 就会自动零配置地生成路由。
  • Vue 应用对 SEO 并不友好,而你会希望你的应用的某些页面能被搜索引擎恰当地索引,从而更容易被人找到。最好的解决方案之一是在服务器端预渲染你的页面,但靠自己来配置,可能需要很强的技巧性。而 Nuxt 已经被预设好了,能够借助路由配置在服务器端生成你的应用,并更容易添加 SEO 相关的 tag。

不该使用 NuxtJS 的地方

如果你使用自定义的库来构建应用,使用 Nuxt 可能会颇具挑战性。

如果你第一次使用 Nuxt,同时你的 Vue 应用的开发计划排期比较紧,你可能会遇到一些问题。

应用调试会很痛苦 —— 这是开发者社区中的常见问题。

为什么选 GatsbyJS?

Gatsby 也是一个基于 React 的、GraphQL 驱动的静态站点生成器。更简单地说,Gatsby 是一个静态站点生成器。这是什么意思呢?静态站点的意思是我们放在服务器上的东西是由 Gatsby 生成的静态的 HTML 文件。这与许多网站的工作方式不同。

需要指出的是,静态站点并不意味着不可互动和非动态。我们可以在 Gatsby 服务器上的 HTML 文件中加载 JavaScript,发起 API 请求、进行交互、构建丰富大型的网站等,尽管它们是静态的。

图 08: https://2019.stateofjs.com/back-end/gatsby/

GatsbyJS 的优点

  • Gatsby 使用 GraphQL —— 我们可以从任何地方导入数据到 Gatsby 站点,这太激动人心了。
  • 我们可以使用从数据库中获取到的 Markdown 文件,可以接入常见的 CMS,例如 WordPress 或者其他的 Headless CMS。然而,Gatsby 并不会为我们处理数据 —— 它只是获取拉取到 Gastby 中的数据,并从数据中生成站点。
  • Gatsby 也使用 React 和 CSS,你应该会很熟悉。React 用于所有的模板和 CSS 样式中。也就是说,GraphQL 会拉取来我们的数据,React 处理应该使用何种模板和 CSS 样式。最后,所有东西都会被导出到静态的 Gatsby 站点。
  • Gatsby 是基于插件架构的 —— 由于我们在构建一个静态站点,这是一个很好的架构。
  • Gatsby 有一个强大的团队、开源社区和完善的文档。它是一个开源项目,所以也很有从社区贡献中获得成长的潜力。

不该使用 GatsbyJS 的地方

如果你要和 WordPress 一起使用 Gatsby,你需要换用很多 WordPress 内置的功能 —— 比如,你不能使用主题的自定义布局。

由于 Gatsby 站点是静态的,所以每个改动都需要一次新的部署。

由 Nathan Dumlao 发布于 Unsplash

结论

基于上面的优缺点和调查,我们可以得出结论:NextJS 是未来最好的服务端渲染框架。然而,如果我们看看前端开发的未来,我们可以看到,Vue 在实际项目开发中也表现得不错。基于上面的各项因素,我建议你学习和使用 NextJS。

感谢阅读。

资源

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

程序员必备的 10 大 GitHub 仓库

GitHub 不仅是维护代码的好工具,而且也是学习和成长的好去处。身为一个软件开发者,我一直在寻找有用的 GitHub 仓库,从中学习,以及找到灵感。以下是我最喜欢的 10 个仓库。

构建你自己的 X

GitHub stars: 92.4k

如果你想要做些什么,并且想要获得一些关于如何实现它的指导,这是一个很好的资源。通过浏览列表,你还会发现很多真正有趣的事情。

地址:https://github.com/danistefanovic/build-your-own-x

JavaScript 算法

GitHub stars: 92.1k

一个区别是,软件工程师和软件开发人员更容易掌握算法和数据结构。但是,不管你的背景是什么,这个仓库提供了很多不同的算法,一个详尽的数据结构列表,还有你可能在软件工程面试中遇到的一些典型问题的答案。

地址:https://github.com/trekhleb/javascript-algorithms

OSSU

GitHub stars: 72.8k

不管你是一个从事编程的人,还是一个已经在业界自学的开发者, OSSU 的课程为所有想要学习计算机科学的人提供了大量的免费学习资源。

地址:https://github.com/ossu/computer-science

代码 30 秒

GitHub stars: 72.8k

超过 100 个代码段,涵盖了 JavaScript 中的各种内容,从典型的算法,到你可能会发现自己需要完成的常见任务。非常值得一看。

地址:https://github.com/30-seconds/30-seconds-of-code

真实世界示例应用

GitHub stars: 52.6k

有没有想过要学习如何使用特定的语言 / 技术开发适当的应用?这就是为你准备的 GitHub 仓库!这超出了典型的“ to-do”应用程序, RealWorld 的示例使整个“Medium-style”应用程序更加丰富,包括了所有的钟声、哨声和最佳实践。

地址:https://github.com/gothinkster/realworld

免费编程书籍

GitHub stars: 170k

听起来确实如此。大量的免费编程书籍可以帮助你的知识和理解更上一层楼。

地址:https://github.com/EbookFoundation/free-programming-books

系统设计入门

GitHub stars: 118k

如果你正在寻找高级软件工程(或更高的)职位,那么拥有设计大型系统的能力是很有价值的,很多大型技术公司都希望你具备这一能力。这也是一个重要技巧,如果你打算为你正在做的任何工作构建任何大型系统的话。这份指南提供了大量的信息来帮助你做好准备。

地址:https://github.com/donnemartin/system-design-primer

了不起的 Python

GitHub stars: 92.2k

Python 内建了一系列不同的库、框架和技术的列表。对那些想要学习一种新的编程语言或仅仅想要提高对已有 Python 的了解的人来说,这是一个极好的指南。

地址:https://github.com/vinta/awesome-python

node 最佳实践

GitHub stars 58.6k

我总是找不到足够的最佳实践指南。所以当发现这个 GitHub 仓库时,我觉得必须把它包括进去。自学的坏处之一就是,你并不总是从最佳实践开始。因此,拥有这些详细的指南可以帮助你快速提高技能。

地址:https://github.com/goldbergyoni/nodebestpractices

超棒的机器学习

GitHub stars: 47.8k

像我们之前看到的 Python 清单一样,这个 GitHub 仓库包含大量不错的机器学习资源。

地址:https://github.com/josephmisiti/awesome-machine-learning

原文链接:

https://medium.com/javascript-in-plain-english/10-essential-github-repos-for-software-developers-6a42ebba279

ReactNative

ReactNative

RND,可以简单看做是 ReactNative 在桌面系统上的实现,Native 部分采用C++开发。RND 的 JS Framework 基于 RN 0.57.8,node.js 运行时 8.1.2。

一、开发环境

  1. 安装nodejs:v12.10.0
  2. 安装vscode:v1.46.0
  3. 初始化工程:
npm install -g react-native-cli
react-native init demo --version 0.57.8
npm install --save @suhao/react-node-desktop
react-native clone-demo
  1. 调试
react-native run-demo
  1. 打包
react-native package

二、重要概念

1. Flexbox布局

image

在 flex 容器中默认存在两条轴,水平主轴(main axis) 和垂直的交叉轴(cross axis),这是默认的设置,当然你可以通过修改使垂直方向变为主轴,水平方向变为交叉轴,这个我们后面再说。

在容器中的每个单元块被称之为 flex item,每个项目占据的主轴空间为 (main size), 占据的交叉轴的空间为 (cross size)。

这里需要强调,不能先入为主认为宽度就是 main size,高度就是 cross size,这个还要取决于你主轴的方向,如果你垂直方向是主轴,那么项目的高度就是 main size。

实现 flex 布局需要先指定一个容器,任何一个容器都可以被置顶为 flex 布局,这样容器内部的元素就可以使用 flex 来进行布局。简单说来,如果你使用块元素如 div,你就可以使用 flex,而如果你使用行内元素,你可以使用 inline-flex。

需要注意的是:当时设置 flex 布局之后,子元素的 float、clear、vertical-align 的属性将会失效。

2. Style样式定义

<View style={{width:50,height:50,backgroundColor:'red'}}></View>

在React Native中,您并不需要学习什么特殊的语法来定义样式。仍然是使用JavaScript来写样式。所有的核心组件都接受名为style的属性。这些样式名基本上是遵循了web上的CSS的命名,只是按照JS的语法要求使用了驼峰命名法,例如将background-color改为backgroundColor。

style属性可以是一个普通的JavaScript对象。这是最简单的用法,因而在示例代码中很常见。你还可以传入一个数组——在数组中位置居后的样式对象比居前的优先级更高,这样你可以间接实现样式的继承。 比如:

<Text style={[styles.welcome,{fontSize:30}]}>
    Hello,RN
</Text>

实际开发中组件的样式会越来越复杂,我们建议使用StyleSheet.create来集中定义组件的样式。比如像下面这样:

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    welcome: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
    },
    instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: 5,
    },
    button: {
        backgroundColor:'black'
    }
});

常见的做法是按顺序声明和使用style属性,以借鉴CSS中的“层叠”做法(即后声明的属性会覆盖先声明的同名属性)。

3. Props参数传递

大多数组件在创建时就可以使用各种参数来进行定制。用于定制的这些参数就称为props(属性)。

以常见的基础组件Image为例,在创建一个图片时,可以传入一个名为source的prop来指定要显示的图片的地址,以及使用名为style的prop来控制其尺寸。

自定义的组件也可以使用props。通过在不同的场景使用不同的属性定制,可以尽量提高自定义组件的复用范畴。只需在render函数中引用this.props,然后按需处理即可。

4. State状态控制

我们使用两种数据来控制一个组件:props和state。props是在父组件中指定,而且一经指定,在被指定的组件的生命周期中则不再改变。 对于需要改变的数据,我们需要使用state。

一般来说,你需要在constructor中初始化state(译注:这是ES6的写法,早期的很多ES5的例子使用的是getInitialState方法来初始化state,这一做法会逐渐被淘汰),然后在需要修改时调用setState方法。

大多数组件在创建时就可以使用各种参数来进行定制。用于定制的这些参数就称为props(属性)。

假如我们需要制作一段不停闪烁的文字。文字内容本身在组件创建时就已经指定好了,所以文字内容应该是一个prop而文字的显示或隐藏的状态(快速的显隐切换就产生了闪烁的效果)则是随着时间变化的,因此这一状态应该写到state中。

典型的场景是在接收到服务器返回的新数据,或者在用户输入数据之后。你也可以使用一些“状态容器”比如Redux来统一管理数据流(译注:但我们不建议新手过早去学习redux)。

5. Props和State比较

props是properties的缩写,您可以把任意类型的数据传递给子组件,如此子组件的默认数据源就是props。在使用中可以通过this.props.data调用props属性,但是切记不能用this.props.xxx修改属性,这会导致props错乱。可以把props当做const类型来看待。

state是组件的内部状态属性,主要用来存储组件自身需要的数据。 除了初始化时可能由props来决定,之后就完全由组件自身去维护。 组件中由系统定义了setState方法,每次调用setState时都会更新组件的状态,触发render方法重新渲染界面。 需要注意的是render方法是被异步调用的,这可以保证同步的多个setState方法只会触发一次render,这样做是有利于提高性能的。

6. 组件生命周期

  • 创建阶段:该阶段主要发生在创建组件类的时候,在这个阶段中会初始化组件的属性类型和默认属性
  • 实例化阶段:该阶段主要发生在组件类被调用(实例化)的时候。
    • constructor是对控件的一些状态进行初始化,由于该函数不同于defaultProps,在以后的过程中,会再次调用,所以可以将控制控件的状态的一些变量放在这里初始化。切记,在此处不要使用setState。
    • componentWillMount准备加载组件。 这个调用时机是在组件创建,并初始化了状态之后,在第一次绘制 render() 之前。可以在这里做一些业务初始化操作,也可以设置组件状态。这个函数在整个生命周期中只被调用一次。 如果在这个函数里面调用setState,本次的render函数可以看到更新后的state,并且只渲染一次。
    • render是一个组件必须有的方法,形式为一个函数,渲染界面,并返回JSX或其他组件来构成DOM,和Android的XML布局、WPF的XAML布局类似,只能返回一个顶级元素。
    • componentDidMount()调用了render方法后,组件加载成功并被成功渲染出来以后所执行的hook函数,一般会将网络请求等加载数据的操作,放在这个函数里进行,来保证不会出现UI上的错误。
  • 运行(更新)阶段:
    • componentWillReceiveProps(nextProps):当组件接收到新的props时,会触发该函数。在该函数中,通常可以调用setState()来完成对state的修改。 输入参数 nextProps 是即将被设置的属性,旧的属性还是可以通过 this.props 来获取。在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState() 来更新你的组件状态,这里调用更新状态是安全的,并不会触发额外的 render() 调用
    • shouldComponentUpdate(nextProps, nextState): 返回布尔值(决定是否需要更新组件)。输入参数 nextProps 和上面的 componentWillReceiveProps 函数一样,nextState 表示组件即将更新的状态值。这个函数的返回值决定是否需要更新组件,如果 true 表示需要更新,继续走后面的更新流程。否者,则不更新,直接进入等待状态。 默认情况下,这个函数永远返回 true 用来保证数据变化的时候 UI 能够同步更新。在大型项目中,你可以自己重载这个函数,通过检查变化前后属性和状态,来决定 UI 是否需要更新,能有效提高应用性能。
    • componentWillUpdate(nextProps, nextState):shouldComponentUpdate返回true或者调用forceUpdate之后,就会开始准更新组件,并调用 componentWillUpdate()。 输入参数与 shouldComponentUpdate 一样,在这个回调中,可以做一些在更新界面之前要做的事情。需要特别注意的是,在这个函数里面,你就不能使用 this.setState 来修改状态。这个函数调用之后,就会把 nextProps 和 nextState 分别设置到 this.props 和 this.state 中。紧接着这个函数,就会调用 render() 来更新界面了。
    • render():再确定需要更新组件时,调用render,根据diff算法,渲染界面,生成需要更新的虚拟DOM数据。
    • componentDidUpdate():虚拟DOM同步到DOM中后,执行该方法,可以在这个方法中做DOM操作。 除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate。
    • componentWillMount、componentDidMount和componentWillUpdate、componentDidUpdate可以对应起来。区别在于,前者只有在挂载的时候会被调用;而后者在以后的每次更新渲染之后都会被调用。 ps:绝对不要在componentWillUpdate和componentDidUpdate中调用this.setState方法,否则将导致无限循环调用。
  • 销毁阶段: 该阶段主要发生组件销亡的时候,触发componentWillUnmount。当组件需要从DOM中移除的时候,通常需要做一些取消事件绑定,移除虚拟DOM中对应的组件数据结构,销毁一些无效的定时器等工作,都可以在这个方法中处理。
    • componentWillUnmount: 当组件要被从界面上移除的时候,就会调用 componentWillUnmount。 在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等。

image

7. 生命周期调用和setState

image

8. 原生组件

React Native 的一个很大的技术特点就是它虽然用JavaScript语言开发,但是界面用原生端控件来渲染,从而获得平台上的组件本来就具备的所有特性和原生开发出来的界面一致的体验。

RN 中的原生组件通常包含原生端实现(用i平台原生语言开发)和JS端的载体(将原生控件的属性和方法暴露给JS端)两个部分。

原生组件使得RN具备很大的灵活性,RN官方自带的组件库不能满足需求的时候,可以随时将用原生语言开发的控件(包括开源的)封装成JS端可以使用的组件,这也使得RN 开源社区蓬勃发展,催生了各种开源RN组件,封装了各种高级组件,是RN官方组件的有益补充,降低了使用门槛,提高了开发效率。

9. 原生模块

React Native官方封装了一些API用来调用原生端能力,但是毕竟无法满足各种各样的需求。RN提供的原生模块特性为你提供了一种便捷的途径来将原生端的所有能力暴露给JS端调用。当RN官方还不支持某个你需要的原生特性时,你就可以用原生模块来自己实现该特性的封装。

RN原生模块的实现一般只需要编写原生端代码,暴露一些方法(支持回调,不支持直接返回值,因为跨语言通信是异步的)给JS端,JS端简单调用这些方法就可以了。有时为了使用方便,我们也可以将某个原生模块在JS端对应的实现一个类,封装里面的方法,这样调用方法的时候就可以利用IDE的代码自动补全功能,提高便利性,也可以同时封装一些额外的逻辑,比如参数校验等等。

常见错误

1. SyntaxError: Invalid regular expression: Unterminated character class at new RegExp ()

2. yarn与npm混合使用的依赖验证

  • 参考:yarnpkg/yarn#5240
  • npm5要求在安装完成后的package.json中增加一些元数据,yarn不会产生这些元数据。所以之后运行npm install其他package的时候,npm5会认为之前由yarn安装的一些包是未经验证的(缺少元数据),因此会删除它们
  • 修复:使用yarn,或者再次执行npm install

Python基本数据类型

python中一切皆对象,内置的对象类型有:数字、字符串、列表、字典、元组、文件、集合、布尔型、None等;编程单元类型有函数、模块、类,与实现相关的类型有编译的代码堆栈跟踪,还有套接字对象等。

(学会使用help万能函数,来探索功能哦)

下面,我们先来学习下基本的数据类型,也是核心数据类型。Python的基本数据类型有interge、boolean、string(字符串也称为序列)、list、tuple、dict,可以分为可变(list/dict)和不可变两类(interge/string/tuple)。

变量之赋值一切皆为引用,动态类型但是却是固定类型,不可动态转换

$ str = "abc"         #动态类型 
$ str + 1             #固定类型, ERROR: 不可动态转换
$ print 1 == 1        #print, 条件语句

一、数字

包括整数、浮点数、复数、十进制数、有理分数、集合等。常支持加减乘除,双星号的乘方等。。。

import math
import random

二、字符串:英文双引号,字节串

用来记录文本信息,是python的一种序列。序列包含元素并从左到右或者其他的可定义明了的顺序,可以根据相对位置存取。严格意义上,字符串是单个字符的字符序列,其他的还包含列表、元组。

首先讲下编码,在C++中有宽字节字符、窄字节字符、CP_ACP、CP_UTF8等,文件还有各种格式,如果不遵循统一的规则,或者接入各种第三方模块却未有明确约束,会深陷泥潭不可自拔。

在python中,默认的文件编码是ascii的,默认文件中是不能输入中文的;但是我们可以在文件开始加入utf-8标识来解决。这在C++中是依赖系统的文件属性,太不程序员了。。。

UTF-8是unicode的一种标准的实现。

请动手google搜索下如下两个概念,以及其区别哦。。。
ASCII码
Unicode编码标准

2.1 序列的操作

  • len:默认为ascii的字节长度
  • 转义符:反斜杠,让程序读懂文本** : r(raw原始字符串常量)不转义,u转utf-8字符串
  • 索引:偏移量从0开始,正向索引从左向右,反向索引从右向左
  • 切片:slice[i:j]不包含j,a[-1]取最后一个字符, a[0:]取字符串,负的索引号会简单的与字符串长度相加
  • 替换:replace,修改实际是新建了一个字符串
  • 拼接:%s %d, join
  • 读写文本:open('file.log', 'w');close()

python强制类型转换是失效的,如int("abc1222")是错误的

a = "中文"
#输出6,注意文件增加coding=utf-8也依旧输出6
print len(a)         

#输出2              
print len(u"中文");  
b = a.decode('utf-8')
print len(b)               

单引号,双引号,三引号:
三引号用于多行注释,单双引号可以用于嵌套引号

格式化%,format方法{偏移量占位},{name}

2.2 模式匹配

Python中进行模式匹配,需要导入re模块,包含了类似搜索、分割、替换等。

import re
match = re.match('hello[ \t]*(.*)[ \t]*world', 'Hello     Python world')
match.group(1)

三、列表

可存储任意类型对象,位置相关的,有序的集合,通过偏移来索引,支持嵌套,可变的类型。大小不固定,通过偏移量可以修改列表的大小。列表也是序列的一种,故支持各种序列操作。

最为强大的是,列表没有固定类型的约束。由于python中存储的元素可以看作是指针,故而可以在C++中理解为各种对象的指针void*

3.1 列表的操作

  • 切片:正向索引,反向索引,默认索引
  • 添加:+(生成一个新的列表), extend(接受参数并将该参数每个元素都添加到原有列表中,原地修改列表而不是新建列表);append(添加任意对象到列表末端);insert(插入任意对象到列表中,可以控制插入位置)
  • 修改:直接索引赋值即可
  • 删除:del(索引删除指定位置元素), remove(移除列表中的第一个匹配值,没找到则抛异常),pop(返回最后一个元素,并删除)
  • 成员关系:in not in,返回bool是否在列表中
  • 推导式/列表高级解析:[expr for iter in iterable (if cond_expr)]
  • 排序翻转:sort,reverse, 返回值均为none
  • 内置list方法:返回一个列表,参数为可迭代对象
  • xrange:开始,结束,步长,返回xrange对象,类似一个生成器;处理大量数据节省内存,或者在循环中使用
  • range:返回一个列表
a = [1,2,3]
print a[-1]

b = [[1,2,3],[4,5,6]]
print b

3.2 列表推导式之应用

["%s" % d for d in xrange(10)]
[(x,y) for x in range(2) for y in range(2)]
dict([(x,y) for x in range(2) for y in range(2)])

3.3 引用

b=a,则b为列表的同一个引用

3.4 del

  • del a:删除列表对象的引用
  • del a[:]:清空列表对象里的元素

四、元组和集合

4.1 元组

元组是一个有序的集合,通过偏移来取数据,属于不可变对象,不能原地修改无排序修改等操作

优点:保证数据安全,传递给一个不熟悉的方法或者数据接口时,可以保证方法和接口不会改变我们的数据,以免引发未知问题

a = (1,2,3)
a[1:3]
dir(a)
b = list(a)
b[0] = 5
type(b)
a = tuple(b)
print a

4.2 集合

集合没有顺序的概念,不能使用切片和索引操作

  • 创建:set可变集合,frozenset不可变集合
  • 添加:add,update
  • 删除:remove
  • 成员关系:in,not in
  • 交并差集:& | -
  • set去重:list转换为set,然后再转为list

五、字典

字典是无序,不能通过偏移存取,只能通过键来存取。

  • 内部没有顺序,可嵌套
  • 可以原地修改内容,可变类型
  • 键必须是不可变数据类型,如数字、字符串、元组等,列表、字典等可变对象不能作为键

5.1 字典的操作

  • 创建:{}、dict()
  • 添加内容:a[key]=value
  • 修改内容:a[key]=value,update
  • 删除:del,clear,pop
  • 成员关系:in、has_key
  • keys():返回列表,包含所有键
  • values():返回列表,包含所有值
  • items():生成一个字典的容器,[()]
  • get:

用Lua脚本配合C++开发

image

引言

Lua 是一门脚本语言,它有着一个重要的特性就是:它很容易嵌入其它语言。现在,越来越多的 C++ 服务器和客户端融入了脚本的支持,尤其在网游领域,脚本语言已经渗透到了方方面面,比如你可以在你的客户端增加一个脚本,这个脚本将会帮你在界面上显示新的数据,亦或帮你完成某些任务,亦或帮你查看别的玩家或者 NPC 的状态。对于游戏测试来说,大量繁杂的工作都可以由 Lua 代劳,因此在测试领域它也是一把利器。

关于 Lua 的一些重要基础知识:

Lua 有重要之重要的概念,就是栈。Lua 与别的语言交互以及交换数据,是通过栈完成的。其实简单的解释一下,你可以把栈想象成一个箱子,你要给他数据,就要按顺序一个个的把数据放进去,当然,Lua 执行完毕,可能会有结果返回给你,那么 Lua 还会利用你的箱子,一个个的继续放下去。而你取出返回数据呢,要从箱子顶上取出,如果你想要获得你的输入参数呢?那也很简单,按照顶上返回数据的个数,再按顺序一个个的取出,就行了。不过这里提醒大家,关于栈的位置,永远是相对的,比如 -1 代表的是当前栈顶,-2 代表的是当前栈顶下一个数据的位置。栈是数据交换的地方,一定要有一些栈的概念。

首先、编译 Lua

首先下载 Lua,一般情况下,很容易就可以通过 make macosx 或者 make linux 来编译。这样会生成 liblua.a 静态库文件,我们随后要连接这个库。

其次、准备环境

首先,我们要在开发工具中连接 liblua.a,然后要将 Lua 源代码文件的位置添加到编译器的头文件搜索目录列表里面去,这样我们的编译器才能找到 Lua 的头文件。

然后、创建程序

我们需要创建一个 C++ 的主程序,以便同 Lua 进行通信。

cpp 程序如下:

#include <iostream>
#include <lua.hpp>

extern "C" {
static int lua_cpp_function(lua_State* L) {
  double arg = luaL_checknumber(L, 1);
  lua_pushnumber(L, arg*0.5);
  return 1;
}
}

int main(int argc, const char * argv[]) {
    lua_State *L;
    L = luaL_newstate();
    cout << ">> 载入(可选)标准库,以便使用打印功能" << endl;
    luaL_openlibs(L);
    cout << ">> 载入文件,暂不执行" << endl;
    if (luaL_loadfile(L, "luascript.lua")) {
        cerr << "载入文件出现错误" << endl;
        cerr << lua_tostring(L, -1) << endl;
        lua_pop(L,1);
    }
    
    cout << ">> 从 C++ 写入数据 cppvar" << endl;
    lua_pushnumber(L, 1.1);
    lua_setglobal(L, "cppvar");
    
    cout << ">> 执行 lua 文件" << endl << endl;
    if (lua_pcall(L,0, LUA_MULTRET, 0)) {
        cerr << "执行过程中出现错误" << endl;
        cerr << lua_tostring(L, -1) << endl;
        lua_pop(L,1);
    }
    
    cout << ">> 从 Lua 读取全局变量 luavar 到 C++" << endl;
    lua_getglobal(L, "luavar");
    double luavar = lua_tonumber(L,-1);
    lua_pop(L,1);
    cout << "C++ 从 Lua 读取到的 luavar = " << luavar << endl << endl;
    
    cout << ">> 从 C++ 执行 Lua 的方法 myfunction" << endl;
    lua_getglobal(L, "myluafunction");
    lua_pushnumber(L, 5);
    lua_pcall(L, 1, 1, 0);
    cout << "函数返回值是:" << lua_tostring(L, -1) << endl << endl;
    lua_pop(L,1);
    
    cout << ">> 从 Lua 执行 C++ 的方法" << endl;
    cout << ">>>> 首先在 Lua 中注册 C++ 方法" << endl;
    lua_pushcfunction(L,lua_cpp_function);
    lua_setglobal(L, "cppfunction");
    
    cout << ">>>> 调用 Lua 函数以执行 C++ 函数" << endl;
    lua_getglobal(L, "myfunction");
    lua_pushnumber(L, 5);
    lua_pcall(L, 1, 1, 0);
    cout << "函数返回值是:" << lua_tonumber(L, -1) << endl << endl;
    lua_pop(L,1);
    
    cout << ">> 释放 Lua 资源" << endl;
    lua_close(L);
    
    return 0;
}

其次,是 lua 文件,我们将它命名为 luascript.lua

print("Hello from Lua")
print("Lua code is capable of reading the value set from C++", cppvar)
luavar = cppvar * 3

function myluafunction(times)
  return string.rep("(-)", times)
end

function myfunction(arg)
  return cppfunction(arg)
end

运行 cpp 文件,结果如下:

>> 载入(可选)标准库,以便使用打印功能
>> 载入文件,暂不执行
>> 从 C++ 写入数据 cppvar
>> 执行 lua 文件

Hello from Lua
Lua code is capable of reading the value set from C++	1.1
>> 从 Lua 读取全局变量 luavar 到 C++
C++ 从 Lua 读取到的 luavar = 3.3

>> 从 C++ 执行 Lua 的方法 myfunction
函数返回值是:(-)(-)(-)(-)(-)

>> 从 Lua 执行 C++ 的方法
>>>> 首先在 Lua 中注册 C++ 方法
>>>> 调用 Lua 函数以执行 C++ 函数
函数返回值是:2.5

>> 释放 Lua 资源

代码分析

1. 初始化

lua_State *L;
L = luaL_newstate();
luaL_openlibs(L); // 载入(可选)标准库,以便使用打印功能

if (luaL_loadfile(L, "luascript.lua")) { // 载入文件,暂不执行
    cerr << "载入文件出现错误" << endl;
    cerr << lua_tostring(L, -1) << endl;
    lua_pop(L,1);
}

上述代码创建 lua_State 并载入标准库,同时载入代码 luascript.lua

2. 从 C++ 向 Lua 添加变量

lua_pushnumber(L, 1.1);
lua_setglobal(L, "cppvar");

if (lua_pcall(L,0, LUA_MULTRET, 0)) {
    cerr << "执行过程中出现错误" << endl;
    cerr << lua_tostring(L, -1) << endl;
    lua_pop(L,1);
}

在 C++ 中通过 lua_setglobal 在 Lua 中设置一个全局变量 cppvar。因为 C++ 和 Lua 通过 lua_State 中的堆栈来交换数据,所以要先 push 数据到堆栈,然后调用 lua_setglobal,这样就将数据赋给相应的值。

设置完全局的 cppvar 之后,执行 lua_pcall 来运行我们的 Lua 代码文件,之后,Lua 就可以使用 cppvar 变量。在 Lua 代码中,还创建了新的全局变量 luavar 可以供 C++ 访问。

3. 从 C++ 中读取 Lua 变量

lua_getglobal(L, "luavar");
double luavar = lua_tonumber(L,-1);
lua_pop(L,1);
cout << "C++ 从 Lua 读取到的 luavar = " << luavar << endl << endl;

要从 Lua 中读取数据,我们先要使用 lua_getglobal 将数据放到栈顶,然后将栈顶数据通过 lua_tonumber 转为 double,然后通过 lua_pop 将其移除出栈顶。

4. 从 C++ 中调用 Lua 函数

lua_getglobal(L, "myluafunction");
lua_pushnumber(L, 5);
lua_pcall(L, 1, 1, 0);
cout << "函数返回值是:" << lua_tostring(L, -1) << endl << endl;
lua_pop(L,1);

由了前面的过程,这个比较容易理解了:首先我们通过 lua_getglobal 取得方法名称,即将其放到栈顶,然后通过 lua_pushnumber 给参数赋值,然后通过 lua_pcall 执行。

执行之后,再去栈顶获取返回结果,然后 lua_pop 将其移除。

5. 从 Lua 中调用 C++ 函数

lua_pushcfunction(L,l_cppfunction);
lua_setglobal(L, "cppfunction");

lua_getglobal(L, "myfunction");
lua_pushnumber(L, 5);
lua_pcall(L, 1, 1, 0);
cout << "函数返回值是:" << lua_tonumber(L, -1) << endl << endl;
lua_pop(L,1);

这一段先是使用 lua_pushcfunction 来将 C++ 的方法 l_cppfunction 传递给 Lua,然后通过 lua_setglobal 给予其在 Lua 中的方法名称 cppfunction,接下来执行就很简单了。

6. 释放

lua_close(L);

以上就是一个简单的 Lua 与 C++ 交互的简单例子,算是入门的基础吧。我们可以看到,C++ 和 Lua 能够很自由的进行通信,而我们也可以很方便的修改 Lua 文件代码来实现对 C++ 程序流程的直接控制,对游戏开发、调试有很大的好处。

转自:https://indienova.com/indie-game-development/lua-as-script-with-cpp-development/

Node.js环境

  1. nvm-windows, yvm
  • 下载nvm最新版本
  • nvm list available
  • nvm install lts
  • nvm install latest
  1. .nvmrc, .yvmrc
  • nvm root
  • nvm use
  • node -v > .nvmrc
  1. nrm/yrm: NPM/YARN registry manager
  • nrm ls
  • yrm ls
  1. corepack
  • Node.js >=16.10: npm i -g corepack
  • Node.js < 16.10: corepack enable
  • yarn set version stable
  1. npx

常用批处理

本文整理了工作中常用的批处理命令,汇总后可以便于查找和提高工作效率。

批处理

批处理是一系列DOS命令,通常用于自动执行重复性任务。批处理也称为批处理脚本,就是对某个对象进行批量处理,批处理文件扩展名为bat/cmd。批处理类似于Unix系统中的shell脚本。

批处理的常见规则如下:

  • 正常情况下,每条命令占据一行;可以使用特定符号将多条语句命令写入同一行
  • 系统在解释运行批处理时,首先扫描整个批处理,然后从第一行代码向下逐句执行所有命令,直至程序结尾或者遇到exit命令或者出错意外退出

常用命令

文件夹管理

  • cd/md/rd
  • dir:显示目录中的文件和子目录列表
  • tree:以图形显示驱动器或路径的文件夹结构
  • path:为可执行文件显示或设置一个搜索路径
  • xcopy:复制文件和目录树

文件管理

  • type:显示文本文件内容
  • copy:将一份或多份文件复制到另一个位置
  • del:删除一个或多个文件
  • move:移动文件并重命名文件和目录
  • ren:重命名文件
  • replace:替换文件
  • attrib:显示或更改文件属性
  • find:搜索字符串
  • fc:比较两个文件或两个文件集并显示他们之间的不同

网络命令

  • ping
  • ftp
  • net
  • telnet
  • ipconfig
  • msg:给用户发送消息
  • arp:显示、修改局域网的IP地址-物理地址映射列表

系统管理

  • at:安排在特定日期和时间运行命令和程序
  • shutdown:立即或定时关机或重启
  • tskill:结束进程
  • taskkill:结束进程
  • tasklist:显示进程列表
  • sc:系统服务设置与控制
  • reg:注册表控制台工具
  • powercfg:控制系统上的电源设置

常用命令和解析

  1. 回显命令:echo和@
@                #关闭单行回显
echo off         #从下一行开始关闭回显
@echo off        #从本行开始关闭回显,一般批处理第一行都是这条命令
echo on          #从下一行开始打开回显
echo             #显示当前回显状态
echo.            #输出一个回车换行空白行
  1. 错误码
echo %errorlevel%   #每个命令运行结束可以用这个命令行格式查看返回码,默认值为0,执行出错会设置为1
  1. 显示文件夹内容:dir
dir              #显示当前目录中的文件和子目录
dir /a           #显示当前目录中的文件和子目录,包含隐藏文件和系统文件
dir c: /a:d      #显示当前目录中的目录
dir c: /a:-d     #显示当前目录中的文件
dir c: /b/p      #b显示文件名,p分页显示
dir *.exe /s     #显示当前目录和子目录的所有exe文件
  1. 目录操作及文件:cd/md/rd
cd /d D:\
md d:\a\b\c       #如果不存在将自动创建中级目录
rd abc            #删除当前目录里的abc子目录,要求为空目录
rd /s/q d:\temp   #删除文件夹及其子文件夹和文件,q指示为安静模式
del d:\test.txt   #删除指定文件不能是隐藏、系统、只读文件
del /q/a/f d:\temp\*.*    #删除temp文件夹里的所有文件,包含隐藏、只读、系统文件,不包含子目录
del /q/a/f/s d:\temp\*.*  #同上,同时删除子文件夹里的文件,不包含子目录
  1. 拷贝文件:copy
copy c:\t.txt d:\b.bak   #复制文件并重命名
copy con test.txt        #从屏幕上等待输入,按ctrl+z结束输入,输入内容存为text.txt文件;con-屏幕,prn-打印机,nul-空设备
copy 1.txt + 2.txt 3.txt #合并1.txt和2.txt的内容保存到3.txt,如果不指定3.txt则存到1.txt
copy 1.txt +             #复制文件到自己,实际是修改文件日期
  1. 时间日期
date                     #显示当前日期,/t控制不提示输入新日期,输入时按回车可以略过输入
time                     #显示当前时间,/t控制不提示输入新时间,输入时按回车可以略过输入
  1. 跳转命令:goto和:
:label                   #行首为:表示该行是标签行,标签行不执行操作
goto lable               #跳转到指定的标签那一行
  1. 外部命令
find "abc" c:\test.txt    #find查找命令,在txt文件里查找含abc字符串的行,如果找不到将errorlevel返回码设为1
find /i "abc" c:\test.txt #查找,忽略大小写
find /c "abc" c:\test.txt #显示含abc的行的函数
more c:\test.txt          #逐屏显示文件内容
  1. 逻辑命令和管道命令:&、&&、||和|
&                         #顺序执行多条命令,而不管命令是否执行成功
&&                        #顺序执行多条命令,当遇到执行出错的命令后将不再执行后续命令
find "ok" c:\test.txt && echo 成功
||                        #顺序执行多条命令,当碰到执行正确的命令后将不执行后续命令
find "ok" c:\test.txt || echo 不成功
|                         #管道命令
dir *.* /s/a | find /c ".exe" #先执行dir命令对其输出结果执行后边的find命令
  1. 输出重定向:>和>>,主要将本来显示在屏幕上的内容输出到指定文件,若文件不存在则自动创建
>                             #清除文件中原有的内容后再写入
>>                            #追加内容到文件末尾而不会清除原有内容
type c:\test.txt >prn         #屏幕上不显示文件内容,转向输出到打印机
echo hello world > con        #在屏幕上显示,实际上所有输出默认都为con
copy C:\test.txt f: >nul      #拷贝文件,不显示复制成功提示;如果f盘不存在会显示出错信息
copy c:\test.txt f: >nul 2>nul#拷贝文件,不显示复制成功提示,也不显示出错信息
echo ^^W ^> ^W > c:\test.txt  #生成的文件内容为 ^W > W, ^和>是控制命令,要输出到文件需要在前面加个^符号
  1. 输入信息重定向:<,从文件中获得输入信息而不是屏幕上,用于data time lable等需要等待输入的命令
@echo off
:: 不等待输入直接修改当前日期
echo 2022-09-01 > temp.txt
data < temp.txt
del temp.txt
  1. 命令行传递给批处理的参数:%0、%1、%*
%0                           #批处理文件本身
%1                           #第一个参数
%2                           #第二个参数
%9                           #第九个参数
%*                           #从第一个参数开始的所有参数
%~1                          #删除引号,扩充%1
%~f1                         #将%1扩充到一个完全合格的路径名
%~d1                         #仅将 %1 扩充到一个驱动器号
%~p1                         #仅将 %1 扩充到一个路径
%~n1                         #仅将 %1 扩充到一个文件名
%~x1                         #仅将 %1 扩充到一个文件扩展名
%~s1                         #扩充的路径指含有短名
%~a1                         #将 %1 扩充到文件属性
%~t1                         #将 %1 扩充到文件的日期/时间
%~z1                         #将 %1 扩充到文件的大小
%~$PATH : 1                  #查找列在 PATH 环境变量的目录,并将 %1扩充到找到的第一个完全合格的名称。如果环境变量名未被定义,或者没有找到文件,此组合键会扩充到空字符串


  1. 设置变量: set,引用变量可在变量名前后加 % ,即 %变量名%
set                       #显示目前所有可用的变量,包括系统变量和自定义的变量
echo %SystemDrive%        #显示系统盘盘符。系统变量可以直接引用
set p                     #显示所有以p开头的变量,要是一个也没有就设errorlevel=1
set p=aa1bb1aa2bb2        #设置变量p,并赋值为 = 后面的字符串,即aa1bb1aa2bb2
echo %p%                  #显示变量p代表的字符串,即aa1bb1aa2bb2
echo %p:~6%               #显示变量p中第6个字符以后的所有字符,即aa2bb2
echo %p:~6,3%             #显示第6个字符以后的3个字符,即aa2
echo %p:~0,3%             #显示前3个字符,即aa1
echo %p:~-2%              #显示最后面的2个字符,即b2
echo %p:~0,-2%            #显示除了最后2个字符以外的其它字符,即aa1bb1aa2b
echo %p:aa=c%             #用c替换变量p中所有的aa,即显示c1bb1c2bb2
echo %p:aa=%              #将变量p中的所有aa字符串置换为空,即显示1bb12bb2
echo %p:*bb=c%            #第一个bb及其之前的所有字符被替换为c,即显示c1aa2bb2
set p=%p:*bb=c%           #设置变量p,赋值为 %p:*bb=c% ,即c1aa2bb2
set /a p=39               #设置p为数值型变量,值为39
set /a p=39/10            #支持运算符,有小数时用去尾法,39/10=3.9,去尾得3,p=3
set /a p=p/10             #用 /a 参数时,在 = 后面的变量可以不加%直接引用
set /a p=”1&0″            #”与”运算,要加引号。其它支持的运算符参见set/?
set p=                    #取消p变量
set /p p=请输入           #屏幕上显示”请输入”,并会将输入的字符串赋值给变量p,注意可以用来取代 choice 命令
  1. for循环命令
这个比较复杂,请对照 for/? 来看
for %%i in (c: d: e: f:) do echo %%i
依次调用小括号里的每个字符串,执行 do 后面的命令
注意%%i,在批处理中 for 语句调用参数用2个%
默认的字符串分隔符是"空格键","Tab键","回车键"
for %%i in (*.txt) do find "abc" %%i
对当前目录里所有的txt文件执行 find 命令
for /r . %%i in (*.txt) do find "abc" %%i
在当前目录和子目录里所有的.txt文件中搜索包含 abc 字符串的行
for /r . %%i in (.) do echo %%~pni
显示当前目录名和所有子目录名,包括路径,不包括盘符
for /r d:mp3 %%i in (*.mp3) do echo %%i>>d:mp3.txt
把 d:mp3 及其子目录里的mp3文件的文件名都存到 d:mp3.txt 里去
for /l %%i in (2,1,8) do echo %%i
生成2345678的一串数字,2是数字序列的开头,8是结尾,1表示每次加1
for /f %%i in ('set') do echo %%i
对 set 命令的输出结果循环调用,每行一个
for /f "eol=P" %%i in ('set') do echo %%i
取 set 命令的输出结果,忽略以 P 开头的那几行
for /f %%i in (d:mp3.txt) do echo %%i
显示 d:mp3.txt 里的每个文件名,每行一个,不支持带空格的名称
for /f "delims=" %%i in (d:mp3.txt) do echo %%i
显示 d:mp3.txt 里的每个文件名,每行一个,支持带空格的名称
for /f "skip=5 tokens=4" %%a in ('dir') do echo %%a
对 dir 命令的结果,跳过前面5行,余下的每行取第4列
每列之间的分隔符为默认的"空格"
可以注意到 dir 命令输出的前5行是没有文件名的
for /f "tokens=1,2,3 delims=- " %%a in ('date /t') do (
echo %%a
echo %%b
echo %%c
)
对 date /t 的输出结果,每行取1、2、3列
第一列对应指定的 %%a ,后面的 %%b 和 %%c 是派生出来的,对应其它列
分隔符指定为 - 和"空格",注意 delims=- 后面有个"空格"
其中 tokens=1,2,3 若用 tokens=1-3 替换,效果是一样的
for /f "tokens=2* delims=- " %%a in ('date /t') do echo %%b
取第2列给 %%a ,其后的列都给 %%b
  1. 其他
title 标题               #设置cmd窗口标题
cls                      #清屏
ver                      #显示系统版本
vol                      #显示卷标
label                    #显示卷标,同时提示输入新卷标
pause                    #暂停命令
rem                      #注释命令,注释不执行操作
::                       #注释命令,注释不执行操作
start                    #批处理中调用外部程序的命令,否则等外部程序完成后才继续执行剩下的指令
call                     #批处理中调用另外一个批处理的命令,否则剩下的批处理指令将不会被执行
choice                   #选择命令,让用户输入一个字符,从而选择运行不同的命令,返回码errorlevel为1234……

常用实例

  1. 管理员权限执行
@ECHO OFF

Rd "%WinDir%\system32\test_permissions" >NUL 2>NUL
Md "%WinDir%\System32\test_permissions" 2>NUL||(Echo 请使用右键管理员身份运行!&&PAUSE >NUL&&EXIT)
Rd "%WinDir%\System32\test_permissions" 2>NUL
  1. 删除Visual Studio编译的临时文件
@echo off
echo ----------------------------------------------------
echo Press any key to delete all files with ending:
echo  *.aps *.idb *.ncp *.obj *.pch *.tmp *.sbr
echo Visual c++/.Net junk 
echo ----------------------------------------------------
 
del /F /Q /S *.aps *.idb *.ncp *.obj *.pch *.sbr *.tmp *.bsc *.ilk *.res *.ncb *.opt *.suo *.manifest *.dep
  1. 生成目录链接
mklink /D E:\dev\intermediate  "F:\cache\build\dev"
  1. 进入bat文件所在目录
cd /d "%~dp0"

注意这里的~dp是变量扩充,变量%0是批处理的路径。

Linux操作

习惯于windows的操作,学习下linux的基础操作。

一、Linux基本命令与开发环境

1.1 目录

  • etc:配置文件
  • boot:引导文件,例如grub
  • var:动态数据,经常更迭变化的;例如数据库文件
    • log:日志
  • tmp:存储临时文件
  • home:用户文件夹

1.2 终端命令

  • ls:显示文件夹文件列表;-l显示详细信息,显示的rwx分别说明读写执行权限,从用户、用户组、其他用户三个维度标记
  • chmod:更改权限,x=1,w=2,r=4;rwx=4+2+1=7
  • touch:新建文件
  • rm:删除
  • tail -行数 文件名:查看
  • man:查看帮助,如man tail
  • wget:下载
  • unzip:解压

1.3 开发环境

  • sudo apt-get update
  • sudo apt-get install

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.