C语言深度刨析(二)——符号


前言

参考书籍大家可入群下载:
qq群:829876251
也欢迎热爱C语言的朋友们一起学习交流!!!


符号有什么好说的呢?确实,符号可说的内容要少些,但总有些可以唠嗑地方。/这个符号在C语言里都用在那些地方?,如果你真正掌握了C语言,你就能很轻易的回答上来。这个问题就请读者试着回答一下吧(我刚开始好像就看出了除号,^_^)。
在这里插入图片描述
C语言的基本符号就有20多个,每个符号可能同时具有多重含义,而且这些符号之间相互结合使得C语言中的符号变得更加复杂起来。
你也许听过“国际C语言乱码大赛(IOCCC)”,能获奖的人毫无疑问是世界顶级C程序员。这是他们利用C语言的特点极限挖掘的结果。下面的这个例子就是网上广为流传的一个经典作品:

#include <stdio.h>
main(t,_,a)char *a;{
    
    return!0<t?t<3?main(-79,-13,a+main(-87,1-_,
main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?
main(2,_+1,"%s %d %d\n"):9:16:t<0?t<-72?main(_,t,
"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#\
;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l \
q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# \
){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' \
iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c \
;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# \
}'+}##(!!/")
:t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,
"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m.vpbks,fxntdCeghiry"),a+1);}

还没发狂?看来你的抵抗力够强的。这是IOCCC 1988年获奖作品,作者是Ian Phillipps。毫无疑问,Ian Phillipps是世界上最顶级的C语言程序员之一。你可以数数这里面用了多少个符号。当然这里并不会讨论这段代码,也并不是鼓励你也去写这样的代码(关于这段代码的分析,你可以上网查询)。恰恰相反,要告诉你的是:
大师把代码写成这样是经典,你把代码写成这样是垃圾!
所以垃圾和经典之间,你需要做一个抉择。

注释符号

几个似非而是的注释问题

C语言的注释可以出现在C语言代码的任何地方。这句话对不对?这是我当学生时我老师问的一个问题。我当时回答是不对。好,我们看看下面的例子:

int /*..*/i;
char* s="abcdefgh   //hijklmn";
//Is it a\
vaild comment?
in/*..*/t i;

我们知道C语言可以有两种注释方式:/**/和//。那上面3条注释对不对呢?建议你亲自在编译器中测试一下。上述三条注释都是正确的,最后一条不正确
第一个,有人会认为编译器剔除掉注释后代码会被解析成inti,所以不正确。编译器的确会将注释剔除,但不是简单剔除,而是用空格代替原来的注释
第二个,我们知道双引号引起来的都是字符串常量,那么双斜杠也不例外。
第三个,这是一条合法的注释,因为\是一个接续符。
第四个,前面说过注释会被替换成空格,那这条注释不正确就很好理解了。
注意:/*...*/这种形式的注释不能嵌套,因为/*总是与离它最近的*/匹配

y = x/*p

y = x/*p,这是表示x除以p指向的内存里的值,把结果赋值为y?我们可以在编译器上测试,编译器提示出错。
实际上,编译器把/*当作是一段注释的开始,把/*后面的内容都当作注释内容,直到出现*/为止。这个表达式其实只是表示把x的值赋给y,/*后面的内容都当作注释。但是由于没有找到*/,所以提示出错。
我们可以修改如下:

y = x/ *p
//或者
y=x/(*p)

这样的话,表达式的意思就是x除以p指向的内存里的值,把结果赋值给y了。
也就是说只要斜杠(/)和星号(*)之间没有空格,都会被当作注释的开始,这一点一定要注意

如何编写出出色的注释

注释写的出色非常的不容易,但是写的糟糕确实人人可为之。糟糕的注释只会帮倒忙。
小故事:
在这里插入图片描述
在这里插入图片描述
出色注释的基本要求

  • 注释应当准确、易懂、防止有二义性。错误的注释不但无益反而有害。
  • 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要及时删除。
  • 注释是对代码的"提示",而不是文档。程序中的注释应当简单明了,注释太多了会让人眼花缭乱。
  • 一目了然的语句不加注释
  • 对于全局数据(全局变量、常量定义等)必须要加注释。
  • 注释采用英文,尽量避免在注释中使用缩写,特别是不常用的缩写。
  • 注释的位置应与被描述的代码相邻,可以与语句在同一行,也可以在上行,但不可放在下方。同一结构中不同区域的注释要对齐。
  • 当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。
  • 注释的缩进要与代码的缩进一致。
  • 注释代码段时应注重"为何做,而不是怎么做"——指明意图
  • 数值的单位一定要注释
  • 对变量的范围给出注释
  • 对一系列的数字编号给出注释,尤其在编写底层驱动程序的时候。(比如管脚编号)
  • 对于函数的入口出口数据给出注释

接续符和转义符

C语言里以反斜杠\表示断行。编译器会将反斜杠剔除掉,跟在反斜杠后面的字符自动接续到前一行。但是注意:反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格
反斜杠除了可以被用作接续符,还能被用作转义字符的开始标识
常用的转义字符及其含义:

转义字符 含义
\n 回车换行
\t 横向跳到下一个制表位置
\v 竖向跳格
\b 退格
\r 回车
\f 走纸换页
\ 反斜杠符""
单引号符
\a 鸣铃
\ddd 1~3位八进制数所代表的字符
\xhh 1~2位十六进制数所代表的字符

单引号、双引号

我们都知道双引号引起来的都是字符串常量,单引号引起来的都是字符常量。但初学者还是很容易弄错这两点。比如'a'"a"完全不一样,在内存里面前者占1byte,后者占2byte。详细的分析见后面专栏博客。

逻辑运算符

||和&&是我们经常用到的逻辑运算符,与按位运算符|&是两码事。注意区分。

位运算符

C语言中位运算符包括下面几种:

字符 含义
& 按位与
| 按位或
^ 按位异或
~ 取反
<< 左移
>> 右移

左移和右移

左移运算符<<是双目运算符。其功能把左边的运算数的各二进制位全部左移若干位。由<<右边的数指定移动的位数,高位丢弃,低位补0.
右移运算符>>是双目运算符。其功能是把左边的运算数的各二进制位全部右移若干位。由>>右边的数指定移动位数。注意:对于有符号数,在右移时,符号位将随着移动。当为正数时,最高位补0;而为负数时,符号位为1,最高位补0或是补1取决于编译系统的规定

0x01<<2+3的值为多少?

结果为7吗?测试一下。结果为32?
因为+号的优先级比移位运算符的优先级高。
再思考,在32为系统下,0x01<<2+30;或0x01<<2-3;
这样行吗?
不行!一个整型长度为32位,左移32位发生什么事?溢出!左移-1位呢?反过来移?所以,左移和右移的位数是有讲究的。
左移和右移的位数不能大于数据的长度,不能小于0

花括号

花括号没个人都见过,很简单吧。
char a[10]={"abcde"};
这个表达式正确吗?再看看下面这个例子:
char a[10]{="abcde"};
这个表达式呢?
花括号的作用是什么呢?我们平时写函数,if、while、for、switch语句等都用到了它,但有时又省略掉了它。简单来说花括号的作用就是打包。你想想以前使用花括号是不是为了把一些语句或代码打个包包起来,使之形成一个整体,并与外界绝缘。这样理解就说的通了。

++、–操作符

int i=3;
++i + ++i + ++i = ? (不同编译器处理效果不同,但注意++和+的优先级,答案为18 or 16。)
如何理解 ++i+++i+++i? 分解为:++i + ++i + ++i
(C语言会按照贪心法处理,编译器将程序分解成符号的方法是,从左到右一个一个字符地读入,如果该字符可能组成一个符号那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断。直到读入的字符组成的字符串已不再可能组成一个有意义的符号。)

2/(-2)的值是多少?

2/(-2)的值为多少?2%(-2)的值呢?
假定我们让a除以b,商为q,余数为r:
q=a/b;
r=a%b;
这里我们不妨先假定b大于0
我们希望a、b、q、r之间维持什么样的关系呢?

  1. 最重要的一点,我们希望q*b + r == a,因为这是定义余数的关系。
  2. 如果我们改变a的正负号,我们希望q的符号也随之改变,但q的绝对值不会变。
  3. 当b>0时,我们希望保证r>=0且r<b。
    这三条性质是我们认为整数除法和余数操作所应具备的。但是,很不幸,它们不可能同时成立。
    分析(-3)/2即可明白三条性质无法同时成立。
    因此,C语言或其他语言在实现整数除法截断运算时,必须放弃上述三条性质中的至少一条。大多数编程语言选择放弃第三条,而改为要求余数与被除数的正负号相同。这样性质1和2就可以得到满足。大多数C语言编译器也都是如此。
    所以
    2/(-2)=-1
    2%(-2)= 0

运算符的优先级

运算符的优先级表

在这里插入图片描述
在这里插入图片描述

同一优先级的运算符,运算次序由结合方向所决定
4个一级>单目>双目>三目>其他

一些容易出错的优先级问题

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_56145255/article/details/130785210