C++笔记(一)

C++基础
1. C++能作什么
后台服务程序,可运行于windows/linux系统
    设备接入服务程序(VAG),流媒体服务器(VTDC)、联网网关(NCG)等。
桌面客户端程序
    电视墙客户端、视频巡逻客户端等
网页插件、通常为IE浏览器插件
    预览控件、回放控件、地图插件等。
各类SDK程序
    海康的设备网络SDK(HCNetSDK)、取流库、播放库等。
2. 面向对象-基本概念
    对象是对客观事物的抽象,类是对对象的抽象
    对象(Object)是类(class)的一个实例(Instance)
3. 封装-基本概念
封装是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
C语言的结构体不具备封装属性。
4. 继承/派生-基本概念
    类的继承,是新的类从已有类那里获得已有的特性,或从已有类产生新
    类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。
5. 继承/派生-注意问题
    1. 用一个类作基类,至关于声明一个该类的对象,因此必定要有类的定义,只有声明是不行的
    class emploee;  //只有声明 没有定义
    class manager:public emploee{}; //不行, 没有emploee定义
    2. 派生类可使用基类中全部public和protected的成员
    3. 类对象的构造是自下向上的,首先是基类,然后是成员,再后才是派生类自己,类对象的销毁恰好相反。
    4. *构造函数和析构函数没法继承,所以在派生类中须要本身对基类的构造函数进行调用。
    5. 构造函数原则:
        1)子类没有定义构造方法,系统默认调用父类的无参数构造方法。
        2)子类定义了构造方法(不论无参数仍是带参数),若未显示调用父类的构造方法,
        首先执行父类无参数的构造方法,而后执行本身的构造方法。
        3)子类定义了构造方法(不论无参数仍是带参数),若未显示调用父类的构造方法,
        可是父类只定义了有参构造函数,则会出错(若是父类只有有参数的构造方法,则子类
        必须显示调用此带参构造方法。
        4)子类在构造函数中显示带哦用父类带参数的构造方法,须要初始化父类成员对象的方法。
6. 重载/多态
    多态指发出一样的消息被不一样类型的对象接收时有可能致使彻底不一样的行为。
    多态性是一种实现“一种接口,多种方法“的技术
    重载也是一种多态,不过是一种特殊形态的多态,是编译时决定的静态多态。(编译时多态)
    多态的三大特征:重写、继承、父类指针指向子类对象(运行时多态)
    
    函数重载:在C++程序中 ,将语义、功能类似的几个函数用同一个名字表示。
    重载发生在同一个做用域中。
    运算符重载:在C++语言中,用关键字operator加上运算符来表示函数。
    虚函数:是C++实现统一接口的途径,在基类中定义具备通用接口(泛化)的虚函数,在派生类中将虚函数实现(特化)。
7. 为何使用函数重载?
    1. 便于记忆,提升函数易用性。
    2. 类的构造函数须要重载机制。
8. 重载如何区分,靠参数,编译器根据参数为每一个重载函数分配不一样的标识符。
9. 运算符重载的规则
    1)只能重载C++中已有的运算符
    2)类属关系运算符“.”、做用域分辨符“::”、成员指针运算符“*”、sizeof运算符
    和三目运算符“?:”不能重载
    3)重载以后的运算符的优先级和结合性都不能变,单目运算符只能重载为单目运算符,双目运算符
    只能重载为双目运算符。
    4)运算符重载后的功能应该与原有功能相相似。
    5)重载运算符含义必须清楚,不能有二义性。
10. 虚函数与重载的关系
    1)虚函数的重载要求函数名、返回类型、参数个数、参数类型以及参数的顺序都与基类中原型彻底相同,
    不能有任何的不一样。
    2)通常函数的重载,只要函数名相同便可,函数的返回类型以及所带的参数能够不一样。
11. 虚函数规则
    1)只有成员函数才能声明为虚函数,由于虚函数仅适用于有继承关系的类对象,因此普通函数不能声明为虚函数。
    2)虚函数必须是非静态成员函数,由于静态成员函数不受限于某个对象。
    3)内联函数不能声明为虚函数,由于内联函数不能在运行中动态的肯定其位置。
    4)构造函数不能声明为虚函数,多态是指不一样的对象对同一消息有不一样的行为特性。
    虚函数做为运行过程当中多态的基础,主要是针对对象的,而构造函数是在对象产生以前
    运行的,所以,虚构造函数是没有意义的。
    5)析构函数能够声明为虚函数,析构函数的功能是在该类对象消亡以前进行一些必要的清理工做,析构函数没有类型,
    也没有参数,和普通成员函数相比,虚析构函数状况略微简单些。
12. 纯虚函数
    概念:一个在基类中没有定义具体操做内容的虚函数,要求各派生类根据实际须要定义本身的实现内容。
    声明形式为:virtual <函数类型> <函数名> (参数表) = 0
    抽象类:
        主要做用是经过它为一个类族创建一个公共接口,使他们可以更有效的发挥多态特性。
        一个抽象类至少带有一个纯虚函数。
13. 纯虚函数规则
    1)抽象类只能用做其余类的基类,不能创建抽象类对象
    抽象类处于继承层次结构的较上层,一个抽象类自身没法实例化,只能经过继承机制,生成抽象类的非抽象派生类,而后再实例化。
    2)抽象类不能用做参数类型、函数返回值或显示转换的类型。
    3)能够声明一个抽象类的指针和引用。
14. 单例对象
    1. 确保一个类只有一个实例被创建
    2. 提供了一个对象的全局访问点,被全部程序模块共享
    为了防止在外部调用累的构造函数而构造实例,须要将构造函数的访问权限标记为protect或private,最后,
    须要提供要给全局访问点,就须要在类中定义一个static函数,返回在类内部惟一构造的实例。
15. 静态对象
    局部静态对象:局部静态变量在程序第一次执行定义语句时初始化,直到程序终止被销毁,其生命周期贯穿函数
    调用及以后的时间。
    int count_calls(){
        static int ctr = 0; //调用结束后仍然存在
        renturn ++ctr;
    }
    类的静态成员:在类的成员变量前加上static,可使该成员与类关联在一块儿,从而不与任何对象绑定,
    被该类及其派生类的全部对象共享。
    类的静态成员不在建立类的对象时被定义,所以也不是由类的构造函数初始化。通常来讲在类的外部定义和初始化类的静态成员。
    class bass{
    public:
        static int _num; //类内声明
    }
    int base::_num = 0;  //类外定义
    
    class derived:public base{
    };
    
    main()
    {
    base a;
    derived b;
    a._num++;
    cout << "base class static data number _num is" << a._num << endl;
    b._num++;
    cout << "derived class static data number _num is" << b._num << endl;
    }//结果为1, 2; 可见派生类与基类共用一个静态数据成员
    
    静态成员变量能够是类自己的对象,普通成员变量不能够;
    class base{
    public:
    static base _object1; //正确,静态数据成员
    base _object2; //错误
    base *pObject; // 正确,指针
    base &mObject; // 正确,引用
    }
    
    静态成员变量能够做为成员函数的默认参数,普通变量不能够
    class base{
    public:
    static int _staticVar;
    int _var;
    void foo1(int i = _staticVar);//正确, _staticVar为静态数据成员
    void foo2(int i =  _var);//错误,_var为普通数据成员
    }
16.静态成员函数
    1)静态成员函数不与类的对象相关联,即函数内不含this指针,所以只能访问类的静态成员变量与静态成员函数,不能调用类内非静态成员。
    2)与非静态成员函数相比,静态成员函数不是须要等待类的对象初始化,在没有对象被初始化时也能够直接经过类进行调用。
    3)静态成员函数的地址可用普通函数指针存储,而普通成员函数地址须要用类成员函数指针来存储。
    class base{
    static int fun1();
    int fun2();
    };
    int (*pf1)() = &base::func1;//普通函数指针
    int (base::*pf2)() = &base::func2;//成员函数指针
    
17. 指针的基本概念
    1)引用是某一变量的一个别名,对引用的操做与对变量直接操做彻底同样。
    2)指针是指向另外一种类型的复合类型,保存制定对象的地址。经过指针对象能够实现对其余对象的间接访问,这一点与引用相似。
    3)指针依赖于指向的类型,其不只所指向对象在内存空间的位置,还包含所指向对象占用的内存空间大小。
    4)相对于引用,指针自己是一个对象,声明时能够不赋初值,也能够在生命周期内更改所指向的对象。
18. 指针常量与常量指针
    指针常量:指针里面所存储的内容(内存地址)是常量,不能改变 
    声明格式为:数据类型 *const 指针变量
    常量指针:指针指向的是常量,他指向的内容不能发生改变  
    声明格式为:数据类型 const *指针变量
19. 数组指针与指针数组
    数组指针:即指向数组的指针,如 int(*a)[4]
    指针数组:用于存储指针的数组,也就是数组元素都是指针,如 int* a[4]
数组指针:
    通常来讲编译器会直接将数组转换为指针,便可以将数组名直接看成数组第一个成员的指针来使用。
指针数组;
    因为指针也是对象,所以能够声明指针的数组,用来保存一列相同对象的集合。
    定义指向int类型的指针的数组,其中[]优先级更高,即p为一个n维数组,其次为*,即为指针数组,最后为int,即为指向int类型的指针的数组。
    结合数组指针的内容,能够将二位数组的每一行看成指针,则二维数组与指针的数组相似。
    int *p[3];
    int a[3][4];
    for (int i = 0; i < 3; i++)
        p[i] = a[i];
20. 指针参数
    1)指针能够做为函数的参数,传递给函数体内的代码使用。当指针做为函数形参时,其行为
    与其余非引用类型同样,实参的值会被拷贝给形参。因为指针能够间接访问指向的对象,
    所以经过被拷贝的形参能够指向实参所指向的对象,函数体中对所指对象的修改能够被保留。
    2)当所指的对象占用内存控件较大时,经过指针形参能够避免对象的拷贝操做,转为指针的拷贝操做,提升效率。
21. 函数指针
    函数指针指向函数而非对象,其指向的类型由函数的返回类型和形参类型共同决定。
    bool (*pf)(int, int*); //定义了一个函数指针,指向返回类型为bool,形参为int和int*的函数。
    函数名会被自动看成函数指针来处理
    bool chk(int, int*){return false;}
    pf = chk;
    函数指针做为指针,能够成为其余函数的形参,也能够做为其余函数的返回值。
    int compare(int, int, int(*pf)(int, int)); //定义了函数compare(int, int, int(*pf)(int, int))
    int (*f1(int))(int*, int);//定义了函数f1, 形参为int, 返回类型为int(*)(int*, int)
22. this指针
    在每一个成员函数中都包含了一个特殊的指针,称为this指针,他是指向本类对象的指针,他的值是当前被
    调用成员函数所在对象的起始地址。
23. 内存分区
    栈(stack)
    局部变量存储区,函数内部、实参的局部变量,由编译器负责分配,函数结束,栈变量失效。
    堆(heap)
    用户自行分配,使用new/delete、deelte[]进行释放(malloc\calloc\relloc\  free), 成对出现,申请则释放
    不然形成内存泄露。
    全局区/静态区(global static area)
    全局变量和静态变量存放区,程序一经编译好,该区域便存在。全局变量和静态变量编译器会给这些变量自动初始化赋值。
    因为全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。
    常量区
    这是一块比较特殊的存储区,专门存储不能修改的常量,如字符串常量。
    代码区
    函数体的二进制代码。
24. 堆栈区别
                栈                            堆
    存储对象     局部变量、函数参数等        malloc或new出来的对象
    大小        小,vc缺省大小为2M            大,受限于机器的虚拟内存大小
    速度        快                            相对栈来讲较慢
    结构        先进后出                    无严格的数据结构
    内存连续    内存连续,不会出现碎片问题    频繁的new/delete等操做势必会形成内存空间的不连续,从而形成大量的碎片,使程序的效率下降
    管理        操做系统维护                使用者维护
25. 内容操做注意事项
    1)用malloc或new申请内存以后,应该当即检查指针值是否为NULL,防止使用指针值为NULL的内存
    2)不要忘记为数组或动态内存赋初值(好比calloc比malloc就要好),指针初始化为NULL(c++为0)
    3)避免数组或指针下标越界,特别小心发生 多1 或者 少1 的操做
    4)动态内存的申请和释放必须配对,防止内存泄露,具体为malloc/calloc/realloc和free配对,new和delete以及delete[]配对
    5)用free或delete释放内存后,应当即将指针设置为NULL(C++中为0),防止 野指针 悬垂指针
    6)类的内存大小  c++中空类/结构体的内存大小为1字节
26. 内存池 一种内存分配方式,提升内存的使用效率
    在真正使用内存以前,先申请分配必定数量的、大小相等(通常状况下)的内存块留做备用,当有新的内存需求时,就从内存池中分出一部份内存块,若内存块不够再申请新的内存。
    解决频繁调用new/delet产生的性能消耗
    减小内存碎片
27.    内存池分类
    线程安全角度
        单线程内存池:整个生命周期只被一个线程使用,所以不须要考虑互斥访问的问题。性能更高
        多线程内存池:可能被多个线程共享,所以则须要在每次分配和释放内存时加锁。适用范围更广
    可分配单元大小
        固定内存池:应用程序每次从内存池中分配出来的内存单元大小事先已经肯定,是固定不变的。
        可变内存池:每次分配的内存单元大小按需变化。
28. STL容器
    序列容器:线性结构的可序群集,容器内的元素按顺序进行存放,可按位置存储和访问,元素排列次序与元素无关,
    而是由元素添加到容器内的次序决定的。
    vector、list、deque、string
    
    关联容器:非线性结构,更准确说是二叉树结构,各元素之间没有严格的物理上的顺序关系,即元素
    在容器中并灭有保存元素植入容器时的逻辑顺序。以键值的方式保存数据,把关键字或值关联起来保存键值对容器,元素按键进行排序。
    set、hash_set、multiset、hash_map、map、hash_multiset、multimap、hash_multimap
29. 迭代器
    迭代器用来访问一个容器类的所包含的所有元素,其行为像一个指针。
    迭代器是个所谓的复杂的指针,具备遍历复杂数据结构的能力。其下层运行机制取决于遍历的数据结构。
30. vector 
    vector可采用连续存储空间来存储元素。能够采用下标对vector元素进行访问
    vector在访问元素时候更加高效,在末尾添加和删除元素相对高效。对于其余不在末尾的删除和插入操做,效率更低。
    遍历方法;下标法 和 迭代器
31. list 
    一种序列型容器,是一个线性链表结构,他的数据由若干个节点构成,无需分配制定的内存大小且能够任意伸缩,这是由于
    他存储在非连续的内存空间中,而且由指针将有序的元素连接起来。
    因为是链表结构,list随机检索的性能很是的很差,可是他能够迅速的在任何节点进行插入和删除操做。
    遍历方法: 迭代器
32. map
    一种关联型容器,一个键值对(key/value)容器,map与multimap差异仅仅在于multimap容许一个键对应多个值。
    map内部是自建一颗红黑树(一种非严格意义上的平衡二叉树),这棵树具备对数据自动排序的功能,因此在map内部
    全部的数据都是有序的。其“键“子啊容器中不能够重复,且按必定顺序排列,能够修改实值,可是不能修改key
    map的检索操做比vector慢,比list快。
    遍历方法:迭代器法 和 赋值
    删除元素:注意迭代器的位置
33. 文件读写
    C操做 ANSI C提供的一组标准库函数实现 fopen、fseek、fread、fwrite、fclose
    MFC操做 MFC提供的CFile类
    WINAPI win32 api函数 CreateFile
    C++操做 C++提供的文件操做类ofstream、ifstream、fstram
34. 常引用、常对象和对象的常成员
    http://www.jizhuomi.com/software/68.html
    1)常引用
    用const声明的引用就是常引用。常引用所引用的对象不能被更改。咱们常常见到的是常引用做为函数的形参,这样不会发生对实参的误修改。
    常引用的声明形式为:const 类型说明符 &引用名。
    #include<iostream>
    using namespace std;
    void show(const double& r);
    int main()
    {  
           double d(9.5);
           how(d);
           return 0;
    }
    void show(const double& r)
    //常引用做形参,在函数中不能更新r所引用的对象。
    {  
          cout<<r<<endl;  
    }
    2)常对象
    所谓常对象,是指数据成员在它的生存期内不会被改变。定义常对象时必须对其进行初始化,而且不能改变其数据成员的值。
    常对象的声明形式为:类名 const 对象名 或者 const 类名 对象名。
    class A
    {
    public:
               A(int i,int j) {x=i; y=j;}
                 ...
    private:
              int x,y;
    };
    A const a(6,8); //a是常对象,不能被更新
   说明: 若是程序中出现对常对象的数据成员修改的语句,编译器会报错。
   通常修改对象的数据成员有两种途径,一种是经过对象名访问公有数据成员并修改其值,而常对象的数据成员是不能被修改的;
   另外一种是类的成员函数修改数据成员的值,而常对象不能调用普通的成员函数。但是这样的话,常对象就只剩数据,没有对外的接口了,
   这就须要为常对象专门定义的常成员函数了。
   3)类的常成员函数
   类中用const声明的成员函数就是常成员函数。
   常成员函数的声明形式为:类型说明符 函数名(参数表) const。
   a.常成员函数在声明和实现时都要带const关键字;
   b.常成员函数不能修改对象的数据成员,也不能访问类中没有用const声明的很是成员函数;
   c.常对象只能调用它的常成员函数,不能调用其余的普通成员函数;
   d.const关键字能够被用于参与对重载函数的区分,好比,若是有两个这样声明的函数:void fun(); void fun() const;,则它们是重载函数。
    #include<iostream>
    using namespace std;
    class R
    {
    public:
                R(int r1, int r2)    { R1=r1; R2=r2; }
                void print();
                void print() const;
    private:
                int R1,R2;
    };
    void R::print()
    {
               cout<<R1<<":"<<R2<<endl;
    }
    void R::print() const
    { 
              cout<<R1<<";"<<R2<<endl;
    }
    int main()
    {
             R a(5,4);
             a.print(); //调用void print()
             const R b(20,52); 
             b.print(); //调用void print() const
             return 0;
    }
































































































































































































































































































































html

    上面的R类中声明了两个同名函数print,第二个是常成员函数。
    在main函数中定义了两个对象a和b,b是常对象,经过a调用的是没有用const声明的函数,而经过b调用的是用const声明的常成员函数。
    4)类的常数据成员
    类的数据成员也能够是常量和常引用,用const声明的数据成员就是常数据成员。在任何函数中都不能对常数据成员赋值。构造函数对常数据成员初始化,只能经过初始化列表。
    #include<iostream>
    using namespace std;
    class A
    {
    public:
                   A(int i);
                   void print();
                   const int& r;
    private:
                   const int a;
                   static const int b;  //静态常数据成员
    };
    const int A::b=20;
    A::A(int i):a(i),r(a) {}
    void A::print()
    {   
                 cout<<a<<":"<<b<<":"<<r<<endl;
    }
    int main()
    {
               //创建对象a和b,并以50和10做为初值,分别调用构造函数,经过构造函数的初始化列表给对象的常数据成员赋初值
               A a1(50),a2(10); 
               a1.print();
               a2.print();
               return 0;
    }




























linux

    此程序的运行结果是:ios

    50:20:50
    10:20:10
35. C++ const修饰函数、函数参数、函数返回值
    https://www.runoob.com/w3cnote/cpp-const-keyword.html
    https://www.cnblogs.com/wjxx836/p/4440823.html
36. 互斥锁、递归锁、读写锁和自旋锁区别
    https://www.cnblogs.com/evenleee/p/11309156.html





c++

    互斥锁
    共享资源的使用是互斥的,即一个线程得到资源的使用权后就会将改资源加锁,使用完后会将其解锁,因此在使用过程当中有其它线程想要获取该资源的锁,那么它就会被阻塞陷入睡眠状态,直到该资源被解锁才会别唤醒,若是被阻塞的资源不止一个,那么它们都会被唤醒,
    可是得到资源使用权的是第一个被唤醒的线程,其它线程又陷入沉睡。

windows

    递归锁
    同一个线程能够屡次得到该资源锁,别的线程必须等待该线程释放全部次数的锁才能得到。
api

    读写锁
    读写锁拥有读状态加锁、写状态加锁、不加锁三种状态。只有一个线程能够占有写状态的锁,
    但能够多个线程同时占有读状态锁,这也是它能够实现高并发的缘由。
    当其处于写状态锁下,任何想要尝试得到锁的线程都会被阻塞,直到写状态锁被释放;
    若是是处于读状态锁下,容许其它线程得到它的读状态锁,可是不容许得到它的写状态锁,
    当读写锁感知到有线程想要得到写状态锁时,便会阻塞其后全部想要得到读状态锁的线程。
    因此读写锁很是适合资源的读操做远多于写操做的状况。





数组

    读写锁三个特征:浏览器

    多个读者能够同时进行读
    写者必须互斥,只容许一个写者写,也不能读者写者同时进行
    写者优先于读者,一旦有写者,则后续读者必须等待,唤醒时优先考虑写者
    自旋锁
    自旋锁是一种特殊的互斥锁,当资源被加锁后,其它线程想要再次加锁,
    此时该线程不会被阻塞睡眠而是陷入循环等待状态(不能再作其它事情),
    循环检查资源持有者是否已经释放了资源,这样作的好处是减小了线程
    从睡眠到唤醒的资源消耗,但会一直占用CPU资源。适用于资源的锁被持
    有的时间短,而不但愿在线程的唤醒上花费太多资源的状况。







安全

    自旋锁的目的服务器

    自旋锁的实现是为了保护一段短小的临界区操做代码,保证这个临界区的操做是原子的,
    从而避免并发的竞争冒险。在Linux内核中,自旋锁一般用于包含内核数据结构的操做,
    你能够看到许多内核数据结构中都嵌入有spinlock,这些大部分就是用于保护它自身被操做的原子性,
    在操做这样的结构体时都经历这样的过程:上锁-操做-解锁。


    若是内核控制路径发现自旋锁“开着”(能够获取),就获取并继续本身的执行。     相反,若是内核控制路径发现锁由运行在另外一个CPU上的内核控制路径“锁着”,     就在原地“旋转”,反复执行一条紧凑的循环检测指令,直到锁被被释放。自旋锁是循环检测“忙等”,     即等待时内核无事可作,进程在CPU上保持运行,因此它的临界区必须小,且操做过程必须短。不过,     自旋锁一般很是方便,由于不少内核资源只锁1毫秒的时间片断,因此等待自旋锁的释放不会消耗太多CPU的时间。

相关文章
相关标签/搜索