一、导论
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的分数。然后,编译器搜寻可用函数(可以使这行通过的函数),
- operator + (double/int) 整数可以转换为浮点数
- 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 中讲述)