面向对象高级开发

摘自侯捷老师的《面向对象高级开发(上下两部曲)》

谈谈const

const常规用法

  • 如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

    char greeting[] = "Hello"; 
    char* p= greeting;          \\ non-const pointer,non-const data
    const char* p =greeting;    \\ non-const pointer,const data
    char* const p = greeting;   \\ const pointer,non-const data
    const char* const p = greeting;  \\const pointer,const data 
    
  • STL迭代器系以指针为根据塑模出来,所以迭代器的作用就像个T指针。声明迭代器为const就像声明指针为const一样(即声明一个T const指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。如果希望迭代器所指的东西不可被改动(即希望STL模拟一个const T* 指针),你需要的是const_iterator:

vector<int>vec;
const vector<int>::iterator iter = vec.begin();  //iter的作用像个T* const
*iter =10;                                      //没问题,改变iter所指物
++iter;                                        // 错误,iter是const
vector<int>:: const_iterator cIter = vec.begin();  //cIter的作用像个const T*
*cIter =10;                                     //错误,*cIter是const
++cIter;                                       //没问题,改变cIter

const修饰函数时

  • const在函数名前面的时候表示修饰函数返回值

  • const在函数名后面的时候表示修饰成员函数,该成员函数只会读取但不改变classdata

    const object
    (data members不得变动)
    non-const object
    (data members可变动)
    const member function
    (保证不更改data members)
    non-const member functions
    (不保证data members不变)
    ×

    Tips:当成员函数的constnon-const版本同时存在,const object只会调用const版本,non-const object只会调用non-const版本。

    const Sring str("hello world!");
    str.print();
    //如果当初设计String::print()时未声明const,那么上行经由const object调用non-const member function会出错。此非吾所愿。
    

    Tips:也就说在声明函数的时候,只要函数没有改变数据,就加上const。

转换函数

  • 第一种
class Fraction
{
public:
    Fraction(int num, int den =1): m_numerator(num), m_denominator(den) { }
    operation double() const {
        return (double)(m_numerator / m_denominator)
    }

private:
    int m_numerator;
    int m_denominator;
}

//用法
Fraction f(3, 5);
double d = 4 + f; //调用operator double()将f转为0.6
  • 第二种:non-explicit-one-argument ctor
class Fraction
{
public:
    Fraction(int num, int den=1): m_numerator(num), m_denominator(den){ }
    
    Fraction operator+(const Fraction& f){
        return Fraction(......); //操作符重载返回Fraction
    }
    
private:
    int m_numerator;
    int m_denominator;
}

//用法
Fraction f(3,5);
Fraction d2=f+4;  //调用non-explict ctor 将4转为Fraction(4,1),然后调用operator+

C++子类的构造函数后面加:冒号的作用

在C++类的构造函数中经常会看到如下格式的写法:

MyWindow::MyWindow(QWidget* parent , Qt::WindowFlags flag) : QMainWindow(parent,flag)

上述语句中单冒号(:)的作用是表示后面是初始化列表,一般有三种使用场景。

  • 对父类进行初始化

调用格式为“子类构造函数 : 父类构造函数”,如下,其中QMainWindowMyWindow的父类:

MyWindow::MyWindow(QWidget* parent , Qt::WindowFlags flag) : QMainWindow(parent,flag)
  • 对类成员进行初始化

调用格式为“构造函数 : A(初始值),B(初始值),C(初始值)……”,如下,其中A、B、C分别是类的成员变量:

class rectangle //头文件中类定义
{
public:
    rectangle( int pointX, int pointY, int Width, int Length );
private:
    CPoint m_point;
    int m_Width;
    int m_Length;
};

rectangle::rectangle(int pointX, int pointY, int Width, int Length) : m_point(pointX,pointY),m_Width(Width),m_Length(Length)//源文件中构造函数实现
{
    todo......
}

当然,上面构造函数的实现与下面的写法等价

rectangle::rectangle(int pointX, int pointY, int Width, int Length)//源文件中构造函数实现
{
    m_point.X = pointX;
    m_point.Y = pointY;
    m_Width   = Width;
    m_Length  = Length;
    todo......
}
  • 对类的const成员变量进行初始化

由于const成员变量的值无法在构造函数内部初始化,因此只能在变量定义时赋值或使用初始化列表赋值。

对于2、3中的应用场景,有以下两点说明:

1、构造函数列表初始化执行顺序与成员变量在类中声明顺序相同,与初始化列表中语句书写先后无关。

2、相对于在构造函数中赋值,初始化列表执行效率更高。

point-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->method(); // == px->method();
    
  • 关于迭代器

    template<class T, class Ref, class Ptr>
    struct __list_iterator
    {
        .......
        typedef __list_node<T>* link_type;
        link_type node;
        reference operator*() const { return (*node).data; }
        pointer operator->() const { return &(operator*()); }
        .......
    }
    
    template <class T>
    struct __list_node
    {
        void* prev;
        void* next;
        T data;
    }
    
    //用法
    list<Foo>::iterator ite;
    ...
    *ite;  //获得一个Foo Object;
    ite->method();
    //意思是调用Foo::method();
    //相当于(*ite).method();
    //相当于(&(*ite))->method();
    

namespace经验谈

using namespace std;
//----------------------------------------------
#include <iostream>
#include <memory> //shared_ptr
namespace jj01
{
void test_member_template() { ...... }
} //namespace
//----------------------------------------------
#include <iostream>
#include <list>
namespace jj02
{
template<typename T>
using Lst = list<T, allocator<T>>;
void test_template_template_param() { ....... }
} //namespace
//---------------------------------------------

//用法
int main(int argc, char** argv)
{
    jj01::test_member_template();
    jj02::test_template_template_param();
}

class template,类模板

类是模板

//类模板
template<typename T>
class complex
{
public:
	complex (T r = 0, T i = 0): re(r), im(i) { }
    complex& operator += (const complex&);
    T real () const { return re; }
    T imag () const { return im; }
private:
    T re, im;
    
    friend complex& __dopal (complex*, const complex&);
}

//使用
complex<double> c1(2.5, 1.5);
complex<int> c2(2,6);

function template,函数模板

函数是模板

stone r1(2,3), r2(3,3), r3;
r3 = min(r1, r2);
//函数模板
template <class T>
inline
const T& min(const T& a,const T& b){
    return b < a ? b:a;
}
class stone
{
public:
    stone(int w, int h, int we):_w(w), _h(h), _weight(we){ }
    bool operator< (const stone& rhs) const {return _weight < rhs>_weight; }
private:
    int _w, _h, _weight;
}

member template,成员模板

模板本身是一个类的成员

template <class T1, class T2>
struct pair{
    typedef T1 first_type;
    typedef T2 second_type;
    
    T1 first;
    T2 second;
    
    pair():first(T1()), second(T2()) { }
    pair(const T1& a, const T2& b):first(a), second(b) { }
    
    //成员模板
    template <class U1, class U2>
    pair(const pair<U1, U2>& p):first(p.first), second(p.second) { }
} 

specialization,模板特化

与模板泛化相反。模板泛化是使用的时候再定义具体类型。而模板特化是使用之前就定义具体类型。

//模板泛化
template <class Key>
struct hash { };
//模板特化
template<>
struct hash<char>{
	size_t operator() (char x) const { return x; } 
};

template<>
struct hash<int>{
	size_t operator() (int x) const { return x; } 
};

template<>
struct hash<long>{
	size_t operator() (long x) const { return x; } 
};

//使用
count << hash<long>() (1000);

patial specialization,模板偏特化

  • 个数的偏

    原来的泛化模板占用1个字节去表示占一位的bool,浪费内存。所以使用模板偏特化

    template<typename T, typename Alloc=....>
    class vector
    {
        ....
    };
    
    //将typename T 与 bool绑定
    template<typename Alloc=...>
    class vector<bool, Alloc>
    {
        ...
    }
    
  • 范围的偏

    template <typename T>
    class C
    {
        ...
    };
    
    template <typename T>
    class C<T*>
    {
        ...
    };
    
    //使用
    C<string> obj1;
    C<string*> obj2;
    

template template parameter,模板模板参数

  • 例1

    template<typename T, 
    					template <typename T> 
    					class Conrtainer<T> >
    class XCls
    {
    private:
        Container<T> c;
    public:
        .......
    };
    
    template<typename T>
    using Lst = list<T, allocate<T>>;
    
    XCls<string, list> mylst1;//error,list作为容器,可能不止string这一个模板参数。
    XCls<string, Lst> mylst2;
    
  • 例2(SmartPtr只有一个模板参数)

    template<typename T, 
    					template <typename T> 
    					class SmartPtr<T> >
    class XCls
    {
    private:
        SmartPtr<T> sp;
    public:
        XCls(): sp(new T) { }
    };
    
    XCls<string, shared_ptr> p1;
    XCls<string, unique_ptr> p2;//error
    XCls<string, weak_ptr> p3;//error
    XCls<string, auto_ptr> p4;
    

variadic templates(since C++11)

void print()
{
}

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

//使用
print(7.5, "hello", bitset<16>(377), 42);
//第一次打印时,7.5是firstArg,其他是args;第二次打印时,"hello"是firstArg,其他是args;以此类推,最后一次分为42和空包。

...就是一个所谓的pack(包)

用于template parameters,就是template parameters pack(模板参数包)

用于function parameters types,就是function parameters types pack(函数参数类型包)

用于function parameters,就是function parameters pack(函数参数包)

auto(since C++11)

list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);
list<string> c;
...
auto ite = find(c.begin(), c.end(), target);
list<string> c;
...
auto ite;
ite = find(c.begin(), c.end(), target);//error,使用auto时,等号右边一定要有值。

ranged-base for(since C++11)

for (decl : coll) {
    statement;
} 
for (int i : {2, 3, 5, 7, 9, 13, 17, 19 }){
    cout << i << endl;
}
vector<double> vec;
...
for (auto elem : vec){//elem数值改变不会改变vec中的元素
    cout << elem << endl;
}

for (auto& elem : vec){//elem数值改变会改变vec中的元素,因为elem代表vec中元素
    elem *= 3;
}

reference

int x = 0;
int* p = &x; // p is a pointer to x/变量p point to int,初始化为x的地址
int& r = x;  // r is a reference to x/变量r reference to int,初始化为x,代表x
int x2 = 5;

r = x2;      //r不能重新代表其他物体。现在r,x都是5
int& r2 = r; //现在r2是5(r2代表r;亦相当于代表x)

例子(object和其reference的大小相同,地址也相同(全都是假象)):

typedef struct Stag { int a,b,c,d} S;
int main(){
    double x = 0;
    double* p = &x;  //p指向x,p的值是x的地址
    double& r = x;   //r代表x,现在r,x都是0
    
    cout << sizeof(x) << endl;  //8
    cout << sizeof(p) << endl;  //4
    cout << sizeof(r) << endl;  //8
    cout << p << endl;          //0065FDFC
    cout << *p << endl;			//0
    cout << x << endl;			//0
    cout << r << endl;			//0
    cout << &x << endl;			//0065FDFC
    cout << &r << endl;			//0065FDFC
    
    S s;
    S& rs = s;
    cout << sizeof(s) << endl;  //16
    cout << sizeof(rs) << endl;	//16
    cout << &s << endl;			//0065FDE8
    cout << &rs << endl;		//0065FD68
}

reference的常见用途

void func1(Cls* pobj) { pobj->xxx(); }
void func2(Cls obj) { obj.xxx(); }
void func3(Cls& obj) { obj.xxx(); } //传值和传引用的被调用端写法相同,很好
...
Cls obj;
func1(&obj); //接口调用不同,困扰
func2(obj); 
func3(obj);  //调用端接口相同,很好

reference通常不用于声明变量,而用于参数类型(parameters type)和返回类型(return type)的描述。以下被视为same signature(所以二者不能同时存在):

double imag(const double& im) {...}
double imag(const double im) {...}  //Ambiguity

Inheritance(继承)关系下的构造和析构

举个例子,猪(Derived)是哺乳动物(Base)。

base classdtor必须是virtual,否则会出现undefined behavior

构造由内而外

Derived的构造函数首先调用Basedefault构造函数,然后才执行自己。

Derived::Derived(...) : Base() { ... }

析构由外而内

Derived的析构函数首先执行自己,然后才调用Base的析构函数。

Derived::~Derived(...) {... ~Base() };

Composition(复合)关系下的构造和析构

Container包含Component

构造由内而外

Container的构造函数首先调用Componebtdefault构造函数,然后才执行自己。

Container::Container(...) : Component() { ... }; 

析构由外而内

Container的析构函数首先执行自己,然后才调用Component的析构函数。

Container::~Container(...) { ... ~Component() };

Inheritance+Conposition关系下的构造和析构

构造由内而外

Derived的构造函数首先调用Basedefault构造函数,然后调用Componentdefault构造函数,然后才执行自己。

Derived::Derived(...) : Base(),Component() { ... };

析构由外而内

Derived的析构函数首先执行自己,然后调用Component的析构函数,然后调用Base的析构函数。

Derived::~Derived(...) { ... ~Compoent(),Base() };

对象模型(Object Model):关于vptr和vtbl

image-20231004103103217

class A{
public:
    virtual void vfunc1();
    virtual void vfunc2();
    void func1();
    void func2();
private:
    int m_data1, m_data2;
};

class B{
public:
    virtual void vfunc1();
    void func2();
private:
    int m_data3;
}

class C{
public:
    virtual void vfunc1();
    void func2();
private:
    int m_data1, m_data4;
}

//总结
1.父类中虚函数都会被子类继承,子类可以重写父类中的虚函数。即子类要想重写父类的函数,需要在父类中的哪个函数之前加上关键字virtual。
2.子类继承的是父类中函数的调用权,而不是内存。

对象模型(Object Model):关于Dynamic Binding

动态绑定:

将汇编语言call及以上的代码翻译成C++语言,如下:

(*(p->vptr)[n])(p)
或
(*p->vptr[n])(p)

动态绑定需要满足三个条件:

  1. 通过指针
  2. 虚函数
  3. 向上转型
  • 24
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值