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

通过llama.cpp与羊驼聊天的网页界面- 详解 Serge 的启动使用

 

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

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

  当类成员有其它对象时,构造器内给对象赋值会触发成员对象的默认构造函数(无参数的),如果成员对象没有默认构造函数编译报错.
  所以有成员变量为对象这种场景下,要用 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.制造出的新对象是不是可以做左值;

  运算符原型

  

 

Refer:有哪些C加加的使用场景

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

posted on 2018-02-10 23:42  ercom  阅读(1160)  评论(0编辑  收藏  举报