Object Based (基于对象) vs. Object Oriented (面向对象)
Object Based : 面对的是单一 class 的設計
Object Oriented : 面对的是多重 classes **的设计,classes 和 classes 之间的关系。
类的两个经典分类:
- Classes without pointer members
- Classes with pointer members
一、Classes without pointer members
1、头文件的防卫式声明
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
作用:防止由于同一个头文件被包含多次,而导致了重复定义。
建议:为了不必要的麻烦,最好在每个头文件上加上这样的防卫式声明。即使现在只有一个cpp使用该头文件,万一之后再有别的cpp使用该头文件造成不必要的麻烦。
注意:防卫式声明一般这样写:(2个下划线,不是1个)+头文件名的大写(不包括.h)+(2个下划线,不是1个),例如头文件为head.h
时,就使用__HEAD__
,这是为了防止重复。
2、inline (内联)函数
内联函数 更快更好 但最终是否成为inline由编译器决定
函数若在类内完成定义,便自动成为inline候选函数
3、access level(访问级别)
数据 data 一般是 private , 大部分函数要给外界用 public
4、构造函数:
下面是构造函数特有的写法 ,(initialization list)初始化列表的形式 ,这样写更好,效率更高
- 变量的生成有两个阶段
- 1.初始化,冒号后
- 2.赋值:大括号里
注意:不带指针的类多半不用写析构函数
complex (double r = 0, double i = 0)
: re (r), im (i) { }
与下面的形式是不同的(这是赋值操作 assignments):
complex (double r = 0, double i = 0)
{ re = r; im = i; }
不允许此种写法,上面的构造函数已经有默认值了,冲突
complex () : re(0), im(0) { }
5、设计模式Singleton单例模式,构造函数放在private内,不允许被外界实例化对象,外界只允许用一份,使用static关键字
class A {
public:
static A& getInstance();
setup() { ... }
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a; // 自己创建了一份自己,且只有一份
return a;
}
A::getInstance().setup(); // 外界调用时要靠getInstance()取得唯一的一个实例
6、const成员函数(常量成员函数)
-
好习惯:不改变成员变量的成员函数一定要加const,否则使用时可能会报错。
-
const修饰函数 放在()后面 {}前面 const加在对象前面,修饰变量,不允许修改
double real () const { return re; } double imag () const { return im; }
const complex c1(1, 2);
cout << c1.real(); // 若complex::real()没加const,const对象调用非const函数,会报错
cout << c1.imag();
7、参数传递
尽量不要使用值传递 ,整个传进来,很慢 引用传递 相当于指针传递 速度很快 尽量所有的参数传递都用引用传递 const+引用的形式 表明传进来的参数不能被函数内部修改 不希望被修改
8、返回值传递
返回值的传递尽量返回引用 & 可以的情况下 尽量用引用(返回局部变量不可以返回引用,其余情况都尽量返回引用。)
如果函数内部创建一个临时变量,返回此临时变量时就不可以返回引用,因为一旦离开此函数,临时变量就消失了
9、友元
- 友元函数可以自由取得类的private成员,友元可以直接拿数据 否则用函数去拿 但打开了封装
- 相同class的各个对象(objects)之间互为友元
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
int func(const complex& param)
{ return param.re + param.im; }
private:
double re, im;
};
{
complex c1(2,1);
complex c2;
c2.func(c1);
}
10、操作符重载
- 成员函数 由this指针
有左右两个操作数的操作符,一定是作用到左边操作数上:
c2 += c1;// 这里this==c2
所有的成员函数一定带着一个隐藏的参数this,代表调用者(例如上面的c2)右边加到左边,this是个指针
inline complex&
__doapl(complex* ths, const complex& r)
{
...
return *ths;//这里的返回形式并不关心接收者是以引用的形式还是值的形式接受,使用引用会更快。
}
//连加操作 c2既做左值又作右值 ,返回值不可以是void 所以设计函数返回值的时候要特别注意
c3 += c2 += c1;
inline complex&
complex::operator += (const complex& r)
{
return __doapl(this,r);
}
- 非成员函数 (全局函数) 无this指针
inline complex operator + (const complex& x, const complex& y) {
return complex (real(x) + real(y), imag(x) + imag(y)); // 返回local object,不能return by reference
//这里可以返回引用
inline complex
operator + (const complex& x)
{
return x;
//这个函数绝不可return by reference,返回的一定是 local object。
inline complex
operator - (const complex& x)
{
return complex (-real (x), -imag (x));
}
临时对象 (temp object)形式:typename()
没有名字,就像是int i; 一样
//这里也是临时对象 没有名称 进行到下一行生命就消失了
complex();
#include <iostream.h>
ostream&
// os一直在变化这里不能加const
operator << (ostream& os, const complex& x)
{
return os << '(' << real (x) << ','
<< imag (x) << ')';
}
{
complex c1(2,1);
//C++所有符号都作用到左边参数上 对于特殊的操作符<< 只能写成非成员函数 global function
cout << conj(c1);
//连续输出 不能返回void 可以返回引用 os不是临时对象 上面的操作符重载函数
cout << c1 << conj(c1);
}
注意:
// 这里传引用和值是相同的效果 double4个字节
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
小结:
- 构造函数会用initialization list
- 该加const的成员函数要加const
- 参数传递尽量pass by reference
- 返回值尽量return by reference
- 数据放在private区
- 成员函数和全局函数都可以声明为inline 实际上要看编译器是不是真的能变成inline函数
- 临时对象形式,不能返回引用
- 友元函数直接访问成员变量
二、Classes with pointer members
1、带有指针的类 一定要自己写拷贝函数
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
inline char* get_c_str() const { return m_data; }
private
char* m_data; // 动态分配内存
Big Three,三个特殊函数
//拷贝构造 接受的是类的类型 自己的东西
String(const String& str);
//操作符重载 拷贝赋值 传入的也是自己类的东西 只要类中有指针 拷贝构造 和 拷贝赋值 析构函数一定要写
String& operator=(const String& str);
// 加const
char* get_c_str() const { return m_data; }
//指针 动态分配内存
char* m_data;
构造函数:
inline String::String(const char* cstr = 0)
{
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
} else {
//分配内存 放置空字符串
m_data = new char[1];
*m_data = '\0';
}
析构函数:
inline ~String()
{
delete[] m_data; // 释放动态分配的内存,否则内存泄漏
}
{
String s1(),
String s2("hello");
String* p = new String("hello");
//3个字符串要调用3次析构函数 s1和s2在离开作用域的时候 自动调用析构函数释放p
delete p;
}
2、注意:带有指针的类一定要有拷贝构造和=操作符重载函数
浅拷贝只是指针指向的内存相同 ,会造成 内存泄漏问题
- 拷贝构造函数:
inline String::String(const String& str)
{
//直接取另一个 object 的 private data.(它们互为 friend) +1 是结束符号
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
-
拷贝赋值函数
与拷贝构造函数的区别:拷贝赋值时,这个对象已经存在,所以需要先清空再拷贝
inline String::String operator= (const String& str)
{
//一定要写 检测自我赋值,传进来的指针和 操作符左边的指针(原来的指针)如果指向同一块内存空间,直接返回,效率高。否则没有检测自我赋值,在真的自赋值时会发生不确定行为,造成错误。
if (this == &str) // 检测自我赋值,否则自我复制会出错
return *this;
//分为3步 首先delete s2 创造和s1相同的动态空间 拷贝过来
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
3、stack(栈) 和 heap (堆)
**Stack,是存在于某作用域 (scope) 的一块內存空间 (memory space)。**例如当你调用函数,函数本身会形成一个stack 用来放置它所接收的参数,以及返回地址。在函数内部声明的任何变量、在其使用的内存块都来自stack。
**Heap,或称 system heap,是指由操作系统提供的 一块 global 內存空间,**程序可动态分配 (dynamic allocated) 获得若干内存块(blocks)。
class Complex { … };
...
{
//当离开作用域的时候c1自然就会消失了 c1占用空间来自stack 栈 p 必须手动delete 堆 动态空间
Complex c1(1,2);
Complex* p = new Complex(3);
}
小结:
-
变量的生命期
-
stack object (又被称为local object, auto object):离开作用域会被自动清理
class Complex { ... }; ... { Complex c1(1,2); }
-
static object:作用域结束后仍然存在,在程序结束时才结束
class Complex { … }; ... { static Complex c2(1,2); }
-
global object:在程序结束时才结束,也可以看作是static object
class Complex { … }; ... Complex c3(1,2); int main() { ... }
-
heap object: 在被delete之后才结束,否则不delete的话会出现内存泄漏(离开作用域失去了对动态空间的控制,指针就死亡了,但空间还在,没有还给系统)
lass Complex { … }; ... { Complex* p = new Complex; ... delete p; } class Complex { … }; ... { Complex* p = new Complex; ... delete p; }
-
-
new先分配内存,再调用构造函数(3个动作)
-
1.分配内存:内部调用
malloc(n) void* mem = operator new(sizeof(Complex));
-
2.转型
pc = static_cast<Complex*>(mem);
-
3.构造函数
pc->Complex::Complex(1,2);
Complex *pc; void* mem = operator new( sizeof(Complex) ); //分配內存 pc = static_cast<Complex*>(mem); //转型 pc->Complex::Complex(1,2); //构造函数
-
-
delete先调用析构函数,再释放内存
-
1.析构函数
String::~String(ps)
-
2.释放内存:内部调用
free(ps)
operator delete(ps)
Complex::~Complex(pc); // 析构函数 operator delete(pc); //释放内存
-
-
array new一定要搭配array delete,否则会造成内存泄漏,分配的数组的内存,析构函数调用
String* p = new String[3];
...
delete[] p;
注意:
-
连续赋值时必须要返回引用 void的话就不适用连续赋值
-
&放在typename后面 是引用,&放在object前面 表示取地址 得到指针
-
传出去的东西,(return )不会关心接收端是引用还是值
三、类模板 函数模板 static
1、static
**数据成员加上了static,data就不属于某一个对象,**在内存某一个区域,只有一份,所有的对象都需要同样的一个数据,这时候就可以把数据设置为static,比如银行利率。
小结:
- static
- static成员变量:与对象脱离,在内存中只有一份
- 使用场景:与对象无关的一般数据
- **静态数据一定要在类外面写一行对其的定义,给不给初值都可以。**要在类的外部赋值:
double Account::m_rate = 8.0;
- non static成员函数:在内存中只有一份,靠this pointer来区分要处理不同的对象
double real() const { return this->re; }
- static成员函数:没有this pointer,只能处理static成员变量,不能访问、存取对象中的数据,静态函数只能存取静态数据。
- 调用static函数的两种方式
- 1.通过对象调用
a.set_rate(7,0);
- 2.通过class name调用
Account::set_rate(5.0);
- 1.通过对象调用
- 调用static函数的两种方式
- static成员变量:与对象脱离,在内存中只有一份
2、类模板
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;
friend complex& __doapl (complex*, const complex&);
};
{
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
...
}
3、函数模板
template <class T> // class 和typename 相同
inline
const T& min(const T& a, const T& b)
{
return b < a ? b : a;
}
与类模板不同的是函数模板会进行自动实参推导。
四、组合与继承
-
Composition复合
-
表示has-a,这个queue类里有一个
deque<T> c
-
设计模式Adapter
- 已经有一个功能强大的class deque了,现在开放它的部分接口,改装成class queue
-
构造和析构(Container拥有Component)
-
构造由内向外
// 2 1 Container::Container Container::Container((……): ): Component() Component() { { …… };
-
析构由外向内
// 1 2 Container::~Container((……){ ){ …… ~Component() ~Component() };}
-
-
-
Delegation委托
- 类(handle)里有一个指向另一个类(body) 的指针
StringRep* rep
,虽然是指针指向另一个类,但在学术上也成为by reference 生命不同 - 引用计数 共享特性
- 类(handle)里有一个指向另一个类(body) 的指针
-
Inheritance继承
-
三种继承方式
public最重要 is- a的关系
父类的数据被继承下来
继承和虚函数搭配最有价值 -
-
表示is-a
class List_node
: public List_node_base -
构造和析构
-
构造由内向外(先父类后子类)
// 2 1 Derived::Derived Derived::Derived((……): ): Base() Base() { { …… };};
-
析构由外向内(先子类后父类)
// 1 2 Derived::~Derived Derived::~Derived((……){ ){ …… ~Base() ~Base() };};
- 父类的析构函数必须是virtual,否则会出现undefined behavior(未定义行为)
-
-
虚函数
-
-
non-virtual函数,不希望重新定义(override)它。
-
virtual函数:希望 允许派生类或者子类 去重新定义 override
-
pure virtual 纯虚函数 子类 一定要定义 注意: 和空函数不一样,抽象类
-
设计模式观察者
委托+继承
class Subject
{
private:
int m_value;
vector<Observer*> m_views;
public:
void attch(Observer* obs)
{
m_miews.push_back(obs);
}
void set_val(int value)
{
m_value = value;
notify();
}
void notify()
{
for (int i = 0; i < m_views.size(); i++)
m_views[i]->update(this, m_value);
}
};
class Observer
{
public:
virtual void update(Subject* sub, int value) = 0;
};