提问:如果我们要获得a数组的第二个元素,请问一下那种方式可以达到要求。
int a[] = { 1,2,3,4,5,6 };
cout << a[1] << endl;
cout << *(a+1) << endl;
cout << 1[a] << endl;
cout << (1)[a] << endl;
答案是都可以。
首先,ar[i]和*(ar+1)这两个表达式都是等价的,这里就不再多说了,而1[a]
与 (1)[a]
这是个什么东西,好像以前没见过啊。
对于数组而言, 要访问一个数组元素无非是 数组名[下标] ⇒ base[offset] // 基地址+偏移
,因此[ ]
运算符与数组在一起时我们把它叫做下标运算符。
用于识别数组元素的数字被称为下标(subscript)、索引(indice)或
偏移量(offset)。下标必须是整数,而且要从0开始计数。数组的元素被依
次储存在内存中相邻的位置。
然而下标运算符还有另外的用法似乎一直被我们忽略了,下面将演示它的另外两种用法 前缀式 与 负下标。
参考:https://docs.microsoft.com/zh-cn/cpp/cpp/subscript-operator?view=msvc-170
下标运算符也可以称之为后缀表达式,因为它在使用时是一个数组名加一个后缀的形式。
postfix-expression [ expression ]
一般来说,后缀表达式 表示的值是一个指针值(如数组标识符),而 expression 是一个整数值 (包括) 枚举类型。 但是,从语法上来说,只需要一个表达式是指针类型,另一个表达式是整型。
因此,我们可以用这样 下标[数组名] ⇒ offset[base] // 偏移基+地址
来表示数组中的某个数。而可以这样做的原因在MicroSoft Document给出了解释
下标表达式的结果 e1[e2] 是由指定的:
*((e2) + (e1))
.
表达式生成的地址不是来自地址e1的e2字节。 相反,地址将进行缩放以生成数组 e2中的下一个对象。
这里我们利用*(ar+1) 与 ar[1] 来理解就会清晰很多,前者都是在a的地址基础上偏移一个 单位 的位置上取数据 ,后者也是同样的道理。
而对于如何理解下标运算符,MicroSoft Document也给出了示例:
double aDbl[2];
和的地址aDb[0]
与aDb[1]
类型的对象的大小相隔8个字节 —double
。 这种根据对象类型进行缩放的操作是由 c + + 语言自动完成的,并且在 加法运算符 中定义,其中讨论了指针类型的操作数的加法和减法。
表达式2[2] [expression3]
下标表达式从左至右关联。 首先计算最左侧的下标表达式 expression1[expression2]。 通过添加 expression1 和 expression2 得到的地址构成一个指针表达式;然后 expression3 将添加到此指针表达式,从而构成一个新的指针表达式,依此类推,直到添加最后一个下标表达式。 在
*
计算最后一个下标表达式后 () 的间接寻址运算符(除非最终指针值用于处理数组类型)。
这里讲的下标表达式可以支持多个下标,其实就是我们的多维数组的使用方式。例如 nums[i][j], array[i][j][k] 等。
数组的第一个元素是元素 0。 C + + 数组的范围是从 array[0] 到 array[size -1]。 但是,C++ 支持正负下标。 负下标必须在数组边界内;否则结果不可预知。 以下代码显示了正数组和负数组下标:
#include
#include
using namespace std;
int main() {
int intArray[1024];
/* 初始化,intArray:{0,1,2,3,4...1023 } */
iota(intArray, intArray + _countof(intArray), 0);
cout << intArray[512] << endl; // 512
cout << 257[intArray] << endl; // 257
int* midArray = &intArray[512]; // pointer to the middle of the array
cout << midArray[-256] << endl; // 256
cout << intArray[-256] << endl; // unpredictable, may crash
}
这里midArray 指向了intArray中间的位置,因此midArray[-256]
其实使用了 减法运算符(指针减法),即——以midArray 地址为基准向左偏移256个单位。
关于减法运算符:指针减法
如果两个操作数都是指针,则减法运算的结果就是两个操作数之差(在数组元素中)。 减法表达式生成在标准包含文件 ptrdiff_t < stddef.h >
.
其中一个操作数可以是整型,条件是该操作数是第二操作数。 减法的结果的类型与原始指针的类型相同。 减法的值是指向第 () 数组元素的指针,其中 n 是原始指针指向的元素 ,i是第二个操作数的整数值。
而 intArray[-256] 的结果之所以不可预测是因为,intArray地址是数组起始位置,如果向左寻址则超出数组界限之外,而在那部分的内存是我们程序未申请的部分,如果强行访问可能会造成未知的后果。
再举个例子:我们使用负下标输出数组nums的所有元素即其地址
int nums[] = { 1,2,3,4,5,6,7,8,9 };
const int len = _countof(nums); // nums元素个数
int* tail = nums + len; // 指向nums尾部
cout << static_cast<void*>(nums) << " ——address of nums \n";
cout << static_cast<void*>(tail) << " ——address of tail \n";
for (int i = 1; i <= len; ++i)
{
cout << static_cast<void*>(tail - i)
<< " offfset:" << i << " value="
<< tail[-i] << "\n";
}
cout << endl; // 9 8 7 6 5 4 3 2 1
运行结果:
从运行结果可以看出,tail每次的偏移量左移一个单位,地址值减少4(int类型所占字节大小),而随着9个元素偏移量全部算在内时,它的地址就等于初始时nums的地址。