Object Based 面对单一class设计
> 经典分类
Class without pointer member(s)
- complex
Class with pointer member(s) - string
> complex例子
Header(头文件)中防卫式声明
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
避免重复包含头文件,导致重复定义。
> complex class需求
{
complex c1(2,1);
complex c2;
cout << c1 << endl;
cout << c2 << endl;
c2 = c1 + 5;
c2 = 6 + c1;
c2 = c1 + c2;
c2 += c1;
c2 += 3;
c2 = -c1;
cout << (c1 == c2) << endl;
cout << (c1 != c2) << endl;
cout << conj(c1) << endl;
}
> complex class设计
class complex{
public:
complex (double r=0, double i=0)
: re(r), im(i)
{ }
complex& operator += (const complex&);
double real () const { return re; } //inline
double imag () const { return im; } //inline
private:
double re, im;
friend complex& __doapl (complex*, const complex&);
};
inline double
imag(const complex& x)
{
return x.imag();
}
> 构造函数重载ctor-overloading
complex c1;
complex c2(); //等价,等同于调用无参构造函数
若遇到如下情况,编译器将无能为力。
complex (double r=0, double i=0) : re(r), im(i) {}
complex () : re(0), im(0) {}
两者都可以接受无参构造。
> 常量成员函数 const member functions
{
complex c1(2,1);
cout << c1.real();
cout << c1.imag();
}
{
const complex c2(2,1);
cout << c2.real();
cout << c2.imag();
}
若在real和imag函数中,没有声明为const类型,左边例子正确执行。但在右例中,由于c2是const对象,而调用了非常量成员函数,进而有修改成员变量的风险。
> 参数传递 pass by value vs. pass by reference(to const)
地址传递比值传递效率更高,但有副作用,可能引起值的更改。
const限制参数是否能修改。
complex & complex::operator += (const complex&);
ostream & operator << (ostream & os, const complex &x);
操作符+=、<<都使用对象,而不修改对象,为了效率又限制修改对象,使用const complex &。
> 返回值传递 return by value vs. Return by reference(to const)
按照C++运算规则,以下操作合法。
a += b += c;
cout << a << b << c << endl;
运算符+=和<<在C++均可被重载,由程序员指定具体行为。
这样的运算符为左结合,为了b.operator +=()作用后,a仍然能处理。
a.operator += ( b.operator += ( c ) );
同理,cout处理a后,需要仍然能处理b、c。所以两者的都应返回对象的引用。
ostream & operator<< (ostream os, const complex& x)
{
return os << ‘(’ << x.real() << ‘,’ << x.imag() << ‘)’;
}
> 友元 friend
申明为友元的函数,可以任意访问该类的私有成员变量。在一定程度上破坏了类的封装性,但也使得存取变量效率更高。
相同class的各个objects互为友元。
例:
class complex
{
public:
int func(const complex& param){ return param.re + param.im; }//访问私有成员
private:
double re, im;
};
complex c1;
complex c2;
c2.func(c1);
> 成员函数 vs. 全局函数
为使得以下成立,可以均使用全局函数。
{
complex c1;
complex c2(1,2);
c2 = c1 + c2;
c2 = c1 + 5;
c2 = 7 + c1;
}
inline complex & operator+ (const complex& x1, const complex& x2);
inline complex operator+ (const complex& x1, doublex2);
inline complex operator+ (double x1, const complex& x2);
c2 = c1+c2将c1加c2运算结果放在c2中,c2空间本来存在,可以返回引用更快。
然而,c1+5和7+c1运算的结果在operator+中存储在临时空间里,退出作用域后,栈上临时空间自动销毁,故不能返回引用。
每个函数,都有可能设计为两种情况,成员函数和全局函数。但在7+c1例中,使用全局函数更好(也可考虑使用类型转换构造函数)。
> 临时对象 temp objects
inline complex operator+ (const complex & x, const complex &y)
{
return complex (real(x)+real(y), imag(x)+imag(y));
}
inline complex operator+ (const complex & x, double y)
{
return complex (real(x)+y, imag(x));
}
inline complex operator+ (double x, const complex &y)
{
return complex (x+real(y), imag(y));
}
使用typename ()的方法创建临时对象,它们无名,生命周期仅限在当前语句。
> 另外的操作符重载
inline complex operator + (const complex& x); //正
inline complex operator - (const complex& x); //负
正操作符中,无需更改,可以直接返回x的引用,也可返回临时对象。
负操作符中,一定不能返回x的引用,因为是local object。
inline complex operator - (const complex& x)
{
return complex (-real(x), -imag(x));
}
此外,需要重载==、!=等操作符,以供复数使用。
> String例子
> 三个特殊函数 Big Three
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& opertor = (const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char *m_data;
};
构造函数、析构函数和赋值运算统称为big three。
在不自己编写赋值运算时,编译器将使用默认按位拷贝。在动态分配内存例子中,会引起内存泄漏。
因此在含有指针的类中,应该使用深拷贝,另外开辟空间,并把内容拷贝,而不是仅拷贝指针。还应注意检查不要自我赋值。
> 栈堆
Stack,是存在某个作用域的一个内存空间。在函数体内声明的任何临时变量,都位于栈中。Stack objects的生命周期在作用域结束之后结束,将被自动清理。Static local objects的生命周期和整个程序一致。Global objects也可视为一种static object,作用域为整个程序。
Heap,由操作系统提供的一块全局内存空间,程序可动态分配其中的若干块。
Heap objects的生命周期直到调用delete为止。
> new
先分配内存,再调用构造函数。
complex *pc = new complex(1,2);
编译器转化为:
complex *pc;
void *mem = operator new( sizeof(complex) ); //内部调用malloc
pc = static_cast<complex*> (mem); //转型
pc->complex::complex(1,2); //构造函数
> delete
与new相对应,先调用析构函数,再释放内存。
delete pc;
编译器转化为:
complex::~complex(pc); //析构函数
operator delete(pc); //释放内存,内部调用free
> array new与array delete
在VC中,动态分配内存,首尾四字节存储分配内存大小,4bits对齐(16整除)。最后一比特为1,为以分配;为0,为未分配状态。Debug模式将会多占36bytes。以数组动态分配还会在实际分配空间之上多分配4bytes存储数组大小。
delete [] p; //唤起3次dtor
delete p; //唤起1次dtor
如果没有配套使用array new和array delete,则会引起m_data指向区域内存泄漏,而p指向对象区域是可以正常回收的。
> static
类中存在静态成员变量和非静态成员变量,非静态成员变量在每个类对象中均有一份,而静态成员变量所有类对象共用。
成员函数,无论是否静态,都只有一份。唯一不同点在于静态成员函数,仅能操作静态成员变量。
由于静态成员变量仅有一份,不需要this来指定具体属于哪一个类对象。所以在访问时,可以无需创建对象。
class Account{
public:
static double m_rate;
static void set_rate(const double& x) { m_rate = x; }
};
double Account::m_rate = 8.0; //定义,分配空间
Account::set_rate(5.0);
> this指针
在非静态成员函数中,会悄悄传递一个this作为函数参数,用来指定调用哪一个类对象。
而静态成员函数,由于是共用的,函数里不再有this指针。
> Singleton & Meyers singleton
singleton将构造函数放在private中
class A{
public:
static A& getInstance() { return a; }
setup() { ... }
private:
A();
A(const A& rhs);
static A a;
...
};
编写构造函数,编译器将不会自动加入默认构造函数。
构造函数设为私有,外界无法调用,创建新的对象。
在私有成员中,A仅有一份,通过唯一接口getInstance访问。
缺点是A总是存在。
class A{
public:
static A& getInstance();
setup() { ... }
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a;
return a;
}
这样在调用getInstance时,才创建A。
A::getInstance().setup();
> 类模板 class template
类中某些对象类型在定义时,才能确定。
template <typename T>
class complex
{
public:
complex (T r=0, T i=0)
: re(r), im(i)
{ }
complex& 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;
}
在调用函数模板时,会进行参数推导。
示例代码
/* complex.h */
#ifndef __COMPLEX__
#define __COMPLEX__
#include <iostream>
using namespace std;
class complex{
private:
double re, im;
public:
complex(double r=0,double i=0)
: re(r), im(i)
{ }
complex& operator += (const complex& x);
complex& operator += (double x);
double real() const { return re; }
double imag() const { return im; }
friend ostream& operator << (ostream& os, const complex& x);
friend complex& __doapl(complex* x, const complex& y);
};
inline ostream&
operator << (ostream& os, const complex& x)
{
return os << "(" << x.real()
<< "," << x.imag() << ")";
}
inline complex&
complex::operator += (const complex& x)
{
return __doapl(this, x);
}
inline complex&
complex::operator += (double x)
{
complex tmp(x, 0);
return __doapl(this, tmp);
}
inline complex&
__doapl(complex* x, const complex& y)
{
x->im += y.im;
x->re += y.re;
return *x;
}
inline complex
operator + (const complex& x, const complex& y)
{
return complex(x.real() + y.real(),
x.imag() + y.imag());
}
inline complex
operator + (const complex &x, double y)
{
return complex(x.real() + y, x.imag());
}
inline complex
operator + (double x, const complex& y)
{
return complex(x + y.real(), y.imag());
}
inline complex
operator + (const complex& x)
{
return x;
}
inline complex
operator - (const complex& x)
{
return complex(-x.real(), -x.imag());
}
inline bool
operator == (const complex& x, const complex& y)
{
return (x.real() == y.real())
&& (x.imag() == y.imag());
}
inline bool
operator == (const complex& x, double y)
{
return x.imag() == 0 && x.real() == y;
}
inline bool
operator == (double x, const complex& y)
{
return y.imag() == 0 && y.real() == x;
}
inline bool
operator != (const complex& x, const complex& y)
{
return (x.real() != y.real())
|| (x.imag() != y.imag());
}
inline bool
operator != (const complex& x, double y)
{
return x.imag() != 0 || x.real() != y;
}
inline bool
operator != (double x, const complex& y)
{
return y.imag() != 0 || y.real() != x;
}
inline complex
conj(const complex& x)
{
return complex(x.real(), -x.imag());
}
#endif
/* complex-test.cpp */
#include <iostream>
#include "complex.h"
using namespace std;
int main()
{
complex c1(2,1);
complex c2;
cout << c1 << endl;
cout << c2 << endl;
c2 = c1 + 5;
c2 = 7 + c1;
c2 = c1 + c2;
c2 += c1;
c2 += 3;
c2 = -c1;
cout << (c1 == c2) << endl;
cout << (c1 != c2) << endl;
cout << conj(c1) << endl;
return 0;
}
/* string-test.cpp */
#include <iostream>
#include "string.h"
using namespace std;
int main()
{
String str1;
String str2("Hello");
String *str3 = new String("World");
str1 = "Hello, world!";
cout << str1.get_c_str() << endl;
cout << str2.get_c_str();
cout << str3->get_c_str() << endl;
return 0;
}
/* string.h */
#ifndef __STRING_H__
#define __STRING_H__
#include <iostream>
#include <string.h>
using namespace std;
class String
{
private:
char * m_data;
public:
String(const char * cstr = 0);
String(const String& s);
String& operator = (const String& s);
~String();
char * get_c_str() const { return m_data; }
};
inline
String::String(const char *cstr)
{
if(cstr){
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
} else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::String(const String& s)
{
m_data = new char[strlen(s.m_data)+1];
strcpy(m_data, s.m_data);
}
inline String&
String::operator = (const String& s)
{
if(this == &s)
return *this;
delete [] m_data;
m_data = new char[strlen(s.m_data)+1];
strcpy(m_data, s.m_data);
return *this;
}
inline
String::~String()
{
delete [] m_data;
}
#endif