c++笔记

最近重新温习了一遍c++,把一些原本掌握不牢的语法记了下来。

引用

int n = 4;
int & setValue() {return n;}
int main()
{
    setValue() = 40;//setValue的返回值是引用,是一个左值
    cout << n << endl;
    return 0;
} // 输出: 40

const T & 与 T & 区别

T & 类型的引用或T类型的变量可以初始化 const T & 类型的引用。
const T 类型的常变量和const T & 类型的引用不能用来初始化 T & 类型的引用,除非进行强制类型转换

常量定义: const 与define

比起define,const具有类型,便于类型检查。

内联成员函数

class B
{
    void func1();//第一种方式
    void func2()        //第二种方式
    {
    };
}
inline void B::func1(){} //第一种方式
  • 优点:减少调用函数的开销
  • 缺点:增加代码量
  • 其他:声明inline不一定被内联,比如虚函数,递归函数。

函数缺省值

参数缺省值只能出现在函数的声明中,而不能出现在定义体中。

class A
{
    void init(int x = 0, int y = 0);
}
void A::init(int x, int y)
{
}

构造函数在数组中

class A
{
    A(){};
    A(int){};
    A(int, int){};
}

A test[2] = {1};//  test[0] 调用A(int)
                    test[1]调用A();

A test2[3] = {1, A(1,2)};//test2[0]调用A(int)
                         //test2[1]调用A(int,int)
                         //test2[2]调用A()

A *test3[3] = {new A(4), new A(1,2)};//同上

复制构造函数

形式 : X::X(X&) 或者 X::X(const X&)
后者能以常量对象作为参数

//初始化 
A c2(c1);
A c2 = c1;//是初始化,调用复制构造函数  
c2 = c1;// 是赋值 不调用复制构造函数

//函数参数为类的对象时候
//void func(A);
func(c1);   //函数参数为类的对象时候,调用复制构造函数

//函数的返回值为类A
A func(){A c3;return c3;} //调用复制构造函数,参数为c3

类型转换构造函数

class A
{
    public:
    A(int i) {}
    A(double r, double i) {}
}

A c1(1,2);  //调用第二个构造函数
A c2 = 12;  //调用第一个构造函数,初始化,不会生成临时对象
c1 = 1;     //调用第一个构造函数,临时生成对象A temp(1),然后执行 c1 = temp <-这是赋值

构造函数与析构函数的关系

构造函数:

  • 成员函数的一种
  • 名字和类相同
  • 可以有参数,不能有返回值
  • 可以有多个构造函数
  • 用来初始化对象
  • new会调用构造函数

析构函数:

  • 名字和类名相同
  • 函数名前面加~
  • 没有参数和返回值
  • 一个类最多一个析构函数
  • 对象消亡时自动调用,释放内存空间,每一个new对应一个delete
  • delete会调用析构函数

其他

  • 离对象最近的花括号就是对象的作用域(生命周期)
    如 { A c1; } //在花括号后对象被析构

  • 先被构造的对象最后被析构,static定义的对象例外,它在程序结束前被析构

静态成员

静态成员变量:

  • 一个类的多个对象共享一个静态成员变量
  • sizeof不计算静态成员变量。

    class A
    {
        int n;
        static int s;
    }//则 sizeof(A) = 4
    

静态成员函数:

  • 普通成员函数作用于某个对象,如c1.func,
    静态成员函数不具体作用某个对象,
    不需要通过对象就能访问

访问静态成员

  • 类名::成员名 A::func();
  • 对象名.成员名 c1.func();//形式和访问普通成员一样,但func并不是作用于c1的。
  • 指针->成员名 p->func();//同上
  • 引用.成员名 同上

其他

  • 静态成员变量本质上是全局变量,哪怕一个对象不存在,静态成员变量也存在
  • 静态成员函数本质上是全局函数
  • 设置静态成员的目的:将和某些类紧密相关的全局变量和函数写到类里面,看上去一个整体,易于维护和理解
  • 必须在定义类的文件中对静态变量进行一次声明或者初始化,否则编译能通过,链接不能通过
  • 静态成员函数不能访问非静态成员变量,也不能访问非静态成员函数

封闭类

封闭类:包含成员对象的类

class B
{
    int a;
    int b;
    public:
        B(int a, int b);
}
class A
{
    int temp;
    B b1;
    public:
        A(int temp, a, b);
}
A::A(int temp, int a, int b) : temp(temp), b1(a, b) {};

定义封闭类时候,确保每个成员对象都有构造函数进行初始化,可用初始化列表进行初始化。
初始化列表写在类定义中

生成封闭类对象时候

  • 先执行所有成员对象的构造函数
  • 再执行封闭类的构造函数

成员对象构造函数调用顺序

  • 和成员对象在类中的说明顺序一致
  • 与在成员初始化列表中出现的顺序无关

封闭类消亡时(先构造的后析构)

  • 先执行封闭类的析构函数
  • 再执行成员对象的析构函数

友元

友元函数

一个类的友元函数可以访问该类的私有成员

class B;//声明
class A
{
    public:
        void func1(B *p);
}
class B
{
    private:
        int temp;
    friend int func2(B arr[], int num);//普通非成员函数作为友元函数
    friend void A::func1(B *p);//一个类的成员函数作为另一个类的友元函数,函数名应该加类名限定
}

void A::func1(B *p)
{
    p->temp = 0;//访问private
}
int func2(B arr[], int num)
{
    for (int i = 0; i < num; i++)
        arr[i].temp = 0;//访问private
}

友元类

A是B的友元类:A的成员函数可以访问B的private

class B
{
    private:
        int temp;
    friend class A;
}
class A
{
    public
        B b1;
        void fun1();//如果没有友元类,只能访问B的public,有了友元类,可以访问B的private
}

友元类既不能传递也不能继承

  • B是A的友元类,C是B的友元类,但C不是A的友元类。
  • B是A的友元类,C继承B,C不是A的友元类

this指针

this指向调用该函数的对象

class A
{
    public:
        void print(){cout << "hello" << endl;}
} 

int main()
{
    A *p = NULL;
    p->print();
    return 0;
}//输出hello

相当于void print(struct A *this)和print(p)

然而下面这种情况是错的:

class A
{
    int i;
    public:
        void print(){cout << i << endl;}
} 

int main()
{
    A *p = NULL;
    p->print();
    return 0;
}//错误

相当于void print(struct A *this)和print(p)
由于p是空指针,不存在i,所以在输出i的时候会出错。

其他

  • 静态成员函数不能用this指针。
    因为静态成员函数并不具体作用于某个对象。
    因此,静态成员函数的真实的参数的个数就是程序中写出的参数个数(少了参数this指针)。

  • 一个对象的this指针并不是对象本身的一部分
    不会影响sizeof(对象)的结果。

  • this作用域是在类内部,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。
    也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。

常量

常量对象

  • 不允许在常量对象上调用非常量成员函数,甚至对不会修改对象的成员函数,也是如此。

常量成员函数

  • 编译器不允许声明常量的成员函数修改对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。
  • 对一般情况下,构造函数和析构函数不允许被声明成const。
  • 常量成员函数与普通成员函数可以重载

    • 普通对象自动调用普通成员函数
    • 常量对象自动调用常量成员函数

      //函数声明
      int test() const;
      int test();

常引用

  • 用对象的引用作为参数,可以减少复制构造函数的开销,常引用确保不会修改对象。

其他

  • 常量数据成员(常量对象和常量变量)以及被声明为引用的数据成员,都必须用成员初始化列表初始化,在构造函数中对这些类型的数据赋值是不允许的。

  • 尽管构造函数必须是非常量成员函数,但它仍然可用于初始化常量对象。
    常量对象的“常量性”是在构造函数完成了对象的初始化之后生效的,持续到调用了对象的析构函数。

运算符重载

返回值类型 operator 运算符 (形参表)
{
}

重载为普通函数

A operator+ (const A &a, const A &b)
{
}

重载为成员函数

A operator-(const A &b)
{
}

A a, b;
a - b;// a.operate-(b);

赋值运算符=只能重载为成员函数

String s1 = "hello";//调用构造函数
String s2;
s2 = "world";//调用赋值语句

s2 = s1;

- 浅拷贝。两个都指向hello的内存,world的内存不被使用。
- 深拷贝,将s1指向的内容 复制到 s2指向的地方

    String& String::oprator=(const String &s)
    {
        if (str == s.str) 
            return *this;   //如果没有这句 执行s=s会出错
        if (str) delete []str;
        if (s.str)
        {
            str = new char[strlen(s.str) + 1];
            strcpy(str, s.str);
        }   
        else
            str = NULL;
        return *this;
    }

oprator 返回值不能设置为 void,否则执行 a = b = c; 会出错

流插入运算符和流提取运算符的重载

ostream& ostream::operator<<(int n)
{
    ....//输出n的代码
    return *this;
}

实际应用,由于无法修改ostream类,所以一般定义普通函数进行重载,同时声明为友元函数

ostream & operator<<(ostream &o. const A &s)
{
    o << s.value;
        return o;
}

自增/自减运算符

  • 前置:
    • 重载为成员函数 T operator++() //自增自减运算符基本重载成员函数而不是普通函数
    • 重载为全局函数 T operator++(T)
  • 后置:

    • 重载为成员函数 T operator++(int) //参数int无实际意义,编译器将其初始化为0
    • 重载为全局函数 T operator++(T, int)

    A A::operator++() //前置++
    {
    n++;
    return *this;
    }
    A A::operator++(int k) //后置++
    {
    A tmp(*this);
    n++;
    return tmp;
    }

类型强制转换运算符重载

operator int(){return n;}
类型强制转换运算符重载

  • 不能写返回值类型
  • 因为其返回值类型与调用此函数的对象的类型相同

运算符重载注意事项

  • c++不允许定义新的运算符
  • 重载后的运算符应该满足日常习惯
    • 如 a + 5 和 5 + a
      a + 5 可以重载为成员函数
      5 + a 需要重载为普通函数并声明友元
    • 如a+b+c
      返回值类型应该是类的引用
  • 运算符重载不改变运算符的优先级
  • 一些运算符不能被重载 “.”,”.*”,”::”,”?:”,sizeof
  • 重载运算符(),[],->,=的时候,重载函数必须声明为类的成员函数
  • 书上推荐的一般经验:
    • 对于单目运算符,建议选择成员函数;
    • 对于运算符“=,(),[],->”只能作为成员函数;
    • 对于运算符“+ =,-=,/=,*=,&=,!=,~=,%=,<<=,>>=”建议重载为成员函数;
    • 对于其他运算符,建议重载为友元函数。

继承和派生类

  • 派生类拥有基类全部成员函数和成员变量
  • 在派生类的各个成员函数中,不能访问基类的private成员

    class 派生类名字:基类名
    {
        int v1;
        void func1();
    }
    
    
    class CBase
    {
    }
    class CDericed : public CBase
    {
        int v3;
        void func1();   //同名时隐藏基类的func1,仍然可以通过 CBase::func1() 调用基类的func1
    }
    

派生类对象的内存空间

派生类包含基类对象,且基类对象的存储位置位于派生类对象新增的成员变量之前

复合

类C中有成员变量a,成员变量a是类D的对象,则C,D是复合关系
避免循环定义,即C中含D的对象a,D中含C的对象b,但a,b有一个或者两个是指针就可以。
一般我们实例化两个指针,使a,b相互联系,又能保持独立性

访问范围说明符

  • private
    • 基类的成员函数
    • 基类的友元函数
  • protected

    • 基类的成员函数
    • 基类的友元函数
    • 派生类的成员函数可以访问当前对象的基类的protected

      class Base
      {
          private: int nPrivate;
          protected: int nProtected;
          public: int nPublic;
      };
      class Son : public Base
      {
          void func1()
          {
              nPublic = 1;//ok
              nPrivate = 1;//wrong
              nProtected = 1;ok
              Son f;
              f.nProtected = 1;//wrong,f不是调用func1函数的对象
          }
      }
      
  • public
    • 基类的成员函数
    • 基类的友元函数
    • 派生类的成员函数
    • 派生类的友元函数
    • 其他的函数

派生类的构造函数

构造函数名(形参表):基类名(基类构造函数实参表)
{
} 

- 由于派生类包含基类对象,执行派生类的构造函数之前,先执行基类的构造函数.再调用成员对象类的构造函数
- 由于无法访问基类的private,所以要用初始化列表调用基类的构造函数初始化基类的private
- 如果派生类的构造函数中,省略了基类构造函数,那么派生类的构造函数自动调用基类默认的构造函数(无参构造函数)
- 派生类的析构函数被执行时,执行完派生类的析构函数,自动先调用成员对象类的析构函数,再调用基类的析构函数

public继承的赋值兼容规则

Base是基类,Derived是派生类

    Derived b;
    Base a = b;//ok,由于派生类中包含基类,所以相当于将b中基类的部分拷贝给a
    Base &a = b;//ok
    Base *a = &b;//ok 基类在派生类存储空间的最前面,所以基类的首地址就是派生类的首地址

直接基类与间接基类

A是B的基类,B是C的基类,C是D的基类。
那么A是B的直接基类,是B,C的间接基类。

在声明派生类时,只需要列出它的直接基类

  • 派生类沿着类的层次自动向上继承它的间接基类。
  • 派生类的成员包括:
    • 派生类自己定义的成员
    • 直接基类中的所有成员
    • 所有间接基类的全部成员
  • 执行构造函数之前,先执行顶层间接基类的构造函数,往下执行直接基类的构造函数,析构函数则相反。

静态成员的继承

  • 父类的static变量和函数在派生类中依然可用,但是受访问性控制(比如,父类的private域中的就不可访问),而且对static变量来说,派生类和父类中的static变量是共用空间的
  • static函数没有“虚函数”一说。因为static函数实际上是加上了访问控制的全局函数

虚函数与多态

虚函数

class Base
{
    virtual int get();//虚函数
}
int Base::get(){}

- virtual关键字只用在类定义里的函数声明中,写函数体时不用。
- 构造函数和静态成员函数不能是虚函数
- 虚函数可以参与多态,普通函数不能
- 一旦基类的成员函数被声明为虚函数,其派生类中的同名函数自动成为虚函数(覆盖基类的同名虚函数)。派生类声明该函数时,可以加virtual也可以不加。

多态

  • 派生类的指针可以赋值给基类指针(或引用)。
  • 通过基类指针(或引用)调用基类和派生类中的同名虚函数
    • 若该指针指向一个基类对象,那么被调用的是基类的虚函数。
    • 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。
  • 非构造函数,非析构函数的成员函数中调用虚函数都是多态。

        class Base
        {
            public:
            void func2(){this->func();}
            virtual void func(){}
        };
        class Derived:public Base
        {
            public:
            virtual void func(){}
        };
    
        int main()
        {
            Base *p;
            Base a;
            Derived b;
            p = &a;
            p->func();//调用Base的func函数
            p = &b;
            p->func();//调用Derived的func函数    
            //若没有声明virtual,则上面两个都是调用基类的func函数
    
    
            //另外一个例子
            p = &b;
            p->fun2();//func2中调用了func,而这个func是Derived的func,因为this也是基类指针,也是多态
        }
    

纯虚函数与抽象类

virtual void func() = 0;
  • 纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。
  • 创建纯虚函数时就会将这个类变成抽象类,抽象类只能作为基类来派生新类使用,不允许创建对象
  • 可以用抽象类的指针(或引用)指向派生类的对象
  • 由抽象类派生的类,只有实现了抽象类的所有纯虚函数,才能摆脱抽象类的身份。
  • 成员函数可以调用纯虚函数。

虚函数表

每一个有虚函数的类(或有虚函数的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数指针。虚函数表中列出来该类的虚函数地址。多出来的4个字节就是用来放虚函数表的地址的。

        class Base
        {
            public:
            int i;
            void func(){}

        };//sizeof(Base) = 8
        class Derived:public Base
        {
            public:
            int n;
            virtual void func(){}
        };//sizeof(derived) = 12

多态实现的关键是虚函数表
多态的函数调用语句编译成了一系列的根据基类指针指向的对象中存放的虚函数表的地址,在虚函数表中查找到虚函数地址,并调用虚函数的命令

虚析构函数

Base *p = new Derived;
delete p;

问题:通过基类的指针删除派生类对象时候,只调用基类的析构函数。

解决:使用虚析构函数,就能在delete对象时,先调用派生类的析构函数,再调用基类的析构函数。

内存分配

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区

  • 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。
  • 堆,就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。堆可以动态地扩展和收缩。
  • 自由存储区,就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free 来结束自己的生命的。
  • 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放),在 C++ 里面没有这个区分了,他们共同占用同一块内存区。
  • 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)

堆与栈

void f() { int* p=newint[5]; }  

这条短短的一句话就包含了堆与栈,看到 new,我们首先就应该想到,我们分配了一块堆内存,那么指针 p 呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针 p。在程序会先确定在堆中分配内存的大小,然后调用 operator new 分配内存,然后返回这块内存的首地址,放入栈中

类的内存分配-总结

  • 类的函数是不算到sizeof中的,因为函数是代码,被各个对象共用,跟数据处理方式不同。对象中不必有函数指针,因为对象没必要知道它的各个函数的地址(调用函数的是其他代码而不是该对象)。
  • 空类的sizeof为1,因为一个空类也要实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都有独一无二的地址。
  • 静态成员不占内存,因为静态成员并不属于某个对象,sizeof取的是对象大小。
  • 一个虚函数表指针占4个字节。
  • 类的总大小也遵守类似class字节对齐规则。

字节对齐规则

  • 对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍
  • 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
  • 如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型

文件

数据层次 : 位 -> 字节 -> 记录/域
顺序文件 : 一个有限字符构成的顺序字符流。 将所有记录顺序地写入一个文件。

容器和算法

顺序容器

  • vector 动态数组
  • deque 双端队列
  • list 链表

关联容器

  • set/multset 集合

    • set不允许相同元素,multset中允许相同元素
  • map/multmap

    • map中存放元素有且仅有两个成员变量,first和second。
    • first相当于关键字second相当于值。
    • map根据first进行排序。map不允许first相同,multmap允许first相同。
  • 关联容器特点

    • 元素是排序
    • 插入任何元素,都按照相应的排序规则来确定位置
    • 查找性能好
    • 通常用AVL树实现,插入和查找都是O(log(N))

容器适配器

  • stack 栈
  • queue 队列
  • priority_queue 优先级队列

顺序容器和关联容器的成员函数

  • begin : 返回第一个元素的迭代器。
  • end : 返回最后一个元素后面的位置的迭代器。
  • rbegin : 返回最后一个元素的迭代器。
  • rend : 返回第一个元素前面位置的迭代器。
  • erase : 从容器中删除一个或几个元素。
  • clear : 从容器中删除所有元素。

顺序容器常用的成员函数

  • front : 返回第一个元素的引用。
  • back : 返回最后一个元素的引用。
  • push_back : 在容器末尾添加新元素。
  • pop_back : 删除容器末尾的元素。
  • erase : 删除迭代器指向的元素,或者删除一个区间,返回被删除元素后面哪个元素的迭代器。

迭代器

  • 用于指向顺序容器和关联容器中的元素。
  • 用法和指针类似
  • 有const 和 非const
  • 通过迭代器可以读取它指向的元素
  • 通过非const迭代器可以修改它指向的元素

  • 定义方法

    容器类名::iterator 变量名;
    容器类名::const_iterator 变量名;
    
  • 访问迭代器指向的元素

    *迭代器变量名
    
  • 具有++操作,同指针。

双向迭代器操作
list,set/multset,map/multmap

  • ++p , p++
  • –p , p–
  • *p
  • p = p1
  • p == p1

随机迭代器操作
vector,deque

  • p += i
  • p -= i
  • p + i
  • p - i
  • p[i]
  • p < p1 比较位置

不支持迭代器
stack,queue,proority_queue

算法

有的算法,如sort需要通过随机访问迭代器来访问容器中的元素,那么不支持随机访问迭代器的容器不支持该算法。

binary_search中的 == 是用小于实现的

函数对象

一个类重载了运算符()则该类的对象就成为了函数对象

class Sum
{
    public:
    int operator()(int a, int b)
    {
        return a + b;
    }
}
int main()
{
    Sum test;
    cout << test(1,2) <, endl;
    return 0;
}

multiset/set

区间都是左闭右开

set不可插入重复元素

//类需要重载 > 
class A { };

multiset<A,greater<A> > b;

b.insert(A());

multmap/map

元素是pair
可以用 []运算符 map[k]

  • 如果k存在,则表示关键字(first)为k的值的引用(second);
  • 如果k不存在,则插入k,second根据默认构造函数决定

强制类型转换

类型强制转换的重载不写返回值,转换的是什么类型,返回值就是什么类型

operator int(){}

static_cast

  1. 用来进行“自然”和低风险的转换,比如整形和实数型,字符型。
  2. 不能用来不同类型指针/引用之间转换;不能用于整形和指针之间的转换。

reinteroret_cast

  1. 用来各种不同类型的指针/引用之间的转换;指针和能容纳指针的整数类型(指针4个字节)之间的转换。

const_cast

  1. 用来进行去除const属性的转换。将const引用/指针转换为同类型非const引用/指针。

dynamic_cast

  1. 专门用于将多态基类的指针/引用转换为派生类的指针/引用,而且能够检查转换的安全性。
  2. 不能用于将非多态基类的指针/引用转换为派生类的指针/引用

C++11

统一的初始化方法

int arr[3]{1,2,3};
vector<int> iv{1,2,3};
map<int,string> mp{{1,"a"},{2,"b"}}
string str{"Hello World"}
int *p = new int[20]{1,2,3};//前三个元素1,2,3,后面的初始化为0

变量默认初始值

class A
{
    public:
    int n = 123;
}

auto

用于定义变量,编译器可以自动判断变量的类型
for(auto i = mp.begin(); i != mp.end(); ++i)
{
cout << i->first << i->second;
}

decktype

double x;
double y;
decltype(x) x2;//x2 is double
decltype((x)) x3 = y;//x3 is double&

智能指针shared_ptr

include <memory>
shared_ptr<T> ptr(new T);
shared_ptr<T> prt2(ptr);
ptr.reset();//放弃托管
prtr2.reset();//new出来的空间没有shared_ptr托管,自动消亡

- 多个shared_ptr对象可以托管一个指针,系统会维护一个托管计数,当无shared_ptr托管该指针时,delete该指针
- shared_ptr对象不能托管指向动态分配的数组的指针

    A *p = new A();
    shared_ptr<A> ptr(p);
    shared_ptr<A> ptr2;
    ptr2.reset(p);//并不增加ptr中对p的托管计数,程序结束后,p会被delete 2次

空指针nullptr

int *p1 = NULL;
int *p2 = nullptr;
shared_ptr<double> p3 = nullptr;
// p1 == p2 true ok
//  p3 == nullptr    true ok
//  p3 == p2  error  类型不匹配
//  p3 == NULL true ok
// int i = nullptr   error

for循环

//struct A{int n};
vector<A> st(arr, arr+5);
for(auto it: st)
    it.n = 10;//it遍历st的每个元素

右值引用和move语义

右值:不能出现赋值号的左边,一般说来,不能取地址的表达式,就是右值,能取地址的就是左值
目的:主要是提高程序运行的效率,减少需要进行深拷贝的对象进行深拷贝的次数

class A{}
A &r = A();//&r是左值,A()是A类型的无名变量,是右值,所以error
A &&r = A();//&&r是右值引用,可以引用右值


//move constructor
String(String &&s):str(s.str)
{
    s.str = new char[1];
    s.str[0] = 0;this->str指向s.str,s指向NULL
}

//move assigment
String& operator =(String &&s)
{
    if (str != s.str)
    {
        delete []str;
        str = s.str;
        s.str = new char[1];
        s.str[0] = 0;
    }
    return *this;
}


template <class T>
void MoveSwap(T &a, T &b)
{
    T tmp(move(a));//a是左值,std::move(a)是右值,所以调用参数为右值的move constructor
    a = move(b);//move assigment
    b = move(tmp);//move assigment
}
//用右值引用比深拷贝快,但是函数中会改变a,b的值

无序容器(哈希表)

//#include<unordered_map>

unordered_map<string, int> turingWinner;//string不能重复 和map一样
turingWinner.insert(make_pair("Dijkstra",1972));
turingWinner.insert(make_pair("Scott",1976));
turingWinner["Ritchie"] = 1983;

unordered_map<string,int>::iterator p = turingWinner.find(name);
//哈希表的插入和查询的时间复杂度几乎是常数

正则表达式

//#include<regex>
regex reg("b.?p.*k");
cout << regx_match("bopggk", reg) << endl;//输出1表示匹配成功

Lambda表达式

目的:只用一次的函数对象,不用专门为其编写一个类;只调用一次的简单函数,调用时才写出其函数体。

/*************
[外部变量访问方式说明符](参数表) ->返回值类型
{
    语句组
}

[=] 以传值的方式使用外部变量
[] 不使用任何外部变量
[&] 以引用的形式使用外部变量
[x, &y] x以传值,y以引用
[=, &x, &y] x,y以引用,其余以传值

“->返回值类型”可以省略,编译器自动判断返回值类型
***************/
cout << [](double a, duoble b){return a + b;}(1.2,2.5) << endl;//(1.2,2.5)不是lambda表达式的一部分,是给lambda表达式的实参。

auto sum = [](double a, duoble b){return a + b;}
cout << sum(1.2,2.5) << endl;

for_each(arr, arr+4, [](int){cout << x << " ";})

//实现递归求斐波那契数列
function<int(int)> fib = [&fib](int){return n <= 2 ? 1 : fib(n-1) + fib(n-2);};
//function<int(int)>表示返回值int,有一个int参数的函数
//不能写 auto fib =  …………  因为编译器无法判断 fib(n-1) + fib(n-2) 的类型
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值