Tuesday, May 27, 2008

Using typedef to Curb Miscreant Code

Defining Mnemonic Type Names
Code Simplification

The typedef I've shown thus far behave like a #define macro that substitutes a synonym with its actual type. Yet unlike macros, typedef is interpreted at compile-time, thereby enabling the compiler to cope with textual substitutions that are beyond the preprocessor's capabilities.

For example,

typedef int (*PF) (const char *, const char *);

This declaration introduces the type PF as a synonym for 'pointer to function taking two const char * arguments and returning int'. In the following function declaration, the use of this typedef is indispensable:

PF Register(PF pf);// callback func as argument

Register() takes a
callback function of type PF and returns the address of a function with a similar signature that was previously registered. Take a deep breath. I'm about to show you how this declaration would look without a typedef:

int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *);

Few programmers understand what it means, not to mention its risk of introducing mistakes into such convoluted code. Obviously, the use of a typedef here isn't a prerogative but a must.
This brings us to trap #2:

typedef register int FAST_COUNTER; //error

This won't compile. The problem is that you can't have multiple storage class specifiers in a declaration. Because the token typedef already occupies the position of a storage class specifier, you can't use register (or any other storage class specifier) in a typedef declaration.

Facilitating Cross-platform Development
typedefs have another important use, namely defining machine-independent types.

For example, you can define a floating point type called REAL that has the highest precision available on the target machine:

typedef long double REAL;

On machines that don't support long double, this typedef will look like this:

typedef double REAL;

And on machines that don't even support double:

typedef float REAL;

You can compile applications that use the type REAL on every platform without making any changes to the source file. The only thing that will change is the typedef itself. In most cases, even this tiny change will be totally automatic thanks to the wonders of
conditional compilation. Nifty, isn't it?

Monday, May 26, 2008

Using typedef to Curb Miscreant Code [by Danny Kalev, C++ Pro]

Defining Mnemonic Type Names

The most common use of typedef is creating mnemonic type names that document the programmer's intention. The type being declared appears in the position of a variable's name, right after the keyword 'typedef'. For example,

typedef int size;

This declaration defines a synonym for int called size. Notice that a typedef doesn't create a new type; it merely adds a synonym for some existing type. You can use size in any context that requires int:

void measure(size * psz);
size array[4];
size len = file.getlength();

typedef may also disguise composite types such as pointers and arrays. Define a typedef that will be used every time you need an array of the same type and size:

typedef char Line[81];
Line text, secondline;
getline(text);

Similarly, hide pointer syntax like this:

typedef char * pstr;
int mystrcmp(pstr, pstr);

This brings us to the first typedef trap.
The standard function strcmp() takes two arguments of type 'const char *'. Therefore, it might be tempting to declare mystrcmp() like this:
int mystrcmp(const pstr, const pstr);
This is wrong, though. The sequence 'const pstr' is interpreted as 'char * const' (a const pointer to char), rather than 'const char *' (a pointer to const char).

You can easily solve this problem, though:
typedef const char * cpstr; //a pointer to const char
int mystrcmp(cpstr, cpstr); //now correct

Remember: Whenever you declare a typedef for a pointer, adding const to the resulting typedef name makes the pointer itself const, not the object.

[C++语法] 关键字typedef用法(转) [C++语法] 关键字typedef用法(转)

  • 定义结构体类型
typedef struct {
int x;
int y;
} point;

point var_1; // 定义了变量var_1
point array_1 [10]; // 定义了数组array_1

struct {
point part1;
int part2;
} cplx; // 定义了复杂类型变量cplx

需要说明的是,我们还可以使用下面的方法来定义结构体变量:
对于定义链接队列中的结点,我们可以这样实现:

typedef struct t_node {
int Value;
struct t_node *next;
} Node;

当然也可以这样定义:

typedef strcut t_node Node;
struct t_node {
int Value;
Node *next;
};

  • 定义数组类型
与定义结构体类型相似,可以使用typedef来定义数组类型,例如:

typedef int MyIntArray [100];

那么程序中的

MyIntArray ia;

就相当于

int ia[100];

  • 定义函数指针
看下面的代码:

typedef void (*FUNCADDR)(int)

此处FUNCADDR是指向这样一个函数的指针,该函数的返回值为void类型,函数有一个int型的参数。再例如:

void print (int x){
printf (“%d\n”, x);
}

int main (int argc, char *argv[]){
FUNCADDR pFunc;
pFunc = print; // 将指针指向print函数
(*pFunc)(25); // 调用函数print
return 0;
}

函数指针一般用于回调函数、中断处理过程的声明,以及在面向对象程序设计中对事件处理过程的声明。

  • 定义类类型
类是面向对象程序设计语言中引入的一种新的数据类型,既然是数据类型,就可以使用typedef对其进行定义:

typedef class {
private:
int a;
public:
int b;
} MyClass;

其实这和定义结构体类型非常相似,不过很少有人这么使用。

Wednesday, May 21, 2008

原码、补码、反码

数分为有符号数和无符号数。 原码、反码、补码都是有符号定点数的表示方法。一个有符号定点数的最高位为符号位,0是正,1是副。以下都以8位整数为例。
1 原码就是这个数本身的二进制形式。
例如,0000001 就是+1,1000001 就是-1。
2 正数的反码和补码都是和原码相同。
负数的反码是将其原码除符号位之外的各位求反。 [-3]反=[10000011]反=11111100
负数的补码是将其原码除符号位之外的各位求反之后在末位再加1。 [-3]补=[10000011]补=11111101。 一个数和它的补码是可逆的。

为什么要设立补码呢?
第一是为了能让计算机执行减法: [a-b]补=a补+(-b)补。
第二个原因是为了统一正0和负0 。正零:00000000, 负零:10000000, 这两个数其实都是0,但他们的原码却有不同的表示。 但是他们的补码是一样的,都是00000000 特别注意,如果+1之后有进位的,要一直往前进位,包括符号位!(这和反码是不同的!) [10000000]补 =[10000000]反+1 =11111111+1 =(1)00000000 =00000000(最高位溢出了,符号位变成了0)。

有人会问 10000000这个补码表示的哪个数的补码呢?
其实这是一个规定,这个数表示的是-128 所以n位补码能表示的范围是 -2^(n-1)到2^(n-1)-1 比n位原码能表示的数多一个 。

总结:在计算机内,定点数有3种表示法:原码、反码和补码
所谓原码就是前面所介绍的二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
补码表示法规定:正数的补码与其原码相同;负数的补码是在其反码的末位加1。
1、原码、反码、补码的表示方法

1)原码:在数值前直接加一符号位的表示法。
例如: 符号位 数值位
[+7]原= 0 0000111 B
[-7]原= 1 0000111 B
注意:
a. 数0的原码有两种形式:[+0]原=00000000B [-0]原=10000000B
b. 8位二进制原码的表示范围:-127~+127

2)反码:
正数:正数的反码与原码相同。
负数:负数的反码,符号位为“1”,数值部分按位取反。
例如: 符号位 数值位
[+7]反= 0 0000111 B
[-7]反= 1 1111000 B
注意:
a. 数0的反码也有两种形式,即
[+0]反=00000000B
[- 0]反=11111111B
b. 8位二进制反码的表示范围:-127~+127

3)补码
1)模的概念:把一个计量单位称之为模或模数。例如,时钟是以12进制进行计数循环的,即以12为模。在时钟上,时针加上(正拨)12的整数位或减去(反拨)12的整数位,时针的位置不变。14点钟在舍去模12后,成为(下午)2点钟(14=14-12=2)。从0点出发逆时针拨10格即减去10小时,也可看成从0点出发顺时针拨2格(加上2小时),即2点(0-10=-10=-10+12=2)。因此,在模12的前提下,-10可映射为+2。由此可见,对于一个模数为12的循环系统来说,加2和减10的效果是一样的;因此,在以12为模的系统中,凡是减10的运算都可以用加2来代替,这就把减法问题转化成加法问题了(注:计算机的硬件结构中只有加法器,所以大部分的运算都必须最终转换为加法)。10和2对模12而言互为补数。
同理,计算机的运算部件与寄存器都有一定字长的限制(假设字长为8),因此它的运算也是一种模运算。当计数器计满8位也就是256个数后会产生溢出,又从头开始计数。产生溢出的量就是计数器的模,显然,8位二进制数,它的模数为2^8=256。在计算中,两个互补的数称为“补码”。
2)补码的表示:
正数:正数的补码和原码相同。
负数:负数的补码则是符号位为“1”,数值部分按位取反后再在末位(最低位)加1。也就是“反码+1”。
例如: 符号位 数值位
[+7]补= 0 0000111 B
[-7]补= 1 1111001 B
补码在微型机中是一种重要的编码形式,请注意:
a.采用补码后,可以方便地将减法运算转化成加法运算,运算过程得到简化。正数的补码即是它所表示的数的真值,而负数的补码的数值部份却不是它所表示的数的真值。采用补码进行运算,所得结果仍为补码。
b.与原码、反码不同,数值0的补码只有一个,即 [0]补=00000000B。
c.若字长为8位,则补码所表示的范围为-128~+127;进行补码运算时,应注意所得结果不应超过补码所能表示数的范围。