1-2 基础以及头文件
设计的类里面有无指针?
string 里保有的是一个指针,指针指出去的(另外分配的空间)放这个内容
#include 标准库 是用<>
#include 自己写的东西 “”
insertor << 把一个对象插入到输出流 (往左边丢 cout)
extractor >> 输入流给到一个对象里 (往右边丢 cin)
头文件的正确写法:
#ifndef __xxxx__
#define __xxxx__
....
#endif
这样就不会有重复地引入.h了
-
对于单个.cpp来说:防止在一个.cpp里面 #include 同一个.h 多次,导致那一个.h里的声明重复出现(类的声明不能重复出现)
-
对于多个.cpp来说:同时.h里不能放定义,也是因为最后在链接的时候遇到重复定义的东西
模版:
T 是一个 typename
3-构造函数
函数若在class body 内定义完成,那就成为了inline函数的候选人
inline会更快,但是对于有些函数你把它定义为inline,编译器也不会把它当作inline,(有的过于复杂)
类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。 在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加 const,而对于改变数据成员的成员函数不能加 const。
这两个函数重载是不允许的,发生冲突
4-参数传递与返回值
Singleton设计模式(想说的是,确实有把构造函数放在private里面的)
函数的后面+const (不会改变数据内容的,马上加上const,为什么要这么做呢?
double real() const {return real;}
//如果 不加const、c1是一个常量,在调用函数的时候,这个函数说,我不一定会不会改变成员变量的值哦,这是不可以的。必须确定地说!我是不会改变成员变量的值的
const complex c1(2,1);
c1.real();
参数传递: pass by value、pass by reference(to const)
by reference时也有不带const的
operator << (ostream& os, const complex a);
最好所有的参数传递都传引用,尽量不要传value
这里不是to const 也就是说会给传进来的东西做改变
返回值传递:return by value vs. return by reference(to const)
double real();
double& real();
如果这个__doapl不是friend函数的话,那么它就没法直接拿对象的成员变量了
注意:相同class的各个objects 互为friends
直接拿了这个对象的实部和虚部,且也不是友元
什么时候返回reference?什么时候返回普通的?
上面这个情况,是把值加到了ths里,它的声明周期并不仅在这个函数中,那就是可以返回它的ref
还有这么一种情况,我们在这里面新建了一个对象,让它等于了a+b,并且返回这个新对象的ref,那就不可以了,因为它本身的声明周期只在这个函数内部。
5-操作符重载
所有的成员函数都带着一个隐藏的参数(虽然你没有写,但是它在,它就是this)
谁调用我,那个谁就是this
传递者无需知道接收者是以reference形式接收
*返回的是指针所指的东西,return ths
c1 传的时候也不知道它是被引用接收了还是by value
这一部分应该理解为是,接收它的是什么人!这里是说 接收它的是一个引用
用value来接收速度就慢了
c2 += c1; 如果使用者仅仅这么用的话
这个函数用void返回就可以了其实
但是如果这么用:c3 += c2 += c1;
c2 += c1 后的东西还要再次当 += 的右值 (const complex& r)所以它的返回值必须是complex&
临时对象
如果把这几个+都放在复数的class里头,它只能执行一种 没法应付后两种
这三个函数是return by value 注意三个函数千万不能返回reference,因为它返回的是在里面创建的对象,生命周期只在函数内
之前是 += assignmentplus 它可以加到另一个里面去
这两个没有名称的临时对象,进行到下一行,生命就结束了
对正/负的重载,参数只有一个
//<<要写成全局函数的形式,不要写成成员函数
//cout本身是不认识复数的
cout << conj(c1);
ostream& os 前面不要加const,加了const表示为 传进这个函数后的os不可以被改变了,没办法给这个流里面插入东西了就
返回类型是什么?ostream&
因为ostream不是这个函数里生命周期里的东西,是本来就有的,ok,可以加引用
如果我们只是输出一次,那void就可以,一次就输出到屏幕上了
但是如果想连串输出 cout << c1 << conj(c1);
输出完c1后还想让conj(c1)继续给到cout里,那么就需要返回 ostream&本身
c1丢给cout之后得到的结果 还要再接收 后面那一部分 所以返回不能是const ostream&
总结:
- initializtion list 必须加
- 成员函数该加const的加上
- 参数的传递尽量考虑 pass by reference (同时考虑在前面加不加const)
- return by reference? value?
操作符重载写成成员函数就是下面这样,显然是不对的,因此把它要写成非成员函数
C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型
您可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。
在这里,有一点很重要,我们需要把运算符重载函数声明为类的友元函数,这样我们就能不用创建对象而直接调用函数。
也可以写成普通的函数,但是无法访问对象内的数据
运算符重载
operator 运算符 重载 放在类内的话
是complex类的对象调用的这个运算符,因此只能实现 complex + complex / complex + int 没法实现int + complex
complex& operator + (Complex/int)
operator 运算符重载放在类外 (如果要访问私有,需要设置友元函数)
Complex& operator + (Complex, Complex);
Complex& operator + (Complex, int);
Complex& operator + (int, Complex);
定义在类外时,也可以只定义一个隐式转换去实现
Complex& operator + (Complex, Complex);
Complex(int a):rea(a),imag(0){}
输入输出运算符重载
也定义在类外
因为如果定义到类内,调用的需要先写到前面,就变成了 a << cout (不符合插入运算符的规则)
写在类外的话,第一个参数是(ostream& os) 就可以 cout << a
7-拷贝构造 拷贝复制 析构
上面是拷贝构造
下面是拷贝复制
只要类里带了指针,就要写拷贝构造、拷贝复制
拷贝构造函数
拷贝赋值函数
(1)先检测是不是自我赋值(如果没自我赋值,一上来先把m_data删了,后面都找不到他的长度)
(2)删除 自己的m_data
(3)给m_data开辟新空间并且把值放进去
8-堆栈、堆与内存管理
new -> 先分配memory, 再调用ctor
分配memory 就是 malloc
(1)分配内存
(2)转型
(3)调用构造函数
delete
字符串本身只是一个指针而已,所以这里是有两个删除的动作的
释放本身用的是free
new一个复数类,里面有8个byte 8个字节(绿色)
调试器模式下:
上面是灰色的是一个是4 byte 8个是 8 byte 下面还会多一个灰色 4 上下的粉红色是cookie 一个是4
VC下面分配的内存一定是16的倍数,所以分配64 byte 深绿色的是填补物
cookie 后面是41 、16进制 4表示 64 、 1 表示这一块被分配出去了
非调试模式下:
一个复数真正的大小
上下的cookie:回收用
cookie 后面是11 、16进制 1表示 16 、 1 表示这一块被分配出去了
数组:
注意那个最后的+4是用了一个字节来表示这个数组的长度(对于Vc来说的)
重点
无论带不带[],这一大块都会被释放掉,因为有cookie 21h
但是对应的析构函数调用的次数不同,String的析构函数里还有对new出的char的分配,他们没放掉
delete 包括析构函数、释放内存 如果没加[]、析构函数调用次数不够
注意是先析构的、才释放大块的内存
9-复习String
C 库函数 char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest。
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::~String()
{
delete[] m_data;
}
拷贝构造函数
inline
String::String(const String& str)
{
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;
}
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data,str.m_data);
return *this;
}
静态函数没有this
类中的静态成员变量在类里面的是声明
一定要在类外再来一个定义
补充:把ctors放在private区
//singleton
class A{
public:
static A& getInstance(return a;);
setup() {...}
private:
A();
A(const A& rhs);
static A a;
...
}
希望class只产生一个对象
没有任何对象创建的时候,已经有一个static A a;
不想让外界创建A,就把A的构造函数放在private
那怎么给外接一个接口让得到a呢?
定义一个静态函数
static A& getInstance (return a;);
调用:
A::getInstance().setup();
但是这会存在一个问题:当getInstance没人调用的时候,a也一直在,浪费了空间,于是改写为
//Meyers Singleton
class A{
public:
static A& getInstance();
setup() {...}
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a;
return a;
}
调用:A::getInstance.setup();
template <class T>
inline
const T& min(const T& a, const T& b)
{
return a < b ? a : b;
}
stone r1(2,3), r2(3,3), r3;
class stone
{
public:
stone(int w, int h) : _w(w), _h(h){}
bool operator< (const stone& b) const
{ return _weight < b._weight; }
private:
int _w, _h, _weight;
}
namespace
private是完全私有的,只有自己可以访问,派生类和外部都不可以访问
protected是受保护的,只有派生类可以访问,外部不能访问
11-组合与继承
queue借用deque已经完成的功能来实现自己的功能
说不定deque 有100个功能,进来之后只开放了6个功能
析构的时候是要先抽调外面的,再抽掉里面的。你现抽掉里面的外面直接塌了
Delegation(委托)——> Composition by reference
(两个类之间用指针相连)
空心的菱形,不是完完整整的拥有,是一个虚的拥有
对于组合来说,外面的有了(queue)里面的就一定有了(deque)
对于委托来说,外面的有了,里面的指针还不一定有值
Pimpl (左手边对外不变,右手边是真正的实现) pointer to implementation
这个指针,将来可以指向不同的实现类,右边怎么变动都不会影响到左边
下面的图的意思:三个人共享同一个"Hello",那个n是3
对于这个时候要写的,Copy or write -> 写的时候给你一份副本写
继承 右上角的T指的是 它是一个模版
对于继承来说,构造时也是先由内而外,析构时由外而内
对于父类的析构函数,要定义为虚函数
non-virtual function -> 不希望Derived class 重写它
virtual 函数:你希望derived class 重新定义它 且你对它已经有默认定义
pure virtual 函数:你希望 derived class一定要重新定义它,且你对它没有默认定义
int objectID() const 认为不需要子类再去重新定义它,用父类的这个函数就够了,就不需要设计为虚函数。
virtual void draw() const = 0; 纯虚函数,不知道现在怎么定义,交给子类定义
一个经典的例子:父类当中有一个函数暂时还写不出来,把它延缓到子类里去写
Template Method (23个设计模式之一)
子类先调用父类的OnFileOpen函数,到了Serialize再进自己的里面调用。
调用Serialize函数的时候,其实是通过this来调用的,this就是当前的子类myDoc
对于下面的这种情况,当然是Component构造函数最先被调用,然后是Base的构造函数,然后是Derived的。析构函数相反
委托+继承
Subject里面放指针去指向另一个对象,这里是指针数组
右边这个被指向的东西,可以被继承、(被继承后的也可以放到这个容器里面)
attach进行注册、notify进行修改
OOD object oriented design
13-委托相关设计
这是设计模式里的一种Composite
Composite里的容器是放的Component的指针,可以存父类,也可以存它的子类
下面的add函数同样也是加的指针
add要被下面的子类重新定义,所以是虚函数,但是注意不要是纯虚函数,因为Primitive不用写
子类是未来才会被派生下去的
我现在要去创建未来的Class、怎么办?
画图的时候先写object name 、再写typename
-代表private
+代表public
#代表protected
原型要被登记到最上面的父类里面,要让他能看得到,让上面准备一个空间
构造函数的私有的,刚刚已经出现的自己,创建的时候构造函数可以被调用起来吗?
可以,因为是自己创建的
自己注册自己
addPrototype(父类型的) 把自己挂上去
clone是new自己
注意static要给定义,不能只是声明
自己调用自己,可以,虽然是private
对于clone里的new,也是class里的,也可以调用构造函数,如果还默认用这个构造函数,又会给prototype加一个上去,所以再定义一个cons给这里用
首先它一定是不能公开,这个本来就不准备公开,所以是- / # 都可以,只要和上面默认的构造函数区分开