C++ Primer 数组 笔记

数组定义时的编译器扩展
因为数组中元素的个数是属于数组类型的一部分,编译的时候维度应该是已知的,所以说,定义数组时数组的维度必须是一个常量表达式,如下:

unsigned cnt = 42;   // 不是常量表达式
constexpr unsigned sz = 42;   // 常量表达式,关于constexpr,参见2.4.4节
int arr[10];   // 含有10个整数的数组
int *parr[sz];   // 含有42个整形指针的数组
string bad[cnt];   // 错误:cnt不是常量表达式
strign strs[get_size()];   // 当get_size是constexpr时正确;否则错误
int score[10] = {};   // 注意,这里由于使用了{},因此不管score是定义在函数体内还是外,其元素值都会别初始化为0。

:对于上面代码的第5行,一些编译器不会报错,这就是所谓的编译器扩展(compiler extension)。但一般来说,最好避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上无法正常工作。

理解复杂的数组声明
就数组而言,由内向外阅读要比从右向左好,如下:

int arr[10];
int (*Parry)[10] = &arr;   // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr;   // arrRef引用一个含有10个整数的数组

比如Parray,由内向外的顺序可帮助我们更好的理解它的含义:首先是圆括号起来的部分,*Parray意味着Parray是个指针,接下来观察右边,可知道Parray是个指向大小为10的数组的指针,最后观察左边,知道数组中的元素是int。这样最终的含义就明白无误了,Parray是一个指针,它指向一个int数组,数组中包含了10个元素。同理,(&arrRef)表示arrRef是一个引用,它引用的对象时一个大小为10的数组,数组中元素的类型是int。

访问数组元素
数组可使用下标运算符 [ ] 来访问其中的元素,但稍微注意,数组所使用的下标运算符[ ]是由C++语言直接定义的,这个运算符能用在数组类型的运算对象上。93页的那个程序所用的下标运算符是库模板vector定义的,只能用于vector类型的运算对象。
与vector和string一样,当需要遍历数组的所有元素时,最好的办法也是使用范围for语句。例如,下面的程序输出所有的scores:

for (auto i : scores)
	cout << i << " ";
cout << endl;

**因为维度是数组类型的一部分,所以系统知道数组scores中有多少个元素,**使用范围for语句可以减轻人为控制遍历过程的负担。

指针和数组
C++语言中,使用数组的时候编译器一般会把它转换成指针。
通常情况下,使用取地址符来获取指向某个对象的指针,取地址符可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能得到指向该元素的指针:

string nums[] = {"one", "two", "three"};   // 数组的元素是string对象
string *p = &nums[0];   // p指向nums的第一个元素

由上可知,在一些情况下数组的操作实际上是指针的操作,这一结论有很多隐含的意思。其中一层意思是当使用数组作为一个auto(参见2.5.2节,61页)变量的初始值时,推断得到的类型是指针而非数组:

int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}   // ia是一个含有10个整数的数组
auto ia2(ia);   // ia2是一个整型指针,指向ia的第一个元素
ia2 = 42;   // 错误:ia2是一个指针,不能用int值给指针赋值

尽管ia是由10个整数构成的数组,但当使用ia作为初始值时,编译器实际执行的初始化过程类似于下面的形式:

auto ia2(&ia[0]);   // 显然ia2的类型是int*

但必须指出的是,当使用decltype关键字(参见2.5.3节,第62页)时,上述转换不会发生,decltype(ia)返回的类型是由10个整数构成的数组:

// ia3是一个含有10个整数的数组
decltype(ia) ia3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
ia3 = p;   // 错误:不能用整型指针给数组赋值
ia3[4] = i;   // 正确:把i的值赋给ia3的一个元素

指针也是迭代器
vector和string的迭代器(参见3.4节,第95页)支持的运算,数组的指针全都支持。
就像使用迭代器遍历vector对象中的元素一样,使用指针也能遍历数组中的元素。当然,这样做的前提是先得获取到指向数组第一个元素的指针和指向数组尾元素的下一位置的指针(尾后指针)。我们可以设法获取数组尾元素之后的那个并不存在的元素的地址:

int *e = &arr[10];

for (int *b = arr; b != e; ++b)
	cout << *b << endl;   // 输出arr的元素

注意,不能对上面的变量e执行解引用或递增的操作。

标准库函数begin和end
尽管能计算得到尾后指针,但这种用法极易出错。C++11新标准引入了两个名为begin和end的函数。这两个函数与容器中的两个同名成员(参见3.4.1节,第95页)功能类似,不过数组毕竟不是类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为它们的参数:

int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};   // ia是一个含有10个整数的数组
int *beg = begin(ia);   // 指向ia首元素的指针
int *last = end(ia);   // 指向arr尾元素的下一个位置的指针

使用begin和end可以很容易地写出一个循环并处理数组中的元素。例如,假设arr是一个整型数组,下面的程序负责找到arr中的第一个负数:

// pbeg指向arr的首元素,pend指向arr尾元素的下一位置
int *pbeg = begin(arr), *pend = end(arr);
// 寻找第一个负值元素,如果已经检查完全部元素则结束循环
while (pbeg != pend && pbeg >= 0)
	++pbeg;

指针运算
如果指针执行加法后超出了尾后元素位置将产生错误,而且这种错误编译器一般发现不了。
和迭代器一样,两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组当中的元素。两个指针相减的结果的类型是一种名为 ptrdiff_t 的标准库类型,和size_t一样,ptrdiff_t 也是一种定义在 cstddef 头文件中的机器相关的类型。因为差值可能为负值,所以 ptrdiff_t 是一种带符号类型。
只要两个指针指向同一个数组的元素,或者指向该数组的尾后位置,就能利用关系运算符对其进行比较。如果两个指针分别指向不相关的对象,则不能比较它们。

下标和指针

int ia[] = {0, 2, 4, 6, 8};   // 含有5个整数的数组
int *p = &ia[2];   // p指向索引为2的元素
int j = p[1];   // p[1]等价于 *(p + 1),就是ia[3]表示的那个元素
int k = p[-2];   // p[-2]是ia[0]表示的那个元素

虽然标准库类型string和vector也能执行下标运算,但是数组与它们相比还是有所不同。标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求,上面的最后一个例子很好地说明了这一点。

混用string对象和C风格字符串
如果程序的某处需要一个C风格字符串,无法直接用string对象来代替它。例如,不能用string对象直接初始化指向字符的指针。为了完成该功能,sttring专门提供了一个名为c_str的成员函数:

string s("Hello World");   // s的内容是Hello World
char *str = s;   // 错误:不能用string对象初始化char*
const char *str = s.c_str();   // 正确

使用数组初始化vector对象
3.5.1节(第102页)介绍过不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。相反的,允许使用数组来初始化vector对象。要实现这一目的,只需指明要拷贝区域的首元素地址和尾后地址就可以了:

int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr), end(int_arr))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值