繁体   English   中英

C ++中向量的新手

[英]Newbie in vectors in C++

我试图了解矢量是如何工作的。 从我读过的它们是一个可以用作数组的类,它有许多有用的函数来处理它的元素。 所以我尝试创建一个类A的向量,它包含一个类B的向量。这是代码:

#include <iostream>
#include <vector>

using namespace std;

class B
{
  public:
    B()
    {}
    void print()
    {
        cout<<"The mighty ";
    }
    ~B()
    {}
};

class A
{
B b;
vector<B> Blist;
public:
    A()
    {
        cout<<"An A!"<<endl;
    }
    void pushb()
    {
        Blist.push_back(b);
    }
    void printb()
    {
        Blist[7].print();
    }
    void print()
    {
        cout<<"Kass Company"<<endl;
    }
    ~A()
    {
    }
      };


int main(void)
{
vector<A> Alist;
A a, b, c;
Alist.push_back(a);
Alist[1].printb();
Alist[1].print();
return 0;   
}

好吧,我的问题是......它运作正常。 如果向量像数组一样工作,那么第一个被推回的对象会得到向量的0位置吗? 因此,程序无法运行,因为Alist [1]或Blist [7]中没有对象? 提前致谢!

好吧,我的问题是......它运作正常

嗯,实际上它不应该,因为你正在访问AlistAlist::Blist

如果向量像数组一样工作,那么第一个被推回的对象会得到向量的0位置吗?

std::vector<T>::push_back函数将一个元素追加到向量的末尾,因此push-backed元素的索引size() - 1 (在推送之后,例如旧的size() )。

检查你的界限

使用std::vector您负责检查您尝试访问的边界。 您可以使用std::vector<T>::size()进行此检查,或者使用函数std::vector<T>::at(size_t)如Jarod42所述。 有关更多信息,请参阅STL文档: http//www.cplusplus.com/reference/vector/

为什么它似乎仍然有效

你遇到了未定义的行为,但它似乎工作得很好。 为什么?

好吧,在内部,向量保存一个指向动态分配内存的指针,保存向量内容。 该类封装了所有讨厌的内存管理(调用newdelete ,调整数组大小等)。 当您调用std::vector<T>::operator[](size_t) ,例如通过执行Alist[1] ,它简单地归结为在给定索引处取消引用内部数组(没有绑定检查)。

使用错误的索引,您最终会在分配区域的末尾读取一些内存,这些内存不包含任何有意义的数据,并且可能未初始化或未清零。 总而言之,当你在做Alist[1] ,你得到的一些垃圾记忆被解释为A实例。

现在为什么地狱做Alist[1].print()不会崩溃? 由于函数A::print()不使用类成员,并做a->print()根本不使用a内容。

您可以使用此程序验证这一点(请不要使用此程序,它仅适用于此演示):

int foo = 0xDEADBEEF;
A& z = static_cast<A&>(*((A*) &foo));
z.print();

这段代码只使用整数值foo占用的内存作为A实例(就像你在访问超出边界的向量时使用未初始化的内存一样),并调用A::print()函数。

你可以自己尝试一下,它按预期工作! 这是因为这个成员函数不需要使用实例的实际内存内容,并且无论z指向垃圾还是不运行都会运行。

如何调试和检查此程序

使用valgrind( http://valgrind.org/ )。 当然。 使用valgrindmemcheck ,您可以跟踪无效的读写(以及其他与内存相关的东西):

you$ valgrind --tool=memcheck a.out

==1785== Memcheck, a memory error detector
==1785== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==1785== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==1785== Command: ./a.out
==1785==
An A!
An A!
An A!
==1785== Invalid read of size 8
==1785==    at 0x400F14: std::vector<B, std::allocator<B> >::operator[](unsigned long) (stl_vector.h:771)
==1785==    by 0x400E02: A::printb() (main.c:34)
==1785==    by 0x400C0D: main (main.c:51)
==1785==  Address 0x5a12068 is 8 bytes after a block of size 32 alloc'd
==1785==    at 0x4C28965: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==1785==    by 0x4022E5: __gnu_cxx::new_allocator<A>::allocate(unsigned long, void const*) (new_allocator.h:104)
==1785==    by 0x401D20: std::_Vector_base<A, std::allocator<A> >::_M_allocate(unsigned long) (in /home/amonti/.local/share/people/temp/a.out)
==1785==    by 0x4013F8: std::vector<A, std::allocator<A> >::_M_insert_aux(__gnu_cxx::__normal_iterator<A*, std::vector<A, std::allocator<A> > >, A const&) (vector.tcc:345)
==1785==    by 0x401017: std::vector<A, std::allocator<A> >::push_back(A const&) (stl_vector.h:913)
==1785==    by 0x400BF4: main (main.c:50)
==1785==
The mighty Kass Company
==1785==
==1785== HEAP SUMMARY:
==1785==     in use at exit: 0 bytes in 0 blocks
==1785==   total heap usage: 1 allocs, 1 frees, 32 bytes allocated
==1785==
==1785== All heap blocks were freed -- no leaks are possible
==1785==
==1785== For counts of detected and suppressed errors, rerun with: -v
==1785== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 3 from 3)

在此跟踪中,valgrind在main.c:34处检测到无效读取(大小为8,因为您正在64位平台上读取指针):

Blist[7].print();

所以你可以验证你做错了什么。

在你的情况下,输出可能是一个垃圾结果,因为矢量数据结构的逻辑是它是一个动态数组,它在到达最后一个空闲空间时自我扩展(通过一个恒定范围)。 例如,当第一次创建一个向量时,当它到达10个空格时它有10个空格,它变为20,在这个阶段,vec [11]有一个垃圾值。

这正是您第一次测试/编写程序时应该使用vector::at()而不是vector::operator[]的原因。

您可以使用宏和预处理器定义来声明您正在编译以进行调试,例如:

#ifdef THISISDEBUG
    return myvec.at(5);
#else
    return myvec[5];
#endif

然后在调试/测试时告诉makefile define THISISDEBUG

at()operator[]之间的区别在于at()如果你超出范围, at()会抛出异常,而operator[]直接访问内存。

在C ++中,通常允许您读取内存中的任何位置(至少在Windows和Linux中),但您只能写入属于您的程序的位置。 您的操作系统可以保护您! 想象一下,你做了你在那里所做的事情,并尝试修改一些不属于你的项目的东西。 那时候在80年代和90年代,这将被接受,并将导致蓝屏。 现在,您的操作系统引发了SEGFAULT

另一方面,你在那里看到结果的原因是因为删除对象并不一定意味着重置内存中的值。 它只是意味着你的程序告诉操作系统:“看,我不再需要这个内存区域了”。 因此,您的操作系统可以将此区域分配给另一个程序。 所以,如果你再次尝试阅读该区域,它会起作用,但你会得到垃圾! 这正是技术上的所谓。 就像你这样做:

double x;
std::cout << x << std::endl;

打印的价值是多少? 这是垃圾。 它是其他程序的残余,释放了那些记忆。

基本矢量是数组类。

vector <string> arr; // defines that this is array
vector <MyClass *> arrw; // defines that this is array to my created class vector

当您不知道需要多少数组元素时,向量很有用。 例如,从文件中读取行。 要向vector添加新元素,可以使用arr.insert(arr.end(), ""); add.insert(arr.end(), new MyClass); (我更喜欢这个,然后push_back ,因为你可以插入任何向量的地方。)

您可以通过相同的方式访问您的数组元素:

arr[2];

此外,了解一些技巧,如获取最后一个元素,也是有用的。 arr[arr.size() - 1] i (arr.size()将返回INT [数组中的元素]。并且-1将计算它以获得良好的索引。否则您将得到分段错误)。

PS矢量类和数组之间没有区别,ecxept这个方法允许添加新元素,当你不知道你的数组有多大时。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM