C++-侯捷-面向对象高级编程(上)

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的底层过程

  1. 先得到空间
    先分配内存:使用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 **

  2. 再调用构造函数
    Complex::Complex(1,2);

编译器delete的底层过程

  1. 先调用析构函数
    String::~String(ps);
  2. 再释放内存:
    operator delete(ps);

P10 类 扩展:模板、函数模板

static函数和对象

如果是静态数据,要在类外定义
静态函数只能处理静态数据
普通函数处理普通数据(需要传this pointer),当然也可以处理静态数据

调static函数的方式:

  1. 通过对象调用
  2. 通过类名称调用
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

所有都封锁在一个单元,

  1. using directive命令

全部打开了

using namespace std;

cin<<...;
cout<<"hello";
  1. 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()};
};

构造和析构:

  1. 构造由内而外
  2. 析构由外而内

委托,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;
};

构造和析构

  1. 构造由内而外。

derived(衍生子类)构造函数首先调用Base的default构造函数,然后才执行自己。

  1. 析构由外而内。

继承和虚函数

继承主要的作用体现在与虚函数搭配
非虚函数:不希望子类重新定义(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)

父类想创建未来才定义的子类。
在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值