*ap=0;
说明:
上面两个程序都执行了将array数组清零的任务。
例1中,为了对下标表达式求值,编译器在程序中插入指令,取a的值,并把它与整型的长度4相乘。
这个乘法需要花费一定的时间和空间。
例2中,循环每次执行时,执行乘法运算的数都是两个相同的数(1和4)。
结果,这个乘法只在编译时执行一次——程序现在包含了一条指令,把4与指针相加。
程序运行时并不执行乘法运算。
这个例子说明了指针比下标更有效率的场合——当你在数组中1次1步地移动时,与固定数字相乘的运算在编译时完成,所以运行时所需的指令就少一些。
例3:
a=get_value();
array[a]=0;
例4:
a=get_value();
*(array+a)=0;
说明:
上面两个例子所产生的代码并无区别。
a可能是任何值,在运行时方知。
所以两种方案都需要乘法指令,用于对a的值进行调整。
这个例子说明了指针和下标的效率完全相同的场合。
指针的效率
1.当你根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生效率更高的代码。
当这个增量是1并且机器具有地址自动增量模式时,这点表现得更为突出。
2.声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高。
3.如果你可以通过测试一些已经初始化并经过调整的内容来判断循环是否应该终止,那么你就不需要使用一个单独的计数器。
4.那些必须在运行时求值的表达式较之如&array[SIZE]或array+SIZE这样的常量表达式往往代价更高。
数组和指针
例如:
inta[5];
int*b;
说明:
声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。
声明一个指针变量时,编译器只为指针本身保留内存空间,如果它是一个自动变量,它甚至根本不会被初始化。
因此,上述声明之后,表达式*a是完全合法的,但表达式*b却是非法的。
*b将访问内存中某个不确定的位置,或者导致程序终止。
另一方面b++可以通过编译,但a++不可以,因为a的值是常量。
作为函数参数的数组名
例如:
voidstrcpy(char*buffer,charconst*string)
{
while((*buffer++=*string++)!
=’\0’);
}
说明:
while语句中的*string++表达式,它取得string所指向的那个字符,并且产生一个副作用,就是修改string,使它指向下一个字符。
用这种方式修改形参并不会影响调用程序的实参,因为只有传递给函数的那份拷贝进行了修改。
另外关于这个函数,还有两个要点值得一提。
首先,形参被声明为一个指向const字符的指针。
对于一个并不打算修改这些字符的函数而言,预先把它声明为常量,第一,这是一个良好的文档习惯,第二,编译器可以捕捉到任何试图修改该数据的意外错误,第三,这类声明允许向函数传递const参数。
声明数组参数
例如:
intstrlen(char*string);
intstrlen(charstring[]);
说明:
调用函数时实际传递的是一个指针,所以函数的形参实际上是一个指针。
上面两个函数原型是相等的。
但是第一个指针形式更加准确,因为实参实际实际上是个指针,而不是数组。
函数原型中的一维数组形参无需写明它的元素数目,因为函数并不为数组参数分配内存空间。
形参只是一个指针,它指向的是已经在其他地方分配好内存的空间。
初始化
1.静态初始化:
数组初始化的方式类似于标量变量的初始化方式——也就是取决于它们的存储类型。
存储于静态内存的数组只初始化一次,也就是在程序开始执行之前。
程序并不需要执行指令把这些值放到合适的位置,它们一开始就在那里了。
如果数组未被初始化,数组元素的初始值将会自动设置为零。
当这个文件载入到内存中准备执行时,初始化后的数组值和程序指令一样也被载入到内存中。
因此当程序执行时,静态数组已经初始化完毕。
2.自动初始化:
因为自动变量位于运行时堆栈中,执行流每次进入它们所在的代码块时,这类变量每次所处的内存位置可能并不相同。
在程序开始之前,编译器没有办法对这些位置进行初始化。
所以,自动变量在缺省情况下是未初始化的。
如果自动变量的声明中给出了初始值,每次当执行流进入自动变量声明所在的作用域时,变量就被一条隐式的赋值语句初始化。
这条隐式的赋值语句和普通的赋值语句一样需要时间和空间来执行。
数组的问题在于初始化列表中可能有很多值,这就可能产生许多条赋值语句。
对于那些非常庞大的数组,可能它的初始化时间可能非常可观。
因此,这里就需要权衡利弊。
当数组的初始化局部与一个函数(或代码块)时,在程序的执行流每次进入该函数(或代码块)时,每次都对数组进行重新初始化是不是值得。
如果答案是否定的,你就把数组声明为static,这样数组的初始化只需在程序开始前执行一次。
不完整的初始化
例如:
intvector[5]={1,2,3,4,5,6};
intvector[5]={1,2,3,4};
说明:
在这两种情况下,初始化值的数目和数组元素的数目并不匹配。
第1个声明是错误的,我们没有办法把6个整型值装到5个整型变量中。
但是,第2个声明是合法的,它为数组前4个元素提供了初始值,最后一个元素则初始化为0。
自动计算数组长度
例如intvector[]={1,2,3,4,5};
说明:
如果声明中并未给出数组的长度,编译器就把数组的长度设置为刚好能够容纳所有的初始值的长度。
如果初始值列表经常修改,这个技巧尤其重要。
字符数组的初始化
例如:
charmessage[]={‘H’,’e’,’l’,’l’,’o’,0};
charmessage[]=”hello”;
说明:
第二条程序,尽管看上去它像是一个字符串常量,实际上并不是。
它是前例的初始化列表的另一种写法。
当用于初始化一个字符数组时,它就是一个初始化列表。
在其他任何地方,它都表示一个字符串常量。
例如:
charmessage1[]=”Hello”;
char*message2=”Hello”;
说明:
前者初始化一个字符数组的元素,而后者则是一个真正的字符串常量。
这个指针变量被初始化为指向这个字符串常量的存储位置。
●多维数组
例如:
inta;
intb[10];
intc[6][10];
intd[3][6][10];
说明:
a是个简单整数;b是个向量,包含10和整数;c只是在b的基础上再加一维,可以把c看作是一个包含6个元素的向量,只不过它的每个元素本身是一个包含10个整形元素的向量,即c是一个一维数组的一维数组;d本身是一个包含3个元素的数组,每个元素都是包含6个元素的数组,而这6个元素中的每一个又都是包含10个整型元素的数组。
简洁的说,d是一个3排6行10列的整型三维数组。
存储顺序:
在C中,多维数组的元素存储顺序按照最右边的下标率先变化的原则,称为行主序。
数组名:
一维数组名的值是一个指针常量,它的类型是“指向元素类型的指针”,它指向数组的第一个元素。
而多维数组的第1维的元素实际上是一个数组。
例如:
intmatrix[3][10];
说明:
上面的声明创建了matrix,它可以看作是一个一维数组,包含3个元素,只是每个元素恰好是包含10个整型元素的数组。
matrix这个名字的值是一个指向它第一个元素的指针,所以matrix是一个指向一个包含10个整型元素数组的指针。
下标:
matrix
例如:
intmatrix[3][10];
matrix[1][5]
matrix
matrix+1
说明:
如上图所示matrix的类型是“指向包含10个整型元素的数组的指针”,它指向包含10个整型元素的第1个子数组。
matrix+1也是一个“指向包含10个整型元素的数组的指针”,但它指向matrix的另一行。
因为1这个值根据包含10个整型元素的数组的长度进行调整,所以它指向matrix的下一行。
*(matrix+1):
事实上标识了一个包含10个整型元素的子数组。
数组名的值是一个常量指针,它指向数组的第1个元素。
*(matrix+1)+5:
前一个表达式是个指向整型值的指针,所以5这个值根据整型的长度进行调整。
整个表达式的结果是一个指针,它指向的位置比原先那个表达式所指向的位置向后移动了5个整型元素。
*(*(matrix+1)+5):
它所访问的正是图中的matrix[1][5]。
如果它作为右值使用,你就取得存储于那个位置的值。
如果它作为左值使用,这个位置将存储一个新值。
上面这个表达式与*(matrix[1]+5)、matrix[1][5]相同。
第四章结构和联合
●结构基础知识
结构声明:
structtag{member-list}variable-list;
结构成员:
结构成员的直接访问:
结构成员的间接访问:
结构的自引用:
不完整的声明:
结构的初始化:
链表:
节点的集合。
链表中的每个节点通过链或指针连接在一起。
程序通过指针
访问链表中的节点。
●单链表:
在单链表中,每个节点包含一个指向链表下一节点的指针。
根指针(rootpointer)指向链表第1个节点,找到根指针后,指针就可以带你访问剩余所有的节点。
最后一个节点中的指针为NULL指针。
typedefstructNODE{
structNODE*link;//节点中的指针,指向下一个节点;
intvalue;//节点中的数据为整型值;
}
在链表的起始位置插入一个节点(函数必须修改根指针)
方法1:
由于函数不能访问变量root,可把root声明为