1 说一下static关键字的作用
**1.全局静态变量:**静态存储区,在整个程序运行期间一直存在,未经初始化的全局静态变量会被自动初始化为0,全局静态变量的作用域在声明他的文件之外是不可见的。
**2.局部静态变量:**静态存储去,作用域仍为局部,当定义他的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域之后,并没有被销毁,而是仍然主流内存当中,只不过我们不再对它进行访问,直到该函数再次被调用,并且值不变。
**3静态函数:**函数的定义和声明在默认情况下都是extern的,但静态海曙只是声明他的文件当中可见,不能被其他文件所用。函数的使用static修饰,那么这个函数只可以在本cpp内使用,不能被其他文件所用。warning:不要在头文件中声明static全局函数,不要再cpp内声明非static的全局函数,如果你要在多个cpp中复用函数,就把它的声明提到头文件中去,否则cpp内部声明需要加上static休息。
**4.类的静态成员:**在类中,**静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的规则,保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。**对多个对象来说,静态数据成员只存储一处,共所有对象公用。
**5 类的静态函数:**静态成员函数和静态成员变量一样,它们都属于类的,不是对象的,静态成员函数不能够调用普通的成员函数和普通的成员变量,因为静态成员函数属于类,不知道普通的成员属性属于哪个对象,只能调用静态的类的资源。在访问非静态变量的时候,用的是this指针;而static静态函数没有this指针,所以静态函数也确实没有办法访问非静态成员,静态成员通过<类名>::<静态成员>来使用。
2 说一下c和c++的区别
**设计思想:**c++是面向对象的语言,而c是面向过程的结构化编程语言
**语法上:**c++具有封装,继承和多态三种特性。c++相比c,增加许多类型安全的功能,比如强制类型转化。c++zhichi范式编程,比如类模板和函数模板等/
3 说一说c++中四种cast转换
c++中四种类型转换是:static_cast,dynamic_cast,const_castreinterpret_cast
**reinterpret_cast:**可以用于任意类型的指针之间的转换,对转换的结果不做任何保证
**dynamic_cast:**这种其实也是不被推荐使用的,更多使用static_cast,dynamic本身只能用于存在虚函数的父子关系的强制类型转换,对于指针,转换失败则返回nullptr,对于引用,转换失败会抛出异常
**const_cast:**对于未定义const版本的成员函数,我们通常需要使用const_cast来去除const引用对象的const,完成函数调用。另外一种使用方式,结合static_cast,可以在非const版本的成员函数内添加const,调用完const版本的成员函数后,再使用const_cast去除const限定。
**static_cast:*完成基础数据类型;同一个继承体系中类型的转换;任意类型与空指针类型void 之间的转换。
1.const_cast
用于将const变量转化为非const;
struct SA{
int i;
};
const SA ra;
//ra.i=10;//错误
SA &rb=const_cast<SA&>(ra);
rb.i=10;
2.static_cast
用于各种隐式转化,比如非const转化为const,void*转指针等,static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知。static_cast不能去掉类型的const、volitale属性(用const_cast)
int n=6;
double d=static_cast<double>(n);// 基本类型转换
int *pn=&n;
double* d=static_cast<double*>(&n);//无关类型指针转换,编译错误
void* p=static_cast<void*>(pn);//任意类型转换成void类型
3.dynamic_cast
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。
向上转换:指的是子类向基类的转换
向下转换:指的是基类向子类的转换
它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
class BaseClass {
public:
int m_iNum;
virtual void foo(){}; //基类必须有虚函数。保持多台特性才能使用dynamic_cast
};
class DerivedClass: public BaseClass {
public:
char *m_szName[100];
void bar(){};
};
BaseClass* pb = new DerivedClass();
DerivedClass *pd1 = static_cast<DerivedClass *>(pb); //子类->父类,静态类型转换,正确但不推荐
DerivedClass *pd2 = dynamic_cast<DerivedClass *>(pb); //子类->父类,动态类型转换,正确
BaseClass* pb2 = new BaseClass();
DerivedClass *pd21 = static_cast<DerivedClass *>(pb2); //父类->子类,静态类型转换,危险!访问子类m_szName成员越界
DerivedClass *pd22 = dynamic_cast<DerivedClass *>(pb2); //父类->子类,动态类型转换,安全的。结果是NULL
4.reinterpret_cast
几乎什么都可以转,比如将interesting转指针,可能会出问题,尽量少用。
int doSomething(){return 0;};
typedef void(*FuncPtr)(); //FuncPtr is 一个指向函数的指针,该函数没有参数,返回值类型为 void
FuncPtr funcPtrArray[10]; //10个FuncPtrs指针的数组 让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组:
funcPtrArray[0] = &doSomething;// 编译错误!类型不匹配,reinterpret_cast可以让编译器以你的方法去看待它们:funcPtrArray
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); //不同函数指针类型之间进行转换
4 请说一下c/c++中指针和引用的区别?
1.指针有一块自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小为4,而引用则是被引用对象的大小;
3.指针可以被初始化为NULL,二引用必须被初始化且必须是一个已有对象的引用;
4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
5.可以有const指针,没有const引用;
6.指针在使用中可以指向其他对象,但是引用只是一个对象的引用,不能被改变;
7.指针可以有多级指针(***p),而引用只有一级;
8.指针和引用使用++运算符的意思不一样;
9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能造成内存泄露。
5 请你说一下你理解的c++中的smart pointer四个智能指针:shared_ptr, unique_ptr, weak_ptr,auto_ptr
C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用。
为什么要使用智能指针:
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
智能指针的简单介绍
智能指针主要用于管理堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生命周期结束时描绘在析构函数中释放掉申请的内存,从而防止内存泄漏。c++11中最常用的智能指针类型为share_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用技术的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有当引用计数为0时,智能指针才会自动释放引用的内存资源。对share_ptr进行初始化时不能将一个普通函数直接赋值给智能指针,因为一个是指针一个是类。可以通过make_shared函数或者哦那个过构造函数传入普通指针。并可以通过get函数获得普通指针。
1. auto_ptr(c++98的方案,cpp11已经抛弃)
采用所有权模式。
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
2. unique_ptr(替换auto_ptr)
unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。
采用所有权模式,还是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3;//此时会报错!!
编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed
其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。
注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
3 .share_ptr
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的
4.weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
class B;
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout<<"A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout<<"B delete\n";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_; 改为weak_ptr pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。
注意的是我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();
6 请回答一下数组和指针的区别
请你回答一下野指针是什么
野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针
7 请你回答一下智能指针有没有内存泄漏的情况
当两个对象相互使用一个share_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄露,例如:
8 请你来说一下智能指针的内存泄露如何解决
为了解决循环引用导致的内存泄露,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。
9 请你回答一下为什么析构函数必须是虚函数?为什么c++默认的析构函数不是虚函数?
将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
c++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此c++默认的析构函数不是虚函数,而是只有当需要当作父类时,视之为虚函数。
10 请你来说一下函数指针
1 定义函数指针是指向函数的指针变量。
函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量,字符型,数组一样,这里是指向函数。
c在编译时,每一个函数都有一个入口地址,该入口地址就是函数指向指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可以引用其他类型变量一样,在这些概念上大体是一致的。
2 用途
调用函数和做函数的参数,比如回调函数。
3 实例
char* fun(char* p){} //函数fun
char* (pf)(char p);函数指针pf
pf=fun;//函数指针pf指向函数fun
pf§ //通过函数指针pf调用函数fun;
11 友元函数
友元的作用是提高了程序的运行效率(既减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
友元函数是直接可以访问类的私有成员的非成员函数。它定影在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,形式为 在前面加friend
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括private和prorect成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
friend class 类名:
class A
{
…
public:
friend class B;
…
};
需要注意
1友元关系不能被继承。
2友元关系是单向的额,不具有交换性,若类B是类A的友元,但是类A不一定是类B的友元。
3友元关系不具有传递性。若类B是类A的友元,若类C是类B的友元,类C不一定是类A的友元。
12 请你来说一下fork函数
Fork创建一个和当前进程映像一样的进程可以通过fork()系统调用:
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
成功调用fork()会创建一个新的进程,它几乎与调用fork()的进程一摸一样,这两个进程都会继续运行。在子进程中,成功的fork()的调用会返回0,在父进程中的fork()返回子进程的pid。如果出现错误,fork()返回一个负值。
最常见的fork()用法是船舰一个新的进程,然后使用exec()载入二进制映像,替换当前进程的映像。这种情况下,派生(fork)了新的进程,二这个子进程会执行一个新的二进制可执行文件的映像。
13 请你说一下c++中析构函数的作用
析构函数于构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
析构函数也应与类名相同,只是在函数名前面加一个位取反符,例如stud(),以区别于构造函数。不能有任何参数,也没有返回值。只能有一个析构函数,不能重载。
如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。
如果一个类中有指针,且在使用过程中动态的申请了内存,那么最好显示构造析构函数在销毁之前,释放掉申请的内存空间,避免内存泄漏。
类析构顺序:(1)派生类本身的析构函数;(2)对象成员析构函数(3)基类析构函数。
14 请你来说一下静态函数和虚函数的区区别
静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数标记值,调用的时候会增加一次内存开销。
15 请你来说一说重载和覆盖
重载两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中。
重写子类继承了父类,父类中的函数时虚函数,在子类中重新定义这个虚函数,这种情况时重写。
16 请你说说strcpy和strlen
strcpy是字符串拷贝函数 ,原型:
char* strcpy(char* dest,const char *src);
从src逐字节拷贝到dest,知道与熬到‘\0’,因为没有指定长度,可能导致拷贝越界,造成缓冲区溢出漏洞,安全版本是strncpy函数。
strlen是计算字符串长度的函数,返回从开始到’\0‘之间的字符个数。
17 请你说一说++i和i++的实现
++i先自增1,再返回,i++先返回i,再自增1
//++i实现
int& int::operator++(){
*this+=1;
return this;
}
//i++实现
const int int::operator(int){
int oldvalue=*this;
++(*this);
return oldValue;
}
18 请你写个函数在main函数执行前先运行
__attribute((constructor))void before()
{
printf("before main\n");
}
19 以下四行代码的区别市什么?const char* arr=“123”;char* brr=“123”;const char crr[]=“123”;char drr[]=“123”;
const char arr=“123”*
字符串123保存在常量区,const本来市修饰arr指向的值不能通过arr去修改,但是字符串“123”在常量区,本来就不能改变,所有加不加const效果都一样
char brr=“123”*
字符串保存在常量去,这个arr指针指向的是同一个位置,同样不能通过brr去修改“123”的值。
const char crr[]=“123”
这里123本来是在栈上的,但是编译器,但是编译器可能会做某些优化,将其放到常量区。
char drr[]="123"
字符串123保存在栈区,可以通过drr去修改。
20 请你来说一下c++里是怎么定义常量的?常量存放在内存的那一个位置?
常量在c++里的定义就是一个top-level const加上对象类型,常量定义必须初始化。对于局部对象,常量存放在栈区,对于全局变量,常量存放在全局/静态存储区。对于字面值常量,常量存放在常量存储区。
21 请你来回答一下const修饰成员函数的目的是什么?
cosnt修饰的成员函数表明函数调用不会对对象做出任何修改,事实上,如果确认不会对对象做更改,就应该为函数加上const限定,这样无论const对象还是普通对象都可以调用该函数。
22 浅谈const和define区别
const用于类成员变量的定义,同事const本身就是一个语言结构,而define本身就是一个语言结构,而define是一个函数,const在编译的时候要比define快很多。
区别:
1 const用于成员变量的定义时,不可修改。而define不可用于成类员变量的定义,但是可以用于全局变量。
2 const不能用于条件语句中定义,而define可以,比如if…else。
3 const 采用一个普通的常量名称,define可以采用表达式作为名称。
4 const只能接受静态的标量,而define可以采用任何表表达式
5 const定义常量时大小写是敏感的,而define可通过第三个参数(true)来指定大小写是否敏感.
23 如果同事定义两个函数,一个带const,一个不带,会有问题吗?
不会,相当于函数的重载了。
24 请你来说一说隐式类型转换
**首先,对于内置类型,低精度变量给高精度变量赋值会发生隐式类型转换,**其次,对于只存在单个函数的构造函数的对象构造来说,函数调用可以直接使用该参数传入,编译器会自动调用其构造函数生成临时对象。
25 请你来说以下c++中类成员的访问权限
c++通过public,private和protect三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的,私有的和受保护的,被称为成员访问限定符。在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。
26 请你来说一下c++中class和struct的区别
在c++中,可以用struct和class定义类,都可以继承。区别在于:struct的默认继承权限和默认访问权限是public,而class的默认继承权限和默认访问权限是private。
另外,class还可以定义模板类形参,比如template <class T, int i>。
27 请你来说以下一个c++源文件从文本到可执行文件执行经历的过程?
对于c++源文件,从文本到可执行文件一般需要四个过程:
(1)预处理阶段:对源文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。
(2)编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件。
(3)汇编阶段:将编译阶段生成的汇编文件转换成机器码,生成可重定位目标文件。
(4)链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件。
28 请你来回答一下include头文件的顺序以及双引号“”和尖括号<>的区别?
include头文件的顺序:对于include的头文件来说,如果在文件a.h中声明一个在文件b.h中定义的变量,而不引用b.h,那么要在a.c文件中引用b.h文件,并且要先引用b.h,后引用a.h,否则汇报变量类型未声明错误。
双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不一样。
对于使用双引号包含的头文件,查找头文件路径的顺序为:
(1)当前头文件目录
(2)编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
(3)系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径
对于使用尖括号包含的头文件,查找头文件的路径顺序为:
(1)编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
(2)系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径
29 请你回答以下malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?
malloc函数用于动态分配内存。为了减少内存碎片和系统调用的开销,malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为i内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。malloc采用隐式链表结构将堆区分成连续的,大小不一的块,包含已分配的块和未分配的块;同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录一个连续的、未分配的地址。
30 请你来说一下map和set有什么区别,分别是怎么实现的
map和set都是c++关联容器,其底层实现都是红黑树(RB-Tree)。由于map和set所开放的各种操作接口,RB-tree也都提供了,所以几乎所有的map和set的操作行为,都只是转调RB-Tree的操作行为。
maph和set的区别:
(1)map中的元素师key-value对:关键字起到索引的作用,值则表示与索引相关的数据;Set与之相对就是关键字的简单集合,set中每个元素只包含一个关键字。
(2)set的迭代器是const,不允许修改元素的值;map允许修改value,但不允许修改key。其原因是因为map和set是根据关键字进行排序保证其有序性,如果修改key的话,那么需要首先需要删除该键,然后调节平衡,在修改修改后的键值,调节平衡,如此一来,严重破坏了map和set的结构,导致iterator失效,不知道指向改变前的位置还是改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值;二map的迭代器咋不允许修改key值,允许修改value值。
(3)map支持下标操作,set不支持下标操作。map可以用key坐下标,map的下标运算符[]将关键码作为下标去执行查找,如果关键码不存在,则插入一个具有该关键码和mapped——type类型默认值的元素至map中,因此下标运算符[]在map应用中需要慎用,const_map不能用,只希望确定某一个关键值是否存在而不希望插入元素也不应该使用,mapped_type类型没有默认值也不应该使用。如果find能解决需要,尽量用find。