c++笔记(一)

这里写的主要是一些c/c++值得注意的地方和c++primer笔记,方便以后回顾,复习c++,当然会有一些错误,发现后再改正

 

//当形参引用时,数组不能转化为指针
//“\”是连接符,当宏定义用多行时常用
1:c中不可以连续赋值;
   c++可以,如int a,b,c;a=b=c=1;


2:一般int main(){


        ...
       }
        如果main函数里不加system("pause");的话,程序运行后立马闪退,为了让程序暂停,也可以加
     cin.get();(c++中,)c中可以加getchar();(输入后需按回车键才能发送到输出流),getche();(不      用按回车键,但输入的字符不会回显到屏幕上 ),getch();(不用按回车键,回显)
      为什么呢?就已这个例子来说吧
     int main(){
cout << "How many carrots do you have?" << endl;
int n=0;
cin >> n;
cout << "Here are two more;"< cout << "Now you have " << n + 2 << "carrots" << endl;
cin.get();
        cin.get();
//system("pause");
return 0;
    }        
      当我们根据提示输入一个数字,比如12,然后按回车,其实输入缓冲区里可能还有数据,按回车       后,第一个用来接收缓冲区里的数据,第二个用来停顿(当然用system("pause");最好) 


3: c++在c的基础上又增加了long long型,sizeof(long long)=64;(一般来说);
     bool型,值为false或true


4:  可以通过包含头文件climits来查看整型的最大值最小值(climits专门用于检测整型数据数据类型        的表达值范围。)
        int main(){
int n_int = INT_MAX;      //int型的最大值,下面依次类推 
short n_short= SHRT_MAX;  //这里写法注意,是SHRT_MAX;
long n_long = LONG_MAX;
long long n_llong = LLONG_MAX;
cout << "int is " << sizeof(int) << " bites" << endl;
cout << "short is " << sizeof(short) << " bites" << endl;
cout << "long is " << sizeof(long) << " bites" << endl;
cout << "long long is " << sizeof(long long) << " bites" << endl << endl;
cout << "Maximum values:" << endl;
cout << "shor:" << n_short << endl;
cout << "int:" << n_int << endl;
cout << "long" << n_long << endl;
cout << "long long:" << n_llong << endl;
cout << "The minumum int value:" << INT_MIN << endl;
cout << "Bits per byte:" < system("pause");



return 0;
}


5: c++可以这样初始化:int a(10);//a=10;
                       int b{10};//b=10;
                       int c={};//c=0
                       int d{};  //;也可以不使用=
     为什么要用大括号呢?因为大括号初始化可以初始化任何类型的变量(可以加等号,也可以不加)


6: 如果知道变量的值大于16位整数的最大可能值时,则用long,即使系统上int为32位(即sizeof    (int=4)),也应该这么做,这样,
    将程序移植到16位系统时,就不会突然无法正常工作。如果存储的值超过20亿,可使用long long.


7: 可以这样输出八进制,十进制,十六进制
   cout<    ocut<    cout<

8:  转义字符\a表示振铃字符(ASCII码为7),可以使终端扬声器振铃


9:float a = 2.34e+22f;
float b = a + 1.0f;
cout << "a=" << a << endl;
cout << "b-a=" << b - a << endl;
   b-a应该为1,可是运行程序时输出的确是
    a=2.34e+022;
    b-a=0;
    问题在于,2.34+22是一个小数点左边有23位的小数,加上1就是在第23位加1,但float类型只能表示    数字中的前6位或前7位,因此修改第23位对这个值不会有任何影响。




11: c中的宽字符基于wchar_t数据类它在几个头文件(包括wchar.h)中都有定义,像这样:
     typedef unsigned short wchar_t;
      因此,wchar_t数据类型与无符号短整型相同,都是16位宽
     定义一个宽字节可以这样:
     wchar_t c=‘A’;直接输出的话cout<      还可以定义指向字符串的指针  wchar_t *p=L"hello";//注意要在前面加L(表示long);
                                                    //cout<                                                    // sizeof(*p)是2(即h的字节数(宽))
     也可以直接定义宽字符串wchar_t c[]=L"hello";   // sizeof(c)是12(5个字符,1个字符串结束标志,每                                                     个占2位)
     但这样直接输出cout<      cout<      宽字符想要输出对应的字符,应用wcout。
     wchar_t是用两个字节存储的,例如字符串(wchar_t )“hello!”,intel将其在     内存中存储为
           48 00 65 00 6c 00 6f 00 21 00
     wchar_t *p=L"hello";
     printf("%d\n",strlen(p));
     所以如果这样的话 ,结果是1;因为strlen函数获取字符串的第一个字符后就遇到了0,即结束标志
      ,而单字节就不会有这种情况,为了解决这个问题,有宽字节的strlen版本,就是wcslen(wide 
      character string length)
12:c++中,求字符串长度不是c中的strlen();而是string.size();或string.length;
     string a="hello";
     cout<

13 :##表示粘贴符号
   比如# define T(x) L##x
       如果定义T("hello"),就相当于L"hello";


14:c++中的强制类型转换
    typename(values)//更像是函数调用
    c中: (typename)value
    c++还引用了4个强制类型转换运算符,对它们的使用要求更为严格,例如:
    static_cast (value)
     


    ===================================================================================


       c++中,如果两个类型有关联,比如int型变量和float型变量可以相互转换,举个例子:
  int i=3.14+1;//编译器会警告可能会丢失数据,但不会报错
相加的两个类型不同,c++不会直接将两个不同类型的值相加,而是先根据类型转换规则将类型统一后再求值,这些是自动进行的,不用程序员操心,有时甚至不用程序员了解,被称作"隐式转换";
 其他类型的隐式转换:
  1):数组转换为指针:
          int arr[5];
          int *p=arr;//arr转换为指向数组首元素的指针
        当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof         
     以及typeid等运算符的运算对象时,上述转换不会发生,同样的,如果   
     用一个引用来初始化数组,上述转换也不会发生,当在表达式中使用函       
     数类型时会发生类似的指针转换


   2)指针的转换
       c++还规定了几种其他的指针转换方式,包括常量整数0或者字面  
     值,nulllptr能转换成任意指针类型;指向任意非常量的指针能转换成  
     void *;指向任意对象的指针能转换成const void *;


   3)转换成布尔类型;
        存在一种从算术类型或指针类型向布尔类型自动转换的机制
   4)转换成常量:
        允许将指向非常量类型的指针转换成指向相应的常量类型的指针,
      对于引用也是这样,比如:
      int i;
      const int &j=i;  //可以将非常量转换成常量的引用
      const int *p=&i; //可以将非常量的地址转换成const的地址
      int &q=j,*r=p; //反之则错误


   5)类类型定义的转换:
       类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一
     种类类型的转换;
        string s,t="a string";//字符串字面值转换为string 类型
        while(cin>>s)        //cin转换为布尔值 


显示转换
    1)命名的强制类型转换
          cast-name(expression);
        type是转换的目标类型而expression是要转换的值,如果type是引用
      类型,则结果是左值,cast-name是static_cast,dynamic_cast,
      const_cast和reinterpret_cast(reinterpret)中的一种,


          a.dynamic_cast:
          dynamic_cast支持运行时类型识别,cast-name指定了执行的是哪
       种转换


          b.static_cast:
            当需要把一个较大的算术类型赋给较小的类型时,static_cast
           非常有用,此时不会警告丢失数据了;
             static_cast对于编译器无法自动执行的类型转换也非常有用,
           例如,可以使用static_cast找回存在于void *指针中的值
              void *p=*d;//任何非常量对象的地址都能存入void *
              double *dp=static_cast(p);
            //将void *换回初始的指针的类型
              当把指针存放在void *中,并且使用static_cast将其强制转
            换回原来的类型时,应该保证指针的值保持不变,因此,必须确
            保转换后所得类型就是指针所指的类型,类型一旦不符,将产生
            未定义的后果


           c.const_cast:
              const_cast只能改变运算对象的底层const,
               const  char *pc;//pc是指针,指向const char类型,这里
             //的const是底层const,比如char *const pc,这个const就是
             //顶层const,个人觉得没必要刻意去记,这些东西很好理解
               char *p=const_cast(pc);//正确,但是不能改变p所
             //指对象的值




             d.reinterpret_cast
                 reinterpret_cast通常为运算符对象的位模式提供较低层
               次上的重新解释,比如:
                 int *ip;
                 char *pc=reinterpret_cast(ip);
                 请牢记pc所指的真实对象是一个int而非char,如果把pc当
               成普通的字符指使用就可能在运行时发生错误,例如:
                  string str(pc);//这可能导致异常的运行时行为
                   使用reinterpret_cast是非常危险的,上面这个例子很
               好的证明了这一点,其中的关键问题是类型改变了,但编译                 
               器没有给出任何警告或错误的提示信息,当用一个int的地址
               初始化pc时,由于显示地声称这种转换合法,所以编译器不
               会发出任何警告或错误信息,接下来再使用pc时就会认定它
               的值是char *类型,编译器没法知道它实际是指向int,这种
               错误很难发现 


          2)旧式的强制类型转换
                早期的c++中,显示地进行强制类型转换有两种形式:
                type (expr); //函数形式
                (type) expr; //c语言形式
      
    ===================================================================================


15:  c++11新增了一个工具,让编译器能够根据初始值的类型推断变量的类型,为此,它重新定义了auto,auto是c语言的一个关键字,但很少使用
      auto x=2.1;//x是double型


16:cin可以读取一个字符串到输入流,但是如果用cin输入时,输入的字符串含有空格键,tab   键,回车键等,就会结束输入,这是面向单词的输入,
   istream中的类(如cin)提供了一些面向行的类成员函数:(比如定义了一个数组name[size])getline(name,size)和get(name,size),区别是getline()输入结束后就会丢弃换行符,而get()不会(即缓冲区还有换行符)


17:char a[] ="string";
char *p = a;
cout << "a=" << a << " &a= " << &a << endl;
cout << "p=" << p << " &p=" << &p << " (int *)p=" << (int *)p << endl;
    结果a=string &a=0018F7c0
        p=string &p=0018F7B4 (int *)p=0018F7c0


18:  通常,cout在显示bool值之前将其转化为int,但cout.setf(ios:boolalpha)(老式c++)和cout.setf(
        ios_base::boolalpha)(c++11)函数调用设置了一个标记,命令cout显示true和false,而不是1和0
        int a;
cout << (a>3) << endl;
cout.setf(ios_base::boolalpha);
cout << (a>3)<

19:对于i++和++i,如for(int i=0;i                      for(int i=0;i     在c++内置类型中,没有差别,但如是用户自定义类型中,++i效率更高,直接将i+1然后进入下一循环,而
   i++是先复制一个副本,将其加1,然后再将副本返回


20:  一个细节,值得注意
        int i = 8;

       if (i++==9||i==9)

       cout << "yes" << endl;

       else cout << "no" << endl;
      c++规定,||是个顺序点,也就是说,先修改左侧的值,再对右侧进行判定(c++11的说法是,运算符左边       
     的子表达式先于右边的子表达式)冒号和逗号运算符也是顺序点
      左边i++==9  这里i还是8,然后到右边,i就是9了,所以if里面为true


21:c++从c继承了一个与字符相关的,非常方便的函数软件包,它可以简化诸如确定字符是否为大写字母,数字    
    标点符号等工作,这些都在cctype(c中是ctype.h)中定义的,ep:ch是一个字母,则isalpha(ch)函数返回      
     一个非零值,否则返回0;同样,如果ch是标点符号(如逗号或句号),函数ispunct(ch)函数返回true、(   
     这些函数的返回类型为int,而不是bool,但通常bool转换竜让您能够将它们视为bool类型)
    使用这些函数就方便多了,例如:
     if(ch>='a'&&ch<='z'||ch>'A'&&ch<'Z')
     可以用if(isalpha(ch))代替
     还有很多函数,请看C.Primer.Plus (197页)
     
22:for循环可以这样用
    int a[5] = {1,3,5,7,9};
for (int i : a)
     cout << i << endl;
   结果输出数组a里的全部元素


23:通常,在输入字符,判断何时结束时一般都是按#键结束(或者
其他的一些按键) ,这样虽然大多数情况下不会有问题,但是
如果判断结束的字符是我们要输入的呢?这不就输入不了吗?如果输
入是来自文件·,则可以使用检测文件尾(EOF)来判断是否结
束输入,不过c++还可以用键盘模拟文件尾,过程是这样的,检测到
EOF后,cin将两位(eofbit和failbit)都设置为1。可以通过成员函
数eof()看eofbit是否被设置,如果检测到EOF,cin.eof()返回ture;
同样,用cin.fail()的话,返回true;一般使用较多的是fail(),因为f
ail()可以用于更多的实现


# include
using namespace std;
int main()
{   char ch;
    int count=0;
    cin.get(ch);   //cin不会识别空格,tab,换行等空白键
    while(cin.fail()==false)//当没有检测到EOF,循环
    { 
        count++;
        cout<         cin.get(ch);
    }
    cout<     //结束输入时,按下ctrl+z,然后按回车
    system("pause");
    return 0;
}


24:int *p=new int[size];
    delete []p;


25:double(*p)(double a, double b);
p = max;//函数名就是函数地址
double a = p(4, 5);//函数名是函数地址,指针p指向函数的地址,所以p应该和max有相同的
double b = (*p)(4, 5);//p是函数指针,*p就是函数
cout << "a=" << a << endl << "b=" << b << endl;
    结果a和b一样,这两种用法都可以
    如果声明了一个函数,又定义一个指向该函数的指针,可以使用c++11的·自动类型推断功能,这样
   就方便得多,上面的就可以写成
     auto p=max;而不用写成
        double(*p)(double a, double b);
p = max;
      了,但是auto只能用于单值初始化,而不能初始化列表--
   还可以用typedef简化
     typedef  double(*type)(double a, double b);
      type p;     


26: 内联函数(inline)
     内联函数相比普通函数,调用时相当于直接使用函数的副本,而不用去找到函数的地址执行函数,所以
   速度相对稍快点,不过计算机运行速度非常快,所以也快不了多少,而且内联函数更占用内存
   声明和定义时必须都加上inline关键字,而且内联函数不能递归,关于内联,了解即可,一般用的比较少
   c中使用宏-----内联代码的原始实现
   # define SQUARE(X) X*X
   这是通过字符串替换实现的,而不是传递参数
   SQRARE(5.0);  //可以实现
   SQUARE(1+2);  //得到的不是9,而是1+2*1+2
   SQUARE(a++);  //得到的是a++*a++;a会自增两次
   这里不是为了说如何使用宏,而是为了说明当用c的宏实现某些功能时,应转化为内联


27: ofstream继承了ostream,ostream中的一些方法:
   1)setf()
         让你能够设置各种格式化状态,例如,方法调用setf(ios_base::fixed);将对象置于使用定点表               示法的模式,setf(ios_base::showpoint);将置于显示小数点的模式,即使小数部分为0 
   2)precision()
          指定显示多少位小数(假定对象处于定点模式下)。
    所有这些设置都将一直保持不变,知道再次调用相应的方法重新设置它们。
   3)width()设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后将恢复到默认设置,默认的字段宽度为0
     函数file_it使用了两个调用 
     ios_base::fmtflags initial;
     initial=ios.setf(ios_base::fixed);//save initial formatting state
     ...
     iso.setf(initial); //restore initial formatting state
      方法setf()返回调用它之前有效的所有格式化设置,ios_base::fmtflags是存储这种信息所需的数据类型
     名称,因此,将返回值赋给initial将存储调用file_it()之前的格式化设置,然后便可以使用变量initial
     作为参数来调用setf(),将所有的格式化设置恢复到原来的值。


28:默认参数(声明时给默认值,定义时不要加,有些编译器会报错)
     例如函数声明void fun(int n=1);
     如果调用函数时忘了加参数,则默认传入的参数是1
      对于带参数列表的函数,必须从右向左添加默认值,例如
      int fun(int n,int m=2,int j=3);   //可以
      int fun(int n,int m=2,int j);     //不可以
       实参按从左到右的顺序依次赋给相应的实参,而不能跳过任何参数
       int n=fun(1,,4);//不可以


29:函数重载
     当调用函数时,如果未找到参数列表匹配的函数,c++会尝试使用标准类型转换强制进行匹配,如果     可以唯一匹配则匹配,否则若有多个可以
     匹配,将视为错误
     匹配函数时,并不区分const和非const变量
     但是将非const赋给const是合法的,反之则不行
     函数重载,不可以返回值类型不同,参数相同,但可以返回值类型,参数都不同
     函数重载不要滥用,仅当函数基本执行相同的任务,但是用不同形式的数据时,才采用函数重载,


30: cv-限定符:
    1)const
    2)volatile   (不稳定的;挥发性的;爆炸的)


                                31: 名称空间
    1)术语:
            a: 声明区域(declaration region)
            b: 潜在作用域(potential scope)
    2)名称空间
       用namespace创建名称空间
       ex: 
         namespace Jack{
             double pail;
             void fetch();
             int pal;
             struct Well{...};
         }
         namespaace Jill{
             double bucket(double n){...};         
             double fetch;
             int pal;
             struct Hill{...};
         }     
         名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中;名称空间是开放的,
        可以把名称加入到已有名称空间中,例如:
         namespace Jack{
             char *goose(const char *);
        }
         同样,若名称空间中为某函数提供了原型,可以再次使用该名称空间来提供该函数的代码
         namespace Jack{
            char *goose(const char *)
            {...}
          }
         如果使用using namespace Jack;
          局部变量会隐藏Jack里同名的变量,


31:初始化string对象的方式
     string s1;  s1为空串
     string s2("abc"); 用字符串字面值初始化s2
     string s3(s2); 将s3初始化为s2的一个副本
     string s4(n,'c'); 将s4初始化为字符'c'的n个副本
    string的常用操作:
      s.empty()      若为空串,返回true,否则返回false
      s.size()       返回s中字符的个数
    但是注意!!!!!!!!
      string s="ds"+"df";  这样是非法的,不允许


32: class内部的函数优先是使用内联函数的,即使我们自己没写inline,不过要是无法用内联函数就不会用内
     联函数编译了
                       
                                  c++远征之封装篇
33:                     内存分区
      栈区:int x=0;int *p=NULL;
      堆区:int *p=new int;
      全局区:存储全局变量及静态变量
      常量区: string str="hello";
      代码区:存储逻辑代码的二进制
     
34:                          初始化列表
     可以通过初始化列表的方式
      class Student
     {
       public:
             Student():m_strName("Jim"),m_iAge(19) //对于多个数据成员初始化,要用逗号隔开,赋值时
                                             //用括号
              {
                ...
               }
       private:
             string m_strName;
             int m_iAge;   
      };
       初始化列表优先于构造函数执行,即先给数据成员赋值,在调用构造函数,初始化列表只能用于构造函     数,这种方式初始化速度快,效率高,推荐使用这种方式,那么,既然构造函数完全可以做这种工作,为
     什么又要初始化列表呢?仅仅是为了效率高,速度快?不是的,举个例子:
      class Circle
      {
        public:
              Circle(){m_dPi=3.14;} //如果用这种方式初始化,编译器会报错
              Circle():m_dPi(3.14){} //但是初始化列表可以
        private:
              const double m_dPi;
       };         
      圆这个类中,Pi是不变的,因此用const,以后建议这样用
      Student(string name,int age):m_strName(name),m_iAge(age)
      {
          ...
       } 
 
35:                          拷贝构造函数
        class Student
     {
       public:
             Student(string name,int age):m_strName(name),m_iAge(age)
              {
                cout<<" Student(string name,int age):m_strName(name),m_iAge(age)"<                }
       private:
             string m_strName;
             int m_iAge;   
      };
      当定义了一个Student类,然后再main函数中:
       Student s1;
       Student s2=s1;
       Student s3(s1);
      这样实例化s1调用的是我们在类中定义构造函数,而s1和s3调用的是拷贝构造函数,因为自己没定义拷贝
      构造函数,所以只会打印一行"Student(string name,int age):m_strName(name),m_iAge(age)",而不是三行;如何定义拷贝函数呢?以这个Student为例:
      定义格式:类名(const 类名 &变量名){ }
        Student(const Student &s)
        {
            cout<<"Student(const Student &s)"<          }
       当要传递类实例化的对象的值时就会调用拷贝构造函数,比如定义一个函数void test(Student s){}
      这时由于是值传递,所以会调用拷贝构造函数


36:                       析构函数
     定义格式:~类名(){ }   //不加任何参数


37:                          对象成员
     即类的成员还是类,比如定义一个线段类,两点确定一条线段,所以线段应该有两个表示点的数据成员,     而点也是一个类
 
38:                        深拷贝与浅拷贝
      浅拷贝:只是简单赋值,比如:
      class Array
      {
       public:
              Array(){m_iCount=5;m_pArr=new int[m_iCount];}
              Array(const Array &arr)
              {
                 m_iCount=arr.m_iCount;
                 m_pArr=arr.m_ipArr;
               }
        private:
           int m_iCount;
           int *m_pArr;
       };
       main()中:
       Array arr1;
       Array arr2=arr1;
       这样浅拷贝后,arr1和arr2的成员m_pArr都指向同一块内存,这样显然不是我们的意图  
       
       深拷贝:当不是进行简单的值的拷贝,而是将堆中内存的数据也进行拷贝
       上述代码中拷贝构造函数应改为:
         Array(const Array &arr)
              {
                 m_iCount=arr.m_iCount;
                 m_pArr=new int[m_iCount];
                 for(int i=0;i                     m_pArr[i]=arr.m_pArr[i];
                 }


39: 对象指针
     其实说不说无所谓,没什么好说的,比如Coordinate类
      Coordinate *p=new Coordinate;  //不过这里用new会自动调用Coordinate的构造函数,而malloc则只是
                                      //分配一块内存
      p->m_iX=1;   //(*p).m_iX=1;
      p->m_iY=2;    //(*p).m_iY=2; 
      delete p;
       p=NULL;


40:this指针(就是指向自身数据的指针)
      比如Array这个类
     class Array{
      public: 
             Arrray(int len){m_iLen=ien;}
      private:
            int m_iLen;  
     }; 
      如何知道是将参数赋值给数据成员还是把数据成员赋值给参数呢?其实是通过this指针赋值的,虽然我们
      没有写this,但编译器会自动加上this,这样就算参数与数据成员同名也不会有问题
       class Array{
      public: 
             Arrray(int len){this->len=len;}
      private:
            int this->len;  
     }; 
      还有一种情况,因为类的代码只有一份,在代码区,即对象的数据是私有的,而成员函数(在代码区)是     共享的,当实例化多个对象时,如何分辨是哪个对象的调用函数呢?这也要用到this指针
         class Array{
      public: 
             Arrray(Array *this,int len){this->len=len;}
      private:
            int len;  
     }; 
       Array arr1(1);   ->Array arr1(this,1); ->this->len=len; 
       Array arr2(2);   ->Array arr2(this,2); ->this->len=len; 
       Array arr3(3);   ->Array arr3(this,3); ->this->len=len; 


41: 常对象成员和常成员函数
      常对象成员:比如定义一个坐标类Coordinate,再定义一个线段类Line,当实例化一个线段类后,不希望
      它的值被修改,可以用const修饰,
       class Line{
         public:   
              Line(int x1,int y1,int x2,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
               {
                   ...
                }
          private:
              const Coordinate m_coorA;  //常对象成员
              const Coordinate m_coorB;
       };
       常成员函数:
       比如Coordinate类
       class Coordinate{
         public:
                 Coordinate(int x,int y);//这里写的是声明,节省时间
                 void changeX(int x)const;
                  void changeY(int y)const;
         private:
                 int m_iX;成员的值
                 int m_iY;
        };
        对于常成员函数,不能改变数据成员的值,因为实际上是这样的:
           void changeX(const Coordinate *this,int x)
            {
               this->m_iX=x;
             }
            void changeX(int x)const; //这两个函数也是互为重载的
              void changeX(int x); 
         那么什么时候用常成员函数呢?如果定义了常成员函数,要使用的话,在前面加上const,即:
         const Coordinate coor(1,2);//这时coor是常对象
         coor.changeX(4);            //这里就是调用常成员函数了,即常对象调用常成员函数


42:常指针和常引用 
     以Coordinate类为例
         class Coordinate{
           public: 
                 Coordinate(int x,int y)
                  {
                       m_iX=x;
                       m_iY=y;
                   } 
                  int getX()
                 {
                      return m_iX;   
                  }
                  int getY()
                  {
                      return m_iY; 
                   }
                  void printinfo()const
                  {
                      cout<<"("<                    }
             private:
                  int m_iX;
                  int m_iY;
         };  
        int main()        
         {
               Coordinate coor1(3,5);
               const Coordinate &coor2=coor1;//常引用
               const Coordinate *p=&coor1;   //指向常对象的指针
               coor1.printinfo(); //正确
               coor2.getX();         //不可以,因为getX不是常成员函数                                           
               p->getX();           // 不可以,
               Coordinate * const p1=&coor;  //这里const修饰的是p1,表示p1是一个指向对象的常指针
               p1->getX();         //可以的 
               p1=p;              //不可以                     


               return 0;
          }


43:vector操作和string差不多,不过
vector::size_type这里必须指类型,否则是错误的;
    向vector中增加元素只能用成员函数push_back();索引只能访问其元素
    最好用string::size_type接收string的size函数的返回值和索引
    initializer_list和vectot不同的是,赋值时:
    initializer_ilst a={1,2,3};
    initializer_list b=a;
    结果是a,b共享元素,而且initializer_list对象的元素都是常量


                            c++远征之继承篇
1:比如定义了人类和工人类
    class Person{                         class Worker{
     public:                               public:
           void eat();                           void eat();
                                                 void Work(); //特有
    public:                                 private: 
           string m_sreName;                     string m_sreName;
           int m_iAge;                           int m_iAge;
    };                                           int m_iSalary; //特有  
                                            };
    如果这样写,代码量会大大增加;所以就有了继承
    因为工人类是人类,所以可以继承:
    class Worker:public Person
    {
      public:
void Work(); //特有


      private:
int m_iSalary;//特有
    };
     这样代码就大大简化了,这样实例化一个Worker对象后,访问的就都是Worker的数据成员和成员函数了
     在不涉及继承的时候,protected和private是一样的;注意构派生类造函数用初始化列表赋值时:
     Worker(string name="lanbo", int age=19, int salary=20000):Person(name,age),m_iSalary(salary)
     {....
      }
      Has a关系:是一种包含关系
      Is a 派生类继承了基类,就可以说派生了是一个基类,即Is a;比如工人类继承了人类,那么就是工人
       也是人,这种关系就是Is a,所以可以用子类的对象初始化父类的对象
         Soldier s1;
         Person p1=s1;    //反过来不可以
         这时p1和s1共有的成员(数据成员和成员函数)就会赋值给p1.而s1特有的就不会
       


2:                       函数的覆盖和隐藏
   隐藏:比如父类A和子类B,B继承了A,A中有ABC函数,同时B中也定义了同名到ABC函数,这时实例化一个B对           象,调用ABC函数时,调用的是子类的ABC函数,而不是父类的,这时父类的函数ABC就被隐藏(如果           数据成员同名也一样,也是隐藏)了,不过这种情况比较少见,因为父子成员同名没有什么意义 
         调用时:
          B b;
          b.ABC(); //调用的是B中的ABC
          b.A::ABC();  //这样就是调用A中的ABC,只能这样做 
   虚函数表指针:当我们实例化一个A的对象的时候(比如A a),在这个a中,除了有数据成员,还有一个虚
                数表指针(指向虚函数表),虚函数表中有指向虚函数入口地址的指针,这样就可以找到定义                的虚函数和入口地址
                当实例化B的时候(比如B b),如果B类没有定义和父类同名的虚函数,但是B可以继承A的虚函                数,b的虚函数表指针指向b的虚函数表,b的虚函数表里面也有一个指针,这个指针指向A的函               数入口地址,但是当B类也定义了和A同名的虚函数,实例化A的对象和上面一样,而b的虚函数               表和之前的是一样的,不过虚函数表里面指向同名函数的指针指向的是自己定义的虚函数的地                址,不是指向A的虚函数的地址,这就是函数的覆盖  
   可以通过计算对象的大小来证明虚函数表的存在:
   对象的的大小:类的数据成员的大小,不包括函数(如果类没有数据成员,大小为1而不是0,这样是为了标                 识其存在) 
 
  虚析构函数:值得注意的是,当从堆中用子类实例化父类,销毁时执行的是父类的析构函数,而不会执行子                 类的,这样就有可能造成内存泄漏,为解决这个问题,需要在父类的析构函数前加上virtual关
              键字,即成了虚析构函数,这样子类也会继承这个虚析构函数,所以子类可以不用加virtual,但
              建议还是写上
     virtual不能修饰普通函数(比如全局函数),静态成员函数,内联函数(如果修饰内联函数,计算机会
       忽略掉inline关键字,),构造函数
 
3:                          多继承和多重继承
   多重继承:比如士兵类继承人类,步兵(infantry)类继承士兵类,这就是多重继承
    class Person{};
    class Soldier:public Person
    {};
    class Infantry:public Soldier
     {};
     当实例化一个子类时,会依次执行父类的构造函数,比如:
     Soldier s;//会依次执行Person,Soldier,Infantry的构造函数
   多继承:比如有个一工人类,一个农民类,农民工类继承工人类和农民类,那么农民工类就是多继承
    class Worker{};
    class Farmer{};
    class MigrantWorker:public Worker,public Farmer
    {};
     如果实例化一个子类,会先执行哪个父类的构造函数呢?其实是按照子类继承的初始化列表顺序来的,由    
    于MrgrantWorker是先继承Worker,再继承Farmer,所以先执行Worker的构造函数,再执行Farmer的


4:   虚继承:有些比较复杂的继承既有多继承,又有多重继承,比如菱形继承,这里         人
             工人类和农民类分别继承了人类,而农民工又继承了工人类和农民类,    工人      农民                   那么问题来了,如果实例化一个农民工,岂不是继承了两次人类,这样肯                                                                         农民工
            定不是我们想要的,虽然不会报错,这就要用到虚继承了,这里工人和农民称
            为虚基类,分别加关键字virtual
             
                              c++远征之多态篇
1  多态(包括静态多态和动态多态):是指相同对象受到不同消息或不同对象收到相同消息时产生不同的动作
      静态多态(早绑定):比如定义了互为重载的几个函数,那么在编译的时候根据传入的参数就知道要用
                          哪个函数,这就是静态多态
      动态多态(晚绑定):比如定义一个Shap类,然后定义Rect(矩形)类和Circle(圆)类分别共有继承Shap
                          类,三个类中分都定义了各自的计算面积函数Calcarer(),然后分别用子类实例化
                          父类Shape *p1=new Circle;Shape *p2=new Rect;那么执行p1,p2的计算面积时,
                           调用的其实都是父类的函数,为此,需在父类的函数前加virtual修饰,即虚函,
                           数,建议子类中函数也加上virtual(虽然不加的话编译器会自动加上),这样执行
                           p1,p2的函数就分别是Circle和Rect的函数了,这样在运行阶段才知道执行哪种
                           函数就是晚绑定,即动态多态,这里注意要在Shaped的析构函数加上virtual,不                           然只会执行父类都析构函数,建议都加上virtual


2:纯虚函数:比如类Shape中
       class Shape
       {
         public:
                 virtual double caca();  //虚函数
                 {                       //虚函数表中的函数指针,如果函数是虚函数的话,相应指针的          //的值就是相应函数的地址,而虚函数表中对应纯虚函数的函数指针的值为0
   抽象类:
         含有纯虚函数的类叫做抽象类,抽象类不允许实例化对象
                         return 0;
                  }
                 virtual double calcPerimeter()=0; //纯虚函数
        };
    仅含纯虚函数的抽象类就是接口类(没有数据成员,仅有成员函数,而且成员函数都是纯虚函数,连构造,     析构函数都没),接口类更多到表达一种能了或协议     
                     
4:RTTI:运行时类型识别(Run Time Type Identification)
   关键字:typeid,dynamic_cast;
        举个例子:
        class Flyable{
         public:
                virtual void takeoff()=0;
                virtual void land()=0; 
         };
         class Bird:public Flyable
         {
           public:
                 void foraging(){...}   //forage 觅食
                 virtual void takeoff(){...}  
                 virtual void land(){....}          
            private: 
                 ...  
           };
         class Plan:public Flyable
         {
           public:
                  void carry(){...}
                  virtual void takeoff(){...}  
                  virtual void land(){....} 
           private:  
                   ...      
          };
         假如有一个函数void DoSomething(Flyable *p)
                      {
                          p->takeoff();
                          如果是Bird1,则觅食,
                          如果是Plane,则运输
                          p->land();
                       }
          这就要用到RTTI了
           void DoSomething(Flyable *p)
                      {
                          p->takeoff();
                          cout<                           if(typeid(*p)==typeid(Bird))
                          {
                                Bird *bird=dynamic_cast(p);
                                bird->foraging();
                           }    //是Plane到代码这里就不写了
                          p->land();
                       }
       dynamic_cast注意事项:
            只能用于指针和引用的转换
            要转换的类型2中必须包含虚函数
            转换成功,返回子类到地址,失败则返回NULL
       typeid注意事项:
            type_id返回一个type_info对象的引用
            如果想通过基类的指针获取派生类的数据类型,基类必须带有虚函数
            只能获取对象的实际类型
        想要知道一个数据时什么类型,比如type i;
         可以用typeid(i).name();这样就会打印出该变量的类型名


5:异常处理:对有可能发生异常的地方做出预见性的安排  
   关键字:try...catch...    
           throw
    大致的写法:
        void fun1(){
               .....
               throw 1;  //假设遇到某种异常,抛出一个整形数字11
          }
        int main()
        {
             try
             {
                 fun1();
              }
             catch(int){
                 ...  //对异常做相应处理
             }     
             return 0;
         }
     另外,try可以一对多
       try()
       {
              fun1();
        }
        catch(int)
       {   ...     }
        catch(double)
        {    ...   }
        catch(...) //如果前面的都不能处理跑出到异常,就会交给这里来处理,这里是非常野蛮的,实在处                   // 理不了才交给这里,算是最后的挣扎了
    { .....}
         //这里都是抛出一个数据值,捕获一个类型,也可以抛出值,捕获值,例如:
       char GetChar(const string &aStr,const int alndex)
       {
             if(alndex>=aStr.size())
             {
                  throw string("ivalid  index!");
              }
             return aStr[alndex];
         }
         string str("hello world");
         char ch;
         try{
              ch=GetChar(str,100);  //抛出了异常就不会执行下面的语句了
               cout<           }
          catch(string &aval)
         {
               cout<           }
     常见的异常:
            数据下标越界;
            除数为0;
            内存不足(情况较少);
    异常与多态: 
      可以定义一个异常的接口类:Exception
     然后定义多个细分的子类(HardwareErr,SizeErr,MemoryErr,NetworkErr等)继承Exception,那么当抛出      异常,就都可以用Exception来捕获了
      
                              
  
                              c++远征之模版篇:
1:友元函数和友元类(关键字friend)


  1):    友元函数
             |
       ______|______
       |           | 
   全局函数    成员函数


     a:友元全局函数
       class Coordinate{
         friend void printXY(Coordinate &c){   //传入的是引用,比指针传入效率跟高,速度更快,       提倡用引用
  
               cout<         }


       public:
           Coordinate(int x,int y){
                  m_iX=x;
                  m_iY=y;
            }


        private:
           int m_iX;
           int m_iY;
        };
      int main(){
      Coordinate coor(1,2);
      printXY(coor);
      return 0;
     }
     b:友元成员函数       
        # include
        using namespace std;
         class Time;       //一定要注意顺序
         class Match{
         public:
      void printTime(Time &t);  //这里要用到Time,所以声明 
                       //这里不能函数体,要在后面定义,不然报错


          };
         class Time{


      friend void Match::printTime(Time &t);
         public:
       Time(int x, int y, int z){
        m_iHour = x;
        m_iMinute = y;
       m_iSecond = z;
 }
          private:
       int m_iHour;
        int m_iMinute;
       int m_iSecond;
          };
           int main(){
Time t(19,45,5);
Match m;
m.printTime(t);
system("pause");
return 0;
         } 
        void Match::printTime(Time &t){


          cout << t.m_iHour << ":" << t.m_iMinute << ":" << t.m_iSecond << ":" <<          endl; 
          }








         并不推荐使用友元,因为虽然对于数据的直接访问虽然方便遍了,但在不小心改变数值后不易              察觉,风险与方便并存




  2) 友元类


         class Printinfo;
        class Coordinate{
  friend Printinfo;
       public:
  Coordinate(int x, int y){

m_iX = x;
m_iY = y;
 }
       private:
   int m_iX;
   int m_iY;


        };
       class Printinfo{


        public:
     Printinfo(int x,int y):coor(x,y){   //这里必须初始化列表

cout << "Printinfo" << endl;
          }
      void printxy(){

cout << coor.m_iX << " " << coor.m_iY << endl;
        }


       private:
    Coordinate coor;
            };
     Printinfo已经声明为Coordinate的友元类,就可以任意访问Coordinate的数据成员和成员函数了
    注意事项:    
       友元关系不可传递;
       友元关系的单向性; 
       友元声明的形式及数量不受限制
      (友元只是封装的补充,不得已而为之)
     2:静态数据成员不依赖对象而依赖于类存在,比如Tank tank;sizeof(tank)中不包括静态数据成员的大小       而且静态数据成员必须单独初始化,例如:
        class Tank
        {
            public:
                Tank(string code){
                     m_strCode=code;
                     s_iCount++;
                }
                ~Tank(){
                      s.iCount--;
                }
                 static int GetCount(){
                       return s_iCount;
                 }
                static int s_iCount;
            private:
                string m_strCode; //坦克编号
         };
          int Tank::iCount=0;
          可以通过类Tank::s_iCount直接访问s_iCount的值,也可以通过对象访问:
         cout<          cout<          Tank t1("1");
         Tank t2("2");
         cout<          cout<          普通的成员函数可以调用静态数据成员和静态成员函数,而静态成员函数不可以调用普通数据成员,         和普通成员函数,比如
          void fire(){
           cout<            cout<            static int GetCount()
           [
                m_strCode="01";      //这是不可以的,静态成员函数不会传入this指针,所以无法识别
                                       m_strCode,易知也不能给静态成员函数后面加const
               return s_iCount;     
           }




     3:运算符重载(本质:函数重载,关键字:operator)


        1)一元运算符重载(以++为例):


           a:友元函数重载(全局)


            class Coordinate{
              friend Coordinate &opetator-(coordinate &coor);//需要传入参数(前置)
             friend Coordinate operator++(Coordinate& c,int);//后置的话operator前不加引用,int是表                                                              //示后置的标识
   public:
             Coordinate(int x,int y)
            m_iX=x;
            m_iY=y;
             }
           private:
           int m_iX;
            int m_iY;
            };
          friend Coordinate &opetator-(coordinate &coor){
              coor.m_iX=-coor.m_iX;   
              coor.m_iY=-coot.m_iY;
              return *this;
              }
          friend Coordinate operator++(Coordinate& c,int){
Coordinate c1(c);
c.m_iX++;
c.m_iY++;
return c1;
   }
         使用时
           int main(){
           Coordinate coor(3,5);
           -coor;  //相当于operator-(coor);
           coor++;//operator++(coor,0); 默认是0,没意义,但能够表达出是调用后置++
           return 0;
          }


   b:成员函数重载:
      class Coordinate{
      public:
          Coordinate(int x,int y)
          m_iX=x;
           m_iY=y;
      }


       Coordinate &opetator++(){     //因为是一元运算符,所以不用传入参数
            注意前置++和后置++的区别
         ++m_iX;  
         ++m_iY;
         return *this;
      }
       Coordinate operator++(int){  //这里int只是作为一个标识,表明是后置,而且operator前不                                //加引用,不然cot<<(coor++).get(x)的结果是4


        Coordinate old(*this);
        m_iX++;
        m_iY++;
        return old;  
        }
       private:
        int m_iX;
         int m_iY;
        };
     使用时
     int main(){
      Coordinate coor(3,5);
       ++coor;  //前置  
              //相当于coor.operator++();即调用了该函数
       coor++;//  后置 coor.operator++(0);//这里系统给定的0没有意义,只是为了表示后置 


       return 0;
       }


2)二元运算符重载
  a:友元函数重载
  (-)减号:
    friend Coordinate operator-(const Coordinate &c1,const Coordinate &c2){

Coordinate temp(0, 0);
temp.m_iX = c1.m_iX -c2.m_iX;
temp.m_iY = c1.m_iY - c2.m_iY;
return temp;
}
   !!!注意,这里operator后面不是引用,以-为例,因为-运算符只是为了将两个变量相减之后的值赋给另一个
           变量,所以应该是值传递
   (<<)输出运算符:
        friend ostream& operator<<(ostream &c, const Coordinate &coor){//输出运算符应为一个           ostreaam的对象,故不能用成员函数
c << coor.m_iX << "," << coor.m_iY << endl;
return c;
}


   b:成员函数重载


     (+号:Coordinate operator+(const Coordinate &c){  //在加的过程当中是不希望相加的数变化         的,加const是一种设计上的规范
Coordinate temp(0, 0);
temp.m_iX = c.m_iX + m_iX;
temp.m_iY = c.m_iY + m_iY;
return temp;
}


    ([])索引运算符(不能用友元函数重载):
      int operatot[](int index){
     if(index==0)
         return m_iX;
     if(index==1)
         return m_iY;
      }


  实现时:
    int main(){
Coordinate c1(1, 2);
Coordinate c2(12,4);
Coordinate c(0, 0);
c= c1 + c2;
cout << c.getX() << "," << c.getY() << endl;
c2 = c - c1;
cout << c2.getX() << "," << c2.getY() << endl;
cout << (c1+c2);
cout << c1[0] << endl;
system("pause");
return 0;
         }


4:模版函数与模版类


   1)函数模版
   比如3个函数
       int max(int a,int b){return a>b?a:b;}
       char max(char a,char b){return a>b?a:b;}
       float max(float a,float b){return a>b?a:b;}
 
      如果是比较大的函数,这样(只是函数类型不同的函数)写起来很麻烦,这时就可以用到函数模版            了,当我们传入一个函数类型,系统根据传入的数据类型,返回相应的返回值,通过函数模版生产             的函数就是模版函数
     关键字:template(模版) typname class(这里的class不是表示类,而是表示数据类型)
     使用方法:


    a: 传入的是数据类型
      template  //可以换成typename,建议用typename,因为class与类同名
      T max(T a,T b) 
       {   return a>b?a:b;
              }                         // 函数模版
       int main(){
       int ival=max(100,99);//这里根据传入的类型自动实例化一个int类型的模版函数
       char cval=max('a','g');//这里就必须传入char型   
        return 0;
        }


    b:传入的是变量
      template
       void display(){
       cout<        }
      使用时:
       diaplay<10>();
      比较简单,就不再说了
     c:多个参数的话,用逗号隔开
      template
      void display(T a,T b)
      {
         cout<        } 
2)   类模版:在很多场合下,一个类被用到很多次,一般重次的地方只有数据类型不同,这个时候就要     用到类模版


     template
     class Myarray{
     public:
     void display(){    //类内定义和普通类一样
        ....
       }
      private:
         T *m_pArr;
      };
      template    //类外定义成员函数,每一个函数前都要写
       void MyArray::display(){
       ...
       }
    //实例化时
         MyAray arr;


     特别提醒:模版代码不能分离编译器不能写在多个.h,.cpp文件


5:STL(Standard Template Library)标准模版库(因为非常多,只列举一部分,其他的随用随学)
   1) voctor(向量)(可以把它看作一个数组,只不过相对于传统的数组,功能要强大锝多,可      以根据存储的元素的个数自动地变长或者缩短)
       
     a:本质:对数组的封装
     b:特点:读取能在常时间内完(无论是存10个数据还是10000个数据,都能很快找出我们想要的数据
     c:初始化vector对象的方式
       vector v1;       vector保存数据类型为T的对象;默认构造函数v1为空
       vector v2(v1);   v2是v1的一个副本;
       vector v3(n,i);  v3包含n个值为i的元素(比如n=2,i=9.即存储了2个9)
       vector v4(n);    v4包含有值初始化元素的n个副本(如初始化为0,n=1,即存储了1个0)
     d:vector常用函数
       empty()             判断向量是否为空,bool类型
       begin()             返回向量迭代器首元素
       end()               返回向量迭代器末元素的下一个元素
       chear()             清空向量
       front()             第一个数据
       back()              最后一个数据
       size()              获得向量数据大小
       push_back(enem)     将数据插入向量尾
       pop_back()          删除向量尾部数据 
     
     e:实现(记得加# include)
       int main(){
         vector ivec;
         ivec.push_back(10);
         cout<          ivec.pop_back();
         cout<          systEm("pause");
          return 0;    
        }
      e:遍历(随你使用哪种方法,不过有些情况只能使用迭代器)
       
        (常用遍历方法)
        for(int k=0;k              cout<        
        (用迭代器遍历)iterator(迭代器,迭代程序) 相当于一个指针
          int main(){
            vector::iterator citer = svec.begin();    //定义svec的迭代器,始化指向                                                                 //svec的起始位置
    for (; citer != svec.end(); citer++)//end()是最后一个元素的下一个元素,所以这样                                                  //写是没问题的  
 cout << *citer << endl;


           system("pause");
        }


/***************************************************************************************/
    ps:vector的功能比数组强大,但付出的代价是效率低,如果需要的是长度固定的数组,使用数组是更佳的,   选择但代价是不那么方便。为此,c++11新增了模版类array,也位于std中,与数组一样array对象的长度也       是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全,
   要创建array数组,需包含array头文件,  
/***************************************************************************************/
      array arr;//与创建vector变量不同的是,nElem不能是变量


  2)链表(插入速度快)(头文件#include
   list list1;
list1.push_back(1);
list1.push_back(2);
list1.push_back(3);
list1.push_back(4);
/*for (int j = 0; j< list1.size(); j++)   这样是不行的,必须使用迭代器
cout << list1.[j] << endl;*/
list::iterator ltor = list1.begin();
for (; ltor != list1.end(); ltor++)
cout << *ltor << endl;
cout << endl; 
  
  3)mapping(映射)(头文件# include)
             key                 value
             
             x1 <---------------> y1
             
     键      x2<----------------> y2         值
   
             x3<----------------->y3


      
      map m;     //通过映射定义一个映射的对象m,
map n;
pair p1(10, "shanghai");  //定义对象后,需要向对象中放若干个key-value
pair p2(20, "anhui");
pair p3("c", "beijing");   //通过pair定义若干对key-value
m.insert(p1);
m.insert(p2);
n.insert(p3);   //映射中没有push_back;
cout << m[10] << endl;
cout << n["c"] << endl;   //[]里也可以是字符串
map::iterator mtor = m.begin();
for (; mtor != m.end(); mtor++)
{
cout << mtor->first << endl;       //这里注意不是*mtor。因为映射中的              内容是一对的,用[]的话计算机不知道是哪个
cout << mtor->second << endl;
cout << endl;
}




   #除了顺序容器,标准库还提供了三种顺序容器适配器,queue,priority_pueue和stack。适配器是标准库中通用的概念,包括容器适配器,迭代器适配器和函数适配器,本质上,适配器是使一种事物的行为类似于另一种事物的行为的一种机制容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。例如:stack适配器可以使任何一种顺序容器以栈的方式工作
   使用适配器时,必须包含相应头文件
  # include  //stack adaptor
  # include  //both queue and priority_queue adaptors




//===============================泛型算法==============================================
# 大多数算法都在头文件algorithm中,标准库还在头文件numeric中定义了一组泛型算法。


   #只读算法


     *find:
        find(vi.cbegin(), vi.cend(), value);
        find接受三个参数,前两个是表示元素范围的迭代器第三个参数是一个值,如果在范围内找到         了想要的元素,则返回该该元素的迭代器,否则返回vi.cend();
 
     除了find外,标准库还定义了其他一些更为复杂的查找算法,其中一个是find_first_of函数,这个    算法带有两队迭代器参数来标记两段元素范围,在第一段范围内查找与第二段范围中任意元素匹配元     素,然后返回  一个迭代器,指向第一个匹配的元素,如果找不到匹配元素,则返回第一个范围内的     end迭代器,比roster1和roster2是两个存放名字的的list对象,可用find_first_of统计有多少个名     字同时出现在这两个列表中


   *accumulate:
     accumulate定义在头文件numeric中,接受三个参数,前两个指定了需要求和的元素的范围,第三个
    是和的初值,




   #操作两个序列的算法:


      *equal
        equal用于确定两个序列是否保存有相同的值,它将第一个序列中的每个元素与第二个序列中的       对应元素进行比较,如果所有元素都相等,则返回true,否则返回false
         此算法接受三个迭代器,前两个表示第一个序列的元素范围,第三个表示第二个序列的首元素:
       应保证第二个序列的元素数目至少和第一个的一样多
 
    
    #写容器元素的算法
     
     *fill函数
          fill(vec.begin(),vec.end(),1);//结果就是将第一,二个参数范围内的每个元素设置为给定          的值,即第三个参数的值


     *fill_n函数
          fill(vec.begin(),5,4);//将从vec开始位置往后5个元素设为指定值4
          //注意不要向空容器使用fill或fill_n


     *back_inserter(定义在iterator中)     
          一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器(insert iterator)插         入迭代器是一种向容器中添加元素的迭代器,
           back_inserter接受一个指向容器的引用,返回一个于该容器绑定的插入迭代器。当我们通过        此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中:
            
           vector vec;//空向量
           auto it=back_inserter(vec);//通过它赋值会将元素添加到vec中
           *it=42;//vec中现在有一个元素,值为42
 
        我们常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用,例如:
          
           vector vec;//空向量
           //正确:back_inserter创建一个插入迭代器,可用来向vec添加元素
           fill_n(back_inserter(vec),10,0);
          
        在每步迭代中,fill_n向给定序列的第一个元素赋值,由于我们传递的参数是back_inserter返       回的迭代器,因此每次赋值都会在vec上调用push_back。最终,这条fill_n调用语句向vec的末尾
       添加了10个元素,每个元素都是0




   #拷贝算法 
      *copy:
  
        copy接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。此算法将输        入范围中的元素拷贝到目的序列中。传递给copy目的序列至少包含与输入序列一样多的元素
       copy返回的是其目的位置迭代器(递增后)的值,即目的容器尾元素之后的位置
         
        int a1[]={0,1,2,3,4,5,6};
        int a2[sizeof(a1)/sizeof(*a1)];
        //ret指向拷贝到a2的尾元素之后的位置
        auto ret=copy(begin(a1),end(a1),a2);
          
      *replace
        
          replace算法读入一个序列,并将其中所有等于给定值的元素都改为另一个值,此算法接受4个
        参数:前两个是迭代器,表示输入序列,后两个一个是要搜索的值,另一个是新值。它将所有等        于第三个值的元素替换为第四个值
      
      *replace_copy
        
           如果我们希望保留原序列不变,可以调用replace_copy,此算法接受额外第三个参数,指出调         整后序列的保存位置:
            
            replace_copy(list.cbegin(),list.cend(),back_insertr(ivec),0,42);


            此调用后,list并未改变,ivec包含list的一份拷贝,只不过原来在list中值为0的元素在         ivec中都为42




    #重排容器元素的算法
        *某些算法会重排容器中元素的顺序,一个明显的例子是sort,调用sort会重排输入序列中的元        素,使之有序,它是利用元素类型的<运算法来实现排序的。
          假如,我们想分析一系列儿童故事中所用的词汇,使得每个单词只出现一次,而不管单词在任        意给定文档中到底出现了多少次...(见c++primer第五版p343)
          为了便于说明问题,我门将使用下面简单的故事作为输入:
             the quick red fox jumps over the slow red turtle 
          给定此输入,我们的程序应该生成如下vector:
             fox jumps over quick red slow the turtle


        !消除重复单词
             为了消除重复单词,首先将vector排序,使得重复的单词都相邻出现,一旦vector排序完            毕,我们就可以使用另一个成为unique的标准库算法来重排vector,使得不重复的元素出现            在vector的开始部分,由于算法不能执行容器的操作,我们将使用vector的erase成员来完            成真正的删除操作:
                //elim 消除的;
                //dup vt:打开,重复;adj:复制品
                void elimDups(vector &words)
        {
                 //按字典排序words,以便查找重复单词
                   sort(words.begin(),words.end());
                 //unique重排输入范围,使得每个单词只出现一次
    //排列在范围的前部,返回指向不重复区域之后一个位置的迭代器,如果容器没有重                 //复单词,则unique返回words.end()
                   auto end_unique=unique(words.begin(),words.end());
                 //使用向量操作erase删除重复单词
                   words.erase(end.unique,words.end());
                 }
                




# 定制操作 
      很多算法都会比较输入序列中的元素,默认情况下,这类算法使用元素类型都<或==运算符完成比   较。标准库还为这些算法定义了额外的版本,允许我们提供自己定义的操作来代替默认运算符。 
      例如,sort算法默认使用元素类型的<运算符,但可能我们希望的排列顺序与<所定义的顺序不同,   或是我们的序列可能保存的是未定义<运算符的元素类型,这时,需要重载sort的默认行为
  
     *向算法传递函数
        作为一个例子,假定希望在调用elimDups后打印vector的内容,此外还假定希望单词按其长度排      序,大小相同的在按字典排序。为了按长度重排vector,我们将使用sort的第二个版本,此版本是
      重载过的,它接受第三个参数,此参数是一个谓词
      
     *谓词
         谓词是一个可调用的表达式,其返回结果是一个能用做条件的值。标准库算法所使用的谓词分       为两类:一元谓词(unary predicate)和二元谓词(binary predicate)。接受一个二元谓词参数的     sort版本用该谓词代替<来比较元素
         可以将isShorter传递给sort
             bool isShorter(const string &s1,const string &s2)
             {
       return s1.size()               }
          
             //按长度由短至长排序
              sort(words.begin(),words.end(),isShorter);
          
      *排序算法
           在我们将words按大小重排的同时,还希望具有相同长度的元素按字典序列排列。为了保持相         同长度的单词按字典序列排列,可以使用stable_sort算法。这种稳定排序算法维持相等元素的         原有顺序
            stable_sort(words.begin(),words.end(),isShorter);




   # lambda表达式
        根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数,但      是,有时我们希望进行的操作需要更多参数,超出了算法对谓词的限制。一个相关的例子是,求一      个给定序列中大于等于一个给定长的单词有多少,此函数命令为biggies


          void biggies(vector &words,vector::size_type sz)
          {
                elimDups(words);//将words按字典排序,删除重复单词
                //按长度排序,长度相同的单词维持字典序
                stable_sort(words.begin(),words.end(),isShorter);
                
                //获取一个迭代器,指向第一个满足size()>=sz的元素
                //计算满足size>=sz的元素的数目
                //打印长度打与等于给定值的单词,空格隔开
           }
          
        问题在于如何在vector中寻找第一个大于等于给定长度的元素
        可以使用标准库find_if算法来查找第一个具有特定大小的元素,find_if接受一对迭代器,表示      一个范围。但与find不同的是,find_if的第三个参数是一个谓词。find_if算法对输入序列中的每      个元素调用给定的这个谓词,它返回第一个使谓词返回非0值的元素,如果不存在这样的元素,则      返回尾迭代器
         编写一个函数,令其接受一个string和一个长度,并返回一个bool值表示该string的长度是否        大于给定长度,是一件很容易的事,但是,find_if接受一元谓词--我们传递给find_if的任何函数
      都必须严格接受一个参数,以便能用来自输入序列的一个元素调用它。没有任何办法能传递给它第      二个参数来表示长度,为此,需要引入lambda
       
       *lambda
          到目前为止,我们使用过仅有的两种可调用的对象是函数和函数指针。还有其他两种可调用对        象:重载了函数调用运算符的类,以及lambda表达式,形式如下:
              [capture list](parameter list)->return type(function body)
          捕获列表是一个lambda所在函数中定义的局部变量的列表(通常为空):与普通函数不同,        lambda必须使用尾置返回来指定返回类型
           我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体
           
                 auto f=[]{return 42;};
           这里定义一个可调用对象f,它不接受参数,返回42;


            lambda的调用函数与普通函数的调用方式相同,都是使用调用运算符:
                 cout<       
           在lambda中忽略括号和参数列表等价于指定一个空参数列表,在此例中,当调用f时,参数            列表是空的。如果忽略返回类型,lambda根据函数中的代码推断出返回类型,如果函数体只是         一个return语句,则返回类型从返回的表达式类型推断而来,否则,返回类型为void
           //!!如果lambda和函数体包含任何第一return语句之外的内容,且未指定返回类型,则返回
           //void!!


       *向lambda传递参数
           [](const stsring &a,const string &b)
              {
                    return s.size()                } 
          可以使用此lambda来调用stable_sort;
           stable_sort(words.begin(),words.end(),
                       [](const string &a,const string &b)
                        {return a.size()            当stable需要比较两个元素时,它就会调用给定的这个lambda表达式
 


        *使用捕获列表
            [sz](const string &a)
                 {return a.size()>=sz;};
             
        *调用find_if
           
             使用上面捕获列表的lambda,可以查找第一个长度大于等于sz到元素
             //获取一个迭代器,指向第一个满足size()>=sx的元素
               auto wc=find_if(words.begin(),words.end(),
                                [sz](const string &a)
                                     {return a.size()>=sz;});
             这里对find_if的调用返回一个迭代器,指向第一个长度不小于给定参数sz的元素,如果这           样的元素不存在,则返回words.end()的一个拷贝


  
        *for_each算法
            此算法接受一个可调用对象,并对输入序列中每个元素调用对象:
              
               //打印长度大于等于给定值的单词,空格隔开
               for_each(wc,words.end(),
                        [](cosnt string &s)
             { cout<                cout<               //捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数              //之外声明的变量




     #lambda捕获和返回
         
  *值捕获
              变量的捕获方式也可以是值或引用。采用值捕获的前提是变量可以拷贝,与参数不同,被
            捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝
 
               void fun1()
               {
size_t v1=42;
                        auto f=[v1](){return v1;};
                        v1=0;
                        auto j=f();//j=42;f保存了创建它时v1的拷贝
                }
 
          *引用捕获
               
                void fun2()
                {
                  size_t v1-42;
                        auto f2=[&v1]{return v1};
                        v1=0;
                        auto j=f();//j=0;f2保存va的引用
                 }
               
           *隐世捕获
               &告诉编译器采用捕获引用方式,=则表示采用值捕获方式。例如:
                 
                   //sz为隐世捕获,值捕获方式
                    wc=find_if(words.begin(),words.end(),
                               [=](const string &s)
                                  { 
                                      return s.size()>=sz;
                                  });
                如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混用隐式捕获
               和显式捕获:
                      void biggies*(vector &words,vector::size_type sz,
                                     ostream &os=cout,char c=' ')
                       {
                             //其他处理与之前一样
                             //os显式捕获,引用捕获方式;c隐式捕获,值捕获方式
                            for_each(words.begin(),words.end(),
                             [&,c](const string &s)
                                 {os<                        }
                !!!当混合使用隐式和显式捕获时,显示捕获和隐式捕获必须是不同的方式,即,如果               隐式捕获时引用方式(使用了&),则显式捕获必须采用值方式
                                     


        * 指定lambda返回类型
              可以使用标准库transform算法和一个lambda来将一个序列中每个负数替换为其绝对值
            
               transform(vi.begin(),vi.end(),vi.begin(),
                   [](int i){return i<0?-i:i;};);
 
               transform接受三个迭代器和一个可调用对象,前两个表示输入序列,第三个表示目的位             置。如果将上面的return语句换成if,会报错,必须指定返回类型


               transform(vi.begin(),vi.end(),vi.begin(),
                   [](int i)->int{if(i<0) return -i:else return i;};


        *参数绑定
            如果lambda捕获列表为空,通常可以用函数来代替它。但是,对于捕获局部变量的lambda,
          用函数来替换它就不是那么容易了。比如在find_if调用中的lambda比较一个string和一个给          定大小,我们可以很容易的编写一个完成同样工作的函数
         
                 bool check_size(const string &s,string::size_type sz)
                 {
                      return s.size()>=sz;
                  }
   
              但是,我们不能用这个函数作为find_if的一个参数,find_if接受一个一元谓词,因此传            递给find_if的lambda使用捕获列表来保存sz,为了用check_size来代替lambda,必须解决如            何向sz形参传递一个参数的问题 


          *标准库bind函数
              我们可以解决向check_size传递一个长度参数的问题,方法是使用一个新的名为bind的标            准库函数,它定义在头文件functional中,可以将bind函数看作一个通用的函数适配器,它            接受一个可调用对象生成一个新的可调用对象来"适应"原对象的参数列表
               调用bind的一般形式为:
                   
                  auto newCallable=bind(callable,arg_list);


               其中,enwCallablebe本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对            应给定的callable的参数,即,当我们调用newCallable时,newCallable会调用callable,            并传递给它arg_list中的参数。
                arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是“占位符”              表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生            成的可调用对象中参数的位置:_1为newCallable第一个参数,_2为第二个参会,一次类推
       


        *绑定check_size的sz参数


              auto check6=bind(check_size,_1,6)
               
              此bind调用只有一个占位符表示check6只接受单一参数,占位符出现在arg_list的第一个            位置,表示check6的此参数对应check_size的第一个参数。此参数是一个const string &s,            因此,调用check6必须传递给他一个string 类型的参数,check6会将此参数传递给            check_size.
 
               string s="hello";
               bool b1=check6(s);//check(s)会调用check_size(s,6)
          
            使用bind可以将原来基于lambda的find_if调用替换为:
 
                auto wc=find_if(words.begin(),words.end(),
                                 bind(check_size,_1,sz));
            
            //_1,_2等_n都在命名空间placeholder里
    
         *bind的参数
             例如,假定f是一个可调用对象,它有5个参数,则下面对bind的调用:
              
                 //g是一个有两个参数的可调用对象 
                  auto g=bind(f,a,b,_2,c,_1);
           
             生成一个新的可调用对象,它有两个参数,分别用占位符_2,_1表示,这个新的可调用对象           将自己的参数作为第三个和第五个参数传递给f,f的第一个,第二个和第四个参数分别被绑定           到给定的值a,b,c上
              实际上,这个bind调用会将
       
                  g(param1,param2);
        
              映射为:
       
                   f(a,b,param2,c,param1)




        *用bind重排参数顺序
              下面是用bind重排参数顺序的一个具体例子,我们可以用bind颠倒isShorter的含义:
 
                  //按单词长度有短至长排序
                  sort(words.begin(),words.end(),isShorter);
                  //按单词长度有长至短排序
                  sort(words.begin(),words.end9),bind(isShorter,_2,_1));
 
              在第一个调用中,当sort需要比较两个元素A和B时,它会调用isShorter(A,B).
              而在第二个调用中,传递给isShorter的参数被交换了,就像调用isShorter(B,A)。




         *绑定引用参数
     默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与            lambda类似,有时对有些绑定的参数我们希望以引用方式传递,或是要绑定参数的类型无法            拷贝。例如:为了替换一个引用方式捕获ostream的lambda:
     
                  for_each(words.begin(),words.end(),
                           [&os,c](const string &s){os<  
              可以很容易地编写一个函数,完成同样工作
 
                   ostream &print(ostream &os,const string &s,char c) 
                   {
                           return os<                     } 


               但是,不能直接用bind来代替对os的捕获 


                    //错误:不能拷贝os
                    for_each(words.begin(),words.end(),
                              bind(print,os,_1,' '));
 
                原因在于bind拷贝其参数。如果我们希望传递给bind一个对象而又不是拷贝它,就必须
              使用标准库ref函数
          
                    for_each(words.begin(),words.end(),
                             bind(print,ref(os),_1,' '));
 
                函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的,标准库还有一个cref              函数,生成一个保存const引用的类,与bind一样,函数ref和cref都定义在functional中


      
  #再探迭代器(详细内容见c++primer第五版p358)
       除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器


          *插入迭代器
               这些迭代器被绑定到一个容器上,可用来向容器插入元素
               
                list list1 = { "one", "two", "three", "four" };
list list2, list3,list4;//空list


//list2应该是反的
copy(list1.begin(), list1.end(), front_inserter(list2));
copy(list1.begin(), list1.end(), inserter(list3,list3.begin()));
copy(list1.begin(), list1.end(), back_inserter(list4));
 
          
          *流迭代器
               这些迭代器被绑定到输入或输出流上,可用来遍历所关联的IO流
               
              istream_iterator操作:
                istream_iterator int_it(cin); //从cin读取int
                istream_iterator int_eof;     //尾后迭代器
                //int_eof被定义为空的istream_iterator,从而可以当作尾后迭代器来使用


                下面是一个用istream_iteratoron从标准输入读取数据,存入一个vector的例子:


                 istream_iterator int_iter(cin);//从cin读取int
  istream_iterator eof;//尾后迭代器
                 while(int_iter!=eof)
                      vec.push_back(*int_iter++);


                 //也可以写成这样:
                 istream_iterator int_iter(cin),eof;
                 vector vec(int_iterator,eof);


           ostream_iterator操作:
                 必须将ostream_iterator绑定到一个指定的流,不允许空的或表示尾后位置的                 ostream_iterator 


                 ostream_iterator out(os); out将类型为T的值写到输出流os中
                 ostream_iterator out(os,d); out将类型为T的值写到输出流中,每个值后面都输                                                出一个d,d指向一个空字符串结尾的字符数组
                 out=val; 用<<运算符将val写入到out所绑定的ostream中,val的类型必须与out可写的类型兼容
                 *out,++out,out++ 这些运算都是存在的,但不对out做任何事,每个                                                运算符都返回out
                  
我们可以用ostream_iterator来输出值的序列:


                        vector iVec = { 1, 3, 5, 7, 9 };
ostream_iterator out_iter(cout, " ");
for (auto e : iVec)
*out_iter++ = e;
cout << endl;
                 
                 值得注意的是,当我们向out_iter赋值时,可以忽略解引用和递增运算符,即,循环        可以写成下面这样


                         for(auto e:iVec)
                             out_iter=e;
                         cout<  
                  运算符*和++实际上对ostream_iterator对象不做任何事,因此忽略它们对我们的程                序没有任何影响,但是,推荐第一种形式,这种写法与其他迭代器的使用保持一致
                  也可以使用copy来打印iVec中的内容
                   copy(iVec.begin(),iVec.end(),out_iter);
                   cout<

              *使用流迭代器处理类类型 
                   我闷可以为任何定义了输入运算符(>>)的类型创建istream_iterator对象。类似的                    只要类型有输出运算符(<<),就可以为其定义ostream_iterator




          *反向迭代器           
               这些迭代器向后而不是向前移动,处理forward_list之外的标准容器都有反向迭代器


                   sort(vec.begin(),vec.end());//按“正常序”排序vec
                   sort(vec.rbegin(),vec.rend());//按逆序排序,最小元素在末尾
              我们只能从即支持++也支持--的迭代器来定义反向迭代器。流迭代器不支持递减运算符,              因为不可能在一个流中反向移动因此,不可能从一个forward_list或一个流迭代器创建反              向迭代器


            *反向迭代器和其他迭代器之间的关系
                  比如s是一个保存单词的字符串,以逗号隔开
                      string s = "Hello,world,I,am,coming";
                  可以很容易找到并输出第一个单词
                       auto comma = find(s.cbegin(), s.cend(), ',');
        cout << string(s.cbegin(), comma) << endl;
                  如果反向的呢?
                     auto rcomma = find(s.crbegin(), s.crend(), ',');
    cout << string(s.crbegin(), rcomma) << endl;
                  结果是输出gnimoc!!和我们预先想要的不符,我闷需要做的就是,将rcomma转换为普                通迭代器,我闷通过reverse_iterator的base成员函数来完成这一转换,此函数会返回
                对应的普通迭代器
                     cout<  
                  还是说一下吧,反向迭代器转换为普通迭代器后,它们并不是指向同一元素,只                     是对应范围相同
                   [s.crbegin(),rcomma)和[rcomma.base(),s.cend())
                   rbegin和cend也不是同一位置




          *移动迭代器
               这些专用的迭代器不是拷贝其中的元素而是移动它们


 
   # 特定容器算法
         与其它容器不同,链表类型list和forward_list定义了几个成员函数形式的算法,特别是,它       们定义了独有的sort,merge(合并),remove,reverse和unique,通用版本sort要求随机访问迭代器,
     因此不能用于list和forward_list,因为这两个类型分别提供双向迭代器和前向迭代器。 
         这些链表版本的算法的性能比对应的通用版本好得多,所以,对于list和forward_list,应该      优先使用成员函数版本而不是通用版本
       
           list和forward_list成员函数版本的算法 
        
           这些操作都返回void
           lst.merge(lst2)   将来自lst2的元素合并入lst.lst和lst2必须都是有序的
           lst.merge(lst2,comp) 元素将从lst2中删除,在合并之后,lst2变为空,第一个版本使用<
                                运算符;第二个版本使用给定的比较操作
 
           lst.remove(val) 调用erase删除掉与给定值相等(==)或令一元谓词为真的每个元素
           lst.remove_if(pred)


           lst.reverse()        反转lst中元素的顺序


           lst.sort() 使用<或给定比较操作排序元素
           lst.sort(comp)


           lst.unique() 调用erase删除同一个值的连续拷贝,第一个版本使用==,第二个版            lst.unique(pred)    本使用给定的二元谓词


      
    * splice(拼接,接合,结婚)成员
         链表类型还定义了splice算法,此算法是链表数据结构所特有的,因此不需要通用版本
         
          lst.splice(args)或flst.splice_after(args)
            
          (p,lst2)           p是一个指向lst中元素的迭代器,或一个指向flsg首前位置的迭代器
                                函数将lst2的所有元素移动到lst中p之前的位置或是flst中p之后的位置。将元素从lst2中删除。lst2的类型必须与lst或flst相同,且 不能是同一个链表
          
          (p,lst2,p2)       p2是一个指向lst2中位置的有效的迭代器。将p2指向的元素移动到  lst中,或将p2之后的(一个)元素移动到flst中。lst2可以是与lst或                                flst相同的链表


          (p,lst2,b,e) b和e必须表示lst2中的合法范围。将给定范围中的元素从lst2中移动到lst或flst。lst2与lst(或flst)可以是相同的链表,但是p不能指 向给定范围中的元素
           
     *链表特有的操作会改变容器
         
  


//=====================================关联容器======================================//
    #  关联容器支持高效的关键字查找和访问,两个主要的关联容器类型是map和set。标准库提供8个关      联容器。这8个容器间的不同体现在三个维度上:每个容器 (1)或者是一个map,或者是一个set;(2)       或者要求不重复的关键字,或者允许重复关键字;(3)按顺序保存元素,或者无序保存。
        允许重复关键字的容器的名字中都包含单词multi;不保持关键字按顺序存储的容器的名字都以     单词unordered开头。因此:一个unordered_multi_set是一个允许重复关键字,元素无序保存的集       合,而一个set则是要求不重复关键字,有序存储的集合;无序容器使用哈希函数来组织元素类型
     map和multimap定义在头文件map中;set和multiset定义在头文件set中;无序容器则定义在         头文件unordered_map和unordered_set中
                 
 
       * map
           map是关键字-值对的集合,通常被称为“关联数组”,只不过下标不是整数,每一个关键字-          对都是pair类型,pair保存两个名为first和second的(公有)数据成员,map所使用的pair用        first保存关键字,用second保存对应的值


       * set
           与之相对,set就是关键字的简单集合,当只是想知道一个值是否存在时,set是最有用的。        例如:一个企业可以定义一个名为bad_checks的set来保存那些曾经开过空头支票的人的名字。在接受一张支票之前,可以查询bad_checks来检查顾客的名字是否在其中


       * pair类型
            定义在头文件utility(实用;公共设施;通用的)里


             make_pair(v1,v2) 返回一个用v1和v2初始化的pare,pare的类型从v1和v2的类型推断出来


    * 关联容器操作
        
          key_type         此容器类型的关键字类型
          mapped_type 每个关键字关联的类型;只适用于map
          value_type 对于set,与key_type相同
          对于map,为pair
 
     * 关联容器迭代器
 
          需要记住,map的value_type是一个pair,可以改变pair的值,但是不能改变关键字成员的值
        set的迭代器都是const的,虽然定义了iterator和const_iterator,但是这两种迭代器都只能读         取set中的元素,而不能修改


     * 遍历关联容器
          map和set类型都支持begin和end操作
          
          auto map_it=word_count.cbegin();
          while(map_it!=word_count.cend()) 
          {
                 cout<first<<" occurs "<second<<" times"<                  ++map_it;
                 //输出是按字典顺序排列的
           }


          不能对set执行push_back,back_inserter等操作,因为是无序的




      * 添加元素
 
           vector ivec={2,4,6,8,2,4,6,8};  //ivec有8个元素
           set set; //空集合
           set.insert(ivec.cbegin(),ivec.cend());  //set现在有4个元素 
           set.insert({1,3,5,7,1,3,5,7}); //现在有8个元素 


         inserter有两个版本,分别接受一对迭代器,或是一个初始化器列表
    


      * 向map添加元素
          对map进行inserter操作时,注意map的元素类型是pair,通常,如果没有一个现成的pair对象        ,可以在inserter的参数列表中创建一个pair
 
           //向word_count插入word的4种方法
            
map word_count;
string word("Just hold on");
word_count.insert({ word, 1 });
word_count.insert(make_pair("Here", 1));
word_count.insert(pair("pair", 1));
word_count.insert(map::value_type("map", 1));
         
 
                           关联容器insert操作


         c.insert(v) v是value_type类型的对象;args用来构造一个元素
         c.emplace(args) 对于map和set,只有当元素的关键字不在c中时才插入(或构造)元素函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,
以及一个指示插入是否成的bool值
                                对于multiset和mutilset,总会插入(或构造)给定元素,并返回一个
            指向新元素的迭代器


      c.insert(b,e)b和e是迭代器,表示一个c::value_type类型值的范围;il是这种值的         c.insert(il) 花括号列表。函数返回void
       
         c.insert(p,v) 类似insert(v)(或emplace(args)),但将迭代器p作为一个提示,指        c.emplace(p,args) 出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素




     * 检测insert到返回值
            insert(或emplace)返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加        单一元素的insert和emplace版本返回一个pair,pair的first成员是一个迭代器,指向具有给         定关键字的元素;second成员是一个bool值,指出元素是否插入成功还是已经存在于容器中。         如果关键字已在容器中,则insert什么也不做,且返回值中的bool为false,如果关键字不存在         ,元素被插入到容器中,且bool值为true
            比如:
 
             mapword_count;
             string word;
    while(cin>>word)
      {
                auto ret=word_count.insert({word,1});
                 if(!ret.second)
                 ++ret.first->second;
              }


      * 向multiset和multilemap添加元素
           由于multi容器中的关键字不必唯一,所以在这些类型上调用insert总会插入一个元素            insert返回一个指向新元素的迭代器,无须返回bool值


 
      * 删除元素
            关联容器定义了三个版本的erase,和顺序容器一样,我们可以通过传递给erase一个迭代器        或者一个迭代器对来删除一个或一个元素范围。这两个版本和对应的顺序容器的操作非常相似       指定的元素被删除,函数返回void
           关联容器提供一个额外的erase操作,它接受一个key_type参数,此版本删除所有匹配给定关键字的元素(如果存在的话),返回实际删除的元素的数量
           
             从关联容器删除元素


          c.erase(k) 从c中删除每个关键字为k的元素,返回一个size_type值,指出删除的元素个数
          c.erase(p) 从c中删除迭代器p指定的元素。p必须指向c中一个真实元素,不能为
  c.end().返回一个指向p之后元素的迭代器或者c.end()  
          c.erase(b,e) 删除迭代器对b和e所表示的范围中的元素,返回e


      * map的下标操作
           map和unordered_map容器提供了下标运算符和一个对应的at函数。set类型不支持下标,因为       set中没有与关键字对应的值,元素本身就是关键字,因此,“获取一个与关键字相关联的值”       的操作就没有意义了。我们不能对一个multimap或一个unordered_multimap进行下标操作,因为       这些容器中可能有多个值与一个关键字相关联
            与其他下标运算符不同的是,如果关键字不存在,会为它创建一个元素并插入到map中,关      ,联值进行值初始化
 
                   map和unordered_map的下标操作
        c[k] 返回关键字为k的元素,如果k不在c中,添加一个关键字为k的元素,对其进行
  值初始化
        c.at(k) 访问关键字为k的元素,带参数检查;若k不在c中,抛出一个out_of_range异常




       * 使用下标操作的返回值
           当对map进行下标操作时,会得到一个mapped_type对象,但当解引用一个map迭代器时,会得   到一个value_type对象
 
       * 访问元素
    对于不允许重复关键字的容器,可能使用find还是count没什么区别。但对于允许重复关键  字的容器,count还会做更多的工作:如果元素存在容器中,他还会统计有多少个元素有相同  的关键字。如果不需要计数,最好使用find
              set iset={0,1,2,3,4};
              iset.find(1); //返回一个迭代器,指向key==1的元素
              iset.find(5); //返回iset.end()
              iset.count(1); //返回1
              iset.count(5); //返回0


               在一个关联容器中查找元素的操作
lower_bound和upper_bound不适用于无序容器 
         下标和at操作只适用于非const的map和unordered_map


         c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于k的元素
         c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于k的元素
         c.equal_range(k) 返回一个迭代器pair,表示关键字等于k的元素的范围。若k不存在,pair的两个成员均等于c.end()
 
      * equal_range函数
           此函数接受一个关键字,返回一个迭代器pair。若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置,若未找到匹配元素, 则两个迭代器都指向关键字可以插入的位置
             
                for(auto pos=authors.equal_range(search_item));
                     pos.first!=pos.second;++pos.first)
                 cout<second<



    # 无序容器
          管理桶
              无序容器在存储上组织为一组桶,每个桶保存0个或多个元素。无序容器使用一个哈希函   数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个    桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。如果容器允许重复关键字            ,所有具有相同关键字的元素也会再同一个桶中。因此,无序容器的性能依赖于哈希函数的             质量和桶的数量和大小。
               
                                  无序容器管理操作
              桶接口
              c.bucket_count()     正在使用的桶的数目
              c.max_bucket_count() 容器能容纳的最多的桶的数量
              c.bucket_size(n) 第n个桶中有多少个元素
              c.bucket(k) 关键字为k的元素在哪个桶


              桶迭代
              local_iterator 可以用来访问桶中元素的迭代器类型
              const_local_iterator
              c.begin(n),c.end(n) 桶n的首元素迭代器和尾后迭代器
              c.cbegin(n),c.cend(n)
               
              哈希策略
              c.load_factor() 每个桶的平均元素数量,返回float值
              c.max_load_factor() c试图维护的平均桶大小,返回float值。c会在需要时添加新的桶,以使得load_factor<=max_load_factor
              c.rehash(n)               重组存储,使得bucket_count>=n,且bucket_count>size/max_load_factor
              c.reserve(n) 重组存储,使得c可以保存n个元素且不必rehash


         * 无序容器对关键字类型的要求
              默认情况下,无序容器使用关键字类型的==运算符来比较元素,它们还使用一个    hash类型的对象来生成每个元素的哈希值。标准库为内置类型(包括指针)提供了
            hash模板。还为一些标准库类型,包括string和智能指针类型定义了hash。因此,我们可以   直接定义关键字是内置类型(包括指针类型)、string还是智能指针类型的无序容器
               但是,我们不能直接定义关键字类型为自定义类类型的无序容器。而必须提供我们自己   的hash模板版本。
               为了能将Sales_data用作关键字,我们需要提供函数来替代==运算符和哈希值计算函数
           
                 size_t hasher(const Sales_data &sd)
                 {
                         return hash()(sd.isbn());
                  } 
                 bool eqOp(const Sales_data *lhs,const Sales_data &rhs)
                 {
                          return lhs.isbn()==rhs.isbn();
                  } 
              
                我们使用这些函数来定义一个unordered_multiset
                
                    Using SD_multiset=unordered_multiset                                 decltype(hasher)*,decltype(eqOp)*>;
                   //参数是桶大小,哈希函数指针和相等性判断运算符指针
                    SD_multiset bookstore(42,hasher,eqOp);
    
                 如果我们的类定义了==运算符,则可以值重载哈希函数
                  
                    //使用FooHash生成哈希值;Foo必须有==运算符
                     unordered_set fooset(10,FooHash);








//======================================动态内存========================================//


# 动态内存与智能指针
      shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了    一个名为weak_ptr的伴随类,它时一种弱引用,指向shared_ptr所管理的对象,这三种类型都定义在     memory头文件中


        * shared_ptr类
             shared_ptr p1;
             shared_ptr> p2;


        if(p1&&p1->empty())
              *p1="Just";//如果p1指向一个空string,解引用p1,将一个新值赋给string


                        shared_ptr和unique_ptr都支持的操作
          shared_ptr sp 智能指针,可以指向类型为T的对象
          unique_ptr up
           
          p 将p用作一个条件判断,若p指向一个对象,则为true                         *p解引用p,获得它指向的对象


          p->mem 等价于(*p).mem


          p.get() 返回p中保存的指针,要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了


          swap(p,q) 交换p和q中的指针
          p.swap(q)      




                           shared_ptr独有的操作
          make_shared(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化对象
 shared_ptrp(q)p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必 须能转换为T*
          p=q p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减
  p的引用计数,递增q的引用计数;若p的引用计数为0,则将其管理的原内存释放
          p.unique() 若p.use_count()为1,返回true,否则返回false
          p.use_count() 返回与p共享对象的智能指针数量,可能很慢,主要用于调试
                           


       * make_shared函数
           
     //指向一个值为2的int的shared_ptr
              shared_ptr p=make_shared(2);
              //p2指向一个动态分配的空vector
              auto p3=make_shared>();


       * shared_ptr的拷贝和赋值
           当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。


       * shared_ptr自动销毁所管理的对象


       * 如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只需用其中一部分,要记           得用erase删除不再需要的那些元素
 
       * 内存耗尽
            虽然现代计算机通常配备大容量内存,但是自由空间被耗尽的情况还是有可能发生的。此时  new就会失败,会抛出bad_alloc异常
             
      //如果分配失败,new抛出std::bad_alloc
               int *p1=new int;
               //如果分配失败,new返回一个空指针
               int *p2=new (nothrow)int;
               //bad_alloc和nothrow都在头文件new中


       * shared_ptr和new结合使用
 
            shared_ptr p1(new double(2));
 
            接受指针参数的智能指针构造函数是explicit的,因此,必须使用直接初始化形式
      
            shared_ptr p2=new int(2);//错误的






                          定义和改变shared_ptr的其他方法


             shared_ptr p(q) p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T *类型


             shared_ptr p(u)         p从unique_ptr u那里接管了对象的所有权;将u置为空


             shared_ptr p(q,d)       p接管了内置指针q所指向的对象的所有权,q必须能转换为T
*类型。p将使用可调用对象d来代替delete


             shared_ptr p(p2,d) p是shared_ptr p2到拷贝,唯一区别是p将用可调用对象d来代替delete
           
             p.reset() 若p是唯一指向其对象的shared_ptr,reset会释放此对象。             p.reset(q)若传递了可选的参数内置指针q,会令p指向q,否则会将p置     p.reset(q,d)为空。若还传递了参数d,将会调用d而不是delete来释放q




      * 不要混合使用智能指针和普通指针
          
            shared_ptr p(new int(42));//引用计数为1
            process(p); //拷贝p会递增它的引用计数;在process中引用计数为2
            int i=*p;//正确,引用计数为1
           
            int *x(new int(1024));  //危险!x是一个普通指针,不是智能指针
            process(x); //错误,不能将一个int *转换为shared_ptr
            process(shared_ptr(x));//合法,但内存会被释放,process中引用计数为1
            int j=*x;//未定义的,x是一个空悬指针




       * 也不要使用get初始化另一个智能指针或为智能指针赋值
 
             智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象          此函数是为了这样一种情况设计的:我们需要向不能使用智能指针的代码传递一个内置指针          。使用get返回的指针的代码不能delete此指针


       *其他shared_ptr操作
            shared_ptr还定义了其他一些操作,我们可以用reset来将一个新的指针赋予一个 shared_ptr:
              
                p=new int(2); //错误:不能将一个指针赋予shared_ptr
                p.reset(new int(2)); //正确:p指向一个新对象
  
             与赋值类似,reset会更新引用计数,如果需要的话,会释放p指向的对象。reset成员经常 与unique一起使用,来控制多个shared_ptr共享的对象。在改变底层对象之前,我们会检查自 己是否是当前对象仅有的用户,如果不是,在改变之前要制作一份新的拷贝


               if(!p.unique())
                    p.reset(new string(*p)); //我们不是唯一用户:分配新的拷贝
               *p+=newVal; //现在我们知道自己是唯一的用户,可以改变对象的值


*智能指针和异常
              智能指针可以提供对动态内存分配的内存安全而又方便的管理,但这建立在正确使用的前  提下。为了正确使用智能指针,我们必须坚持一些基本规范:
              
               * 不使用相同的内置指针值初始化(或reset)多个智能指针
               * 不delete get()返回的指针
               * 不使用get()初始化或reset另一个智能指针
               * 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变        为无效了
               * 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器


       
     * unique_ptr 
          一个unique_ptr“拥有”它所指向的对象。与shared_ptr不同,某个时刻只能有又给unique_ptr指向一个给定的对象。与shared_ptr不同,没有类似make_shared的标准库函数返回        一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化形式:
 
          unique_ptr p1; //可以指向一个double的unique_ptr
          unique_ptr p2(new int(2)); //p2指向一个值为2的int


           由于一个unique_ptr拥有它所指向的对象因此不支持普通的拷贝或赋值操作


           
              unique_ptr操作
                       
             unique_ptr u1 空unique_ptr,可以指向类型为T的对象,u1会使用delete    unique_ptr u2 来释放它的指针:u2会使用一个类型为D的可调用对象来释放它的指针


             unique_ptr u(d) 空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
 
             u=nullptr 释放u指向的对象,将u置为空


             u.release() u放弃对指针的控制权,返回指针,并将u置为空


             u.reset() 释放u指向的对象


             u.reset(q) 如果提供了内置指针q,令u指向这个对象,否则将u置为空
 
             u.reset(nullptr)


          虽然我们不能拷贝或赋值unique_ptr,但可以通过release或reset将指针的所有权从一个(非const)unique_ptr转移到另一个unique:
           
               //将所有权从p1(指向string Stegosaurus)转移给p2
               unique_ptr p2(p1.release()); //release将p1置为空


               unique_ptr p3(new string("Trex"));
      //将所有权从p3转移给p2
               p2.reset(p3.release());//reset释放了p2原来指向的内存




         *传递unique_ptr参数和返回unique_ptr
              不能拷贝unique_ptr有规则有一个例外:我们可以拷贝或赋值一个将要被销毁的  unique_ptr。最常见的例子是从函数返回一个unique_ptr:
          
              unique_ptr clone(int p)
              {
                      //正确:从int *创建一个uniquet_ptt
                      return unique_ptr(new int(p));
               }
 
              还可以返回一个局部对象的拷贝
              unique_ptr clone(int p)
              {
                    unique_ptr ret(new int(p));
                     //...
                     return ret;
               }
              对于两端代码,编译器都知道要返回的对象将要被销毁,在此情况下,编译器执行一种特   殊的“拷贝”




      * 向unique_ptr传递删除器
           类似shared_ptr,unique_ptr默认情况下使用delete释放它指向的对象。与shared_ptr一样,我们可以重载一个unique_ptr中默认的删除器,但是,unique_ptr管理删除器的方式与shared_ptr不同,原因在以后介绍


               //p指向一个类型名为objT的对象,并使用一个delT的对象释放objT对象
               //它会调用一个名为fcn的delT类型对象
      unique_ptr p(new objT,fcn);


          
      * weak_ptr
          weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会释放,因此命名为”弱“指针,抓住了这种智能指针的“弱”共享对象的特点
 
                                 weak_ptr
          weak_ptr w 空weak_ptr可以指向类型为T的对象
        
          weak_ptr w(sp) 与shared_ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型 


          w=p p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象


 w.reset() 将w置为空


          w.use_count() 与w共享对象的shared_ptr的数量


 //expired 过期的,失效的
          w.expired() 若w.use_count()为0,返回true,否则返回false
     
          w.lock() 如果expired为true,返回一个看那个shared_ptr;否则返回一个指向w的对象的shared_ptr


           if(shared_ptr np=wp.lock()) //如果np不为空则条件成立
           {   //在if中,np与p共享对象
   }

                              指向数据的unique_ptr
 
           指向数组的unique_ptr不支持成员访问元算符(点和箭头运算符),其他操作不变


           unique_ptr u u可以指向一个动态分配的数组,数组元素类型为T
     
           unique_ptr u(p) u指向内置指针p所指向的动态分配的数组,p必需能转换为类型T *
  
           u[i] 返回u拥有的数组中位置i处的对象
                                u必须指向一个数组




          与unique_ptr不同,shared_ptr不直接支持管理动态数组。如果希望使用sharer_ptr管理一个      动态数组,必须提供自己定义的删除器
    
             shared_ptr sp(new int[10],[](int *p){delete []p;});
             sp.reset();//使用我们提供的lambda释放数组


          shared_ptr不直接支持动态数组管理这一特性会影响我们如何访问数组中的元素
             
              //shared_ptr未定义下标运算符,并且不支持指针的算术运算
              for(size_t i=0;i!=10;++i)
              {  
                   *(sp.get()+i)=i;
              }




     * allocator类
               标准库allocator类及算法
            allocator a 定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存
 
            a.allocate(n) 分配一段原始的,未构造的内存,保存n个类型为T的对象


            a.deallocate(p,n) 释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象,p必须是一个先前由allocate返回的指针,且n必须是p创建时所 要求的大小,在调用deallocate之后,用户必须对每个在这块内存中创建的对象调用destroy


            a.construct(p,args) p必须是一个类型为T *的指针,指向一块原始内存:arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象


            a.destroy(p) p为T*类型的指针,此算法对p所指向的对象执行析构函数




      *拷贝和填充未初始化内存的算法


                                    allocator算法
       这些函数在给定目的位置创建元素,而不是由系统分配内存给它们
 
           uninitialized_copy(b,e,b2) 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能容纳输 入序列中元素的拷贝


           uninitialized_copy(b,n,b2) 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中


           uninitialized_fill(b,e,t) 在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝
           
           uninitialized_fill_n(b,n,t) 从迭代器b指向的内存地址开始创建n个对象,b必须指向足              够大的未构造的原始内存,足够容纳给定数量的对象




        作为一个例子,假定有一个int的vector,希望将其内容拷贝到动态内存中。我们将分配一块比        vector中元素所占用空间大一倍的动态内存,然后将原vector中的元素拷贝到前一半,对后一般空       间用一个给定值进行补充
          
        //分配比vi中元素所占用空间大一倍的动态内存
        auto p=alloc.allocate(vi.size()*2);
        //q指向最后一个构造的元素之后的位置
        auto q=uninitialized_copy(vi.begin(),vi.end(),p);
        //剩余元素初始化为2
         unitialized_fill_n(q,vi.size(),2);




                   




 第3部分
 类设计者的工具


//==================================拷贝控制=======================================


  当定义一个类时我们显示或隐式地指定在此类型的对象拷贝、移动、赋值和销毁时做什么。一个类通过定义5中特殊的成员函数来控制这些操作,包括:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。
     拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么。
     拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。
     析构函数定义了当此类型对象销毁时做什么。
   我们称这些操作为拷贝控制操作


     * 拷贝初始化
 
           string dots(10,','); //直接初始化
           string s(dots); //直接初始化
           string s2=dots; //拷贝初始化
           string null_book="9-999-9999"; //拷贝初始化
           string nines=string(100,'9'); //拷贝初始化


         当使用直接初始化时,实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹 配的构造函数。当使用拷贝初始化时,要求编译器将右侧运算对象拷贝到正在创建的对象中,如 果需要的话还要进行类型转换
          拷贝初始化通常使用拷贝构造函数来完成,但是,如果一个类有一个移动构造函数,则拷贝初始化有时会使用移动构造函数而非拷贝构造函数来完成。




     * 使用=default
          我们可以通过将拷贝控制成员定义为=default来显示地要求编译器生成合成的版本


     * 阻止拷贝
           虽然大多数类应该定义(而且通常也的确定义了)拷贝构造函数和拷贝赋值运算符,但对某些类来说,这些操作没有合理的意义。此时必须采用某种机制阻止拷贝和赋值。例如,iostream
         类阻止了拷贝,以避免多个对象写入或读取相同的IO缓冲。为了阻止拷贝,看起来可能应该不定义拷贝控制成员。但是,这种策略是无效的:如果我们的类未定义这些操作,编译器为生成 它合成的版本


     * 定义删除的函数
           在新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝  。删除的函数时这样一种函数:我们虽然声明了它们,但不能以任何方式使用它们。


                struct NoCopy
               {
                    NoCopy()=default; //使用合成的默认构造函数
                    NoCopy(const NoCopy&)=delete; //阻止拷贝
                    Nocopy &operator=(const NoCopy&)=delete;//阻止赋值
                    ~NoCopy()=default; //使用合成的析构函数
   //其他成员
                };




      * private拷贝控制
           在新标准发布之前,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝:
       
                class coor
               {
public:


coor(int x, int y):m_iX(x), m_iY(y)
{
cout << "构造函数" << endl;
}


private:
coor(const coor&c1)
{
m_iX = c1.m_iX;
m_iY = c1.m_iY;
}
void print()const
{
cout << "(" << m_iX << "," << m_iY << ")" << endl;
}
private:
int m_iX;
int m_iY;
};


 
           希望阻止拷贝的类应该使用=delete来定义它们自己的拷贝构造函数和拷贝赋值运算符,而不应该将它们声明为private




       * 行为像值的类
          
              class HasPtr
             {
               public:
                    HasPtr(const std::string &s=std::string()):
                            ps(new std::string(s)),i(0){}
                     //对ps指向的string,每个HasPtr对象都有自己的拷贝
                     HasPtr(const HasPtr &p):
                      ps(new std::string(*p.ps)),i(p.i){}
                     HasPtr & operator=(const HasPtr &);
                     ~HasPtr(){delete ps;}
                private:
                      std::string *ps;
                      int i;
              };
       


              HasPtr & HasPtr::operator=(const HasPtr &rhs)
              { 
                    auto newp=new string(*rhs.ps); //拷贝底层string
                    delete ps; //释放旧内存
                    ps=newp; //从右侧运算对象拷贝数据到本对象
     i=rhs.i;
                    return *this;//返回本对象
               }


            !!关键概念:赋值运算符
             当你编写赋值运算符时,有两点要记住:
                 *如果将一个对象赋予它自身,赋值运算符必须能正确工作
                 *大多数赋值运算符组合了析构函数和拷贝构造函数的工作。
              当你编写一个赋值运算符时,一个好的模式是先将右侧运算对象拷贝到一个临时对象中。             当拷贝完成后,销毁左侧运算对象的现有成员就是安全的了,一旦左侧运算对象的资源被
              销毁,就只剩下将数据从临时对象拷贝到左侧运算对象的成员中了
             
               //这样编写赋值运算符时错误的!!
                HasPtr& HasPtr::operator=(const HasPtr& rhs)
                {
                       delete ps;//释放对象指向的string
                        //如果rhs和*this是同一个对象,我们就将从以释放的内存中拷贝数据!!
                       ps=new string(*(rhs.ps));
                       i=rsh.i;
                       return *this;
                 }


        * 行为像指针的类
               对于行为像指针的类,我们需要为其定义拷贝构造函数和拷贝赋值运算符,来拷贝指针   成员本身而不是它指向的string。我们的类任然需要自己的析构函数来释放接受string参数
            的构造函数分配的内存。但是,在本例中,析构函数不能单方面地释放关联的string。只有   当最后一个指向string的HasPtr销毁时,它才可以释放string
               另一个展现类似指针的行为的最好方法是使用shared_ptr来管理类中的资源。拷贝(或   赋值)一个shared_ptr会拷贝(赋值)shared_ptr所指向的指针。shared_ptr类会记录有多   少用户共享它所指向的对象。当没有用户使用对象时,shared_ptr类负责释放资源
               但是,有时我们希望直接管理资源,在这种情况下,使用引用计数就很有用了。为了说   明引用计数如何工作,我们将重新定义HasPtr,令其行为像指针一样,但不使用shared_ptr   ,而设计自己的引用计数


 
                class HasPtr
      {
public:
//构造函数分配新的string和新的计数器。将计数器置为1
HasPtr(const string &s = string()):


ps(new string(s)),i(0),use(new size_t(1)){}

//拷贝构造函数拷贝所有三个数据成员,并递增计数器
HasPtr(const HasPtr&p) :
ps(p.ps), i(p.i), use(p.use){++*use;};

HasPtr& operator=(const HasPtr&);
~HasPtr();

private:
string *ps;
int i;
size_t *use;//用来记录有多少个对象共享*ps的成员
};


HasPtr::~HasPtr()
{
if (--*use == 0)
{
delete ps;
delete use;
}
}


HasPtr& HasPtr::operator=(const HasPtr& rhs)
{
++*rhs.use;//递增右侧运算对象的引用计数
if (--*use == 0)//然后递减本对象的引用计数
{
delete ps; //如果没有其他用户
delete use; //释放本对象分配的成员
 }
 ps = rhs.ps;
i = rhs.i;
use = rhs.use;
return *this;
}




     * 交换操作

              这里的swap函数是类HasPtr的友元函数
inline void swap(HasPtr &p1, HasPtr &p2)
{
swap(p1.ps, p2.ps);//如果参数定义了自己的swap函数,则调用的
//自己的swap函数,否则是标准库std::swap函数
swap(p1.i, p2.i);
}


       * 在赋值运算符中使用swap
            定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了一种名为 拷贝并交 换 的技术。这种技术将左侧运算对象与右侧运算对象的一个副本进行交换:
         
        //注意rhs是按值传递的,意味着HasPtr的拷贝构造函数
//将右侧运算对象中的string拷贝到rhs
HasPtr& HasPtr::operator=(HasPtr rhs)
{
//交换左侧运算对象和局部变量rhs的内容
                        swap(*this,rhs); //rhs现在指向本对象曾经使用欧冠的内存
return *this; //rhs被销毁,从未delete了rhs中的指针
      }






        * 动态内存管理类(看书c++primerp525)
 
        * 对象移动
             标准库容器、string和shared_ptr类既支持移动也支持拷贝。IO类和unique_ptr类可以移          动但不能拷贝

          *右值引用
              为了支持移动操作,新标准引入了一种新的引用类型--右值引用。所谓右值引用就是必须   绑定到右值的引用。我们通过&&而不是&来获得右值引用。右值引用有一个重要的性质--只   能绑定到一个将要销毁的对象。


                int i=2;
                int &r=i; //正确,r引用i
                int &&rr=i; //错误,不能将一个右值引用绑定到一个左值上
                int &r2=i*2; //错误,i*2是一个右值
                const int &r3=i*2; //正确,可以将一个const的引用绑定到右值上
                int &&rr2=i*2; //正确
           




                int &&rr1=2;//正确
                int &&r2=rr1;//错误,rr1是变量,是左值




     * 标准move函数


          虽然不能将一个右值引用直接绑定到一个左值上,但我们可显示地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。


              int &&rr3=std::move(rr1); //正确








  # 重载运算与类型转换
          
        * 输入和输出运算符
            输入运算符必须处理输入可能失败的情况,而输出运算符不需要


        * 算术和关系运算符
              
             *相等运算符
             *关系运算符


        * 赋值运算符


        * 复合赋值运算符


        * 下标运算符


        * 递增和递减运算符


        * 成员访问运算符


        * 函数调用运算符
              如果类定义了调用运算符(),则该类的的对象称作函数对象。因为可以调用这种对象所以   我们说这鞋对象的"行为像函数一样"
             *lambda是函数对象


             * 标准库定义的函数对象
                 标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义      了一个执行命名操作的调用运算符。例如plus类定义了一个函数调用运算符用于对一对      运算对象执行+的操作;modulus类定义了一个调用运算符执行二院的%操作;equal_to类      执行==,等等
                  这些类都定义成了模板的形式
                   
                      plus intAdd; //可执行加法的函数对象
                      int sum=intAdd(2,3);
           
                       
                                  标准库函数对象


           算术 关系逻辑
           plus        equal_tological_and
           minus not_equal_tological_or
           multiplies greaterlogical_not
           divdides greater_eaual
           modulus less
           negate less_equal
           //均在头文件functional中         
     
          
       * 在算法中使用标准库函数对象
               表示运算符的函数对象类常用来替换算法中的默认运算符。比如,默认情况下排序算法    使用operator<将序列按照升序排列。如果要执行降序排列的话,我们可以传入一个    greater类型的对象。
                 sort(svec.begin(),svec.end(),greater());
 
                需要特别注意的是,标准库规定其函数对象对于指针同样适用。之前曾经介绍过比较两     个无关指针将产生未定义的行为,然而我们可能会希望通过比较指针的内存地址来sort指     针的vector。直接这么做将产生未定义的行为,因此我们可以使用一个标准库函数对象来     实现该目的
                
                 vector nameTable; //指针的vector
                 //错误:nameTable中的指针彼此之间没有关系,所以<将产生未定义的行为
                 sort(nameTable.begin,nameTable.end(),[](string *a,string *b)
                       {return a                  //正确:标准库规定指针的less是定义良好的 
                 sort(nameTable.begin(),nameTable.end(),less());


      * 可调用对象与function
             c++中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了  函数调用运算符的类


         *不同类型可能具有相同的调用形式
             
             //普通函数
             int add(int i,int j){ return i+j; }
    //lambda,其产生一个未命名的函数对象类
             auto mod=[](int i,int j){ return i%j;}
             //函数对象类
             struct divide{
               int operator()(int denominator,int divisor)
                { return denominator/divisor;}};


            上面这些可调用对象分别对其参数执行了不同的算术运算,尽管它们的类型各不相同,但是 共享一种调用形式
                int(int,int)


         * 函数表
              以使用一些可调用对象构建一个简单的桌面计算器,用于存储可调用对象的“指针”。当            程序需要执行某个特定的操作时,从表中查找该调用的函数。在c++语言中,函数表很容易   通过map来实现。此例中,我们使用一个表示运算符符号的string对象作为关键字;使用实   现运算符的函数作为值。
     
               //构建从运算符到函数指针的映射关系,其中函数接受两个int、返回一个int
               map


              //函数指针
               int add(int a, int b)
              {
          return a + b;
               }
  int(*p)(int a, int b) = add;











































 

你可能感兴趣的:(c/c++)