2021.09.29 00:25:19
先上两个赋值语句。
int a = 5;
//版本1
float c_1 = (a-9)*5/9;
//版本2
float c_2 = (a-9)*5/9.0f;
那么请问,c_1是否和c_2的值相等呢?
不相等。
因为这里发生了隐式类型转换。对于第一个语句,5/9
的值实际上是整数,这是因为9和5都是整数。第二个语句则会得到符合我们直觉的结果。
虽然现如今很多编译器已经支持这种自动转换,但是为了确保兼容性,我们还是严格按照数据类型编写赋值语句吧。
这里再说一句,编译器遇到这样的表达式时,会进行自动类型转换,将所有操作数的类型转换为其中容纳范围最大的数据类型。顺序是short->int->unsigned int->long->unsigned long->float->double->long double
先来看看这个语句。
int ch;
while((ch = getchar()) != '\n')
{
//statements
}
在一些情况下,它的确可以正常运行。但是,在一些特殊情况下,它会无限输出。因为它的结束条件是读入的字符不是换行符。换句话来说,就是此处while的边界条件太少了,以至于循环很可能无限执行下去,导致输出超限。
之前我也不是很清楚,所以说得很抽象。实际上,这里需要使用while((ch=getchar())!=EOF)
,这才是正确的终止条件。
除此之外,ch
务必声明为int
类型。因为char
类型实际上就是短整型,所以读取的字符如果ASCII码过大(超过256)就会发生溢出,从而有可能和EOF
的值相等,从而异常退出。
请看下面的赋值语句,想想a,b的数据类型。
int* a,b;
答案是:a是指向int的指针,而b是int类型的变量。原因是什么?因为*是和a在一起的。所以通常我们不会这么写,我们一般会把*放在靠近变量的一侧来避免混淆。
再来看看这个。
#define INTPTR int*
......
,b,c; INTPTR a
其中,只有a是指向int的指针类型。b和c都是整数类型。所以,宏指令并不能很好地处理指针类型。因此我们通常会用typedef取而代之。
这是转义字符\
。注意,对于printf()而言,未定义的转义字符会直接输出反斜杠后的字符。
另外,有一种三符号系统,用\??*
来表示其他符号。所以连续使用问号时请务必注意。
在很多语言中,单引号和双引号是等效的(比如Python)。但是对于C语言而言,单引号内只能表示单个字符,而双引号只能表示字符串。
来看看这个。
=s+(t=u-v)/3; r
这个表达式合法吗?合法。因为C语言中,赋值并不是语句,而是表达式。所以它可以出现在任何允许出现的地方。
既然是表达式,那么它就有返回值。 赋值表达式的值就是左操作数的新值。
再来看看这个语句。
int a;
(a=4)=3*4;
合法吗?不合法。括号项是表达式,它作为另一个赋值表达式的左值参与赋值运算。但是左值不能是常量,而(a=4)
的值是4,显然不能被赋值。
逗号运算符将几个表达式相连接,构成一个表达式。这个表达式的值就是最右边的子表达式的值。
在这里有一个小技巧:
while(expression1)
,
statement1; statement2
事实上这两条语句都会循环运行。此处的逗号运算符将两条语句合并成一条语句。
除了这里可以这么用,可以在循环条件中这么写:如果这么做能使程序更优秀的话。
C的for
是while
的一种常用语句组合形式的简写法。语法如下所示:
for(expression1; expression2; expression3)
; statement
其中statement
称为循环体。expression1
为初始化部分,只在循环开始时执行一次。expression2
称为条件部分,它在循环体每次执行前都要执行一次,和while
语句中的表达式一样。expression3
称为调整部分,它在循环体每次执行完毕,在条件部分即将执行前执行。
这三个表达式都是可省略的。若省略条件部分,表示测试的值始终为真。
表达式可以出现在任何地方,而语句只能出现在单独的一行。C语言没有赋值语句,它只有赋值表达式。
所以嘛,表达式能出现的地方,都可以赋值,这就有了上面那个奇怪的赋值2。
我们知道,变量是有作用域的。也就是说,它可以声明在最外层,或者是代码块开头。其实,函数的声明也一样。看看这个:
#include <stdio.h>
int main(void)
{
int square(int a)
{
return a*a;
}
int num;
("%d",&num);
scanf("%d",square(num));
printf
return 0;
}
一样,它也可以使用函数原型。只需要在前面声明函数原型,在之后写上函数实现就行。不过,以这种形式只能在这个语句块中使用。所以,我们可以随便套娃我们可以声明任意多重的函数。不过注意作用域问题:内层声明会在当前语句块内覆盖重名的外层声明。
另外还有,关于代码块,它并非必须和for
等一起出现。它也可以单独出现。和一起出现时一样,它形成了一个块作用域,可以划定更精细的作用域和生命周期。
关于
main()
函数其实它真的和其他函数一样是平等的。编译器编译时并没有区别对待它,但是连接器在链接过程,会将一个中间文件链接过来,那个文件指明了程序的入口点:
main()
。 程序只是从main()
开始执行,仅此而已。 既然如此,那么其他函数的操作,在main()
函数,也可以使用了。比如递归(虽然这种用法极度罕见),被别的函数调用等各种操作。
需要用宏来实现。这些宏位于stdargs.h
头文件,是C标准库的一部分。
#include <stdarg.h>
float avarage(int n_values, ...)
{
va_list var_arg;
int count;
float sum=0;
(var_arg, n_values);
va_startfor(count=0;count<n_values;count++)
+=va_arg(var_arg, int);
sum(var_arg);
va_end
return sum/n_values;
}
switch(cond) {
do{
case 1: i++;
case 2: i++;
case 3: i++;
case 4: i++;
case 5: i++;
case 6: i++;
case 7: i++;
}
while(cond2);
}
没啥技巧,就是反映了C实现的灵活性。原作者为了减少转移次数来优化性能,就整了这么个写法来增加一次跳转后执行的指令数,同时借助switch
控制非整数量来对齐结果。最重要的是,这用法反应了编译器/C标准对switch
这个语法结构的描述,以及对于程序执行流控制的方式。