• [Cpp] 面向对象程序设计 C++


    初始化列表(包括成员对象初始化)

      初始化列表 ( 推荐 ) :
      可以初始化任何类型的数据, 不管是不是普通类型还是对象,都建议用.
      不再需要在构造器中赋值了, 而且初始化列表比构造函数要早执行.
      成员初始化次序取决于成员在类中的声明次序.

      当类成员有其它对象时,构造器内给对象赋值会触发成员对象的默认构造函数(无参数的),如果成员对象没有默认构造函数编译报错.
      所以有成员变量为对象这种场景下,要用 initializer list.

    Source:https://github.com/farwish/unix-lab/blob/master/cpp/Initializer_list.cc

    继承

      复用的一种方式,还有上面介绍过的 "对象组合"(成员变量为其他对象)

      私有属性只能由父类自己访问;受保护的属性可以由子类访问,别人都无法访问.

      当实例化子类时,会先调用父类的构造函数,当父类没有默认构造函数时又没有初始化自己的构造函数时,编译报类似 "no matching function AA::AA( )",所以在子类中只能用 initializer list 对父类成员初始化.

      析构的调用次序则反过来,先子类后父类.

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Extends.cc

    函数重载(Function overload)和默认参数(Default argument)

      同名函数通过拥有不同的参数表实现重载. void print( );   void print ( int i )

      默认参数是在头文件中给原型的默认参数值,唯一的好处是某些情况下少打字;但是在调用时容易造成阅读困难,另外也不安全,如果我们不 include 头文件而是自己写一个函数声明,把默认参数值设为其它的,那么就和设计者的意图不一样。所以建议不使用 Default argument 如 void f (int i , int j = 10); 

      

    内联函数(Inline functions)

      当函数前面有 inline 时,它就是一个 declaration,而不再是 defination,因此不需要担心重复定义的问题。

      内联函数的 body 放在头文件里就可以了,不需要 .cpp 文件,和传统的一个 .h 对应一个 .cpp 不同。

      因为内联函数有类型检查,因此比做同样事情的宏要好。 

      ( 使用场合:函数只有2~3行的,需要重复调用的;不适合的:函数比较大,递归 )

      成员函数在 class 声明时如果给出了 body,那么这些都是 inline 函数,只要有一个头文件就够了。

      另一种写法是保持 class 声明干净,而为单独实现的成员函数前面加 inline.

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Inline.h

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Inline_main.cc

    const; 不可修改的对象(对象成员)

    成员函数 const 的用法:

      在声明和定义的地方要一起用. 

        int getData( ) const;

        int getData( ) const { return data }

      不修改数据的成员函数应该被定义为 const.

      如果类有 const 成员变量 或者 实例一个 const 对象,那么一定要在 initialize list 里面初始化变量,否则编译无法通过,因为后面无法修改它 (成员变量)。

      func( ) { } 和 func( ) const { } 是不一样的,它们构成重载( overload ),因为它们相当于是 func( A* this ) 与 func( const A* this ),参数表不一样。

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Const_class.cc

    引用(C++数据类型)

      char c; char* p = &c; char& r = c;

      本地变量或全局变量,必须有初始值,type& name = 'name'  

        int x = 3;

        int& y = x;            # 赋初值

        const int& z = x;  # z 不能做左值,但是可以通过修改 x 来修改 z

      作为参数和成员变量时,可以没有初始值,因为它们会在构造对象时被调用者初始化,type& name;

        void f ( int& x )

        f ( y );      # 在函数调用时初始化

      指针和引用的区别:

        引用不能为 null.              指针可以为 null.

        引用依赖另一个变量,是一个变量的别名.  指针独立于已存在的对象.

        引用不能指向一个新的地址.        指针可以更改指向不同的地址.

        cpp内存模型的复杂性体现在:三个地方放对象(堆,栈,全局数据区),访问对象的方式(变量放对象,指针访问,引用访问)。

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Reference.cc

    引用再研究

      引用作为类的成员时,声明时没有办法给初始值,因为它需要和另外一个变量捆绑在一起,作为别名;所以必须在构造函数的 initializer list 里初始化。

      函数可以返回一个引用,但不能引用本地变量。

      参数前的 const 的引用,const 保证不被修改,引用使传参高效,好处是函数中不用使用 * 号。 

      参数传引用,这说明参数是一个可以做左值的东西,传参不能使用变量非const的表达式。

        void func(int &); func(i * 3); // error:invalid initialization of non-const reference of type 'int&' from a temporary of type 'int'   error: in passing argument 1 of 'void f (int &)'

        void func(const int&); int i = 3; func(i * 3); // 区别仅在于参数是const的,正确输出9

      不能对函数返回的对象做左值,编译会报错,error: using temporary as lvalue [-fpermissive]。

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Reference_2.cc

    向上造型(Upcasting)

      子类的对象当做父类的对象来看,叫做向上造型,因为一般习惯把父类画在上面;Upcasting 一定是安全的,最多子类拥有的被无视。

      父类的对象当做子类的对象看,叫做向下造型,Downcasting 有风险,因为父类不一定拥有子类的东西。

      类型转换和造型的区别,类型转换原来的值转换完就变了,而造型数据没变,子类的对象还是子类的对象,只是看待的眼光不一样。

      Persion John('JOHN');

      Animal* p = &John;  // Upcast,  因为Person是Animal的一种, 但反过来就是 Downcast

      Animal& q = John;   // Upcast

    多态性(polymorphism):Upcast 和 Dynamic binding 两个条件构成多态性

      Upcast: 把派生类当做基类使用。

      Dynamic binding: 调用对象的函数。

        (Static binding: 调用代码写明的函数)

    /**
     * 通用函数,对任何 Shape 和其子类都通用.
     *
     * 动态绑定,调用的 render 在运行时决定:
     *  p 有一个静态类型和动态类型,如果 p 的 render 函数是 virtual 的,那么是动态绑定,不是 virtual 则是静态绑定。
     *  所以动态绑定还是静态绑定取决于 render 函数,而不是对象 p;如果我们调用的是 move 函数,那么就是静态绑定。
     */
    void render(Shape* p)
    {
        p->render();
    }

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Polymorphism.cc

    虚 析构函数

      Shape的析构不是 virtual 时,默认是静态绑定,delete p 时,只有 Shape 的析构会被调用,Ellipse 的不会调用。

      Shape的析构是 virtual 时,表示动态绑定,delete p 时,会先调子类的析构,在调父类的析构。

        Shape* p = new Ellipse(100.0, 110.0);
        delete p;

      其它 OOP 语言默认就是 virtual 的,也就是动态绑定的,而C++默认是静态绑定的,动态绑定需要手动加 virtual。

      如果一个类里有一个 virtual 函数,它的析构函数就必须是 virtual 的。

      如果父类和子类有名字相同、参数表相同的 virtual 函数,那么子类成员函数就对父类构成了重写/覆盖。

      子类成员函数中调用父类的同名函数用 Base::func( ) 的方式。

      父类里有两个 virtual 的重载(overload)函数,那么子类里也要实现两个 overloaded 的函数,否则另一个函数会发生 name hidden,只有 C++ 会发生函数的隐藏。

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Polymorphism.cc

    拷贝构造

      拷贝构造的唯一形式:T::T(const T&)

      拷贝构造什么时候被调用?
        1.用对象进行初始化时,Person p = p1 或 Person p(p1),这两种写法相同,注意它不是 assignment 而是 initialization (因为变量前有类型)。
        2.调用一个函数,函数的参数是一个对象时,void func(Person p);
        3.用返回对象的函数返回值进行初始化。

      Construction vs Assignment
      每个对象只能构造一次,每个对象应该被析构一次,
      对象一旦被构造,它可以是被赋值的目标,前头有类型就是 initialization,没有类型就是 assignment.

      Copy constructor guidelines
      写一个类就先写三个函数 default constructor, virtual distructor, copy constructor。
      如果确实不需要拷贝构造,那么就声明为私有,不建议这么做,限制了很多事不能做。

    Source: https://github.com/farwish/unix-lab/blob/master/cpp/Copy_constructor.cc

    静态对象

      static两个基本的含义:

        静态存储,本地变量是static,这个本地变量具有持久存储(事实上static的本地变量就是全局变量)。

        名字可见性,全局变量、函数的static,那么这个全局变量、函数只在当前文件中可用。

      static 在 C++ 中的使用:

        静态本地变量 - 持久存储。

        静态成员变量 - 所有对象间共享。

        静态成员函数 - 所有对象间共享,它只能访问静态成员变量。

        对象是静态的 - 除了遵守两个基本法则(存储、可见性),保证只构造析构一次。

      静态初始化的依赖

        多个 cpp 文件都有自己的全局变量的情况,没人保证初始化顺序先后;

        如果一个变量的初始化依赖另一个变量的值作为参数,那么需要先初始化那另一个变量,但是跨文件的初始化是不存在的。所以解决方案是,1. 别这么干。 2. 逻辑上许可的话,把所有有依赖的全局变量放到一个地方。

    Source:https://github.com/farwish/unix-lab/blob/master/cpp/Static_members.cc

    运算符重载(Overloading Operators) - 基本规则

      运算符允许通过自己定义 function 来重载。

      

      只有已存在的运算符可以被重载,不能对类和枚举重载,必须保持操作数个数(如加法需要两个操作数),必须保证优先级。

      使用 operator 关键字作为函数名字,如重载 * 号,就是 operator * (...)

      可以作为成员函数,const String String::operator + (const String& that); 需要两个操作数,因为有默认参数 this 作为第一个,所以再只需要一个。

      可以是全局函数,const String operator + (const String& r, const String& l); 这时参数表需要两个参数。

      Integer x(1), y(5), z;

      x + y; ====> x.operator+(y);  这里的 x 就是receiver,receiver 决定 operator 用哪个。

      z = x + y; 可以。

      z = x + 3; 可以。

      z = 3 + y;  编译通不过。

      Tips:做成成员函数还是函数?

        单目的运算符应该做成是成员的,但非强制。

        = ( ) [] -> ->* 必须是成员的。

        赋值运算符应该做成是成员的。

        所有其它二元操作符作为非成员的。

      原型

      参数传递:如果是只读的,那么传 const 的“引用”,不修改算子的成员函数加 const,全局函数可能两个都加或者又一个不加 const。

      返回值:1.决定了是对自己进行了修改还是返回了新对象; 2.制造出的新对象是不是可以做左值;

      运算符原型

      

      

    Link:http://www.cnblogs.com/farwish/p/8099721.html

  • 相关阅读:
    hdu 1017 A Mathematical Curiosity 解题报告
    hdu 2069 Coin Change 解题报告
    hut 1574 组合问题 解题报告
    hdu 2111 Saving HDU 解题报
    hut 1054 Jesse's Code 解题报告
    hdu1131 Count the Trees解题报告
    hdu 2159 FATE 解题报告
    hdu 1879 继续畅通工程 解题报告
    oracle的系统和对象权限
    oracle 自定义函数 返回一个表类型
  • 原文地址:https://www.cnblogs.com/farwish/p/8099721.html
Copyright © 2020-2023  润新知