C语言的基础知识1
1、作用域
1.1代码块作用域:花括号之间的所有语句称为一个代码块,作用域就是{}内。
注意:如果内层代码有一个标识符的名字和外层代码块的一个标识符同名,内层的就将隐藏外层的。外层的标识符无法在内层代码通过名字访问。
ANSI C把形参的作用域设定为函数最外层的那个作用域(也就是整个函数体),函数最外层作用域的局部变量无法与形参名同名。
1.2函数作用域:goto所用的标识符,尽量不要用
1.3文件块作用域:任何在所有代码块之外声明的标识符都具有文件作用域,在文件内有效,且包括#include包含的文件。
1.4原型作用域:只适用于函数原型中声明的参数名,比如在int b(int c);
2、链接属性
2.1 外部external,成为全局实体,不论声明多少次,位于几个源文件内,都是同一实体。
2.2 内部internal,在同一个源文件内指同一实体,位于不同源文件是不同的实体。
2.3 无none,总是被当成单独的个体,多个声明被当作不同的实体。
注意:在默认情况下,在代码块之外的标识符的链接属性为external,其余为none
2.4 修改链接属性
2.4.1 extern 一般而言,它为一个标识符指定external链接属性,这样就可以访问在其他任何位置定义的这个实体。当extern用于源文件中一个标识符的第1次声明时,它指定该标识符具有external链接属性,但是如果是第2次或者以后的声明时,它并不会更改第1次声明所指定的链接属性。
2.4.2 static 如果某个声明在正常情况下有external链接属性,前面加上static就改为internal链接属性,可以防止被其他源文件调用。只对缺省为external链接属性的声明才有效。
3、常量
不能修改的值称为常量,用const 或者用define
4、存储类型
有三个地方用于存储变量:普通内存、运行时堆栈、硬件寄存器
4.1 凡是在任何代码块之外声明的变量总是存储在静态内存中的,这类变量成为静态(static)变量。静态变量在程序运行之前创建,在整个执行期间始终存在。
4.2 代码块内部声明的变量默认存储类似是自动的(automatic),存储在堆栈里,关键字auto用来修饰(但很少使用,因为默认就是),调用时分配,调用结束销毁。
4.3 关键字register可以用于自动变量的声明,存储在硬件寄存器中,而不是堆栈中。
在代码块内部声明的变量,前面加上static,则存储类型变成静态,在整个程序执行过程中一直存在,而不仅仅在声明它的代码块执行时存在。
注意:修改变量的存储类型并没有修改变量的作用域,它仍然只能在代码块内部按名字访问。
函数的形参不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。
4.4 对于函数而言,函数代码总是存储在内存中的。
5、初始化
5.1 静态变量初始化:把想要初始化的值放在当程序执行时变量将会使用的位置。当可执行文件载入内存时,这个已经保存了正确初始值的位置将赋值给那个变量,如果不显示的初始化,静态变量将被初始化为0。
5.2 自动变量初始化:要赋值,不然它的值就是堆栈中上一次的垃圾值。
6、static总结
6.1 当它用于函数定义时,或用于代码块之外的变量声明时,static用于修改标识符的链接属性,由external-->internal,但标识符的存储类型和作用域不受影响。
6.2 当用于代码块内部的变量声明时,修改存储类型为静态,只初始化一次,程序执行之前创建,运行时一直存在,代码执行完毕销毁。
7、操作符
7.1 算术操作符:+ - * / %,其中%只能用于整型操作数,其他既可整型又可浮点型
7.2 移位操作符:>> <<,转换成二进制移位。
向右移时,情况分两种
7.2.1 逻辑移位:左边补0
7.2.2 算术移位:左边补符号位
说明:无符号数都采用逻辑移位,有符号数则要看编译器的设置
7.3 位操作符 &(按位与) |(按位或) ^(按位异或)
7.4 赋值 x=y+3;这个也是个表达式,赋值表达式的值就是左操作数的新值,赋值操作符的结合型(求值的顺序)是从右向左。
7.5 复合赋值符 += -= *= /= %= <<= >>= &= ^= |=
7.6 单目操作符 !逻辑取反 ++ -- -(负数) &(取地址) sizeof(操作者的类型长度,以字节为单位) ~求补操作(1变成0 0变成1) -- +(什么也不干) * (间接访问)
sizeof(int)返回整型变量的字节数
sizeof x 返回变量x所占据的字节数,字符变量的长度为1个字节。当sizeof的操作数是个数组名时,它返回数组的长度,以字节为单位的。
判断表达式的长度并不需要对表达式求值,所以sizeof(a=b+1)并没有向a赋任何值
7.7 关系操作符 > >= < <= != ==
7.8 逻辑操作符 && ||
7.9 条件操作符 expression1?expression2:expression3
7.10 逗号操作符 expression1,expression2,expression3,...,expressionN 自左向右求值,整个表达式的值就是最后那个值
7.11 左值和右值 a=b+25;
a是个左值,标识了一个可以存储结果值的地点
b是个右值,指定了一个值
不可以互换,左右两边不能互换,因为b+25不是一个右值,当计算机计算b+25时,它的结果必然保存在计算机的某个地方,但是程序员没有办法预测这个结果会存储在什么地方,也无法保证这个表达式的值下次还存储在那个地方,其结果是这个表达式不是一个左值。同理,字面值常量也都不是左值。
C/C++语言中可以放在赋值符号左边的变量,即具有对应的可以由用户访问的存储单元,并且能够由用户去改变其值的量。左值表示存储在计算机内存的对象,而不是常量或计算的结果。或者说左值是代表一个内存地址值,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作;这也就是为什么左值可以被赋值的原因了。相对应的还有右值:当一个符号或者常量放在操作符右边的时候,计算机就读取他们的“右值”,也就是其代表的真实值。简单来说就是,左值相当于地址值,右值相当于数据值。右值指的是引用了一个存储在某个内存地址里的数据。
7.12操作符的属性
复杂表达式的求值顺序绝对因素:操作符的优先级、操作符的结合性、操作符是否控制执行的顺序。
8、指针
8.1 指针运算
8.1.1 算术运算 p是一个指向int类型的指针,p+2则是移动2个int类型的位置,也就是8个字节
8.2.2 指针-指针,只有当两个指针都指向同一数组中的元素时,才允许一个指针减去另一个指针,结果类型是ptrdiff_t,有符号整型类型,是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位)
如果两个指针指向的不是同一个数组的元素,那么他们相减的结果就是未定义的。
9、函数
9.1 函数的参数 传递给函数的标量参数都是传值调用的,传递给函数的数组参数在行为上就像的他们通过传址调用那样(因为传值传的是一个地址,再通过间接访问,实际访问的仍然是那个地方)
9.2 可变参数列表 是通过宏来实现的,这些宏在stdarg.h中。这个头文件声明了一个类型va_list和三个宏va_start、va_arg、va_end。我们可以声明一个va_list的变量,与这几个宏配合使用,访问参数的值。
C函数要在程序中用到以下这些宏:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
va_list:用来保存宏va_start、va_arg和va_end所需信息的一种类型。为了访问变长参数列表中的参数,必须声明
va_list类型的一个对象 定义: typedef char * va_list;
va_start:访问变长参数列表中的参数之前使用的宏,它初始化用va_list声明的对象,初始化结果供宏va_arg和
va_end使用;
va_arg: 展开成一个表达式的宏,该表达式具有变长参数列表中下一个参数的值和类型。每次调用va_arg都会修改
用va_list声明的对象,从而使该对象指向参数列表中的下一个参数;
va_end:该宏使程序能够从变长参数列表用宏va_start引用的函数中正常返回。
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第 一个可变参数的前一个参数,是一个固定的参数.
3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型.
4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.
二、可变参类型陷阱
下面的代码是错误的,运行时得不到预期的结果:
view plaincopy to clipboardprint?
va_start(pArg, plotNo);
fValue = va_arg(pArg, float); // 类型应改为double,不支持float
va_end(pArg);
va_start(pArg, plotNo);
fValue = va_arg(pArg, float); // 类型应改为double,不支持float
va_end(pArg);
下面列出va_arg(argp, type)宏中不支持的type:
—— char、signed char、unsigned char
—— short、unsigned short
—— signed short、short int、signed short int、unsigned short int
—— float
在C语言中,调用一个不带原型声明的函数时,调用者会对每个参数执行“默认实际参数提升(default argument promotions)”。该规则同样适用于可变参数函数——对可变长参数列表超出最后一个有类型声明的形式参数之后的每一个实际参数,也将执行上述提升工作。
提升工作如下:
——float类型的实际参数将提升到double
——char、short和相应的signed、unsigned类型的实际参数提升到int
——如果int不能存储原值,则提升到unsigned int
然后,调用者将提升后的参数传递给被调用者。
所以,可变参函数内是绝对无法接收到上述类型的实际参数的。
关于该陷井,C/C++著作中有以下描述:
在《C语言程序设计》对可变长参数列表的相关章节中,并没有提到这个陷阱。但是有提到默认实际参数提升的规则:
在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double类型。
——《C语言程序设计》第2版 2.7 类型转换 p36
在其他一些书籍中,也有提到这个规则:
事情很清楚,如果一个参数没有声明,编译器就没有信息去对它执行标准的类型检查和转换。
在这种情况下,一个char或short将作为int传递,float将作为double传递。
这些做未必是程序员所期望的。
脚注:这些都是由C语言继承来的标准提升。
对于由省略号表示的参数,其实际参数在传递之前总执行这些提升(如果它们属于需要提升的类型),将提升后的值传递给有关的函数。——译者注
——《C++程序设计语言》第3版-特别版 7.6 p138
…… float类型的参数会自动转换为double类型,short或char类型的参数会自动转换为int类型 ……
——《C陷阱与缺陷》 4.4 形参、实参与返回值 p73
这里有一个陷阱需要避免:
va_arg宏的第2个参数不能被指定为char、short或者float类型。
因为char和short类型的参数会被转换为int类型,而float类型的参数会被转换为double类型 ……
例如,这样写肯定是不对的:
c = va_arg(ap,char);
因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:
c = va_arg(ap,int);
10、指针
10.1指针与效率
10.1.1 当你根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生效率更高的代码。当这个增量是1并且机器具有地址自动增量模型时,这点表现的更为突出。
10.1.2 声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高(具体提高的幅度取决于你所使用的机器)。
10.1.3 如果你可以通过测试一些已经初始化并经过调整的内容来判断循环是否终止,那么你就不需要使用一个单独的计数器。
10.1.4 那些必须在运行时求值的表达式较之诸如&array[SIZE]或array+SIZE这样的常量表达式往往代价更高。
10.2数组和指针
指针和数组并不是相等的,int a[5]; int b;
声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个指针常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。所以a是完全合法的,但是*b确是非法。另一方面表达式b++可以通过编译,a++却编译不了,因为a的值是个常量
11、数组
11.1 字符数组的初始化
char message1[] = "Hello";
char *message2 = "Hello";
11.2 多维数组 matrix[1][5]=*(*(matrix+1)+5)
11.3 指向数组的指针
int matrix[3][10];
int (*p)[10]=matrix; p指向matrix数组的第1行,指向拥有10个整型元素的数组的指针
int *pi=&matrix[0][0];
int *pi=matrix[0];这样就可以逐个对数组进行移动求值了
11.4 作为函数参数的多维数组,编译器需要知道它的维数,以便为函数形参的下标表达式进行求值。
int matrix[3][10]; void func2(int (*mat)[10]);或者 void func2(int mat[][10])
11.5 指针数组 int *api[10];指向整型的指针数组,一个维度为10的数组,每一个元素都是指向整型的指针