C和指针(四)指针、函数

指针
1,内存和地址,每个类型只有一个地址,是最左边字节的位置还是最右边字节的位置,不同机器有不同的规定,有些机器还设计了边界对齐,例整型值存储起始位置只能是特定字节(2或4)的倍数。

2,硬件需要通过地址访问内存位置,编绎器实现通过变量名字取地址的内容。
1)如果一变量占用4个字节,每个字节8个位通过0或1表示,这个变量包含一序列0或1的位,可以解释为整数也可以解释为浮点数。
2)如果使用整型算术指令,值解释为整数,如果使用浮点型指令,解释为浮点数。
3)不可通过值的位判断它的类型,4个字节内容可以解释成4个字节的整型,或4个字节的浮点,或两个2字节的短整型,或四个单字节的字符型。
4)编绎器通过变量类型执行相应的类型指令,以恰当的方式访问变量。

3,标准定义NULL指针不指向任何位置,可以赋值指针变量为NULL,也可以赋零值。
1)机器内部NULL指针实际值不一定是零值,编绎器处理NULL指针与零值一致。
2)NULL指针不指向任何位置,解引用NULL指针是非法的。
3)位于静态内存中的指针变量自动初始化为NULL。
4)所有的指针变量都应该显式初始化为确定地址或NULL,解引用前作NULL值判断。

4,在有些机器上,操作系统与输入输出设备控制器通信,需要访问硬件本身,通过某个特定内存地址访问输入输出控制器,将地址常量强制转换为指针类型,解引用转换后的指针量,读取或写入该地址常量的内容,例*(int*)100 = 25。
5,指针变量和其他变量一样占据内存,用&操作符取地址是合法的(声明为register的变量不可&取地址),赋值给指向指针的指针。

6,指针的算术运算:指针+/-整数。
1)整数执行加减法运算前需要与指针指向元素类型大小相乘进行调整,移动整数*元素类型长度的字节。
2)适用于数组指针及malloc函数动态分配的内存。
3)对指针执行加法或减法运算超出数组范围,结果未定义。
4)指针指向最后一个元素后面的位置是合法的,但不可解引用。

7,指针-指针,两指针指向同一数组时,允许一个指针减去另一指针,结果类型为有符号整型ptrdiff_t,表以数组元素长度为单位的两指针距离,差值与数据类型无关。
1)编绎器不会检查指针表达式是否位于合法范围内,特别注意指针越界问题和指向未知值指针问题。

8,指针的关系运算,指向同一数组的两指针,使用<、<=、>、>=操作符判断指针指向更前或更后的元素。
9,可在两任意指针之间执行==、!=操作符。

函数
1,函数定义即函数体的实现,函数调用时执行函数体的代码块,形参列表包括变量和类型,代码块包含局部变量声明和函数执行语句,return语句允许从函数任何位置返回一个值。
1)如果函数不需返回值时可省略,执行到函数体尾隐式返回,没有返回值函数声明为void。
2)没有参数的函数声明的参数列表中只有一个void,表示没有参数,不是有一个类型为void的参数。
3)return返回表达式的类型即函数的返回类型,类型不同时编绎器可通过算术转换将表达式类型转换为返回类型,否则类型不符。

2,函数声明向编绎器提供函数的相关信息,确定函数正确调用。
1)使用函数原型,将原型置于单独文件,源文件再包含该文件,原型告诉编绎器函数的参数类型和数量以及返回值类型,编绎器检查函数调用的正确性。
2)或者同一源文件前面出现了函数的定义后,编绎器会记住参数类型和数量,以及函数返回类型,并可以检查后续该函数的调用是否正确。
3)函数原型后紧跟;区别于函数定义。
4)如果实参类型或返回值类型不匹配,编绎器可通过一般类型转换为正确的类型。

3,函数原型具有文件作用域,只书写一次即可作用于整个源文件。
1)不需要在每次函数调用前单独书写一份函数原型,不会出现书写的同一个原型不匹配。
2)函数定义需要修改时,修改函数原型,并重新编绎所有包含该原型的源文件。
3)函数原型#include到函数定义源文件中,编绎器可以确定函数原型与定义的匹配。

4,函数的缺省认定。
1)当程序调用一个无法见到原型的函数时,编绎器认定该函数返回整型值,它将产生整数指令操作返回值。
2)所有的函数都应该具有原型,尤其是返回值不是整型的函数。
3)对于没有原型的函数,实参执行缺省参数提升。

5,C函数所有参数以传值方式传递,函数获得实参值的拷贝,可以修改拷贝值而不影响实参。
1)如果传递的是数组名,并且在函数中使用下标引用数组元素进行修改,则会实际修改数组中的元素。
2)数组名作为指针传递,获得指针的拷贝,下标引用实际是对指针执行间接访问。

6,ADT和黑盒
1)C可以通过限制函数和数据定义的作用域设计和实现抽象数据类型,称为黑盒设计。
2)模块实现功能并提供接口调用,通过static关键字限制对非接口的函数和数据的访问,只能通过定义好的接口访问模块。

7,C通过运行时堆栈支持递归函数,追踪递归函数执行过程理解函数中声明变量怎么存储。
1)递归函数调用自身时,创建一批变量将掩盖递归函数前一次调用所创建的变量,前一次调用的变量仍保留在堆栈上,但不可访问,当前递归调用返回后变量从堆栈中销毁,递归函数前一次调用继续。
2)递归函数带来运行时开销,参数压到堆栈,局部变量分配内存空间,保存寄存器的值,递归函数返回时,局部变量和参数出栈,寄存器值恢复。
3)许多问题下迭代实现比递归实现效率更高,但代码可读性稍差一些,一个问题相当复杂,难以用迭代形式实现时,递归实现的简洁性可补偿它所带来的运行时开销。

可变参数
1,一般函数原型会列出固定数目的函数参数,让函数在不同时候接受不同数目参数称可变参数列表。
1)可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件,它是标准库的一部分,头文件中声明了一个类型va_list和三个宏——va_start、va_arg、va_end。
2)函数原型和定义时参数列表中书写省略号表示可能传递类型和数量未知的参数,函数内声明一个va_list类型变量访问参数列表未确定部分。
3)调用va_start初始化va_list变量,宏va_start第一个参数是va_list变量,第2个参数是省略号前最后一个参数,初始化过程把va_list变量设置为指向可变参数部分的第一个参数。
4)为了访问参数,调用va_arg宏,第一个参数是va_list变量,第二个参数是当前遍历的可变参数的类型(有些函数中可能通过前面获得的数据判断下一个参数的类型),然后返回这个可变参数的值,并使va_arg指向下一个可变参数。
5)访问完最后一个可变参数,调用va_end,参数为va_list变量。

2,可变参数须按照顺序从头到尾遍历,可提前终止访问,但不可从中间开始访问。
3,可变参数类型没有原型,所以可变实参值执行缺省的参数类型提升。

4,参数列表中至少要有一个命名参数,否则无法使用va_start,且宏无法判断实际存在的参数数量,同样无法判断每个参数类型,至少需要一个命名参数,传递参数数量去遍历可变参数,并且指定参数类型或根据上个参数值判断。
1)例printf函数中命名参数是格式字符串,不仅指定参数的数量,且指定了参数的类型。
2)如果在va_arg中指定了错误的类型,结果未定义。
3)va_arg执行缺省的类型提升,其他类型转换结果未知。

猜你喜欢

转载自blog.csdn.net/mei_true/article/details/131070813