Skip to content

C++面向对象高级编程(下)--兼谈对象模型

本课程是"面向对象程序设计"的续集

转换函数


class Fraction
{
public:
    Fraction(int num, int den = 1)
        : m_numerator(num), m-denominator(den) {}
    operator double() const {
        return (double)(m_numerator / m_denominator);
    }
    operator string() const {...}
private:
    int m_numerator;    //分子
    int m_denominator;  //分母
}
  • 其中operator double() const转换函数,转换函数没有类型声明,因为函数名中隐含了类型

  • 转换函数的目的是在将来需要的时候,自动进行类型转换,如:

  • cpp Fraction f(3, 5); double d = 4 + f; //自动调用operator double() 将f转为0.6 //编译器的第一件事是找'+'是否存在操作符重载,第二件事则是寻找f的转换函数以完成编译

  • 可同时定义多个转换函数.

explict(关键字)


class Fraction
{
public:
    Fraction(int num, int den = 1)
        : m_numerator(num), m-denominator(den) {}

    Fraction operator+(const Fraction& f) {
        return Fraction(...);
    }
private:
    int m_numerator;    //分子
    int m_denominator;  //分母
}

Fraction f(3, 5);
Fraction d = f + 4;
//success! 4被转换成了4/1
class Fraction
{
public:
    Fraction(int num, int den = 1)
        : m_numerator(num), m-denominator(den) {}
    operator double() const {
        return (double)(m_numerator / m_denominator);
    }
    Fraction operator+(const Fraction& f) {
        return Fraction(...);
    }
private:
    int m_numerator;    //分子
    int m_denominator;  //分母
}

Fraction f(3, 5);
Fraction d = f + 4;
//error! 编译器不知道选择何种方式
class Fraction
{
public:
    explicit Fraction(int num, int den = 1)
        : m_numerator(num), m-denominator(den) {}
    operator double() const {
        return (double)(m_numerator / m_denominator);
    }
    Fraction operator+(const Fraction& f) {
        return Fraction(...);
    }
private:
    int m_numerator;    //分子
    int m_denominator;  //分母
}

Fraction f(3, 5);
Fraction d = f + 4;
//error! conversion from 'double' to 'Fraction' requested.
  • 可见,explicit会让编译器不去自动调用构造函数,该关键字使用频率较低,绝大多数是在上述情况中使用,即用在构造函数之前避免发生隐式转换

pointer-like classes(智能指针)

template<class T>
class shared_ptr
{
public:
    T& operator*() const {
        return *px;
    }
    T* operator->() const {
        return px;
    }
    shared_ptr(T* p) : px(p) { }
private:
    T* px;
    long* pn;
}

struct Foo
{
    ...
    void method(void) {...}
};

shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->mathod();
//等价于px->method();因为'->'符号很特别,得到的东西要继续用箭头作用上去
  • 迭代器也可以看成是一种智能指针, 除了上述*,->操作之外,还要重载++,--,如果不重载它们,指针的++,--意味着指向的地址的变化,而迭代器主要的用途是遍历容器

function-like classes(仿函数)


  • 因为函数要跟一个小括号,所以我们把有这种特征的类称为仿函数
  • 重载了()则是一个仿函数

namespace


using namespace std;
//-----------------------------
#include<iostream>
#include<memory>//shared_ptr

namespace jj01
{
void test_member_template() {...}
}//namespace

模板


  • 成员模板

image-20230409203651143

  • 泛化是模板,特化是对于某些情况的独特的设计

  • 全特化与偏特化:

  • 类模板

    ```cpp template class Test { public: Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;} private: T1 a; T2 b; };

    template<> class Test { public: Test(int i, char j):a(i),b(j){cout<<"全特化"<<endl;} private: int a; char b; };

    template class Test { public: Test(char i, T2 j):a(i),b(j){cout<<"偏特化"<<endl;} private: char a; T2 b; }; //下面3句依次调用类模板、全特化与偏特化 Test t1(0.1,0.2); Test t2(1,'A'); Test t3('A',true); ```

  • 函数模板(不存在偏特化)

    ```cpp //模板函数 template void fun(T1 a , T2 b) { cout<<"模板函数"<<endl; }

    //全特化 template<> void fun(int a, char b) { cout<<"全特化"<<endl; }

    //函数不存在偏特化:下面的代码是错误的 / template void fun(char a, T2 b) { cout<<"偏特化"<<endl; } / ```

模板模板参数


template <typename T,
          template <typename T>
            class Container
          >
class XCls
{
private:
    Container<T> c;
public:
    ...
};

三个主题


variadic templates(数量不定的模板参数)

  • 注意pack的概念

```cpp void print() { }//最后一包之后print空单独写出来

template void print(const T& firstArg, const Types&... args) { cout << firstArg << endl; print(args...); }

//用法 print(7.5, "hello", bitset<16>(377), 42);

//output 75 hello 0000000101111001 42 ```

auto

  • auto可以看成是一个语法糖

cpp list<string> c; ... list<string>::iterator ite; ite = find(c.begin(), c.end(), target); //以前的 auto ite = find(c.begin(), c.end(), target); //现在的 auto ite; ite = find(c.begin(), c.end(), target); //错误的

  • 一个极端的想法:全部都用auto,只要能让编译器自动推导出类型, 可以,但非常不推荐, 因为习惯不好

ranged-base for(since C++11)

  • for(decl : coll) {}:从右手边的容器找出每一个元素给左边的变量

  • cpp for(int i : {2, 3, 5, 6}){ ... }

  • cpp vector<double> vec; ... for(auto elem : vec){ cout << elem << endl; //pass by value,如果改变elem的值,不会影响vec中的值 } for(auto& elem : vec){ elem *= 3; //pass by reference }

  • 编译器实现引用靠的就是指针

reference


int x = 0;
int* p = &x;
int& r = x; //r代表x,现在r,x都是0
int x2 = 5;

r = x2;     //r不能重新代表其他物体,现在r,x都是5
int &r2 = r;//现在r2是5(r2代表r即代表了x)
  • 引用在声明时必须指明引用的对象,且不能改变
  • 编译器为reference制造了两个假象:sizeof(r) == sizeof(x);&r = &x

  • reference可以看成是一种十分漂亮的指针,它的最常见的用途是函数传参

  • 以下被视为same signature(两者不能同时存在)

cpp double imag(const int x) { }; double imag(const int& x) { };//Ambiguity,函数签名相同 //------------------------------------------ double imag(const int x) const { }; double imag(const int& x) { };//valid,函数签名不同

复合&继承关系下的构造和析构


  • "先构造爹,再构造儿"就像入栈一样,先构造的要后析构
  • 在继承关系下,base是爹
  • 在复合关系下,container是爹

关于vptr和vtbl(虚指针和虚表)

只要有虚函数就有虚指针, 则sizeof(obj)比不含虚指针的内存多4

image-20230420103416545

考虑一种情况: base是shape形状, derive是圆形, 矩形等不同的类.

这是想要把这些不同的形状们放到一个容器里面去, 然而任何容器都不能够存放不同大小的元素, 此时只能使用容器存放指针, 而指针的类型应该为base

/*
    自己写了一个样例程序, 使用了虚函数,继承以及上述容器的使用
*/



#include <iostream>
#include <vector>
using namespace std;

class Shape
{
public:
    virtual void Draw() const = 0;
    Shape()
        : length(0), e(0)
        {}
    void SetE(int n){
        e = n;
    }
    void SetLen(double &l){
        length = l;
    }
    int GetE() const {
        return e;
    }
    double GetLen() const {
        return length;
    }
private:
    double length;
    int e;
};

class Triangle
: public Shape
{
public:
    Triangle(double l = 0){
        SetLen(l);
        SetE(3);
    }
    virtual void Draw() const;
};

class Quadrangle
: public Shape
{
public:
    Quadrangle(double l = 0){
        SetLen(l);
        SetE(3);
    }
    virtual void Draw() const;
};

inline void Quadrangle::Draw() const
{
    cout << "Quadrangle and its edge length is " << GetLen() << endl;
}


inline void Triangle::Draw() const
{
    cout << "Triangle and its edge length is " << GetLen() << endl;
}


void print(vector<Shape*>& vec)
{
    for(auto& i : vec){
        i->Draw();
    }
}

int main()
{
    Triangle* t = new Triangle(1);
    Quadrangle* q = new Quadrangle(4.6);
    vector<Shape*> vec;
    vec.push_back(t);
    vec.push_back(q);
    print(vec);
    return 0;
}

关于this

先浅谈动态绑定, 满足以下三个条件编译器将会动态绑定

image-20230420142717734

  1. 对象指针this->Serialize();
  2. 向上转型:CMyDoc(derive)CDocument(base)
  3. 调用虚函数:virtual void Serialize()

由于每个成员函数在调用的时候都隐含了参数this, this指针指向函数的调用者

则当程序执行到Serialize();这一行的时候实际上该函数的调用者是this之指针所指向的对象, 则该行被认为是this->Serialize();符合以上动态绑定的条件, 实际上编译器会执行的指令用c语言的形式表示为(*(this->vptr)[n])(this);

谈谈const

const object non-const object
const member functions yes yes
non-const member functions no yes