20141101
1、stl由哪些东西组成,stl的容器有哪些,各个容器是如何分配内存的
stl由容器,算法、迭代器、分配空间的分配器、容器适配器
2、怎样让一个类只能在堆上面分配内存;
//HeapOnly.cpp 只能在堆或者栈上分配内存的类 #include <iostream> using namespace std; class HeapOnly { public: HeapOnly() { cout << "constructor." << endl; } void destroy () const { delete this; } private: ~HeapOnly() {} }; int main() { HeapOnly *p=new HeapOnly; p->destroy(); // HeapOnly h; // h.Output(); return 0; } //StackOnly.cpp //2005.07.18------2009.06.05 #include<iostream> using namespace std; class StackOnly { public: StackOnly() { cout << "constructor." << endl; } ~StackOnly() { cout << "destructor." << endl; } private: void* operator new (size_t); }; int main() { StackOnly s; //okay StackOnly *p=new StackOnly; //wrong return 0; }
3、线程同步的方式
互斥对象,时间对象,消息队列,
4、类的静态成员函数有什么用
基于:它跟类的实例无关,只跟类有关,不需要this指针。 (1)可以实现某些特殊的设计模式:如Singleton;(2)由于没有this指针,可以把某些系统API的回调函数以静态函数的形式封装到类的内部。因为系统API的回调函数通常都是那种非成员函数(孤立函数),没有this指针的。比如你可以在类的内部写一个线程函数供CreateThread创建线程用,如果没有静态函数,那么这种回调函数就必须定义成全局函数(非静态成员函数指针无法转换成全局函数指针),从而影响了OO的“封装性”。
(3)可以封装某些算法,比如数学函数,如ln,sin,tan等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好,比如定义一个数学函数类Math,调用Math::sin(3.14);如果非要用非静态函数,那就必须:Math math; math.sin(3.14);行是行,只是不爽:就为了一个根本无状态存储可言的数学函数还要引入一次对象的构造和一次对象的析构,当然不爽。而且既然有了对象,说不得你还得小心翼翼的定义拷贝构造函数、拷贝赋值运算符等等,对于一些纯算法的东西显然是不合适的。
(4)绝对东西的只有一点:“静态函数不需要实例化就可以被调用,不会也不可以调用或操纵非静态成员”。
5、类的静态成员变量有什么用
(1)不需要实例化就可以调用他
(2)可以作为计数器,比如这个创建了多少个这个类的对象
(3)实现单例设计模式
(4)更好体现出C++封装性更好,代替全局变量
(5)静态数据成员不能在类中初始化
6、main函数之前都做了什么
(1).设置栈指针
(2).初始化static静态和global全局变量,即data段的内容
(3).将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容
(4).运行全局构造器,估计是C++中构造函数之类的吧
(5).将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数
7、单例设计模式
- 普通构造函数、拷贝构造函数、赋值构造函数、甚至析构函数都要定义为private
- 在类得内部定义static对象,这个就是所谓的单例。
- 定义静态成员函数来返回static对象
8、类的模板的定义和实现绝对不能分开,必须都在同一个文件中,通常是头文件。
这是因为,当include该模板头文件时,编译器要看到模板实现才能展开模板。
9、什么是友元
友元函数 :
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
friend 类型 函数名(形式参数);
友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。
一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
友元函数的调用与一般函数的调用方式和原理一致。
友元类 :
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
例如,以下语句说明类B是类A的友元类:
class A
{
…
public:
friend class B;
…
};
经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。
使用友元类时注意:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明
10、友元的优点和缺点
有时需要定义一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该函数的友元函数。友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
11、父类中不能被继承的东西
构造函数,赋值函数,友元
12、子类和父类之间的重载不加特殊处理是不能进行的。
#include <iostream> using namespace std; class B{ public: int f(int i) { cout << "f(int): "; return i+1;} }; class D : public B { public: double f(double d,int x) { cout << "f(double): "; return d+1.3; } }; int main(){ D* pd = new D; cout << pd->f(2.1,2) << '\n'; cout << pd->f(2.3) << '\n';//发生错误,不能重载 }
13、为什么查看不到子类的虚函数
#include<iostream> using namespace std; class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } int base; protected: private: }; class Child1 : public Base { public: virtual void f1() { cout << "Child1::f1" << endl; } virtual void g1() { cout << "Child1::g1" << endl; } virtual void h1() { cout << "Child1::h1" << endl; } int child1; protected: private: }; class Child2 : public Child1 { public: virtual void f2() { cout << "Child2::f2" << endl; } virtual void g2() { cout << "Child2::g2" << endl; } virtual void h2() { cout << "Child2::h2" << endl; } int child1; protected: private: }; int main() { Base B; Child1 *C1=new Child1; Child2 *C2=new Child2; return 1; }
14、只要有虚函数“=0”的类,不管这个虚函数有没有具体实现,这个类就是抽象基类!!因此,不能实例化,但可以定义指针和引用
#include <iostream> using namespace std; class Shape {public: virtual float area() const {return 0.0;} virtual float volume() const {return 0.0;} virtual void shapeName() const =0; static void f1(){cout<<"f1"<<endl;} }; class circle {public: virtual float area() const {return 0.0;} virtual float volume() const {return 0.0;} virtual void shapeName() const {cout<<"Shape"<<endl;} static void f1(){cout<<"f1"<<endl;} }; void Shape::shapeName() const {cout<<"Shape"<<endl;} int main() { circle cir; Shape &pt=(Shape &)cir; pt.f1(); return 0; }
指向子类对象。
15、Q:STL中set底层实现方式? 为什么不用hash?
A: 第一个问题:set底层实现方式为RB树(即红黑树)。
第二个问题:
首先set,不像map那样是key-value对,它的key与value是相同的。关于set有两种说法,第一个是STL中的set,用的是红黑树;第二个是hash_set,底层用得是hash table。红黑树与hash table最大的不同是,红黑树是有序结构,而hash table不是。但不是说set就不能用hash,如果只是判断set中的元素是否存在,那么hash显然更合适,因为set 的访问操作时间复杂度是log(N)的,而使用hash底层实现的hash_set是近似O(1)的。然而,set应该更加被强调理解为“集合”,而集合所涉及的操作并、交、差等,即STL提供的如交集set_intersection()、并集set_union()、差集set_difference()和对称差集set_symmetric_difference(),都需要进行大量的比较工作,那么使用底层是有序结构的红黑树就十分恰当了,这也是其相对hash结构的优势所在。
16、stl的map和sethttp://blog.sina.com.cn/s/blog_627021ec0100q3g5.html
# 为何map和set的插入删除效率比用其他序列容器高?
因为它们的实现是红黑树,只需要改变指针的指向,不需要内存的拷贝和移动。
# 为何每次insert之后,以前保存的iterator不会失效?
插入之后也只是指针指向的改变,节点的内存地址并没有变化,所以自然iterator也不会变化。
# 为何map和set不能像vector一样有个reserve函数来预分配数据?(yexuannan的理解:不连续空间如何保留,保留哪一部分都不知道)
引起它的原因在于在map和set内部存储的已经不是元素本身了,而是包含元素的节点。也就是说map内部使用的Alloc并不是map<Key, Data, Compare, Alloc>声明的时候从参数中传入的Alloc。
例如:map<int, int, less<int>, Alloc<int> > intmap;
这时候在intmap中使用的allocator并不是Alloc<int>, 而是通过了转换的Alloc,具体转换的方法时在内部通过
Alloc<int>::rebind重新定义了新的节点分配器,详细的实现参看彻底学习STL中的Allocator。其实你就记住一点,在map和set内面的分配器已经发生了变化,reserve方法你就不要奢望了。
# 当数据元素增多时(10000到20000个比较),map和set的插入和搜索速度变化如何?
在map和set中查找是使用二分查找,如果有16个元素,最多需比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是
20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。
你明白这个道理后,就可以安心往里面放入元素了。
17、resize和reserve的区别
reserve是容器预留空间(改变的是capacity大小,size并没有改变),但并不真正创建元素对象,在创建对象之前,不能引用容器内的元素,因此当加入新的元素时,需要用push_back()/insert()函数。
resize是改变容器的大小(size改变,capacity没改变),并且创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象
18、C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等
2.标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般的平衡二叉树
3.STL map和set的使用虽不复杂,但也有一些不易理解的地方,如:
map: type pair<const Key, T>,很多不同的const Key对应的T对象的一个集合,所有的记录集中只要const Key 不一样就可以,T无关!
set: type const Key. 只存单一的对const Key,没有map 的T对像!可以看成map的一个特例
19.set, multiset
set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。
因为是排序的,所以set中的元素不能被修改,只能删除后再添加。
向set中添加的元素类型必须重载<操作符用来排序。排序满足以下准则:
1、非对称,若A<B为真,则B<A为假。
2、可传递,若A<B,B<C,则A<C。
3、A<A永远为假。
set中判断元素是否相等:
if(!(A<B || B<A)),当A<B和B<A都为假时,它们相等。
20.map, multimap
map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序,map中元素的key不允许重复,multimap可以重复。map<key, value>
因为是排序的,所以map中元素的key不能被修改,只能删除后再添加。key对应的value可以修改。向map中添加的元素的key类型必须重载<操作符用来排序。排序与set规则一致。
21. List的功能方法
实际上有两种List: 一种是基本的ArrayList,其优点在于随机访问元素,另一种是更强大的LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。
List : 次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用。)一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。
ArrayList : 由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和移除元素。因为那比LinkedList开销要大很多。
LinkedList : 对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替。)还具有下列方法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用
22、 hash_map和map的区别在哪里?
构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).
存储结构。hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其memory数据结构是不一样的。
23、 什么时候需要用hash_map,什么时候需要用map?
总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小无关,属于常数级别; 而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且hash_map的构造速度较慢。
现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用。
24.一些使用上的建议:
Level 1 - 仅仅作为Map使用:采用静态数组
Level 2 - 保存定长数据,使用时也是全部遍历:采用动态数组(长度一开始就固定的话静态数组也行)
Level 3 - 保存不定长数组,需要动态增加的能力,侧重于寻找数据的速度:采用vector
Level 3 - 保存不定长数组,需要动态增加的能力,侧重于增加删除数据的速度:采用list
Level 4 - 对数据有复杂操作,即需要前后增删数据的能力,又要良好的数据访问速度:采用deque
Level 5 - 对数据中间的增删操作比较多:采用list,建议在排序的基础上,批量进行增删可以对运行效率提供最大的保证
Level 6 - 上述中找不到适合的:组合STL容器或者自己建立特殊的数据结构来实现
9. (1).vector - 会自动增长的数组
vector<int> vec(10) //一个有10个int元素的容器
vector<float> vec(10, 0.5)//一个有10个float元素的容器,并且他们得值都是0.5
vector<int>::iterator iter;
for(iter = vec.begin(); iter != vec.end(); iter++)
{
//. . . . . . .
}
vector由于数组的增长只能向前,所以也只提供了后端插入和后端删除,
也就是push_back和pop_back。当然在前端和中间要操作数据也是可以的,
用insert和erase,但是前端和中间对数据进行操作必然会引起数据块的移动,
这对性能影响是非常大的。
最大的优势就是随机访问的能力。
vector<T1>::iterator相关的方法有:
begin():用来获得一个指向vector第一个元素的指针
end():用来获得一个指向vector最后一个元素之后的那个位置的指针,注意不是指向最后一个元素。
erase(vector<T1>::iterator):用来删除作为参数所传入的那个iterator所指向的那个元素。
(2).list - 擅长插入删除的链表
list<string> Milkshakes; list<int> Scores;
push_back, pop_back push_front. pop_front
list是一个双向链表的实现。
为了提供双向遍历的能力,list要比一般的数据单元多出两个指向前后的指针
一个使用iterator来删除元素的例子
list<string> stringList;
list<string>::iterator iter;
advance(iter, 5); //将iterator指向stringList的第五个元素
stringList.erase(iterator);//删除
那么删除操作进行以后,传入erase()方法的iterator指向哪里了呢?它指向了删除操作前所指向的那个元素的后一个元素。
(3).deque - 拥有vector和list两者优点的双端队列
(4).这三个模板的总结 比较和一般使用准则
这三个模板都属于序列类模板,可以看到他们有一些通用方法
size():得到容器大小
begin():得到指向容器内第一个元素的指针(这个指针的类型依容器的不同而不同)
end():得到指向容器内最后一个元素之后一个位置的指针
erase():删除传入指针指向的那个元素
clear():清除所有的元素
==运算符:判断两个容器是否相等
=运算符:用来给容器赋值
上面的三个模板有各自的特点
vector模板的数据在内存中连续的排列,所以随机存取元素(即通过[]运算符存取)的速度最快,这一点和数组是一致的。同样由于它的连续排列,所以它在除尾部以外的位置删除或添加元素的速度很慢,在使用vector时,要避免这种操作。
list模板的数据是链式存储,所以不能随机存取元素。它的优势在于任意位置添加 删除元素的速度。
deque模板是通过链接若干片连续的数据实现的,所以均衡了以上两个容器的特点
25、虚函数表解析
http://blog.csdn.net/haoel/article/details/1948051/
26、虚函数表的安全性,虚表,如何输出所有虚函数的地址
#include<iostream> using namespace std; class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } int base; protected: private: }; class Child1 : public Base { private: virtual void f1() { cout << "Child1::f1" << endl; } virtual void g1() { cout << "Child1::g1" << endl; } virtual void h1() { cout << "Child1::h1" << endl; } int child1; protected: private: }; int main() { Base B; Child1 C1; cout<<"虚函数指针的地址:"<<(int *)(&C1)<<endl;//这里int *表示强制类型转化,意思是改变指针操纵的范围是4个字节 cout<<"虚表地址:0x"<<hex<<(int *)*(int*)(&C1)<<endl;//相当于取得虚函数指针的值,表示虚表的地址 cout<<"第1个虚函数地址:"<<hex<<*(int *)*(int *)(&C1)<<endl;//对虚表的地址,作解引用操作,取出第一个虚函数的地址 cout<<"第2个虚函数地址:"<<hex<<*((int *)*(int *)(&C1)+1)<<endl;//对虚表的首地址+1,然后解引用操作,取出第二个虚函数的地址 typedef void(*Fun)(void); Fun pFun=NULL; pFun=(Fun)*((int*)*(int*)(&C1)); pFun(); pFun=(Fun)*((int*)*(int **)(&C1)+1); pFun(); pFun=(Fun)*((int*)*(int*)(&C1)+2); pFun(); pFun=(Fun)*((int*)*(int*)(&C1)+3); pFun(); pFun=(Fun)*((int*)*(int*)(&C1)+4); pFun(); pFun=(Fun)*((int*)*(int*)(&C1)+5); pFun(); return 1; }
27、STL的erase函数的用法
#include<iostream> #include<string> #include<vector> using namespace std; int main() { //iterator的使用陷阱: vector<int> veci; veci.push_back(3); veci.push_back(2); veci.push_back(3); veci.push_back(4); veci.push_back(5); veci.push_back(3); veci.push_back(2); veci.push_back(3); vector<int>::iterator iter; vector<int>::iterator iter2; for(iter=veci.begin(); iter!=veci.end();) //正确做法1 { if( *iter == 3) { iter=veci.erase(iter); } else iter++; } for(iter=veci.begin(); iter!=veci.end();iter++) //正确做法2 { if( *iter == 3) { iter2=iter; iter--; veci.erase(iter2); } } //}//错误做法 ////这样使用是错误的,因为earase结束后,iter变成了野指针,iter++就产生了错误。 //for(iter=veci.begin(); iter!=veci.end();) //正确做法 //{ // if( *iter == 3) // iter=veci.erase(iter); // else // iter++ //} }
28、list<string>
#include <iostream> #include<vector> using namespace std; #include<set> #include<map> #include<list> #include<string> #include<algorithm> void show(list<string> vec) { list<string>::iterator iter; for(iter=vec.begin();iter!=vec.end();iter++) cout<<*iter<<" "; cout<<endl; } int main() { list<string> stringList; list<string>::iterator iter; stringList.push_back("yexuannan"); /*for(iter=stringList.begin();iter!=stringList.end();iter++)*/ for(int i=0;i<10;i++) stringList.push_back("yexuannan"); show(stringList); iter=stringList.begin(); //iter必须先初始化,然后才能放进advance advance(iter, 5); //将iterator指向stringList的第五个元素, stringList.erase(iter);//删除 }
29、C++容器选用总结
30、为什么智能指针不能是容器元素
http://my.oschina.net/costaxu/blog/105101
STL容器在分配内存的时候,必须要能够拷贝构造容器的元素。而且拷贝构造的时候,不能修改原来元素的值。而auto_ptr在拷贝构造的时候,一定会修改元素的值。所以STL元素不能使用auto_ptr。
31、没有指向引用的数组
int &a[5]是错误的
33、类的static成员变量一定要在类的外部初始化(不能再main函数中),不初始化就会出现如下错误
#include <iostream>//饿汗式 using namespace std; class Singleton{ public: static void getInstance(); static int instance; }; void Singleton::getInstance() { if (instance==NULL) { instance = 2; } } int Singleton::instance=0;//一定要初始化,不然会出错 int main(){ Singleton* singleton1 ; //Singleton* singleton3=new Singleton(); Singleton::getInstance(); cout<<Singleton::instance; return 0; }