• C++进阶 面向对象基础(三)


    类的的定义:
    初始化一般建议使用构造函数初始化列表形式:
    Person(const string nm, const string addr):name(nm), address(addr){}
    this指针:
    类中使用this指针,特别是有些情况不能省略,例如在子类的方法中调用父类的某个成员变量,得加上this,不然有些编译器不通过,又例如
    class Per{
     Per& getName(){
        return *this;
     }
     Per& getAge(){
        return *this;
     }
    
    };
    这个调用 Per per; per.getName().getAge(); 
    如果函数也是const, 那么对应的返回也应该是const 并且,可以基于const进行函数重载;
    对象的创建,如果是创建一个指针,对必须用new。
    
    构造函数:
    引用类型,const类型,没有默认构造函数的类类型的成员,必须再初始化列表中进行初始化。
    单参数构造函数需要制定关键字explicit,防止发生隐式转换
    不要在构造函数中,做太多逻辑相关的操作。
    为避免使用默认构造函数,可以将其声明了private成员,并且使用宏代替。 友元数,友元类:可以直接调用友元的的私有成员。 友元函数A在类B中声明,A类变为B类的友元类,将A类在B类中声明。 例如:Class A{ friend
    class B;}; ”表示B是A的友元类,则B可以用A的任意东西“。则可以在B类中可以任意的调用A的私有成员。 static成员: 属于公用的共享的成员,例如一个类,声明了两个对象,这这两个对象的静态成员是公用的、享的。而普通成员则不具有这种特性。。 所以可以充当各个类之间的全局变量,比普通的全部变量好处在于可以避免不同类之间全局变量的干扰,适合某一类数据的全局变量的封装; 静态的变量可以通过类的作用域操作符,直接对静态变量进行初始化,或者使用,Class A{ static float x;} A::x =0.3; 另外静态的成员函数里面不能使用this指针,因为静态的成员函数不属于任何一个对象,因为是公共的; 例如静态的const的整型变量,可以再类中声明的时候直接初始化。其他的都不可以复制构造函数,赋值操作符; 以上两个,如果自己没写,C++ 会默认写一个。很多的时候不用自己写,但是当类中有一个成员是指针的时候,一定要自己写一个。 复制构造函数 的参数只有一个,并且一般都是const 类型的当前类 例如 class A{ A{const A &a):x{a.x},y(a.y){}} 复制构造函数将会将当前 对象的成员复制到另外一个对象里。所以可以A a(b); 赋值操作符:当一个类的数据成员有指针,动态分配内存的时候,一般必须写一个赋值构造函数class A{ A& operation =(const A &b){a.x = b.x; a.y = b.y;}} ; 所以可以直接 a = b;b 复制构造函数: class A{ public: A(A& b):p(new std::string (*(b.p))){} //如果掉用的默认的复制构造函数,则这里为A(A& b):p(b.p){} 只是将指针复制给了指针,而我们的目的是将原来的字符串拿到,并用来初始化当前的指针,重新创建一个字符串 private: str::string *p; } 同理赋值操作符为: A& operation=(const &b){ p = new std::string; //如果用的默认的只是 p = b.p; 并没有用里面的数字进行真正的复制。浅复 *p = *(b.p); return *this; } 析构函数: 如果写了析构函数,就应该写复制构造函数,复制操作符。(三法则) 浅复制与深复制 数据成员是指针,一定要做深复制。 如果类操作了系统的资源,只要动态的分配了资源,复制的时候,也一定要写深复制。 管理指针:(当一个类有指针成员的时候,一定要注意) 常规指针类(浅复制)避免浅复制,一般可用以下方法:智能指针类(计数类)有浅复制的特性,数据共用,但是又不会产生野指针;值型类(深复制) 自己设计智能指针类中的每个成员是私有的,内部有个指针和对应的计数器变量。 重载操作符:
    一般不要或者尽量少用操作符重载,如果要使用,可以使用函数去实现。例如==,可以使用Equals,或者CopyFrom去实现。 关键字
    operator 操作符重载: 输入输出操作符重载,特别是输入操作符重载要注意检查输入。 算术操作符重载,+一般是非成员函数,+=一般作为成员函数。 操作符重载:(转换操作符) operator int()const必须是成员函数,不能指定返回值,形参表必须是空的,必须显示的的返回一个指定类型的值,通常定义为const类型,防止改变被转化对象class A{ public: A():x(3){} operator int()const{ return x; } private: int x; } A a; int y = a; //这样可以直接将一个类类型自动转换为了一个int类型,然后复制给y了。

    打印错误信息:
    printf("%s %d %s ", __FILE__, __LINE__, "operation failed with this");
    fflush(stdout);
    exit(-1);

    操作符重载:(比较操作符)
    == != ; >= <=; > <;
    一般用这种重载是配套的,定义了一个,也得定义另外一个,并且利用第一个来做第二个,并且第一个一般用友元函数的方式实现,而第二个就可以不用友元方式了。
    操作符重载:(赋值操作符)
    = += -= *= /= %= 等等
    赋值操作符必须返回对*this的引用
    操作符重载:(下标操作符)
    [] 一般重载的写两个:可变成员函数,常量成员函数,函数里面的东西都是一样,定义const的意义是,如果定义的const,那就要求不能修改。
    例如 使用的时候,String s("heelo"); String const s2("dog"); 则这里就是要求s2不能修改,所以类String中还需要重载一个const常量成员函数。
    操作符重载:(成员访问操作符)
    * -> 主要在定义只能指针的时候,有可能需要重载这两个操作符,普通的类一般是不需要对这两个操作符重载的。

    函数对象:
    重载函数调用操作符,operator(),函数对象可以在一些算法传递函数对象的地方可以使用,而普通函数则不行。函数对象实际上是一个类,因为类有数据成员,所以可以
    保存一定的状态。如果函数对象的返回值是bool,我们叫这个函数对象是谓词。返回一个值,就是一元谓词,二个值,就是二元谓词。
    函数对应可以用类做,也可以用结构体做。
    template <typename elementType>
    struct DisplayElements{
    int m_count;
    DisplayElements(){
    m_count = 0;
    }
    void operator()( const elementType &a){
    m_count++; //这里就比普通的函数多了个计数器,也就是当这个函数对象调用了多少次,可以通过m_count获取。
    cout<< a << ' ';
    }
    };
    调用时
    DisplayElements mResult;
    mResult = for_each(a.begin(), a.end(), mResult<int>); //函数对象确定名称调用的形式
    mResult = for_each(a.begin(), a.end(), DisplayElements<int>); //函数对象匿名调用的形式
    cout << mResult.m_count << endl; //可以获得比普通函数更多的信息

    
    基类和派生类:
    protected和public的成员,在子类中可以直接使用,但是不能在子类中,通过基类去获得对应的protected成员。受保护的不能用基类直接调用,
    受保护的成员,专门用于继承使用的,所以受保护的成员,基类是可以直接用的,并且只能在基类定义的内部使用,不能再别的地方直接调用使用。
    class base{
    public:
        int x;
    protected:
        int y;
    private:
        int z;
    }
    class bundle_base :public base{
    public:
        bundle_base(): base(){}
        void display(){
            cout << x<< endl;//可以直接调用x
            cout << y << endl; //可以直接调用
        }
        void display(bundle_base &a, base &b){
            cout <<a.x<< endl;
            cout <<a.y<<endl; //并且这个受保护的成员y,在别的作用域是不能直接调用的,在子类的以为区域,就相当于变成了私有成员了。
            ?? cout <<b.y <<endl; //这里是不能用的.
        }
    }
    虚函数:可以重写和不重写,纯虚函数:必须重写
    子类重载的虚函数,一般也写上virtual。 动态绑定(多态) 多态性,派生类到基类的转换,引用或指针既可以指向基类对象,也可以指向派生类对象,只有通过引用或指针调用虚函数才会发生动态绑定。 三种继承 一般用public继承,几乎不用protected和private继承,默认是私有继承,java只有public继承。私有继承中,想把基类的public中的类型变回public, 可以使用using,来去除个别成员的私有特性,来修改继承访问。
    class A{ public: int x; int y; } class B : private A{ publicusing A::x; //这里把x变成了公有继承 y; //这里的y,对于B而言,就是private成员了,因为是私有继承。区别上面的(修改继承访问)方式。 } 派生类的构造函数和析构函数 派生继承 类的构造和析构中,构造函数,从先调用基类的构造函数,然互调用成员对象的构造函数,最后调用自己的构造函数。注意:这个过程在构造析构子类的时候,就会发生。 class E : public B, public A, public C{ //构造E的时候,构造函数调用顺序为 B,A,C,D,E的构造函数。析构则相反顺序 private: D d; } 转换和继承: 引用转换/指针转换 对象转换 (派生类到基类) 把派生类传给基类,如果是对象传递,则,无法实现多态。 1,void (Base a){a.function();} 2, void(Base &a){a.function();} 3,void(Base *a){a->function();};如果将一个子类 传递给基类Base, 则如果是 对象传递1,则使用使用基类的function,无法实现多态的意义,所以一般用2和3,引用和指针传递。 如果是基类转换到子类,一把是禁止的,如果要进行,则需要用强制转换。 友元和继承: 友元可以访问类的private和protected成员。但是友元关系不能继承。 静态成员和继承: 基类中的static成员,在整个继承层次中只有一个实例。 访问方式:基类名::成员名 子类名::成员名 对象.成员名 指针->成员名 成员名(在子类中,只要有访问权限) 纯虚函数和抽象类: 含有纯虚函数的类为抽象类, 纯虚函数是虚函数声明后面加上"=0;",纯虚函数的定义可以写可不写,一般不写,让子类来实现; 抽象类不能创建对象,即不能实例化,只能继承;纯虚函数必须实现;只具有纯虚函数的抽象类成为c++接口。 具有纯虚函数的类的子类,对应的函数也一定是虚函数,所以对应的析构函数也必须是虚函数,但是对应的子类不是抽象类了,既可以实例化其对象。(因为具有虚函数的类的析构函数必须是虚函数) 模板与泛型编程: 类模板和函数模板;模板编程又称泛型编程。 队列:顺序队列 push pop front Rear isEmpty等操作,顺序队列是用数组做的队列,中途new空间。如果大小改变,得重新分配空间。 队列:链式队列 使用链表做的队列,比顺序队列更灵活,设计更加简单。 函数模板: 函数模板->实例化->函数。 使用模板形参 template <typename T> 一般可以实现代码复用。 异常: try catch throw 异常类型:数字,字符串,类对象。 比如出错了,不用return 返回各种不同数值,然后判断数值的方式, 而是用thow来抛出异常。抛出的异常可以用数字对象和字符串等。 int isXEqual() {if(x==y) thow 2;} 或者thow “failed”; 等方式来跑出异常。调用的时候可以用 tyr{ isXEqual();} catch(int e){ printf(”异常 %d “, 1); } 当发生异常的时候,会运行catch部分,比这种return来检查出错结果是啥,会好很多。 catch(...){printf("exeption ");} catch所有异常用‘...’代替。 异常(2):自己创建异常类 在类中创建异常类,类名一般用xName的形式(x开头的名字),使用的时候,直接throw xName;就可以了。 class array{ private: size_t itsSize; public: array(size_t x):itsSize(x){} class xBase{ publicvirtual void printError(){printf("the exeption comes ");} } class xName : public xBase{ virtual void printError(){printf("the exeption name comes ");} };//如果需要写多个异常类,返回多个不同的异常,可以使用多态方法;继承一个类,然后在基类中使用虚函数的方式,获取基类的异常即可 class xSize : public xBase{ virtual void printError(){printf("the exeption size comes ");} } void getSize(){ if(size>3) throw xSize(); if(itsSize <1) throw xName(); } }; 调用的时候可以: try{ array arr(3); getSize(); } catch(array::xBase &exep){ //一定要按引用或者指针传递,多态技术,可以使用一个catch 来捕获所有的异常 exep.printError(); } catch(array::xName){ //当然也可以单个的使用某个异常,优先使用上面的那个方法 printf("name exeption "); } 异常:标准异常 exception runtime_error rang_error overflow_error underflow_error logic_error domain_error invalid_argument length_error out_of_range bad_alloc(分配空间过多失败的异常) try{ int x = new int[100000000000]; }catch(bad_alloc err){ printf("bad alloc err "); } 职能指针: 智能指针是个指针模板类。常常解决:深度复制,写时复制,引用计数,引用连接,破坏性复制。 std::auto_ptr Boost职能指针,ATL框架中的智能指针。常用shared_ptr,unique_ptr,weak_ptr。 智能指针类都有一个明确的explicit构造函数,所以使用智能指针的时候,要求明确的转换,不允许不明确的转换,智能指针使用的时候,就像和指针使用一样。 shared_ptr<double> pd; double *p_reg = new double; pd = shared_ptr<double>(p_reg); 或者 shared_ptr<double> pshared(p_reg); 注意以下不明确的转换时不允许的:例如pd = p_reg; shared_ptr<double> pshared = p_reg; 另外注意,不使用new分配内存时,不能使用shared_ptr,auto_ptr和unique_ptr 命名空间: 每个命名空间是一个作用域,命名空间可以是不连续的,接口和实现的分离,嵌套命名空间。(命名空间可以防止重名) 头文件中一边不用using std::cout等方式,一般在哪里调用,就直接使用std::cout里面,因为这样会把大量的东西带入头文件命名空间的别名:例如 using namespace c = std::cout; 作为别名,可以减少命名空间的长度。 多继承与虚基类:(建议不要使用多继承,或者尽量少用多继承,一般使用单继承就可以了) 很多语言中取消了多继承,但是多继承是c++的一个很重要的功能。多继承多个父类之间用“,”隔开。 多继承中注意构造函数和析构函数的的调用顺序。特别是构造函数的初始化列表,要注意父类的构造函数列表的初始化。 例如一个构造函数: subClass(int x):y(x), baseClass(2x), base1Class(x, 2x){} 得非常注意二义性问题:一个父类A有两个子类B,C,然后这两个子类B,C又是另外一个类D的父类。 所以在构建D的时候,会构造B,然后B会调用A构造一次A,同理C构造的时候,也会构造一次A,这样就会两次构建A,产生了二义性,即两个A对象。 这里一般使用虚基类解决二义性问题:即使用虚继承,这里就是B和C虚继承A。 即 class B : virtual public A{ B(){ A(); //虚基类必须重新调用父类的构成函数 } A(){} } class C :virtual public A{ C(){ A(); //虚基类必须重新调用父类的构成函数 } }, 然后D 正常继承B和C class D: public B, public C{},这样之后,构造D的时候,会构造B,和C,但是这里构造B和C的时候,不去构造A了。 只有D构造的时候,一次构造A,所以就不会产生两个A了。 特殊工具和技术: extern “C” : allocator 类:常用于非配固定大小的内存。例如 allocator<aClass> a; a.allocate(100); 为aClass类,分配了100大小的空间。 RTTI技术:如dynamic_cast动态的类型转换,在运行时进行识别技术,可以将基类转变为子类,即可以向下转换。如果是子类赋值给基类,是可以自动进行的(向上转换时自动的)。 RTTI技术,如,typeid(aClass).name()可以获取aClass的类名。 类成员的指针:可以指向类的成员的指针,例如std:: Iten_base:: *pf = &Iten_base.isBn; 指向Iten_base的成员isBn的指针pf。也可以定义类的指向成员函数的指针。 union UTest{ char cV; int intV; double dV; }; 如果 UTest ut = {'a'}; cout << ut.intV << endl;输出将是97,也就是cV的assic码值,因为这个里面的都是共有的,也就是,都是同一个值。 位域:即一个类中的成员用的位,比如 class xC{ Bit v:1; Bit b:2;} volatile (易变量标识符):volatile int y; 告诉c++不要对他进行优化,因为,这个变量可能不稳定。
  • 相关阅读:
    拷贝构造函数 转型构造函数
    构造函数对const成员的初始化
    拷贝构造函数
    类数据成员和类成员函数
    顶层函数进行操作符重载与操作符重载直接作为类的构造函数
    构造函数和new 与new【】 析构函数
    Openfire+XIFF实时通信&一个特殊的GP
    客户端数据动态渲染
    数据处理:由面得点,构造其边界
    从GISALL博客搬家而来
  • 原文地址:https://www.cnblogs.com/hansjorn/p/5423463.html
Copyright © 2020-2023  润新知