Classes的两个经典分类
Class without pointer member(s)
  complex
Class with pointer member(s)
  string


Header中的防卫式声明
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
//code
#endif

 

inline function
函数若在class body内定义完成,便自动成为inline function的候选人
class body之外定义的函数,需要加上inline关键字,以此建议编译器将其编译为inline function


constructor(ctor, 构造函数)
class complex
{
  public:
    complex (double r = 0, double i = 0) // 默认实参
      : re (r), im (i)  // 初值列
    { }
};
构造函数中用初值列初始化变量,而最好不在函数体中用"="来赋值
一个变量的构造有两个阶段:初始化,赋值;所以使用初值列的机制效率更好(省去了一个赋值阶段)
ctor可以有多个重载


ctor放在private区
Singleton单例模式
class A
{
  public A& getInstance();
    setup() {...};
  private:
    A();
    A(const A& rhs);
    ...
};

A& A::getInstance()
{
  static A a;
  return A;
}

A::getInstance().setup();
外界不能直接创建对象的实例,只能通过class内的函数来创建


const member function(常量成员函数)
double real() const { return re; }
这里的const关键字声明将一定不改变类内的成员变量re

例如:若不写上述的const,以下代码块将会编译错误
{
  const complex c1(1, 2)
  cout << c1.real() << c1.imag();
}
因为使用者创建了一个const的对象c1,即规定c1是一个常量,其中的成员变量也将是const的,没有可能被改变;而在调用成员函数real()时,并未声明其为一个常量成员函数,即类内的成员变量re将"有可能"被改变,这是矛盾的


参数传递:pass by value VS. pass by reference(to const)
按值传递将原封不动的复制参数,按引用传递相当于传递参数的地址(底层是指针);所以通常传引用效率更好
例如:complex& operator += (const complex&);
目的是按reference传入一个complex对象(的引用),并声明传入的对象本身禁止被函数修改,[pass by reference(to const)]
参数的传递尽量by reference


返回值传递:pass by value VS. pass by reference
什么情况下可以pass by reference(to const)?
什么情况下可以return by reference?
例如后面的"__doapl"函数,它的第一参数将会被改动,第二参数不会被改动
此时第二参数可以pass by reference(to const),第一参数可以return by reference

下面是一个return by reference的正确例子:
inline complex& __doapl(complex* ths, const complex& r)
{
  ths->re += r.re;
  ths->im += r.im;
  return *ths;
}
inline complex& complex::operator += (const complex& r)
{
  return __doapl(this, r);
}
注意第一参数传入的是一个指针,指向的对象不是函数体内临时创建的local变量,而是函数外已经存在的某个变量,因而可对其return by reference

什么情况下不能return by reference?
函数体内的local变量不能return by reference。因为当函数结束,其local变量也消亡了,若return by reference传出的一个"地址"指向的内容已经坏掉了
例如:
inline complex& __doapl(complex* ths, const complex& r)
{
  return (ths->re + r.re);
}


friend(友元)
类中的private成员re与im不能被外界直接取用(可通过类内函数取用),但类内的友元函数可以直接取得其"朋友"的私有成员
class complex
{
  private:
    double re, im;
    friend complex& __doapl(complex*, const complex&);
};

inline complex& __doapl(complex* ths, const complex& r)
{
  ths->re += r.re; //自由取得friend的private成员
  ths->im += r.im;
}

相同class的各个objects互为friends(友元)
class complex
{
  public:
    int func(const complex& param)
    { return param.re + param.im; }
    // 这里直接取用了"朋友"的私有成员
  private:
    double re, im;
};

{
  complex c1(2,1);
  complex c2;
  // c1与c2互为friends
  c2.func(c1);
}


编写一个class的小总结
数据一定放private中
参数传递与返回值传递尽可能by reference
在class body中的成员函数,需要加const的一定要加(常量成员函数)
ctor尽量用它的初值列机制


Big Three 三个特殊函数
拷贝构造,拷贝赋值,析构函数
一定要在拷贝赋值中检查是否self assignment
inline String& String::operator=(const String& str)
{
  if(this == &str)
    return *this;
  //...
}


内存管理
new:先分配内存,再调用ctor
Complex* pc = new Complex(1,2);
编译器转化为:
Complex *pc;
void* mem = operator new(sizeof(Complex)); //分配内存
pc = static_cast<Complex*>(mem); //强制类型转换
pc->Complex::Compelex(1,2); //调用构造函数
//Complex::Compelex(pc相当于隐藏在此的一个this指针,1,2);

delete:先调用dtor,再释放内存
delete pc;
编译器转化为:
Complex::~Complex(pc); //析构函数
operator delete(pc); //释放内存

注:"operator new()"和"operator delete()"是特殊的C++系统函数;前者用于分配内存,其内部调用malloc();后者释放内存,其内部调用free()


静态数据成员与静态成员函数
class内静态的数据只存在一份(存在于"全局/静态存储区")
静态函数没有this指针,所以静态函数只能处理位于全局/静态存储区的静态数据,而不能访问class内的非静态数据成员
例如:设计一个银行账户
class Account
{
public:
  static double m_rate; //利率
  static void set_rate(const double& x)
  { m_rate = x; }
};

double Account::m_rate = 0.01; //静态数据成员的赋值方式1(静态数据成员的初始化)

int main()
{
  Account::set_rate(0.02); //静态数据成员的赋值方式2(通过class name调用静态函数)
 
  Account a; //静态数据成员的赋值方式3(通过object调用静态函数)
  a.set_rate(0.03);
}


class template类模板
示例:
template<typename T>
class complex
{
public:
  complex(T r = 0, T i = 0)
    :re (r), im (i)
  { }
private:
  T re, im;
};

{
  complex<double> c1(2.0, 1.0); //class中的"T"会全部替换成"double"
  complex<int> c2(2, 1); //class中的"T"会全部替换成"int"
}

 

补充一个类模板的例子:
const std::size_t DefaultStackSize = 1024;
template<typename T, std::size_t n = DefaultStackSize>
class Stack
{
public:
  void Push(const T const& element);
  ...
private:
  std::vector<T> m_Members;
  std::size_t m_nMaxSize = n;
};

类模板参数可以是任意类型名或常量(常量仅限int或enum类型);
模板实参可以是一个int型或enum型的常量(此处是size_t,其实质是无符号整型;size_t是标准C库中定义的,在32位系统中为unsigned int,占4个字节;在64位系统中为 long unsigned int,占8个字节);
n是编译时定义的常量,n可以有默认值;
成员变量m_nMaxSize用n进行了初始化;


function template函数模板
例如有一个stone类:
class
{
public:
  stone(int w, int h, int we)
    :width(w), height(h), weight(we)
    { }
  bool operator < (const stone& rhs) const
  { return weight < rhs.weight; }
private:
  int width, height, weight;
};

要创建两个stone对象,并且比较stone的重量:
stone r1(1,2,3), r2(2,3,4);
r3 = min(r1, r2);

若有函数模板:
template<class T>
inline const T& min(const T& a, const T& b)
{
  return b < a ? b : a;
}
则函数模板中的"T"将会全部替换成类名"stone"
因为r1和r2都是stone类,编译器会对function template进行argument deduction参数推导

而在进行对象的大小比较时,因为"T"为"stone",于是调用stone::operator<

 

补充一个函数模板的例子:
template <typename T> T Max(T a, T b)
{
  return a>b?a:b;
}

1.对于不同的实参类型,模板函数定义了一族函数;
2.当传递模板实参时,函数模板依据实参的类型进行实例化;
3.可以显式指定模板的实参类型,例如:
对函数模板

template <typename T>
inline T const& Max(const T const& a, const T const& b)
{...}

以下代码

Max<double>(1,2)
函数模板会实例化一个函数
inline double const& Max(const double const& a, const double const& b)
{...}
此时参数1,2也将被转换成double型;
4.函数模板可以重载;
5.当重载函数模板时,将改变限制在:显式指定模板参数;
6.所有的重载版本的声明必须位于它们被调用的位置之前;


组合与继承

Composition(复合) 表示”has-a”


复合关系下的构造与析构
构造由内而外
Container的构造函数首先调用Component的default构造函数,然后再执行自己

析构由外而内
Container的析构函数首先执行自己,然后再调用Component的析构函数


Delegation(委托) or Composition by reference
桥接模式(Handle/Body模式) or pImpl(Pointer to Implementation)
有一个String类,除了包含class必要的声明之外,实际的实现方式定义在另一个类StringRep中,在String类中设置一个指针指向具体实现的类StringRep

//file String.hpp
class StringRep; //声明
class String
{
public:
  String(); //默认构造
  String(const char* s); //拷贝构造
  String(const String& s); //拷贝构造
  String& operator=(const String& s); //拷贝赋值
  ~String(); //析构
private:
  StringRep* rep; //Handle/body(pImpl)
}

//file String.cpp
#include "String.hpp"
namespace {
  class StringRep {
    friend class String;
    StringRep(const char* s);
    ~StringRep();
    int count;
    char* rep;
  };
}

Inheritance(继承) 表示"is-a"

构造由内而外
Derived(派生类/子类)的构造函数首先调用Base(基类/父类)的default构造函数,然后执行自己

析构由外而内
Derived的析构函数先执行自己,然后调用Base的在析构函数

base class的dtor必须是virtual,否则会出现undefined behavior


虚函数与多态
Inheritance(继承) with virtual functions(虚函数)
non-virtual函数:你不希望derived class重新定义(override/复写)它;
virtual函数:你希望derived class重新定义(override/复写)它,并且你对它也有默认定义;
pure virtual函数:你希望derived class一定要重新定义(override/复写)它,你对它没有默认的定义。


conversion function 转换函数
class Fraction
{
public:
  Fraction(int num, int den=1)
    :m_numerator(num), m_denominator(den) {}
  operator double() const
  {
    return (double)(m_numerator*1.0 / m_denominator);
  }
private:
  int m_numerator; //分子
  int m_denominator; //分母
};

在执行以下代码块时:
{
Fraction f(3,5);
double d=4+f;
}
先构造一个Fraction 3/5,然后尝试double与Fraction相加,并得到一个double值
编译器会先检查是否定义了double+Fraction的operator+,若是即调用operator+函数,否则编译器会检查class Fraction是否定义了Fraction to double的转换函数,若是则调用该转换函数,否则编译错误


non-explicit-one-argument ctor
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 d2=f+4;
}
左操作数是Fraction、右操作数是double,而operator+函数左操作数是Fraction(隐含的this指针)、右操作数也是Fraction;因此编译器会尝试调用non-explicit ctor将"4"转"Fraction(4,1)",然后调用operator+


conversion function 与 non-explicit-one-argument ctor并存
class Fraction
{
public:
  Fraction(int num, int den=1)
    :m_numerator(num), m_denominator(den) {}

  //conversion function
  operator double() const
  { return (double)(m_numerator*1.0 / m_denominator); }
  //non-explicit-one-argument ctor
  Fraction operator+(const Fraction& f)
  { return Fraction(...); }

private:
  int m_numerator; //分子
  int m_denominator; //分母
};

在执行以下代码块时:
{
  Fraction f(3,5);
  Fraction d2=f+4;
}
因为二者任一均可调用,产生二义性,编译出错


explicit-one-argument ctor
class Fraction
{
public:
  explicit 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 d2=f+4; //[error] 改成"Fraction d2=f+Fraction(4);"可通过
}
将会报错,因在"explicit"关键字的限定下,operator+函数的实参一定要是Fraction类型,就算仅需要一个int型参数也能构造Fraction(例如Fraction(4)也就是4/1),但explicit仍不允许此行为发生


pointer-like class 关于智能指针
智能指针将一个一般指针封装到一个class中,并且重载*与->,使得智能指针不仅能实现一般指针的操作,而且能在class内扩展其它的操作
智能指针的一般框架:
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; //指向class T类型的指针
  //...  
};

相当于有一个"智能指针" shared_ptr(实际是一个class),其中包含一个"真正指针" px(私有变量;是一个指向class T类型的指针)

使用示例:
struct Foo
{
  //...
  void method(void) {...}
};

shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();
首先,声明一个指向class Foo类型的智能指针sp;
然后,*sp会返回*px(class Foo对象)的reference,以此通过class Foo的拷贝构造函数创建class Foo对象f
最后,sp->会返回px(指向class Foo类型的一般指针),所以sp->method()也就相当于px->method()


pointer-like class 关于迭代器
struct __list_node //链表元素
{
  void* prev;
  void* next;
  T data; //这里T假定为struct Foo
}

struct __list_iterator //链表迭代器
{
  //...
  typedef __list_node<T>* link_type;
  link_type node; //指向__list_node object的指针

  //typedef T& reference
  reference operator*() const
  {
    return (*node).data;
  }

  //typedef T* pointer
  pointer operator->() const
  {
    return &(operator*()); //会调用class内的operator*函数,返回一个T对象(的reference)
    //然后再用&取地址,返回一个T*类型的指针
  }
  //此外,迭代器不仅需要处理*和->,一般还需要处理++ --操作等,这里不扩展了...
};

使用示例:
list<Foo>::iterator ite;
ite->method();
首先,申请一个迭代器ite,该迭代器是元素类型为class Foo的链表
然后,有一个操作:ite->method(),通过迭代器ite调用class Foo的成员函数method()
意思是调用Foo::method();
相当于(*ite).method();因为*ite会获得一个Foo object;
相当于(&(*ite))->method();因为&(*ite)会获得Foo object的指针,最后即通过该指针调用method();

因为"ite->method()"最终相当于"(&(*ite))->method()",为了响应这种操作需求,这也就是为何struct __list_iterator内的operator->函数写法的原因 ["&(operator*())"响应"&(*ite)"]


partial specialization,模板偏特化 - 个数的偏
template<typename T, typename Alloc=...>
(1)
class vector
{
  ...
};

绑定第一个模板参数为bool类型:
(2)
class vector<bool, Alloc=...>
{
  ...
};

这样,当模板的第一个参数限定为bool类型时,将会使用代码片(2),而不是(1)
注:只能从第一个模板参数开始绑定一个或连续的多个参数


partial specialization,模板偏特化 - 范围的偏
template <typename T>
(1)
class C
{
  ...
};

当模板参数范围限定为指向T类型的指针:
(2)
class C<T*>
{
  ...
};

这样,当以T类型创建class C时将使用(1);而以T*类型创建class C时将使用(2)
例如:
C<string> obj1; //使用(1)创建obj1
C<string*> obj2; //使用(2)创建obj2

 

reference与pointer的一些对比
指针在声明时可以不初始化,而引用在声明时就必须对其初始化,并且在对引用初始化之后,不能再给它赋值
例如:
int x=0;

int* px = &x; //ok

int* px;
px = &x; //ok

int& rx = x; //ok

int& rx; //no;必须在声明时初始化

int y;
rx = y; //no;在初始化后不能再对其赋值

object与其reference的大小相同,地址也相同:
sizeof(rx) == sizeof(x)
&rx == &r
(虽然编译器会这么告诉你,但实际这是假象)

reference通常不用于声明变量,而通常用于参数类型(parameters type)和返回类型(return type)的描述

以下被视为"same signature",二者不能同时存在:
double imag(const double& im) ... {...}
double imag(const double  im) ... {...}

例如:
double d=1.0;
imag(d); //ambiguity
若以上两个函数并存,imag(d)即产生二义性,因为二者均可调用,编译器无法决定选择哪一个

扩展:
问:const是不是函数签名的一部分?
答:是
例如:
int f(...) const {...}
int f(...) {...}
它们被认为是两个不同的函数(函数签名不同)

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!