LaTeX 中文排版上手随记
最近更新于: 2008-07-19 23:37:55 +0800
PDF引擎对比
首先, 来对比一下几种PDF生成方式. 以此参考做出选择.
| XeTeX | DVIPDFMx1 | pdfTeX | |
| 支持的字体类型 | OpenType (部分) TrueType Type 12 |
OpenType (部分) TrueType Type 1 |
OpenType (未知) TrueType (不支持ttc3) Type 1 |
| 使用前需要配置的字体类型 | 仅 Type 1 | 全部 | 全部 |
| 对传统TeX的支持 | 可能不成熟4 | ||
| 中文特殊排版 | 可能不完善5 | ||
| 需要CJK宏包支持中文6 | 不必需 | 是 | 是 |
| PDF文本搜索和复制(GBK7) | N/A | 支持 | 不支持 |
| PDF中文书签(GBK7) | N/A | 需要编码转换8 | 不支持 |
| PDF文本搜索和复制(UTF89) | 支持 | 支持 | 支持 |
| PDF中文书签(UTF89) | 支持 | 支持 | 支持 |
| 配置过程 | 简单 | 较复杂 | 极复杂 |
| 使用过程 | 简单 | 麻烦6 | 麻烦6 |
| 生成文件大小 | 小 | 小 | 大 |
1 严格地说, 应该是LaTeX+DVIPDFMx.
2 实际上是通过xdvipdfmx实现的.
3 可以使用breakttc工具把ttc变成ttf.
4 如endash, emdash等, 还有可能的宏包兼容性问题.
5 xeCJK宏包能实现如下中文排版处理: 空格, 标点, 字体.
6 CJK有一个很大的不便之处就是: 要在波浪线 ~ 和空格之间做痛苦的选择.
7 即使用 {CJK}{GBK}{font} 环境.
8 需要一条控制语句和CMaps, 参考
[1] Ubuntu下用dvipdfmx生成可以复制的中文和中文bookmarks
[2] Adobe Font and Type Technology Center
9 对于XeTeX, hyperref 宏包打开 xetex 选项; 对于另外两种方式, 使用CJKutf8宏包, 并使用 {CJK}{UTF8}{font} 环境, hyperref 要打开 unicode 选项.
经过这样的对比不难看出, 选择UTF-8比选择GBK得到的好处要多了, 这当然要归功于CJK发展出的CJKutf8宏包. 对UTF-8的支持终于健壮得可以一统江湖了. ^o^/
字体配置cheatsheet
下面是一些字体配置的cheatsheet, 用于向MiKTeX 2.7中添加字体支持.
其实添加字体最简单的方式是安装CTeX的字体包, 但是恐怕只能用GBK编码, 因为人家做tfm的时候就是这么做的. 所以, 如果要使用UTF-8, 或者对字体的授权问题有些敏感的话, 可以考虑自己安装字体.
简单地说, DVIPDFMx方式需要生成tfm文件并更新cid-x.map, pdfTeX方式需要生成tfm, enc文件并更新map文件. 我下面的例子假设的是英文的Windows系统, 没有楷体, 所以使用了文鼎的楷体(用MiKTeX安装arphic包). 中文Windows系统包含了simkai, 就可以拿来使用了.
DVIPDFMx, UTF-8中文字体
mkdir "%TEXMFLOCAL%fontstruetypem$pushd %TEXMFLOCAL%fontstruetypem$mklink simsun.ttc %windir%Fontssimsun.ttcmklink simhei.ttf %windir%Fontssimhei.ttfpopd pushd %TEMP%ttf2tfm %windir%Fontssimsun.ttc -q simsun@Unicode@ttf2tfm %windir%Fontssimhei.ttf -q simhei@Unicode@mkdir "%TEXMFLOCAL%fontstfmm$simsun"move /y simsun*.tfm "%TEXMFLOCAL%fontstfmm$simsun" > nulmkdir "%TEXMFLOCAL%fontstfmm$simhei"move /y simhei*.tfm "%TEXMFLOCAL%fontstfmm$simhei" > nulpopd mkdir "%TEXMFLOCAL%dvipdfmconfig"echo simsun@Unicode@ Identity-H :0:simsun.ttc>> "%TEXMFLOCAL%dvipdfmconfigcid-x.map"echo simhei@Unicode@ Identity-H :0:simhei.ttf>> "%TEXMFLOCAL%dvipdfmconfigcid-x.map" mkdir "%TEXMFLOCAL%texlatexcjkUTF8"notepad "%TEXMFLOCAL%texlatexcjkUTF8c70mswin+arphic.fd" texhash
其中 c70mswin+arphic.fd 内容如下:
ProvidesFile{c70mswin+arphic}[]
DeclareFontFamily{C70}{mswin+arphic}{hyphenchar fontm@ne}
DeclareFontShape{C70}{mswin+arphic}{m}{n}{<-> CJK * simsun}{}DeclareFontShape{C70}{mswin+arphic}{bx}{n}{<-> CJK * simhei}{}DeclareFontShape{C70}{mswin+arphic}{m}{sl}{<-> CJK * gkaiu}{}DeclareFontShape{C70}{mswin+arphic}{m}{it}{<-> CJK * gkaiu}{}DeclareFontShape{C70}{mswin+arphic}{bx}{sl}{<-> CJKb * gkaiu}{CJKbold}DeclareFontShape{C70}{mswin+arphic}{bx}{it}{<-> CJKb * gkaiu}{CJKbold}
endinput
DVIPDFMx, GBK中文字体
mkdir "%TEXMFLOCAL%fontstruetypem$pushd %TEXMFLOCAL%fontstruetypem$mklink simsun.ttc %windir%Fontssimsun.ttcmklink simhei.ttf %windir%Fontssimhei.ttfpopd pushd %TEMP%ttf2tfm %windir%Fontssimsun.ttc -q -w simsun-gbk@UGBK@ttf2tfm %windir%Fontssimhei.ttf -q -w simhei-gbk@UGBK@mkdir "%TEXMFLOCAL%fontstfmm$simsun"mkdir "%TEXMFLOCAL%fontsencm$simsun"move /y simsun-gbk*.tfm "%TEXMFLOCAL%fontstfmm$simsun" > nulmkdir "%TEXMFLOCAL%fontstfmm$simhei"mkdir "%TEXMFLOCAL%fontsencm$simhei"move /y simhei-gbk*.tfm "%TEXMFLOCAL%fontstfmm$simhei" > nulpopd mkdir "%TEXMFLOCAL%dvipdfmconfig"echo simsun-gbk@UGBK@ UniGB-UCS2-H :0:simsun.ttc>> "%TEXMFLOCAL%dvipdfmconfigcid-x.map"echo simhei-gbk@UGBK@ UniGB-UCS2-H :0:simhei.ttf>> "%TEXMFLOCAL%dvipdfmconfigcid-x.map" mkdir "%TEXMFLOCAL%texlatexcjkGB"notepad "%TEXMFLOCAL%texlatexcjkGBc19mswin+arphic.fd" texhash
其中 c19mswin+arphic.fd 内容如下:
ProvidesFile{c19mswin+arphic}[]
DeclareFontFamily{C19}{mswin+arphic}{hyphenchar fontm@ne}
DeclareFontShape{C19}{mswin+arphic}{m}{n}{<-> CJK * simsun}{}DeclareFontShape{C19}{mswin+arphic}{bx}{n}{<-> CJK * simhei}{}DeclareFontShape{C19}{mswin+arphic}{m}{sl}{<-> CJK * gkaiu}{}DeclareFontShape{C19}{mswin+arphic}{m}{it}{<-> CJK * gkaiu}{}DeclareFontShape{C19}{mswin+arphic}{bx}{sl}{<-> CJKb * gkaiu}{CJKbold}DeclareFontShape{C19}{mswin+arphic}{bx}{it}{<-> CJKb * gkaiu}{CJKbold}
endinput
pdfTeX, UTF-8中文字体
pushd %TEMP%ttf2tfm %windir%Fontssimsun.ttc -q -w simsun@Unicode@mkdir "%TEXMFLOCAL%fontstfmm$simsun"mkdir "%TEXMFLOCAL%fontsencm$simsun"move /y simsun*.tfm "%TEXMFLOCAL%fontstfmm$simsun" > nulmove /y simsun*.enc "%TEXMFLOCAL%fontsencm$simsun" > nulpopd mkdir "%TEXMFLOCAL%fontsmapdvipsm$simsun"copy /y nul "%TEXMFLOCAL%fontsmapdvipsm$simsunsimsun.map"for %i in (%TEXMFLOCAL%fontstfmm$simsunsimsun*.tfm) do @echo %~ni SimSun ^< %~ni.enc ^< simsun.ttc>> "%TEXMFLOCAL%fontsmapdvipsm$simsunsimsun.map"initexmf --edit-config-file updmap texhashupdmap
其中 initexmf –edit-config-file updmap 打开的文件应添加如下行:
Map simsun.map
XeTeX, T1英文字体
XeTeX虽然支持系统中所有已安装的字体, 但是TeX发行中的一些Type 1字体还是非常好看的. 要使用它们, 必须让它们支持XeTeX定义的EU1编码. 其实很简单, 参考euenc.pdf文档, 里面讲了如何把T1的fd变成EU1. 关于LaTeX的字体编码(即T1, OT1, EU1等等)还有fd文件等概念, 参考TeX Font Guide. 注意, LaTeX中的T1编码和PostScript Type 1字体是两个概念.
mkdir "%TEXMFLOCAL%texxelatexeuencpsnfss"pushd "%TEXMFLOCAL%texxelatexeuencpsnfss"copy "%TEXMFMAIN%texlatexpsnfsst1*.fd"cmd /v:onfor %i in (t1*.fd) do @( set FN=%i sed -e s/t1/eu1/ -e s/T1/EU1/ "!FN!" > "!FN:t1=eu1!" del "!FN!" )exitpopd texhash
Phison芯片的DataTraveler量产USB-CDROM成功
这已经不是新闻啦. 我不过是站在一堆巨人的肩膀上玩了一下. 但是太刺激了!
调试机器, 装系统, 免不了遇到别人光驱坏了的情况. 早就想搞USB启动了, 甚至还买了一块USB移动硬盘来试, 但是一直不完美. USB启动, 除了光驱, 没有标准可循, 每种主板都不一定兼容. 最典型的就是DELL的BIOS, 在AMI和AWARD上做好的U盘到DELL上基本都起不来.
前一阵搜索USB启动的时候, 听说了一种叫做"量产"的方法, 可以让U盘划出一块空间来模拟CD-ROM. 这不就全解决了! 昨天一起吃饭, 听哈哈同学说了他自己的成功案例, 我是信心十足呀.
可惜回来拿我的爱国者U盘试验失败. 配置的是安国(Alcor)芯片. 后来发现网上根本就没有成功案例, 大家都是刷进去一个镜像, 有至少一半的数据是坏的.
所以今天回来就要拿我老爸的Kingston DataTraveler 2.0来造啦. 配置是群联(Phison)的PS2231, 正好就是哈哈同学成功的那种. 呵呵, 找到量产工具, 试验了几次就成功啦!
^_^
下面贴上我的参数文件:
UP12_PS2231_Kingston_DataTraveler_2.0.ini
[PenDriveMP]IC Type=PS2231Used MPTool=Last Version[Parameter Mark]Parameter Type=F1_MP_21[Customize Info]USB VID=0x0930USB PID=0x6545String Product Name=DataTraveler 2.0String Manufacturer Name=KingstonInquiry Version=PMAP[Configuration]Reset Serial Number=0Preformat Target Capacity=1900[TestItemOption]Do Preformat Test=1[Extra]Mode=21[Misc]Privacy Volume Label=UFDCDROM Image=C:MegaBoot.2006.jackqq.iso[Advance]Tick=1FC1=0xffFC2=0x1[Firmware]ISP=1Burner File=C:phison-v1.96.00BN206.BINFirmware Name=C:phison-v1.96.00FF0110D.BINVerified Firmware Name=C:phison-v1.96.00FF0110D.BIN
GnuPG: 用多个sub keys保护primary key
我想到了一种保护PGP密钥长时间不换的方法. 就是利用GnuPG的一个扩展, 只导出secret sub keys而不导出primary secret key, 把这些secret sub keys放到不同的地方使用.
最开始是看了GnuPG的FAQ发现的, 后来发现原来还是有很多先人呀.
参考:
How can I use GnuPG in an automated environment?
http://www.gnupg.org/documentation/faqs.en.html#q4.14
Using multiple subkeys in GPG
http://fortytwo.ch/gpg/subkeys
using various subkeys [HOWTO]
http://lists.gnupg.org/pipermail/gnupg-users/2002-August/014780.html
PGP: Public Key Servers
http://www.rossde.com/PGP/pgp_keyserv.html
这种方法的特点:
- 可以满足日常的加密, 解密, 签名的需要.
- 可以在不同地方设置不同的密码, 从而提供不同的保密级别.
- 只导出secret sub keys可以保护primary key的安全.
- 只有primary key才能签署其他人的密钥, 这样做使得primary key用起来麻烦一些, 强迫我们三思而后行.
但是,
- 比较老的PGP keyserver都不支持多个sub keys, 上传的key会被截断. 可以用别的keyserver, 或者直接用文本传.
运行PGP Key Server 0.9.6的服务器, 如MIT的, 就不接受多个sub keys. 而运行SKS 1.0.10的服务器, 如Ubuntu的, 就支持多个sub keys.
还有一个就是密钥生成过程中的存储问题. 因为密钥的生成过程是要保存在外存上的, 即使随后删除也有可能会被离线恢复. 我想了下面几种办法.
一个就是在生成密钥并导出之后, 用Sysinternals的sdelete工具对中间文件进行安全删除. 但是如果假设文件系统一般会为改写的文件重新分配空间, 所以感觉这不是一种很安全的办法.
还有一个就是用NTFS提供的EFS机制, 不过如果对Windows系统保护不够强, 还是形同虚设. 具体做法是, 先将AppData里的gnupg目录设置为加密, 然后再进行创建密钥等操作, 保存到硬盘上的数据起码就不是明文了. 不过通过一些工具盘应该还是能通过SAM找到EFS密钥的.
虽然有顾虑, 其实上面两种办法已经给破解加大了不少难度.
最后, 如果能在一张UDF的CD-R上操作, 然后直接妥善保存这张CD-R, 我想这应该是最安全的了.
Windows Vista本身就支持UDF的写入. Windows XP需要第三方的UDF写入支持, 如
- Sonic Drive Letter Access
- Roxio Drag-to-Disc (Adaptec DirectCD)
- Nero InCD
美中不足的是这些UDF写入方式看起来也不甚兼容.
假设已经以UDF格式化好的CD-R在F:, 那么设置GNUPGHOME环境变量就可以让GnuPG
在光盘上工作了.
C> F:F> set GNUPGHOME=F:GnuPG
============================================================
首先创建primary key. 原则上, 作为自己身份的证明, 应该是永远不过期的. passphrase则应足够保险, 自己好记的, 别人不容易猜出的, 允许空格, 最好有数字符号, 不少于20个字符.
F> gpg --gen-key
Please select what kind of key you want:
(1) DSA and Elgamal (default)
(2) DSA (sign only)
(5) RSA (sign only)
Your selection? 1
What keysize do you want? (2048)
Requested keysize is 2048 bits
Key is valid for? (0)
Key does not expire at all
Real name: Quan Qiu
Email address: jackqq@gmail.com
Comment: Testing GnuPG
You selected this USER-ID:
"Quan Qiu (Testing GnuPG) <jackqq@gmail.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key.
Enter passphrase:
Repeat passphrase:
……………+++++
gpg: key 9061D35B marked as ultimately trusted
public and secret key created and signed.
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
pub 1024D/9061D35B 2008-06-21
Key fingerprint = 04EE 15CC 1DE5 1115 8F8A F0F0 6301 CEEF 9061 D35B
uid Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
sub 2048g/11DD7B4D 2008-06-21
============================================================
别忘了生成一个密钥撤销证书. 这个一定要妥善保管, 不能泄露.
F> gpg --output revokecert.txt --gen-revoke jackqq
sec 1024D/9061D35B 2008-06-21 Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Create a revocation certificate for this key? (y/N) y
Please select the reason for the revocation:
0 = No reason specified
1 = Key has been compromised
2 = Key is superseded
3 = Key is no longer used
Q = Cancel
(Probably you want to select 1 here)
Your decision? 1
Enter an optional description; end it with an empty line:
>
Reason for revocation: Key has been compromised
(No description given)
Is this okay? (y/N) y
You need a passphrase to unlock the secret key for
user: "Quan Qiu (Testing GnuPG) <jackqq@gmail.com>"
1024-bit DSA key, ID 9061D35B, created 2008-06-21
ASCII armored output forced.
Revocation certificate created.
Please move it to a medium which you can hide away; if Mallory gets
access to this certificate he can use it to make your key unusable.
It is smart to print this certificate and store it away, just in case
your media become unreadable. But have some caution: The print system of
your machine might store the data and make it available to others!
============================================================
下面为各种用途创建sub keys. 比如, 在家里, 工作场所和便携电脑上的, 用于签名和加密的不同密钥. 这些sub keys可以适当设置有效期限.
F> gpg --edit jackqq
Secret key is available.
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: ultimate validity: ultimate
sub 2048g/11DD7B4D created: 2008-06-21 expires: never usage: E
[ultimate] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Command> addkey
Please select what kind of key you want:
(2) DSA (sign only)
(4) Elgamal (encrypt only)
(5) RSA (sign only)
(6) RSA (encrypt only)
Your selection? 2
DSA keypair will have 1024 bits.
Key is valid for? (0) 3m
………………………….+++++
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: ultimate validity: ultimate
sub 2048g/11DD7B4D created: 2008-06-21 expires: never usage: E
sub 1024D/5B0E7472 created: 2008-06-21 expires: 2008-09-19 usage: S
[ultimate] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Command> addkey
Please select what kind of key you want:
(2) DSA (sign only)
(4) Elgamal (encrypt only)
(5) RSA (sign only)
(6) RSA (encrypt only)
Your selection? 4
What keysize do you want? (2048)
Key is valid for? (0) 3m
………………………+++++^^^
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: ultimate validity: ultimate
sub 2048g/11DD7B4D created: 2008-06-21 expires: never usage: E
sub 1024D/5B0E7472 created: 2008-06-21 expires: 2008-09-19 usage: S
sub 2048g/CB36A1C8 created: 2008-06-21 expires: 2008-09-19 usage: E
[ultimate] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Command>quit
Save changes? (y/N) y
============================================================
然后就可以为各种用途导出钥匙链了. 用–export-secret-subkeys选项导出的私钥钥匙链里的primary key是无效的, 从而保证了它的安全. 在修改不同用途的钥匙链时, 可以用passwd命令设置不同的passphrase, 然后去掉多余的 sub keys.
F> mkdir home office laptopF> cd officeF> gpg --export-secret-subkeys --no-comments > secring.gpgF> copy %GNUPGHOME%pubring.gpg .F> gpg --homedir . --edit jackqq
Secret key is available.
gpg: ./trustdb.gpg: trustdb created
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: unknown validity: unknown
sub 2048g/11DD7B4D created: 2008-06-21 expires: never usage: E
sub 1024D/5B0E7472 created: 2008-06-21 expires: 2008-09-19 usage: S
sub 2048g/CB36A1C8 created: 2008-06-21 expires: 2008-09-19 usage: E
sub 1024D/6C06E245 created: 2008-06-21 expires: 2008-12-18 usage: S
sub 2048g/5CBC7406 created: 2008-06-21 expires: 2008-12-18 usage: E
sub 1024D/903CF7E7 created: 2008-06-21 expires: 2009-06-21 usage: S
sub 2048g/084E750C created: 2008-06-21 expires: 2009-06-21 usage: E
[unknown] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Command> addkey
Secret parts of primary key are not available.
gpg: Key generation failed: secret key not available
Command> passwd
Secret parts of primary key are not available.
You need a passphrase to unlock the secret key for
user: "Quan Qiu (Testing GnuPG) <jackqq@gmail.com>"
2048-bit ELG-E key, ID 11DD7B4D, created 2008-06-21
Enter passphrase:
Enter the new passphrase for this secret key.
Enter passphrase:
Repeat passphrase:
Command> key 1
Command> key 5
Command> key 6
Command> key 7
Command> key 8
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: unknown validity: unknown
sub* 2048g/11DD7B4D created: 2008-06-21 expires: never usage: E
sub 1024D/5B0E7472 created: 2008-06-21 expires: 2008-09-19 usage: S
sub 2048g/CB36A1C8 created: 2008-06-21 expires: 2008-09-19 usage: E
sub* 1024D/6C06E245 created: 2008-06-21 expires: 2008-12-18 usage: S
sub* 2048g/5CBC7406 created: 2008-06-21 expires: 2008-12-18 usage: E
sub* 1024D/903CF7E7 created: 2008-06-21 expires: 2009-06-21 usage: S
sub* 2048g/084E750C created: 2008-06-21 expires: 2009-06-21 usage: E
[unknown] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Command> delkey
Do you really want to delete the selected keys? (y/N) y
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: unknown validity: unknown
sub 1024D/5B0E7472 created: 2008-06-21 expires: 2008-09-19 usage: S
sub 2048g/CB36A1C8 created: 2008-06-21 expires: 2008-09-19 usage: E
[unknown] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Command> quit
Save changes? (y/N) y
============================================================
别忘了发布公钥.
F> gpg --keyserver keyserver.ubuntu.com --send-keys 9061D35B
============================================================
把这个私钥钥匙链, 也就是secring.gpg, 复制到相应的地方导入即可.
C> set GNUPGHOME=C> gpg --import F:officesecring.gpg
更新一下公钥, 因为私钥中没有保存revuid的信息.
C> gpg --keyserver keyserver.ubuntu.com --refresh-keys
如果还没信任过这个key, 现在可以设置信任.
C> gpg --edit-key jackqq
Command> trust
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: unknown validity: unknown
sub 1024D/5B0E7472 created: 2008-06-21 expires: 2008-09-19 usage: S
sub 2048g/CB36A1C8 created: 2008-06-21 expires: 2008-09-19 usage: E
[ unknown] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Please decide how far you trust this user to correctly verify other users’ keys
(by looking at passports, checking fingerprints from different sources, etc.)
1 = I don’t know or won’t say
2 = I do NOT trust
3 = I trust marginally
4 = I trust fully
5 = I trust ultimately
m = back to the main menu
Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y
pub 1024D/9061D35B created: 2008-06-21 expires: never usage: SC
trust: ultimate validity: unknown
sub 1024D/5B0E7472 created: 2008-06-21 expires: 2008-09-19 usage: S
sub 2048g/CB36A1C8 created: 2008-06-21 expires: 2008-09-19 usage: E
[ unknown] (1). Quan Qiu (Testing GnuPG) <jackqq@gmail.com>
Please note that the shown key validity is not necessarily correct
unless you restart the program.
Command> quit
My PGP Key
这是我的PGP公钥. 目前还只是试用, 2008年年底过期. 它包含多个sub keys.
你可以通过Ubuntu的keyserver(SKS 1.0.10)获得我的完整密钥, 但MIT的keyserver(PGP Key Server 0.9.6)不支持多个sub keys.
KeyID: 88A1880EFingerprint: 7295 6509 7643 544B AB36 3C04 A2BD 6E5D 88A1 880E
-----BEGIN PGP PUBLIC KEY BLOCK-----Version: GnuPG v2.0.9 (FreeBSD) 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=8P3l-----END PGP PUBLIC KEY BLOCK-----
RTL_NUMBER_OF_V2 用 C++ 模板的好处
Windows Platform SDK 中的 winnt.h 中有一个宏 RTL_NUMBER_OF,这个宏可以得到数组中元素的个数。
它有两种实现,看其中一种:
extern "C++"template <typename T, size_t N>char (*RtlpNumberOf( UNALIGNED T (&)[N] ))[N]; #define RTL_NUMBER_OF_V2(A) (sizeof(*RtlpNumberOf(A)))
按照 winnt.h 中的注释所述, RtlpNumberOf 只是个声明,无需定义,借助模板的声明就达到了取个数的目的,而且还只接受数组不接受指针。通过 C++ 的 template 实现,发挥了 C++ 的类型检查的功能。
关于 template 能自动推断被引用的数组的大小这个特性,见 Thinking in C++ Vol 2 第5章 Templates in Depth 中的 Type deduction of function template arguments 小节。
今天终于搞明白了。
cheatsheet: PDF on PSP
首选当然是bookr。官方已经很久没维护了,还只支持1.5核心,下载应用程序框架和字库之后,还应该从这里下载支持3.XX的主程序(在我的3.71 M33-4上测试通过)。
但是,bookr 对大图片很不感冒,速度奇慢。看扫描的pdf基本必宕机。所以不如干脆转成图片格式一张张看吧。参考这里,方法如下, (win32 cmd, 依赖 Ghostscript 和 ImageMagick)
gswin32 -dSAFER -dBATCH -dNOPAUSE -r300 -sDEVICE=png16m ^ -dGraphicsAlphaBits=4 -dTextAlphaBits=4 ^ -sOutputFile="book.%04d.png" -f "book.pdf" for %i in (*.png) do @( echo %i convert %i -negate -resize 800 -quality 80 %~ni.jpg)
rem EOF
2038年问题 (Y2K38)
把你的计算机时钟调到“2038年1月20日”,看看会有多少程序挂掉?
这两天遇到的就是这样的一个问题。一台机器中恶意程序了,一开机就不停地有程序挂掉,当然都是那些恶意的程序。手工清理掉它们之后,用户报告说还有问题,比如Photoshop不能启动、诺顿杀毒 (Symantec AntiVirus) 不能启动等,另外他们注意到一个现象,就是系统时间被改成了2058年。
转天我去,经过一阵奋战,实在没发现那两个程序的出错之处,也没再发现其它的恶意程序。安全模式也不行。正当我准备放弃时,用户又提醒了我系统日期的问题。我在安全模式下把日期改过来,重新启动。嘿嘿,问题解决了。
此时,我才想起来一件事:2037年问题。给人家解释了一通,总之就是和千年虫类似的问题啦,听起来蛮恐怖的哈。回来自己又测试了一下,企业版诺顿确实会在2038年之后挂掉。
最后更正一下,回来再查时才发现,原来是“2038年问题”。
读书笔记: Thinking in C++, 2e, Vol. 1
Subject: Thinking in C++ 2e
————————
Date: Sat, Apr 5, 2008 at 8:43 AM
[C and C++ compared] @ pp.91
int func();
C: a function with any number and type of argument
C++: a function with no arguments
———-
Date: Sat, Apr 5, 2008 at 9:21 AM
int i;
is a definition. (and of course, a declaration.)
extern int i;
is a declaration.
usually, multiple global (non-static)
int i;
will be stripped to one instance by the linker.
———-
Date: 2008/4/5
[Linker]
pp.97
When the linker finds a definition in a library, the entire object
module, not just the function definition, is linked into the
executable program. Note that the whole library isn’t linked, just the
object module in the library that contains the definition you want.
也就是说,library其实是一个包含多个obj的文件,当linker在某个lib中解析到某个函数时,便会把其中的obj整个连接进来。
———-
Date: 2008/4/5
[Standard C++ Library] @pp.102
在向后兼容的头文件中,是没有名字空间的。所以,
#include <iostream.h>
的含义就是
#include <iostream>
using namespace std;
———-
Date: Sun, Apr 6, 2008 at 11:06 PM
[C and C++ compared] @ pp.125
func(void);
C: a function returning int
C++: error
———-
Date: Mon, Apr 7, 2008 at 10:53 AM
[Intrinsic Types] @ pp.143
The Standard stipulates only the minimum and maximum value of a type,
not the actual value domain of the type.
Therefore, a short can be 64 bits long on a 64-bit platform.
* valid type specifier usage:
(long|short) (int)?
(signed|unsigned) (long|short) (int)?
(signed|unsigned) char
long double
* invalid, eg.:
short float
short double
long float
signed float
———-
Date: Mon, Apr 7, 2008 at 11:17 AM
[Pointer] @ pp.148
int * a, b, c;
defines a as a pointer to int, b and c as int’s.
It’s better to do this way:
int *a;
int *b;
int *c;
———-
Date: Mon, Apr 7, 2008 at 9:36 PM
[C and C++ compared] @ pp.166
const int k;
C: OK. containing garbage if a local variable.
C++: error. const must be initialized.
———-
Date: 2008/4/7
[Operators] @ pp.173
Shift operators: << and >>
移位运算符的定义比较开放.
这两种移动都是logical(不考虑符号)或者arithmetic(考虑符号)的, 不是shift-and-rotate(循环移位).
当移位位数是负数时, 结果是undefined.
左移的定义比较简单明了, 相当于乘以2的幂.
右移就复杂了. 如果移位位数比被操作数的位数多, 结果是undefined.
如果被操作数是signed, 那么结果还是implementation defined.
参考MSDN.
———-
Date: Wed, Apr 9, 2008 at 11:36 PM
[C and C++ compared] @ pp.258
struct X {};
C: illegal.
C++: legal. size nonzero, because each object must have a unique address.
———-
Date: Wed, Apr 9, 2008 at 11:52 PM
[Linkage] @ pp.262
In Standard C++, file static is a deprecated feature.
———-
Date: 2008/4/9
[C and C++ compared] @ pp.262
C和C++都允许函数被声明多次,条件是这些声明都一致。但是都不允许结构体的重新声明。
———-
Date: Thu, Apr 10, 2008 at 12:18 AM
[Identifier] @ pp.265, pp.404
Identifiers with leading underscores are reserved for system use.
See also:
* C Primer Plus, 5e, Chapter 2, Keywords and Reserved Identifiers.
Reserved identifiers include those beginning with an underscore
character and the names of the standard library functions.
* The C Programming Language, 2e, Appendix B – Standard Library
External identifiers that begin with an underscore are reserved for
use by the library, as are all other identifiers that begin with an
underscore and an upper-case letter or another underscore.
* The C++ Programming Language, 3e, 4.9.3 Names [dcl.name]
Names starting with an underscore are reserved for special facilities
in the implementation and the run-time environment, so such names
should not be used in application programs.
———-
Date: 2008/4/11
[Encapsulation] @ pp.279
原文所述
If the interface and implementation are clearly separated and
protected, you can accomplish this and require only a relink by the
client programmer.
这不总是正确的。如果实现变了的同时导致了类的成员变化,也就是定义变化了,则需要重新编译。否则以前编译的目标代码(obj)还会按照以前的类定义来分配或者理解内存对象,新的目标代码则按照新的定义。后果嘛,难以想象哦,当然是灾难性的。
参考Don Box的Essential COM(潘爱民译)第1章。
———-
Date: 2008/4/13
[Internal] @ pp.288
对象在内存中的存放,C++规定相同访问级别的成员(within a particular "access
block"),保证在内存中是连续的。然而不同访问级别对应的区块(access
block)不一定按照声明的顺序存放,甚至不一定是连续的,这是实现相关的(implementation-specific)。
———-
Date: 2008/4/14
[C and C++ compared] @ pp.308
在C中,定义与初始化一般是分开的,因为定义总是在一个block的开始。C99改变了这一点,允许在作用域的任何位置定义变量了。
在C++中,定义与初始化是不分家的。C++保证一个对象在建立的时候被初始化。C++提倡用到变量的时候再定义它。
———-
Date: 2008/4/14
[Special Members] @ pp.304
有参数的构造函数会禁止编译器隐式生成无参数的构造函数。
[Special Members] @ pp.305
构造函数和析构函数都没有任何返回值。
———-
Date: 2008/4/14
[Lifetime and Scope] @ pp.306
出了作用域的对象会被析构,包括用goto跳出的。但是setjmp和longjmp不会导致析构函数被调用。
[Lifetime and Scope] @ pp.311
栈上的对象空间是预先分配好的,到了对象被定义的序列点(sequence point)那里,才会调用构造函数。
如果某些定义会被goto或者switch跳过,编译器会给出警告。
———-
Date: 2008/4/14
[Lifetime and Scope] @ pp.3
10
for循环的循环变量,如果在初始化表达式里定义,例如
for (int i = 0; …, …)
其作用域是for循环。
———-
Date: 2008/4/14
[Initialization] @ pp.320
int b[6] = {0};
会用给出的值按顺序初始化数组,其余的值填0。这比for循环省事多了,也安全多了(不会出现off-by-one错误)。
int c[] = {1, 2, 3, 4};
char s[] = "string";
则是让编译器自动决定数组的大小。
Y y1[] = { Y(1,2), Y(2,3), Y(3,4) };
有构造函数的类或者结构体可以这样初始化。
———-
Date: 2008/4/14
[Special Members] @ pp.323
无参数的构造函数,确切地说,应该是,默认构造函数。
当一个类没有定义任何构造函数时,编译器会自动生成一个默认构造函数。这个默认构造函数什么也不做。
如果定义了任何的构造函数,则编译器就不会自动生成默认构造函数。当需要使用默认构造函数初始化对象时,就可能产生编译错误。
一般的,应该定义默认构造函数,不要让编译器生成。
———-
Date: 2008/4/14
[Overloading] @ pp.331
重载仅限于不同作用域(scope)或者不同参数(arguments)的函数。只有返回值(return value)不同的函数,是不能被重载的。
———-
Date: 2008/4/14
[Internal] @ pp.331
Name mangling 或者叫 name decoration 的一个价值是 type-safe
linkage。因为声明不同的函数会使编译器产生不同的 name decoration,从而错误的调用会在连接的时候产生解析错误。
———-
Date: 2008/4/14
[Aggregation Types] @ pp.339
匿名联合(anonymous union),也就是既没有类型名也没有变量名的联合。这样的联合,其成员的名字直接进入包含这个联合的scope。例如,
int main() {
union {
int i;
float f;
};
i = 12;
f = 1.22;
}
或者
struct Entry {
char* name;
Type t;
union {
char* s;
int i;
};
};
如果这个union是在文件作用域(file scope)的,则必须是静态(static)的。
———-
Date: 2008/4/14
[Declarations] @ pp.341
默认参数有两个要求:
1) 只有末尾的参数可以设置默认值。
2) 调用的时候,一旦开始使用默认值,后面的就都必须使用默认值。
默认参数在函数的声明时指明,注意不是函数定义时。因为编译器需要先知道默认参数,才能执行函数调用。
因为默认参数是静态绑定的,所以永远不要重新定义继承来的默认参数。参考 Effective C++ Item 38。
———-
Date: 2008/4/15
[Design] @ pp.348
接口的设计是最重要的,要想着调用者和读者。不要过分重视效率。
如果使用默认参数会使实现内部根据这个参数选择分支,那么还不如把接口分开设计。
使用默认参数的一种好处是,可以预先设计一些占位参数(placeholder arguments),这样当接口需要新的参数的时候,调用代码基本不需要变化。
———-
Date: 2008/4/15
[Type Qualifiers] @ pp.354
const 的作用有:
1. 代替 #define,编译器尽量不为 const 分配空间,直接将其展开。如,
const int i = 100;
2. 标志不可修改的变量,编译器会分配空间,可以被动态或静态初始化,只是之后不再修改。如,
const char c = cin.get();
const int ii[] = { 1, 2, 3, 4 };
文件作用域(file scope)的const,是static,也就是internal linkage的,一般不会被分配空间。
除非明确指明extern,这时就会被分配空间了。
当遇到隐式或显示的引用,用法1的 const 就会被分配空间。如,
extern const int i;
或者
const int j = 5;
long address = (long)&j;
———-
Date: 2008/4/15
[C and C++ compared] @ pp.358
const 变量
C:
1) 含义是,不能通过这个变量修改内存(并不禁止如其它线程、其它指针等等的修改方式)。
2) 不是一个编译器常量。不能用来代替 #define 或者字面常量。
3) 默认是external linkage,不同文件中同名会冲突,总会被分配存储空间。
C++:
1) 包含C的含义,还是编译器认可的常量。
2) 可以用来代替字面常量,从而代替 #define。
3) 默认是internal linkage,不同文件中同名不会冲突,如果可能的话就不会被分配存储空间。
———-
Date: 2008/4/15
[C and C++ compared] @ pp.364
char *s = "howdy";
这里s指向的是静态只读的字符串。把const的东西赋给非const的东西,本来编译器应该报错的。不过为了迁就以往的C代码,这里就不报错了。但是,通过s修改静态只读的字符串,其结果是undefined。
一般应该用字面常量来初始化一个数组,如
char s[] = "howdy";
———-
Date: 2008/4/15
[Design] @ pp.365
按值传递的参数,加上const修饰,实际上是没有意义的。如,
void func(const int i);
如果函数实现中需要限制const,那么在实现中声明一个const引用。如,
void func(int i) {
const int &ii = i;
i++; // error
}
这样的话,函数声明可以避免给调用者造成疑惑。
———-
Date: 2008/4/15
[Design] @ pp.369
按引用传递,实际上是按值传递指针或引用,这样的参数应该尽可能地加上const修饰。如,
或者
void func(const int *pi);
否则函数对const的变量就不能适用了。
———-
Date: 2008/4/15
[Design] @ pp.372
C++中传递对象的最好方式是按引用传递,const引用,这样可以减少临时对象的创建与清除。
还有一个好处是,因为临时对象都是const的,所以用const引用传递,那么函数就可以接受临时对象。
———-
Date: 2008/4/16
[Internal] @ pp.415
标准提到,类内的内联(inline)函数直到类的结尾括号,才会被生成代码。所以在类内的简单的内联函数互相引用还可以被内联。如,
class Forward {
int i;
public:
Forward() : i(0) {}
// Call to undeclared function:
int f() const { return g() + 1; }
int g() const { return i; }
};
但其它情况下,调用未经定义的内联函数,很
可能会被编译器退化成普通调用。
———-
Date: 2008/4/16
[Internal] @ pp.431
全局静态对象(global static objects)会在进入main()之前被初始化。
局部静态对象(local static objects)会在第一次进入函数的时候被初始化。
内建类型(intrinsic types)的静态变量被初始化为0;用户定义类型,也就是类对象,编译器会调用构造函数。
静态对象,包括局部的(local)和全局的(global),会在main()退出后或者在exit()中被析构。所以不要在析构函数中调用exit(),否则可能造成死循环。而abort()不会析构静态对象。析构的顺序与被构造的顺序相反。没有被构造的静态对象不会被析构。
———-
Date: 2008/4/16
[Linkage] @ pp.434
在C++中,static的函数和变量、const的变量、inline的函数,是internal
linkage的。所以能把const的变量和inline的函数放在头文件里。
注意在C中const的变量是external linkage的。
Linkage指的是一个名字对linker的可见性。在link/load时有地址的元素才有linkage的概念。所以类的声明和局部变量没有linkage。
———-
Date: 2008/4/17
[Namespaces] @ pp.439
名字空间可以起别名。如
namespace Bob = BobsSuperDuperLibrary;
无名的名字空间(unnamed namespace),对于每个translation
unit是唯一的。它提供了一种隐藏名字的手段,这样就不再需要通过internal linkage来实现了。如,
namespace {
int i, j, k;
}
通过friend声明的名字,会被注入该类所在的名字空间(包括全局名字空间)。
———-
Date: 2008/4/17
[Namespaces] @ pp.441
using directive:
using namespace std;
把名字空间中的所有名字引入当前的名字空间。
using declaration:
using std::cout;
把指定的名字引入当前的名字空间。由于只是名字,所以对于函数,包含所有的重载。
需要注意的是, using directive
可能引入不同名字空间中的名字冲突,从而引起二义性(ambiguity),但是直到这个名字被用到的时候,才会产生错误。
using declaration 可以覆盖 using directive 引入的名字。
由于 using declaration
只是引入名字,所以可能引入相同参数类型的函数重载(这样的函数重载是不可能直接声明的),所以这将引入另一种二义性。而且也是直到函数被调用时,才会产生错误。
———-
Date: 2008/4/17
[Namespaces] @ pp.445
using directive 和 using declaration 可以在 cpp 文件中使用,不应该在 h 文件中使用。
否则显然会污染名字空间。
———-
Date: 2008/4/17
[Static Members] @ pp.450
整数类型的静态常量成员,可以在类内初始化。这是为了能够用它来确定数组的大小,从而避免 enum hack。如,
class Values {
static const int size = 100;
};
所有其它类型的静态常量成员,包括但不限于浮点型、数组等,必须在外面,也就是类的实现文件中,定义和初始化。
整数类型的静态常量成员,当遇到引用时,编译器就需要为它划分存储空间,从而需要在类的实现文件中定义它,而且这时不能再进行初始化了。(注意前面提到的只是初始化,没有定义。)如,
static const int size;
The C++ Programming Language, 3e, 10.4.6.2 [class.memconst] 是这么说的。
这个行为我在 g++ 3.4.2 (mingw-special) 上验证通过。但是在 VC 8 (14.00.50727.42)
上,在实现中的定义会被认作是重复定义。如果去掉,在 g++ 上又通不过。
所以看来还是要避免引用整数类型的静态常量成员,让它们就充当 #define 的替代者就行了。
———-
Date: 2008/4/17
[Internal] @ pp.455
静态对象的初始化顺序,在一个translation unit中是确定的,按照声明的顺序初始化,按照相反的顺序消亡。
但是在不同的translation
unit中的静态对象,初始化的顺序是由linker决定的,也就是不确定的。那么对于有依赖关系的静态对象,结果就是不确定的甚至可能是灾难性的。
书中给出了2中解法。看书吧。
———-
Date: 2008/4/17
[Linkage] @ pp.466
由extern后接一个字符串声明一种name decoration方式。如,
extern "C"
extern "C++"
是标准规定的两种。
———-
Date: 2008/4/17
[Internal] @ pp.474
编译器为按值传递的对象生成代码的过程,使用的是拷贝构造函数。
———-
Date: 2008/4/17
[References] @ pp.475, pp.477
常引用(const reference),可以被临时对象(temporary)初始化。如,
const int &i = 1;
编译器会先用字面常量初始化一个临时对象,然后用临时对象初始化常引用。
———-
Date: 2008/4/18
[Special Members] @ pp.480, pp.495
拷贝构造函数(copy constructor),编译器用它来生成按值传递的中间对象,包括参数和返回值。
而且如下的定义也会调用拷贝构造函数。
Composite c2(c);
Composite c2 = c;
如果用户不提供拷贝构造函数,那么编译器将自动生成一个。对于基本类型成员,是位拷贝(bitwise copy,
bitcopy);对于用户定义类型成员,是调用该类型的拷贝构造函数。
(注意pp.496说的WoCC是bitwise copy是不对的,实际上是调用了string的拷贝。)
———-
Date: 2008/4/18
[Design] @ pp.498
函数参数类型如果使用引用的话,调用这个函数不容易看出来它是否修改参数。如,
char c;
cin.get(c);
所以,可以考虑这样,
用常引用(const reference)来传递不修改的对象,用非常量指针(non-const pointer)来传递要修改的对象。
———-
Date: 2008/4/18
[Pointer to Member] @ pp.499
成员指针实际上是一种偏移(offset),它需要配合一个对象才能产生有效地址。
例如,数据成员的指针,
int ObjectClass::*pointerToMember = &ObjectClass::a;
objectPointer->*pointerToMember = 47;
object.*pointerToMember = 47;
函数成员的指针,
void (Widget::*pmem)(int) const = &Widget::h;
(w.*pmem)(1);
(wp->*pmem)(2);
———-
Date: Sat, Apr 19, 2008 at 11:32 AM
[Operator Overloading] @ pp.515
declaration of unary operators:
class Integer {
…
public:
friend const Integer& operator+(const Integer& a);
friend
const Integer operator-(const Integer& a);
friend const Integer operator~(const Integer& a);
friend Integer* operator&(Integer& a);
friend int operator!(const Integer& a);
friend const Integer& operator++(Integer& a); // prefix
friend const Integer operator++(Integer& a, int); // postfix
friend const Integer& operator–(Integer& a); // prefix
friend const Integer operator–(Integer&a, int); // postfix
};
class Byte {
…
public:
const Byte& operator+() const;
const Byte operator-() const;
const Byte operator~() const;
Byte operator!() const;
Byte* operator&();
const Byte& operator++(); // prefix
const Byte operator++(int); // postfix
const Byte& operator–(); // prefix
const Byte operator–(int); // postfix
};
———-
Date: Sat, Apr 19, 2008 at 11:54 AM
[Operator Overloading] @ pp.520
declaration of binary operators:
class Integer {
…
public:
friend const Integer operator+(const Integer& left, const Integer& right);
friend const Integer operator-(const Integer& left, const Integer& right);
friend const Integer operator*(const Integer& left, const Integer& right);
friend const Integer operator/(const Integer& left, const Integer& right);
friend const Integer operator^(const Integer& left, const Integer& right);
friend const Integer operator&(const Integer& left, const Integer& right);
friend const Integer operator|(const Integer& left, const Integer& right);
friend const Integer operator<<(const Integer& left, const Integer& right);
friend const Integer operator>>(const Integer& left, const Integer& right);
friend Integer& operator+=(Integer& left, const Integer& right);
friend Integer& operator-=(Integer& left, const Integer& right);
friend Integer& operator*=(Integer& left, const Integer& right);
friend Integer& operator/=(Integer& left, const Integer& right);
friend Integer& operator%=(Integer& left, const Integer& right);
friend Integer& operator^=(Integer& left, const Integer& right);
friend Integer& operator&=(Integer& left, const Integer& right);
friend Integer& operator|=(Integer& left, const Integer& right);
friend Integer& operator>>=(Integer& left, const Integer& right);
friend Integer& operator<<=(Integer& left, const Integer& right);
friend int operator==(const Integer& left, const Integer& right);
friend int operator!=(const Integer& left, const Integer& right);
friend int operator<(const Integer& left, const Integer& right);
friend int operator>(const Integer& left, const Integer& right);
friend int operator<=(const Integer& left, const Integer& right);
friend int operator>=(const Integer& left, const Integer& right);
friend int operator&&(const Integer& left, const Integer& right);
friend int operator||(const Integer& left, const Integer& right);
};
class Byte {
…
public:
const Byte operator+(const Byte& right) const;
const Byte operator-(const Byte& right) const;
const Byte operator*(const Byte& right) const;
const Byte operator/(const Byte& right) const;
const Byte operator%(const Byte& right) const;
const Byte operator^(const Byte& right) const;
const Byte operator&(const Byte& right) const;
const Byte operator|(const Byte& right) const;
const Byte operator<<(const Byte& right) const;
const Byte operator>>(const Byte& right) const;
Byte& operator=(const Byte& right); // only as member function
Byte& operator+=(const Byte& right);
Byte& operator-=(const Byte& right);
Byte& operator*=(const Byte& right);
Byte& operator/=(const Byte& right);
Byte& operator%=(const Byte& right);
Byte& operator^=(const Byte& right);
Byte& operator&=(const Byte& right);
Byte& operator!=(const Byte& right);
Byte& operator>>=(const Byte& right);
Byte& operator<<=(const Byte& right);
int operator==(const Byte& right) const;
int operator!=(const Byte& right) const;
int operator<(const Byte& right) const;
int operator>(const Byte& right) const;
int operator<=(const Byte& right) const;
int operator>=(const Byte& right) const;
int operator&&(const Byte& right) const;
int operator||(const Byte& right) const;
};
———-
Date: 2008/4/19
[Operator Overloading] @ pp.531
记住,在各种赋值运算中检查自身赋值(self-assignment)。例如,
Integer& operator+=(Integer& left, const Integer& right) {
if (&left == &right) {
// self-assignment
}
left.i += right.i;
return left;
}
———-
Date: 2008/4/19
[Internal] @ pp.534
return value optimization
在按值传递返回值的函数中,如果返回语句这么写,
return Integer(left.i + right.i);
那么编译器就会在栈上的返回值容器中直接构造对象(1个构造函数调用)。反之,如果写成
Integer tmp(left.i + right.i);
return tmp;
则会产生1个构造函数调用、1个拷贝构造函数调用、1个析构函数调用。
前面那种情况被称为 return value optimization。
———-
Date: 2008/4/19
[Operator Overloading] @ pp.544
运算符重载不能
* 创造新的运算符
* 改变运算符的优先级
———-
Date: Sat, Apr 19, 2008 at 2:29 PM
[Operator Overloading] @ pp.545
为 iostream 重载运算符,如
class A {
…
public:
friend ostream& operator<<(ostream& os, const A& a);
friend istream& operator>>(istream&is, A& a);
};
ostream& operator<<(ostream& os, const A&a) {
… // e.g., os << a.member;
return os;
}
istream& operator<<(istream& is, A& a) {
… // e.g., is >> a.member;
return is;
}
———-
Date: Sat, Apr 19, 2008 at 2:35 PM
[Operator Overloading] @ pp.547
basic guidelines for choosing between members and non-members:
all una
ry operators
member
= () [] -> ->*
member, must
+= -= /= *= ^= &= |= %= >>= <<=
member
all other binary operators
non-member
———-
Date: 2008/4/19
[Internal] @ pp.549
运算符=可能产生不同的两种调用:
1) 拷贝构造函数(copy constructor);
2) 赋值运算符(operator=)。
如,
MyType b;
MyType a = b; // MyType(MyType&)
a = b; // operator=
其实只需要记住一个原则:新对象必须初始化,而初始化过的对象才是真正的赋值。
或者避免使用=号,使用另一种初始化类型,
MyType a(b);
———-
Date: 2008/4/19
[Special Members] @ pp.560
除了拷贝构造函数,运算符=也会被编译器自动生成,生成方式类似,也是成员递归调用+位拷贝的形式。
不要让编译器自动生成拷贝构造函数(copy-constructor)和运算符=(operator=),应该实现它们。
如果不想给出实现,或者说不提供调用的可能性,可以将其声明为私有(private)。
———-
Date: 2008/4/20
[Type Conversion] @ pp.561
一个参数(其它类型)的构造函数(或者后面都有默认参数的构造函数),会被编译器用来做自动类型转换(automatic type
conversion)。也就是,当一个需要A类型参数的函数,被传入一个B类型的参数,而A恰好有一个接受B的构造函数,编译器自动由B创建一个A的临时对象,传入函数。
为了避免这种情况,可以在这样的构造函数前面加上 explicit,告诉编译器这个构造函数不能被用于隐式类型转换(implicit conversion)。
———-
Date: 2008/4/20
[Type Conversion] @ pp.563
类型转换函数(conversion functions),或者叫做类型转换运算符(cast
operator),也可以帮助编译器实现自动类型转换(automatic type conversion)。
这种方式与前面所说的构造函数的方式的区别是,构造函数是在目标类中的,类型转换运算符是在源类中的。构造函数可以在当前系统下添加新的转换方式,而类型转换运算符可以转换到内建类型。
当双目的类型转换运算符被声明为友元(friend)时,就允许左运算子的隐式类型转换,如果是成员函数,则左运算子的类型必须是该类对象。如,
1 – a;
a + 1;
———-
Date: 2008/4/20
[Type Conversion] @ pp.566
自动类型转换的一种常见的应用,就是 string 类型到 const char* 的转换。
不过自动类型转换常常会产生二义性。为了避免这种二义性,可以干脆避免使用自动类型转换,或者在设计的时候考虑一条单一的转换路径。
由于自动类型转换还可能牵扯到拷贝构造函数等等隐式行为,应该尽量少使用这种特性。
———-
Date: 2008/4/20
[Dynamic Objects] @ pp.581
malloc() / free() 与 new / delete 应该配对使用,不要混用。否则行为是undefined。
delete 空指针,是安全的,它什么也不做。所以 delete 完之后立即将指针置为空,能有效防止多次 delete。
———-
Date: 2008/4/20
[Dynamic Objects] @ pp.584
delete void* 指针,因为对象类型不能确定,所以不能调用析构函数,只会释放内存。
所以基本上可以认为,出现这种情况就是有错儿了。
当程序出现内存泄露(memory leak)时,应该想到检查所有 delete 语句的指针类型。
———-
Date: 2008/4/20
[Dynamic Objects] @ pp.592
在堆(heap)上建立对象数组,需要默认构造函数。每个新创建的对象都会被调用默认构造函数。
在栈(stack)上建立对象数组,不一定需要调用默认构造函数。如果数据成员都是public的话,可以直接使用聚合类型初始化(aggregate
initialization)表示法。
———-
Date: 2008/4/20
[Dynamic Objects] @ pp.592
new [] 和 delete [] 应该配对使用。如果用 new [] 初始化的数组,由 delete
释放,那么数组中除了第1个元素之外,其它的元素可能都不能被正确析构。
———-
Date: 2008/4/20
[Operator Overloading] @ pp.596
重载 operator new 和 operator delete 可以改变编译器遇到 new 和 delete
的时候分配和释放内存的行为。注意构造函数和析构函数永远会被调用,这里只是修改分配和释放内存的行为。
重载 operator new 时,要加入错误处理机制,如返回空指针、调用 new-handler、或者抛出 bad_alloc
异常等。这些行为在重载的 operator new 中必须手动添加。
———-
Date: 2008/4/20
[Operator Overloading] @ pp.597
重载全局的 operator new 和 operator delete,会完全重载语言默认提供的机制,即使在重载的定义中也不能调用默认机制。
而且在全局的 operator new 和 operator delete 中,不能使用 C++ 标准库提供的对象,如 cin、cout
等,因为它们使用 new 来分配内存。
void* operator new(size_t sz);
void operator delete(void* m);
———-
Date: 2008/4/20
[Operator Overloading] @ pp.599
在类内重载 operator new 和 operator delete,必定是类的静态成员(static),这个关键字不必给出。
对于该类的对象,它们会优先于全局的 operator new 和 operator delete
被编译器使用。(继承(inheritance)引出的问题以后再说。)
在类内重载的 operator new 和 operator delete 中可以使用 C++ 标准库的对象,如 cin、cout
等,因为它们使用的是全局的 operator new 和 operator delete,这就不会造成死锁了。
class Framis {
…
static unsigned char pool[];
static bool alloc_map[];
public:
…
void* operator new(size_t) throw(bad_alloc);
void operator delete(void*);
};
———-
Date: 2008/4/20
[Operator Overloading] @ pp.603
在类内重载 operator new[] 和 operator delete[],即可重载编译器为对象数组的分配和释放内存的行为。
如果未重载这两个函数,那么分配对象数组会调用全局的 operator new 和 operator delete。
class Widget {
…
public:
void* operator new(size_t);
void operator delete(void*);
void* operator new[](size_t);
void operator delete[](void*);
}
———-
Date: 2008/4/20
[Internal] @ pp.605
当 operator new 不能分配足够内存时,应该提示出错的状态。
以往的习惯是返回空指针。这样编译器就不会调用构造函数。然后 new 表达式产生空指针,从而客户代
码得知失败。
如今标准的约定是,应该抛出 bad_alloc 异常,从而跳出 operator new, new 表达式也被跳过。客户代码如果处理这种异常,即获得失败状态。
———-
Date: 2008/4/20
[Special Members] @ pp.619
构造函数初始化列表(constructor initializer list)在定义构造函数的时候,用来初始化基类(base
class)和成员对象(member object)。
class Derived2 : public Derived 1 {
…
public:
Derived2(int) : m3(1), Derived1(2), m4(3) {
…
}
…
};
———-
Date: 2008/4/20
[Internal] @ pp.625
类的基类子对象(base class subobject)及成员对象(member object)的初始化顺序是:
* 在每一层,基类的构造函数最先被调用;
* 然后是成员对象的构造函数,被调用的顺序取决于类内的声明顺序,而不是构造函数的定义。
析构函数的调用顺序与构造函数的调用顺序正好相反。因为要稳定构造和析构过程中的依赖关系。
———-
Date: 2008/4/20
[Inheritance] @ pp.625
在子类(derived class)中,对基类(base class)中重名的函数,有两种情况:
* 重定义(redefine)。这个名字被子类重新定义。基类中重名的函数,无论参数和返回类型是什么,都会被隐藏(name hiding)。除非是后一种情况。
* 覆盖(override)。基类中的虚函数(virtual function)会被子类中的相同参数的函数覆盖。
名字隐藏(name hiding)的原理,详见 pp.627。主要是重用名字则意味着类的设计有问题,可能本该用 composition
而用了 inheritance。
———-
Date: 2008/4/20
[Inheritance] @ pp.633
不会被继承的成员函数,包括
* 构造函数
* 析构函数
* operator=
因为子类(derived class)和基类(base class)在这几个函数上肯定不能相同。
一般子类(derived class)的拷贝构造函数(copy
constructor)和赋值运算符函数(operator=)都应该调用基类(base
class)的相应函数(如通过构造函数初始化列表(constructor initializer
list))。否则编译器会调用默认构造函数(default constructor)进行拷贝,而对于赋值运算来说,什么也不做。
类型转换运算符(cast operator)会被自动继承。仔细想想,既然基类,也就是子类的一个子对象能自动转换,那么子类当然也能自动转换喽。
静态(static)成员函数会被继承。它们遵守相同的隐藏(name
hiding)规则。静态(static)成员不可能是虚拟的(virtual),所以不可能被覆盖(override)。
———-
Date: 2008/4/21
[Design] @ pp.637
关于使用组合(composition)还是继承(inheritance)一般的设计思路,
is-a: inheritance
has-a: composition
———-
Date: Mon, Apr 21, 2008 at 4:05 PM
[Inheritance] @ pp.640
通过 private 方式继承下来的成员,可以通过下面的方式使其可见,
class Pet {
public:
char eat() const { return ‘a’; }
int speak() const { return 2; }
float sleep() const { return 3.0; }
float sleep(int) const { return 4.0; }
};
class Goldfish : Pet { // Private inheritance
public:
Pet::eat; // Name publicizes member
Pet::sleep; // Both overloaded members exposed
};
———-
Date: 2008/4/21
[Design] @ pp.641
不过终归还是应该谨慎使用 private inheritance。能用 composition 的话更好。
———-
Date: 2008/4/21
[Inheritance] @ pp.643
重载的运算符(operator),除了 operator= 之外,其余的都会被继承。
显然,继承的运算符只是修改基类的子对象。
———-
Date: 2008/4/21
[Internal] @ pp.650
总结一下,编译器会自动生成的特殊成员函数(special member)是:
* 默认构造函数 (default constructor)
* 拷贝构造函数 (copy constructor)
* 赋值运算符 (operator=)
* 析构函数 (destructor)
自动生成的拷贝构造函数(copy constructor)和赋值运算符(operator=),递归调用基类(base
class)和数据成员(data member)的相应的函数,没有相应的函数的,进行位拷贝(bitwise copy)。
如果定义了拷贝构造函数和赋值运算符,那么编译器不再帮我们做任何事情。如果没处理基类和数据成员,那么在拷贝构造函数(copy
constructor)中它们的默认构造函数(default
constructor)将被调用,在赋值运算符(operator=)中什么也不做。所以不要忘了处理基类和数据成员。如,
Child(const Child& c) : Parent(c), i(c.i), m(c.m) {}
———-
Date: 2008/4/21
[Inheritance] @ pp.653
向上类型转换(upcast)是类型安全的,因而编译器允许隐式转换。使用的情况如,
* 指针或引用赋值
Instrument *p = &wind;
Instrument &r = wind;
* 指针或引用参数传递
void func(Instrument &r);
参考 pp.647。
———-
Date: 2008/4/22
[C and C++ compared] @ pp.674
C 和 C++ 的参数传递顺序都是从右向左压栈。这样,通过栈帧(stack frame)指针如 ebp + n (n > 0)
的方式就可以从左向右读取参数了,当然这里可能还有返回地址和返回值等。
这样做主要是为了实现 C 的可变参数(variable argument)函数。
———-
Date: 2008/4/22
[Internal] @ pp.676
编译器创建的默认构造函数(default constructor),其实也不是什么都不做,起码还要初始化 vptr 的。
———-
Date: 2008/4/22
[Design] @ pp.680
You create an abstract class when you only want to manipulate a set of
classes through a common interface, but the common interface doesn’t
need to have an implementation (or at least, a full implementation).
当需要通过一个通用的接口来操纵一组类,并且这个接口类不需要实现(或者至少不需要完全实现)时,我们使用抽象类。
———-
Date: 2008/4/22
[Design] @ pp.683
因为纯虚函数(pure virtual function)使抽象类(abstract
class)的对象不能被创建,也就迫使函数必须按引用(by
reference)传递该抽象类,从而也就避免了按值传递引起的对象切割(object slicing)。
———-
Date: Tue, Apr 22, 2008 at 9:55 PM
[Virtual Functions] @ pp.684
纯虚函数(pure virtual function)可以被定义。不过不能在类的声明内定义。如,
class Pet {
public:
virtual void speak() const = 0;
virtual void eat() const = 0;
// Inline pure virtual de
finitions illegal:
//! virtual void sleep() const = 0 {}
};
// OK, not defined inline
void Pet::eat() const {
cout << "Pet::eat()" << endl;
}
void Pet::speak() const {
cout << "Pet::speak()" << endl;
}
———-
Date: 2008/4/22
[Virtual Functions] @ pp.693
尽管在子类(derived class)中覆盖(override)虚函数(virtual
functions)必须保证参数和返回值(还有修饰符如const等)一致,不过也有个小小的例外:如果基类(base
class)中的虚函数返回A类的引用(或指针),那么子类中的覆盖可以返回A的子类的引用(或指针),编译器能自动进行向上类型转换(upcast)。如,
class PetFood {
…
};
class Pet {
public:
virtual string type() const = 0;
virtual PetFood* eats() = 0;
};
class Cat : public Pet {
public:
…
class CatFood : public PetFood {
};
CatFood* eats() { return &cf; }
private:
CatFood cf;
};
———-
Date: 2008/4/22
[Special Members] @ pp.696
不要小看构造函数的隐含调用,可能包括:检查 this 是否为空、基类构造函数的调用、成员对象的初始化、 vptr 的初始化,等等。
所以,构造函数的内联可能引起代码膨胀。不过这些问题可以到代码优化的时候再考虑。
———-
Date: 2008/4/22
[Virtual Functions] @ pp.698
构造函数(constructor)中的虚函数(virtual
function)没有多态性(polymorphism),换句话说,是静态绑定(early binding)的。
可以这样想,一个类在进入构造函数之前初始化 vptr,这时它无法知道它自己有什么样的子类(derived class),所以 vptr
只能指向这个类自己的 vtable。沿着继承关系,子类的构造函数逐个被调用,最后生成的对象的 vptr 才指向它自身类别的 vtable。
既然构造函数中的虚函数没有多态性,多数编译器会优化为静态绑定。
———-
Date: 2008/4/23
[Virtual Functions] @ pp.699
析构函数(destructor)中的虚函数(virtual
function)也没有多态性(polymorphism)。可以这样想,因为对象是自底向上析构的,从基类的析构函数呼叫子类覆盖的函数,实际上是在操作已经被析构了的部分,这显然是错误的。
析构函数(destructor)都应该是虚函数(virtual function),这样才能保证通过基类指针析构子类对象的时候行为是正常的。
析构函数可以是纯虚函数(pure virtual
function),但是一定要给出函数体,因为编译器要调用所有类的析构函数。不过纯虚的析构函数,除了能使类成为抽象类之外,没有额外价值。
———-
Date: 2008/4/23
[Inheritance] @ pp.712
dynamic_cast是一个类型安全(type-safe)的向下类型转换(downcast)操作,如果转换失败返回的是空指针。
dynamic_cast使用vtable中的信息来确定对象的实际类型。所以使用dynamic_cast需要对象有vptr。
尽管static_cast不允许跳出类的层次关系,使用静态类型转换终归是有风险的。这种情况还是用dynamic_cast好。
而dynamic_cast被大量使用,是一种设计缺陷。好的设计应该利用多态性(polymorphism),而不要经常类型转换。
———-
Date: 2008/4/23
[Templates] @ pp.731
一些术语:
@ pp.731
parameterized type
@ pp.733
instantiation
———-
Date: Wed, Apr 23, 2008 at 11:07 PM
[Templates] @ pp.734
模板类的声明,例如,
template<class T>
class Array {
…
T A[100];
public:
T& operator[](int index);
};
template<class T>
T& Array<T>::operator[](int index) {
return A[index];
}
———-
Date: 2008/4/23
[Templates] @ pp.734
一般模板类的声明,包括接口和实现,都应该包含在头文件(header)中。这样编译器才能在实例化(instantiation)的时候,能翻译相应的模板代码。
编译器会通知连接器(linker),去掉冗余的模板目标代码。
有的编译器(可能是msvc)有一种机制能把实例化的模板代码生成一个动态连接库(dll),这时模板的实现就应该放在一个单独的cpp源文件中。
———-
Date: 2008/4/24
[Design] @ pp.759
迭代器(iterator)的 end() 的惯例是:
end iterator 应被理解为 end sentinel,换句话说,就是 one past the
end,因此不应该被解引用(dereference)。
习惯的用法是,
while(start != end)
cout << start++ << endl;
———-
Date: 2008/4/26
[Templates] @ pp.777
只要支持模板函数(function template)中用到的运算,那么函数模板就可以实例化(instantiate)为支持该数据类型的函数。如,
template<class Iter>
void drawAll(Iter start, Iter end) {
while(start != end) {
(*start)->draw();
start++;
}
}
int main() {
Shape* sarray[] = {
new Circle, new Square, new Line
};
// Even works with array pointers:
drawAll(sarray,
sarray + sizeof(sarray)/sizeof(*sarray));
}
———-
Date: 2008/4/26
[Design] @ pp.778
迭代器(iterator)的最大价值在于,它提供了一种访问容器(container)类的形式上统一的手段,从而为函数模板(function
template),也就是算法(algorithm)的一般化(generic),提供了基础。
昨天一个同事说他电脑不识别新插入的U盘了,只能插以前插过的U盘,今天拿过来给我看。
我开着设备管理器看,发现插上新的U盘,“其它”里会出现比如 USB Mass Storage Device 之类的设备。说明这个设备还是被识别出来了,只不过没有自动安装驱动。为了正式这个说法,我用“重新安装驱动程序”手动安装了一遍驱动,很顺利,因为U盘驱动是 M$ 原厂的嘛。
我又把所有的 USB OHCI/ECHI 设备全部删除,让设备管理器“扫描硬件改动”,结果全都列在“其它”下面了,还是不自动安装。
有一个可能性,完全可以排除:数字签名。因为这些设备的驱动都是随 XP 发行的原厂驱动。可是为什么要手动安装呢?难道系统不应该自动安装驱动程序吗?
然后我看了 setupapi.log 没有发现任何异常,只不过就是除了我手动的卸载和安装操作,没有任何其它的设备安装操作。
那一定还有某些其它的触发设备安装的机制。我想起来有一个 newdev.dll,好像是设备安装向导,就查它怎么调用,但是执行了却没有反应。下面再搜 xp automatically install driver,结果全是别人问如何 stop 它,解答里也没有什么实质内容。
这里要补充一下,其实刚才还有一个疑点,就是设备管理器里本来就有一个没识别的设备,我查出是 Intel 2200 一类的无线网卡,他给我找出驱动盘来,可是安装驱动的时候却报错,说“设备管理器属性页或者添加新硬件向导已经打开”云云,而当时都没打开。
我重启到安全模式,问题现象依旧。
找出事件日志来分析,发现系统事件里有一个,事件源是 PlugPlayManager, ID 是 270,描述是“ Plug and Play user-interface dialogs have been suppressed in Factory Mode.”(中文版描述不像人话)。咦?工厂模式?把上面这句英文描述放到 Google 里搜,结果发现了这么一个同主题的页面,里面提到了“添加新硬件”向导也会出错。我跑到控制面板里一试,果然报错“一次只能安装一个设备”云云。看来我遇到的问题和他的差不多了,不过我可不想像他一样“sysprep -reseal”。
那么再搜“一次只能安装一个设备”吧,唉,中文网页还是没什么答案。然后借助 Google 蒙出了英文的错误信息“You can only install one device at a time.”,搜之,终于找到了正确答案[1][2]。简言之如下:
- 将 HKLMSYSTEMSetup 键下面的 FactoryPreInstallInProgress 和 AuditInProgress 这两个 REG_DWORD 值全部设置为 0×0,或者删除这两个值。
- 将 HKLMSOFTWAREMicrosoftFactory 键删除。
这样就把系统从工厂模式中“救”出来啦。然后重新启动系统,设备驱动就都开始自动安装啦。
P.S. 回来之后又通过上述两个注册表值反向查找,发现了 M$ 新闻组里原来也曾经提到过这个解法,只不过是在一个不太对题的主题下。另外再附上刚刚找到的一篇 XP的U盘除错合集 [中文翻译]。
换 Google Talk 了
一直在用 Gmail Notifier 查信,突然想起来经典的 cookie 安全隐患[1] [2]。看了一下 gnotify 的服务器端口,是 80。 天!
今天下了个最新的 Google Talk,服务器端口是 5222,查了一下 services,得知是 XMPP。在 Wikipedia 上查出 XMPP 支持 SASL 和 TLS。
然后用抓包工具看了一下, gnotify 确实是明文泄露了 cookie,因为是 http 所以连接很快就结束了。而 gtalk 建立连接之后有一个 starttls 的过程,而且连接很长时间一直保持不断,估计后面的就都是 TLS 了吧。
出于安全考虑,还是用 Google Talk 吧。
