GeekBand C++ week3

字数 1490阅读 56

一、导论


1.I和II的区别

I:C++面向对象程序设计
II: C++程序设计兼谈对象模型

勿在浮沙筑高台

2.C++两大技术主线

Generic Programming 泛型编程 template
Object-Oriented Programming 面向对象编程 class

二、转换函数


I:由其它对象向该类型对象转换
II:由该类型对象向其它类型对象转换

convertion function

转型函数
语法结构:operator + typename(只要是之前声明的,包括基本和构造类型) + () + const

class Fraction
{
public:
    Fraction(int num, int den=1) : m_numerator(num), m_denominator(den) { }
    operator double() const {
        return (double) (m_numerator / m_denominator);
    }
private:
    int m_numerator; //分子
    int m_denominator; //分母
};

转换函数样例: operator double() const
特征:

  • 静态函数,表不改变对象内数据。
  • 无返回类型声明,默认目标类型为返回类型。有效的防止声明和返回值(目标类型不对应)
  • 无参数,转换不需要参数。
Fraction f(3, 5);
double d=4+f; //调用operator double() 将 f 转为0.6

调用示例:
在这段代码执行时,先构造一个分子为3,分母为5的分数。然后,编译器搜寻可用函数(可以使这行通过的函数),

  1. operator + (double/int) 整数可以转换为浮点数
  2. operator double (fraction) 以满足operator +对左右操作数的类型要求。

non-explicit-one-argument ctor

explicit是声明符,
没有explicit声明的单实参构造函数,可以只传入一个实参。
通过隐式调用,以构造的方式,把其它类型转换为该类型。

class Fraction
{
public:
    Fraction(int num, int den=1) : m_numerator(num), m_denominator(den) { } //non-explicit-one-argument ctor,只有一个参数有默认值
    Fraction operator + (const Fraction& f) {
        return Fraction(......)
    }
private:
    int m_numerator; //分子
    int m_denominator; //分母
};

区分argument和parameter argument:实参;parameter:参数,形参

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

operator +需要左右都为Fraction类型,于是搜寻转换函数,将4转换为4/1进行运算

convertion function vs. non-explicit-one-argument ctor

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; //分母
};

//test
Fraction f(3, 5);
Fraction d2=f+4; //[Error] ambiguous 二义的

出现两种路径:将4转为Fraction 4/1,然后用类内+计算出d2;将f转为double 0.6,然后用全局+,在赋值时调用单参构造函数,将double和转换为Fraction 4/1。
编译器在面对两条路径时不能比对优劣,会报错。

explicit-one-argument ctor

explicit 明确的,清楚的;一般修饰在构造函数前,表示构造函数需要显式调用(即以构造方式调用)。

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; //分母
};

//test
Fraction f(3, 5);
Fraction d2=f+4; //[Error] conversion from 'double' to 'Fraction' request

处理后,operator = 左右类型不同。因为4无法通过隐式调用构造函数变为Fraction 4/1,只能将 f 转换为double 0.6,而在赋值时,隐式调用再次失败,左右类型不同,赋值运算失败。

综合:设计模式 Proxy 代理

template<class Alloc>
class vector<bool, Alloc> //对容器vector进行模板偏特化
{
public:
    typedef __bit_reference;
protected:
    reference operator[ ] (size_type n) { //传出第n个位置的bool值,却以reference传回,形成代理
        return *(begin() + difference_type(n));
}
...
struct __bit_reference {
    unsigned int* p;
    unsigned int mask;
    ...
public:
    operator bool() const { return !(!(*p & mask)); //reference代理bool,自然需要转换函数
    ...
}

三、pointer-like template


智能指针

template<class T>
class shared_ptr
{public:
    //操作符重载
    T& operator* () const //dereference解参考,取指针所指的东西
    { return *px; }   //通用写法
    T* operator-> () const   //通过对象调用成员
    { return px; }
    //构造函数
    shared_ptr(T* p) : px(p) { }

private:
    T* px; //指针成员
    long* pn;
   ...
};

典型组成:指针成员+操作符重载
’->‘ 的特殊行为:‘->’ 符号作用的结果,继续受 ‘->' 作用。

迭代器

用于遍历容器,指针代表容器内的一个元素
含有++,--操作,涉及指针的移动

template<class T, class Ref, class Ptr>
struct __list_iterator {
    typedef __list_iterator<T, Ref, Ptr> self;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef __list_node<T>* link_type;

    link_type node;

    bool operator== (const self& x) const { return node == x.node; }
    bool operator!= (const self& x) const { return node != x.node; }

    reference operator* () const { return (*node).data; }
    pointer operator-> () const { return &( operator* () ); }   //为了在双向链表中取得data域的地址

    self& operator++ () { node = (link_type)(*(node).next); return *this; }
    self operator++ (int) { self tmp = *this; ++*this; return *this; }
    self& operator-- () { node = (link_type)((*node).next); return *this; }
    self operator-- (int) { self tmp = *this; --*this; return *this; }
};

四、function-like template


'()' function call operator函数
如果一个东西可以接受 '()' 的操作,则称为function / function-like

functer仿函数

//GNU C添加
template <class T>
struct identity : public unary_function<T, T>{   //视为、证为是同一物体
    const T&
    operator() (const T& x) const { return x; }
};
template <class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
    const typename Pair::first_type&
    operator() (const Pair& x) const { return x.first; }
};
template <class Pair>
struct select2nd : public unary_function<Pair, typename Pair::first_type> {    const typename Pair::second_type&
    operator() (const Pair& x) const { return x.second; }
};
//标准库
template <class T1, class T2>
struct pair {
    T1 first;
    T2 second;
    pair() : first(T1()), second(T2()) { }
    pair(const T1& a, const T2& b) : first(a), second(b) { }
    ...
};
//base classes
template <class Arg, class Result>
struct unary_function {
    typedef Arg argument_type;
    typedef Result result_type;
};

在标准库中仿函数普遍继承一些仅含typedef,size为0的父类(base classes)

五、namespace


建立名称区隔,解决软件开发过程中名称冲突的问题

using namespace std;
//----------------------------------------
#include <iostream>
#include <memory> //sharedptr
namespace jj01
{
void test_member_template()
{ ... }
} //namespace

int main(int argc, char** argv)
{
    jj01::test_member_template();
}

六、template 模板


抽出认为可以被使用者任意指定的类型,以实现代码复用

class template 类模板

template<typename T>
class complex
{
public:
    complex (T r = 0, T i = 0) : re (r), im (i) { }
    comple& operator+= (const complex&);
    T real () const { return re; }
    T imag () const { return im; }
private:
    T re, im;
    ...
}
{
   complex<double> c1(2.5, 1.5);
   complex<int> c2(2, 6);
}

function template 函数模板

template <class T>
inline
const T& min (const T& a, const T& b)
{
    return b < a ? b : a;
}
stone r1(2, 3), r2(3, 3), r3;
r3 = min(r1, r2);
}

函数模板在调用时不需显式声明参数类型,编译器会根据实参类型进行推导(argument deduction)
编译器编译模板到目标文件只是半成品,也只保证模板本身的语法通过性,链接时根据调用搜寻是否具有合法操作路径,决定能否生成可执行代码

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) { }
    //member template
    template<class U1, class U2>
    pair(const pair<U1, U2>& p) : first(p.first), second(p.second) { } //满足类型转换条件下建立一种拷贝构造函数,让构造函数更有弹性
};

满足类型转换条件(尤其是将子类向父类转化,up-cast)下建立一种拷贝构造函数,让构造函数更有弹性

(full) specialization 模板特化

与泛化相对,模板本身就是泛化,可能会需要对某些特殊类型进行特殊设计

//泛化标准库
template<class key>
struct hash { };
//特化
template<>
struct hash<char> {
    size_t operator() (char x) const { return x; }
};
template<int> 
struct hash<int> {
    size_ operator() (int x) const { return x; }
};
template<>
struct hash<long> {
    size_t operator() (long x) const { return x; }
};

特化可以有无数个版本

partial specialization 偏(局部)特化

个数上的偏

template<typename T, typename Alloc=...>
class vector
{
...
};
templaste<typename Alloc=...>
class vector<bool, Alloc> //将T与bool绑定,实现只针对一个进行特化
{
..
}

范围的偏

//泛化
template <typename T>
class C
{
...
};
//特化
template <typename T> //可以完全将T替换成U,完全成立
class C<T*>                   //针对任意类型的指针进行特化
{
...
};

template template parameter 模板模板参数

template<typename T,    
                template <typename T>
                    class Container
                >   //提供指定容器与指定类型的组合
class XCls 
{
private:
    Container<T> c;
public:
    ...
};
template<typename T>
using Lst = list<T, allocator<T>>;
    XCls<string, Lst> mylst;   //注意,容器具有多于一个参数时,会发生编译错误,需要以上处理

模板<>中typename和class共通,早先没有typename关键字,借用class

七、C++11的三个特性


语法糖(Syntactic sugar) 指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

variadic templates(since C++11) 数量不定的模板参数

void print() //当后边一包已经为空时,调用该函数,以完整程序
{
}
template<typename T, *typename... Types*>
void print (const T& firstArg, const Types&... args)
{
    cout<<firstArg<<endl;
    print(args...);
}

将函数模板的参数分成一个和一包(包中参数具有任意类型和个数)
sizeof...(args) 查看args的个数

关键字auto(since C++11)

//正常版
list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);
//auto 推导版
list<string> c;
...
auto ite = find(c.begin(), c.end(), target);

用auto方式声明后,编译器可通过赋值来推导ite的类型,减少了编码工作量。
在编程过程中尽量明确类型的使用是编程素养的体现。

ranged-base for(since C++11)

for(decl :coll) {
    statement
}

将一个容器中的元素,逐一赋值给decl,至容器中最后一个元素也完成对decl的赋值。

八、reference


变量有三种,value,pointer,reference

int x=0; //int 4byte
int* p = &x; //32bit OS pointer 4byte
int& r = x; //r 代表 x,现在 r=x=0。此时 r 应逻辑上看作整数,只是实现上是指针
int x2 = 5;

r = x2; //r 不能重新代表其他物体。现在r,x都是5

声明reference一定有初值,且设定后不能改变,而指针可变。
object和其reference的大小相同,地址也相同。
reference通常不用于声明变量,而用于参数类型(parameters type)和返回类型(return type)的描述。
reference就是一种漂亮的pointer。pass by reference保持了指针传递速度快的同时,保持了和pass by value相同的函数接口。因此同函数名,同参数个数、类型,一个是reference,一个是value,为same signature。C++的函数签名,不包含返回类型。

(面向对象部分将集中在C++ week 4 中讲述)

推荐阅读更多精彩内容