分类目录计算机语言

Javascript中的函数表达式

  • 函数声明 有提升 而函数表达式没有,因此不应该通过if…else来声明函数
     
     
        if (condition) {
            function sayHi() {}
        } else {
            function sayHi() {}
        }
       
  • 在递归函数中使用arguments.callee可以避免因函数名改变产生的错误。
     
     
        function factorial(num) {
            if (num 
                return 1;
            } else {
                return num * arguments.callee(num - 1);
            }
        }
       
  • 闭包是指函数中定义的函数,闭包的作用域链中包含父函数的执行环境(变量对象),所以即使父函数退出,如果闭包仍然被引用,那么父函数的变量对象就一直存在于内存中。所以,过度的使用闭包会导致内存占用过多
  • 闭包的另外一个副作用是闭包只能取得父函数中变量的最后一个值,例如:
     
     
        function createFunctions() {
            var result = new Array();
            for(var i = 0; i < 10; i++) {
                result[i] = function() {
                    return i;
                }
            }
            return result;
        }
       

    调用result中的每个函数都会返回10

  • 闭包如果是匿名函数,则该函数中的this指向全局对象
  • 可以用定义一个立即执行函数来模仿块级作用域:
     
     
            (function(){
            //块级作用域                                   
            })();                                        
       

Javascript中的对象

属性的类型

1.1 数据类属性

数据类属性的属性——特性为:

  • [[Configurable]]: 默认为true。表示能否通过delete删除属性,能否修改属性的特性。
  • [[Enumerable]]: 默认为true。表示在用for-in迭代对象时,能否迭代出该属性。
  • [[Writable]]: 默认为true。表示能否修改属性的值。
  • [[Value]]: 默认为undefined。保存属性的值。

1.2 访问器类属性

访问器类属性的特性为:

  • [[Configurable]]: 默认为true。表示能否通过delete删除属性,能否修改属性的特性。
  • [[Enumerable]]: 默认为true。表示在用for-in迭代对象时,能否迭代出该属性。
  • [[Get]]: 默认为undefined。读取属性时调用的函数。
  • [[Set]]: 默认为undefined。写入属性时调用的函数。

使用访问器类型的属性的常用方法是写入该属性时往往会导致其他属性也跟着变化。

 

1.3 对特性的操作方法:

Object.defineProperty(Object, propertyName, {attributes}): 设置属性的特性。
Object.defineProperties(Object, {propertyName: {attributes},}): 设置多个属性的特性。
Object.getOwnPropertyDescriptor(Object, propertyName);

 

创建对象的方法

2.1 工厂模式

 
 
function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}
var sen = createPerson('sen', 19, 'programer');

缺点:无法识别一个对象属于哪一个类

 
 
alert(sen instanceof createPerson) // false

2.2 构造函数模式

 
 
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    }
}
var sen = new Person("sen", 19, "programer");
alert(sen instanceof Person) // true
alert(sen.constructor === Person) // true

缺点:每个对象都会有独立的函数定义

 
 
var sen1 = new Person("sen1", 19, "player");
alert(sen1.sayName == sen.sayName) // false

2.3 原型模式

 
 
function Person() {
}
Person.prototype.name = "sen";
Person.prototype.age = "19";
Person.prototype.job = "programer";
Person.prototype.sayName = fucntion() {
    alert(this.name);
}
var sen = new Person();
Person.prototype.constructor === Person // true
Person.prototype.isPrototypeOf(sen) // true
Object.getPrototypeOf(sen) === Person.prototype // true
sen.hasOwnProperty("name") // false
"name" in sen // true

判断属性是不是在原型对象中:

 
 
!sen.hasOwnProperty("name") && ("name" in sen)

原型模式更简洁的写法:

 
 
function Person(){};
Person.prototype = {
    constructor = Person,
    name: "sen",
    age: 19,
    job: "programer",
    sayName: function() {
        alert(this.name);
    }
}
Object.defineProperty(Person.prototype, {
    enumerable: false,
    value: Person
});

缺点:

1. 省略了传递初始化参数
2. 如果原型中的属性为引用类型,则所有对象会共用这个引用类型

2.4 组合使用构造模式和原型模式

 
 
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
}
Person.prototype.sayName = function() {
    alert(this.name)
}

缺点: 构造函数和原型是独立的

2.5 动态原型模式

 
 
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    if (typeof this.sayName !== "function") {
        Person.prototype.sayName = function() {
            alert(this.name);
        };
    }
}

2.6 寄生构造函数模式

 
 
function Person(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}
var sen = new Person('sen', 19, 'programer');

寄生构造函数模式一般用于创建特殊的引用类型。例如,创建一个包含toPipedString方法的数组类型。

 
 
function SpecialArray() {
    var arr = new Array();
    arr.push.apply(arr, arguments);
    arr.toPipedString = function() {
        return this.join(" | ");
    }
    return arr;
}
var colors = new SpecialArray("red", "black");
alert(colors.toPipedString());

缺点和工厂模式一样,都无法被instanceof识别

2.7 稳妥构造函数模式

与寄生构造函数类似,区别在于:

  • 不使用this给类添加公共属性
  • 创建对象时不使用new关键字
 
 
  1. function Person(name, age, job) {
  2. var o = new Object();
  3. o.sayName = function() {
  4. alert(name);
  5. }
  6. return o;
  7. }
  8. var sen = Person('sen', 19, 'programer');

缺点和工厂模式一样,都无法被instanceof识别

继承

3.1 原型链

 
 
function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.property;
}
function SubType() {
    this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
    return this.subProperty;
}
var obj = new SubType();
alert(obj instanceof SubType) // true
alert(obj instanceof SuperType) // true
alert(obj instanceof Object) // true

缺点:

  • SuperType中的属性如果为引用类型,所以的子类都会共用该属性
  • 创建子类时,如果给父类传递参数,会影响其他的子类

3.2 构造函数继承继承

 
 
function SuperType() {
    this.colors = ['black', 'red'];
}
function SubType() {
    SuperType.call(this);
}

缺点:函数无法复用

3.3 组合继承

 
 
function SuperType(name) {
    this.name = name;
    this.colors = ['black', 'red'];
}
SuperType.prototype.sayName = function() {
    return this.name;
}
function SubType(name) {
    SuperType.call(this, name);
}
SubType.prototype = new SuperType();

3.4 原型式继承

 
 
var person = {
    name: "sen"
}
var newPerson = Object.create(person, {name: {
    value: "yang"
}});

缺点: 原型式继承仅仅是对父类做了一个浅拷贝,并加入了子类自己的属性。所以问题和原型链相同,父类中的引用类型属性会被所有子类共用。

3.5 寄生式继承

 
 
function createAnother(obj) {
    var clone = Object.create(obj);
        clone.sayHi = function() {
        alert('hi');
    }
    return clone;
}

缺点: 函数不能复用

3.6 寄生组合式继承 (常用)

 
 
function inheritPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'black'];
}
SuperType.prototype.sayName = function() {
    alert(this.name);
}
function SubType(name) {
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);

Javascript中的单体内置对象

Global

  1. encodeURI() encodeURIComponent(): 用utf-8编码替换URI中的无效字符,区别在于encodeURI并不对属于URI的特殊字符进行编码,如:// #?等
  2. eval(“var msg=’hello world'”); alert(msg);
  3. Global对象的属性:
    • undefined
    • NaN
    • Infinity
    • Object
    • Array
    • Function
    • Boolean
    • String
    • Number
    • Date
    • RegExp
    • Error
    • EvalError
    • RangeError
    • ReferenceError
    • SyntaxError
    • TypeError
    • URIError
  4. 可通过window对象来访问global对象的变量和函数
  5. var global = function(){return this;}();

Math

  • Math对象的属性:
    • Math.E
    • Math.LN10
    • Math.LN2
    • Math.LOG2E
    • Math.LOG10E
    • Math.PI
    • Math.SQRT1_2
    • Math.SQRT2
  • Math.min([1, 2, 3]) Math.max([1, 2, 3])
  • Math.ceil()
  • Math.floor()
  • Math.round()
  • Math.random():  [0-1)
  • Math.abs()
  • Math.exp()
  • Math.log()
  • Math.pow(num, power)
  • Math.sqrt()
  • Math.acos(x)
  • Math.asin(x)
  • Math.atan(x)
  • Math.atan2(y, x)
  • Math.cos(x)
  • Math.sin(x)
  • Math.tan(x)

Javascript中的基本包装类型

当读取一个基本类型(Boolean、String、Number)的值时,后台对创建一个对应的基本包装类型,从而让我们可以调用一些方法来操作这些数据。

基本包装类型与引用类型的区别:

  • 生存期。自动创建的包装类型只存在于一行代码存在的瞬间。不能在运行时为基本类型添加属性和方法。

Number类型常用的方法

  • toString()
  • toFixed(2) //小数点后两位
  • toExponential()
  • toPrecision(3)

String类型常用的属性和方法

  • length: 返回字符串的长度
  • charAt(2) charCodeAt(2): 返回字符串中第三个字符/编码
  • concat(“xxx”) 或者 + “xxx”: 字符串拼接
  • slice(start, end) subString(start, end) subStr(start, number): 字符串切片
    当start为负值时,slice和subStr会把它与字符串长度相加得到新的start;而subString则会将其转化为0
    当end/number为负值时,slice会把它与字符串长度相加得到新的end,subStr和subString会将其转化为0
  • indexOf(‘d’, start) 和lastIndexOf(‘d’, start): 从start位置开始向左(右)查找’d’在字符串中的位置
  • trim(): 返回删除字符串左右的空格的新字符串
  • trimLeft() trimRight(): 返回删除字符串左(右)的空格的新字符串
  • toLowerCase() toLocaleLowerCase() toUpperCase() toLocaleUpperCase()
  • match(pattern/RegExp): 返回的数组与RegExp.exec相同
  • search(pattern/RegExp): 返回匹配到的位置
  • replace(old, new):
    如果old为字符串时,仅替换该字符串;如果Old是带’g’标志的pattern时,会替换所有匹配的字符串
    new字符串中可以是下列特殊序列:
    $$: $
    $&: 等价于RegExp.lastMatch
    $’: RegExp.leftContext
    $`: RegExp.rightContext
    $n $nn: 第n个捕获组匹配到的字符串
    new可以是一个函数:function(match, pos, orignialText){return string}
  • var array = “string”.split(‘string/pattern’, number)

Javascript中的函数

函数的定义

  1.  var foo = fucntion(a, b) {}; //函数表达式
  2. function foo(a, b) {}; //函数声明
  3. var foo = new Function(“a”, “b”, “return false”); //不推荐

对于函数声明,可以在声明之前调用该函数;而对于函数表达式,不可以在表达式之前调用该函数。

函数的属性

  • this: 引用的是函数执行的环境对象,全局作用域中的this引用的是window对象
  • arguments: 保存函数参数的数组
  • arguments.callee: 指向该函数的指针
  • caller: 函数的调用者
  • length: 函数希望接收到的命名参数的个数

函数的方法

  • call(obj, arg1, arg2…)  //设置函数的运行环境(改变this的值)
  • apply(obj, [arg1, arg2]) //设置函数的运行环境(改变this的值)
  • bind(obj) //绑定函数的运行环境

Javascript中的正则表达式

1. RegExp类型的创建

var expression = /pattern/flags; 或者

var expression = new RegExp(“pattern”, “flags”);

用创建对象的方式来创建RegExp是需要对”pattern”中的\进行转义,例如:

/\[bc\]at/  => “\\[bc\\]at”

flags 可以是:

  • g :全局匹配
  • i :忽略大小写
  • m :多行模式

2. RegExp实例的属性

  • global: true or false, 表示标志位’g’是否被设置
  • ignoreCase: true or false
  • multiline: true or false
  • lastIndex: 下一次搜索开始的字符位置
  • source: pattern的字符串表示

3. RegExp实例的方法

  • exec(“target_string”)
    • ret.index: 匹配到的字符串的起始位置
    • ret.input: “target_string”
    • ret[0]: 匹配到的字符串
    • ret[1 – n]: 匹配组1-n匹配到的字符串
  • test(“target_string”)
    • true: 匹配到字符串
    • false: 没有匹配到字符串
  • exec和test只有在设置了’g’标志位的情况下才回去更新exp.lastIndex

4. RegExp 构造函数属性

  • input ($_): 最近一次匹配成功的源字符串
  • lastMatch($&): 最近一次的匹配项
  • lastParen($+): 最近一次匹配的捕获组
  • leftContext($`): input字符串中匹配项之前的字符串
  • rightContext($*): input字符串中匹配项之后的字符串
  • $1-9: 第1-9个匹配组

附录:

正则表达式元字符表

Metacharacter Description
^ Matches the starting position within the string. In line-based tools, it matches the starting position of any line.
. Matches any single character (many applications exclude newlines, and exactly which characters are considered newlines is flavor-, character-encoding-, and platform-specific, but it is safe to assume that the line feed character is included). Within POSIX bracket expressions, the dot character matches a literal dot. For example, a.c matches “abc”, etc., but [a.c] matches only “a”, “.”, or “c”.
[ ] A bracket expression. Matches a single character that is contained within the brackets. For example, [abc] matches “a”, “b”, or “c”. [a-z] specifies a range which matches any lowercase letter from “a” to “z”. These forms can be mixed: [abcx-z] matches “a”, “b”, “c”, “x”, “y”, or “z”, as does [a-cx-z].The - character is treated as a literal character if it is the last or the first (after the ^, if present) character within the brackets: [abc-], [-abc]. Note that backslash escapes are not allowed. The ] character can be included in a bracket expression if it is the first (after the ^) character: []abc].
[^ ] Matches a single character that is not contained within the brackets. For example, [^abc] matches any character other than “a”, “b”, or “c”. [^a-z] matches any single character that is not a lowercase letter from “a” to “z”. Likewise, literal characters and ranges can be mixed.
$ Matches the ending position of the string or the position just before a string-ending newline. In line-based tools, it matches the ending position of any line.
( ) Defines a marked subexpression. The string matched within the parentheses can be recalled later (see the next entry, \n). A marked subexpression is also called a block or capturing group. BRE mode requires \( \).
\n Matches what the nth marked subexpression matched, where n is a digit from 1 to 9. This construct is vaguely defined in the POSIX.2 standard. Some tools allow referencing more than nine capturing groups.
* Matches the preceding element zero or more times. For example, ab*c matches “ac”, “abc”, “abbbc”, etc. [xyz]* matches “”, “x”, “y”, “z”, “zx”, “zyx”, “xyzzy”, and so on. (ab)* matches “”, “ab”, “abab”, “ababab”, and so on.
{m,n} Matches the preceding element at least m and not more than n times. For example, a{3,5} matches only “aaa”, “aaaa”, and “aaaaa”. This is not found in a few older instances of regexes. BRE mode requires \{m,n\}.

 

Metacharacter Description
? Matches the preceding element zero or one time. For example, ab?c matches only “ac” or “abc”.
+ Matches the preceding element one or more times. For example, ab+c matches “abc”, “abbc”, “abbbc”, and so on, but not “ac”.
| The choice (also known as alternation or set union) operator matches either the expression before or the expression after the operator. For example, abc|def matches “abc” or “def”.

 

POSIX Non-standard Perl/Tcl Vim Java ASCII Description
[:ascii:][30] \p{ASCII} [\x00-\x7F] ASCII characters
[:alnum:] \p{Alnum} [A-Za-z0-9] Alphanumeric characters
[:word:][citation needed] \w \w \w [A-Za-z0-9_] Alphanumeric characters plus “_”
\W \W \W [^A-Za-z0-9_] Non-word characters
[:alpha:] \a \p{Alpha} [A-Za-z] Alphabetic characters
[:blank:] \s \p{Blank} [ [[\t]]] Space and tab
\b \< \> \b (?<=\W)(?=\w)|(?<=\w)(?=\W) Word boundaries
\B (?<=\W)(?=\W)|(?<=\w)(?=\w) Non-word boundaries
[:cntrl:] \p{Cntrl} [\x00-\x1F\x7F] Control characters
[:digit:] \d \d \p{Digit} or \d [0-9] Digits
\D \D \D [^0-9] Non-digits
[:graph:] \p{Graph} [\x21-\x7E] Visible characters
[:lower:] \l \p{Lower} [a-z] Lowercase letters
[:print:] \p \p{Print} [\x20-\x7E] Visible characters and the space character
[:punct:] \p{Punct} [][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] Punctuation characters
[:space:] \s \_s \p{Space} or \s [ \t\r\n\v\f] Whitespace characters
\S \S \S [^ \t\r\n\v\f] Non-whitespace characters
[:upper:] \u \p{Upper} [A-Z] Uppercase letters
[:xdigit:] \x \p{XDigit} [A-Fa-f0-9] Hexadecimal digits

 

常用分组语法
分类 代码/语法 说明
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

 

常用的反义代码
代码/语法 说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

 

 

 

Javascript中的Date

1. Date类型的创建

  • var date = new Date() //获取当前时间
  • var date = new Date(ms) //根据Unix时间戳来创建Date对象
  • var date = new Date(Date.parse(“string”)) //string为日期时间组成的字符串,符合要求的字符串格式包括:
    • “m/d/y”
    • “January 12, 2004”
    • “Tue May 25 2004 00:00:00 GMT-0700”
    • YYYY-MM-DDTHH:mm:ss.sssZ”
  • var date = new Date(Date.UTC()) // Date.UTC的参数为年、月、日、小时、分钟、秒、毫秒

2. Date类型的操作

var date = new Date();

  • Date.now() 或者+new Date()获取当前的时间戳, “+”操作符可获取Date对象的时间戳
  • date.toDateString(); // “Thu May 17 2018”
  • date.toTimeString(); // “14:37:29 GMT+0800 (CST)”
  • date.toLocaleDateString(); // “2018/5/17”
  • date.toLocaleTimeString(); // “下午2:37:29”
  • date.toUTCString(); // “Thu, 17 May 2018 06:37:29 GMT”
  • date.toLocaleString(); // “2018/5/17 下午2:37:29”
  • date.getTime(); // 1526539049298
  • +date; // 1526539049298
  • date.setTime(1526539049300);
  • date.getFullYear(); // 2018
  • date.getUTCFullYear(); // 2018
  • date.getMonth(); // 0-11
  • date.getUTCMonth(); // 0-11
  • date.getDate(); //1-31
  • date.getUTCDate(); // 1-31
  • date.getDay(); // 0-6 0表示周日
  • date.getUTCDay(); // 0-6 0表示周日
  • date.getHours(); // 0-23
  • date.getUTCHours(); // 0-23
  • date.getMinutes(); // 0-59
  • date.getUTCMinutes(); // 0-59
  • date.getSeconds(); // 0-59
  • date.getUTCSeconds(); // 0-59
  • date.getMilliseconds(); // 0-999
  • date.getUTCMilliseconds(); // 0-999
  • date.getTimezoneOffset(); // -480

Javascript中的数组

  • 数组的定义:
    • var colors = new Array(20);
    • var colors = new Array(‘red’);  // [‘red’]
    • var colors = [‘red’, ‘green’];
  • 判断变量是不是数组:
    • colors instanceof Array;  //true
    • Array.isArray(colors); //true
  • 将数组转化为字符串:
    • colors.toString(); // ‘red,green’
    • colors.join(‘ ‘); //’red green’
    • colors = [ “red”, undefined, “green”, null, “yellow” ];
      • colors.toString(); //"red,,green,,yellow"
        
        colors.join(' '); //"red  green  yellow"
        
        colors.join(';'); //"red;;green;;yellow"
  • 栈方法
    • colors.push(‘yellow’); //[‘red’, ‘green’, ‘yellow’]
    • colors.pop(); //[‘red’, ‘green’]
  • 队列方法
    • colors.push(‘yellow’); //[‘red’, ‘green’, ‘yellow’]
    • colors.shift(); //[‘green’, ‘yellow’]
    • colors.unshift(‘black’); //[‘black’, ‘red’, ‘green’]
  • 重新排序
    • colors.reverse(); //[‘green’, ‘red’]
    • colors.sort();
      • 字符串升序排列
        • var nums = [1, 5, 10]; //nums.sort();//[1, 10, 5]
      • colors.sort(function(a,b){return a – b;});升序排列
      • colors.sort(function(a,b){return b – a;});降序排列
  • 操作
    • var ret = colors.concat(‘black’); // ret = [‘red’, ‘green’, ‘black’]
    • var ret = colors.slice(0, 1); //[‘red’] 切片
    • colors.splice(pos, length, item, item…); 在pos处删除length项,然后插入item…
  • 位置
    • indexOf(‘red’); //0
    • lastIndexOf(‘red’); //0
  • 迭代
    • colors.every(function(item, index, array){}); 所有元素返回true则返回true
    • colors.filter(function(item, index, array){}); 返回为true的元素组成的数组
    • colors.forEach(function(item, index, array){}); 无返回值
    • colors.map(function(item, index, array){}); 返回函数调用结果组成的数组
    • colors.some(function(item, index, array){}); 如果有一项返回true则返回true
  • 归并方法
    • var sum = values.reduce(function(prev, cur, index, array){return prev + cur}, 0);
    • reduceRight(); 从后往前循环

Javascript中的基本数据类型

Undefined

  • 在var或者let中声明了变量但没有赋值时,这个变量的值就是undefined.
  • 使用typeof关键字检测未声明变量的类型为undefined.

Null

  • null表示一个空对象指针,所以用typeof检测null时,会返回object
  • undefine派生自null, null == undefined 为true, null === undefined为false

Boolean

  • true false 区分大小写
  • 空字符串、0和NaN、null、undefined转换为boolean的值为false

Number

  • Number表示整数和浮点数
  • 八进制数以0开头,十六进制数以0x开头
  • Number.MIN_VALUE 表示Javascript支持的正的最小数值,Number.MAX_VALUE表示Javascript支持的最大数值
  • 超出最大数值就会被转化为Infinity,如果为负值则会被转化为-Infinity
  • isFinite()函数可以判断一个数值是否在支持的范围之内
  • NaN表示本来该返回数值的操作数未返回数值的情况,如除以0就会返回NaN
  • NaN的数值运算会返回NaN
  • NaN == NaN 为false
  • isNaN()函数可以判断一个数值是不是NaN
  • Number()函数可以将其他类型的值转换为Number类型:
    • Number(true) = 1; Number(false) = 0;
    • Number(null) = 0;
    • Number(“”) = 0; Number(“0x1a”) = 26; Number(‘076’) = 76;
    • Number(‘100hello’) = NaN;
    • 如果是对象,则会调用对象的valueOf()方法,如果返回的是NaN则会先调用toString方法转化为字符串,然后根据字符串的转换规则来转换
  • parseInt()函数:
    • parseInt(‘100hello’) = 100;
    • parseInt(”) = NaN;
    • parseInt(‘0x1a’) = 26;
    • parseInt(‘076’) = 76; parseInt(‘076’, 8) = 62;
  • parseFloat()与parseInt()类似,但有如下区别:
    • parseFloat不能传入第二个参数(进制),不能解析十六进制字符串

String

  • 字符串一旦创建,其值不能改变,如:var lang = ‘Java’; lang += ‘Script’; 会重新创建一个字符串,填充上’JavaScript’, ‘Java’和’Script’都将被销毁
  • 除了null和undefined之外,其他的几个数据类型都有toString()方法,可以将其转换为字符串
  • 数值类型调用toString()方法可以传入进制作为参数,如:var a=20; a.toString(2);
  • String()方法可以将null转化为’null’,把undefined转化为’undefined’

CSS概要

CSS 基础知识

语法

CSS全称为“层叠样式表 (Cascading Style Sheets)”,它主要是用于定义HTML内容在浏览器内的显示样式, 如文字大小、颜色、字体加粗等。使用CSS样式的一个好处是通过定义某个样式,可以让不同网页位置的 文字有着统一的字体、字号或者颜色等。

CSS语法
选择符 { 属性:值}

  • 选择符:又称选择器,指明网页中要应用样式规则的元素,如本例中是网页中所有的段(p)的文字将变 成蓝色,而其他的元素(如ol)不会受到影响。
  • 声明:在英文大括号“{}”中的的就是声明,属性和值之间用英文冒号“:”分隔。当有多条声明时,中间 可以英文分号“;”分隔
  1. 最后一条声明可以没有分号,但是为了以后修改方便,一般也加上分号
  2. CSS注释 – /*注释语句*/
  3. CSS的某些样式是具有继承性的。
  4. 为了使用样式更加容易阅读,可以将每条代码写在一个新行内

插入方式

CSS样式可以写在哪些地方呢?从CSS 样式代码插入的形式来看基本可以分为以下3种:内联式、嵌入式和

外部式三种。

  1. 内联式:把css代码用style属性直接写在现有的HTML标签中。如: <p style=”color:red”>这里文字是红色。</p>
  2. 嵌入式:把css样式代码写在<style type=“text/css”></style>标签之间。并且一般情况下嵌入式css样式 写在<head></head>之间。如:<style type=”text/css”> span{ color:red; } </style>
  3. 外部式:把css代码写一个单独的外部文件中,这个css样式文件以“.css”为扩展名,在<head>内(不是在<style>标签内)使用<link>标签将css样式文件链接到HTML文件内。如: <link href=”base.css” rel=”stylesheet” type=”text/css” />

优先级:内联式 > 嵌入式 > 外部式 ?

离被设置元素越近优先级别越高

权值、层叠、重要性

标签的权值为1,类选择符的权值为10,ID选择符的权值最高为100

  • p{color:red;} /*权值为1*/
  • p span{color:green;} /*权值为1+1=2*/
  • .warning{color:white;} /*权值为10*/
  • p span.warning{color:purple;} /*权值为1+1+10=12*/
  • #footer .note p{color:yellow;} /*权值为100+10+1=111*/

层叠就是在html文件中对于同一个元素可以有多个css样式存在,当有相同权重的样式存在时,会根据这些 css样式的前后顺序来决定,处于最后面的css样式会被应用。

p{color:red;}
p{color:green;}
<p class=”first”>hello world</p>

!important语句可以把样式改变为最高权值

p{color:red !important;} p{color:green;}
<p class=”first”>hello world</p>

单位和值

颜色值

  1. 英文命令颜色。 如: p{color:red;}
  2. RGB颜色 p{color:rgb(133,45,200);} 3.十六进制颜色 p{color:#00ffff;}

长度值

  1. 像素。 如: p{font-size: 20px}
  2. em。1 em = 父元素的font-size 如: p{font-size:14px} span{font-size:0.8em;} <p>以这个<span>例子</span>为例。</p>
  3. 百分比 p{font-size:12px;line-height:130%}

CSS 选择器

在{}之前的部分就是“选择器”,“选择器”指明了{}中的“样式”的作用对象,也就是“样式”作用于网页中的哪些元素。

  • 标签选择器 – 标签选择器其实就是html代码中的标签。

  • 类选择器

    .setGreen {
        color:green;
    }

    <span class=”setGreen”>胆小如鼠</span>

  • Id选择器

    #setGreen {
         color:green;
    }

  • 子选择器 – 即大于符号(>),用于选择指定标签元素的第一代子元素

    .food>li{border:1px solid red;}

  • 包含选择器 – 即加入空格,用于选择指定标签元素下的后辈元素

  • 通用选择器 – 它使用一个(*)号指定,它的作用是匹配html中所有标签元素

  • 伪类选择符 – 它允许给html不存在的标签(标签的某种状态)设置样式,如:a:hover{color:red;}

  • 分组选择符 – html中多个标签元素设置同一个样式时,可以使用分组选择符

    h1,span{color:red;}

CSS 排版 

  1. 设置字体: font-family:”宋体”

  2. 设置字号、颜色:font-size:12px;color:#666

  3. 设置文字的样式:粗体、斜体、下划线、删除线:

    • font-weight:bold;

    • font-style:italic

    • text-decoration:underline;

    • text-decoration:line-through;

  4. 设置文字缩进:text-indent:2em;

  5. 设置行高:line-height:2em;

  6. 设置字符间距、单词间距:

    • letter-spacing:50px;

    • word-spacing:50px;

  7. 设置对齐方式:

    • text-align:left;

    • text-align:center;

    • text-align:right;

CSS 布局模型

元素分类 在CSS中,html中的标签元素大体被分为三种不同的类型:块状元素、内联元素(又叫行内元素)和内联块状元素。

常用的块状元素(display: block)有: <div>、<p>、<h1>…<h6>、<ol>、<ul>、<dl>、<table>、<address>、<blockquote> 、<form>

  • 每个块级元素都从新的一行开始,并且其后的元素也另起一行。
  • 元素的高度、宽度、行高以及顶和底边距都可设置。
  • 元素宽度在不设置的情况下,是它本身父容器的100%(和父元素的宽度一致),除非设定一个宽度。

常用的内联元素(display: inline)有: <a>、<span>、<br>、<i>、<em>、<strong>、<label>、<q>、<var>、<cite>、<code>

  • 和其他元素都在一行上;
  • 元素的高度、宽度及顶部和底部边距不可设置;
  • 元素的宽度就是它包含的文字或图片的宽度,不可改变。

常用的内联块状元素(display: inline-block)有: <img>、<input>

  • 和其他元素都在一行上;
  • 元素的高度、宽度、行高以及顶和底边距都可设置。

盒子模型

流动模型 flow

流动模型,流动(Flow)是默认的网页布局模式。也就是说网页在默认状态下的 HTML 网页元素都是根据流

动模型来分布网页内容的。 流动布局模型具有2个比较典型的特征:

  • 块状元素都会在所处的包含元素内自上而下按顺序垂直延伸分布,因为在默认状态下,块状元素的 宽度都为100%。实际上,块状元素都会以行的形式占据位置。
  • 在流动模型下,内联元素都会在所处的包含元素内从左到右水平分布显示。

浮动模型 float

浮动模型,浮动(Float)如果想让两个块状元素并排显示,需要用到浮动模型。

Div {
    width:200px;
    height:200px;
    border:2px red solid;
    float:left;
}

<div id=”div1″> </div>
<div id=”div2″> </div>

层模型

什么是层布局模型?层布局模型就像是图像软件PhotoShop中非常流行的图层编辑功能一样,每个图层能够 精确定位操作,但在网页设计领域,由于网页大小的活动性,层布局没能受到热捧。但是在网页上局部使用 层布局还是有其方便之处的

层模型有三种形式:

  1. 绝对定位(position: absolute)
  2. 相对定位(position: relative)
  3. 固定定位(position: fixed)

如果想为元素设置层模型中的绝对定位,需要设置position:absolute(表示绝对定位),这条语句的作用将元 素从文档流中拖出来,然后使用left、right、top、bottom属性相对于其最接近的一个具有定位属性的父包含块 进行绝对定位。如果不存在这样的包含块,则相对于body元素,即相对于浏览器窗口。

如果想为元素设置层模型中的相对定位,需要设置position:relative(表示相对定位),它通过left、right、top、 bottom属性确定元素在正常文档流中的偏移位置。相对定位完成的过程是首先按static(float)方式生成一个元 素(并且元素像层一样浮动了起来),然后相对于以前的位置移动,移动的方向和幅度由left、right、top、 bottom属性确定,偏移前的位置保留不动。

fixed:表示固定定位,与absolute定位类型类似,但它的相对移动的坐标是视图(屏幕内的网页窗口)本身。 由于视图本身是固定的,它不会随浏览器窗口的滚动条滚动而变化,除非你在屏幕中移动浏览器窗口的屏幕 位置,或改变浏览器窗口的显示大小,因此固定定位的元素会始终位于浏览器窗口内视图的某个位置,不会 受文档流动影响

弹性盒布局

详细介绍请参考:https://www.ibm.com/developerworks/cn/web/1409_chengfu_css3flexbox/

.flex-container {
   list-style: none;
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
}
<ul class="flex-container">
<li class="flex-item"><imgsrc="//placehold.it/300&text=1"></li>
<li class="flex-item"><img src="//placehold.it/300&text=2"></li>
<li class="flex-item"><img src="//placehold.it/300&text=3"></li>
<li class="flex-item"><img src="//placehold.it/300&text=4"></li>
<li class="flex-item"><img src="//placehold.it/300&text=5"></li>
<li class="flex-item"><img src="//placehold.it/300&text=6"></li>
</ul>
.flex-item {   
    padding: 5px;
}

CSS 设计技巧

• 水平居中设置-行内元素

通过给父元素设置 text-align:center 来实现的

• 水平居中设置-定宽块状元素

通过设置“左右margin”值为“auto”来实现居中的

• 水平居中设置-不定宽块状元素方法

  1. 加入 table 标签

  2. 设置 display: inline 方法:与第一种类似,显示类型设为 行内元素,进行不定宽元素的属性设置

  3. 设置 position:relative 和 left:50%:利用 相对定位 的方式,将元素向左偏移 50% ,即达到居中的目的

• 垂直居中-父元素高度确定的单行文本

通过设置父元素的 height 和 line-height 高度一致来实现的

• 垂直居中-父元素高度确定的多行文本

  1. 使用插入 table (包括tbody、tr、td)标签,同时设置 vertical-align:middle

  2. 设置块级元素的 display 为 table-cell(设置为表格单元显示),激活 vertical-align 属性

HTML概要

HTML CSS Javascript 的关系

HTML是网页内容的载体。内容就是网页制作者放在页面上想要让用户浏览的信息,可以包含文字、图片、视频等。

CSS样式是表现。就像网页的外衣。比如,标题字体、颜色变化,或为标题加入背景图片、边框等。所有这些用来改变内容外观的东西称之为表现。

JavaScript是用来实现网页上的动态效果。如:鼠标滑过弹出下拉菜单。或鼠标滑过表格的背景颜色改变。还有焦点新闻(新闻图片)的轮换。

HTML 标签语法

1标签由英文尖括号<>括起来,如<html>就是一个标签

2. html中的标签一般都是成对出现的,分开始标签结束标签。结束标签比开始标签多了一个/

如:

1) <p></p>

2) <div></div>

3) <span></span>

3. 标签与标签之间是可以嵌套的,但先后顺序必须保持一致,如:<div>里嵌套<p>,那么</p>必须放在</div>的前面。如下图所示

4. HTML标签不区分大小写,<h1><H1>是一样的,但建议小写,因为大部分程序员都以小写为准。

HTML标签

<p> 标签

如果想在网页上显示文章,就需要<p>标签,把文章的段落放到<p>标签中

语法

<p>段落文本</p>

 

<hx> 标签

使用<hx>标签来制作文章的标题
标题标签一共有6个,h1h2h3h4h5h6分别为一级标题、二级标题、三级标题、四级标题、五级标题、六级标题。并且依据重要性递减。<h1>是最高的等级

语法

<h1>段落文本</h1>

<strong> <em>标签

在一段话中特别强调某几个文字,这时候就可以用到<em><strong>标签。

但两者在强调的语气上有区别:<em> 表示强调,<strong> 表示更强烈的强调。并且在浏览器中<em> 默认用斜体表示,<strong> 粗体表示

语法

<em>段落文本</em>

<strong>段落文本</strong>

<q> 标签

使用q标签可以在html中添加一段引用,如作家的话诗句等。

1注意要引用的文本不用加双引号,浏览器会对q标签自动添加双引号

语法

段落文本<q>引用文本</q>段落文本

<blockquote> 标签

 blockquote作用也是引用别人的文本。但它是对长文本的引用,如在文章中引入大段某知名作家的文字,这时需要这个标签。

1浏览器对<blockquote>标签的解析是缩进样式,而不是添加引号

语法

<blockquote>引用段落</blockquote>

<br /> 标签

 需要加回车换行的地方加入<br /><br />标签作用相当于word文档中的回车。

1浏览器会忽略HTML中的回车和空格,如果需要换行,就要用到<br />标签。

2. 如果需要加空格,则需要用&nbsp来替换空格。

语法

引用段落<br />

<hr /> 标签

在信息展示时,有时会需要加一些用于分隔的横线,这样会使文章看起来整齐.

1. <hr />标签和<br />标签一样也是一个空标签,所以只有一个开始标签,没有结束标签

2. <hr />标签的在浏览器中的默认样式线条比较粗,颜色为灰色。可以通过css来改变水平线的样式。

语法

<p>文本段落</p>

<hr />

<address> 标签

一般网页中会有一些网站的联系地址信息需要在网页中展示出来,这些联系地址信息如公司的地址就可以<address>标签。也可以定义一个地址(比如电子邮件地址)、签名或者文档的作者身份

1. <address>标签中的内容会在浏览器中显示为斜体。

语法

<address>地址信息或联系人信息</address>

<code><pre> 标签

在介绍语言技术的网站中,避免不了在网页中显示一些计算机专业的编程代码,当代码为一行代码时,你就可以使用<code>标签了,如下面例子:

<code>var i=i+300;</code>

1. 如果是多行代码,可以使用<pre>标签。

2. <pre> 标签的主要作用:预格式化的文本。被包围在 pre 元素中的文本通常会保留空格和换行符。

语法

<code>一行计算机代码</code>

<pre>多行计算机代码</pre>

<ul> <li>标签

利用<ul><li>可以生成没有顺序的列表。

语法

<ul>

  <li>精彩少年</li>

  <li>美丽突然出现</li>

  <li>触动心灵的旋律</li>

</ul>

<ol> <li>标签

利用<ol><li>可以生成有顺序的列表。

语法

<ol>

  <li>前端开发面试心法 </li>

  <li>零基础学习html</li>

  <li>JavaScript全攻略</li>

</ul>

<div> 标签

在网页制作过程过中,可以把一些独立的逻辑部分划分出来,放在一个<div>标签中,这个<div>标签的作用就相当于一个容器

1. divspan类似,都没有特殊的语义。

2. div可以用来排版。

3. div属于块级元素。

4. 可以用id来为div分组命名

语法

<div id=’xxx’>

  

</div>

<caption> 标签

可以使用caption标签来为表格添加标题:

语法:

<table>

  <caption>标题文本</caption>

  <tr>

    <td>…</td>

    <td>…</td>

     

   </tr>

   

</table>

<a> 标签

使用<a>标签可实现超链接,它在网页制作中可以说是无处不在,只要有链接的地方,就会有这个标签

语法:

<a href=”目标网址” title=”鼠标滑过显示的文本“>链接显示的文本</a>

1. title属性的作用鼠标滑过链接文字时会显示这个属性的文本内容。这个属性在实际网页开发中作用很大,主要方便搜索引擎了解链接地址的内容(语义化更友好)

2. <a>标签在默认情况下,链接的网页是在当前浏览器窗口中打开,如果需要在新的浏览器窗口中打开,则需要用到target选项。

<a href=”目标网址” target=”_blank”>click here!</a>
3. a标签还有一个作用是可以链接Email地址,使用mailto能让访问者便捷向网站管理者发送电子邮件

<img> 标签

使用<img>标签可以在网页中插入图片。

语法:

<img src=”图片地址” alt=”下载失败时的替换文本” title = “提示文本“>
1src标识图像的位置

2alt指定图像的描述性文本,当图像不可见时(下载不成功时),可看到该属性指定的文本

3title提供在图像可见时对图像的描述(鼠标滑过图片时显示的文本)

4图像可以是GIFPNGJPEG格式的图像文件。

HTML表单

表单可以把浏览者输入的数据传送到服务器端,这样服务器端程序就可以处理表单传过来的数据

语法:

<form method=”传送方式” action=”URL”>

</form>

1.<form> <form>标签是成对出现的,以<form>开始,以</form>结束

2.action 浏览者输入的数据被传送到的地方,比如一个PHP页面(save.php)

3.method  数据传送的方式(get/post

4. 所有表单控件(文本框、文本域、按钮、单选框、复选框等)都必须放在 <form></form> 标签之间
5. get请求会把表单提供的参数放到URL中,而post请求会把参数放到http请求体中

文本、密码输入框

当用户要在表单中键入字母、数字等内容时,就会用到文本输入框。文本框也可以转化为密码输入框

语法:

<form>

  <input type=”text/password” name=”名称” value=”文本” />

</form>

1type

   当type=”text”时,输入框为文本输入框;

   当type=”password”输入框为密码输入框

2name:为文本框命名,以备后台程序ASP PHP使用

3value:为文本输入框设置默认值。(一般起到提示作用)

文本域 多行文本输入

当用户需要在表单中输入大段文字时,需要用到文本输入域。

语法:

<textarea rows=”行数 cols=”列数>

文本

</textarea>

1<textarea>标签是成对出现的,以<textarea>开始,以</textarea>结束

2cols 多行输入域的列数

3rows 多行输入域的行数

4、在<textarea></textarea>标签之间可以输入默认值

单选框、复选框

在使用表单设计调查表时,为了减少用户的操作,使用选择框是一个好主意,html中有两种选择框,即单选框复选框,两者的区别是单选框中的选项用户只能选择一项,而复选框中用户可以任意选择多项,甚至全选

语法:

<input type=”radio/checkbox” value=”” name=”名称” checked=”checked”/>

1type:

   当 type=”radio” 时,控件为单选框

   当 type=”checkbox” 时,控件为复选

2value:提交数据到服务器的值(后台程序PHP使用

3name:为控件命名,以备后台程序 ASPPHP 使用

4checked:当设置 checked=”checked” 时,该选项被默认选中

下拉列表框

下拉列表在网页中也常会用到,它可以有效的节省网页空间。既可以单选、又可以多选

下拉列表也可以进行多选操作,在<select>标签中设置multiple=”multiple”属性,就可以实现多选功能

提交按钮

在表单中有两种按钮可以使用,分别为:提交按钮、重置。这一小节讲解提交按钮:当用户需要提交表单信息到服务器时,需要用到提交按钮

语法

<input type=”submit” value=”提交“>

1, type:只有当type值设置为submit时,按钮才有提交作用

2, value按钮上显示的文字

重置按钮

当用户需要重置表单信息到初始时的状态时,比如用户输入“用户名”后,发现书写有误,可以使用重置按钮使输入框恢复到初始状态。只需要把type设置为“reset”就可以

语法

<input type=”reset” value=”重置“>

1, type:只有当type值设置reset,按钮才有重置作用

2, value按钮上显示的文字

form表单中的label标签

label标签不会向用户呈现任何特殊效果,它的作用是为鼠标用户改进了可用性。如果你在 label 标签内点击文本,就会触发此控件

语法

<label for=”控件id名称“> </label>

HTML5

更简化的语法:

  • <!doctype html>
  • <html lang=“zh-CN”>
  • <meta charset=“utf-8”>
  • 不区分大小写  HTML = HtMl
  • checked=“checked” à checked
  • <html><head><body>可以省略

      。。。

废除的标签

  • 能用css替代的标签:basefont, font, center
  • Frame相关的标签,只支持iframeframeset, frame, noframes
  • 只有部分浏览器支持的标签:applet, bgsound
  • 其他废除的标签:rb, dir, listing, xmp

废除了一些和样式相关的属性

HTML5 新增标签

  • <section></section> 表示页面中的一个内容区块,不用作排版
  • <article></article> 表示与上下文不相干的独立内容,比如说文章
  • <aside></aside> 表示与文章相关的辅助信息,如文章后面的推荐阅读
  • <header></header> 页眉
  • <footer></footer> 页脚
  • <nav></nav> 导航栏
  • <figure></figure> 标签规定独立的流内容(图像、图表、照片、代码等等)。figure 元素的内容应该与主内容相关,但如果被删除,则不应对文档流产生影响
  • <figcaption></figcaption> 标签定义 figure 标题(caption)。figcaption元素应该被置于 “figure” 元素的第一个或最后一个子元素的位置
  • <video></video> 视频标签
  • <audio></audio> 音频标签
  • <embed /> 嵌入标签,可以是视频、音频、flash等多媒体内容
  • <mark></mark> 替换文本的背景色来标记文本
  • <progress max=“100” value=“85”></progress> 进度条
  • <canvas></canvas> 绘图,替代flash
  • <details><summary>always show</summary>something can be hidden</details>
  • <datalist></datalist>

HTML5 新增属性

  • <html manifest=“xxx.manifest”>
  • <meta charset=“utf-8”>
  • <link rel=‘icon’ href=“URL” type=image/gif sizes=“16×16”> //目前几乎没有主流浏览器支持 sizes 属性
  • <script defer src=“*.js”></script>
  • <script async src=“*.js”></script>
  • <a media=“tv href=“www.baidu.com hreflang=“zh ref=“external”>baidu</a>
  • <ol start=”50” reversed></ol>
  • <style scoped></style>
  • <iframe seamless srcdoc=“<h1>hello</h1>” sandbox src=www.baidu.com> </iframe>

HTML5 新增全局属性

  • data-*
  • Hidden
  • Tabindex
  • spellcheck=”true”
  • contenteditable=“true”

 

Python中list的遍历

在python中,若要遍历一个list而且需要在遍历时修改list,则需要十分注意,因为这样可能会导致死循环,例如:

In [10]: ls = ['hello', 'world', 'bugggggggg']

In [11]: for item in ls:
   ....:     if len(item) > 5:
   ....:         ls.insert(0, item)
   ....:         print ls
   ....:         
['bugggggggg', 'hello', 'world', 'bugggggggg']
['bugggggggg', 'bugggggggg', 'hello', 'world', 'bugggggggg']
['bugggggggg', 'bugggggggg', 'bugggggggg', 'hello', 'world', 'bugggggggg']
...

所以,为了安全起见,在遇到需要修改列表的时候,都不对列表本身进行遍历,而是创建一个列表的备份,然后对这个备份进行遍历,从而避免了上述情形。例如:

In [20]: In [10]: ls = ['hello', 'world', 'bugggggggg']

In [21]: for item in ls[:]:
   ....:     if len(item) > 5:
   ....:         ls.insert(0, item)
   ....:         

In [22]: print(ls)
['bugggggggg', 'hello', 'world', 'bugggggggg']

 

Python中的参数传递与解析

Python传递命令行参数

Python的命令行参数传递和C语言类似,都会把命令行参数保存到argv的变量中。对于python而言,argv是sys模块中定义的一个list。与C语言不同的是,python中并没有定义argc,要获得参数的个数,需要使用len(sys.argv)

当用户使用’python -c “command” ‘来运行一条python语句时,argv中保存的是[‘-c’]及”command”后面的参数,例如:

$ python -c 'import sys
print sys.argv' hello world
['-c', 'hello', 'world']

当用户使用’python -m “module” ‘来运行一个模块时,argv中保存的是模块名及”module”后面的参数,例如:

$ python -m 'show_args' hello world
['/home/kelvin/tmp/show_args.py', 'hello', 'world']

当运行python脚本时,argv中保存的是脚本名及其后面的参数:

$ python show_args.py hello world
['show_args.py', 'hello', 'world']
length of argv: 3

$ cat show_args.py 
#!/bin/env python

import sys

print sys.argv
print "length of argv: " + str(len(sys.argv))

使用标准库getopt解析选项和参数

getopt模块和C语言中的getopt函数有着一样的API,熟悉C语言的同学可快速上手。

# -*- coding: utf-8 -*-
#!/bin/env python

import getopt
import sys


def usage():
    print("Usage:")
    print(sys.argv[0] + ' -i input_file -o output_file')


def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ho:i:", ["help", "output=", "input="])
    except getopt.GetoptError as err:
        # print help information and exit:
        print("[" + err.opt + "]" + err.msg)
        usage()
        sys.exit(2)
    output = None
    input = None
    for o, a in opts:
        if o in ("-i", "--input"):
            input = a
        elif o in ("-h", "--help"):
            usage()
            sys.exit()
        elif o in ("-o", "--output"):
            output = a
        else:
            assert False, "unhandled option"
    print("Input file: %s" % (input))
    print("Output file: %s" % (output))

if __name__ == "__main__":
    main()

getopt需要传入3个参数:

1.  需要解析的字符串,即sys.argv[1:]

2. 短选项集合。其中跟冒号的短选项需要后接参数,如’o:’表示’-o’选项需要接参数。

3. 长选项列表。其中跟等号的长选项需要后接参数。

getopt返回一个元组,元组包括两个列表opts和args。opts的元素是一个元组,保存了解析好的选项和参数对。args保存了除去所有选项和选项的参数之外,剩下的所有参数。

如果解析出错则会抛出GetoptError异常,该异常有一个参数err。err.opt是出错时正在解析的选项,err.msg是错误消息。

出错的情况包括:

1. 选项没有在传入参数中的短选项或者长选项列表定义。

2. 需要带参数的选项没有跟参数。

3. 不需要带参数的长选项带了参数。

4. 其他。

kelvin@kvm:~/tmp$ python parse_args.py --help=out
[help]option --help must not have an argument
Usage:
parse_args.py -i input_file -o output_file
kelvin@kvm:~/tmp$ python parse_args.py -i input_file.txt 
Input file: input_file.txt
Output file: None
kelvin@kvm:~/tmp$ python parse_args.py -i input_file.txt --output="hello_world.txt"
Input file: input_file.txt
Output file: hello_world.txt
kelvin@kvm:~/tmp$ python parse_args.py -i input_file.txt -o "output.txt"
Input file: input_file.txt
Output file: output.txt

使用标准库argparse来解析选项和参数

argparse模块功能更加强大,例如可以自动生成help文档等,使用起来也更加简便,只需要三个步骤即可。第一步,创建一个ArgumentParser对象:

parser = argparse.ArgumentParser(description='Description of the program')

第二步,添加需要解析的选线和参数:

parser.add_argument('integers', metavar='N', type=int, nargs='+',
                    help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')

第三部,开始解析:

args = parser.parse_args()

ArgumentParser

 class argparse.ArgumentParser(prog=None, usage=None, 
description=None, epilog=None, parents=[], 
formatter_class=argparse.HelpFormatter, prefix_chars='-',
 fromfile_prefix_chars=None, argument_default=None, 
conflict_handler='error', add_help=True, allow_abbrev=True)

常用的参数解释如下:

prog: 指定程序的名字,默认为sys.argv[0].

usage: 描述程序该如何使用的字符串,默认会根据添加的参数和选项自动生成

description: 描述程序的功能,默认为空

epilog: epilog指定的字符串将会显示在帮助文档的最后

parents: 一个 ArgumentParser对象的列表,这些对象的选项和参数也会被继承

add_help: 添加-h/–help选项,默认为True

allow_abbrev: 允许长选项的缩写,默认为True

add_argument

 ArgumentParser.add_argument(name or flags...[, action]
[, nargs][, const][, default][, type][, choices][, required]
[, help][, metavar][, dest])

name将会作为解析后返回的对象args的属性,存储参数的值,flags定义指定的选项,flag的名字也会作为解析后返回的对象的属性,存储该选项的参数。例如:

parser.add_argument('-f', '--foo')
parser.add_argument('arg0')
args = parser.parse_args()
print(args.foo)
print(args.arg0)

执行结果如下:

$./arg_parse.py --foo hello world
hello
world

nargs指定选项参数或者参数本身的个数。例如:

parser.add_argument('-f', '--foo', nargs=2)
args = parser.parse_args()
print(args.foo)

执行结果如下:

$./arg_parse.py --foo hello world
['hello', 'world']

argparse会将–foo选项后面的两个参数都作为–foo的参数处理。

action指定argparse如何处理该选项的参数,共有8个值可选。

  1. ‘store’: 默认值,表示存储参数,如上面例子中的args.foo存储hello world.
  2. ‘store_const’: 存储常量,常量的值位于const参数中。如:
    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--foo', action='store_const', const=1024)
    args = parser.parse_args()
    print(args.foo)
    
    $ ./arg_parse.py -f
    1024
    

     

  3. ‘store_true’或者’store_false’:和’store_const’类似,存储的值为True或者False

  4. ‘append’:连个同样的选项的参数会被放到同一个list里面,例如:
     

    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--foo', action='append')
    args = parser.parse_args()
    print(args.foo)
    
    $ ./arg_parse.py --foo 1 --foo 2 -f 3
    ['1', '2', '3']

     

  5. ‘append_const’: 可将多个常量存放到一个list中,选项出现几次,list中的常量就出现几次,例如:
     

    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--foo', action='append_const', const="1024")
    args = parser.parse_args()
    print(args.foo)
    
    $ ./arg_parse.py --foo --foo -f
    ['1024', '1024', '1024']

     

  6. ‘count’:  存储选项出现的次数。例如:
     

    $ cat arg_parse.py 
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--verbose', action='count')
    args = parser.parse_args()
    print(args.verbose)
    
    $ ./arg_parse.py -vvv
    3
    
    $ ./arg_parse.py -v --verbose -v
    3

     

  7. ‘help’: 当出现这个选项时,程序打印help文档然后退出。

  8. ‘version’: 当出现这个选项时,程序打印版本信息然后退出,版本信息可通过version定义,例如:
     

    $cat arg_parse.py
    #!/usr/bin/env python
    
    import argparse
    
    parser = argparse.ArgumentParser(prog="wchat")
    parser.add_argument('-v', '--version', action='version', version='%(prog)s 3.8.5')
    args = parser.parse_args()
    
    $ ./arg_parse.py --version
    wchat 3.8.5

     

required指定该参数或者选项是必须提供的,否则会报错退出。

type指定参数的类型,可以是任何python内建的数据类型如int等,也可以是自定义的类型转换函数的函数名。例如:

$ cat ./arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', type=int)
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -f 12
12

$ ./arg_parse.py -f '12'
12

$ ./arg_parse.py -f hello
usage: arg_parse.py [-h] [-f FOO]
arg_parse.py: error: argument -f/--foo: invalid int value: 'hello'

choices指定一组参数,选项的参数必须从这组参数中来选取。例如:

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', type=int, choices=range(1, 4))
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -f3
3

$ ./arg_parse.py -f19
usage: arg_parse.py [-h] [-f {1,2,3}]
arg_parse.py: error: argument -f/--foo: invalid choice: 19 (choose from 1, 2, 3)

help指定参数或者选项的帮助信息,会出现在help文档里。

metavar可以改变帮助文档中选项的参数占位字符串,例如,–foo默认的占位字符串为FOO,可以通过metavar改为foo_arg:

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo')
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -h
usage: arg_parse.py [-h] [-f FOO]

optional arguments:
  -h, --help         show this help message and exit
  -f FOO, --foo FOO

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', metavar='foo_arg')
args = parser.parse_args()
print(args.foo)

$ ./arg_parse.py -h
usage: arg_parse.py [-h] [-f foo_arg]

optional arguments:
  -h, --help            show this help message and exit
  -f foo_arg, --foo foo_arg

dest可以修改存储参数的属性名,例如:

$ cat arg_parse.py 
#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', dest='bar')
args = parser.parse_args()
print(args.bar)

$ ./arg_parse.py -f hello
hello

小结

getopt虽然提供了接近Unix C的用户接口,方便了熟悉Unix C的程序猿/媛们,但argparse模块功能更为强大,使用起来也更为简洁,所以大多数的python项目都采用argparse来解析参数。

基于web技术的操作系统安装器的设计

什么是基于web技术的安装器(web-based installer)?

传统的Linux操作系统安装需要启动一个LiveOS,然后在LiveOS中运行一个本地安装程序,如Fedora下的Anaconda. LiveOS除了让用户在安装操作系统之前能预先体验之外,也为安装器提供了运行环境。这对于桌面操作系统已然足够,因为PC、笔记本电脑自带终端设备——键盘、显示器、鼠标。然而,如果要给一台服务器安装操作系统则稍微复杂了一点,因为服务器通常没有这些终端设备。这就需要利用网络和VNC将服务器端的图像传送出来。这样做的缺点在于,网络负载很大,在网络条件不好的环境下会带给用户非常差的安装体验。另外,本地安装器也会依赖很多的图形软件包,不仅会增加ISO的大小,也会带来很多版权及法律上的工作量。

基于web技术的安装器则利用web开发技术——HTML5,Javascript,CSS,web server等,让用户可以通过浏览器直接安装操作系统。目前,”KVM for IBM z Systems”已经采用这种安装方式。

基于web技术安装器的优势

  • 依赖更少的软件包
  • 非常小的网络负载,网页加载后只需要通过Ajax来与服务器通信
  • 可通过浏览器跨平台访问
  • 对于服务器及集群安装非常方便
  • 易于与其他软件集成,提供RESTful API
  • 满足可访问性,方便残障人士使用

使用web安装器的安装流程

使用web安装器来安装操作系统的流程非常简单,只需要如下几个步骤:

  1. 加载ISO
  2. 用https://{IP_OF_SERVER}作为URL来访问安装器
  3. 按照安装器的引导完成安装
  4. 点击安装器的重启按钮重启服务器,安装完成

Web安装器实现的功能

  • 国际化及多语言支持,选择安装语言与系统语言
  • 版权声明
  • 磁盘列表及选择安装磁盘
  • 添加SCSI磁盘
  • 添加DASD磁盘
  • Swap分区加密
  • 自动分区
  • 手动分区
  • 分区操作列表
  • 激活网卡
  • IPv4配置
  • IPv6配置
  • 主机名及搜索域配置
  • NTP配置
  • Kdump配置
  • 时区设置
  • Root密码设置
  • 添加、删除用户
  • 配置总结列表
  • 无人值守安装
  • 安装日志下载

Web安装器的架构设计

Web安装器分为前端和后端两部分实现:前端负责UI展示及用户交互,后端负责给前端提供RESTful API并根据前端的API调用来存储用户配置数据,执行分区、安装、配置目标系统等操作。

Web安装器由4个HTML页面组成:

  • 欢迎页:介绍操作系统,提供选择安装语言的下拉框,点击下一步可进入到版权声明页
  • 版权声明页:显示版权文件,在用户同意后可跳转到配置页面
  • 配置页:引导用户进行系统配置
  • 安装页:展示安装进度,安装完成后可点击重启按钮重启系统

UI是基于HTML5、CSS3及Javascript等网页开发技术,并利用如下工具:

  • jQuery:一个快速、小巧且功能丰富的js库,可用来操作DOM,处理事件及Ajax请求
  • Bootstrap:最流行的前端开发框架之一,多用于开发响应式、移动优先的web项目
  • Bootstrap-select: jQuery 插件,利用Bootstrap,但提供了功能更加丰富的下拉选择框控件
  • Jquery Validation :jQuery插件,用来检验表单的合法性

后端由以下几个模块组成:

  • CherryPy:一个轻量级的python web发布器
  • Model:存储用户的配置数据
  • RESTful API:为前端提供API接口
  • 子功能模块:提供安装器的各个子功能

使用jQuery Validation插件来验证表单

jQuery Validation是一个用于验证表单的jQuery插件,简单易用,已经包含了16种内置的验证规则.Github上也有更多的验证规则可以使用.这都不是重点,重点是你可以轻松的定制自己的规则.

内置规则:

  • required – Makes the element required.
  • remote – Requests a resource to check the element for validity.
  • minlength – Makes the element require a given minimum length.
  • maxlength – Makes the element require a given maximum length.
  • rangelength – Makes the element require a given value range.
  • min – Makes the element require a given minimum.
  • max – Makes the element require a given maximum.
  • range – Makes the element require a given value range.
  • step – Makes the element require a given step.
  • email – Makes the element require a valid email
  • url – Makes the element require a valid url
  • date – Makes the element require a date.
  • dateISO – Makes the element require an ISO date.
  • number – Makes the element require a decimal number.
  • digits – Makes the element require digits only.
  • equalTo – Requires the element to be the same as another one

内置规则的使用

内置规则的使用非常简单:

首先将该插件的js文件包含进html文件:

    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/js/jquery.validate.min.js"></script>

然后用jQuery选择需要验证的表单,执行validate()函数即可:

    <script>
    $("#form_id").validate();
    </script>

jQuery Validation官网上的例子:

    <!DOCTYPE HTML>
    <html>
    <head>
    <script src="jquery.min.js"></script>
    </head>
    <body>
    <form class="cmxform" id="commentForm" method="get" action="">
    <fieldset>
    <legend>Please provide your name, email address (won't be published) and a comment</legend>
    <p>
    <label for="cname">Name (required, at least 2 characters)</label>
    <input id="cname" name="name" minlength="2" type="text" required>
    </p>
    <p>
    <label for="cemail">E-Mail (required)</label>
    <input id="cemail" type="email" name="email" required>
    </p>
    <p>
    <label for="curl">URL (optional)</label>
    <input id="curl" type="url" name="url">
    </p>
    <p>
    <label for="ccomment">Your comment (required)</label>
    <textarea id="ccomment" name="comment" required></textarea>
    </p>
    <p>
    <input class="submit" type="submit" value="Submit">
    </p>
    </fieldset>
    </form>
    <script>
    $("#commentForm").validate();
    </script>
    </body>
    </html>

jQuery Validation会根据表单设置的type和属性自动为他们分配内置的规则,比如email,url,required等.

运行一下看看:

什么都不输入,直接点提交:

空表单

输入错误的Email地址,改正后错误提示自动消失: Email输入错误Email输入正确

添加自定义规则

jQuery Validation最吸引人的feature,它可以轻松的加入自定义的规则:

第一步,在js中调用jQuery.validator.addMethod函数来添加规则,例如添加IP格式检查的规则:

$.validator.addMethod( "ipv4", function( value, element ) {return this.optional( element ) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test( value );}, "Invalid IP v4 address." ); //自定义其他规则只需要替换规则名"ipv4",正则表达式//之间的内容,以及出错后显示的字符串"Invalid IP v4 address."即可.

第二步,把规则应用到指定的表单项,即在执行$(“#form_id”).validate()函数的时候加入rules参数:

    23 $("#ip_form").validate({
    24     rules: {
    25         ip: {
    26             required: true,
    27             ipv4: true,
    28         },
    29         netmask: {
    30             required: true,
    31             ipv4: true,
    32         },
    33         gateway: {
    34             required: true,
    35             ipv4: true,
    36         },
    37     },
    38 }); //其中ip,netmask,gateway为表单项的name属性.required和ipv4是规则名.

生效后的样子,可以添加如下css来修改错误信息的样式:

    label.error {
        margin-left: 10px;
        padding-left: 5px;
        padding-right: 5px;
        color: #E2303A;
        font-style: italic;
        font: Helvetica Neue, 12px, #E2303A;
        border: 1px solid #F2A9AE;
    }

ipv4规则生效

使用json提交数据

表单验证通过后,提交动作默认是使用form本身的提交动作,即指定form的action和method属性:

    method="get" action=""

可以在validate()函数中添加submitHandler参数来指定点击提交后执行的函数,我们可以在该函数中使用$.json来提交数据:

    23 $("#ip_form").validate({
    24     rules: {
    25         ip: {
    26             required: true,
    27             ipv4: true,
    28         },
    29         netmask: {
    30             required: true,
    31             ipv4: true,
    32         },
    33         gateway: {
    34             required: true,
    35             ipv4: true,
    36         },
    37         dns: {
    38             dns: true,
    39         }
    40     },
    41     submitHandler: function(form) {
    42         ip_ok();
    43         $("#ip-conf").modal('hide');
    44     }
    45 });

阅读原文

QEMU 2: 参数解析

#一、使用gdb分析QEMU代码#

使用gdb不仅可以很好地调试代码,也可以利用它来动态地分析代码。使用gdb调试QEMU需要做一些准备工作:

1, 编译QEMU时需要在执行configure脚本时的参数中加入–enable-debug。

2, 从QEMU官方网站上下载一个精简的镜像——linux-0.2.img。linux-0.2.img只有8MB大小,启动后包含一些常用的shell命令,用于QEMU的测试。

$wget http://wiki.qemu.org/download/linux-0.2.img.bz2
$bzip2 -d ./linux-0.2.img.bz2

3, 启动gdb调试QEMU:

gdb --args qemu-system-x86_64 -enable-kvm -m 4096 -smp 4 linux-0.2.img

-smp指定处理器个数。

#二、参数解析用到的数据结构#

QEMU系统模拟的主函数位于vl.c文件,无论是qemu-system-x86_64还是qemu-system-ppc64,都是从vl.c中的main函数开始执行。下面先介绍main函数涉及到的一些数据结构。

###QEMU链表

QEMU的链表在include/qemu/queue.h文件中定义,分为四种类型:

  • 单链表(singly-linked list):单链表适用于大的数据集,并且很少有删除节点或者移动节点的操作,也适用于实现后进先出的队列。
  • 链表(list):即双向链表,除了头节点之外每个节点都会同时指向前一个节点和后一个节点。
  • 简单队列(simple queue):简单队列类似于单链表,只是多了一个指向链表尾的一个表头,插入节点的时候不仅可以像单链表那样将其插入表头或者某节点之后,还可以插入到链表尾。
  • 尾队列(tail queue):类似于简单队列,但节点之间是双向指向的。

这里不一一介绍各种链表的用法,只通过NotifierList的定义来说明QEMU链表(list)的用法。在main函数的开头定义的DisplayState结构体使用到了NotifiereList,NotifierList就用到了链表。

a. 表头及节点的定义

定义表头需要用到QLIST_HEAD,定义如下:

 86 #define QLIST_HEAD(name, type)                                          \
 87 struct name {                                                           \
 88         struct type *lh_first;  /* first element */                     \
 89 }

NotifierList就采用了QLIST_HEAD来定义表头:

 27 typedef struct NotifierList
 28 {
 29     QLIST_HEAD(, Notifier) notifiers;
 30 } NotifierList;

定义节点需要用到QLIST_ENTRY,定义如下:

 94 #define QLIST_ENTRY(type)                                               \
 95 struct {                                                                \
 96         struct type *le_next;   /* next element */                      \
 97         struct type **le_prev;  /* address of previous next element */  \
 98 }

Notifier的节点定义如下:

 21 struct Notifier
 22 {
 23     void (*notify)(Notifier *notifier, void *data);
 24     QLIST_ENTRY(Notifier) node;
 25 };

b. 初始化表头

初始化表头用到QLIST_INIT:

103 #define QLIST_INIT(head) do {                                           \
104         (head)->lh_first = NULL;                                        \
105 } while (/*CONSTCOND*/0)

初始化NotifierList就可以这样进行:

 19 void notifier_list_init(NotifierList *list)
 20 {
 21     QLIST_INIT(&list->notifiers);
 22 }

c. 在表头插入节点

将节点插入到表头使用QLIST_INSERT_HEAD:

122 #define QLIST_INSERT_HEAD(head, elm, field) do {                        \
123         if (((elm)->field.le_next = (head)->lh_first) != NULL)          \
124                 (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
125         (head)->lh_first = (elm);                                       \
126         (elm)->field.le_prev = &(head)->lh_first;                       \
127 } while (/*CONSTCOND*/0)

插入Notifier到NotifierList:

 24 void notifier_list_add(NotifierList *list, Notifier *notifier)
 25 {
 26     QLIST_INSERT_HEAD(&list->notifiers, notifier, node);
 27 }

d. 遍历节点

遍历节点使用QLIST_FOREACH或者QLIST_FOREACH_SAFE,QLIST_FOREACH_SAFE是为了防止遍历过程中删除了节点,从而导致le_next被释放掉,中断了遍历。

147 #define QLIST_FOREACH(var, head, field)                                 \
148         for ((var) = ((head)->lh_first);                                \
149                 (var);                                                  \
150                 (var) = ((var)->field.le_next))
151 
152 #define QLIST_FOREACH_SAFE(var, head, field, next_var)                  \
153         for ((var) = ((head)->lh_first);                                \
154                 (var) && ((next_var) = ((var)->field.le_next), 1);      \
155                 (var) = (next_var))

NotifierList在执行所有的回调函数时就用到了QLIST_FOREACH_SAFE:

 34 void notifier_list_notify(NotifierList *list, void *data)
 35 {
 36     Notifier *notifier, *next;
 37 
 38     QLIST_FOREACH_SAFE(notifier, &list->notifiers, node, next) {
 39         notifier->notify(notifier, data);
 40     }
 41 }

###Error和QError

为了方便的处理错误信息,QEMU定义了Error和QError两个数据结构。Error在qobject/qerror.c中定义:

101 struct Error
102 {
103     char *msg;
104     ErrorClass err_class;
105 };

包含了错误消息字符串和枚举类型的错误类别。错误类别有下面几个:

 139 typedef enum ErrorClass
 140 {
 141     ERROR_CLASS_GENERIC_ERROR = 0,
 142     ERROR_CLASS_COMMAND_NOT_FOUND = 1,
 143     ERROR_CLASS_DEVICE_ENCRYPTED = 2,
 144     ERROR_CLASS_DEVICE_NOT_ACTIVE = 3,
 145     ERROR_CLASS_DEVICE_NOT_FOUND = 4,
 146     ERROR_CLASS_K_V_M_MISSING_CAP = 5,
 147     ERROR_CLASS_MAX = 6,
 148 } ErrorClass;

QEMU在util/error.c中定义了几个函数来对Error进行操作:

error_set  //根据给定的ErrorClass以及格式化字符串来给Error分配空间并赋值
error_set_errno  //除了error_set的功能外,将指定errno的错误信息追加到格式化字符串的后面
error_copy  //复制Error           
error_is_set   //判断Error是否已经分配并设置
error_get_class  //获取Error的ErrorClass      
error_get_pretty  //获取Error的msg
error_free  //释放Error及msg的空间

另外,QEMU定义了QError来处理更为细致的错误信息:

 22 typedef struct QError { 
 23     QObject_HEAD;
 24     Location loc;
 25     char *err_msg;
 26     ErrorClass err_class;
 27 } QError;

QError可以通过一系列的宏来给err_msg及err_class赋值:

 39 #define QERR_ADD_CLIENT_FAILED \
 40     ERROR_CLASS_GENERIC_ERROR, "Could not add client"
 41 
 42 #define QERR_AMBIGUOUS_PATH \
 43     ERROR_CLASS_GENERIC_ERROR, "Path '%s' does not uniquely identify an object"
 44 
 45 #define QERR_BAD_BUS_FOR_DEVICE \
 46     ERROR_CLASS_GENERIC_ERROR, "Device '%s' can't go on a %s bus"
 47 
 48 #define QERR_BASE_NOT_FOUND \
 49     ERROR_CLASS_GENERIC_ERROR, "Base '%s' not found"
...

Location记录了出错的位置,定义如下:

 20 typedef struct Location {
 21     /* all members are private to qemu-error.c */
 22     enum { LOC_NONE, LOC_CMDLINE, LOC_FILE } kind;
 23     int num;
 24     const void *ptr;
 25     struct Location *prev;
 26 } Location;

###GMainLoop

QEMU使用glib中的GMainLoop来实现IO多路复用,关于GMainLoop可以参考博客GMainLoop的实现原理和代码模型。由于GMainLoop并非QEMU本身的代码,本文就不重复赘述。

#三、QEMUOption、QemuOpt及QEMU参数解析

QEMU定义了QEMUOption来表示执行qemu-system-x86_64等命令时用到的选项。在vl.c中定义如下:

2123 typedef struct QEMUOption {
2124     const char *name;   //选项名,如 -device, name的值就是device
2125     int flags;  //标志位,表示选项是否带参数,可以是0,或者HAS_ARG(值为0x0001)
2126     int index;  //枚举类型的值,如-device,该值就是QEMU_OPTION_device
2127     uint32_t arch_mask;  //  选项支持架构的掩码
2128 } QEMUOption;

vl.c中维护了一个QEMUOption数组qemu_options来存储所有可用的选项,并利用qemu-options-wrapper.h和qemu-options.def来给该数组赋值。赋值语句如下:

2130 static const QEMUOption qemu_options[] = {
2131     { "h", 0, QEMU_OPTION_h, QEMU_ARCH_ALL },
2132 #define QEMU_OPTIONS_GENERATE_OPTIONS
2133 #include "qemu-options-wrapper.h"
2134     { NULL },
2135 };

#define QEMU_OPTIONS_GENERATE_OPTIONS选择qemu-options-wrapper.h的操作,qemu-options-wrapper.h可以进行三种操作:

QEMU_OPTIONS_GENERATE_ENUM: 利用qemu-options.def生成一个枚举值列表,就是上面提到的QEMU_OPTION_device等
QEMU_OPTIONS_GENERATE_HELP: 利用qemu-options.def生成帮助信息并输出到标准输出
QEMU_OPTIONS_GENERATE_OPTIONS: 利用qemu-options.def生成一组选项列表

可以通过下面的方法来展开qemu-options-wrapper.h来查看上述操作的结果,以生成选项为例。

  1. 在qemu-options-wrapper.h第一行写入#define QEMU_OPTIONS_GENERATE_OPTIONS.
  2. 执行命令gcc -E -o options.txt qemu-options-wrapper.h
  3. 查看文件options.txt即可

给qemu_options数组赋值后,QEMU就有了一个所有可用选项的集合。之后在vl.c中main函数的一个for循环根据这个集合开始解析命令行。for循环的框架大致如下:

  1     for(;;) {
  2         if (optind >= argc)
  3             break;
  4         if (argv[optind][0] != '-') {
  5         hda_opts = drive_add(IF_DEFAULT, 0, argv[optind++], HD_OPTS);
  6         } else {
  7             const QEMUOption *popt;
  8 
  9             popt = lookup_opt(argc, argv, &optarg, &optind);
 10             if (!(popt->arch_mask & arch_type)) {
 11                 printf("Option %s not supported for this target\n", popt->name);
 12                 exit(1);
 13             }
 14             switch(popt->index) {
 15             case QEMU_OPTION_M:
 16             ......
 17             case QEMU_OPTION_hda:
 18             ......  
 19             case QEMU_OPTION_watchdog:
 20             ......
 21             default:
 22                 os_parse_cmd_args(popt->index, optarg);
 23             }   
 24         }
 25     }

QEMU会把argv中以’-‘开头的字符串当作选项,然后调用lookup_opt函数到qemu_options数组中查找该选项,如果查找到的选项中flags的值是HAS_ARG,lookup_opt也会将参数字符串赋值给optarg。找到选项和参数之后,QEMU便根据选项中的index枚举值来执行不同的分支。

对于一些开关性质的选项,分支执行时仅仅是把相关的标志位赋值而已,如:

3712             case QEMU_OPTION_old_param:
3713                 old_param = 1;
3714                 break;

也有一些选项没有子选项,分支执行时就直接把optarg的值交给相关变量:

3822             case QEMU_OPTION_qtest:
3823                 qtest_chrdev = optarg;
3824                 break;

对于那些拥有子选项的选项,如”-drive if=none,id=DRIVE-ID”,QEMU的处理会更为复杂一些。它会调用qemu_opts_parse来解析子选项,如realtime选项的解析:

3852             case QEMU_OPTION_realtime:
3853                 opts = qemu_opts_parse(qemu_find_opts("realtime"), optarg, 0);
3854                 if (!opts) {
3855                     exit(1);
3856                 }
3857                 configure_realtime(opts);
3858                 break;

对子选项的解析涉及到4个数据结构:QemuOpt, QemuDesc, QemuOpts, QemuOptsList. 它们的关系如下图所示:

Qemu选项数据结构关系图

QemuOpt存储子选项,每个QemuOpt有一个QemuOptDesc来描述该子选项名字、类型、及帮助信息。两个结构体定义如下:

 32 struct QemuOpt {
 33     const char   *name;  //子选项的名字
 34     const char   *str;  //字符串值
 35 
 36     const QemuOptDesc *desc;  
 37     union {
 38         bool boolean;  //布尔值
 39         uint64_t uint;  //数字或者大小
 40     } value; 
 41 
 42     QemuOpts     *opts;  
 43     QTAILQ_ENTRY(QemuOpt) next;
 44 };

 95 typedef struct QemuOptDesc {
 96     const char *name;
 97     enum QemuOptType type;
 98     const char *help;
 99 } QemuOptDesc;

子选项的类型可以是:

 88 enum QemuOptType {
 89     QEMU_OPT_STRING = 0,   // 字符串
 90     QEMU_OPT_BOOL,       // 取值可以是on或者off
 91     QEMU_OPT_NUMBER,     // 数字
 92     QEMU_OPT_SIZE,       // 大小,可以有K, M, G, T等后缀
 93 };

QEMU维护了一个QemuOptsList*的数组,在util/qemu-config.c中定义:

10 static QemuOptsList *vm_config_groups[32];

在main函数中由qemu_add_opts将各种QemuOptsList写入到数组中:

2944     qemu_add_opts(&qemu_drive_opts);
2945     qemu_add_opts(&qemu_chardev_opts);
2946     qemu_add_opts(&qemu_device_opts); 
2947     qemu_add_opts(&qemu_netdev_opts);
2948     qemu_add_opts(&qemu_net_opts);
2949     qemu_add_opts(&qemu_rtc_opts);
2950     qemu_add_opts(&qemu_global_opts);
2951     qemu_add_opts(&qemu_mon_opts);
2952     qemu_add_opts(&qemu_trace_opts);
2953     qemu_add_opts(&qemu_option_rom_opts);
2954     qemu_add_opts(&qemu_machine_opts);
2955     qemu_add_opts(&qemu_smp_opts);
2956     qemu_add_opts(&qemu_boot_opts);
2957     qemu_add_opts(&qemu_sandbox_opts);
2958     qemu_add_opts(&qemu_add_fd_opts);
2959     qemu_add_opts(&qemu_object_opts);
2960     qemu_add_opts(&qemu_tpmdev_opts);
2961     qemu_add_opts(&qemu_realtime_opts);
2962     qemu_add_opts(&qemu_msg_opts);

每个QemuOptsList存储了大选项所支持的所有小选项,如-realtime大选项QemuOptsList的定义:

 507 static QemuOptsList qemu_realtime_opts = {
 508     .name = "realtime",
 509     .head = QTAILQ_HEAD_INITIALIZER(qemu_realtime_opts.head),
 510     .desc = {
 511         {
 512             .name = "mlock",
 513             .type = QEMU_OPT_BOOL,
 514         },
 515         { /* end of list */ }
 516     },
 517 };

-realtime只支持1个子选项,且值为bool类型,即只能是on或者off。

在调用qemu_opts_parse解析子选项之前,QEMU会调用qemu_find_opts(“realtime”),把QemuOptsList *从qemu_add_opts中找出来,和optarg一起传递给qemu_opts_parse去解析。QEMU可能会多次使用同一个大选项来指定多个相同的设备,在这种情况下,需要用id来区分。QemuOpts结构体就表示同一id下所有的子选项,定义如下:

 46 struct QemuOpts {
 47     char *id;
 48     QemuOptsList *list;
 49     Location loc;
 50     QTAILQ_HEAD(QemuOptHead, QemuOpt) head;
 51     QTAILQ_ENTRY(QemuOpts) next;
 52 };

其中list是同一个大选项下不同id的QemuOpts链表。

QEMU 1: 使用QEMU创建虚拟机

一、QEMU简介#

QEMU是一款开源的模拟器及虚拟机监管器(Virtual Machine Monitor, VMM)。QEMU主要提供两种功能给用户使用。一是作为用户态模拟器,利用动态代码翻译机制来执行不同于主机架构的代码。二是作为虚拟机监管器,模拟全系统,利用其他VMM(Xen, KVM, etc)来使用硬件提供的虚拟化支持,创建接近于主机性能的虚拟机。

用户可以通过不同Linux发行版所带有的软件包管理器来安装QEMU。如在Debian系列的发行版上可以使用下面的命令来安装:

sudo apt-get install qemu

或者在红帽系列的发行版上使用如下命令安装:

sudo yum install qemu -y

除此之外,也可以选择从源码安装。

##获取QEMU源码##

可以从QEMU官网上下载QEMU源码的tar包,以命令行下载2.0版本的QEMU为例:

$wget http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2
$tar xjvf qemu-2.0.0.tar.bz2

如果需要参与到QEMU的开发中,最好使用Git获取源码:

$git clone git://git.qemu-project.org/qemu.git

##编译及安装##

获取源码后,可以根据需求来配置和编译QEMU。

$cd qemu-2.0.0 //如果使用的是git下载的源码,执行cd qemu
$./configure --enable-kvm --enable-debug --enable-vnc --enable-werror  --target-list="x86_64-softmmu"
$make -j8
$sudo make install

configure脚本用于生成Makefile,其选项可以用./configure --help查看。这里使用到的选项含义如下:

--enable-kvm:编译KVM模块,使QEMU可以利用KVM来访问硬件提供的虚拟化服务。
--enable-vnc:启用VNC。
--enalbe-werror:编译时,将所有的警告当作错误处理。
--target-list:选择目标机器的架构。默认是将所有的架构都编译,但为了更快的完成编译,指定需要的架构即可。

#二、基本原理#

QEMU作为系统模拟器时,会模拟出一台能够独立运行操作系统的虚拟机。如下图所示,每个虚拟机对应主机(Host)中的一个QEMU进程,而虚拟机的vCPU对应QEMU进程的一个线程。

QEMU结构图

系统虚拟化最主要是虚拟出CPU、内存及I/O设备。虚拟出的CPU称之为vCPU,QEMU为了提升效率,借用KVM、XEN等虚拟化技术,直接利用硬件对虚拟化的支持,在主机上安全地运行虚拟机代码(需要硬件支持)。虚拟机vCPU调用KVM的接口来执行任务的流程如下(代码源自QEMU开发者Stefan的技术博客):

open("/dev/kvm")
ioctl(KVM_CREATE_VM)
ioctl(KVM_CREATE_VCPU)
for (;;) {
     ioctl(KVM_RUN)
     switch (exit_reason) {
     case KVM_EXIT_IO:  /* ... */
     case KVM_EXIT_HLT: /* ... */
     }
}

QEMU发起ioctrl来调用KVM接口,KVM则利用硬件扩展直接将虚拟机代码运行于主机之上,一旦vCPU需要操作设备寄存器,vCPU将会停止并退回到QEMU,QEMU去模拟出操作结果。

虚拟机内存会被映射到QEMU的进程地址空间,在启动时分配。在虚拟机看来,QEMU所分配的主机上的虚拟地址空间为虚拟机的物理地址空间。

QEMU在主机用户态模拟虚拟机的硬件设备,vCPU对硬件的操作结果会在用户态进行模拟,如虚拟机需要将数据写入硬盘,实际结果是将数据写入到了主机中的一个镜像文件中。

#三、创建及使用虚拟机#

##命令行创建及启动虚拟机##

成功安装QEMU之后便可创建自己的虚拟机。具体步骤如下:

1, 使用qemu-img创建虚拟机镜像。虚拟机镜像用来模拟虚拟机的硬盘,在启动虚拟机之前需要创建镜像文件。

[kelvin@kelvin tmp]$ qemu-img create -f qcow2 fedora.img 10G
Formatting 'fedora.img', fmt=qcow2 size=10737418240 encryption=off cluster_size=65536 lazy_refcounts=off 
[kelvin@kelvin tmp]$ ls
fedora.img

-f选项用于指定镜像的格式,qcow2格式是QEMU最常用的镜像格式,采用写时复制技术来优化性能。fedora.img是镜像文件的名字,10G是镜像文件大小。镜像文件创建完成后,可使用qemu-system-x86来启动x86架构的虚拟机:

qemu-system-x86_64 fedora.img

此时会弹出一个窗口来作为虚拟机的显示器,显示内容如下:

QEMU虚拟机显示器输出

因为fedora.img中并未给虚拟机安装操作系统,所以会提示“No bootable device”,无可启动设备。

2, 准备操作系统镜像。

可以从不同Linux发行版的官方网站上获取安装镜像,以fedora20为例:

[kelvin@kelvin tmp]$ wget http://ftp6.sjtu.edu.cn/fedora/linux/releases/20/Live/x86_64/Fedora-Live-Desktop-x86_64-20-1.iso

3, 检查KVM是否可用。

QEMU使用KVM来提升虚拟机性能,如果不启用KVM会导致性能损失。要使用KVM,首先要检查硬件是否有虚拟化支持:

[kelvin@kelvin ~]$ grep -E 'vmx|svm' /proc/cpuinfo

如果有输出则表示硬件有虚拟化支持。其次要检查kvm模块是否已经加载:

[kelvin@kelvin ~]$ lsmod | grep kvm
kvm_intel             142999  0 
kvm                   444314  1 kvm_intel

如果kvm_intel/kvm_amd、kvm模块被显示出来,则kvm模块已经加载。最后要确保qemu在编译的时候使能了KVM,即在执行configure脚本的时候加入了–enable-kvm选项。

4, 启动虚拟机安装操作系统。

执行下面的命令启动带有cdrom的虚拟机:

[kelvin@kelvin tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img -cdrom ./Fedora-Live-Desktop-x86_64-20-1.iso

-m 指定虚拟机内存大小,默认单位是MB, -enable-kvm使用KVM进行加速,-cdrom添加fedora的安装镜像。可在弹出的窗口中操作虚拟机,安装操作系统,安装完成后重起虚拟机便会从硬盘(fedora.img)启动。之后再启动虚拟机只需要执行:

[kelvin@kelvin tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img

即可。

##图形界面创建及启动虚拟机##

命令行启动虚拟机比较繁琐,适合开发者,但对于普通用户来说,采用图形界面管理虚拟机则更为方便。采用图形界面管理QEMU虚拟机需要安装virt-manager,红帽系列的发行版只需要执行命令:

$sudo yum install virt-manager -y

安装完成后用root用户启动virt-manager:

$su -
#virt-manager

启动后的界面如下图所示:

virt-manager界面

点击左上角电脑图标即可创建虚拟机。按照步骤操作即可完成对虚拟机的创建。

Linux shell 程序设计5——shell中一些特殊符号的用法总结

1、{} 大括号:

eg: ls my_{finger,toe}s

这条命令相当于如下两个命令的组合:

ls my_fingers ; ls my_toes

eg: mkdir {userA,userB,userC}-{home,bin,data}

我们得到 userA-home, userA-bin, userA-data, userB-home, userB-bin, userB-data, userC-home, userC-bin, userC-data,这几个目录

大括号也可用于语句块的构造,命令之间可用回车隔开

2、[] 中括号:允许匹配方括号中任何一个单个字符

eg: ls /[eh][to][cm]*

相当于执行 ls /etc 和 ls /home。

常出现在流程控制中,其作用是括住判断式。注意:[、] 与表达式之间有空格。

eg:

if [ "$?" != 0 ]
then echo "Executes error"
fi

3、command 反引号:反引号中的指令将会被执行

eg: fdv=date +%F

在倒引号内的 date +%F 会被视为指令,执行的结果会带入 fdv 变量中

4、’string’ 单引号 和 “string” 双引号:如果想在定义的变量中加入空格,就必须使用单引号或双引号,单、双引号的区别在于双引号转义特殊字符,而单引号不转义特殊字符。

  eg: heyyou=home
      echo '$heyyou'
      # We get $heyyou
  eg: heyyou=home
      echo "$heyyou"
      # We get home

5、$#:它的作用是告诉你引用变量的总数量是多少

6、$$:它的作用是告诉你shell脚本的进程号

7、$1、$2、$3……${10}、${11}、${12}…… :表示脚本的各个参数

8、$@:列出所有的参数,各参数用空格隔开

9、AND列表 statement1 && statement2 && statement3 && ……:只有在前面所有的命令都执行成功的情况下才执行后一条命令

10、OR列表 statement1 || statement2 || statement3 || ……:允许执行一系列命令直到有一条命令成功为止,其后所有命令将不再被执行。

11、[ condition ] && command for true || command for false:当条件为真时,执行command for true ,当条件为假时,执行command for false

12、: 冒号:内建指令,但返回值为0

  eg: :
      echo $?
      #We get 0
while:实现一个无限循环

13、; 分号:在 shell 中,担任”连续指令”功能的符号就是”分号”

eg:cd ~/backup ; mkdir startup ; cp ~/.* startup/.

14、~:代表使用者的 home 目录

15、# 井号:表示符号后面的是注解文字,不会被执行

16、\ 倒斜线:放在指令前,有取消 aliases 的作用;放在特殊符号前,则该特殊符号的作用消失;放在指令的最末端,表示指令连接下一行

17、! 惊叹号:通常它代表反逻辑的作用,譬如条件侦测中,用 != 来代表”不等于”

18、* 星号:在文件名扩展上,她用来代表任何字元

19、** 次方运算:两个星号在运算时代表 “次方” 的意思

  eg:let "sus=2**3"
     echo "sus = $sus"
     # sus = 8

Linux shell 程序设计4——shell变量

1、shell变量没有类型,所有变量都被当作字符串来处理。

2、shell变量的命名和c语言相同。

3、shell变量赋值和c语言略有不同,shell赋值要求等号的两边不能出现空格,而在linux C 中,一般为了增强代码的可读性,等号的两边都加一个空格。如果shell变量的赋值为字符串,而且字符串中含有空格,则必须给该字符串加单引号或双引号。

4、shell变量不同于c语言,无需定义可直接赋值使用。例如:

#!/bin/bash
#This is an example to show how to use variables

version="2.6.24"
name="linux-headers-2.6.24"

echo -e "name:$name\nversion:$version"

执行结果:

name:linux-headers-2.6.24
version:2.6.24

5、shell变量的作用范围是本shell环境。例如:

我们编写如下脚本:

#!/bin/bash
#script name: exam.sh
#This is a example to show the action range of a variable

os_name=linux
echo $os_name

当我们使用./exam.sh执行脚本结果为:

linux

然后我们在命令行中执行:

echo $os_name 结果为空

而如果使用 source exam.sh 执行脚本或者是 .空格exam.sh命令执行脚本后键入echo $os_name 命令,我们会得到:

linux

6、有一种能继承给子shell的变量,称之为环境变量。让一个变量变身为环境变量的方法为:

export 变量名

例如:在终端中我们敲入如下命令:

执行脚本:

#!/bin/bash
echo $a

我们什么也不能得到。而如果在终端中使用命令:

export a=linux

然后执行上述脚本,我们的到结果:

linux

7、shell内置变量:bash设置了许多内置变量,在进行shell程序设计的时候可能需要用到。详见:

$?:最后一次执行的命令的返回码
$$:shell进程自己的PID
$!:shell进程最近启动的后台进程PID
$#:shell脚本参数个数,不含脚本名
$0:脚本文件本身的名字
$1、$2...:第一个参数、第二个参数...
$*:代表所有的参数(不含脚本名)组成的字符串
$@:命令行参数组成的多个字符串,每个参数对应一个

8、设置shell变量属性:

readonly:使用readonly命令可以

"