第1章 快速上手
(1)数组参数是以引用(reference)形式进行传递的,也就是传址调用,而标量和常量则是按值(value)形式传递的,在函数中对标量参数的任何修改都会在函数返回时丢失,因此,被调用函数无法修改调用函数以传值形式传递给它的参数。
(2)在函数声明的数组参数中,并未指定数组长度。这个是正确的,可以使函数可以处理任何长度的一维数组,但是如果要知道数组的长度的话就必须将数组长度作为参数传递给函数。
int read_column_numbers(int columns[],int max)
{}
(3)当scanf函数对输入值进行转换时,它只读取需要读取的字符。这样,该输入行包含了最后一个值的剩余部分仍会留在那里,等待被读取。它可能只包含作为终止符的换行符,也可能包含其他数字。不论如何,while循环将读取并丢弃这些剩余的字符,防止他们被解释为第一行数据。
while((ch = getchar()) != EOF && ch != '\n')
;
(4)
int ch;
ch = getchar();
在这里ch被声明为int型,但是我们事实上却用它来读取字符。EOF是一个整形值,它的位数比字符类型要多,把ch声明为整形可以防止输入读取的字符意外地被解释为EOF,字符只是小整型数而已,所以用一个整型变量容纳字符值并不会引起任何问题。(如果输入中不再有任何字符,函数就会返回常量EOF)。
第2章 基本概念
2.2.1 字符
(1)三字母词(trigrph)
??( [ ??< { ??= #
??) ] ??> } ??/
??! | ??’ ^ ??- ~
(2)特殊字符
? 在书写连续多个问号时使用,防止它们被解释为三字母词
" 以下情况大多在字符串常量中表示\后面的字符
’
\
(3)转义符
\a 警告字符。响铃或者产生一些其他可以看见的信号。
\b 退格键
\f 进纸字符
\n 换行符
\r 回车符
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1~3位八进制数字。这个转义符就是给定的八进制数值所代表的字符。
\xddd 与上面类似这是十六进制。
例子:\x100:超出字符表示的范围(字符为八位,此十六进制数为十进制256,为9位二进制,所以\x99就可以)
\0123: 被分为\012 和3两部分,因为\ddd只能由1~3位数字
\x0123:这个超出了范围1
2.2.2注释
(1)所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现于任何空格可以出现的地方。
2.2.4标识符
(1)标识符(identifier)由大小写字母、数字和下划线组成,但不能以数字开头。
(2)标识符长度没有限制,但标准允许编译器忽略第31个字符以后的字符。标准同时允许编译器对用于表示外部名字2(也就是链接器操纵的名字)的标识符进行限制,只识别前六位不区分大小写的字符。
第3章 数据
3.2.2 声明简单数组
(1)如果下标值是从那些已知是正确的值计算得来的,,那么就u需要检查它的值。如果一个用作下标的值是根据某种方法从用户输入的数据产生而来的,那么在使用它之前必需进行检测,确保它们位于有效的范围之内。
3.2.3 声明指针
(1)指针声明
#define pointer int*
int* b,c,d; //这里声明了两个int型变量和一个int型指针
int *b,*c,*d; //这里声明了三个int型指针
pointer b,c; //只声明了一个int型指针变量b和一个int型变量c,类似第二行
3.4 常量
(1)常指针:
int const *pci; //是一个指向整型常量的指针。你可以修改指针的值,但是你不能修改它所指向的值。
int *const pci; //是一个指向整型的常量指针。此时指针是常量,它的值(地址)无法修改,但是你可以修改它所指向的整型的值。
int const * const pci; //指针和指针所指向的值都是常量。
(2)const和#define
#define MAX_ELEMENTS 50
int const max_elements = 50;
允许使用字面值常量的地方都可以使用前者,比如声明数组的长度。const变量只能用于允许使用变量的地方。
3.5.1 代码作用域
(1)当代码处于嵌套状态时,声明于内层代码块的标识符的作用域到达该代码块的尾部便告终止。如果内层代码块有一个标识符的名字与外层代码块的一个标识符同名,内层那个标识符就将隐藏外层的标识符——外层的那个标识符无法在内层代码块中通过名字访问。
3.5.2 文件作用域
(1)任何在所有代码块之外声明的标识符都具有文件作用域(file scope),它表示这些标识符从他们的声明之处直到它所在的源文件结尾处都是可以访问的。
3.6 链接属性
1———> typedef har *a;
2———> int b;
3———> int a(int d);//int d 为 4
{
5———>int e;
6———>int f (int g);//int g 为 7
……
}
(1)在缺省的条件下,标识符b、c、和f的链接属性为external,其余的标识符的链接属性为none。如果另一个源文件也包含了标识符b的类似声明并调用函数c。它们实际上访问的事这个源文件所定义的实体。f的链接属性之所以是external是因为它是个函数名。在这个源文件中调用函数f,它实际上将链接到其他源文件所定义的函数,甚至这个函数的定义可能出现在某个函数库。
(2)static
如果第二个声明改为:
static int b;//则变量b就将成为这个源文件所私有。它其他源文件中如果也链接到一个叫做b的变量,那么他所引用的是另一个不同的变量。
static int c(int d){
} //这可以防止它被其他源文件调用
static只对缺省链接属性为external的声明才改变链接属性。
(3)extern
static int i;
int func()
{
int j;
extern int k;
extern int i;
……
}
extern比较复杂。
1⃣️:如上面代码第五行对k的声明,函数就可以访问在其他源文件声明的外部变量了。
2⃣️:如果extern用于标识符的第二次或以后的声明它并不会更改第一次的声明。如上面的代码第六行对i的声明不会改变第一行的声明链接属性。
3.7 存储类型
(1)register
register可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是。通常,寄存器变量比存储于内存的变量访问起来更高效。
(2)初始化
静态变量将初始化为0.自动变量的初始化需要更多的开销。
1⃣️:在声明变量的同时进行初始化和先声明后赋值语句效率并无提高。
2⃣️:可能会重复运行
3⃣️:优点:由于初始化在运行时执行,你可以用热和表达式作为初始化值。
4⃣️:除非你对自动i按量进行显式的初始化,否则当自动变量创建时,它们的值总是垃圾。
3.13 问题
3.假定你正在编写一个程序,它必须运行于两台机器之上。这两台机器的缺省整型长度并不相同,一个是16位另一个是32位。而这两台机器的长整型长度分别为32位和64位。程序所使用的有些变量的值并不太大,足以保存于任何一台机器的缺省整型变量中,但有些变量的值却有些大,必须是32位的整型变量才能容纳。但在1.位机器上,对于那些用16位足以容纳的值而言,时间和空间的浪费不可小视。在32位机器上也存在时间和空间的浪费问题。
如果想让这些变量在任何一台机器上的长度都合适的话,你该如何声明他们呢?(包含一个头文件,里面包含每台机器特定的声明)
第4章 语句
4.5.1 break和continue语句
(1)在while循环中可以使用break语句,用于永久终止循环。
(2)在while循环中也可以使用continue语句,它用于永久停止当前的那次循环。在执行完continue语句之后,执行流接下来就是重新测试表达式的值,决定是否继续执行循环。
(3)这两条语句的任何一条如果出现在嵌套的循环内部,它只对最内层的循环起作用,你无法使用break和continue语句影响外层的循环。(连续两个break;没有意义,第二个无法被执行)(要想直接退出多重循环可以在循环条件里加一个标志量,在应该用break;前,改变这个标志量,就可以退出多重循环。也可以用goto语句)
4.8 switch语句
switch( expression )
case constant-expression: statement; //没有break的话,程序会继续向下运行
case constant-expression:
/*FALL THRU*/ //如果一个case没有break的话最好加上这个,方便以后阅读和维护,因为大多数case中都有break;
default: ;//如果所有case都没有被执行,则这部分将被执行。
(1)其中expression的结果必须是整数。
(2)每个case标签必须有一个唯一的值。常量表达式(constant-expression)是指在编译期间进行求值的表达式,它不能是任何变量。
(3)case标签并不把语句列表划分为几个部分,只是确定语句列表的进入点。
4.9 goto语句
i = 0;
outer_next:
statement;
goto outer_end;
inter_next:
statement;
outer_end:
;
(1)如上述程序,在语句前面加上标签。就是标识符后面加上冒号。
(2)goto语句是一种危险的语句,尽量不要使用,但是可以用来退出深层嵌套。
第5章 操作符和表达式
5.1.1 算数操作符
/ * - + %
除了%,其余几个操作符都是既适用于浮点类型又适用于整型类型。当/操作符的两个操作数都是整数时,它执行整除运算,在其他情况下则执行浮点数除法。
5.1.2 移位操作符
(1)左移位:值最左边的几位被丢掉,右边多出来的几个空位则由0补齐。
(2)右移位:逻辑移位(左边移入的位用0填充),算术移位(左边移入的位由符号位决定是什么移入什么,保持原来的正负号)。
5.1.3 位操作符
& | ^
分别为AND OR 和 XOR(异或,两个相同则为0,不同则为1)
例子:value & 1 << bit number //左移符号优先级高,所以先算后面的,这条语句的功能是对value的第bit number位测试,如果为1,则表达式结果为非零值。
5.1.4 赋值
(1)警告例子:
a = x = y + 3; //如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符类型的变量中。
这个问题在下面的例子中会造成错误。
char ch;
...
while((ch = getchar()) != EOF ) ...
EOF 需要的位数比字符型值所提供的位数要多,这也是getchar返回一个整形值而不是字符值的原因。但是返回的时候被ch所截短,就会出现错误。
5.1.5 单目操作符
! ++ - & sizeof ~ - - + * (类型)
(1)~操作符对整形的操作数进行求补操作,所有位1变为0,0变为1.
(2)+操作符和-操作符在这里表示正负的意思。
(3)*操作符是间接访问操作符,与指针一起使用。
(4)sizeof在判断表达式的长度时不需要对表达式进行求值,所以执行sizeof(a=b+1)并没有向a赋任何值。
(5)++a的结果是a值的拷贝,而不是a变量。
5.1.6 关系操作符
> >= < <= != ==
这些操作符产生的结果都是一个整型值,而不是布尔值。
5.1.8 条件操作符
expression1 ? expression2 : expression3
条件操作符的优先级非常低,所以各个操作数即使不加括号,一般也不会有问题,但是还是要加上的;
例子:
b[2*c+d