整数除法会截断结果中的小数部分.
取模运算符%不能应用于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,它们用来测试某个名字是否已经定义。
