By:LShang

Blog:http://www.cnblogs.com/LShang

 

C 语言的发展

1965-7(BCPL)->1969(B)->1971(New B)->1972-3(早期的C)->1976-9(K&C)->1983-9(ANSI C)->

C 的许多特性是为了方便编译器设计者而建立的

  1. 数组下标从0开始(定义数组a[100]的合法范围是a[0] ~ a[99])
  2. 基本数据类型直接与底层硬件对应
  3. auto关键字只对创建符号表入口的编译器设计者有意义(进入程序块时自动分配内存)
  4. 表达式中的数组名可以看作是指针(并非永远如此)
  5. float会被自动扩展为double(仅在最初如此,ANSI C不再如此)
  6. 不允许嵌套函数(函数内部不允许包含另一个函数的定义)
  7. register关键字(可以提供程序中的热门变量,使之将其存放到寄存器中。)
关于register关键字,书中说
“这个设计可以说是一个失误,如果让编译器在使用各个变量时自动处理寄存器的分配工作,显然比一经声明就把这类变量在生命期内始终保留在寄存器里要好。使用register关键字,简化了编译器,却把包袱丢给了程序员。”

C 编译器不曾实现的一些功能必须通过其他途径实现:

标准 I/O 库和 C 预处理器

最早的可移植 I/O 库出现在1972年,由 Mike Lesk 编写

C 预处理器主要实现三个功能

  • 字符串替换
  • 头文件包含
  • 通用代码模板的扩展(宏)
宏的实际参数只按照原样输出。
在宏的扩展中,空格会对扩展的结果造成很大影响。
#define a(y) a_expanded(y)
a(x)
//被扩展为
a_expanded(x);
//
#define a (y) a_expanded(y)
//则被扩展为
(y) a_expanded (y)(x);
书中建议:
  1. 宏最好只用于命名常量,并为一些适当的结构提供简捷的记法。
  2. 宏名应该大写,这样便容易与函数名区分
  3. 千万不要使用 C 预处理器来修改语言的基础结构,因为这样 C 就不再是 C 

K&R C 和 ANSI C

1978年,《The C Program Language》一书出版,其作者 Brian Kernighan 和 Dennis Ritchie 名声大噪。这个版本的 C 被称为 K&R C

1983年,美国国家标准化组织(ANSI)成立了 C 语言工作小组,开始了 C 语言的标准化工作

1989年12月,ANSI委员会接受了 C 语言标准草案,随后国际标准化组织 ISO 也接纳了 ANSI C 标准(C 89标准)

1990年初,ANSI 重新采纳了 ISO C(删除掉了Rationale一节),所以原则上说日常所说的标准 C 应是 ISO C 而不是 ANSI C

K&R C 和 ANSI C 的区别

  1. 函数原型的增加
  2. 关键字的增加
  3. 安静的改变
  4. 其他区别

原型是函数声明的扩展,这样不仅函数名和返回类型已知,所有形参类型也是已知的

参数传递的过程类似于赋值

每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)

参数传递时的相容与不相容

char *cp;
constchar *ccp;
ccp = cp;
//可以相容

char * 是一个指向没有限定符的 char 型指针

const char * 是一个指向有 const 限定符的 char 型指针

char 类型与 char 类型可以相容,左操作数(形参)具有右操作数(实参)所指向类型的限定符(无限定符),再加上自身的限定符(const)。

char *cp;
constchar *ccp;
cp = ccp;
//不可以相容

char * 是一个指向没有限定符的 char 型指针

const char * 是一个指向有 const 限定符的 char 型指针

char 类型与 char 类型可以相容,左操作数(形参)不具有右操作数(实参)所指向类型的限定符(const)

测试代码

#include <stdio.h>

int main()
{
char *cp = {"By LShang"};
constchar *ccp = cp;
printf("%s\n",ccp);
return0;
}

--------------------Configuration: Test - Win32 Debug--------------------
Compiling...
Test.c

Test.obj - 0 error(s), 0 warning(s)

#include <stdio.h>

int main()
{
constchar *cp = {"By LShang"};
char *ccp = cp;
printf("%s\n",ccp);
return0;
}

--------------------Configuration: Test - Win32 Debug--------------------
Compiling...
Test.c
C:\Code\Test\Test.c(5) : warning C4090: 'initializing' : different 'const' qualifiers

Test.obj - 0 error(s), 1 warning(s)

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

借用书中代码

foo(constchar **p) {}
main(int argc,char **argv)
{
foo(argv);
}

根据 ANSI C 标准,此代码中的形参和实参是不相容的

const char **p 指向一个没有限定符的 const char * 型指针

char **argv 指向一个没有限定符的 char * 型指针

虽然形参具有实参所指向类型的限定符(无),但是由于形参和实参所指向的类型不一样,所以它们不相容

const char * 类型与 char * 类型可以相容,但相容性无法传递,所以即使const char * 类型与 char * 类型可以相容也不代表const char ** 类型和 char ** 类型可以相容,所以虽然const char ** 类型和 char ** 类型都没有限定符,但是它们之间不能进行赋值

以上内容均根据理论推出,书中提到这种赋值方式在 C ++编译器中是合法的,所以无法在 VC 环境下测试,TC 2.0环境貌似也没有警告。。。

const 限定符

关键字 const 并不能将一个变量变成一个常量

在一个符号前加上 const 限定符只是表示这个符号不能被赋值,但它并不能防止通过其他方法来修改这个值(如指针)

constint n;   
// 带有限定符(const)的 int 型变量,n 不可改变
constint *n;
// 指向一个带有限定符(const)的 int 的指针,*n 不可改变
constint **n;
// 指向一个带有限定符(const)的 int 的指针的指针,**n 不可改变
... ...

但可以通过其他方式来间接改变带有 const 限定符的变量的值

#include <stdio.h>

int main()
{
constint n = 0;
int *pn = &n;
printf("n = %d\n",n);
*pn = 1;
printf("n = %d\n",n);
return0;
}

虽然在编译时会产生一条警告信息

--------------------Configuration: Test - Win32 Debug--------------------
Compiling...
Test.c
C:\Code\Test\Test.c(6) : warning C4090: 'initializing' : different 'const' qualifiers

Test.obj - 0 error(s), 1 warning(s)

但是程序可以正常链接和运行

而且也满足了间接修改一个带有 const 限定符的变量的值的要求

n = 0
n = 1
Press any key to continue

算术转换的改变

K&R C 采用无符号保留(unsigned preserving)原则,就是当一个无符号类型与 int 或更小的整型混合使用时,结果类型是无符号类型

ANSI C 采用值保留(value preserving)原则,就是把几个整型操作数混合使用时,结果类型有可能是符号数,也可能是无符号数,取决于操作数的类型的相对大小

采用通俗语言,ANSI C 标准所表示的意思大致如下:

  当执行算术运算时,操作数的类型如果不同,就会发生转换。数据类型一般朝着浮点精度更高、长度更长的方向转换,整型数如果转换为 signed 不会丢失信息,就转换为 signed,否则转换为 unsigned。

对无符号类型的建议

  • 尽量不要再你的代码中使用无符号类型,以免增加不必要的复杂性。
  • 尽量使用像 int 那样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况。
  • 只有在使用位段和二进制掩码时,才可以用无符号数。应该在表达式中使用强制类型转换,是操作数均为有符号数或无符号数,这样就不必有编译器来选择结果类型。