C++ 面向对象兼对象模型

conversion function 转换函数

1
2
3
4
Type one;
double two;
Type Three = one + two; //先检测对+运算符的重载,然后检测能否类型转换,注意二义性
double four = one + two; //先检测对+运算符的重载,然后检测能否类型转换,注意二义性
  • 类类型转换为其他类型
1
operator conType() const { return value-conversion}

只要认为合适,一个类中可以有多个转换函数sad

执行运算时,编译器会首先查找是否有运算符重载函数,没有之后再去调用转换函数

有返回值但是不需要显式写出返回值类型

  • 其他类型转换为类类型

    no-explict-one-argument

    设置只需一个实参的带默认参数的构造函数

  • 二义性

    导致原因

    1. 存在对类类型对运算符的重载
    2. 存在类类型对其他类型的转换函数
    3. 存在其他类型转换为类类型的one-argument构造函数

    解决

    explict-one-argument

    将构造函数前加explict关键字,指明该构造函数不可用于类型转换,只可用于实例化对象

point-like classes

包含指针成员

作用在指针上的运算符的重载

智能指针

shared_ptr

封装一个普通指针并添加额外操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//.h
template <class T>
class shared_ptr {

public:
shared_ptr(T* px):p(px){}
T& operator* const() {
return *p;
}
T* operator-> const() {
return p;
}
private:
T* p;
...
}
//.cpp
//X结构体的定义省略
...X
int main() {

shared_ptr<X> sp(new X);
// *sp 相当于*p p为指向动态创建的指向X类型的指针
// sp->function_fromX() sp->相当于p-> ---由于->的特性 sp->得到p的运算结果后仍保留
}

迭代器

需要指向容器中的某个元素

指向链表的迭代器

用户角度需要以迭代器获得指定数据

存储角度该链表迭代器类中存的为指向结点的指针

因此需要作各种运算符重载来通过封装好的迭代器指针来直接获取指定数据

1
2
3
4
5
6
7
8
...
//以两个运算符重载为例
T& operator* const() {
return (*node).data; //node为指向链表结点的指针
}
T* operator-> const() {
return &(operator*()); //取所需数据的地址
}

function-like classes

Functor,仿函数

一种特殊的类

  • 含有对()运算符的重载
  • 继承特殊的标准库中的模板类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//仿函数
template <class pair>
struct First : public unary_function<Pair,typename Pair::first> {

const typename Pair::first& operator() (const Pair& x) const {
return Pair::first;
}
}

int main() {
...
Pair x;
First first(x)(); //第一个()为创建first类型的函数对象,第二个为调用成员函数(执行函数调用操作符)
}

namespace

泛型模板

  • class template类模板

    创建时需要指定类型参数的类型

  • function template函数模板

    编译时系统自动根据实参推断参数类型,常用于算法但是注意对应类型应该定义对运算符的重载函数 -实参推导

  • member template成员模板

    一个函数模板作另一个类模板的成员

    • 多用于类模板构造函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    template<class T1,class T2>
    struct Pair {

    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){} //上转型,U1类继承自T1类

    T1 first;
    T2 second;
    }

    int main() {
    ... //fish,bird为鱼类,鸟类对象 lfish,lbird为草鱼,麻雀对象 省略类模板的定义
    Pair<lfish,lbird> p1();
    Pair<fish,bird> p(p1);
    }


    - 智能指针的应用

    为实现多态性,即一般指针的上转型,智能指针也需要具有该性质

    ### 模板特化

    在模板泛化的基础上对某一种类型进行具体处理

    - full specialization 全局特化

    ``` c++
    ... 忽略了泛型模板的声明,但是必须存在不能缺少,模板特化需要与泛型模板一同出现
    template<>
    struct hash<char> {
    char& operator() (char x) const{ return x; }
    }

    int main() {
    cout<<hash<char>()('a')<<endl;
    }
    • partial specialization 偏(局部)特化

      个数偏特化

      有多个模板参数,但是需要绑定某个或者某几个连续参数来作特殊处理

      1
      2
      3
      4
      5
      template <class T1,class T2,class T3>
      struct hash{}

      template<class T2,class T3> //对哪个类型进行特化处理,就缺哪个模板类型
      struct hash<char,T2,T3> {}

      范围偏特化

      对某种具有范围的类型如指针进行特写

      1
      2
      3
      4
      5
      6
      template <class T>
      struct hash{}

      template<class U>
      struct hash<U*> {} //这样当类型为指针类型时,创建的对象为该类模板对应的类型 U可替换为T 这里的T与泛型的T没有关系

模板模板参数

template template parameter

一个类模板做另一个类模板的模板参数

personal:外层类模板用来指定创建内层类模板的模板类型,内层类模板在外层类模板中被创建并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
template<class T,
template<class T>
class A
>
class B {
...
private:
A<T> inn;
}

int main() {
B<int,A> b; //A不可以为类模板的实例化对象,否则不叫模板模板参数,b对象中含有A<int>类型的成员
}

C++ 标准库

  • 迭代器 Iterators
  • 容器 Containers
  • 仿函数 Functors
  • 算法 Algorithms

C++11

可变模板参数

variadic templates

指定模板中的某个参数的个数可变,即指定包pack,…就是一个包

  • 模板参数包

    …用于template parameters

    template <class T,class... U>

  • 函数参数类型包

    …用于function parameter types

    T function (const T& a,const U&... b)

  • 函数参数包

    …用于function parameters

    function(U...);

1
2
3
4
5
6
7
8
9
//以函数模板中的可变参数为例
template <class T,class... U> //模板类型声明处需要将可变参数用...指出
T function (const T& a,const U&... b){ //函数原型处需要在变量声明与变量名之间用...指出
cout<<a<<endl;
function(b...); //使用参数时也需要在变量名之后用...指出
//这里可变函数模板的使用方法为一个参数加包,第一个参数来逐个递归引用包中的各个参数
}

void function() {} //此处无参function的重载为了上述递归无参数的情况

可边参数大小

1
sizeof...(b);

auto

类型推断,根据变量值来推断变量的类型

注意:必须在定义时初始化

ranged-base for

范围遍历

  • 用decl逐个只读遍历coll容器

    编译会将coll中的元素逐个copy到decl参加运算

1
2
3
4
//decl为基本类型
for(decl : coll) {
statement
}
  • 用decl逐个可读可写遍历coll容器

    编译会将coll中的元素的地址逐个copy到decl参加运算

1
2
3
4
//decl为引用类型
for(decl : coll) {
statement
}

Reference 假象

Reference

  • 底部为一个常量指针,真正所占内存只有4byte

  • 逻辑上为别名,且一旦指定object就不可更改,什么使用的时候什么都跟object一样

1
2
3
4
5
6
int a;
int& r=a;
//sizeof(r)==sizeof(a) true
//&r==&a true
//假象!!!! 所有代码呈现的r均与a具有相同性质
//实际内部为常量指针 额外占内存空间且占4byte字节的内存

常见用途

  • 作函数参数与返回类型

指针与引用的区别

  1. 指针是一个实体,而引用仅是个别名

  2. 引用使用时无需解引用(*),指针需要解引用

  3. 引用只能在定义时被初始化一次,之后不可变;指针可变

  4. 引用没有 const,指针有 const,const 的指针不可变

  5. 引用不能为空,指针可以为空,所以引用更安全

  6. “sizeof 引用”得到的是所指向的object的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小

  7. 指针和引用的自增(++)运算意义不一样

    指针的++操作,会使指针指向下一个对象

    引用的++操作,会是object的值++

signature

签名可作是否可进行函数重载的判断

函数类型 |函数名(函数参数) const| {} ||括起部分为signature签名

  • const

常成员函数可以被const对象或非const对象调用,const对象只会调用const函数而不去调用非const函数,

非const对象会优先调用非const成员函数

1
2
3
4
5
6
void image(const double& im)() {} //1
void image(const double im)() {} //2
void image(const double& im)()const{} //3
void image(const double im)() {} //4
//12会导致2义性
//13作成员函数不会导致2义性

对象模型(Object Model)

虚函数与多态

多态

  • 静态多态

    通过函数重载在编译时指定同名函数执行的函数体,编译时的多态

  • 动态多态

    通过虚函数实现运行时的多态

虚函数

继承体系中,允许派生类重写与父类同名的函数,并在调用时通过指针或者引用来调用父类或者子类的同名函数

继承体系中父类与子类重名

  • 父类声明为虚函数

    子类将override重写父类同名虚函数,子类仅含重写后的虚函数,而不包含父类的同名虚函数

  • 父类未声明为虚函数

    子类将override重写父类同名虚函数,且子类中将仍包含父类的同名函数,若要调用父类的被隐藏的同名函数可通过::调用

    注意: 两者区别在于指针调用时,指向派生类对象的指针上转型为指向父类对象的指针,第二种情况将仍调用父类的同名函数

    虚机制

vptr虚指针

只要具有虚函数的类对象都会额外分配一个且仅一个4byte的内存给虚指针来关联各虚函数

vtbl虚表

在编译时会为每一个具有至少一个虚函数的类绑定一个虚表

存放绑定类的包含的所有虚函数的函数指针

动态绑定

条件

  1. 通过指针或者引用调用

  2. 上转型

引用绑定

指针指向

  1. 调用虚函数

处理

触发虚机制

内部实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class father{
father(){
//这里将隐含对vptr的赋值,使vptr指向vtbl
};
virtual void function(){};
}

class son{
son(){
//这里将隐含对vptr的赋值,使vptr指向vtbl
};
virtual void function(int a){ cout<<a<<endl; };
}
int main() {
son sn;
father* p = &sn; //上转型
father& a= sn; //上转型
p->function(); //属于多态
a.function(); //属于多态
sn.function(); //不属于多态
}

//指针虚机制原理
p->function();
//(*(p->vptr)[n])(p)
//或(* p->vtbl[n])(p)```

静态绑定

  • 通过非引用对象调用对应类的方法
  • 内部实现为call+函数地址

this指针

template method 设计模式

Const

  • const常成员函数 添加const声明,不会对本类数据进行更改,且不能包含对非常成员函数的调用
  • non-const非常成员函数 无const声明,可能对本类数据进行更改,且可包含对常成员函数的调用
  • 常对象 仅能调用本类的const常成员函数
  • 非常对象 均可调用

函数重载

若一个类同名常成员函数与非常成员函数同时存在

  • const常对象仅能调用该类对应同名的const版本
  • 非常对象仅能调用该类同名的非const版本

new,delete运算符

  • new

    动态创建分三步 1.创建指定大小的内存空间 2.指针类型转型 3.调用对象构造函数

  • delete

    删除分两步 1.调用对象的析构函数 2.删除指针指向的内存空间

重载

将new的第一步操作与delete的第二步操作做特殊处理

  • 对全局new,delete运算符的重载

    ::operator new ::operator new[] ::operator delete ::operator delete[]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //...
    inline void* operator new(size_t size1) {
    return malloc(size1);
    }
    inline void* operator new[](size_t size2) {
    return malloc(size2);
    }
    inline void operator delete(void*p,size_t size) { //可以省略第二个参数
    free(p);
    }
    inline void operator delete[](void*p,size_t size2) { //可以省略第二个参数
    free(p);
    }

    size1/size2详解

    size1大小对象的内存大小

    size2 vc编译环境下

    size2=arrays.length*sizeof(obj)+4

    • 需要添加4字节的cookie头,值为实际元素个数,指明构造函数与析构函数的执行次数,从上而下逐个构造,析构相反

    注意:对全局new,delete运算符重载需要慎重

  • 对类成员new,delete运算符的重载

    new,delete运算符重载函数,该重载范围仅影响对应类类型的对象

  • 对类成员new[],delete[]运算符的重载

    new[],delete[]运算符重载函数,该重载范围仅影响对应类类型的对象

  • placement argument 对new,delete重载时有多个函数参数 多参重载

    对operator new的重载

    第一个参数必须是size_t类型

    其余位置参数的初始值指定方式为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    int*p = new(argument)int;



    operator delete的重载

    来清除创建失败的内存,仅在new运算符执行抛出异常时才会执行这些operetor delete(不同编译环境情况不同)

    一般delete语句并不会调用placement delete重载函数,而只会调用一般重载函数

    用来处理异常的,即使没有也编译通过,不再处理异常

    ```c++
    inline void* operator new(size_t size1,int x) {
    //...x
    return malloc(size1);
    }

    inline void operator delete(void* p,int x) {
    //...x
    free(p);
    }

    int main() {
    int x;
    obj* p=new (x) obj;
    }

new与malloc的区别

  1. new是操作符,而malloc是函数
  2. malloc开辟内存时需要指定大小并返回void*,需要将指针进行类型转换才能操作对象,new运算符开辟内存时需要指定类型并直接返回对应类型的指针
  3. malloc和new都是在堆上开辟内存的,new在调用的时分配内存后,还会调用构造函数初始化,而malloc仅分配内存
  4. new开辟内存失败抛出异常,malloc返回null
  5. new运算符可以被重载
  6. malloc可以分配任意字节,new 只能分配实例所占内存的整数倍数大小