zoukankan      html  css  js  c++  java
  • [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

  • 相关阅读:
    Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门
    开源精神就意味着免费吗?
    是时候给大家介绍 Spring Boot/Cloud 背后豪华的研发团队了。
    springcloud(十五):Spring Cloud 终于按捺不住推出了自己的服务网关 Gateway
    写年终总结到底有没有意义?
    培训班出来的怎么了?
    【重磅】Spring Boot 2.1.0 权威发布
    一线大厂逃离或为新常态,大龄程序员改如何选择?
    Elastic 今日在纽交所上市,股价最高暴涨122%。
    技术人如何搭建自己的技术博客
  • 原文地址:https://www.cnblogs.com/farwish/p/8099721.html
Copyright © 2011-2022 走看看