《C程序设计语言》笔记(一)
一:导言
1:printf中的格式化字符串:
%ld 按照long整型打印
%6d 按照十进制整数打印,至少6个字符宽,不够的话空格补齐
%6f 浮点数打印,至少6个字符宽
%.2f 浮点数打印,精确到小数点2位
%6.2f 浮点数打印,至少6个字符宽,小数点后有2位
%.0f 不打印小数点和小数部分
2:较早版本C语言,可以按照下面的方式定义函数:
power(base, n)
int base, n;
{
...
}
而且,早期C语言,可以在程序的开头按照下面这种形式声明power函数:
int power();
函数声明中不允许包含参数列表。
ANSIC仍然支持旧式的函数声明与定义。这种形式的声明和定义,在linux下使用gcc编译,不会产生错误。
3:C语言中,所有函数参数都是“通过值”传递的。也就是说,传递给被调用函数的参数值存放在临时变量中,而不是存放在原来的变量中。所以,在被调用函数中,参数可以看做是局部变量,比如:
int power(int base, int n)
{
int p;
for(p= 1; n > 0; --n)
{
p = p * base;
}
return p;
}
二:类型、运算符与表达式
1:局部变量一般使用较短的变量名,外部变量使用较长的名字。
2:类型限定符signed和unsigned可用于限定char类型或任何整型。
类型长度定义的符号常量以及其他与机器和编译器有关的属性可以在标准头文件<limits.h>和<float.h>中找到。
3:long类型的常量以字母l或L结尾,比如123456789L。如果一个整数太大以至于无法用int类型表示时,也将被当做long类型处理。
无符号常量以u或U结尾。后缀ul或UL表明是unsignedlong类型。
没有后缀的浮点数常量为double类型。后缀f或F表示float类型,而后缀l或L表示long double类型。
整数除了用十进制表示外,还可以用八进制或十六进制表示。带前缀0的整型常量表示它为八进制;前缀为0x或0X,则表示它为十六进制。比如31可以写为037,也可以写为0x1f或0X1F。
可以用’\ooo’表示任意字节大小的位模式。其中,ooo代表1-3位八进制数字。这种位模式还可以用’\xhh’表示。
字符常量’\0’表示值为0的字符,也就是null,通常用’\0’的形式代替0。字符串中,使用\”表示双引号。
编译时,可以将多个字符串常量连接起来,比如:”hello,” “world”等价于”hello, world”。注意,两个字符串之间是空格,不是逗号,所以只有printf(“hello, ” “world\n”); 才能打印正确,而printf(“hello, ”, “world”)不会。
4:枚举类型
a:枚举是一个常量整型值的列表,比如:enum boolean{NO, YES}; 在没有显示说明的情况下,enum类型中,第一个枚举名的值为0,第二个为1,以此类推。如果只指定了部分枚举名的值,那么未指定值的枚举名的值将依着最后一个指定值向后递增。比如:
enum months {JAN= 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC}; FEB的值为2,MAR的值为3,依次类推。
调试程序可以以符号的形式打印出枚举变量的值。
b:以下代码定义了这种新的数据类型 - 枚举型
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY是一种类型,而不是变量,它是枚举类型,这比较类似于结构体类型的定义。注意,定义枚举类型,也要以”;”结尾。
c:用枚举类型声明变量的方式如下: enum DAY yesterday; 其中yesterday是变量名,它是一种枚举类型的变量。其他定义枚举类型变量的方式有:
enum
{
saturday, sunday = 0, monday, tuesday, wednesday, thursday, friday
} workday; //变量workday的类型为枚举型enum DAY
enum week { Mon=1, Tue, Wed, Thu, Fri Sat, Sun} days; //变量days的类型为枚举型enum week
enum BOOLEAN { false, true } end_flag, match_flag; //定义枚举类型并声明了两个枚举型变量
d:可以用typedef关键字将枚举类型定义成别名,并利用该别名进行变量声明:
typedef enum workday
{
saturday, sunday = 0, monday, tuesday, wednesday, thursday, friday
} workday; //此处的workday为枚举型enum workday的别名
workday today, tomorrow; //变量today和tomorrow的类型为枚举型workday,也即enum workday
enum workday中的workday可以省略:
typedef enum
{
saturday, sunday = 0, monday, tuesday, wednesday, thursday, friday
} workday; //此处的workday为枚举型enum workday的别名
workday today, tomorrow; //变量today和tomorrow的类型为枚举型workday,也即enum workday
e:同一个程序中不能定义同名的枚举类型,不同的枚举类型中也不能存在同名的命名常量。
错误声明一:存在同名的枚举类型
typedef enum
{
wednesday, thursday, friday
} workday;
typedef enum WEEK
{
saturday, sunday = 0, monday,
} workday;
错误声明二:存在同名的枚举成员
typedef enum
{
wednesday, thursday, friday
} workday_1;
typedef enum WEEK
{
wednesday, sunday = 0, monday,
} workday_2;
f:枚举类型变量的赋值:
typedef enum {ONE, TWO, THR}number;
numbera = ONE; //ok
numberb = 100; //ok 可以用超出范围的整型值
numberc = 2.3; //ok 可以用浮点数
numberd = 'c'; //ok 可以用字符常量
numbere = "hh"; //error:incompatible types when initializing type ‘number’ using type ‘char *’
g:可以直接用枚举类型的常量给变量赋值:
typedef enum {ONE, TWO, THR}number;
int a = ONE; //ok
floatg = ONE; //ok
doubleh = ONE; //ok
chari = ONE; //ok
5:如果变量不是自动变量,则只能进行一次初始化操作。这是在程序开始执行之前进行的,并且初始化表达式必须为常量表达式。如果定义外部变量、内部静态变量如下,则会出错:
int a = f(); // error: initializer element is not constant
static int b =f(); // error: initializer elementis not constant
自动变量的初始化时,其初始化表达式可以是任何表达式。
6:在有负操作数的情况下,整数除法截取的方向,以及模运算结果的符号取决于具体机器的实现。
7:在关系表达式或逻辑表达式中,如果关系为真,则表达式的结果值为数值1;如果为假,则结果值为数值0。比如:
int c = (3 < 4); //1
int b = (1 > 2); //0
int d = (3 < 4) || (1 > 2); //1
int e = (3 < 4) && (1 >2); //0
在if,while,for等语句的测试部分,“真”就意味着非0。
8:转换
a:一般来说,自动转换是指把“比较窄的”操作数,转换为“比较宽的”操作数,并且不丢失信息的转换。
b:需要注意,当把一个char类型的值转换为int类型的值时,其结果是否为负数,对于不同的机器,会有不同的结果。
c;C语言的定义,保证了机器的标准打印字符集中的字符不会是负值。
d:在一个表达式中,凡是可以使用整型的地方,都可以使用带符号或无符号的char、short、enum类型的对象。如果原始类型的所有值都可用int类型表示,则其值被转换为int类型;否则,将被转换为unsigned int类型。这称为整型提升。比如:
char a = 0;
short c = 0;
sizeof(a) 为1,sizeof(c)为2, sizeof(a+1)为4,sizeof(c+1)为4,后两个之所以为4,是因为发生的整型提升。注意:sizeof(‘a’); 的值,也是4.
e:很多情况下会进行隐式的算数类型转换。如果二元操作符的两个操作数具有不同的类型,那么在进行运算之前,需要先把“低”类型提升为“高”类型,运算的结果为高类型。这种方式的转换为称为“普通算术类型转换”:
首先,如果任何一个操作数为longdouble类型,则将另一个操作数转换为long double类型。
否则,如果任何一个操作数为double类型,则将另一个操作数转换为double类型。
否则,如果任何一个操作数为float类型,则将另一个操作数转换为float类型。
否则,同时对两个操作数进行整型提升;然后,如果任何一个操作数为unsigned long int类型,则将另一个操作数转换为unsigned long int类型。
否则,如果一个操作数为long int类型且另一个操作数为unsignedint类型,则结果依赖于long int类型是否可以表示所有的unsigned int类型的值。如果可以,则将unsigned int类型的操作数转换为long int;如果不可以,则将两个操作数都转换为unsigned long int类型。
否则,如果一个操作数为long int类型,则将另一个操作数转换为long int类型。
否则,如果任何一个操作数为unsignedint类型,则将另一个操作数转换为unsigned int类型。
否则,将两个操作数都转换为int类型。
注意:新版本的C标准有两个变化:第一,对float类型操作数的算术运算可以只用单精度而不是双精度,而在之前的版本,所有的浮点运算都是双精度。也就是float类型,不在自动提升为double类型。第二,当较短的无符号类型与较长的带符号类型一起运算时,不将无符号类型的属性传递给结果类型;而在旧版本中,无符号类型总是处于支配地位。
f:强制类型转换:(类型名)表达式。它的含义是:表达式首先被赋值给类型名指定的某个变量,然后再用该变量替换上述整条语句。
g:当函数被调用时,声明将对参数进行自动强制转换。比如sqrt函数原型是double sqrt(double);则函数调用sqrt(2),不需要使用强制类型转换运算符,就可以自动将整数2强制转换为double类型的值2.0。
9:s[j++] = s[i]; 该语句等价于s[j] = s[i]; j++; 使用第一种方式,更加简洁!
10:在对unsigned类型的无符号值进行右移位时,左边空出的部分将用0填补;当对signed类型的带符号值进行右移时,不同的机器,将采取不同的补齐方式:算术移位(左边空出的部分用符号位填补)、逻辑移位(左边部分用0填补)。
11:x = x & ~077; 本语句将把x的最后6位设置为0。注意,该表达式与机器字长无关,他比形式为x&0177700的表达式要好,因为后者假定x是16位的数值。而且,这种可移植的形式并没有增加额外开销,因为~077是常量表达式,可以在编译时求值。
12:e1 op= e2等价于e1 = (e1) op (e2),它们的区别是,前一种形式e1只计算一次。也就是说:x *= y+1,等价于x = x * (y+1)
13:赋值表达式的类型是它的左操作数的类型,其值是赋值完成后的值。
14:x &= x -1,他可以删除x中最右边值为1的一个二进制位。
15:除了运算符&&、||、?:和逗号运算符之外,C语言没有指定同一运算符中,多个操作数的计算顺序,所以,x = f() + g();这样的语句中,函数f和g谁先调用是不确定的。
参数的求职顺序也没有指定,比如这样的语句也要避免:
printf(“%d %d\n”, ++n, power(2, n));
C语言标准对大多数这类问题有意未做具体规定。如果代码的执行结果,与求值顺序相关,则这是不好的程序设计风格。
16:运算符的优先级和结合性:
三:控制流
1:switch语句是一种多路判定语句,它测试表达式是否与一些常量整型值中的某一个值匹配。
在switch语句中,case的作用只是一个标号,因此,某个分支中的代码执行完后,程序将进入下一分支继续执行,除非在程序中显示的跳转。
作为一种良好的程序设计风格,在switch语句最后一个分支,也就是default分支的后面也加一个break语句,虽然逻辑上没有必要,但是会降低犯错的可能性。
2:for(e1; e2; e3){} 中,3个组成部分都可以省略,但是分号必须保留。如果省略了e2,则认为其值永为真值。
3:逗号运算符是C中优先级最低的运算符,在for中会经常用到。被逗号分隔的一对表达式将按照从左到右的顺序进行求值,表达式右边的操作数的类型和值即为其结果的类型和值。
4:尽量少的使用goto语句,但是,某些场合下goto语句还是用得着的,最常见的用法是终止程序在某些深度嵌套的结构中的处理过程,比如一次跳出两层或多层循环。比如:
for(...)
for(...){
...
if(disaster)goto error;
}
...
error:
...
标号的命名同变量命名的形式相同。标号的作用域是整个函数。