P2 头文件与类的声明
代码基本形式
include自己写的是“.h”,系统自己的标准库是<>
防卫式声明
不会重复引用
#ifndef _COMPLEX_
#define _COMPLEX_
#endif
class的模板声明
把类型抽象成一个模板
template<typename T>
class complex {
public:
complex(T r,T i):re(r),im(i){}
private:
T re, im;
};
complex <double> c2(2.5, 1.5);
complex<int> c1(2, 6);
P3 构造函数
inline内联函数
函数在class内部定义,可以写成inline,但编译器自己决定接不接受
复杂的函数编译器会拒绝
访问级别
public / private
建议所有的数据都放在private。
后面会有protected
构造函数
自动调用,构造函数有自己独特的写法,名称就是class名称
//任何函数都可以写默认实参
//使用初值列,initialization list(只有构造函数才有的语法)
complex(double r = 0,double i = 0):
re(r), im(i){ }
//这种赋值方法比上面不好
//原因是:初始化和赋值分离了,多走了一步,而上面一致
complex(double r = 0,double i = 0)
{ re = r ; im = i; }
这里不需要析构函数,不带指针的多半不需要析构
overloading 重载
相同函数名称,编译器会区分返回值,参数类型等等
//get
double real() const{return re;}
//set
void real(double r){re = r;}
函数重载常常发生在构造好书,但这样冲突的不对
class complex {
public:
complex(double r, double i) :re(r), im(i) {}
complex() :re(0), im(0) {}
private:
double re, im;
};
complex c1;
complex c2();
构造函数放在private区
这样构造函数不能被调用
但singleton单例可以
class A{
public:
static A& getInstance();
setup(){...}
private:
A(); //默认构造
A(const A& rhs); //赋值copy构造
}
A& A::getInstance(){
static A a;
return a;
}
P4 参数传递与返回值
常量成员函数
不会改变成员数据的函数要加const(拿出来打印之类的),注意const的位置
class complex {
public:
complex(double r, double i) :re(r), im(i) {}
double real() const { return re; }
double imag() const { return im; }
private:
double re, im;
};
{
complex c1(2, 1);
cout << c1.real();
cout << c1.imag();
}
class里没有用class,使用的的时候确加了,是错的
class complex {
public:
complex(double r, double i) :re(r), im(i) {}
double real() { return re; }
double imag() { return im; }
private:
double re, im;
};
{
const complex c1(2, 1);
cout << c1.real();
cout << c1.imag();
}
pass by value&&pass by reference
尽量pass by reference,传的速度很快,如果不想被修改,加const
// pass by ref to const,看入参
complex& operator += (const complex&);
return by value&&return by reference
尽量用reference
return的那个不能是在函数内部才创建的一个变量。
friend 友元
通常封装数据之后,外部都无法访问数据。但是友元可以。
class complex {
public:
private:
double re, im;
friend complex& _doapl(complex*, const complex&);
};
inline complex& _doapl(complex* ths, const complex& r) {
ths->re = r.re;
ths->im = r.im;
return *ths;
}
相同class的各个objects互为friends
相同class的各个objects互为friends。即同类的objects可以互相访问private data。
P5 操作符重载和临时对象
成员函数 this
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);
}
这里隐藏了操作符的左操作数,是this,参数列表中不需要显式写出
传递者不用知道接收者是以reference形式接收,所以把*ths传给complex&
c3+=c2+=c1;
要想这样用,就要返回形式可以作为下一次的传递形式
非成员函数 无this
正负:
inline complex
operator + (const complex& x)
{
return x;
}
inline complex
operator - (const complex& x)
{
return complex(-real(x), -imag(x));
}
复数相加:
不能return by reference,在函数里面创建的,函数结束时也就没了
可以用临时对象 typename()
这也是临时对象,没有名称。
两复数相等或不等:
inline bool
operator == (const complex& x, const complex& y)
{
return real(x) == real(y) && imag(x) == imag(y);
}
inline bool
operator == (const complex& x, double y)
{
return real(x) == y && imag(x) == 0;
}
inline bool
operator == (double x, const complex& y)
{
return x == real(y) && imag(y) == 0;
}
inline bool
operator != (const complex& x, const complex& y)
{
return real(x) != real(y) || imag(x) != imag(y);
}
inline bool
operator != (const complex& x, double y)
{
return real(x) != y || imag(x) != 0;
}
inline bool
operator != (double x, const complex& y)
{
return x != real(y) || imag(y) != 0;
}
<<重载(std重载)
#include <iostream>
ostream&
operator << (ostream& os, const compelex& x){
return os << '(' << real(x) <<',' << imag(x) << ')';
}
P7 Big Three:拷贝构造、拷贝赋值、析构
String s1();
String s2("我是一个酒精过敏的帅哥");
String s3(s1); //拷贝构造
s3 = s2; //拷贝赋值
类声明
class类如果带有指针,就不能用编译器默认的拷贝构造函数,否则就会出现浅拷贝,仅仅把s3的指针指向了s1,而s3原本只想的内容就会内存泄漏(没有指针指向它),s1和s3指向同一块内容,也是比较危险的,改变了一个,另一个也会跟着改变。
class String {
public:
String (const char* cstr = 0); //构造函数
String (const String& str); //拷贝构造
String& operator=(const String& s); //拷贝赋值,操作符重载
~String(); //析构函数
char* get_c_str() const(return m_data); //get方法,声明为常量成员函数
private:
char* m_data;//指向字符的指针,之后可以new动态分配大小
}
拷贝构造,拷贝复制,析构函数是带指针的类的big three
constructor和destructor(构造函数和析构函数)
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;
}
copy ctor(拷贝构造)
inline
String::String(const String& str){
m_data = new char[str(str.m_data) + 1];
strcpy(m_data,str.m_data); //之所以可以访问private data,是因为同class的objects是友元
}
copy assignment operator(拷贝赋值函数)
inline
String& String::operator=(const String& str){//&是引用
if (this == &str){ //&是取地址,得到指针。检测自我赋值,self assignment。不然下面delete后无法赋值
return *this;
}
delete[] m_data; //回收内存
m_data = new char[ strlen(str.m_data) + 1]; //重新分配
strcpy(m_data,str.m_data); //copy值过去
return *this;//不必知道接收端的接收形式
}
P8 栈stack、堆heap与内存管理
堆栈说明
栈,存在于某个作用域。离开这个作用域就死了。
堆,全局内存空间,可动态分配内存,需要手动delete
{
Complex c1(1,2); //栈,local object
Complex* p = new Complex(3); //堆
}
static local objects静态对象
在作用域结束后还存在,整个程序结束才结束
{
static Complex c;
}
global objects全局对象
整个程序
Complex c3; //global objects
int main{
}
heap objects堆的说明
不delete会内存泄漏,作用域结束后,指针p已经没有了,但p指向的堆区还在。作用域之外的地方看不到p了,也就没机会delete p
{
Complex* p = new Complex;
delete p;
}
如果对于array new ,要搭配array delete
String* p = new String[3];
delete[] p;
编译器new的底层过程
-
先得到空间
先分配内存:使用malloc函数,大小为sizeof(对象大小),对象大小是成员变量决定的。
如果创建单个对象:如Complex两个double,则8字节,头尾要加上cookie,表明该部分内存已经被占用,一个cookie为4个字节。即release下8+2*4 = 16字节(必须是16字节的倍数,如果不足则向上取整)。debug下就得再加32+4,其实没必要记。
如果创建对象数组:除了要n份成员变量以外,需要额外一个字节来记录数组的大小。**即如创建长度为3的Complex数组,release下:8 * 3(成员变量) +4 * 2 (cookie)+ 4 (记录长度) = 36 -> 48 ** -
再调用构造函数
Complex::Complex(1,2);
编译器delete的底层过程
- 先调用析构函数
String::~String(ps); - 再释放内存:
operator delete(ps);
P10 类 扩展:模板、函数模板
static函数和对象
如果是静态数据,要在类外定义
静态函数只能处理静态数据
普通函数处理普通数据(需要传this pointer),当然也可以处理静态数据
调static函数的方式:
- 通过对象调用
- 通过类名称调用
class Account{
publid:
static double m_rate;
static void set_rate(const double& x){m_rate = x;}
}
double Account::m_rate = 8.0;//类外定义
int main)_{
Account() a;
a.set_rate(5.0); //调用方法1:通过实例对象访问
Account::set_rate(5.0); //调用方法2:通过类名称直接访问
}
把构造函数放在private 单例设计模式
外界不能创建A,通过外界的静态函数把唯一的a返回出去。
class A{
public:
static A& getInstance();
setup(){...}
private:
A(); //默认构造
A(const A& rhs); //赋值copy构造
static A a;
}
A& A::getInstance(){
}
改进一下,只有调用过getInstance,a才会被创建:
class A{
public:
static A& getInstance();
setup(){...}
private:
A(); //默认构造
A(const A& rhs); //赋值copy构造
}
A& A::getInstance(){
static A a;
return a;
}
为什么cout可以接受那么多类型去输出
cout继承于ostream,标准库团队在ostream实现重载了大量类型的<<操作符
function template,函数模板
使用场景:任意元素的比较大小,使用函数模板
//调用函数的时候不必显式说明类型,编译器会推导
template <class T>//这里的class只是一个通用的类型说明符,c++11多用typename
inline const T& min(const T&a,const T&b ){
return b<a ? b:a;
}
当然如果自己设计的类要调用该函数,一定是需要自己在被调用的类重载操作符,不然会报错。
class A{
public:
bool operator < (const stone& rhs) cosnt{
return this.___< ths.___;
}
};
namespace
所有都封锁在一个单元,
- using directive命令
全部打开了
using namespace std;
cin<<...;
cout<<"hello";
- using declaration
只打开了一个
using std::cout;
std::cin<<...;
cout<<"hello";
P11 继承、复合与委托
类的关系有继承、复合与委托
复合 表示has-a
一个类中,包含了另一个类,就叫复合。生命周期同步。
如下,queue(队列)类里面包含了Sequence类。可以使用deque(双端队列)实现queue的所有功能,只是改造了一下名字。
template<class T>
class queue{
protected:
deque<T> c;//底层容器
public:
void pop() {c.pop_front()};
};
构造和析构:
- 构造由内而外
- 析构由外而内
委托,Composition by reference
一个类,仍然包含另一个类,但是不是通过内存直接包含,而是用一个指针包含。用指针包含生命可以不同步,StringRep变动,String不用修改。
class StringRep;//前置声明
class String{
private:
StringRep* rep; //委托
}
Inheritance 继承,表示is-a
父类的数据可以完全继承,子类对象有父类成分。
struct _List_node_base{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template < Typename _Tp >
struct _List_node: public _List_node_base{
_Tp _M_data;
};
构造和析构
- 构造由内而外。
derived(衍生子类)构造函数首先调用Base的default构造函数,然后才执行自己。
- 析构由外而内。
继承和虚函数
继承主要的作用体现在与虚函数搭配
非虚函数:不希望子类重新定义(override)。
虚函数:如果希望子类重新定义,则可以把函数声明为虚函数,但同时你需要有一个默认定义。
纯虚函数:自己不给默认,希望子类一定去重新定义。
class Shape{
public:
virtual void draw() const = 0; //纯虚函数,pure vitual
virtual void error(const std::string& msg); //虚函数, impure virtual
int objectID() const; //非虚函数
};
class Rectangle:public Shape{...};
class Ellipse: public Shape{...};
CDocument是父类,CMyDoc是子类:
委托+继承 经典案例
经典案例1(设计模式Observer)
多个Obsever观察同一个数据/文档。每个Observer都有自己对数据的显示方式。Subject用于observer的登记注册,
经典案例2 (设计模式composite)
firesystem,composite可以容纳很多primitive,父类component,composite和primitive都是子类
add要被子类定义,是虚函数
经典案例3 (设计模式Prototype)
父类想创建未来才定义的子类。