迭代语句
范围for语句
范围for语句语法形式:
1 | for(declaration : expression) |
expression表示一个序列,共同特点是要有能返回迭代器的begin和end成员。比如说花括号括起来的初始值列表、数组、vector和string等类型。
declaration定义一个变量,要求序列中每个元素能够转换成该变量类型。最简单的办法是使用auto类型说明符。如果需要对序列中的元素执行写操作,循环变量必须声明为引用类型。
范围for语句语法形式:
1 | for(declaration : expression) |
expression表示一个序列,共同特点是要有能返回迭代器的begin和end成员。比如说花括号括起来的初始值列表、数组、vector和string等类型。
declaration定义一个变量,要求序列中每个元素能够转换成该变量类型。最简单的办法是使用auto类型说明符。如果需要对序列中的元素执行写操作,循环变量必须声明为引用类型。
左值可以位于赋值语句的左侧,右值则不能。
当一个对象被用作右值时,用的是对象的值(内容);当对象用作左值时,用的是对象的身份(在内存中的位置)。
使用关键字decltype,左值和右值也有所不同,如果表达式的求值结果是左值,decltype会得到一个引用类型。假设p类型int*,因为解引用生成左值,所以decltype(*p)的结果是int&。
优先级规定运算对象的组合方式,但没有说明运算符对象的求值顺序。在大多数情况下,不会明确指定求值顺序。例如,下面的表达式中,我们可以确定f1和f2一定会在乘法前调用,因为调用符优先级高于乘法,但我们无法知道f1和f2谁先调用。
1 | int i = f1() * f2(); |
对于没有明确指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将导致未定义行为。例如:
1 | int i = 0; |
编译器可能先求++i的值,再求i的值,因此输出结果是1 1;也可能先求i的值再求++i的值,输出结果是0 1,甚至编译器还可能做完全不同的操作。在单词程序中,编译器可能按照下面的任意一种方式处理表达式,也可能采用别的方式处理它。
1 | *beg = toupper(*beg); //如果先求左侧的值 |
明确规定了求值顺序的运算符:逻辑与(&&) 逻辑或(||) 条件运算(?:) 逗号(,)运算
more >>处理复合表达式原则
1)使用括号强制让表达式的组合关系符合逻辑要求
2)如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象
规则2的例外,当改变对象的子表达式本身就是另外一个子表达式的运算对象时,该规则无效。比如常用的用法*++iter,因为递增运算必须先求值,然后才轮到解引用。
头文件不应包含using声明
因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件都会有这个声明。对于某些程序来说,由于不经意间包含了一些名字,可能产生意想不到的冲突。
如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization)
,因为编译器创建临时对象,然后会把等号右侧的初始值拷贝到新创建的对象中去。如果不使用等号,则执行的是直接初始化(direct initialization)
。
string的size函数返回的是一个string::size_type类型,它是一个无符号类型。切记:如果表达式中已经有size()函数就不要再使用int了,从而可以避免混用int和unsigned可能带来的问题。例如:假设n是一个负值的int,则表达式s.size() < n
的判断结果几乎肯定是true,因为负值n会转换成一个很大的无符号值。
在C++11新标准中,可以通过auto或decltype来判断变量类型:
1 | auto len = s.size(); //len的类型是string::size_type |
使用C++版本的C标准库头文件,C++标准兼容C语言标准库。C语言的头文件刑如name.h,C++则将文件命名为cname。在名为cname的头文件中定义的名字从属于std命名空间,而定义在名为.h的头文件中则不然。一般来说,C++程序应该使用名为cname的头文件,标准库的名字总能在std中找到。
more >>切勿混用无符号和有符号类型
如果表达式里既包含有符号类型,也包含无符号类型,当有符号类型的取值为负时会出现异常结果,因为有符号类型会自动转换成无符号类型。
变量定义基本形式:类型说明符(type specifier) + 变量名
当对象在创建时获得一个特定的值,我们就说这个对象被初始化(initialized)
了。初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
在C++11中,用花括号初始化变量的形式成为列表初始化(list initialization)
。无论是初始化对象还是某些时候为对象赋新值,都可以使用花括号括起来的初始值。当应用于内置类型时,如果使用列表初始化而且初始值存在丢失信息的风险,则编译器报错。
如果变量定义时没有指定初值,则变量被默认初始化(default initialized)
。如果是内置类型,定义于任何函数外的变量初始化为0,定义在函数体内部的变量则不被初始化(unitialized)
。未初始化的内置类型的值是未定义的。类对象如果没有显式地初始化,则其值由类确定。所以,建议初始化每一个内置类型的变量。
本文介绍虚拟内存如何工作以及应用程序如何使用和管理虚拟内存。虚拟内存由硬件异常、硬件地址翻译、主存、磁盘文件和内核交互,为每个进程提供一个大的、一致的和私有的地址空间,提供如下三个重要功能:
- 作为主存和磁盘的高速缓存,在主存中只保存活动区域
- 为每个进程提供了一致的地址空间,从而简化内存管理
- 保护每个进程的地址空间不被其他进程破坏
磁盘和主存间的传输单元为虚拟页(Virtual Page, VP)。虚拟页和物理页(页帧)为相同的固定大小的块。虚拟存储在磁盘上,物理页存储在主存中。在任意时刻,虚拟页集合分为三个不相交的子集:未分配的,缓存的和未缓存的。
术语SRAM缓存表示CPU和主存之间的L1、L2和L3高速缓存,DRAM缓存表示虚拟内存系统的缓存,在主存中缓存虚拟页。因为大的不命中处罚和访问磁盘第一个字节的开销,所以虚拟页往往很大
,通常在4KB~2MB。由于大的不命中处罚,DRAM缓存是全相联
,即任何虚拟页都可以放置在任何的物理页中。最后,因为对磁盘的访问时间长,DRAM缓存总是使用写回
,而不是直写。
git config --global user.name "your name"
git config --global user.email "email@gmai.com"
--global
表示机器上所有Git仓库都使用该配置git config --global credential.helper store
本地保存账号密码
步骤1:本地创建版本库目录
步骤2: 使用git init
将目录变成git可以管理的仓库,生成.git目录
步骤1:
git add file
把文件添加到仓库
步骤2:git commit -m "comment"
把文件提交到仓库
more >>
git status
显示仓库当前状态git diff file
显示指定文件差异git show commitId
显示某次提交的修改内容git show <commit-hashId> filename
显示某次提交某个文件的修改信息git log
显示提交日志记录git log --pretty=online --graph --color
git reflog
查看命令历史,以便确认回到未来哪个版本
从开机到关机,处理器做的工作其实很简单,就是不断读取并执行指令,每次执行一条,整个指令执行的序列,称为处理器的控制流。对于程序内部本身的控制,可以通过跳转、调用和返回等程序指令实现;然而,如果需要对系统状态变化做出反应,因为系统状态不能被程序变量捕获,有时甚至与程序的执行无关。比如说定时器产生信号,数据包到达网卡,程序向磁盘请求数据等情况发生时,需要采用另外的机制对上述的异常情形做出反应,这些突变就称为异常控制流(Exception Control Flow,ECF)。
ECF存在于系统的每个层级,最底层的机制称为异常(Exception),用以改变控制流以响应系统事件,通常是由硬件的操作系统共同实现的。更高层次的异常控制流包括进程切换(Process Context Switch)、信号(Signal)和非本地跳转(Nonlocal Jumps),也可以看做是一个从硬件过渡到操作系统,再从操作系统过渡到语言库的过程。进程切换是由硬件计时器和操作系统共同实现的,而信号则只是操作系统层面的概念了,到了非本地跳转就已经是在C运行时库中实现的了。
进程是计算机科学中最为重要的思想之一,进程是运行实例。进程给每个应用提供了两个非常关键的抽象:一是逻辑控制流,二是私有地址空间。逻辑控制流通过称为上下文切换(context switching)的内核机制让每个程序都感觉自己在独占处理器。私有地址空间则是通过称为虚拟内存(virtual memory)的机制让每个程序都感觉自己在独占内存。这样的抽象使得具体的进程不需要操心处理器和内存的相关适宜,也保证了在不同情况下运行同样的程序能得到相同的结果。对于fork关系复杂的进程,建议使用进程图
画出各个进程的关系,梳理所有调用时序的可能性。
当一个进程由于某种原因终止时,内核并不立即从系统中删除。而是进程被保持在一种已终止的状态,直到被它的父进程回收
。所以,当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()
系统调用取得子进程的终止状态。一个终止了但未被回收的进程称为“僵尸进程”。系统进程表是一项有限资源,如果系统进程表被僵尸进程耗尽的话,系统就可能无法创建新的进程。模拟产生僵尸进程示例:
链接就是将代码和数据组合成一个单一文件的过程,组成的文件可以被加载到内存并执行。链接可以执行于编译时,也可以执行于加载时,甚至执行于运行时。链接在软件开发中扮演着一个关键的角色,因为它使得分离编译成为可能,我们可以将一个大型的应用程序分解为更小更好管理的模块,并且独立的修改和编译这些模块,当我们修改这些模块中的一个时只需要重新编译它,并重新链接应用,而不必重新编译其他文件。
在将源代码编译成可执行程序时,会经历如下步骤:首先,运行预处理器将源程序翻译成一个ASCII码的中间文件;然后,运行C编译器将中间文件翻译成一个汇编文件;再运行汇编器将汇编文件翻译成可重定位目标文件;最后,运行链接程序将可重定位目标文件以及一些必要的系统目标文件组合起来,创建一个可执行目标文件。
1 | cpp sum.c /tmp/sum.i或者gcc -E sum.c -o /tmp/sum.i #预处理 |
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。
在调试多线程程序时,经常需要查看线程堆栈信息,如果线程数目过多,每次查看一个线程堆栈,繁琐耗时。下面介绍一种一次性将所有线程堆栈输出到文件的方法:
1 | # 将gdb attach到调试进程 |
直接输出所有线程堆栈信息到指定文件
1 | gdb -ex "thread apply all bt" -batch -p pid > thread_stack.info |
存储系统采用分层结构,越靠近CPU,访问速度快,容量小,越远离CPU,访问速度慢,容量大。存储器分层结构利用程序的局部性
基本属性,即程序在一段时间内,倾向于访问相同集合的数据例如循环遍历数组或者访问相邻的数据集合的特性,从而提升访问性能。所以,利用缓冲
思想,将存储系统设计为层次结构,例如高速Cache作为CPU寄存器和内存的缓冲区,内存作为高速Cache和硬盘的缓冲区,硬盘可以作为网络数据的缓冲区。
RAM分为SRAM和DRAM,SRAM主要用于作为高速Cache,DRAM用来作为主存或者图形帧缓冲区。桌面系统SRAM容量不超过几M字节,DRAM却有几百到几千M字节。从磁盘读取信息为毫秒级,比从DRAM读慢了10万倍,比从SRAM读慢了100万倍。
CPU发送完读磁盘请求后,由相应的控制器完成磁盘与内存间数据的读写处理,CPU不需要参与,可以继续执行其他的指令。当读写操作完成后,控制器通过中断通知CPU,称之为直接内存访问(DMA,Direct Memory Access)
。
时间局部性:被引用过一次的内存位置在不久的将来再次被引用
空间局部性:被引用过一次的内存位置在不久的将来引用附近的内存访问
重复引用相同变量的程序具有良好的时间局部性;步长为1的引用模式具有良好的空间局部性,随着步长的增加,空间局部性下降;对于指令来说,循环具有良好的时间和空间局部性,循环体越小,迭代次数越多,局部性越好
CPU访问寄存器需要一个时钟周期,SRAM需要几个时钟周期,DRAM需要几十到几百时钟周期。
基本原则:上层缓存下层的一个块,每次传输时使用块作为单元,为了补偿访问时间,因此,在越远离CPU的层次时块的大小越大。存储器层次结构如下图所示:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true