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
模板
- 成员模板
- 泛化是模板,特化是对于某些情况的独特的设计
-
全特化与偏特化:
-
类模板
```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
//用法 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
考虑一种情况: 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
先浅谈动态绑定, 满足以下三个条件编译器将会动态绑定
- 对象指针
this->Serialize();
- 向上转型:
CMyDoc(derive)
→CDocument(base)
- 调用虚函数:
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 |