我如何使用C ++数组? [英] How do I use arrays in C++?

查看:154
本文介绍了我如何使用C ++数组?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++继承了C,其中使用它们几乎无处不阵列。 C ++提供了更易于抽象使用,更不容易出错(的std ::矢量< T> 由于C ++ 98的 的std ::阵列< T,N> 以来的C++11 ),因此需要对数组不会出现很经常,因为它在C确实然而,当你阅读传统code或C编写的库进行交互,你应该有数组是如何工作的牢固掌握。

这常见问题分成五个部分:


  1. 在类型级别阵列和访问元素

  2. 阵列创建和初始化

  3. 分配和参数传递

  4. 多维数组和指针数组

  5. 常见的陷阱,使用数组时

如果你觉得重要的东西在这个FAQ缺失,写答案,并在此处链接作为一个额外的部分。

在下文中,阵列是指C数组,而不是类模板的std ::阵列。假定的C声明语法的基本知识。请注意,删除作为证明下面是例外面对极其危险的,但是这是手动使用另一个常见问题。

<子>
(注:这是为了是堆栈溢出的C ++ FAQ 条目如果你想批评。以这种形式提供常见问题的想法,然后的张贴在这开始这一切元是做到这一点的地方。回答这个问题在 C的监测++聊天室,那里的FAQ想法摆在首位开始了,所以你的回答很可能得到那些谁的想法来到了阅读。)


解决方案

的类型级别阵列

这是数组类型记为 T [N] ,其中 T 是的元素类型 N 是一个积极的尺寸的,数组中元素的个数。数组类型是元素的类型和尺寸的产品类型。如果一个或两个这些成分的不同,你会得到一个截然不同的类型:

 的#include&LT; type_traits&GT;static_assert(性病:: is_same&LT;!INT [8],浮[8]&GT; ::价值,不同的元素类型);
static_assert(性病:: is_same&LT;!INT [8],INT [9]&GT; ::价值,不同大小);

注意大小是类型的一部分​​,也就是数组类型不同规模的不兼容类型绝对无关,与对方。 的sizeof(T [N])等同于 N * sizeof的(T)

阵列到指针衰减

之间唯一的连接 T [N] T [M] 是两种类型可以隐是的转换 T * ,而这种转换的结果是一个指向数组的第一个元素。也就是说,任何地方 T * 是必需的,你可以提供一个 T [N] ,编译器会默默地提供指针:

  + --- + --- + --- + --- + --- + --- + --- + --- +
the_actual_array:| | | | | | | | | INT [8]
                  + --- + --- + --- + --- + --- + --- + --- + --- +
                    ^
                    |
                    |
                    |
                    | pointer_to_the_first_element为int *

此转换被称为阵列到指针衰变,它是混乱的一个主要来源。阵列的大小是失去了在这个过程中,因为它是该类型不再一部分( T * )。优点:忘记的类型水平的数组的大小允许指针指向的任何的大小的数组的第一个元素。缺点:给定一个指针到第一(或任何其他)的阵列的元件,也没有办法来检测阵列多大或完全相同的指针指向相对于该阵列的边界的位置。 指针是愚蠢至极

数组不是指针

,编译器会悄悄地产生一个指向数组的第一个元素时,它被认为是有用的,那就是,每当操作将失败的阵列上,但是,一个指针成功。这种由数组指针转换是平凡的,因为结果指针的的仅仅是数组的地址。注意,指针的的存储阵列本身(或者在存储器中其他地方)的一部分。 数组是不是一个指针。

  static_assert(的std :: is_same&LT;!INT [8],INT * GT; ::价值的数组不是指针);

在一个数组做的的当&放衰变成一个指针,它的第一个元素是一个重要方面; 运算符应用于它。在这种情况下,在&放大器; 操作者产生一个指针的整个的阵列,而不是仅仅一个指向它的第一个元素。虽然在这种情况下的的(地址)是相同的,一个指针数组的第一元素和一个指向整个阵列是完全不同的类型:

  static_assert(的std :: is_same&LT;!为int *,INT(*)[8]&GT; ::价值,不同的元素类型);

下面的ASCII艺术说明了这种区别:

  + ----------------------------------- +
      | + --- + --- + --- + --- + --- + --- + --- + --- + |
+ - &GT; | | | | | | | | | | | INT [8]
| | + --- + --- + --- + --- + --- + --- + --- + --- + |
| + --- ^ ------------------------------- +
| |
| |
| |
| | pointer_to_the_first_element为int *
|
| pointer_to_the_entire_array INT(*)[8]

请注意如何指针的第一个元素仅指向一个整数(描绘为一个小盒子),而指向整个阵列点8的整数数组(描绘为一个大的框)。

同样的情况也出现在课堂上,是可能更为明显。一个指向对象的指针,它的第一个数据成员具有相同的的(同一地址),但它们是完全不同的类型。

如果您不熟悉与C声明语法,在类型括号 INT(*)[8] 是必不可少的:


  • INT(*)[8] 是一个指向8的整数数组。

  • 为int * [8] 8指针数组,类型的每个元素为int *

访问元素

C ++提供了两种语法变化,以访问数组的单个元素。
他们既不是优于其他,你应该熟悉这两种。

指针运算

给定一个指针 P 到一个数组的第一个元素,前pression P + I 产生的指针数组的第i个元素。通过事后取消引用该指针,可以访问各个元素:

 的std ::法院LT&;&LT; *(X + 3)&所述;&下; ,&所述;&下; *(X + 7)所述;&下;的std :: ENDL;

如果 X 表示的阵列的,那么阵列到指针的衰减会踢,因为添加一个数组和一个整数是无意义的(有关于阵列没有加操作),但添加一个指针和一个整数有意义

  + --- + --- + --- + --- + --- + --- + --- + --- +
X:| | | | | | | | | INT [8]
   + --- + --- + --- + --- + --- + --- + --- + --- +
     ^ ^ ^
     | | |
     | | |
     | | |
X + 0 | X + 3 | X + 7 |为int *

(注意,隐式生成的指针没有名字,所以我写了 X + 0 ,以确定它。)

如果,在另一方面, X 表示的指针的到第一(或任何其他)的数组的元素,则基于阵列到指针衰减没有必要,因为指针上 I 将要加入已存在:

  + --- + --- + --- + --- + --- + --- + --- + --- +
   | | | | | | | | | INT [8]
   + --- + --- + --- + --- + --- + --- + --- + --- +
     ^ ^ ^
     | | |
     | | |
   + - | - + | |
X:| | | X + 3 | X + 7 |为int *
   + --- +

请注意,在描述的情况下, X 是一个指针的变量的(由小方块明显旁边的 X ),但它也可以同样是返回一个指针(或任何其他类型的前pression一个函数的结果 T * )。

索引操作符

由于语法 *(X + I)是一个有点笨拙,C ++提供了替代语法 X [I]

 的std ::法院LT&;&LT; ×〔3]所述;&下; ,&所述;&下; ×〔7]所述;&下;的std :: ENDL;

由于这样的事实,除了是可交换的,下面的code不完全相同的:

 的std ::法院LT&;&LT; 3 [X]&下;&下; ,&所述;&下; 7 [X]&下;&下;的std :: ENDL;

索引操作符的定义导致以下有趣等价

 &放大器; X [I] ==&放大器; *(X + I)== X +我

然而,&放大器; X [0] 一般的等同于 X 。前者是一个指针,后者的阵列。只有当上下文触发的阵列到指针衰变能 X &放大器; X [0] 可以互换使用。例如:

  T * p =&放大器;数组[0]; //重写为&放大器; *(数组+ 0),衰减发生由于加入
T * Q =阵列; //衰变发生因分配

在第一行,编译器检测从指针的指针,这平凡的成功转让。在第二行,它检测到来自的数组赋值的一个指针。由于这是没有意义的(不过的指针应用于指针赋值是有道理的),数组到指针衰减踢如常。

范围

类型的数组 T [N] N 元素,从索引 0 N-1 ;没有元素 N 。然而,为了支持半开的范围(其中,开始是<青霉>包容和端的独家的),C ++允许指针的(不存在)的计算第n个元素,但它是非法的解引用该指针:

  + --- + --- + --- + --- + --- + --- + --- + --- + ...
X:| | | | | | | | | 。 INT [8]
   + --- + --- + --- + --- + --- + --- + --- + --- + ...
     ^^
     | |
     | |
     | |
X + 0 | X + 8 |为int *

例如,如果要排序的数组,两个以下会工作同样出色:

 的std ::排序(X + 0,X + N);
性病::排序(安培; X [0],&放大器; X [0] + N);

请注意,这是非法提供&放大器; X [N] 作为,因为这第二个参数是相当于&放大器; *(X + N),和子恩pression *(X + N)技术上调用的用C未定义行为 ++(但不是在C99)。

另外请注意,你可以简单地提供 X 作为第一个参数。这是对我的口味有点过于简洁,而且也使模板参数推导有点难编译器,因为在这种情况下,第一个参数是一个数组,但第二个参数是一个指针。 (同样,数组到指针衰减踢)。

C++ inherited arrays from C where they are used virtually everywhere. C++ provides abstractions that are easier to use and less error-prone (std::vector<T> since C++98 and std::array<T, n> since C++11), so the need for arrays does not arise quite as often as it does in C. However, when you read legacy code or interact with a library written in C, you should have a firm grasp on how arrays work.

This FAQ is split into five parts:

  1. arrays on the type level and accessing elements
  2. array creation and initialization
  3. assignment and parameter passing
  4. multidimensional arrays and arrays of pointers
  5. common pitfalls when using arrays

If you feel something important is missing in this FAQ, write an answer and link it here as an additional part.

In the following text, "array" means "C array", not the class template std::array. Basic knowledge of the C declarator syntax is assumed. Note that the manual usage of new and delete as demonstrated below is extremely dangerous in the face of exceptions, but that is the topic of another FAQ.

(Note: This is meant to be an entry to Stack Overflow's C++ FAQ. If you want to critique the idea of providing an FAQ in this form, then the posting on meta that started all this would be the place to do that. Answers to that question are monitored in the C++ chatroom, where the FAQ idea started out in the first place, so your answer is very likely to get read by those who came up with the idea.)

解决方案

Arrays on the type level

An array type is denoted as T[n] where T is the element type and n is a positive size, the number of elements in the array. The array type is a product type of the element type and the size. If one or both of those ingredients differ, you get a distinct type:

#include <type_traits>

static_assert(!std::is_same<int[8], float[8]>::value, "distinct element type");
static_assert(!std::is_same<int[8],   int[9]>::value, "distinct size");

Note that the size is part of the type, that is, array types of different size are incompatible types that have absolutely nothing to do with each other. sizeof(T[n]) is equivalent to n * sizeof(T).

Array-to-pointer decay

The only "connection" between T[n] and T[m] is that both types can implicitly be converted to T*, and the result of this conversion is a pointer to the first element of the array. That is, anywhere a T* is required, you can provide a T[n], and the compiler will silently provide that pointer:

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

This conversion is known as "array-to-pointer decay", and it is a major source of confusion. The size of the array is lost in this process, since it is no longer part of the type (T*). Pro: Forgetting the size of an array on the type level allows a pointer to point to the first element of an array of any size. Con: Given a pointer to the first (or any other) element of an array, there is no way to detect how large that array is or where exactly the pointer points to relative to the bounds of the array. Pointers are extremely stupid.

Arrays are not pointers

The compiler will silently generate a pointer to the first element of an array whenever it is deemed useful, that is, whenever an operation would fail on an array but succeed on a pointer. This conversion from array to pointer is trivial, since the resulting pointer value is simply the address of the array. Note that the pointer is not stored as part of the array itself (or anywhere else in memory). An array is not a pointer.

static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");

One important context in which an array does not decay into a pointer to its first element is when the & operator is applied to it. In that case, the & operator yields a pointer to the entire array, not just a pointer to its first element. Although in that case the values (the addresses) are the same, a pointer to the first element of an array and a pointer to the entire array are completely distinct types:

static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");

The following ASCII art explains this distinction:

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | int[8]
|     | +---+---+---+---+---+---+---+---+ |
|     +---^-------------------------------+
|         |
|         |
|         |
|         |  pointer_to_the_first_element   int*
|
|  pointer_to_the_entire_array              int(*)[8]

Note how the pointer to the first element only points to a single integer (depicted as a small box), whereas the pointer to the entire array points to an array of 8 integers (depicted as a large box).

The same situation arises in classes and is maybe more obvious. A pointer to an object and a pointer to its first data member have the same value (the same address), yet they are completely distinct types.

If you are unfamiliar with the C declarator syntax, the parenthesis in the type int(*)[8] are essential:

  • int(*)[8] is a pointer to an array of 8 integers.
  • int*[8] is an array of 8 pointers, each element of type int*.

Accessing elements

C++ provides two syntactic variations to access individual elements of an array. Neither of them is superior to the other, and you should familiarize yourself with both.

Pointer arithmetic

Given a pointer p to the first element of an array, the expression p+i yields a pointer to the i-th element of the array. By dereferencing that pointer afterwards, one can access individual elements:

std::cout << *(x+3) << ", " << *(x+7) << std::endl;

If x denotes an array, then array-to-pointer decay will kick in, because adding an array and an integer is meaningless (there is no plus operation on arrays), but adding a pointer and an integer makes sense:

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(Note that the implicitly generated pointer has no name, so I wrote x+0 in order to identify it.)

If, on the other hand, x denotes a pointer to the first (or any other) element of an array, then array-to-pointer decay is not necessary, because the pointer on which i is going to be added already exists:

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

Note that in the depicted case, x is a pointer variable (discernible by the small box next to x), but it could just as well be the result of a function returning a pointer (or any other expression of type T*).

Indexing operator

Since the syntax *(x+i) is a bit clumsy, C++ provides the alternative syntax x[i]:

std::cout << x[3] << ", " << x[7] << std::endl;

Due to the fact that addition is commutative, the following code does exactly the same:

std::cout << 3[x] << ", " << 7[x] << std::endl;

The definition of the indexing operator leads to the following interesting equivalence:

&x[i]  ==  &*(x+i)  ==  x+i

However, &x[0] is generally not equivalent to x. The former is a pointer, the latter an array. Only when the context triggers array-to-pointer decay can x and &x[0] be used interchangeably. For example:

T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
T* q = array;      // decay happens due to the assignment

On the first line, the compiler detects an assignment from a pointer to a pointer, which trivially succeeds. On the second line, it detects an assignment from an array to a pointer. Since this is meaningless (but pointer to pointer assignment makes sense), array-to-pointer decay kicks in as usual.

Ranges

An array of type T[n] has n elements, indexed from 0 to n-1; there is no element n. And yet, to support half-open ranges (where the beginning is inclusive and the end is exclusive), C++ allows the computation of a pointer to the (non-existent) n-th element, but it is illegal to dereference that pointer:

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

For example, if you want to sort an array, both of the following would work equally well:

std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);

Note that it is illegal to provide &x[n] as the second argument since this is equivalent to &*(x+n), and the sub-expression *(x+n) technically invokes undefined behavior in C++ (but not in C99).

Also note that you could simply provide x as the first argument. That is a little too terse for my taste, and it also makes template argument deduction a bit harder for the compiler, because in that case the first argument is an array but the second argument is a pointer. (Again, array-to-pointer decay kicks in.)

这篇关于我如何使用C ++数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆