• 首页
  • 加入
  • RSS
  • Tuesday, May 22, 2018

    从零构建 dde-dock 的插件
    本教程将展示一个简单的 dde-dock 插件的开发过程,插件开发者可跟随此步骤为 dde-dock 创造出更多具有丰富功能的插件。

    在本教程中,将创建一个可以实时显示用户家目录(~/)使用情况的小工具。

    插件的工作原理
    dde-dock 插件本质是一个按 Qt 插件标准所开发的共享库文件(so)。通过 dde-dock 预定的规范与提供的接口,共同完成 dde-dock 的功能扩展。

    准备环境
    插件的开发环境可以是任意的,只要是符合 Qt 插件规范及 dde-dock 插件规范的共享库文件,都可以被当作 dde-dock 插件载入。下面以 Qt + qmake 为例进行说明:

    安装依赖
    以 Deepin 15.5.1 环境为基础,至少先安装如下的包:

    • dde-dock-dev
    • qt5-qmake
    • qtbase5-dev-tools
    • libqt5core5a
    • libqt5widgets5
    • pkg-config

    基本的项目结构
    创建必需的项目目录与文件
    插件名称叫做home_monitor,所以创建以下的目录结构:

    home_monitor
    ├── home_monitor.json
    ├── homemonitorplugin.cpp
    ├── homemonitorplugin.h
    └── home_monitor.pro

    Sunday, March 11, 2018

    dde-session-ui里面包含了很多项目,是一个集合,但是其中的代码缺少合理的维护,以至于已经到了必须重构才能继续开发和维护,在支持AD域登录的时候,如果强制加上功能,代码会变得更加糟糕,所以和石博文一块重构了其中非常重要的UserWidget。

    重构前的设计

    重构前的dde-lock和lightdm-deepin-greeter是非常混乱的,处理逻辑都混杂在一块,虽然能看出有基本的结构,但是整体并未解耦。

    重构后的设计

    • 基于User类的处理
    • UserWidget负责提供对用户的处理,暴露出基本的currentUser和LogindUsers。
    • Lock和Greeter的Manager从UserWidget、SessionWidget中获取用户和用户的会话。
    • Manager只负责控件的位置和用户的验证。
    • 背景修改为Manager提供模糊的壁纸,FullBackground只提供绘制。

    重构以后用了大概原代码的1/3,启动速度也快了,感觉世界充满了美好… 就是重构历程太辛苦…

    本次也发现了很多自身的问题,基础并没有学好,很多地方都可以使用更好的处理方式【就是管不住这手…】

    Friday, January 12, 2018

    在阅读本篇文章之前,你需要掌握基本的Qt/C++开发知识。

    注意:本篇文章基于Deepin平台,其他平台请自行补充依赖关系。

    先安装DTK的依赖关系。

    sudo apt install libdtkwidget2 libdtkcore2

    新建Qt项目,编辑pro文件,添加项目依赖。

    CONFIG += c++14 link_pkgconfig
    PKGCONFIG += dtkwidget

    DTK目前有两个组件,一个是提供库功能的core,一个是提供控件的widget。

    修改main.cpp,删除QApplication的相关内容,改为DApplication。

    注意: 使用DTK的组件,需要使用DTK的宏,根据使用的文件来选择对应的宏。

    DWIDGET_USE_NAMESPACE
    DCORE_USE_NAMESPACE

    DTK使用了deepin自己的qt插件,需要在DApplication前调用。

    DApplication::loadDXcbPlugin();
    DApplication app(argc, argv);

    DApplication中提供了很多方法来设置程序的各种信息,具体请看头文件的定义。

    主窗口由DMainWindow提供,新建类,然后添加DMainWindow的头文件和DTKWIDGET的宏。

    #include <DMainWindow>

    DWIDGET_USE_NAMESPACE

    然后修改继承关系,改为继承DMainWindow。DMainWindow提供了一些我们封装的方法。目前为止,该程序的界面已经符合Deepin程序的风格了,我们封装了一些其他控件,使其样式符合我们的风格,如果要在其他Qt程序中使用,也是同样的步骤,载入Qt插件,添加对应的头文件和DTK的宏。

    Wednesday, August 23, 2017

    这里记录了开发topbar中遇到的坑和一些问题。

    使用Qt提供的qxcb方法注册阴影为dock类型,反而处于DESKTOP和NORMAL之间。其实当初并不是想设置为DOCK类型的,因为这样阴影也会在窗口上方,我希望的是阴影在普通程序下方,在桌面上方。今天曹哥来讲窗管的一些坑,讲到窗管是如何控制窗口的,我的阴影其实是被Qt注册成_NET_WM_STATE_BELOW了。
    这里可以看到一些type的介绍

    在_NET_WM_STATE中一共有这么几个类型:

    _NET_WM_STATE_MODAL, ATOM
    _NET_WM_STATE_STICKY, ATOM
    _NET_WM_STATE_MAXIMIZED_VERT, ATOM
    _NET_WM_STATE_MAXIMIZED_HORZ, ATOM
    _NET_WM_STATE_SHADED, ATOM
    _NET_WM_STATE_SKIP_TASKBAR, ATOM
    _NET_WM_STATE_SKIP_PAGER, ATOM
    _NET_WM_STATE_HIDDEN, ATOM
    _NET_WM_STATE_FULLSCREEN, ATOM
    _NET_WM_STATE_ABOVE, ATOM
    _NET_WM_STATE_BELOW, ATOM
    _NET_WM_STATE_DEMANDS_ATTENTION, ATOM

    如果程序被注册成_NET_WM_STATE_BELOW,则会被放置在DESKTOP之上的一层。不是很清楚Qt是出于什么策略,才把我的阴影注册为这个状态,反而是刚好满足了我的需求。

    这要多谢曹哥了,我才终于明白了为什么会这样,以及以后如何正确的设置type。

    DESKTOP1DESKTOP2

    Wednesday, August 16, 2017

    前言: 项目一定要留一些文档!! 修bug前一定要知道所有的流程!!!

    这两天一直在修一个用户切换的bug,众所周知,deepin的多用户切换一直都不是正常工作的,确切来说是压根没有正常工作,还好用户不是经常切换,不然早就收到一大波的报告了。

    dde-session-ui项目中包含了以下软件:

    • dde-lock
    • dde-shutdown
    • dde-osd
    • lightdm-deepin-greeter
    • dde-switchtogreeter
    • dde-suspend-dialog
    • dde-warning-dialog
    • dde-welcome
    • dde-wm-chooser
    • dde-lowpower
    • dde-offline-upgrader

    大部分项目根据名字就可以知道是做什么的,这是一个软件组的集合。
    而dde-lock和lightdm-deepin-greeter二者有大量重复的功能和代码,这是它俩的工作性质决定的。

    lightdm-deepin-greeter: display-manager启动的实体,登录的界面是它负责的。

    dde-lock: 用户层面的屏幕锁定,基于我们的设计,和lightdm-deepin-greeter是大致相同的布局。

    而且都包含了用户密码的验证,用户的切换,但是二者工作的层面是不同的,为了方便切换,就有了dde-switchtogreeter,用来协调二者的工作,只需要提供用户名就可以切换。

    然而,虽然这样的想法是很好的,可是当初并没有人写文档,随着人员的变动,现在公司应该没有一个人是比较完整了解整个的工作流程了,用户切换的bug也就这样被留下来了。

    上次修复用户切换的问题,是发现登录以后lightdm-deepin-greeter没有退出,由于不是很清楚linux的登录流程,再加上代码中有不工作的退出代码,当时就改好了退出的问题,这样就引入了第二个问题,而这个问题,就导致了两天三个人在一直查找问题所在。

    这次的问题是发现一直切换greeter,会导致Xorg一直在开新的display,这就很奇怪了,正常来说是不会一直创建才对。

    最开始以为是dde-switchtogreeter的问题,毕竟切换是它在做,dde-switchtogreeter是单文件的c代码,代码没有任何的说明,真的是为切换而生,我从main函数开始自己走了好几遍的流程,一边看着d-feet的数据来验证,然而只发现了一个小问题,整个代码是没啥问题的。

    最后在后端大佬的帮助下,知道了display-manager会自己退掉greeter,不需要自己退,然后我就想起来了以前改的地方,赶紧把退出代码删掉,重新编译,问题得到了解决。

    如果我知道display-manager的工作流程,也许这个问题就不会拖两天了。

    Monday, August 14, 2017

    排查了一天,最后终于确认了流程,知道了问题所在,不得不说,dde-session-ui这个项目太需要一个文档了,要把工作流程写的非常详细才可以。

    上午收到了一条新任务,是龙芯上新安装的系统需要输入两次密码才可以登录,没有错误提示。近期并没有什么太大的改动,无非是给龙芯也用上了简单重构过的dde-session-ui,怎么会导致这样的问题。

    由于是新安装的系统才会发生,而且是现象一旦发生,就无法重现,这让我头有点大,怎么会有这样的神奇的事情,而且日志中很正常,没有收到message导致密码框被清空。我提交了一个添加了更多日志的,然后重装的龙芯的系统(龙芯重装一次要半个小时),等重装完了,切换到tty去安装这个包,然后重启lightdm,让我输入密码回车以后,密码消失,我赶紧去看日志,但是日志中并没有我的输出,回车以后肯定会有的一行输出也没有(内心OS:What the fuck is that?)

    我又回去看验证的流程,并没有发现有什么不对的地方,而且是近期才有的,我在自己电脑上使用了龙芯的编译参数,打了一个deb包,并没有发生和龙芯一样的情况(这里并不需要,既然是新安装的系统才会发生,在旧系统上是无法重现的)。

    再然后我暂时没有管这个,先去修其他bug了。忙完以后,我去问了一下其他大佬,大佬给我提了几条让我去看看,是不是起了两个lightdm-deepin-greeter进程,确认一下使用的二进制是不是你加了log的。(然后我又重装龙芯了),之后确认了是我的二进制,也没有起两个进程。但是ps中有两行输出,我以为是起了两个,就让后端大佬看了一下,后端大佬告诉我说一个是shell的进程,一个是本体,还是只有一个进程存在的。我彻底懵逼了,然后后端大佬告诉我,是不是greeter进程写入什么了,之后的验证中内容已经存在,所以就不会重现了。

    其实这个我也想过,但是没考虑太深,greeter并没有操作文件,但是大佬这么一说,我想到有一些dbus的调用,是有写入文件的,然后我把/var/lib/lightdm/lightdm-deepin-greeter目录给删除了,完美重现。

    我的天啊,排查了快一天,居然是这个目录在新装的系统上没有,所以回车登录以后收到了来自dbus的switchToUser,界面重启导致的内容消失,根本不是收到了Message才被清空的,所以我的log也没有打印出来。

    知道了如何重现,可是要怎么修复呢,似乎在greeter上并不能修复,只能去改dde-daemon中LockService,如果文件不存在,就不要发送userChanged的信号。(流程是读取这个文件的信息,和传入的参数进行对比,但是文件是空的,所以被认为不是同一个账户,就发送了信号,也导致了界面上重启,以后无法重现是因为里面已经有内容了)。

    就这样,一个流程很正确,但是表现到界面上时就成了一个bug的问题被解决了。写下这篇内容是为了记录我如何解决对我来说很棘手的问题,其实这个问题并不是很困难,但是对整个工作的流程不是很熟悉,导致浪费了大量的时间在非关键点处理,有空要写一些文档了。

    Friday, August 11, 2017

    其实这个问题影响并不是很大,只是稍微的增加一点点访问速度,缓存这东西有利有弊。

    在写完这篇文章以后,我就不用dnsmasq了,现在用的是github上的Pcap_DNSProxy。用来防止dns污染的。

    根据https://stackoverflow.com/questions/11020027/dns-caching-in-linux 中回答者提供的信息来看,linux发行版是不提供dns解析缓存的,上面提到的nscd也不在deepin的预装列表中,所以我们只能自己动手丰衣足食了。

    首先安装口碑较好的dnsmasq,来为我们提供dns缓存。

    sudo apt install dnsmasq

    如果是deepin最新的2015.4.1版本中安装,安装结束会提醒一个错误,这个错误的解决办法来自https://stackoverflow.com/questions/16939306/i-cant-restart-my-dnsmasq-service-so-my-fog-server-wont-work.
    这个错误似乎是因为/usr/share/dns/root.ds文件更新后结构导致的错误。

    编辑/etc/init.d/dnsmasq,并找到

    ROOT_DS="/usr/share/dns/root.ds"

    if [ -f $ROOT_DS ]; then
    DNSMASQ_OPTS="$DNSMASQ_OPTS `sed -e s/". IN DS "/--trust-anchor=.,/ -e s/" "/,/g $ROOT_DS | tr '\n' ' '`"
    fi

    修改为

    ROOT_DS="/usr/share/dns/root.ds"

    if [ -f $ROOT_DS ]; then
    DNSMASQ_OPTS="$DNSMASQ_OPTS `sed -e s/".*IN[[:space:]]DS[[:space:]]"/--trust-anchor=.,/ -e s/"[[:space:]]"/,/g $ROOT_DS | tr '\n' ' '`"
    fi

    当错误解决以后,我们手动重启一下dnsmasq的systemd服务。

    sudo systemctl restart dnsmasq

    deepin的/etc/resolv.conf来自/etc/NetworkManager/resolv.conf.是一个软连接。我采取的行为是删除这个文件,重新创建。

    sudo rm /etc/resolv.conf

    sudo vim /etc/resolv.conf

    然后写入本地地址当做dns地址。

    nameserver 127.0.0.1

    dnsmasq是一个本地的dns和dhcp服务器,当我们在上面成功启动dnsmasq以后,个人系统中就已经在提供dns服务了,所以本机使用回环地址来表明dns服务器就是本机,所有的dns查询都会发送到本机的dnsmasq中。

    如果需要额外添加dns服务器,做法来自https://wiki.archlinux.org/index.php/Dnsmasq#More_than_three_nameservers.

    创建一个 /etc/resolv.dnsmasq.conf,写入其他dns服务器的地址。

    nameserver 114.114.114.114

    然后编辑 /etc/dnsmasq.conf,找到resolv-file字段,改为

    resolv-file=/etc/resolv.dnsmasq.conf

    然后重启dnsmasq。

    验证的话通过dig命令。

    dig blog.mkacg.com

    通过执行两次来判断,Query time在第二次查询是为0 msec。

    Thursday, July 20, 2017

    自己搭了一个仓库,提供deepin-topbar及相关依赖的包。

    I created a repository,provide deepin-topbar and dependencies.

    也许需要安装dirmngr

    maybe you need install dirmngr

    追加内容到/etc/apt/sources.list

    Append content to /etc/apt/sources.list

    deb [arch=amd64] https://packages.mkacg.com panda main 

    导入key

    import key

    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3BBF73EE77F2FB2A

    刷新列表,进行安装

    then, refresh list and install

    sudo apt update && sudo apt install deepin-topbar 

    Wednesday, May 4, 2016

    • “五仁”官方名称:zScript
    • 变量使用关键字"var"进行声明,变量本身无类型
    • 变量声明可放在源码中任何位置,可在任何位置被调用,例如,var a在源码中最后一行,也可以在第一行使用变量a
    • 每条语句的结尾必须是';‘或换行符(后面准备改成强制使用’;‘结尾)
    • 目前支持的数据类型:int、double、bool、string、list、tuple、object 、undefined
    • 函数也是object类型
    • string的定义为 '’ 或 "" 中所包裹的内容,支持C和JavaScript风格的字符转义
    • 支持的运算符: =、+、-、、/、%、&、|、~、^、&&、||、+=、-=、=、/=、%=、&=、|=、~=、^=、&&=、||=、!、>、<、=、==、===、!=、!==、++、–
    • 支持匿名函数,函数闭包,多值返回
    • 支持的分支结构:if else
    • 支持的循环结构:goto while for(后面准备加入do while、foreach)
    • while for 循环结构中使用break跳出循环,continue立马开始执行下一次循环,break和continue可组合使用 例如:break, break 则可以直接跳出两层循环,组合使用时continue只能放在末尾
    • 支持switch case语句,可枚举int bool string undefined

    Flex(Lexical Analyzar)

    • Lex是一个产生词法分析器的工具(最早是Eric Emerson Schmidt和Mike Lesk制作)是许多UNIX系统的标准词法分析器(lexical analyzer)产生程序,而且这个工具所作的行为被详列为POSIX标准的一部分。而Flex就是由Vern Paxon实现的Lex
    • FLex读进一个代表词法分析器规则的输入字符串流,然后输出C/C++语言的词法分析器源代码。
    • wiki:https://en.wikipedia.org/wiki/Flex_%28lexical_analyser_generator%29

    Flex的文件结构

    • 文件分成三个区块,均以一个只有两个百分比符号(%%)的单行来分隔,如下:

    定义区域 %% 规则区域 %% C/C++代码区

    • 定义区块是用来定义宏以及导入C写成的头文件所在区块。在这里面也可以写一些C代码,这一些代码会被复制到产生出来的C源代码的开头部分。
    • 规则区块是最重要的区块;这里将样式与C的陈述(statement)串连在一起。这一些样式都是正则表达式。当lexer看到输入里面有合乎给定的样式时,则会操作相对应的C代码。这就是flex运作的基础。
    • C代码区块的内容会原封不动的照搬到产生出来的C源代码里面。

    小例子

    %{
     #include 
    %}
    number [0-9]+
    %option noyywrap
    %% // 第一部分结束
    {number} {
        printf("number: %s\n", yytext);
    }
    %% // 第二部分结束
    int main()
    {
        yylex();
        return 0;
    }
    
    • %option noyywrap用于指明未定义yywrap函数,此函数用于在文件(或输入)的末尾调用。如果函数的返回值是1,就停止解析。否则等待输入后继续解析 注:正则表达式是使用单个字符串来描述、匹配一系列字符串的规则

    wiki:https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F

    Bison

    Bison的文件结构

    • 和Flex一样,也分为三个部分
    • 分别是声明、语法规则和代码段,使用%%分开

    定义区域 %% 规则区域 %% C/C++代码区

    • 第一部分和Flex一样,用于存放终结符的声明、符号优先级和符号类型等定义
    • 第二部分用来定义文法规则
    • 第三部分也是用来存放C/C++代码

    来个小例子

    %{
    #include 
    #define YYSTYPE int
    
    void yyerror(char const *);
    int yylex(void);
    %}
    
    %token NUMBER
    
    %debug
    
    %%
    
    start:  { printf("yylval = %d\n", yylval);}
        | NUMBER {
            printf("NUMBER %d\n", yylval);
        }
        start NUMBER {
            printf("start NUMBER %d\n", yylval);
        }
        ;
    
    %%
    
    void yyerror(char const *msg)
    {
        printf("error: %s\n", msg);
    }
    
    int yylex()
    {
        printf("&gt;&gt;&gt;");
    
        char str[10] = {};
    
    if(scanf("%s\n", str) == EOF)
        return 0;
    
    int number = atoi(str);
    
    printf("%d\n", number);
    
    yylval = number;
    
    return NUMBER;
    }
    
    int main()
    {
    return yyparse();
    }
    
    • %token用于定义一个终结符
    • %debug指定bison在生成的代码中加入debug输出(和bison的-t参数一样)
    • yyerror在分析过程中有语法错误时被调用
    • yylex用于给分析器返回token(在生成的代码中自动被调用) 注:终结符是指文法规则中不能再被分解的最小单位,非终结符则是相反的概念。

    例如上面的文法规则中“start”就是非终结符,NUMBER就是一个终结符。

    wiki:https://zh.wikipedia.org/wiki/%E7%B5%82%E7%B5%90%E7%AC%A6%E8%88%87%E9%9D%9E%E7%BB%88%E7%B5%90%E7%AC%A6#

    语法介绍

    变量的定义

    变量使用关键字“var”进行定义。

    zScript提供了两种形式的变量定义语句。

    1.直接定义的形式,该语句的一般形式为:

    var 标识符;
    

    该语句的含义是:定义变量名为“标识符”的变量。例如:

    var a;
    

    该语句的作用是:定义一个名为“a”的变量。 2.定义同时给变量初始化赋值,该语句的一般形式为:

    var 标识符 = 表达式;
    

    该语句的含义是:定义变量名为“标识符”的变量,且给变量赋值为表达式的结果。例如:

    var a = 1 + 2;
    

    该语句的作用是:定义一个名为“a”的变量,且赋值为3。 “var”关键字可同时定义多个变量,每个变量之间使用符号“,”分割。

    匿名函数的定义

    匿名函数定义的一般形式为:

    (形参列表) {
        语句组
    }
    

    该语句的含义是:生成一个函数对象,例如:

    (a) {
        console.log(a);
    }
    

    该语句的作用是:定义一个函数,它有一个名为“a”的形参,执行此函数会将a的值打印到屏幕上。 形参列表,可为空、一个、多个,多个形参之间使用“,”分割。

    数组的定义

    数组定义的一般形式为:

    var a = [表达式1, 表达式2, ...];
    

    该语句的含义是:生成一个数组,数组元素为中括号中表达式的结果。例如:

    var a = [1, 2, 3];
    

    该语句的作用是:定义一个包含三个元素的数组,元素的值分别为1、2、3,然后赋值给变量a; 数组使用“[”“]”定义,中间内容可以为空、一个表达式、多个表达式,多个表达式之间使用“,”隔开。

    a = a[0];
    

    取数组元素语法和C语言一样,都是中括号中写入数组下标。

    选择控制语句

    一个选择结构,包括一组或若干组操作,每组操作称为一个分支。通过选择控制语句可以实现选择结构。选择控制语句包括if语句、switch语句及起辅助控制作用的break语句。

    If语句用于计算给定的表达式,根据表达式的值是否为假,决定是否执行某一组操作。

    Switch语句首先求解一个表达式,然后根据计算结果的值,从哈希表中查询该从哪一组操作开始执行。

    Break语句用于switch结构中,用于终止当前switch结构的执行。

    if语句

    zScript提供了两种形式的if语句。

    1.单if子句的if语句。该if语句的一般形式为:

    if(表达式)
    {
        语句组
    }
    

    该语句的含义是:只有表达式的值为非零值时,才执行其内部的语句组。例如:

    if ( a &gt; b )
    {
        console.log(“hello”);
    }
    

    该语句的作用是:当a的值大于b的值时(此时,“a>b”的值为真,为非假值),在屏幕上显示“hello”;否则,不显示“hello”。 2.带else子句的if语句。该if语句的一般形式为:

    if ( 表达式 )
    {
        语句组1
    } else {
        语句组2
    }
    

    该语句的含义是:当表达式的值为非假时,执行语句组1,而不执行语句组2;否则,即表达式的值为假时,执行语句组2,而不执行语句组1。例如:

    if ( a &gt; b )
    {
        console.log(“hello1”);
    } else {
        console.log(“hello2”);
    }
    

    该语句的作用是:若a的值大于b的值(此时“a>b”为真,为非假值),则在屏幕上显示“hello1”,而不显示“hello2”;否则,即表达式的值为假时,显示“hello2”,而不显示“hello1”。

    switch语句

    Switch语句与if语句一样,也可以实现分支选择。但if语句是判断一个表达式的值是否为假,决定是否执行某个分支;而switch语句是计算一个表达式的值,根据计算结果,从哈希表查询从哪个分支开始执行代码。Switch语句的一般形式为:

    switch( 表达式 )
    {
        case 常量1:
        语句组1
        case 常量2:
        语句组2
        ...
        case 常量n:
        语句组n
        default:
        语句组 n + 1
    }
    

    switch语句的执行过程:

    • 1.求解“表达式”的值;

    • 2.如果“表达式”的值与某个case后面的“常量”的值相同,则从这里开始顺序执行语句。结果switch执行有两种形式:一是遇到break语句为止;二是未遇到break语句,则程序依次执行完所有语句组。

    • 3.如果“表达式”的值与任何一个case后面的“常量”的值都不相同,当有default子句时,则执行default后面的语句,如果没有default子句,则结束switch。

    其中break的一般形式为

    break;
    

    循环控制语句

    while语句

    while语句的一般形式为

    while( 表达式 )
    {
        循环体
    }
    

    while语句的执行过程:

    • 1.求解小括号中表达式的值。如果表达式的值为真,转第2步;否则转第3步。

    • 2.执行循环体,然后转第1步。

    • 3.执行while语句后面的语句。

    小括号中表达式的值是否为假,决定着循环体是终止还是继续循环。因此,该表达式的值为循环条件。while循环语句的执行特点是,先判断循环条件是否成立,然后决定是否执行循环体。

    当while语句的循环体只包含一条语句时,包含该循环体的“{}”可以省略。

    for语句

    在两种循环语句中,for语句最为灵活。for语句的一般形式为:

    for (表达式1; 表达式2; 表达式3)
    {
        循环体
    }
    

    for语句的执行过程:

    • 1.求解表达式1的值。

    • 2.求解表达式2的值,若其值为假,则结束循环,转第4步;若其值为真,则执行循环体。

    • 3.求解表达式3。

    • 4.结束循环。

    对象的定义

    对象定义的一般形式为:

    {
        属性名1: 属性的值,
        属性名2: 属性的值,
        ...
        属性名n: 属性的值,
        属性名n+1: 属性的值
    }
    

    例如:

    var object = {
        name: "张三",
        age: 18
    };
    

    变量object即是一个对象,它包含两个属性。 注:当对一个对象不存在的属性赋值时会将此属性加入到对象中,例如:

    object.sex = '男';
    

    对象object中就会多出一个“sex”属性。

    推荐

    Friday, March 25, 2016

    这是我用markdown写的第一篇文章(水文),先来个自我介绍吧,我是小竹,对没错,是小竹,不是竹子,不是竹酱,更不是竹基。

    我玩linux应该有五六年了吧,初二的时候接触的,不过很多年都保持在换各种发行版上,并没有真正意义的玩。上了大学以后,接触的更多了,玩的也更嗨了。
    现在也用上arch+btrfs+uefi了,各种叼炸天。
    irc里面也经常学习【看别人装逼。
    依旧是英语渣渣,数学渣渣,看到win32api,我直接放弃win编程了,我的智商也就玩玩wpf了。
    下面就贴几张我的日常截图。

    My Computer info

    kirigayaloveyousei@linuserOS: Arch LinuxKernel: x86_64 Linux 4.4.5-1-ARCHackages: 1092Shell: zsh 5.2Resolution: 1366x768WM: Mutter(DeepinGala)WM Theme: AdwaitaGTK Theme: Arc-OSX [GTK2/3]Icon Theme: deepinFont: Noto Sans CJK SC Regular 10CPU: Intel Core i5-4210U CPU @ 2.7GHzRAM: 1980MiB / 3861MiB
    pic1pic1pic1pic1pic1pic1