文章目录
C++面向对象编程(中)
1. 类型转换
1. operator 转换函数
- 写法:operator typename() const {…}
- 不需要写return类型,typename相当于返回类型;
- 一般转换函数不会改变数据,所以要加const;
- 对于 double d = 4 + f:
- 由于整形在前面,所以编译器会先寻找,有没有全局的 “+” 操作符函数,并且两个类型对应;
- 找不到,接着找转换函数;
- 在下面的例子中找到了转换函数,将 f 转换为double类型。
2. 隐式转换构造函数
- 需要有 non-explicit-one-argument ctor:
- non-explicit:构造函数最前面没有explicit关键字;
- one-argument:构造函数最少需要传递一个实参(下图有两个实参,但其中一个有默认值);
- 对于 double d = f + 4:
- 编译器会找到 “+”操作符成员函数,但参数是Fraction;
- 之后再找整型能否转换为Fraction。
3. operator 转换函数和隐式转换构造函数并存
- 可能同时有多种方案可以使用,
存在二义性的转换
,导致编译器报错。 - 对于 Fraction d2 = f + 4(下图):
- 第一种:调用Fraction的“+”操作符成员函数,再将 4 转换为Fraction ;
- 第二种:将f转换为double,为了把double + int转换为Fraction;先将double + int转为int,再转为Fraction。
4. explicit关键字
- 不允许隐式转换,这个例子不允许将整型(double)转换为Fraction;
2. 类的行为像指针(pointer-like classes)
1. 实现类的行为像指针
- 一般指针的普遍操作:“*” 操作符和 “->” 操作符;
- 使类的行为像指针:
- 操作符重载:重载 “*” 操作符和 “->” 操作符;
2. 关于智能指针
- 类内容:
- 数据:模板类T的指针 T* px;
- 操作符重载:
-
T& operator*() const { return *px; } T* operator->() const { return x; }
-
- 注意点(->操作符的特殊性):
- ->作用后得到结果,->还会继续作用下去!!!
- 下图是使用 ->操作符的例子:
-
/* sp调用->操作符,得到px; 但->操作符已经消耗了,为什么还会一个->操作符; ->操作符的特殊性: ->作用后得到结果,->还会继续作用下去!!! */ sp->method(); // 相当于px->method();
-
2. 关于迭代器
- 以双向链表为例,存放上图的Foo类型对象;
- 操作符重载:
-
// node为当前指针 // *操作符:取出数据 reference operator*() const { return (*node).data; } // 调用*操作符取出数据,返回数据的指针 pointer operator->() const { return &(operator*()); }
-
3. 类的行为像函数(function-like classes)
- 类重载 () 函数操作符
4. member template
-
member Template:
- 类本身是一个模板;
- 它当中有一个成员函数F,F又是另一个模板(和类的模板参数不一样);
-
成员模板的作用
- 为了让类更有弹性,让模板类型为子类的可以拷贝构造到模板类型为父类的。
- Derived1是Base1的子类;Derived2是Base2的子类;
- p的类型:pair<Derived1, Derived2>;
- p2的类型:pair<Base1, Base2>;
- 问题:
- 可不可以使用p作为p2的初始值?
- 反过来呢?
- 答案:
- 可以(因为子类可以转换为父类???但这不是用父类指针接受,会发生截断);
- 反过来不行。
-
另一个例子:用指针模拟向上转型
5. 模板泛化
1. 全特化
- 对全部模板参数进行特化;
- template<>;
- 也可以特化void版本;
2. 偏特化
-
个数上的偏特化:
- 个数上偏特化指:比如两个模板参数,绑定了一个后,变成了一个模板参数;
- 偏特化要按照从左到右顺序,依次特化:不能特化第一个,跳过第二个,特化第三个。
-
范围上的偏特化:
- 这两个版本的T不是同一个T,不要混淆。
- 这两个版本的T不是同一个T,不要混淆。
6. template template parameter
-
介绍:
- 模板的模板参数:模板参数本身就是类模板;
- 下图中两个模板参数T是不一样的,不要写成一样的更容易理解;
- 模板的模板参数中的T没有用到;
- 下图中的private数据,使用第一个模板参数用于第二个模板参数的实例化,仅仅是特例;可以用类模板内的任何类型来实例化模板的模板参数。(Container c)
-
作用:
- 模板模板参数,常用于传递没有定义内容类型的容器。
-
使用:
- 传入list失败的原因是,是因为
list
容器实质上是有第二参数的,虽然第二参数有默认的参数,正如我们平常使用的那样,只需要指定一个参数,但是在这里无法通过编译。
- 传入list失败的原因是,是因为
-
注意点:
- 这不是模板模板参数;
- 在使用时,传入的list已经不是模板了,模板参数已经固定。
7. variadic templates(可变模板参数)
- 顾名思义,接受数量不定的模板参数;
- 注意模板参数的声明和使用写法(…);
- 用
sizeof...(args)
获取包含了多少个参数。
8. auto
- 靠编译器自动推导变量的类型;
- 注意:写auto一定伴随赋值操作,不然编译器无法推导类型。
9. range base for
- 基于范围的 for(增强for);
10. reference
-
指针的意义是
指向
; -
引用的意义是
代表
,是别名,意味着它代表谁,它就是谁; -
引用重点:
-
引用必须有初始化;
-
引用不能重新代表其他对象:
- 一旦它指定为某一个对象的引用后,就不能更改了。但是,可以用这个引用来改变它的对象的值,从而达到引用的目的——作为变量对象的别名;
- 下图的
r = x2;
就是改变它代表的对象x
的值。
-
编译器对引用的实现是通过指针实现的:
- 但在逻辑上,要让引用是别名,即引用代表int,那它就是int;
- 所以编译器会制造假象,令引用的大小 == 它代表对象的大小,引用的地址 == 它代表对象的地址;但实际上引用只是一个指针的大小;
- 三种数据类型(常规,引用,指针)在内存中的大致表现(下图):
-
测试举例(编译器的假象):
- 关注引用变量和它代表的变量;
- 关注引用变量和它代表的变量;
-
-
reference常见用途:
- reference通常不用于声明变量,而用于传递参数和返回类型的描述;
- 函数重载需要看函数签名是否可以区别:
- 函数签名组成部分:包括函数名,参数类型,参数个数和顺序,以及它所在类和命名空间;
- 函数签名不包括返回类型(看下图);
- reference不能区分函数签名,参数前面的const也不能区分函数签名;二者在调用的时候形式没有区别,如果可以区分函数签名,编译器就区分不出来;(矛盾)
- 参数后的const(灰色位置),即const member function可以区分函数签名。