【C++ STL应用与实现】5: 如何使用std::array (since C++11)

本系列文章的目录在这里:目录. 通过目录里可以对STL总体有个大概了解

前言

本文总结了STL中的序列式容器array的用法及注意事项。array的出现代表着C++的代码更进一步“现代化”,就像std::string的出现代替了c风格字符串并且能和STL配合工作一样,array的出现则将取代语言内置的数组以及c风格的数组字符串,它提供了data()接口,使得能够获得内部数组的首地址,它提供了size(), 能够得其固定的长度,使得C++的数组也可以像Java等语言那样知道自己的length;它提供了begin(), end()等接口使得“数组”也可以和STL血脉相容;它还提供了tuple接口,可以当做tuple来使用;更重要的一点是,array有并不比原生数组差的性能表现。

array的概念

array是STL中的一个序列式容器,它包装了一个c风格的数组,但在外部接口来看,提供了STL容器的常用接口。它的长度是固定的,正如普通的c风格数组那样,一旦创建完成,长度即确定,不能扩大也不能缩小。

它的原型就像这样, 是一个模板类:

namespace std
{
    template <typename T, size_t N>
    class array;
}

第一个模板参数T指明了array中存放的数据类型;

第二个非类型模板参数指明了array的固定大小。

array的接口

constructors

构造函数说明
arrary<T, N> c默认构造函数,N个元素全部使用“默认初始化行为”来构造。
arrary<T, N> c(other)拷贝构造函数,拷贝所有other的元素到c来构造。
arrary<T, N> c = other拷贝构造函数,拷贝所有other的元素到c来构造。
arrary<T, N> c(rValue)移动构造,使用右值rValue里的元素来初始化c。
arrary<T, N> c = rValue移动构造,使用右值rValue里的元素来初始化c。
arrary<T, N> c = initlist使用初始化列表初始化元素

注意: 由于默认构造函数是对每一个元素使用“默认构造”行为来初始化,这意味着对于基本类型的数据其初始值是未定义的。

array 被要求是一个“aggregate”: 没有用户自定义的构造函数、没有非静态的private和protected类型的成员、没有基类、没有虚函数.

因此不支持这样的构造方法:array<int, 3> a({1, 2, 4});

初始化array最常用的方法是使用赋值运算符和初始化列表:

array<int, 3> a = {1, 2, 3};

array<int, 100> b = {1, 2, 3};  // a[0] ~ a[2] = 1, 2, 3; a[3] ~ a[99] = 0, 0, 0 ... 0;

array<int, 3> c;                // c[0] ~ c[2] 未初始化,是垃圾值.

assignment

形式说明
c = other把other的全部元素复制一份给c。
c = rValuerValue全部元素被移动到c
c.fill(val)用val给每个元素赋值
c.swap(c2)交换c和c2的所有元素
swap(c, c2)交换c和c2的所有元素

注意 :array的swap操作通常代价比较高:是一个O(n)的操作。

begin(), end()等迭代器位置及属性获取操作

  • begin() (cbegin())

  • end() (cend())

  • rbegin() (crbegin())

  • rend() (crend())

  • empty()

  • size()

  • max_size()

  • [index]

  • at(index)

- front()

  • back();

tuple接口

array<string, 3> a = {"hello", "hwo", "are"};
tuple_size<a>::value;
tuple_element<1, a>::type;  // string
get<1>(a);                  

把array当做c风格的数组来用

//----------------------- array as c-style array ----------------------
RUN_GTEST(ArrayTest, CStyleArray, @);

// use array<char> as a fix sized c-string.
array<char, 100> str = {0};           // all elements initialized with 0.

char *p = str.data();

strcpy(p, "hello world");
printf("%s\n", p);              // hello world


END_TEST;

上面这个例子让我想起了std::string, 它有一个c_str()方法,同样是返回内部的c风格字符串,同样string也是STL的容器。

这让我想到,array的出现也是像string那样,是为了取代旧的c-风格字符串和内置数组,并且添加了标准容器的一些接口使得数组可以和STL其他组件和谐工作。

综合示例

//----------------------- normal example ----------------------
RUN_GTEST(ArrayTest, NormalExample, @);

array<int, 5> a = { 1, 2, 3 };
psln(a.size());                     // a.size() = 5;
psln(a.max_size());                 // a.max_size() = 5;
EXPECT_FALSE(a.empty());            // empty() is false.

printContainer(a, "array: ");       // array: 1 2 3 0 0

a[a.size() - 1] = 5;                // change last one
printContainer(a, "array: ");       // array: 1 2 3 0 5

a.at(a.size() - 2) = 4;
printContainer(a, "array: ");       // array: 1 2 3 4 5

int sum;
sum = accumulate(a.begin(), a.end(), 0);
psln(sum);                          // sum = 15

try
{
    int i = a.at(5);                // throw.
}
catch ( ... )
{
    pln("exception catched");
}

try
{
    //int i = a[5];                   // won't throw exception.
}
catch ( ... )
{
    pln("exception catched");       
}

// ------------------ copy ------------------
array<int, 5> a2 = a;                   // copy constructor.
printContainer(a2, "a2: ");             // a2: 1 2 3 4 5

array<int, 5> a3(a2);                   //copy ctor.
printContainer(a3, "a3: ");             // a3: 1 2 3 4 5


// ------------------ assign ------------------
array<int, 5> a4;
a4 = a3;                                // assignment operator.
printContainer(a4, "a4: ");             // a4: 1 2 3 4 5


array<int, 4> b = {};
//b = a;                                // error, b is not array<int, 5>!!


// ------------------ fill ------------------
array<int, 5> a5;
a5.fill(5);
printContainer(a5, "a5: ");             // a5: 5 5 5 5 5

// ------------------ move ------------------

// ------------------ array with class objects. ------------------


END_TEST;

二维和多维array

//----------------------- multiple div array example ----------------------
RUN_GTEST(ArrayTest, MatrixOrMultipleDiv, @);

// like plain 2D array
array<array<int, 5>, 5> mat1 = {
    1,2,3,4,5,
    1,2,3,4,5,
    1,2,3,4,5,
    1,2,3,4,5,
    1,2,3,4,5,
};

// construct with 1D arys.
array<int, 5> ary = {1};
array<array<int, 5>, 5> mat2 = { ary, ary, ary, ary, ary};

// just like plain 2D array, but can ommit some value some each div.
array<array<int, 5>, 5> mat3 = {
    array<int, 5>{ 1, 2, 3, 4, 5},
    array<int, 5>{ 1, 2, 3, 4},
    array<int, 5>{ 1, 2, 3},
    array<int, 5>{ 1, 2,},
    array<int, 5>{ 1, }
};

// util function to print matrix.
auto printMatrix = [] (const array<array<int, 5>, 5>& mat) {
    for (const auto& ary : mat) {
        for (const auto& item : ary) {
            cout << item << " ";
        }
        cout << endl;
    }
};

pcln("ma1");
printMatrix(mat1);

pcln("mat2");
printMatrix(mat2);

pcln("mat3");
printMatrix(mat3);

END_TEST;

output:


************************ma1*********************
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
************************mat2*********************
1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
************************mat3*********************
1 2 3 4 5
1 2 3 4 0
1 2 3 0 0
1 2 0 0 0
1 0 0 0 0

注意事项

  • swap的代价:O(n)

  • 基本类型的默认构造是垃圾值,用初始化列表来避免

array<int, 3> a;            // no.
array<int, 3> a = {};       // good.

源码


作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!

欢迎访问github博客,与本站同步更新

<think> 首先,用户的问题是:在C++程序中,使用`std::vector<int> vtrRowBuf(30, 0)`定义成员对象时,是可以在头文件中定义还是只能在源文件中定义。 关键点是:这是一个成员对象定义,所以它应该是类的一个成员变量。 在C++中,成员变量通常在类定义中声明。类定义通常放在头文件中。对于非静态成员变量,声明就是指定类型和名称,但初始化通常在构造函数中进行。 用户给出的代码是:`std::vector<int> vtrRowBuf(30, 0)`。这看起来像一个定义,包括初始化。在C++中,成员变量不能在类定义中直接初始化,除非是静态成员变量或在C++11及以后使用in-class初始化。 回顾C++标准: - 在C++98/03中,成员变量不能在类定义中直接初始化;初始化必须在构造函数中。 - 在C++11及以后,支持in-class initialization,允许在声明成员变量时直接初始化。 用户提到的代码:`std::vector<int> vtrRowBuf(30, 0)`。这似乎是定义一个vector对象,大小为30,初始化为0。 在类定义中,作为非静态成员变量: - 如果这是声明,它应该只有类型和名称,如`std::vector<int> vtrRowBuf;`,然后在构造函数中初始化。 - 但在C++11及以后,可以这样写:`std::vector<int> vtrRowBuf = std::vector<int>(30, 0);`或者使用花括号初始化:`std::vector<int> vtrRowBuf{30, 0};`但要注意,`{30, 0}`可能会被解释为初始化列表,创建一个有两个元素的vector,而不是大小为30全为0的vector。 正确的方式是:`std::vector<int> vtrRowBuf{std::vector<int>(30, 0)};`但这可能不简洁。更简单的做法是:在类定义中声明,然后在构造函数中初始化。 或者使用C++11的in-class initialization: 例如: ```cpp class MyClass { public: std::vector<int> vtrRowBuf = std::vector<int>(30, 0); // 使用赋值初始化 // 或者使用花括号初始化:std::vector<int> vtrRowBuf{30, 0}; 但注意这个创建大小为2的vector,元素为30和0 // 正确的方法是:std::vector<int> vtrRowBuf = std::vector<int>(30, 0); }; ``` 但`{30, 0}` 对于vector来说,会调用initializer_list构造函数,创建一个有两个元素的vector:30和0,这不是用户想要的。 用户想要大小为30,全为0的vector。所以正确的方式是: - 在头文件中声明成员变量,并在构造函数中初始化。 - 或者使用in-class initialization with direct initialization, but since it's a constructor call, it needs to be done carefully. In C++11, you can do: ```cpp class MyClass { public: std::vector<int> vtrRowBuf{30, 0}; // WRONG, creates vector with two elements // Correct way: std::vector<int> vtrRowBuf = std::vector<int>(30, 0); // OK with copy initialization // Or using direct list initialization if supported, but vector doesn't have an initializer_list for size and value }; ``` The best practice is to initialize in the constructor. Now, regarding where to define: the declaration should be in the header file if it's part of a class that is used in multiple files. The initialization should be in the source file for non-static members to avoid multiple definitions if included in multiple translation units. But for non-static members, the initialization happens per object, so it's fine in the header if done correctly without violating ODR. ODR (One Definition Rule): For class definitions, they can be defined in multiple translation units as long as identical. Member functions and static members have more rules. For non-static data members, they are declared in the class definition. Initialization can be in-class or in constructor. If we do in-class initialization, it must be in the header if the class is defined in the header. For example: Header file: myclass.h ```cpp #ifndef MYCLASS_H #define MYCLASS_H #include <vector> class MyClass { public: // With C++11 in-class initialization std::vector<int> vtrRowBuf = std::vector<int>(30, 0); // This is a definition with initialization // But note: this might cause issues if the header is included in multiple files? No, because each object gets its own copy }; #endif ``` 源文件:myclass.cpp ```cpp #include "myclass.h" // 构造函数或其他实现 ``` 当多个源文件包含这个头文件时,每个源文件都会看到类定义,但当它们被编译时,成员变量的初始化在每个翻译单元中是独立的;但由于它是每个对象的一部分,所以没问题。类定义本身是共享的,但每个对象实例有自己的成员变量副本。 然而,对于像 `std::vector<int> vtrRowBuf = std::vector<int>(30, 0);` 这样的 in-class 初始化,它是在每个构造函数中隐式初始化的,所以当创建对象时,初始化发生,并且每个对象都有自己的 vector。 但这里有个潜在问题:如果头文件被包含在多个翻译单元中,而类定义是相同的,那就没问题。但是,如果初始化具有副作用,可能会引起关注,但对于 vector 来说,通常是可以的。 对于静态成员变量,情况就不同了。但用户说的是 "成员对象",我认为是非静态的。 用户说:"定义成员对象",在上下文中,很可能是非静态成员。 在引用中,没有直接相关的内容,但可以参考。 引用[1]:讨论 std::vector 示例,但未涉及定义位置。 引用[2]:讨论 std::array。 引用[3]:讨论 std::unique_ptr 和删除器。 引用[4]:展示在 main 函数中使用的 vector<vector<double>>,但那是在源文件中。 但这些没有直接解决头文件源文件的问题。 最佳实践: - 通常,类定义在头文件中,成员变量的声明(带有或不带有 in-class 初始化)在头文件中。 - 如果没有 in-class 初始化,初始化在源文件的构造函数中进行。 - 使用 in-class 初始化可以在头文件中完成,但需要 C++11 或更高版本。 - 在头文件中进行初始化可能会导致代码膨胀或编译时间变长,但通常对于简单类型是可以接受的。 具体回答用户问题: 用户问:能否在头文件中定义,还是必须在源文件中定义? "定义" 可能有些模糊。在 C++ 中,对于类成员,声明在类定义中,也就是在头文件中。 初始化可以在类定义中(in-class)或在构造函数中。 所以,对于成员变量,声明在头文件中,初始化可以在头文件(如果使用 in-class 初始化)或源文件中(在构造函数中)。 但用户特别给出了代码:`std::vector<int> vtrRowBuf(30, 0)`,这看起来像是一个定义,包括初始化。 在类定义中,你不能写 `std::vector<int> vtrRowBuf(30, 0);` —— 那是一个语法错误,因为它看起来像是一个函数声明。 在类定义中,成员变量的声明不能有参数列表;它必须是一个简单的声明,可能带有初始化器。 例如: 错误: ```cpp class MyClass { std::vector<int> vtrRowBuf(30, 0); // 错误:函数声明,不是变量 }; ``` 正确的方式: ```cpp class MyClass { std::vector<int> vtrRowBuf = std::vector<int>(30, 0); // C++11 起支持 // 或者 std::vector<int> vtrRowBuf{std::vector<int>(30, 0)}; // 笨拙 // 或者更好的方式:在构造函数中初始化 MyClass() : vtrRowBuf(30, 0) {} // 然后声明为 std::vector<int> vtrRowBuf; }; ``` 所以,在头文件中,你不能直接写 `vtrRowBuf(30, 0)`;这在类定义中是无效的。 你可以在头文件中声明它,然后在源文件中初始化,或者使用 C++11 风格的 in-class 初始化。 回答用户: - 在头文件中,你可以声明成员变量,但带参数的初始化不能在类定义中直接进行。 - 要在头文件中“定义”并初始化,需要使用 C++11 或更高版本,并像这样写:`std::vector<int> vtrRowBuf = std::vector<int>(30, 0);` 或使用统一初始化语法(但要小心,避免 initializer_list 问题)。 - 更好的做法是在头文件中声明,在源文件的构造函数中初始化。 由于用户可能没有使用 C++11,需要说明。 但根据引用,例如引用[2]提到 C++17,所以可能允许现代 C++。 在系统指令中,要求使用中文回答。 结构化回答: 1. 解释在类定义中成员变量的规则。 2. 说明在头文件中声明成员变量。 3. 讨论初始化:在构造函数中(在源文件中)或使用 in-class 初始化(在头文件中,适用于 C++11 及以上)。 4. 对于用户的具体代码,给出正确方式。 最后,生成相关问题。 </think> 根据您的问题,在C++使用`std::vector<int> vtrRowBuf(30, 0);`定义成员对象时,其能否在头文件中定义取决于C++版本和初始化方式。关键在于**成员变量的声明和初始化位置**,以避免违反C++的**单一定义规则(ODR)** 和可能的编译错误。下面逐步分析: ### 1. **基本原则:成员变量的声明初始化** - **声明位置**:成员变量的声明(如`std::vector<int> vtrRowBuf;`)必须放置在类定义中,通常位于头文件(`.h`或`.hpp`)。这是标准做法,因为类定义需要在多个源文件中共享[^1][^2]。 - **初始化位置**: - **C++11 之前**:初始化不能在类定义中直接进行,必须在构造函数的初始化列表中实现(源文件中)。 - **C++11 及以后**:支持"in-class initialization",允许在声明时直接初始化(头文件中),但语法有限制。 ### 2. **您的代码 `std::vector<int> vtrRowBuf(30, 0);` 的问题** - **语法错误**:直接在类定义中使用`vtrRowBuf(30, 0);` 是无效的,因为编译器会将其解释为函数声明而非变量初始化。例如: ```cpp // 头文件 (错误示例) class MyClass { std::vector<int> vtrRowBuf(30, 0); // 错误:语法不合法,可能被解析为函数声明 }; ``` 这会导致编译错误,如 `error: expected identifier` 或 `error: function declaration not allowed here`[^1][^3]。 ### 3. **正确做法:头文件 vs. 源文件** - **情况1:初始化在源文件中(推荐,兼容所有C++版本)** - **头文件**:声明成员变量,**不带参数初始化**。 - **源文件**:在构造函数的初始化列表中初始化。 ```cpp // MyClass.h(头文件) #include <vector> class MyClass { public: MyClass(); // 构造函数声明 private: std::vector<int> vtrRowBuf; // 仅声明 }; // MyClass.cpp(源文件) #include "MyClass.h" MyClass::MyClass() : vtrRowBuf(30, 0) { // 正确初始化 // ... } ``` **优点**:避免ODR问题,编译安全,兼容C++98/03/11/14/17。 **缺点**:需额外编写构造函数。 - **情况2:初始化在头文件中(仅限C++11及以后)** - 使用**in-class initialization**语法: - 方式1:`= std::vector<int>(30, 0)`(拷贝初始化)。 - 方式2:`{std::vector<int>(30, 0)}`(花括号初始化)。 ```cpp // MyClass.h(头文件,C++11及以上) #include <vector> class MyClass { public: std::vector<int> vtrRowBuf = std::vector<int>(30, 0); // 正确:in-class初始化 // 或: std::vector<int> vtrRowBuf{std::vector<int>(30, 0)}; // 也可行 }; ``` **注意事项**: - `{30, 0}` 是危险的(会创建两个元素的vector:30和0),必须显式调用构造函数[^1][^4]。 - 若头文件被多个源文件包含,每个对象初始化独立,无ODR问题(因为是非静态成员)。 - **要求C++11或更高版本**(检查编译器标志如`-std=c++11`)。 ### 4. **为何不能在头文件中直接写 `vtrRowBuf(30, 0)`?** - C++语法规定:类定义中,成员变量声明**不能有参数列表**,否则被视为函数声明(如`int func(int, int);`)。 - 编译器错误示例:GCC/Clang会报错 `error: expected ‘;’ at end of member declaration`。 ### 5. **总结:最佳实践** - **推荐方式**:在头文件声明成员变量,在源文件的构造函数中初始化(兼容性好,避免错误)。 - **现代C++方式**:若使用C++11+,可在头文件使用 `= std::vector<int>(30, 0)` 初始化。 - **避免**:直接在头文件写 `vtrRowBuf(30, 0);`——语法错误。 ### 相关问题 1. 在头文件中初始化静态成员变量(`static std::vector<int> vec;`)时,需要注意什么? 2. C++11的in-class initialization对编译器优化有什么影响? 3. 使用`std::vector`作为类成员时,如何避免隐式拷贝导致的性能问题? [^1]: C++ STL源码剖析 5-std::vector,展示了vector的初始化和操作。 [^2]: C++ STL源码剖析 1-std::array,说明了C++版本对容器初始化的影响。 [^3]: C++ `std::unique_ptr`自定义删除器,强调了完整类型定义的重要性。 [^4]: C++ Vector实现任意阶矩阵乘法,演示了vector在类中的使用
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值