C和指针(五)数组

一维数组

数组

1,C中数组名的值是一个指针常量,是数组第一个元素的地址,是指向数组元素类型的指针常量。

2,数组具有确定数量的元素,当数组名在表达式中使用时,表示为一个指针常量,在sizeof操作符和取地址&操作符时,数组名不作为指针常量。
1)sizeof返回整个数组长度。
2)&返回指向数组的指针,而不是指向数组元素的指针。

3,数组的下标引用和使用数组名+偏移量间接访问完全相同,*(array+2)等同于array[2]。

指针与数组
1,根据某个固定数目的增量在数组中移动时,使用指针变量比使用下标效率更高,增量为1且机器具有地址自动增量模型时,效率表现更突出。
1)有些情况下使用指针表达式比下标表达式更有效率,在数组中一次一步或某个固定数字地移动时,与固定数字相乘的运算在编绎时完成,但以下标遍历数组时下标值通常不是固定数字,与数组元素长度相乘,移动相应位置取得下标元素时,乘法需要花费一定的时间和空间。
2)而以指针遍历数组时,循环中移动的固定数字在编绎时完成数组元素与固定数字相乘,指针与移动结果相加得到数组元素,运行时不执行乘法运算,运行指令减少。
3)与固定数字相乘运算在编绎时完成,如果下标或指针移动数字在运行时方知,则两种方案运行时都需要乘法指令,效率无区别。
4)但是使用指针做循环判断时,如果指针与结束指针的距离判断使用加法或减法运算时需要执行乘法或除法运算,如果机器具有32位除法指令,除法运算可能更有效率,但每次循环判断都需要执行运算,效率不高,可使用整型计数器控制循环,去除指针加法或减法运算。

2,声明为寄存器变量的指针比位于静态内存和堆栈中指针效率更高。
1)将指针声明为寄存器变量,就不需要复制指针值到寄存器,使用硬件地址自增直接增加指针的值。
2)循环判断将指针直接与数组尾地址比较,不需要执行加减法运算也没有计数器指令,数组尾地址可以在编绎时求值,效率更高。
3)通过一些已经初始化并经过调整的内容判断循环终止,就不需要使用单独的计数器。

3,从下标引用到使用寄存器变量的指针,最终代码节约几十微秒的运行时间,但代码难编写难维护。
1)有些机器在设计时使用特殊指令,用于执行数组下标操作,编绎器使用这些特殊指令实现下标表达式,但编绎器不一定会用这些指令来实现指针表达式,导致下标比指针效率更高。
2)绝大多数情况下还是会使用下标表达式,循环容易理解,但在一些场合,追求峰值效率至关重要,例如必须对即时发生的事件作出最快反应的实时程序,可通过以上技巧提高运行时效率。

4,必须在运行时求值的表达式比&array[SIZE]或array+SIZE这样的常量表达式代价更高。
5,指针和数组名都可以间接访问和下标引用。

声明
1,在许多机器上,register变量产生的代码比静态内存和堆栈中变量产生的代码执行速度更快,但编绎器可以更合理的分配寄存器,register声明过多反而降低效率。
1)声明数组时,编绎器根据声明指定的元素为数组保留内存空间,再创建数组名,它的值是一个常量,指向这段空间的起始位置。
2)声明指针变量时,编绎器只为指针本身保留内存空间,并不分配指向元素的内存空间,如果是一个自动变量,甚至不会初始化。
3)对数组名可解引用,指针未初始化前不可解引用,但不可对数组名自增或自减。
4)数组名作为实参传递给函数,实际值是指向数组第一个元素的指针,函数拷贝该指针,对指针执行下标引用,实际是对指针执行间接访问操作,可以访问并修改数组元素,修改指针的值但不影响实参的指针,对指针间接访问修改的是数组元素的值。

2,函数形参声明为数组和指针都可以,声明为指针更为准确。
3,数组形参是一个已经分配好内存的空间,不需要再为数组参数分配内存空间,实参可以是任何长度的数组,实际传递的是指向数组第一个元素的指针,如果函数需要知道数组长度,必须显式传递参数给函数。

初始化
1,数组可通过初始化列表初始化。

2,存储于静态内存的数组在程序开始执行之前初始化。
1)程序不需要执行指令将初始化值赋给数组,链接器将初始值存放到合适位置。
2)如果数组无初始化值,数组元素初始值默认为零。
3)当可执行文件加载到内存,初始值和程序指令一起加载到内存,初始值加载到静态数组内存位置,程序执行时,静态数组已经初始化完毕。

3,自动变量缺省情况下是未初始化的。
1)自动变量位于运行时堆栈,执行流每次进入它们所在的代码块时,这类变量所处的内存位置都可能不同,所以程序开始之前,编绎器无法对这些位置进行初始化。
2)如果变量声明中给出了初始值,当执行流进入自动变量声明所在作用域时,变量由隐式赋值语句初始化。
3)初始值列表中存在多个值时,则会产生多条赋值语句。

4,如果数组非常庞大则需要考虑执行流每次进入该代码块对数组重新初始化还是声明数组为static初始化一次。

5,初始化列表允许小于数组指定长度,部分初始化数组,数组尾端几个元素初始化为0。
6,数组不指定长度时,编绎器将数组长度设置为初始化值列表长度。
7,字符串常量作为初始值列表初始化字符数组时,表示初始值列表而不是字符串常量。

多维数组

数组名
1,C中多维数组元素的存储顺序按照最右边的下标率先变化原则,称为行主序。
2,一维数组名的值是一个指针常量,类型是指向元素类型的指针,指向数组第一个元素,多维数组第一维实际是另一个数组,数组名的值为指向第一个元素的指针,则数组名为指向另一个数组的指针。
3,多维数组名+整数,整数需要进行调整*子数组长度,移动到另一个子数组,对其执行间接访问操作得到一个子数组,子数组+整数,根据子数组元素长度进行调整,得到指向子数组整数下标元素的指针,对指针进行间接访问得到数组元素。
4,可用下标代替间接访问,下标是从左向右进行计算,数组名是一个指向第一维元素的指针,第一个下标根据该元素的长度进行调整,结果是一个指向第一维某元素的指针,间接访问操作选择第一维的元素,该元素本身是个数组,所以表达式类型是一个指向下一维第一个元素的指针,下一个下标值根据下一维元素长度进行调整,这个过程重复进行,直到所有下标计算完毕。

指针
1,指向数组的指针int (*p)[10];下标引用优先级高于间接访问,所以加上括号表示p是个指针,指向的是长度为10的整型数组。
1)必须指定数组长度,当指针+整数,整数需要根据数组长度进行调整。
2)如果声明指针指向数组长度为空,则根据空数组的长度进行调整。

2,指针数组int *api[10];下标引用优先级高于间接访问,api是10个指向整型元素的指针数组。

3,作为函数参数的多维数组名传递方式和一维数组名相同,实际传递的是指向数组第一个元素的指针。
1)区别在于多维数组每个元素是另外一个数组,编绎器需要知道维数,以便为函数形参的下标表达式求值。
2)编绎器必须知道第二维及以后各维长度才能根据维数对下标数进行调整,只有第一维可以省略。

初始化
1,多维数组初始化。
1)一种方式是初始值列表,存储顺序按最右边下标率先变化为原则确定。
2)另一种方式基于多维数组是复杂元素的一维数组,使用复杂元素初始值列表,组合成多维数组的初始值列表。每个初始值列表各自都是一个初始值列表,列表中可省略最后几个初始值。

2,多维数组中,只有第一维可根据初始化列表推断,剩余几维必须显式提供。

总结
1,在绝大多数表达式中,数组名是指向数组第一个元素的指针,sizeof和单目操作符&除外。
1)sizeof是返回整个数组所占用的字节而不是一个指针所占用字节。
2)&是返回一个指向数组的指针,而不是指向指向数组第一个元素的指针的指针。

2,除了优先级不同,下标表达式array[value]和间接访问表达式*(array+value)是一样的。
3,下标不仅可以用于数组名也可以用于指针表达式中。
4,声明数组时,同时分配了数组内存空间,用于容纳数组元素,声明指针时,只分配了指针的内存空间。

5,数组名作为函数参数传递时,传递给函数的是指向数组第一个元素的指针,函数接收到的参数是指针的拷贝。
1)函数可以修改指针指向不会影响原参数,但对指针间接访问会修改数组的元素。
2)数组形参既可以声明为指针,也可以声明为指针,两种声明方式只有作函数形参时是相等的。

6,数组可以用初始值列表初始化。
1)静态变量包括数组在程序加载到内存时得到初始值。
2)自动变量包括数组每次当执行流进入声明所在代码块时使用赋值语句初始化。
3)初始值列表个数少于数组元素个数时,数组最后几个元素缺省值初始化。
4)如果数组长度未给出由初始值列表个数推断。

7,多维数组是一维数组的特型,每个元素本身也是一个数组,元素根据行主序存储,最右边的下标率先变化 。
8,多维数组名是指向数组第一个元素的指针,即指向数组的指针,对该指针进行运算将根据它所指向数组的长度对操作数进行调整。
9,多维数组下标引用也可以是指针表达式。
10,当多维数组名作为函数参数传递时,形参声明中必须显式指明第二维及以后维长度。
11,多维数组可由嵌套的初始值列表初始化,只有第一维的长度可由编绎器推断。

猜你喜欢

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