哪些类型可用做 map 容器对象的下标?下标操作符返回的又是啥类型

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哪些类型可用做 map 容器对象的下标?下标操作符返回的又是啥类型相关的知识,希望对你有一定的参考价值。

参考技术A 1.CArrayVS::std::vector?CArray和::std::vector一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。但是,CArray是在C++标准化之前很多年(VC++2.0时代)设计的,当时对C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成CArray的设计有重大错误。在C++语言标准化以后(1998),以及VC++6.0出世以后,提供了标准的::std::vector模板,基本上在任何方面都要优于CArray。Microsoft由于要支持老的程序,因此一直保留了CArray,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArrayconst&)吧)。概括起来,CArray与::std::vector有以下不同:1)CArray是MFC中的,::std::vector存在于任何标准的C++实现中。因此,你用熟了CArray也只能在MFC中用,若用熟了::std::vector,你可以在任何平台的任何C++编译器下使用。使用标准的部件也有利于别人理解你的程序。.CArray继承了CObject,仅仅为了实现serialization,这是不恰当的,违反了"Youdon'tpayforwhatyoudon'tuse."的C++设计原则。::std::vector没有继承任何东西,只是实现了管理一个动态数组该做的事。2)CArray不是一个恰当的值类型,例如下列操作都是不合法的:CArraya;CArrayb(a); //error,mustuseCopy().b=a;    //error,mustuseCopy().b==a;   //error,youmustwriteyourown.b相反,::std::vector是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果T是可比较的,那么::std::vector将自动地是可以比较的。此外,由于涉及到四个特殊成员函数;T();//缺省构造函数(defaultconstructor)~T();//解构函数(destructor)T(Tconst&);//拷贝构造函数T&operator=(Tconst&);//拷贝赋值函数的自动生成,如果使用CArray()作为T的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:structT T() T(Tconst&t)    a_.Copy(t.a_);   i_=t.i_;   d_=t.d_;   s_=t.s_;  T&operator=(Tconst&t)    if(this!=&t)        a_.Copy(t.a_);     i_=t.i_;     d_=t.d_;     s_=t.s_;      return*this; private: CArraya_; inti_; doubled_; ::std::strings_;;如果使用::std::vector:structTprivate: ::std::vectora_; inti_; doubled_; ::std::strings_;;上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减T的成员变量时,你不必到T(Tconst&)和operator=()中去相应地增减。3)没有现成的算法可以对CArray进行操作,而标准C++里的标准算法大多都可以直接在::std::vector上运行。例如:staticintconstinit_vals[]=3,1,4,1,6,9;vectora(init_vals,init_vals+6);*find(a.begin(),a.end(),6)=5;  //把6改成5sort(a.begin(),a.end());  //排序。可以说,CArray的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray继承的派生类呢?CByteArray等的问题与CArray的问题一样,甚至(例如,CPtrArray,永远不要用)。同样,其他的MFCcontainer模板,象CMap,CList等,都有类似问题,都应该用::std::map,::std::list等设计更好的东西代替。2.::std::vector在哪里?::std::vector在头文件中定义:(注意,标准的C++头文件都没有.h后缀,有.h的文件是与C兼容的,或支持老的不标准的东西,象。)namespacestd  template>  structvector      //具体内容稍后讨论  ;  template    booloperator==(vectorconst&a,vectorconst&  b);  template    booloperator!=(vectorconst&a,vectorconst&  b);  template    booloperatorconst&a,vectorconst&  b);  template    booloperator>=(vectorconst&a,vectorconst&  b);  template    booloperator>(vectorconst&a,vectorconst&  b);  template    booloperator>=(vectorconst&a,vectorconst&  b);vector定义在namespacestd中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:#includetypedef::std::vectorIntVector;IntVectora;IntVectorb(a);IntVectorc;c=b;assert(a==c);请注意中定义了六个vector的比较函数。这些函数只在真的用到时才会被实例化,才会要求T也提供operator==()和operator:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在VC++6的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。3.::std::vector中的类型定义vector中定义了一些类型,下面只列出常用的:typedefTvalue_type;typedefT0iterator;typedefT1const_iterator;typedefT2reverse_iterator;typedefT3const_reverse_iterator;value_type就是vector的元素类型,也就是T。当写通用的算法处理任意类型的vector或其他容器类型时是很有用的。iterator/const_iterator是两个vector的实现定义的未知类型,用于访问vector中的元素,类似于T*/Tconst*指针,他们的区别是一个指向的元素可被修改,另一个只可以读:typedef::std::vectorIntVector;IntVector::iteratoriter;IntVector::const_iteratorc_iter;//++iter;iter++;//ok:increment,post-increment.--iter;iter--;//ok:decrement,post-decrement.++c_iter;c_iter++;//ok:increment,post-increment.--c_iter;c_iter--;//ok:decrement,post-decrement.*iter=123;//ok.intk=*iter;//ok.k=*--c_iter;//ok.*c_iter=k;//error.c_iter=iter;//ok:iteratorisconvertibletoconst_iterator.iter=c_iter;//error:can'tconvertconst_iteratortoiterator.在使用上iterator/const_iterator和T*/Tconst*基本相同,事实上有些vector的实现里就是用T*/Tconst*实现iterator/const_iterator的,但又不可以把iterator/const_iterator当作真正的T*/Tconst*:T*p=iter;//mayfailtocompile.Tconst*q=c_iter;//mayfailtocompile.reverse_iterator/const_reverse_iterator与iterator/const_iterator类似,但以相反的次序(从尾至头)访问vector中的元素。各种各样的iterator在STL中有特别重要的意义,但这里我们不做具体介绍。只要理解通过iterator可以访问vector中的元素,大概相当于一个指示位置的指针就行了。4.::std::vector的构造vector提供了以下构造函数:(忽略allocator参数)vector();vector(size_tn,Tconstt=T());vector(vectorconst&);vector(const_iteratorfirst,const_iteratorlast);1)vector();构造一个空的vector,不包含任何元素。IntVectorv1;//空的整数向量。2)vector(size_tn,Tconstt=T());构造一个n个相同元素t组成的vector。如果不给出t,那么将用T()做缺省值:IntVectorv2(100,1234);//100个1234.IntVectorv3(100);//100个0。3)vector(vectorconst&other);复制构造函数,复制other中的内容:IntVectorv4(v2);//100个1234。4)vector(const_iteratorfirst,const_iteratorlast);事实上,这个构造函数应该为template  vector(Iterfirst,Iterlast);即拷贝任意的序列[first,last)到vector中。由于VC++6sp0编译程序的限制,Iter被换为const_iterator了。不过,碰巧const_iterator就是Tconst*,所以可以如下使用:inta[]=1,2,3,4,5;IntVectorv5(a,a+5);//1,2,3,4,5IntVectorv6(v5.begin()+2,v5.end());//3,4,55.访问vector中的元素以下成员函数/运算符用于访问vector中的一个元素:T&at(size_tn);Tconst&at(size_tn)const;T&operator[](size_tn);Tconst&operator[](size_tn)const;T&front();Tconst&front()const;T&back();Tconst&back()const;请注意,由于vector是一个“值”语义的对象,所有的操作函数都必须严格保证const的正确性。所以,所有的元素访问方法都有const和非const两个版本。at(n)和operator[](n)都返回下标为n的元素的引用,他们的区别是,at()进行下标越界检查,若发现越界,抛出range_error异常,operator[]不进行下标检查。front()返回下标为0的元素的引用,back()返回最后一个元素的引用。inta[]=4,1,4,1,5,8;IntVectorv(a,a+6);//使用front(),back():v.front()=3;v.back()=9;//使用operator[]():for(size_ti=0;i的存储管理以下成员函数用于存储管理:voidreserve(size_tn);size_tcapacity()const;voidresize(size_tn,Tt=T());voidclear();size_tsize()const;boolempty()constreturnsize()==0;size_tmax_size()const;另外,push_back(),insert()等也涉及到存储管理,后面另行介绍。1)max_size()返回vector理论上可以装的最多T的个数。这只是一个理论上的数字,大概是4GB/sizeof(T),没有多大实用价值。在程序中不要用。2)size()返回vector中实际装的T的个数。相当于CArray::GetSize()。3)empty()如果vector中没有任何T对象,返回true。也就是返回size()==0。4)clear();清除vector中的所有T对象。执行后empty()返回true。大致相当于resize(0),但不要求T可被缺省构造。相当于CArray::RemoveAll()。5)resize(size_tn,Tt=T());将vector中的元素个数设置为n,n可以大于size()也可以小于size。如果n小于size(),那么vector中下标为n..size()-1的元素都将被解构。如果n>size(),那么将在vector的后面新增加n-size()个相同的元素t。在增大vector时,可能发生存储再次分配。总之,调用resize(n,t)后,(size()==n)成立。请注意,如果调用resize(n)不带参数t,那么T必须可以缺省构造。6)reserve(size_tn);事先分配至少可以保存n个T对象的空间。调用后(capacity()>=n)成立。7)capacity();返回已经分配的存储空间够容纳的T类型对象的个数。后续的增加元素操作(如push_back(),insert())如果增加元素后vector中的总元素个数不超过capacity(),那么vector的实现保证不重新分配存储空间。vector管理的动态存储空间是连续的。执行操作IntVectorv(7,1);//sevenones.v.reserve(12);后,v的状态可以用下图表示:/--size()---\|1|1|1|1|1|1|1|-|-|-|-|-|\--capacity()---------/其中,1是已经构造的int类型的对象,-是可以构造一个int类型的对象,但还没有构造的原始空间。再执行v.push_back(2);v.push_back(3);后,v的状态可用下图表示:/----size()-----\|1|1|1|1|1|1|1|2|3|-|-|-|\----capacity()-------/执行resize(11,4);后:/----size()---------\|1|1|1|1|1|1|1|2|3|4|4|-|\----capacity()-------/capacity()>=size()总是成立的。对于下标为[size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:v[11]=5;//undefinedbehavior-anythingcanhappen.7.添加元素到vector中下列操作添加元素到vector中,并可能引起存储分配:voidpush_back(Tconst&t);voidinsert(iteratorpos,Tconst&t=T());voidinsert(iteratorpos,size_tn,Tconst&t);templatevoidinsert(iteratorpos,Iterfirst,Iterlast);push_back()是把一个元素添加到vector的末尾。insert()是把一个t,或n个t,或从first开始到last结束的一个序列插入到pos指示的位置之前。当插入元素后size()将会大于capacity()时,将引起自动存储分配。vector将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。这就是说,vector自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到vector中时,平均用时是接近于常数的。IntVectorv;//add0,1,,99tov:for(inti=0;i<100;++i)v.push_back(i);//append9,8,7,,0totheend:inta[]=9,8,7,6,5,4,3,2,1,0;v.insert(v.end(),a,a+10);8.删除元素下列成员函数完成元素删除:voiderase(iterator);voiderase(iteratorfirst,iteratorlast);voidpop_back();voidclear();这些函数分别删除一个,一串,最后一个,或全部元素。IntVectorv;for(inti=0;i<100;++i)v.push_back(i);//删除50,51,,89:v.erase(v.begin()+50,v.end()-10);//删除49,48:v.pop_back();v.pop_back();//全部删除:v.clear();注意,删除操作不会引起存储分配,因此capacity()不变。

如果 Python 对象是“可下标的”,这意味着啥?

【中文标题】如果 Python 对象是“可下标的”,这意味着啥?【英文标题】:What does it mean if a Python object is "subscriptable" or not?如果 Python 对象是“可下标的”,这意味着什么? 【发布时间】:2010-09-18 00:49:25 【问题描述】:

哪些类型的对象属于“可下标”的领域?

【问题讨论】:

【参考方案1】:

基本上意味着对象实现了__getitem__()方法。换句话说,它描述的是“容器”的对象,这意味着它们包含其他对象。这包括字符串、列表、元组和字典。

【讨论】:

可靠程度如何:hasattr(SomeClassWithoutGetItem, '__getitem__') 确定事物是否可下标? [...] 索引语法称为下标,因为它等同于使用实际下标的数学符号;例如a[1] 是数学家写成 a₁ 的 Python。所以“可下标”的意思是“可以下标”。这在 Python 术语中意味着它必须实现__getitem__(),因为a[1] 只是a.__getitem__(1) 的语法糖。 hasattr 的调用应该可以正常工作,但这不是Pythonic 的做事方式; Python 实践鼓励Duck Typing。这意味着,如果您打算尝试使用下标从对象中获取项目,请继续执行;如果您认为它可能无法工作,因为该对象不可下标,请将其包装在带有 except TypeErrortry 块中。 super 似乎是个例外。 super返回的对象可以有__getitem__的属性,但不能下标,因此括号切片不起作用。【参考方案2】:

下标在计算中的含义是: “一个符号(名义上写成下标,但实际上通常不写)在程序中单独或与其他人一起使用,以指定数组的元素之一。”

现在,在@user2194711 给出的简单示例中,我们可以看到附加元素不能成为列表的一部分,原因有两个:-

1) 我们并没有真正调用 append 方法;因为它需要() 来调用它。

2) 错误表示函数或方法不可下标;意味着它们不像列表或序列那样可索引。

现在看到这个:-

>>> var = "myString"
>>> def foo(): return 0
... 
>>> var[3]
't'
>>> foo[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'function' object is not subscriptable

这意味着function 中没有下标或say 元素,就像它们按顺序出现一样;在[] 的帮助下,我们无法像我们一样访问它们。

还有;正如mipadi 在他的回答中所说的那样;它基本上意味着该对象实现了__getitem__() 方法。 (如果它是可下标的)。 因此产生了错误:

arr.append["HI"]

TypeError: 'builtin_function_or_method' 对象不可下标

【讨论】:

【参考方案3】:

作为此处先前答案的推论,这通常表明您认为自己有一个列表(或字典,或其他可下标的对象),而实际上却没有。

例如,假设您有一个应该返回列表的函数;

def gimme_things():
    if something_happens():
        return ['all', 'the', 'things']

现在,当您调用该函数,而 something_happens() 出于某种原因没有返回 True 值时,会发生什么? if 失败,所以你失败了; gimme_things 没有显式地 return 任何东西——所以事实上,它会隐式地 return None。然后这段代码:

things = gimme_things()
print("My first thing is 0".format(things[0]))

将因“NoneType object is not subscriptable”而失败,因为thingsNone,因此您尝试执行None[0] 这没有意义,因为...错误消息是什么说。

有两种方法可以修复代码中的这个错误——第一种是通过在尝试使用之前检查things 是否确实有效来避免错误;

things = gimme_things()
if things:
    print("My first thing is 0".format(things[0]))
else:
    print("No things")  # or raise an error, or do nothing, or ...

或等效地捕获TypeError异常;

things = gimme_things()
try:
    print("My first thing is 0".format(things[0]))
except TypeError:
    print("No things")  # or raise an error, or do nothing, or ...

另一个是重新设计gimme_things,以确保它始终返回一个列表。在这种情况下,这可能是更简单的设计,因为这意味着如果有很多地方存在类似的错误,它们可以保持简单和惯用。

def gimme_things():
    if something_happens():
        return ['all', 'the', 'things']
    else:  # make sure we always return a list, no matter what!
        logging.info("Something didn't happen; return empty list")
        return []

当然,您在 else: 分支中添加的内容取决于您的用例。也许您应该在something_happens() 失败时引发异常,以使实际出错的地方更加明显和明确?在您自己的代码中添加异常是让您自己知道发生故障时确切情况的重要方法!

(还请注意,后一种修复方法仍然不能完全修复错误——它会阻止您尝试下标None,但当things 是一个空列表时,things[0] 仍然是IndexError。如果你有一个try 你也可以用except (TypeError, IndexError) 来捕获它。)

【讨论】:

【参考方案4】:

在我的脑海中,以下是唯一可下标的内置函数:

string:  "foobar"[3] == "b"
tuple:   (1,2,3,4)[3] == 4
list:    [1,2,3,4][3] == 4
dict:    "a":1, "b":2, "c":3["c"] == 3

但是mipadi's answer 是正确的——任何实现__getitem__ 的类都是可下标的

【讨论】:

【参考方案5】:

我也有同样的问题。我在做

arr = []
arr.append["HI"]

所以使用[ 会导致错误。应该是arr.append("HI")

【讨论】:

【参考方案6】:

可编写脚本的对象是记录对其执行的操作并将它们存储为可重放的“脚本”的对象。

例如,请参阅:Application Scripting Framework

现在,如果 Alistair 不知道他问的是什么并且真正的意思是“可下标”对象(由其他人编辑),那么(正如 mipadi 也回答的那样)这是正确的:

可下标对象是任何实现__getitem__ 特殊方法的对象(想想列表、字典)。

【讨论】:

请注意,我正在回答有关“可编写脚本”对象的原始问题,而不是其他人编辑的“可订阅”对象,而不是 Alistair。我真的希望 Alistair 发表评论。 啊,我收藏的新徽章! :) 开个玩笑,很明显。唯一证明编辑问题的理由是 Alistair 选择了一个答案。我仍然不确定 Alistair 是否确定要选择。

以上是关于哪些类型可用做 map 容器对象的下标?下标操作符返回的又是啥类型的主要内容,如果未能解决你的问题,请参考以下文章

Chapter11:关联容器

如果 Python 对象是“可下标的”,这意味着啥?

Scala之元组Set和map集合

java之map集合

map的常用用法详解

浅谈关联容器