c++ - c++如何使用数组?

  显示原文与译文双语对照的内容

C++ 从C 继承的数组,它们几乎无处不在。 C++ 提供了更易于使用和 error-prone ( 自 C++11 之后,std::vector<T> 自C++98和 std::array<T, n> )的抽象,因此对数组的需求不会像在C 中那样频繁出现。 但是,当你读取遗留代码或者与用C 编写的库交互时,你应该掌握数组的工作方式。

这里FAQ分为五个部分:

  1. 类型级别上的数组和访问元素
  2. 数组创建和初始化
  3. 赋值和参数传递
  4. 多维数组和指针的数组
  5. 使用数组编辑器时的常见陷阱

如果你觉得在这里FAQ中缺少重要的内容,请写一个答案并将它的链接在这里作为额外的部分。

在以下文本中,"数组"表示"c 数组",而不是类模板 std::array 。 假定C 声明器语法的基本知识。 注意,newdelete的手工用法在面对异常时是极其危险的,但这是另一个常见问题解答的主题。

( 注意:这意味着是一个进入堆栈 C++的条目) 。 如果你希望所到过的理念,提供一个常见问题在这里表单,然后帐将元,开始所有这里将是这个地方可以这么做 它的理念。), 回答上面那个问题加以监测,以它的 C++ 聊天室,从它的开始常见问题解答思路所读取的完成,所以你的答案是极有可能在第一个位置来获取那些谁发明

时间:

类型级别上的数组

它用 T[n]T 哪是来了 array,一个数组类型中的元素的数量类型和 n 是一个正大小,原色 数组类型是元素类型和大小的乘积类型。 如果其中一个或者两个成分不同,你将得到不同的类型:


#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");

注意,大小是类型的一部分,即不同大小的数组类型是互不兼容的类型。 sizeof(T[n]) 等同于 n * sizeof(T)

Array-to-pointer衰减

这两种类型的唯一的"连接" T[n]T[m] 之间是可以隐式地转换到 T*,且这里转换的结果是一个指向该数组的第一个元素的指针。 也就是说,只要需要 T*,你就可以提供一个 T[n],编译器将静默提供该指针:


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

这种转换称为"array-to-pointer衰减",它是一个主要的混淆源。 数组的大小在这里进程中丢失,因为它不再是类型( T* )的一部分。 Pro: 遗忘类型级别上的数组的大小允许指针指向的第一个元素数组的第一个元素。 Con: 给出一个指向数组第一个( 或者其他任何) 元素的指针,没有办法检测数组是多么的大,或者指针指向数组边界的位置。 指针是极其愚蠢的

数组不是指针

编译器将在被认为有用的时候生成指向数组第一个元素的指针,也就是说,每当一个操作在数组上失败但在指针上成功时。 从数组到指针的转换很简单,因为产生的指针值就是数组的地址。 请注意,该指针是不存储为该数组本身( 或者内存中其他地方)的一部分。 对数组求和不是指针。


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

一个数组的重要环境确实不是衰变为一个指向它的第一个元素是当将 & 操作符应用于它。 在这种情况下,& 运算符会生成一个指向整个数组的指针,而不仅仅是指向它的第一个元素的指针。 虽然在这种情况下,值 ( 地址) 是相同的,指向数组第一个元素的指针和指向整个数组的指针是完全不同的类型:


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

以下ASCII艺术解释了这种区别:


 +-----------------------------------+
 | +---+---+---+---+---+---+---+---+ |
+---> | | | | | | | | | | | 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 到数组的第一个元素,表达式 p+i 会生成指向数组的i-th元素的指针。 通过取消引用该指针,可以访问单个元素:


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

如果 x 表示一个数组,那么array-to-pointer衰变就会启动,因为添加一个数组和一个整数是毫无意义的( 数组上没有加号运算),但是添加指针和整数有意义:


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

( 注意,隐式生成的指针没有名称,所以我编写了 x+0 以识别它。)

如果,另一方面,x 表示一个指针与数组的第一个( 或者其他任何) 元素,那么 array-to-pointer衰减 i 是将要被添加已经存在其上,那是没有必要的,因为指针


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

请注意,在所描述的情况下,x 是一个指针变量 ( x 旁边的小方框可以辨别),但它可以被看做是的结果,函数返回一个指针( 或者类型 T*的任何其他表达式) 。

索引运算符

由于语法 *(x+i) 有点笨拙,C++ 提供了替代语法 x[i]:


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

由于加法是交换的,下面的代码完全一样:


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

索引运算符的定义导致以下有趣的等效:


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

然而,&x[0] 是一般不等价于 x 。 前者是指针,后者是数组。 只有当上下文触发array-to-pointer衰变才能 x&x[0] 交替使用。 例如:


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

在第一行,编译器检测到指向指针的指针的赋值,该指针非常成功。 在第二行中,它检测到一个从 。 数组到指针的赋值。 因为这是无意义的( 但是,指针的指针分配有意义),array-to-pointer衰变就像往常一样。

范围

类型 T[n]的数组有 n 元素,从 0n-1 索引;没有元素 n 。 然而,为了支持half-open范围( 在那里一开始就是 上人性化,结尾总是独占 ),C++ 允许计算指向( 不存在) n-th元素的指针,但不允许引用该指针:


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

例如如果你想对一个数组进行排序,那么以下两个都可以正常工作:


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

请注意,提供 &x[n] 作为第二个参数是非法的,因为这等效于 &*(x+n),并且 sub-expression *(x+n) 在 C++ ( 但不在C99中) 中在技术上调用未定义的行为

还请注意,你可以简单地将 x 作为第一个参数。 这对于我的品味来说有点过于简单,而且它也使得编译器的参数推导更加困难,因为在这种情况下第一个参数是数组,第二个参数是一个指针。 ( 同样,array-to-pointer的衰变也随之消失。)

程序员经常混淆多维数组和指针数组。

多维数组

大多数程序员都熟悉命名的多维数组,但很多人不知道多维数组也可以匿名创建。 多维数组通常称为"数组数组"或者"真多维数组"。

命名多维数组

当使用命名多维数组,必须将所有 尺寸在编译 time, known:


int H = read_int();
int W = read_int();

int connect_four[6][7];//okay

int connect_four[H][7];//ISO C++ forbids variable length array
int connect_four[6][W];//ISO C++ forbids variable length array
int connect_four[H][W];//ISO C++ forbids variable length array

这就是命名多维数组在内存中的样子:


 +---+---+---+---+---+---+---+
connect_four: | | | | | | | |
 +---+---+---+---+---+---+---+
 | | | | | | | |
 +---+---+---+---+---+---+---+
 | | | | | | | |
 +---+---+---+---+---+---+---+
 | | | | | | | |
 +---+---+---+---+---+---+---+
 | | | | | | | |
 +---+---+---+---+---+---+---+
 | | | | | | | |
 +---+---+---+---+---+---+---+

请注意,上面的2个网格,如上面的网格,只是有用的可视化效果。 从 C++的角度看,内存是一个"平面"序列。 多维数组的元素以row-major顺序存储。 也就是说,connect_four[0][6]connect_four[1][0] 是内存中的邻居。 实际上,connect_four[0][7]connect_four[1][0] 表示相同的元素 ! 这意味着你可以使用multi-dimensional数组并将它们视为大的one-dimensional数组:


int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

匿名多维数组

匿名多维数组,所有维度除了第一必须在编译 time, known:


int (*p)[7] = new int[6][7];//okay
int (*p)[7] = new int[H][7];//okay

int (*p)[W] = new int[6][W];//ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];//ISO C++ forbids variable length array

这是一个匿名多维数组在内存中的样子:


 +---+---+---+---+---+---+---+
 +---> | | | | | | | |
 | +---+---+---+---+---+---+---+
 | | | | | | | | |
 | +---+---+---+---+---+---+---+
 | | | | | | | | |
 | +---+---+---+---+---+---+---+
 | | | | | | | | |
 | +---+---+---+---+---+---+---+
 | | | | | | | | |
 | +---+---+---+---+---+---+---+
 | | | | | | | | |
 | +---+---+---+---+---+---+---+
 |
 +-|-+
 p: | | |
 +---+

请注意,数组本身仍被分配为内存中的单个块。

指针数组

通过引入另一个间接级别,你可以克服固定宽度的限制。

指针的命名数组

下面是五个指针的命名数组,它们用不同长度的匿名数组初始化:


int* triangle[5];
for (int i = 0; i <5; ++i)
{
 triangle[i] = new int[5 - i];
}

//...

for (int i = 0; i <5; ++i)
{
 delete[] triangle[i];
}

这是内存中的样子:


 +---+---+---+---+---+
 | | | | | |
 +---+---+---+---+---+
 ^
 | +---+---+---+---+
 | | | | | |
 | +---+---+---+---+
 | ^
 | | +---+---+---+
 | | | | | |
 | | +---+---+---+
 | | ^
 | | | +---+---+
 | | | | | |
 | | | +---+---+
 | | | ^
 | | | | +---+
 | | | | | |
 | | | | +---+
 | | | | ^
 | | | | |
 | | | | |
 +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
 +---+---+---+---+---+

由于每一行现在单独分配,因此将 2个数组视为 1个数组不再工作。

匿名指针数组

下面是一个匿名数组 5 ( 或者其他任意数量的) 指针,它们用不同长度的匿名数组初始化:


int n = calculate_five();//or any other number
int** p = new int*[n];
for (int i = 0; i <n; ++i)
{
 p[i] = new int[n - i];
}

//...

for (int i = 0; i <n; ++i)
{
 delete[] p[i];
}
delete[] p;//note the extra delete[]!

这是内存中的样子:


 +---+---+---+---+---+
 | | | | | |
 +---+---+---+---+---+
 ^
 | +---+---+---+---+
 | | | | | |
 | +---+---+---+---+
 | ^
 | | +---+---+---+
 | | | | | |
 | | +---+---+---+
 | | ^
 | | | +---+---+
 | | | | | |
 | | | +---+---+
 | | | ^
 | | | | +---+
 | | | | | |
 | | | | +---+
 | | | | ^
 | | | | |
 | | | | |
 +-|-+-|-+-|-+-|-+-|-+
 | | | | | | | | | | |
 +---+---+---+---+---+
 ^
 |
 |
 +-|-+
 p: | | |
 +---+

转换

Array-to-pointer衰变自然扩展到数组数组和指针数组:


int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

但是,没有从 T[h][w]T**的隐式转换。 在内存中yet,如果这样一个隐式转换 h 确实存在,它的结果便是一个指向数组的第一个元素的指针来 T ( 每个指向原始 2维数组中一行的第一个元素),但该指针数组不存在准接下来 如果需要这样的转换,必须手动创建并填充所需的指针数组:


int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i <6; ++i)
{
 p[i] = connect_four[i];
}

//...

delete[] p;

注意,这将生成原始多维数组的视图。 如果你需要一个副本,你必须创建额外的数组并自己复制数据:


int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i <6; ++i)
{
 p[i] = new int[7];
 std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

//...

for (int i = 0; i <6; ++i)
{
 delete[] p[i];
}
delete[] p;

赋值

由于没有特定的原因,数组不能被分配给另一个。 使用 std::copy 代替:


#include <algorithm>

//...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

这比真正的数组赋值更灵活,因为它可以将较大数组的切片复制到更小的数组中。 std::copy 通常专门用于基元类型以获得最佳性能。 std::memcpy 不可能执行得更好。 如果有疑问,测量。

尽管你不能直接指定数组,你可以分配struct和类哪些 包含数组成员。 那是因为复制数组成员 memberwise 由赋值运算符作为默认由编译器确定是否需要。 如果为自己的结构或类手动定义赋值运算符,则必须回退到数组成员手动复制。

参数传递

数组不能通过值传递。 你可以通过指针传递它们,也可以通过引用传递。

通过指针传递

数组本身不能通过值传递,因此通常通过值传递指向它们的第一个元素的指针。 这通常被称为"通过指针传递"。 数组的最后一个元素的后面由于数组的大小是不能通过该指针可以检索的,你必须通过第二个参数,指示数组的大小 pointing: ( 经典的C 解决方案) 或者第二,指针


#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
 return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
 return std::accumulate(p, q, 0);
}

仅列出作为是一种语法上的替代,还可以声明参数为 T p[], 上下文中的参数,并且它意味着完全一样的东西作为 T* p:


int sum(const int p[], std::size_t n)
{
 return std::accumulate(p, p + n, 0);
}

上下文中的参数只列出你能想到的属于编译器编译它的重写 T p[], T *p. 这个特殊规则部分负责对数组和指针的混淆。 在其他所有上下文中,声明一些作为数组或者一个指针就一个巨大一样了。

很不幸,你也可以在数组参数中提供一个大小,它被编译器忽略。 也就是说,以下三个签名完全等价,如编译器错误所示:


int sum(const int* p, std::size_t n)

//error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

//error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)//the 8 has no meaning here

通过引用传递

数组也可以通过引用传递:


int sum(const int (&a)[8])
{
 return std::accumulate(a + 0, a + 8, 0);
}

在这种情况下,数组大小非常重要。 编写一个函数,该函数仅接受完全 8的数组元素没有多少用处,因为程序员常常编写此类函数为模板:


template <std::size_t n>
int sum(const int (&a)[n])
{
 return std::accumulate(a + 0, a + n, 0);
}

注意,只能用实际的整数数组调用这样的函数模板,而不能用指向整数的指针来调用。 数组的大小会自动推断出,对于每个大小 n,从模板中实例化一个不同的函数。 你也可以编写相当有用的函数模板,它从元素类型和大小。

数组创建和初始化

任何其他类型的C++ 对象一样,数组可以直接存储在命名变量( 那么大小必须是一个compile-time常量;C++ 不支持 vla ) 中,也可以匿名存储在堆上,通过指针( 只有这样才能在运行时计算大小) 间接访问。

自动数组

自动数组在堆栈上( 数组居住"") 是每次的控制流创建穿过一个non-static局部数组变量:的定义


void foo()
{
 int automatic_array[8];
}

初始化以升序执行。 请注意,初始值取决于元素类型 T:

  • 如果 TPOD ( 像上面示例中的int ),则不进行任何初始化。
  • 否则,T default-constructor,初始化所有的元素。
  • 如果 T 没有提供可以访问的default-constructor,程序就不会编译。

或者,初始值可以在 。 数组初始值设定项列表中显式指定,该列表由大括号包围:


 int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

因为在本例中数组初始值设定项中元素的数目等于数组的大小,所以手动指定大小是多余的。 它可以由编译器自动推断:


 int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};//size 8 is deduced

还可以指定大小并提供较短的数组初始值设定项:


 int fibonacci[50] = {0, 1, 1};//47 trailing zeros are deduced

在这种情况下,剩余的元素是 zero-initialized 。 请注意,C++ 允许一个空数组初始值设定项( 所有元素都是 zero-initialized ),而C89不( 至少需要一个值) 。 assignments,中还要注意,数组初始值设定项只能用于初始化阵列;它们不能在以后过去通用

静态数组

静态数组( 数组居住"在数据段") 中是用 static 关键字和命名空间作用域("全局变量") 定义的数组变量定义的本地数组变量:


int global_static_array[8];

void foo()
{
 static int local_static_array[8];
}

( 注意命名空间作用域中的变量是隐式静态的) 。 将 static 关键字添加到它的定义中有一个完全不同的,不推荐使用 deprecated 。

下面是静态数组与自动数组的行为方式:

  • 没有数组初始值设定项的静态数组在进一步初始化之前是 zero-initialized 。
  • 在静态初始化POD数组是 runtime,到底一旦,和通常的初始值是 cost.被应用到了可以执行,在这种情况下没有初始化 但这并不是最常用的space-efficient解决方案,它不是标准所要求的。
  • 静态non-POD数组在控制流通过它的定义时首次初始化 。 对于局部静态数组,如果函数从未被调用,则可能永远不会发生。

( 上任何一个都不是数组特有的。 这些规则同样适用于其他类型的静态对象。

数组数据成员

创建所属对象的所属对象时创建数组数据成员。 不幸的是,C++03不能在成员初始值设定项列表中初始化数组,因此初始化必须用赋值来伪装:


class Foo
{
 int primes[8];

public:

 Foo()
 {
 primes[0] = 2;
 primes[1] = 3;
 primes[2] = 5;
//...
 }
};

或者,你可以在构造函数体中定义一个自动数组,并将元素复制到:


class Foo
{
 int primes[8];

public:

 Foo()
 {
 int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
 std::copy(local_array + 0, local_array + 8, primes + 0);
 }
};

在 C++0x,数组 被初始化可以在成员初始值设定项列表中初始化: 感谢均匀


class Foo
{
 int primes[8];

public:

 Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
 {
 }
};

这是唯一能处理没有默认构造函数的元素类型的解决方案。

动态数组

动态数组没有名称,因此访问它们的唯一方法是通过指针。 因为它们没有名字,我将从现在开始将它们称为"匿名数组"。

在C 中,匿名数组是通过 malloc 和朋友创建的。 在 C++ 中,匿名数组是使用 new T[size] 语法创建的,该语法返回一个指向匿名数组的第一个元素的指针:


std::size_t size = compute_size_at_runtime();
int* p = new int[size];

以下ASCII艺术描述了在运行时将大小计算为 8时的内存布局:


 +---+---+---+---+---+---+---+---+
(anonymous) | | | | | | | | |
 +---+---+---+---+---+---+---+---+
 ^
 |
 |
 +-|-+
 p: | | | int*
 +---+

显然,匿名数组需要比命名数组更多的内存,因为必须单独存储额外的指针。 ( 免费存储也有一些额外的开销。)

here,上请注意,上是没有 array-to-pointer衰减 going. 虽然评价 new int[size] 并实际上创建一个数组的整数,表达式的结果 new int[size] 已经是一个指向单个整数( 第一个元素),不一个整数或者一个指针,该指针指向整型数组,数组大小未知的。 这是不可能的,因为静态类型系统需要数组大小为compile-time常量。 ( 因此,我没有用图片中的静态类型信息对匿名数组进行批注。)

对于元素的默认值,匿名数组的行为类似于自动数组。 通常,匿名POD数组未初始化,但有一个特殊语法触发器触发 value-initialization:


int* p = new int[some_computed_size]();

( 注意分号前面的尾随圆括号。) 再次,C++0x简化规则并允许匿名数组指定初始值,这归功于统一的初始化:


int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

如果使用匿名数组,则必须将它的释放回系统:

 
delete[] p;

 

你必须一次性释放每个匿名数组,然后再以后再不碰它。 不释放它会导致内存泄漏( 或者更一般地,根据元素类型,资源泄漏),并且尝试多次释放它会导致未定义的行为。 使用non-array窗体 delete ( 或者 free ) 而不是 delete[] 来释放数组也是未定义的行为。

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

5.1 陷阱:信任type-unsafe链接。

考虑下面的程序,由两个文件 [main.cpp] 和 [numbers.cpp] 组成:


//[main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
 using namespace std;
 for( int i = 0; i <42; ++i )
 {
 cout <<(i> 0?"," :"") <<numbers[i];
 }
 cout <<endl;
}


//[numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

在 Windows 7中,这个编译和链接同时使用 MinGW G++ 4.4.1和 Visual C++ 10.0.

由于类型不匹配,当你运行它时程序会崩溃。

The Windows 7 crash dialog

In-the-formal说明:程序( 联合) 有未定义的行为,并因此而不是崩溃它可以就再等一下,或者也许什么都不做,或者它可以发送threating电子信函到总统对美国,俄罗斯,印度,中国和瑞士,并使你的鼻子鼻守护程序飞出来了。

In-practice解释:在 main.cpp 中数组被视为指针,放置在与数组相同的地址。 对于 32位 可执行文件,这意味着数组中的第一个 int 值被视为指针。 换句话说,在 main.cppnumbers 变量包含的,或者出现无法抵挡,(int*)1 。 这将导致程序来访问内存下的磁选设备保留在最底部显示的地址空间和 trap-causing 。 结果:你得到了一个崩溃。

于自己的权利为不中板的所有declarations,编译器的相关诊断这里错误,因为 C++11 §3 。有关的要求,兼容 types, 5/10说,

[N3290 §3.5/10]
类型标识上的违反这里规则不需要诊断。

同一段落详细描述了允许的变化:

- - 声明为数组对象可以指定数组类型的一个主要的照明因存在或者不存在的数组绑定( 8.3.4 ) 。

这个允许变化不包括在一个翻译单元中声明一个名称作为数组,也不包括作为另一个翻译单元中的指针。

5.2 陷阱:进行过早优化( memset &好友) 。

还没有写好

5.3 陷阱:使用C 语言来获取元素的数量。

有了深厚的C 经验,写起来很自然。。


#define COUNT_OF( array ) (sizeof( array )/sizeof( array[0] ))

由于 array 衰变到需要第一个元素的指针,所以表达式 sizeof(a)/sizeof(a[0]) 也可以写成 sizeof(a)/sizeof(*a) 。 array,于查找的数字内容中相关的意思是相同的,而不管它是如何写的,它是 C 成语.

主要陷阱:C 语言的习惯用法是不安全的。 例如代码。。


#include <stdio.h>

#define COUNT_OF( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
 int const n = COUNT_OF( a );//Oops.
 printf("%d elements.n", n );
}

int main()
{
 int const moohaha[] = {1, 2, 3, 4, 5, 6, 7};

 printf("%d elements, calling display...n", COUNT_OF( moohaha ) );
 display( moohaha );
}

传递指向 COUNT_OF的指针,因此很可能产生错误的结果。 在 Windows 它产生 7.., 编译成一个 32位 executable.

7 元素,正在调用显示。。
1 元素。

  1. 编译器将 int const a[7] 重写为 int const a[]
  2. 编译器将 int const a[] 重写为 int const* a
  3. 因此,使用指针调用 COUNT_OF
  4. 对于一个 32位 可执行文件 sizeof(array) ( 指针的大小) 然后被 4.
  5. sizeof(*array) 等效于 sizeof(int),对于 32位 可执行文件,它也是 4.

为了在运行时检测到这里错误,你可以。。


#include <assert.h>
#include <typeinfo>

#define COUNT_OF( array ) ( 
 assert(( 
"COUNT_OF requires an actual array as argument", 
 typeid( array )!= typeid( &*array ) 
 )), 
 sizeof( array )/sizeof( *array ) 
 )

7 元素,正在调用显示。。
断言失败:("count_of需要一个实际数组作为参数",typeid( a )!= typeid( &*a ) ),文件 runtime_detect ion.cpp, 行 16

此应用程序要求运行时终止它在一个不正常的路径。
请联络此应用程序的支援团队,以取得更多资讯。

运行时错误检测比没有检测好,但它浪费了一些处理器时间,可能还有更多的程序员时间。 在编译时检测更好 ! 如果你很高兴不支持C++98的本地类型数组,那么你可以这样做:


#include <stddef.h>

typedef ptrdiff_t Size;

template <class Type, Size n> 
Size countOf( Type (&)[n] ) { return n; }

#define COUNT_OF( array ) countOf( array )

编译这个定义代替了第一个完整的程序,用 G++,我得到了。。

m: count> G++ compile_time_detection.cpp
compile_time_detection.cpp: 在函数'void display(const int*)'中:
于对'countOf(const int*&) 调用相关'', compile_time_detection 。cpp: 14: 错误:无匹配的函数

m: count> _

它是如何工作的对 countOf 甲方所数组是传递引用,那么它不会衰减,以指向第一个元素的指针,该函数就能返回所指定的元素数的类型。

还通过C++11你可以使用这里为局部类型的数组,并且它是类型安全的C++ 习语来发现数组的元素的个数。

有一个是多余的细化所需使用rename一个编译时常量,而自认为是所以很少使用( 如果根本),用以指示将浪费空间。

...