制作openstack windows xp镜像

下载官方原版iso镜像:http://pan.baidu.com/s/1c0owVc8

下载virtio驱动:http://alt.fedoraproject.org/pub/alt/virtio-win/archives/virtio-win-0.1-59/virtio-win-0.1-59.iso

创建一个虚拟qcow2盘:

kvm-img create -f qcow2 xp.qcow2 10G

安装xp到创建的虚拟盘中,有些教程说要加载软驱 virtio-win-xx.vfd和virtio驱动,实际上xp不需要,稍后我们再装!

kvm -m 1024 -cdrom xp.iso -drive file=xp.qcow2 -fda  -boot d

安装好系统后,我们进入系统,并且安装virtio驱动,

kvm -hda xp.qcow2 \
    -drive file=xp.qcow2,if=virtio \
    -drive file=virtio-win-0.1-30.iso,media=cdrom,index=1 \
    -net nic,model=virtio \
    -net user \
    -boot d \
    -vga std \
    -m 1024

进入xp系统,点击我的电脑->管理->设备管理,更新scsi和网卡驱动,注意scsi驱动必须安装,否则进入后会出现蓝屏。

驱动安装后,就可以上传到openstack中了

glance image-create --name xp --container-format=ovf --disk-format=qcow2 -file xp.qcow2 --progress

创建虚拟机时,根磁盘要大于创建虚拟磁盘的大小,临时磁盘对应一块新的未格式化的虚拟硬盘,swap不需要,创建成功后,进入系统。

此时c盘大小和创建虚拟盘大小一样,如果分配的磁盘大于虚拟盘大小,比如我们虚拟盘大小的10G,创建云主机时指定根磁盘大小为20G,此时需要使用磁盘扩展工具扩展c盘大小,http://pan.baidu.com/s/1eQh7q9c,如果有临时磁盘,则需要使用磁盘管理工具初始化磁盘,即我的电脑->管理->磁盘管理,然后格式化。

挂载新的云硬盘如果未被初始化,也需要进行磁盘初始化和格式化。

如果我们需要远程登陆,则还需要开启远程桌面功能,我一般还会关掉防火墙。

这是我制作的镜像,可以直接使用:http://pan.baidu.com/s/1pJEyVGZ

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

ssh登录慢问题

    有时使用ssh登录远程主机,一直卡着不动,等待很久才跳出输入密码提示。

    如果不是网络原因(可以ping下网络是否畅通),可能是由于DNS反向解析问题,可以修改远程主机ssh服务器配置文件/etc/ssh/sshd_config文件,设置UseDNS 为no,重启ssh服务器。

   如果debug出现Cannot determine realm for numeric host address而卡住,则修改/etc/ssh/ssh_config(注意不是sshd_config) 为GSSAPIAuthentication no

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

挂载raw和qcow2格式的KVM硬盘镜像

from:http://lazyhack.net/mount-raw-and-qcow2-kvm-disk-images/
raw格式
对于未分区镜像文件直接使用loop:
mount -o loop image.img /mnt/image
已分区的镜像文件:
如果已知分区的起始位置
mount -o loop,offset=32256 image.img /mnt/image
或者使用losetup + kpartx
losetup /dev/loop0 image.img
kpartx -a /dev/loop0
mount /dev/mapper/loop0p1 /mnt/image
kpartx命令的作用,是让Linux内核读取一个设备上的分区表,然后生成代表相应分区的设备。
kpartx -l imagefile 可以查看一个映像文件中的分区,使用 kpartx -a imagefile 命令后,就可以通过 /dev/mapper/loop0pX (其中X是 分区号)来访问映像。
 
qcow2格式
对于qcow2格式需要使用qemu-nbd这个工具
modprobe nbd max_part=63
qemu-nbd -c /dev/nbd0 image.img
mount /dev/nbd0p1 /mnt/image
如果是LVM格式的镜像:
vgscan
vgchange -ay
mount /dev/VolGroupName/LogVolName /mnt/image
最后使用结束需释放资源:
umount /mnt/image
vgchange -an VolGroupName
killall qemu-nbd
kpartx -d /dev/loop0
losetup -d /dev/loop0
转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

java中各种map

java中有各种类型的map,每种类型的map实现方式不同,但接口都类似。所有的具体map类型必须实现Map接口或者其子类,AbstractMap实现了Map接口,并实现了Map接口的部分功能,因此一般具体Map类型只需继承AbstractMap就可以了。

1. HashMap

HashMap的实现方式是一个table数组,数组元素是一个Entry类型(java8 中是Node类型,Entry变成了一个接口),Entry是一个内部类,类似c++的pair类型,b实际保存key 和value值,当put一个值,即调用put时,先计算key的hash值,通过这个hash值找到对应的table数组索引,如果hash冲突,采用的是拉链法解决冲突,即把所有同义词(hash值相等)存储在一个单链表中,因此Entry可以看作是一个单链表,next指向下一个同义词。一个好的hash应该尽量减少同义词,因为当出现hash冲突时,在同义词中查找是线性查找,效率很低。put方法代码为(java 7):

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null) /* null的话直接存放在table[0] */
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length); /*找到table索引 */
        for (Entry<K,V> e = table[i]; e != null; e = e.next) { /* 查找是否已经存在相同的key */
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { /* 如果hash冲突且key一样,则返回旧值,替换成新值 */
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);  //插入新的Entry
        return null; /* 没有替换,则返回null */
    }

从源码中可以看出HashMap允许null作为key值,相当于null的hash为0。当key相同时,则覆盖原来的值,并返回旧的value。判断key是否相同是调用key的equals,因此如果使用自己的类作为key,必须重载equals方法,否则引用相同时才相等。求hash值并不是直接使用key的hashCode方法,而是自己内部实现的hash方法,hash方法会调用key的hashCode方法,这样能够减少hash冲突,且保证hash值能在指定返回内,而hashCode返回的值有可能超出table的索引范围。因此自定义类要作为key还必须覆盖hashCode方法,尽量返回一个素数。

2. IdentityHashMap

上面说了HashMap的key值是否相同是调用equals方法,而IdentityHashMap原理类似,但判断key是否相同运用 == 运算符,即判断是否同一个引用,实现上也有些不同,table不再是Entry,而是一个Object对象数组(不会存在hash冲突)。put时先计算key的hash值,通过hash值找到table数组的索引i,然后table[i]存储key,table[i + 1]存储value,代码为:

  public V put(K key, V value) {
        Object k = maskNull(key);
        Object[] tab = table;
        int len = tab.length;
        int i = hash(k, len);

        Object item;
        while ( (item = tab[i]) != null) {
            if (item == k) {
                V oldValue = (V) tab[i + 1];
                tab[i + 1] = value;
                return oldValue;
            }
            i = nextKeyIndex(i, len);
        }

        modCount++;
        tab[i] = k;
        tab[i + 1] = value;
        if (++size >= threshold)
            resize(len); // len == 2 * current capacity.
        return null;
    }

因此IdentityHashMap用于存储key值重复的map。

3. Hashtable

Hashtable和HashMap类似,不过put方法是同步的,使用synchronized锁住整个方法,支持并发控制,但现在已经废弃了,因为效率太低了,取而代之的是ConcurrentHashMap。

4. ConcurrentHashMap

取代Hashtable,和Hashtable不同,它是使用段加锁的方法实现并发,而不是同步整个方法。和HashMap不同,HashMap所有的Entry保存在一个table数组中,而ConcurrentHashMap是保存在不同的Segment(段)中,通过hash值先定位到Segment(其实也是一个Segment数组),然后只需对这个Segment(继承了ReentrantLock)加锁就可以了,不影响其他Segment。

5.LinkedHashMap

LinkedHashMap是HashMap的子类,因此具有HashMap的所有特点。与HashMap不同的是,HashMap不记录顺序,迭代时不保证任何顺序,而LinkedHashMap Entry使用了一个双链表记录顺序,并且Entry多了before 和after指针,迭代默认根据插入的顺序,可以设置成按最近访问顺序,put效率会比HashMap低,毕竟会有记录顺序的开销,但迭代速度会更快,直接遍历整个双向链表即可。

6. EnumMap

EnumMap使用java Enum类型作为key,相对简单且高效,内部还是使用了table,table的值是Object类型,put时就是根据key的值(原始值,一个整型数)作为索引,直接插入,table的长度就是key的枚举个数,其put代码为:

 public V put(K key, V value) {
        typeCheck(key);

        int index = key.ordinal();
        Object oldValue = vals[index];
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
    }

首先是类型检查,判断key是否是初始化的枚举类型(构造方法必须指定枚举类型的类类型),如果value是null则映射成内部的NULL(也是一个Object对象)。由此可见EnumMap是非常高效的,插入删除访问都是O(1),不存在冲突问题。

7. TreeMap

TreeMap和之前的Map类型使用的数据结构不一样,它实现的是SortedMap,即按照key值自然顺序排列的,内部使用的数据结构是红黑树,红黑树是一种类平衡二叉树(和传统严格的平衡树有点差别),put源码为:

 public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

首先判断根是否空,不为空则遍历红黑树,看是否已经存在key,存在则直接覆盖原来的值并返回旧值,否则插入新数据,并调整红黑树。

8. ConcurrentSkipListMap

ConcurrentSkipListMap也是默认根据key值排序的,并且支持高并发访问(比ConcurrentHashMap并发性能更好,存取速度相对比ConcurrentHashMap低)。不过实现并不是使用红黑树,而是跳表,跳表的性能和查找树性能相当。

题外话:

以上介绍了8中map,如何选择取决于实际应用,如果不关心顺序且是单线程操作,使用HashMap,如果需要key按顺序排列,则使用TreeMap, 如果需要关心插入顺序或者最近访问顺序,使用LinkedHashMap。当涉及并发访问时,不关心key顺序使用ConcurrentHashMap,高并发且关心key顺序,使用ConcurrentSkipListMap,尽量不要使用Hashtable。如果key是枚举类型,则使用EnumMap。另外几乎每个Map类型都有相对应的Set版本,Set一般就是继承自Map,Set的元素值作为Map的key,而Map的value默认为一个Object对象常量,当然其实Map的value是什么都无所谓。

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

linux 磁盘管理工具

总结下一些linux常用的磁盘工具,具体用法可以google之。

1. fdisk 磁盘分区交互式管理工具,我用的最多的命令。

2.gdisk 和fdisk类似,不过对GPT支持,有时使用fdisk修改分区后,出现GPT 签名问题,可以使用fixparts移除之。

3.parted 、partx 同样分区管理工具,不过我很少用,主要是习惯fdisk了。

4.sfdisk同样是分区管理工具,用过一次是分区表的导出(dump)和导入(直接重定向)。

5.testdisk 修复分区工具,可以找回遗失的分区。 photorec是文件恢复工具,可以找回一些误删的文件。

6.lsblk查看block设备,默认不加参数,列举硬盘以及分区、大小。blkid 比较少用,原因是使用lsblk加上-o参数可以达到类似效果。

7.mkfs.XXX创建文件系统,比如ext4,btrfs等。

8.lvm工具。比如pvcreate lvdisplay等。

9.resize2fs 修改extX文件系统分区大小,比较常用的是通过lvm修改了某个lv的大小,但extX文件系统并不能觉察,需要使用resize2fs命令。

10,partprobe 当修改分区表后,不需要重启电脑,运行partprobe通知系统检测新的分区表变化。

11 dd 这个命令很强大,可以克隆整个分区,写入数据到分区中。有时我们把文件删掉后,不安全,系统有可能仅仅移除了inode,而文件内容并没有移除,这时可以使用dd命令写入0或者随机值。

12 df 查看已挂载分区的使用情况以及挂载点、文件系统(-T参数)。

13 dosfslabel,e2label ,如果习惯windows,习惯在分区表设置标签,e2lable可以对extX文件系统分区设置或者修改标签。

14 findfs 根据uuid或者标签查找文件系统,基本没有用过,直接lsblk加上grep。

后续继续补充……

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

使用java反射机制和json创建对象

java反射机制可以获取一个对象的所有属性以及其值,甚至可以改变其值,包括私有属性。利用这个特性可以使用json创建一个对象,尤其是java bean,非常方便。比如有一个对象有属性:id, name,有一个json对象{"id":"1000", "name" : "test"},通过反射机制可以很随意的创建一个和json对象值一样的对象,实现json对象和java对象转化。

首先需要获取一个对象的所有属性,注意到java Class对象方法getFields方法返回的是所有公共属性,包括父类和接口的属性,而getDeclaredFields()方法获取的是所有属性,包括私有属性,但不包含父类的属性。而要获取所有的包括父类的属性,没有现成的接口,于是自己写了个简单的,可能有问题,仅用于测试。

 

public  static Set<Field> getFields(Object obj) {
		Set<Field> fields = new HashSet<Field>();
		Class<? extends Object> cl = obj.getClass();
		String objName = Object.class.getName();
		while (!cl.getName().equals(objName)) {
			fields.addAll(Arrays.asList(cl.getDeclaredFields()));
			cl = cl.getSuperclass();
		}
		return fields;
	}

获取了所有的属性后,就可以通过属性设置新的值,代码为:

public static boolean setValue(Object obj, Field field, Object value) {
		field.setAccessible(true);
		try {
			field.set(obj, value);
		} catch (IllegalArgumentException | IllegalAccessException e) {
			// fixed here
		}
		return true;
	}

于是整个通过json新建对象代码为(这里使用了org.json包):

public Entity(JSONObject entity) {
		Set<Field> fields = Reflect.getFields(this);
		for (Field field : fields) {
			String _field = field.getName();
			if (entity.has(_field)) {
				Reflect.setValue(this, field, entity.get(_field));
			}
		}
	}

通过遍历所有的属性以及Json对象中存在的键值,设置对象的值。这主要在初始化java bean时非常有用。

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

linux中常用的网络工具

linux网络工具非常强大,下面列举下几个常用的网络命令。本文只是列举一些常用的用法,并不对每个命令作详细讲解,请自行google之。

1.ping 这个大家都不陌生了,我们平时使用这个工具查看两个主机是否想通。默认是一直发送直到用户终止(Ctrl + c),这在nova 实例下网页上使用vnc控制时,由于不支持Ctrl,发送ping后不能终止,只能ssh到虚拟机上kill掉,自此我一般都会加上-c参数,指定发送的包数量。

2. ifconfig 这个命令用于查看网络接口信息,修改参数、启动网络接口等,比如查看eth0 信息,ifconfig eth0 ,可以查看eth0的封装类型,mac地址、ip地址、流量等信息。

3.dhclient 如果你是通过dhch获取ip的,有时可能开机后没有获取到ip,通过这个命令可以从dhclient server上分配一个ip地址,比如dhclient -4 -v eth0,-4代表ipv4。这个命令必须以root身份执行。

4.curl是非常强大的http命令行工具,能够发送各种http请求,发送参数。

5.wget 一般用于下载文件,当然curl同样可以实现。通过wget可以爬取获得整个网站的镜像。

6. axel 多线程下载,通过指定-n  numbers 设置线程数,加速下载,一般会比wget快。

7.route 用于查看设置本地路由表。

8. ip 这个命令非常强大,几乎可以取代ifconfig、route。

9.traceroute  ping命令一般只能判断两个主机是否想通,但无法检测中间过程的状态。而traceroute会对每一跳发送一个包,获取RTT,据此当网速慢时,或者网络出问题时,可以通过该命令判断是哪个节点坏掉了。

10 host 想知道google的ip地址?用人说直接ping一下不就知道了,这也是一种方法,但并不完备。有些域名不是指向一个ip,使用host命令就可以获取获取的ip列表。

11 nslookup 同样用于把域名转化为ip地址。

12.netstat 查看开启了哪些服务,这些服务使用了哪些端口,某个端口被谁占用了,哪些端口处于监听状态,当前建立了哪些tcp连接。netstate可以说是非常强大的工具,比如我想知道mysql服务器开的哪个端口:sudo netstat -n -l -t  -p | grep "mysql" ,注意有些服务是属于root的,需要root身份读取,所以需要用root身份执行netstat,否则可能获取的信息不全。

13. lsof 有人说lsof不是查看某个文件被谁打开了吗,这也属于网络工具?ofcource! 其实这个工具几乎可以取代ps 和netstat。比如我想知道3306这个端口被谁占用了,使用sudo lsof -i:3306 查看。

注意,使用netstat和lsof等命令时,有时很慢,这时由于试图通过dns获取主机域名,如果加上-n参数,直接返回ip,不作域名转化,会快很多。 

14 mtr 这个工具基本就是ping和traceroute的集成。

15. nc(netcat),网络中的瑞士军刀。想知道1-9999端口哪个开启了,sudo nc -z -v localhost 1-9999 |& grep -v -i "Connection refused" (注意管道后面的&符号,一般的管道指把标准输出流导入管道,加入&后会把标准错误流导入管道中)。这个命令非常强大,可以实现各种功能,比如扫描端口、聊天工具、开启临时监听服务等。

16 ss 另一个查看socket的工具,类似netstat。

17 rfkill 管理无线设备工具。

18.iptable 设置查看防火墙、nat表等。

19. ifstat 实时查看网络流量信息。

20. tcpdump 抓包工具。

21 ncmap 端口扫描工具、网络主机发现、网络安全审计工具。用于列举网络主机清单、监控主机、服务运行状况等。查看当前主机哪些端口开启了sudo nmap localhost

22 ftp ncftp lftp ssh scp telnet 。

后期会补充更多!

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

tmux使用系统复制粘贴

tmux复制粘贴时使用的是tmux内置的buffer,可是有时需要用到系统复制粘贴,比如有时需要复制错误信息到chrome搜索,似乎就比较麻烦了,因为tmux buffer和系统clipboard是独立的,不能像我们平时习惯的那样,使用鼠标右键复制粘贴功能。而且也不支持鼠标选取右击选择复制粘贴。

有人使用y键绑定了快捷方式实现从tmux buffer中拷贝数据到系统clipborad,如下:

 

bind y run-shell "tmux show-buffer | xclip -sel clip -i" \; display-message "Copied tmux buffer to system clipboard"

这样就能使用prefix + y键实现从tmux buffer 中提取top数据(最后的buffer)拷贝到系统中。

另外一种方式是按住shift键,然后用鼠标选择,就能恢复系统原来的复制粘贴选项了。

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

bash 数组的使用

bash数组不是什么新的东西,网上的资料也很多,我再写,似乎有点多余。但网上都讲的不太清楚,尤其是* @ 和引号之间的区别。

下面先从数组的基本操作开始:

初始化数组, a=(1 2 3 4),这个用多了c语言的尤其容易出错,往往=左右加上空格,( ) 写成{ },中间元素加上多余的逗号等,这都会造成错误,而错误不是语法错误,而是结果不是你所期待的,这才是最危险的!比如写出a=(1,2,3,4),语法没有错误,但结果是一个只有一个元素的数组,元素为"1,2,3,4",a={1 2 3 4 5}则会出现语法错误,解释器会把{1当作一条命令, 数组初始化也可以指定索引,并且可以逻辑不连续存储,即可以这样a=([2]=2 [5]=5),此时a[2]=2, a[5]=5,但a[0],a[1],...等均为空,并且a数组的长度为2而不是6,原因下面讲!

访问某一个元素的值是${array[n]}(前面有美元符号,并且array[n]有花括号括起来,这里不知道为什么不显示)其中array为数组名,n为索引。比如echo ${a[2]}表示输出a数组第3个元素的值。

得到一个数组后就要说数组的长度了,由于bash数组不是静态分配的,具体占多少内存不知道,你完全可以不进行任何声明和初始化,直接输出a[100],没有任何语法错误,只不过它的值为空而已,可以理解成值为“”(注意不是真的是“”,只是好理解而已),而长度仅仅是计算不为空元素的个数,比如a=([2]=2 [5]=5),展开为 "" "" 2 "" "" 5 "" "" .. ,其中""表示空,则这个数组长度为2,而不是5也不是6. 输出数组长度的关键符号是#,比如获取a数组的长度,语法是${#a}吗,错误。。。这个又需要了解两个符号* 和@。

先讲讲#的原理,#可以看作是一个函数,这个函数输出它后面参数并以IFS定义为分隔符的个数,比如 # 1 2 3 4  5, 返回5(IFS默认为空白符),而#  "1 2" "3 4 5" 6返回3,引号内为一个整体,请注意,这样只是为了好理解,并不是说bash真的有这样的语法!

*的作用是把一个数组的所有元素当做一个整体返回,比如 * 1 2 3 4 5 ,返回 "1 2 3 4 5",注意""只表示这是一个整体,并不是真的会有引号,但确实是当做一个字符串处理的。

@的作用是把数组的所有元素逐一返回, 比如 @ 1 2 3 4 5 ,返回 "1" "2" "3" "4" "5",同样注意""只是记号!

好了,假设a={1 2 3 4 5}

,则${#a}输出什么呢?答案是1,为什么呢?注意a后面没有索引符号[ ],则默认为输出a的值,而注意,bash中a和a[0]的值完全等价,即

b=([0]=2),此时$b为2,而c=5,echo ${c[0])也为5,这个学过c应该容易理解.

因此${#a}相当于${#a[0]},展开为# 1 返回1,相当于求a[0]元素的长度。而要求整个数组长度,应该使用* 和 @,

即${#a[*]}, 展开为 # 1 2 3 4 5 ,结果为5,当然使用@返回结果也是一样的。

下面讲讲系统内置数组,即位置参数,shell中相当于传递的命令行参数,而函数,则相当于传递的参数。这个数组是匿名的,即没有名字,访问第3个元素,则${3},[ ]也要省略, 或者直接$3,求长度为${#},简写$#,其他$@ 和 $* 同理。当然对于这个位置参数,系统提供了更丰富的操作,比如shift等。

如果一个元素使用引号引起来,则相当于一个元素,即a={"1 2 3 4 5"},它的长度为1.

接下来看切片,学过python就不会陌生了,这个和python类似,${a[@]:1}表示返回索引1(包括1)开始以后的所有元素,${a[@]:1:2}表示从索引1开始,包括1后面的2个元素,a={1 2 3 4 5}, ${a[@]:1:2}返回2 3。

元素替换,这个和vim的操作类似,比如要把a的所有元素中的2替换成3,则${a[@]/2/3}.

删除元素,使用unset,比如删除第2个元素则unset a[1],注意删除元素但并不会移动元素,只是简单的把该索引的元素置空,原来的元素位置不变。

好了,数组的基本语法基本讲完了,下面看看以下代码:

#!/bin/bash
a=(1 2 3 4 5)
b=("1 2" "3 4" 5)
echo "le a is ${#a[*]}" # 5
echo "len a is ${#a[@]}" # 5
echo "len b is ${#b[*]}" # 3
echo "len b is ${#b[@]}" # 3
c=(${b[*]})
d=(${b[*]})
echo "len c is ${#c[*]}" #5
echo "len d is ${#d[*]}" #5
e=("${b[*]}")
f=("${b[*]}")
echo "len e is ${#e[*]}" #1
echo "len f is ${#f[*]}" #1

g=(${b[@]})
h=(${b[@]})
echo "len g is ${#g[*]}" #5
echo "len h is ${#h[*]}" #5
i=("${b[@]}")
j=("${b[@]}")
echo "len i is ${#i[*]}" #3
echo "len j is ${#j[*]}" #3

 

 

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

c/c++ 宏

c语言中宏还是挺重要的东西,c++中由于有了内联函数和模板,而宏编译器不作任何宏参数类型检查,又极易容易出错,宏慢慢会被取代。

我们可能用的最多的就是利用宏定义常量,但我们经常被建议不要这样做,而应该使用const定义常量,因为const常量有类型,编译器会做更多的检查而减少错误。

我们也经常定义类似函数的宏,最常见的如

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SQUARE(a) ((a) * (a))

你可能以为SQUARE和函数一样,其实不然,比如int i = 5; SQUARE(++i); 我们期望的结果是36, 可结果是49,原因在于宏是会展开的,利用gcc -E 可以查看展开后的结果, ((++i) * (++i)) , 因此是7 × 7 == 49.

用的比较少的是#符号,它的作用是把参数变成字符串,比如

#define STR(s) #s

简单的理解就是#把后面的参数直接加上引号,因此如果是printf("%s\n", STR(5 * 5) ) 不是"25" 而是"5 * 5"。

还有一个##, 它的作用是连接两个参数,即拼凑起来,注意不是字符串连接,而是符号拼凑,比如

#define CAT(s1, s2) s1##s2

CAT(123, 456) 展开就是123456,而CAT("123", "456")得不到"123456", 编译不过去的!

宏还有与函数不同的是,不支持递归!即

#define TEST(a) ((TEST(a) + a))

TEST(5) 展开为((TEST(5) + 5))。

系统一些预定义宏也挺有用的,比如

printf("%s %s %d\n", __FILE__, __func__, __LINE__);

使用gcc -E 会展开为 printf("%s %s %d\n", "tmp.c", __func__, 5);tmp.c是源代码文件名,5是当前行号,__func__没有展开,而运行的时候会被替换成当前函数名,即输出tmp.c main 5

还有一个系统宏是 __VA_ARGS__,这个是变参列表,会自动替换成参数列表,比如

#define ERROR(format, ...) fprintf(stderr, format, __VA_ARGS__)

ERROR("errno = %d: %s\n", 404, "Not Found!");展开为fprintf(stderr, "%d:%s\n", 404, "Not Found!");

这些系统宏主要用于调试。

 

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!