整数除法会截断结果中的小数部分.
取模运算符%不能应用于float和double类型
逻辑运算符&&与||有一些较为特殊的属性,由&&与||连接的表达式按从左到右的顺序进行求值,并且,在知道结果值为真或假后立即停止运算。
/* atoi: convert s to integer */
int atoi(char s[])
{
int i,n;
n = 0;
for(i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
n = 10*n + (s[i] - '0');
return n;
}
表达式 s[i]- '0' 能够计算出s[i]中存储的字符
所对应的数字值,这是因为'0','1'等在字符集中
对应的数值是一个连续的递增序列
/* lower: convert c to lower case; ASCII only */大写转小写
int lower(int c)
{
if (c >= 'A' && c <= 'Z')
return c + 'a' - 'A';
else
return c;
}
c语言的定义保证了机器的标准打印字符集中的字符不会是负值,因此,在表达式中这些字符总是正值。
但是,存储在字符变量中的位模式在某些机器中可能是负的,而在另一些机器上可能是正的。为了保证
程序的可移植性,如果要在char类型的变量中存储非字符数据,最好指定signed或unsigned限定符。
表达式中float类型操作数不会自动转换为double型
当表达式中包含unsigned类型的操作数时,转换规则要复杂一些。主要原因在于,带符号值与无符号值
之间的比较运算是与机器相关的,因为它们取决于机器中不同整数类型的大小。例如,假定int类型占16
位,long类型占32位,那么,-1L<1U, 这是因为unsigned int类型的1U 将被提升为signed long类型;但是
-1L>1UL, 这是因为1L将被提升为unsigned long类型,因而成为一个比较大的正数。
float转int 小数点后去掉 double转float 四舍五入
由于函数调用的参数是表达式,所以在把参数传递给函数时也可能进行类型转换。在没有函数原型的情况下,
char与short类型都将被转换为int类型,float类型将被转换为double类型。因此,即使调用函数的参数为char
或float类型,我们也把函数参数声明为int或double类型。
sqrt((double) n)
在把n传递给函数sqrt之前先将其转化为double类型。注意,强制类型转换只是生成一个指定类型的n的值,
n本身的值并没有改变。
在通常情况下,参数是通过函数原型声明的。这样,当函数被调用时,声明将对参数进行自动强制转换。例如
对于sqrt的函数原型
double sqrt(double);
下列调用:
root2 = sqrt(2);
不需要使用强制类型转换就可以自动将整数2转换为double类型的值2.0
/*squeeze: delete all c from s */
void squeeze(char s[], int c)
{
int i, j;
for(i=j=0; s[i] != '/0'; i+)
if(s[i] != c)
s[j++] = s[i];
s[j] = '/0';
}
由于是j++,++是后缀,所以其中if语句完全等价于下列语句:
if(s[i]!= c)
{
s[j] = s[i];
j++;
}
/* strcat: concatenate t to end of s; s must be big enough */
void strcat(char s[], char t[])
{
int i, j;
i = j = 0;
while (s[i] != '/0') /* find end of s */
i++;
while ((s[i++] = t[j++]) != '/0') /* copy t */
;
}
在将t中的字符逐个拷贝到s的尾部时,变量i和j使用的都是后缀运算符++,
从而保证在循环过程中i与j均指向下一个位置。
按位运算符:
c语言提供了6个位操作运算符。这些运算符只能作用于整型操作数,即只能
作用于带符号或无符号char,short,int,long类型:
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
<< 左移
>> 右移
~ 按位求反(一元运算符)
必须将位运算符&,|同逻辑运算符&&,||区分开来,后者用于从左到右求表达式的真值。
例如,如果x的值为1, Y的值为2,那么,x & y的结果为0,而x && y的值为1。
移位运算符<<与>>分别用于将运算的左操作数左移与右移,移动的位数则由右操作数
指定(有操作数必须是非负值)。因此,表达式x<<2将把x的值左移2位,右边空出的2位
用0填补,该表达式等价于对左操作数乘以4。在对unsigned类型的无符号值进行右移位
时,左边空出的部分将用0填补;当对signed类型的带符号值进行右移时,某些机器将
对左边空出的部分用符号位填补(即“算术移位”),而另一些机器则对左边空出部分用0填补
(即“逻辑移位”)。
x = x & ~077
将把x的最后6位设置为0。注意,表达式 x & ~077 与机器字长无关,它比形式为x & 0177700
的表达式要好,因为后者假定x是16位的数值。这种可移植的形式并没有增加额外的开销,因为
~077是常量表达式,可以在编译时求值。
/* getbits: get n bits from position p */
unsigned getbits(unsigned x, int p, int n)
{
return (x >> (p+1-n))& ~(~0 << n);
}
其中,表达式x >> (p+1-n) 将期望获得的字段移位到字的最右端。~0的所有位为1,这里用语句
~0 << n将~0左移n位,并将最右边的n位用0填补。再使用~运算对它按位取反,这样就建立了最右边
n位全为1的屏蔽码。
x *= y+1
的含义是:
x = x * (y+1)
而不是
x = x * y + 1
i += 2 运算一次
i = i+2 运算两次
条件表达式:expr1 ? expr2 : expr3
中,首先计算expr1, 如果其值不等于0(为真),则计算expr2的值,并以该值作为条件表达式的值,
否则计算expr3的值,并以该值作为条件表达式的值。
采用条件表达式可写简洁代码。例如,打印一个数组的n个元素,每行打10个,每列之间用一个空格
隔开,每行用一个换行符结束(包括最后一行)。
for (int i = 0; i<n; i++)
printf("%6d%c", a[i], (i%10 == 9 || i = n-1)? '/n' : ' ');
运算符优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
() | 圆括号 | (表达式)/函数名(形参表) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | 单目运算符 | ||
-- | 自减运算符 | --变量名/变量名-- | 单目运算符 | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式/整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式<表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式<=表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | 从左向右顺序运算 |
注意,位运算符优先级比运算符==与!=的低。这意味着,位测试表达式,如
if((x & MASK) == 0)...
必须用圆括号括起来才能得到正确结果。
同大多数语言一样,c没有指定同一运算符中多个操作数的计算顺序(&&,||,?:和,运算符除外),例如
x = f() + g();
f()可以在g()之前计算,也可以在之后,因此,当函数f或g改变了另一个函数所使用的变量,那么x的结果
可能会依赖于这两个函数的计算顺序。为了保证特定的计算顺序,可以把中间结果保存在临时变量中。
类是的,c也没指定函数各参数的求值顺序。因此,下列语句
printf("%d %d/n", ++n, power(2, n)); /* 错 */
在不同编译器中可能会产生不同的结果,这取决于n的自增运算在power调用之前还是之后。
a[i] = i++;
也是这种情况,数组下标i 是引用旧值还是引用新值?
所以在任何一种编成语言中,如果代码的执行结果与求值顺序有关,则都是不好的程序设计风格。在不了解
各种机器是如何解决的,就最好不要尝试运用某种特殊的实现方式。
函数:
#include <stdio.h>
#define MAXLINE 1000 /* maximum input line length */
int getline(char line[], int max)
int strindex(char source[], char searchfor[]);
char pattern[] = "ould"; /* pattern to search for */
/* find all lines matching pattern */
main()
{
char line[MAXLINE];
int found = 0;
while (getline(line, MAXLINE) > 0)
if (strindex(line, pattern) >= 0) {
printf("%s", line);
found++;
}
return found;
}
/* getline: get line into s, return length */
int getline(char s[], int lim)
{
int c, i;
i = 0;
while (--lim > 0 && (c=getchar()) != EOF && c != '/n')
s[i++] = c;
if (c == '/n')
s[i++] = c;
s[i] = '/0';
return i;
}
/* strindex: return index of t in s, 1
if none */ 写的好啊 两个for循环就搞定了
int strindex(char s[], char t[])
{
int i, j, k;
for (i = 0; s[i] != '/0'; i++) {
for (j=i, k=0; t[k]!='/0' && s[j]==t[k]; j++, k++)
;
if (k > 0 && t[k] == '/0')
return i;
}
return -1;
}
外部变量或函数的作用域从声明它的地方开始,到其所在的(待编译的)文件的末尾结束。
例如,如果main,sp,val,push与pop是依次定义在某个文件中的5个函数或外部变量,如
下所示:
main() { ... }
int sp = 0;
double val[MAXVAL];
void push(double f) { ... }
double pop(void) { ... }
那么,在push与pop这两个函数中不需要进行任何声明就可以通过名字访问变量sp与val,
但是,这两个变量名不能用在main函数中,push与pop函数也不能用在main函数中。
外部变量的定义中必须指定数组的长度,但extern声明则不一定要指定数组的长度。
用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。
通过static限定外部对象,可以达到隐藏外部对象的目的。
外部的static声明通常多用于变量,当然,它也可用于声明函数。通常情况下,函数名字是全局可
访问的,对整个程序的各个部分而言都可见。但是,如果把函数声明为static类型,则该函数名
除了对该函数声明所在的文件可见外,其他文件都无法访问。
static也可用于声明内部变量。static类型的内部变量同自动变量一样,是某个特定函数的局部变量,
只能在该函数中使用,但它与自动变量不同的是,不管其所在函数是否被调用,它一直存在,而不像
自动变量那样,随着所在函数的被调用和退出而存在和消失。换句话说,static类型的内部变量是一
种只能在某个特定函数中使用但一直占据存储空间的变量。
register声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想是,将register变量放在
机器的寄存器中,这样可以使程序更小,执行速度更快。但编译器可以忽略此选项。
实际使用时,底层硬件环境的实际情况对寄存器变量的使用会有一些限制。每个函数中只有很少的变
量可以保存在寄存器中,且只允许某些类型的变量。但是,过量的寄存器声明并没有什么害处,这是
因为编译器可以忽略过量的或不支持的寄存器变量声明。另外,无论寄存器变量实际上是不是存放在
寄存器中,它的地址都是不能访问的。
在一个好的程序设计风格中,应该避免出现变量名隐藏外部作用域中相同名字的情况,否则,很可能
引起混乱和错误。
在不进行显式初始化的情况下,外部变量和静态变量都将被初始化为0,而自动变量和寄存器变量的初
值则没有定义(即初值为无用的信息)。
对外部变量和静态变量来说,初始化表达式必须是常量表达式,且只初始化依次(从概念上讲是在程序
开始执行前进行初始化)。对于自动变量与寄存器变量,则在每次进入函数和程序块时都将被初始化。
数组初始化:
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
当省略数组的长度时,编译器将把花括号中初始化表达式的个数作为数组的长度,在本例中长度为12。
如果初始化表达式的个数比数组元素少,则对外部变量,静态变量和自动变量来说,没有初始化表达式
的元素将被初始化为0,如果初始化表达式的个数比数组元素多,则是错误的。不能一次将一个初始化式
指定多个数组元素,也不能跳过前面的数组元素而直接初始化后面的数组元素。
字符初始化比较特殊。
可以char pattern[] = "ould ";
等价于char pattern[] = { 'o', 'u', 'l', 'd'};
这种情况下,数组长度5。4+一个字符串结束符'/0'
递归并不节省存储器开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。递归的执行
速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写与理解。在描述树等递归定义
的数据结构时使用递归尤其方便。
很自然,如果某个包含文件的内容发生了变化,那么所有依赖于该包含文件的源文件都必须重新编译。
较长的宏定义可分为若干行,这是需要在待续的行末尾加上一个反斜杠符/。#define 指令定义的名字
的作用域从其定义点开始,到被编译的源文件的末尾处结束。
带参数宏定义
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR
c语言专门定义了两个预处理语句#ifdef与#ifndef,它们用来测试某个名字是否已经定义。