C专家编程 笔记

简介:

C语言中的符号重载

C语言非常的简洁, 以至于不愿意用太多的符号, 这样有很多符号在不同的地方有不同的含义

这样会让用户很困惑, 这是c的语言特性, 也是设计上的一些失误

static    
在函数内部,表示该变量的值在各个调用间一直保持延续性;
对于函数,表示该函数只在本文件中可见

extern
 

用于变量,表示该变量在其它地方定义;
用于函数定义, 表示全局可见(属于冗余的)

void     

用于参数列表中,表示该函数参数为空,如int main(void);
用于返回值,表示该函数返回void,即不返回任何值,相当与pascal中的过程;
在指针声明中,表示通用指针


乘法运算符;
 
用于指针前,表示对指针所指内容的间接引用;
用于声明中表示指针,如int *pi,表示指向整形的指针,char *strcpy(...)表示函数的返回值为指针

&    
  
位与运算符;
取地址操作符

() 

函数定义中,包围函数形式参数表,如int main(int argc, char **argv);
调用一个函数,如srand();
改变表达式的运算顺序,如 a * ( b + c );
类型转换, 如 (int*)x;
定义带参数的宏,如 #define MAX(a, b) ((a) > (b)) ? (a) : (b)
包围sizeof操作符的操作数(如果是类型名),如 sizeof(int)

 

数组和指针的本质区别

C编程新手最常听到的一句话是'数组和指针是相同的', 不幸的是, 这是一种危险的说法, 他并不正确.

最典型的一个错误是

file 1:
int mango[100];
file 2:
extern int *mango;


这样的声明是错的, 必须声明为extern int  mango[];

关于声明的意义在上篇讲解extern时已经详细说明过

 

我们首先要区分"地址y"和"地址y的内容"之间的区别, 这是一个相对微妙之处, 因为大多数编程语言中我们用同一个符号来表示两样东西, 并由编译器根据上下文环境来判断它的具体含义.

int X, Y;

X = Y;

这边我们就来深入讨论一下这个赋值语句, 看上去X和Y是同一种东西, 都是int型变量

但对于编译器而言, 一个赋值语句两边的东西是不一样的 (以汇编的角度思考)

左值  =  右值;

左值是地址, 右值是数据, 是有本质区别的

所以上面的X是X代表的地址, Y是Y代表的地址里面的内容

所以对于编译器而言,

X=2, 这个很直接, 把2放到X代表的地址上

X=Y, 需要先从Y代表的地址上读出数据, 再放到X代表的地址上

需要注意, 左值是在编译时就可以明确知道的, 因为编译器会为每个变量分配一个地址

而右值往往只有在运行时才可知的

 

那么下面详细看看为什么大家会误认为两者相同

int a[10], *p, *q;
p = a ;
q = p+1 ;

q = a+1;

int i;

i = a[0];

i = (*a);

i = (*p);

如上的代码, 你发现可以象操作指针一样来操作数组变量名a, 貌似a和指针p时等价的

其实非也, a其实代表数组的首地址, 就是个常量, 进行q = a+1, 类似于X = 2+1

而p是指针变量, p=a后, p代表的地址里存放了a,即数组首地址

那么q = p+1, 需要从p代表的地址中读出a, 并放到q代表的地址中去, 类似于X = Y+1

同样对于*操作, 是取地址上的内容, *a直接取a地址上的内容, 而*p是要先从p代表的地址取出地址数据, 再取该地址数据上的内容

而且两者还有一个区别是时间上的差别

对于q = a+1, 因为a代表常量, 在编译时就可以算出q的值

而对于q = p+1, p是变量, 必须到运行时才能知道q的值

 

两者是有本质区别的, a代表常量, 有人把它称为'常量指针', 是不能赋值或改变的指针, 这绝对的是误导

a就不是指针, 根本就不能作为左值, 你有见把2作为左值的吗

此处可以认为a等同于&p

之所以大家会混淆, 完全是因为c语言的设计不规范导致的, 相同的语句却有着完全不同的操作

 

此处个人觉得是数组的设计欠缺妥当

int i, * p;

对于一般的变量, 无论是i还是p, 作为左值表示变量名所代表的地址, 作为右值表示变量名所代表的地址上存放的数据. 其实i和p从这个层面上来讲, 完全没有什么分别, 只不过p中存放的数据是地址而不是其他.

 

但是对于int a[];

就不一样了, 因为a只是作为一个数据集合的开始位置, a无法作为左值(设计者认为这样的不恰当). 同时a作为右值时, 也无法取a地址上的数据, 因为它只是个开始位置, 你不知道取多少. 所以设计者打破了普通变量的规则, 把a当作一个地址常量来看待, 即当a作为右值时, 直接取a代表的地址作为右值, 而不会象通常变量一样去取地址上的数据作为右值.

问题是, 根据c的语法, 其在使用时和指针又十分相似, 这样的设计是不严谨的. 其实如果这样设计会稳妥些,

将a[]作为一个整体, 不能单独对a做任何操作, 如需取地址则用&(a[])

不过这样应该不符合c语言的简单美学...c语言是灵活, 简单的语言, 但是很多设计太随意, 不够严谨, 所以导致很多地方不符合逻辑, 很难理解

 

再论数组

本书和很多讲解数组和指针的文章过于复杂, 我个人觉得那是把简单的问题复杂化了.

我在上面应该已经把数组和指针的关系讲清楚了, 下面补充几点

1. 书中指出数组和指针在作为函数参数时是一样的(equivalent), 这个说法来自"C Programming Language, 2nd Ed, Kernighan & Ritchie"

如下,

char my_array[10];
char * my_ptr;
...
i = strlen( my_array );
j = strlen( my_ptr );

在c语言中, 作为函数的参数都是传值的, 但是数组却是个例外, 确实对于大数组, 传值明显是相当低效的

所以当你自己把数组作为函数参数的时候, 编译器其实是生成一个指向数组首地址的指针, 并把这个指针作为参数传入函数.

其实如果想把整个数组传值传入函数, 象前面说的, 只要把数组封装在结构中, 即可, 不过不推荐这样, 甚至在把结构体对象当作参数的时候, 要check结构体内是否有数组, 如果有数组, 最好把地址作为参数.

所以在作为函数参数时, 数组首地址会被转化为指针作为参数, 这个只是编译器出于方便统一这样处理.

在这点上说两者equivalent, 我个人觉得是不妥当的

 

2. 书中说"表达式中的数组名就是指针", 这个说法是相当危险的, 会把刚有些明白的程序员再次绕晕

int a[100];

int *p;

p = a+1;

书中的意思是, 这个操作中, 编译器出于处理的方便, 会先把a封装成一个指针, 然后赋值给p, 所以可以把a看作是指针, 这个说法很不负责

编译器可以出于处理方便的需要把a封装成指针, 但是数组名a绝对不是指针, 如果a是指针, 就应该可以作为左值进行赋值, a = p

实际上是不行的, a就是地址常量

 

3. 数组中的[]符号就代表地址偏移

在编译的时候, a[i] 会被改写成 *(a+i)

对于+是没有前后顺序的

所以你这样写, i[a], 也是可以的, 因为在编译时, 也是被改写成 *(i+a)

所以虽然看着很bt, 但是其实效果是一样的, 这就是[]的真正意义.


本文章摘自博客园,原文发布日期:2011-07-05

目录
相关文章
|
6月前
|
人工智能 搜索推荐 开发者
|
开发者 vr&ar 城市大脑
首次公开!技术人进阶必看的10位阿里大牛思维方式 | 开发者必读(066期)
最炫的技术新知、最热门的大咖公开课、最有趣的开发者活动、最实用的工具干货,就在《开发者必读》!
1297 0
|
JavaScript 前端开发 程序员
《C专家编程》一导读
你是否注意到市面上存有大量的C语言编程书籍,它们的书名具有一定的启示性,如:C Traps and Pitfalls(本书中文版《C陷阱与缺陷》已由人民邮电出版社出版), The C Puzzle Book, Obfuscated C and Other Mysteries,而其他的编程语言好像没有这类书。
1417 0
|
C语言 编译器 自然语言处理