C与C++的故事

c++ Lambda函数

https://www.cnblogs.com/DswCnblog/p/5629165.html
《C++沉思录》读书笔记
1.小项目比大项目效率可能更高,接口开发
2.重新思考类设计和封装的合理性(检查出错和漏洞)
3.做核查表(类,数据测试,UI更改,反馈)
4.质疑设计(公有成员,函数替代变量可能更好)

class与struct区别
https://www.cnblogs.com/starfire86/p/5367740.html

一、enum的取值范围
C++标准规定超出枚举类型表示范围的赋值结果是undefined的。
enum的取值范围和 int 的范围并不是直接一致的。计算enum的取值范围,可以按如下方式操作:
1、不考虑负数:获取enum中的最大值,根据这个最大值所具有的位数,可以计算enum的表示范围。
举个例子:enum test{ a=2, b=4 };最大值为4,二进制表示为100,3bits,因为3bits表示的范围是0~7,所以test的取值范围就是[0, 7];
2、考虑负数情况。负数情况下,需要多一位的符号位,其本质还是一样的。
比如:enum test{ a=-2, b=4 }; 绝对值最大值为4,需要3bits,由于4不是负数,还需要增加一位符号位,4bits,其表示范围就是1000~0111,所以test的取值范围就是[-8, 7]。
如果是enum test{ a=-4, b= }; 则仅仅3bits就足够了,因为 100 就是 -4。

二、enum的size
C++标准规定,enum的size只要能够容纳定义时刻的所有bits就可以了,具体取什么值,由编译器决定。比如enum test{ a=2, b=4 };仅需要3bits,那么可以取1B,也可以取4B,由编译器自己决定。VC中是固定取4B空间。

2018 C++学习计划
①巩固基础(刷题 分析好项目的设计)
②看书(开发+职场)
③做项目(网络编程...)
④补充简书

返回多个函数值:https://www.cnblogs.com/anwcq/p/C_hanshu.html
MVC架构:

Boost
Asio开发一个服务器后台程序:https://blog.csdn.net/zmyer/article/details/21115437
1.入门教程https://wenku.baidu.com/view/80d70dc52cc58bd63186bdbb.html?sxts=1532008418017
2.Asio异步输入输出的核心 https://blog.csdn.net/gongluck93/article/details/79364205 学会使用与看懂实现原理.
使用 Boost.Asio 进行异步数据处理的应用程序基于两个概念:I/O 服务和 I/O 对象。 I/O 服务抽象了操作系统的接口,允许第一时间进行异步数据处理,而 I/O 对象则用于初始化特定的操作。 鉴于 Boost.Asio 只提供了一个名为 boost::asio::io_service 的类作为 I/O 服务,它针对所支持的每一个操作系统都分别实现了优化的类,另外库中还包含了针对不同 I/O 对象的几个类。 其中,类 boost::asio::ip::tcp::socket 用于通过网络发送和接收数据,而类 boost::asio::deadline_timer 则提供了一个计时器,用于测量某个固定时间点到来或是一段指定的时长过去了。 以下第一个例子中就使用了计时器,因为与 Asio 所提供的其它 I/O 对象相比较而言,它不需要任何有关于网络编程的知识
3.建立本地网络服务器 (Boost.Asio库)https://blog.csdn.net/CSND_Ayo/article/details/61577634
4.boost::asio::spawn()创建一个协程,使C++网络编程大大简化,
个人认为这使得 asio 成为C++首选网络库。
boost::asio::spawn(my_strand, do_echo);
一般输入2个参数,参数1是 io_service 或者是 strand,
参数2是协程函数,类型如下:
void coroutine(boost::asio::yield_context yield);
在协程函数中调用各个异步IO,异步操作将挂起协程,待异步操作完成后会自动继续协程

python
1.python -m pydoc -b 查看文档

OOP设计原则
设计模式遵循的一般原则:
1.开-闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。
2.里氏代换原则(Liskov Substitution Principle,常缩写为.LSP)
(1).由Barbar Liskov(芭芭拉.里氏)提出,是继承复用的基石。
(2).严格表达:如果每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换称o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型.
换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别.只有衍生类可以替换基类,软件单位的功能才能不受影响,基类才能真正被复用,而衍生类也能够在基类的基础上增加新功能。
(3).反过来的代换不成立
(4).<墨子.小取>中说:"白马,马也; 乘白马,乘马也.骊马(黑马),马也;乘骊马,乘马也."
(5).该类西方著名的例程为:正方形是否是长方形的子类(答案是"否")。类似的还有椭圆和圆的关系。
(6).应当尽量从抽象类继承,而不从具体类继承,一般而言,如果有两个具体类A,B有继承关系,那么一个最简单的修改方案是建立一个抽象类C,然后让类A和B成为抽象类C的子类.即如果有一个由继承关系形成的登记结构的话,那么在等级结构的树形图上面所有的树叶节点都应当是具体类;而所有的树枝节点都应当是抽象类或者接口.
(7)."基于契约设计(Design By Constract),简称DBC"这项技术对LISKOV代换原则提供了支持.该项技术Bertrand Meyer伯特兰做过详细的介绍:
使用DBC,类的编写者显式地规定针对该类的契约.客户代码的编写者可以通过该契约获悉可以依赖的行为方式.契约是通过每个方法声明的前置条件(preconditions)和后置条件(postconditions)来指定的.要使一个方法得以执行,前置条件必须为真.执行完毕后,该方法要保证后置条件为真.就是说,在重新声明派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件.
3.依赖倒置原则(Dependence Inversion Principle),要求客户端依赖于抽象耦合.
(1)表述:抽象不应当依赖于细节,细节应当依赖于抽象.(Program to an interface, not an implementaction)
(2)表述二:针对接口编程的意思是说,应当使用接口和抽象类进行变量的类型声明,参量的类型声明,方法的返还类型声明,以及数据类型的转换等.不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明,参量类型声明,方法的返还类型声明,以及数据类型的转换等.
要保证做到这一点,一个具体的类应等只实现接口和抽象类中声明过的方法,而不应当给出多余的方法.
只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括参量的类型声明,方法返还类型的声明,属性变量的类型声明等.
(3)接口与抽象的区别就在于抽象类可以提供某些方法的部分实现,而接口则不可以,这也大概是抽象类唯一的优点.如果向一个抽象类加入一个新的具体方法,那么所有的子类型一下子就都得到得到了这个新的具体方法,而接口做不到这一点.如果向一个接口加入了一个新的方法的话,所有实现这个接口的类就全部不能通过编译了,因为它们都没有实现这个新声明的方法.这显然是接口的一个缺点.
(4)一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的登记结构中,而由于一般语言都限制一个类只能从最多一个超类继承,因此将抽象作为类型定义工具的效能大打折扣.
反过来,看接口,就会发现任何一个实现了一个接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个接口.
(5)从代码重构的角度上讲,将一个单独的具体类重构成一个接口的实现是很容易的,只需要声明一个接口,并将重要的方法添加到接口声明中,然后在具体类定义语句中加上保留字以继承于该接口就行了.
而作为一个已有的具体类添加一个抽象类作为抽象类型不那么容易,因为这个具体类有可能已经有一个超类.这样一来,这个新定义的抽象类只好继续向上移动,变成这个超类的超类,如此循环,最后这个新的抽象类必定处于整个类型等级结构的最上端,从而使登记结构中的所有成员都会受到影响.
(6)接口是定义混合类型的理想工具,所为混合类型,就是在一个类的主类型之外的次要类型.一个混合类型表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为.
(7)联合使用接口和抽象类:
由于抽象类具有提供缺省实现的优点,而接口具有其他所有优点,所以联合使用两者就是一个很好的选择.
首先,声明类型的工作仍然接口承担的,但是同时给出的还有一个抽象类,为这个接口给出一个缺省实现.其他同属于这个抽象类型的具体类可以选择实现这个接口,也可以选择继承自这个抽象类.如果一个具体类直接实现这个接口的话,它就必须自行实现所有的接口;相反,如果它继承自抽象类的话,它可以省去一些不必要的的方法,因为它可以从抽象类中自动得到这些方法的缺省实现;如果需要向接口加入一个新的方法的话,那么只要同时向这个抽象类加入这个方法的一个具体实现就可以了,因为所有继承自这个抽象类的子类都会从这个抽象类得到这个具体方法.这其实就是缺省适配器模式(Defaule Adapter).
(8)什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节的改变而改变的真理. 它是系统内部的系统____隐喻.
4.接口隔离原则(Interface Segregation Principle, ISP)
(1)一个类对另外一个类的依赖是建立在最小的接口上。
(2)使用多个专门的接口比使用单一的总接口要好.根据客户需要的不同,而为不同的客户端提供不同的服务是一种应当得到鼓励的做法.就像"看人下菜碟"一样,要看客人是谁,再提供不同档次的饭菜.
(3)胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系.当一个客户程序要求该胖接口进行一个改动时,会影响到所有其他的客户程序.因此客户程序应该仅仅依赖他们实际需要调用的方法.

5.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.
6.迪米特法则(Law of Demeter LoD)又叫做最少知识原则(Least Knowledge Principle,LKP),就是说,一个对象应当对其他对象有尽可能少的了了解.
迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,与1987年秋天由Ian Holland在美国东北大学为一个叫做迪米特(Demeter)的项目设计提出的,因此叫做迪米特法则[LIEB89][LIEB86].这条法则实际上是很多著名系统,比如火星登陆软件系统,木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则.
没有任何一个其他的OO设计原则象迪米特法则这样有如此之多的表述方式,如下几种:
(1)只与你直接的朋友们通信(Only talk to your immediate friends)
(2)不要跟"陌生人"说话(Don't talk to strangers)
(3)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位.
就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
7.单一职责原则(Simple responsibility pinciple SRP)
就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责.

另外:常说的OO五大原则就是指其中的 :
1、单一职责原则;
2、开放闭合原则;
3、里氏替换原则;
4、依赖倒置原则;
5、接口隔离原则。

https://zhidao.baidu.com/question/426438619.html
学习C++一定要多问几个“为什么是这样”,“凭什么要这样”的问题
1.为什么要有拷贝构造函数?http://blog.csdn.net/sun19910114/article/details/39735209
2.为什么要有初始化列表https://www.cnblogs.com/laiqun/p/5776212.html
3.为什么要有template,
4.为什么要有RTTI


C++可以说是C衍生而来却比C高级很多倍,也可认为是与C毫无瓜葛的一门新语言(这个角度可能学得更快)。
字符转int: http://www.cnblogs.com/bluestorm/p/3168719.html
C语言综合项目实战 3.13相片(70 76 80)

零 再提提与C的关系

1.c++中添加c库
extern "C"
{

include <stdio.h>

}
2.对C扩展了:①命名空间 ②引用 ③函数重载 ④默认参数
⑤参数列表
①作用:防止变量冲突(避免全局变量重名,避免使用库函数的时候出现与库中的变量名冲突)-多人开发时类似于c语言中的static文件作用域
②变量的别名不占用内存空间(从本质上说引用是编译器对指针的一种包装Type *const p) 定义:数据类型 &引用名 = 变量名 (定义引用的时候必须要赋值);函数内部可通过const修饰避免改变引用对象值:
int a = 10;//或const int a =10;
const int &b = a;
③函数名相同,参数个数或类型或顺序不同
④形参赋值 从右到左连续默认;
⑤参数列表初始化:Data(int d=20, int n=10):data(d),number(n)//类外实现,创建对象的时候执行,声明时用默认参数,实现时不需要写
3.类与结构体区别(关键字):①类中可以用变量和函数,结构体只能有变量②类默认权限是私有的,结构体是公有
*const修饰常量替代#define定义的宏常量更有效率(裸机开发)

一 何为面向对象

结构体类似C++等语言的类,成员不能放函数,但能放函数指针,C++面向对象思路类似C语言的结构体


微信截图_20170405092523.png

复杂宏分析

大疆14年笔试.png

Paste_Image.png

C++相关文件后缀:.cpp, .cc, .cxx, .hpp, .h
1.0构造(析构)函数:与类名相同,没有返回值,创建对象后系统自动调用(可以在构造方法中初始化成员),系统可默认生成一个不带参数;构造方法重载一般类内声明类外实现,如果是在类的内部声明和实现c++中会认作为内联函数;析构:无返回值无参数,当对象销毁时系统自动调用

1.1拷贝构造函数:浅(系统也可自动生成):Data(Data &d);深拷贝:不但要拷贝对象本身的空间,还要拷贝对象成员指向的额外空间(堆空间)
1.2.成员属性和方法:
1.3.private: //私有,对外隐藏 在类的外部不可以通过类的对象来使用的成员-在类的内部使用无限制
2.1 继承(代码复用):原有基础添加新功能(方法-属性) class Child: public Parent

4.析构方法先调用子类,再调用父类</br>


虚继承.png

2.2 三种不能继承情况
2.3 多继承:class 子类 :继承方式 父类, 继承方式 父类 (如果没有写继承方式默认为私有继承);构造方法调用顺序与继承顺序有关,与参数列表无关;环状多继承;出现多个副本的时候可以用虚继承来避免多个父类对象

2.4 重写与覆盖

3.0 多态(联系继承,纯/虚函数)
虚函数(父类要有):virtual void show();//修饰基类成员函数(派生同名类指向问题解决)[指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数]
子类重写父类普通函数和虚函数区别-虚表查看方法


1.如果一个类中有虚函数那么这个类的对象中包含一个虚表(虚表是在对象的最前面)
2.类中的虚函数指针会存储在虚表中,
3.如果在子类中有与父同名,同参函数,在虚表中会把父类虚函数覆盖
4.如果子类有个与父类不同的虚函数,虚函数指针会存储在父类虚函数表后面


纯虚函数与抽象类:纯虚函数:virtual void show()=0;//不需要函数体,有纯虚函数的类-->抽象类(抽象类是不能被创建对象);继承时子没把父(抽象类)中纯虚函数全部实现也是抽象类
3.0
3.1

4.0 指针与智能指针
指针变量是用于存储地址(切记),定义时为其赋初值NULL即为(void *)0,也就是空指针,这样做

是为了防止野指针;长度:在32位系统当中是4个字节,跟指向的类型空间是没有关系的
int i = 10; //变量i的地址表示 &i;
int *p = &i; //定义一个指针变量p,p指向int数据类型空间,指向空间的值为10

int i = 12
char *p = "hello"; 
int *p1 = &i;
sizeof(p) = 4   sizeof(p1) = 4

错误使用
//1.错误引用指针,未初始化指针,非法引用
int *p;
*p = 50;

//2

一维数组注意:定义a[10]是取不到a[10]这个值的!原因:使用时下标最大值为定义时元素个数减1
数组越界,可以访问但里面值非自己所想

数组本身同指针关系
int a[5] = {1,2,3,4,5};
sizeof(a) = 20 //5*sizeof(int);

a   与   &a
a :数组的首地址,也是首元素地址
&a:整个数组的首地址

自己查阅资料
int *p;
p=a;
printf("%p\n",&p1[10]);//0xbfe10e52,输出第n位地址只能用&符号取地址?
第一个元素地址可用量种方法获得p=&a[0]或p=a;数组名作参数传递时会变为普通指针(4字

节),失去常量特性,可自增减操作,可被修改
指针与二维数组关系:(p+1)+2)表示取a[1][2]内容,*p表示取a[0][1]内容,因为p

是指向整型变量指针;p++表示p的内容加1,即p中存放的地址增加一个整型量的字节数2,从而使p指

向下一个整型量a[0][1]

假设:a = 0x23; 那么&a  = 0x23
    a+1 = 0x27     &a+1 = 37

思考:有数组short a[5] = {1,2,3,4,5};假设a = 0x56;请问下列的值为多少
 a    &a    a+1  &a+1  a+3  *(&a[0])  a[3]+7  *(a+2)  
0x56 0x56  0x58  0x60  0x5c  1          11      3
*(&a[0]) == a[0]

-级指针与一维数组
char ch[6] = "hello"; //ch = 0x22
char *p = NULL; //防止野指针
char *p1= NULL;

p = ch;
p1 = &ch[2]; 

printf("p+2  = %p",p+2);  //ch + 2 = 0x24 
printf("p1 - p  = %p",p1 - p);   //结果是2


结论:
指针相减是针对数组(才有意义)而言,相减结果为地址间的元素的个数(公式:(p1 - 

p)/sizeof(数据类型));
指针相加是无意义的

思考:int a[10] = {12,354,53,645,34,4,6};
      int *p = NULL,*p1 = NULL;

      p = &a[1];
      p1  = &a[9];

      p1-p = 8         (&a[5])-(&a[0]) = 5
      
     p1-p:(p1-p)/sizeof(int) = 32/4 = 8
     (&a[5])-(&a[0]):(&a[5])-(&a[0])/sizeof(int) = 20/4 = 5
Paste_Image.png

二维数组
二维数组:一维数组的数组,由相同的类型组成,有固定长度

二维数组模型:
数据类型 数组名[m][n] = {值};
数据类型:与变量类型是一致的;char short int long float double unsigned(char 

short int long)
数组名:命名规则跟变量命名的规则是一致的
m:大数组个数
n:每个大数组中小数组元素个数

int arr[2][3]; //定义了一个二维数组,大数组2,每个大数组中小数组元素个数3,可以存

六个int 数据;未初始,值是随机
int arr[][50];//不允许,无法确定个数
int arr[][3] = {1,2,3};//系统可以初始化分配空间大小,大数组1,每个大数组中小数组

元素个数3
思考:int arr[][4] = {2,3,4,5,6,5}; //int arr[2][4] = {2,3,4,5,6,5}; arr[1][3]

=0
int arr[3][4] = {{1,2,3,4},{3,4,5,6},{5,6,7,8}};//初始化并赋值
int arr[2][4] = {2,3,4,5,6,5} == int arr[2][4] = {{2,3,4,5},{6,5,0,0}};

char ch[3][4] = {"he","hi","boy"};
ch[0] == "he";ch[1] == "hi";ch[2] == "boy";

ch[1][2] = '\0';

深度剖析二维数组
char ch[3][4] = {"he","hi","boy"};
ch &ch
ch:数组首元素的首地址
&ch:整块数组的首地址

假设上面:ch = 0x14;
           %p      %p        %p        %d
ch +1  &ch+1  ch[0]   ch[2]         ch + 2   a[1][1]+7  ??
0x18(v)   0x38    0x14(v) 0x1c(v)   0x1c(v)  112(v)   
0x15      0x20(v)         0x14+0x8

5.0 类的友元函数:可访问类中所有成员
友元类:B类中声明A类为友元类,A类中所有成员函数都是B类友元函数,所有成员函数可访问B类中所有成员
实现:

拷贝构造函数 引用 深拷贝 浅拷贝

1.什么是拷贝构造函数?
类对象复制时需要用的复制构造函数的一种叫法,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的;是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量[形如CExample(const CExample& C)]
2.怎样调用?有哪几种方式? (①②是编译器自动产生(默认)拷贝构造函数)
调用形式 http://blog.csdn.net/lwbeyond/article/details/6202256
①对象以值传递的方式传入函数参数 如:CExample test(1); g_Fun(test);//传入对象[解析:调用g_Fun()时,会产生以下几个重要步骤(1).test对象传入形参时,会先会产生一个临时变量,就叫 C 吧 (2).然后调用拷贝构造函数把test的值给C。 整个这两个步骤有点像:CExample C(test); (3).等g_Fun()执行完后, 析构掉 C 对象 ]
②对象以值传递的方式从函数返回
③对象需要通过另外一个对象进行初始化 CExample A(100);
CExample B = A; //这里的对象初始化要调用拷贝构造函数,而非赋值 ,CExample B(A); 也是一样的
3.注意事项
①“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间
②可以防止按值传递——声明一个私有拷贝构造函数

二 如何设计我的类和对象

静态成员与方法

3.静态属性和方法-[java没指针(处处有指针)]
C++允许我们把一个或多个成员声明为属于某个类而不仅仅属于该类
的对象(没创建对象也可以调用方法,使用相关属性,还可让有关数据在该)
类所有对象间共享)
创建:static(3个作用:1.隐藏全局变量 2.用来控制变量的存储方式和可见性 3.默认初始为0) 静态方法能访问所有成员吗?

静态成员与静态函数
类对象大小计算:非静态成员属性+在虚表中指针大小
static关键字(全局 局部 可同名,作用域不同(类也有作业域))
声明为静态的好处:
存储(静态存储区) 生命周期最长

静态成员(保存在数据段)
private(class中的):
static int data;
int main(){Data d;//栈中}

int Data:data = 10;//静态成员属性初始化(定义在类外,未赋值不占用空间)
类的静态成员属性先于类的对象存在,公有的话可通过类名使用,被所属类所有对象共用
例:Data d;
Data::data;//属性公有可通过类名使用
Data mD;
d.data = 123;
cout<<mD.data<<endl;//输出123

静态方法 5.5 9:40左右视频
先于对象存在,静态方法中不包含this指针,不能调用非静态成员,可调用先于对象存在的成员和局部变量(和类无关的)
公有方法可被所属类名和类的对象直接调用

单例模式(截图 5.5 10:20) 用途:类传输数据(数据类)-传输:静态 单实例(堆 首地址-所有数据)
获取对象 单例可以被延迟初始化
public: 单例类可以被集成,他的方法可以被覆写
Data(){} 单例可以继承类,实现接口,
static Data *sharedData()
{
if(instance == NULL)
{
instance = new Data();//堆空间
}
应用:整个应用只能创建一个对象,对象间数据传递

总结:或许最重要的是,单例类可以被用于多态而无需强迫用户只假定唯一的实例。举个例子,你可能在开始时只写一个配置,但是以后你可能需要支持超过一个配置集,或者可能需要允许用户从外部从外部文件中加载一个配置对象,或者编写自己的。你的代码不需要关注全局的状态,因此你的代码会更加灵活。

三 库的使用-STL和Boost

STL---标准模板类库 --www.cplusplus.com
string ---
template < class charT,
class traits = char_traits<charT>, // basic_string::traits_type
class Alloc = allocator<charT> // basic_string::allocator_type
> class basic_string;

typedef basic_string<char> string;

list
template < class T, class Alloc = allocator<T> > class list;

vector
template < class T, class Alloc = allocator<T> > class vector; // generic template

set ----存储的数据必须是唯一的, 自动排序
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
> class set;

map
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > // map::allocator_type
> class map;

推荐阅读更多精彩内容