侯捷C++面向对象编程(上)笔记

c++面向对象高级编程(一)

虽然平时C++用的不多,但以后面试以及学校做的项目也是避不开的一点,故学!

视频参考:高清 1080P C++面向对象高级编程(侯捷) P38 24 Basic String使用newextra扩充申请量 (youtube.com)

文章目录

第一部分 基础

二 头文件与类的声明

数据和函数

  • 实部/虚部 complex:
    • 实部–类似c1、c2,数据只有一个
    • 虚部–加减乘除、共轭、正弦
  • 字符String
    • 是一个指针,指向字符串

代码基本声明

  • .h(.hpp)
  • .cpp

防卫式声明

防止文件被重复编译

complex.h

#ifdef  _COMPLEX_
#define _COMPLEX_

...

#endif

complex-test.h

#include <iostream>
#include "complex.h"
using namespace std;

int main(){
...
}

(头文件)的布局

#ifdef  _COMPLEX_
#define _COMPLEX_

#include <cmath>

//forward declaritions
class ostream;
class complex;

class complex
{
...
}

complex :: function ...

#endif

class的声明(declaration)

//head
class complex 
//body
{
	public:
		complex (double r = 0, double i = 0)
			:	re(r), im(i)
			{}
    	complex& opearator += (const complex&);
    	double real () const { return re; }
    	double imag () const { return im; }
    
    private:
    	double re,im;
    
    	friend complex& __doapl (complex* , const complex&)
    
}



{
    complex c1(2,1);
    complex c2;
    ...
}

class template(模板)简介

通过传入不同的参数,使得变量类型发生变化

//head
template<typename T>
class complex 
//body
{
	public:
		complex (T r = 0, T i = 0)
			:	re(r), im(i)
			{}
    	complex& opearator += (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,1);
    complex<int> c2;
    ...
}

三 构造函数

inline(内联)函数

  • 简单
  • 编译器自动选择
  • 函数若在class body里定义完成,便自动成为inline候选人

访问级别

  • public:公开,任何位置都可以使用
  • private: 私有,只有类内部能使用

构造函数

complex (double r = 0, double i = 0) : re(r), im(i) {}

  • double r = 0, double i = 0 : default argument默认实参

    • 没有传入参数就为默认实参
  • re(r), im(i) : initialization list 初值列,初始值

    • 良好的构造函数写法,时间快,效率高,
    • 若在括号里{re = r ; im = i}则效率低,时间慢
  • 创建对象:

    complex c1(2,1);
    complex c2;
    complex* p = new complex(4);
    

重载

//head
class complex 
//body
{
	public:
		complex (double r = 0, double i = 0)
			:	re(r), im(i)
			{} 
    	complex () : re(0), im(0) { }
    	complex& opearator += (const complex&);
    	double real () const { return re; }
    	double imag () const { return im; }
    
    private:
    	double re,im;
    
    	friend complex& __doapl (complex* , const complex&)
    
}



{
    //正常
    complex c1(2,1);
	//报错
    complex c2;
    //正常
    complex c3();
    ...
}
  • 构造函数可以存在多个,需要以重载方式呈现(同名不同参)
  • 当函数存在重载时,若函数存在初始化,则complex c1会使用该函数构造。因此,当多个函数重载有不含参或含参带初始化的构造函数时,会默认这些函数都为候选人而导致错误。

constructor(ctor,构造函数)被放在private区

  • 直接放置,错误

    //head
    class complex 
    //body
    {
    	public:
    
    		..
        private:
        	double re,im;
            complex (double r = 0, double i = 0)
                :	re(r), im(i)
                {}
        	friend complex& __doapl (complex* , const complex&)
        
    }
    {
        complex c1(2,1);
        complex c2;
        ...
    }
    
  • 设计模式–Singleton 单例模式

    • 在public中设置可以访问private的构造函数的函数(防止纂改)
    class A{
    public:
    	static A& getInstance();
        setup () {...}
    private:
        A();
        A(const A& rhs);
    	...
    };
    
    A& A::getInstance()
    {
        static A a;
        return a;
    }
    
    A::getInstance().setup();
    

四 参数传递与返回值

const member functions (常量成员函数)

double real () const { return re; }
double imag () const { return im; }

  • const: 不改变函数值,不加会默认可以改变
    • 常规声明对象没有影响
    • 而若对象被声明为const,函数中的值应该是不可以变化的,需要写const来确保不会变化,否则会报错

所以若无需要修改的变量,尽量给函数写上const

pass by value vs. pass by reference(to const)

  • reference 一般速度更快(仅地址),但是值会被修改
    • 部分传值(如1、2字节的值),会稍快
  • 仅仅想快速传参,为防止修改,可以加上const
    • operator << (ostream& os, const complex& x)

return by value vs. return by reference(to const)

  • double : 返回的是double类型的值
  • complex& : 返回是complex类型的引用

friend 友元函数

  • 自由获得friend的private成员

  • 相同class的各个objects互为friends(友元)

class body外的各种定义(definitions)

  • 什么情况下可以 pass by reference?
    • 数据在private
    • 该加const就加const
  • 什么情况下可以 return by reference?
    • 返回值尽量reference
    • 返回已经存在的空间
      • 若在函数内创建一个值,如果返回引用,函数结束时local被销毁,传出的引用为空。
inline complex&
__dopal(complex& thes, const complex& r)
{
    ths->re += r.re;
    ths->im += r.im;
    return *ths;       
}

inline complex&
    complex::operator += ( const complex& r)
{
    return __dopal (this, r);
}

五 操作符重载与临时对象

operator overloading(操作符重载-1,成员函数)

  • 类里面的函数称为成员函数

    class complex 
    //body
    {
    	public:
    		...
        //成员函数
        	complex& opearator += (const complex&);
        	double real () const { return re; }
        	double imag () const { return im; }
        
        private:
    		...
        
    }
    
    
  • 不在class里的函数称为全局函数

//方法
inline complex&
__dopal(complex& ths, const complex& r)
{
    ths->re += r.re;
    ths->im += r.im; 
    return *ths;       
}

//操作符重载
inline complex&
    complex::operator += ( const complex& r)
{
    return __dopal (this, r);
}
  • this在函数里指的就是左边的对象(c2),引用的complex对象是c1,但是重载时不需要把this写出来

return by reference 语法分析

  • 以前:pointer传递需要知道接受的是什么

  • 传递者无需知道接收者是以reference形式接收

operator overloading(操作符重载-1,非成员函数)

//操作符重载
inline complex
    operator + ( const complex& x , const complex& x)
{
    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& x)
{
    return complex (x+real(y),imag(y));
}
临时对象 typename();
  • 上面这些函数绝不可return by reference,因为它们返回的必定是个local object,是一个临时对象
  • 他们的生命周期在下一行前就会结束。
{
    complex c1(2,1);
    complex c2;
    //临时对象
    complex();
    complex(5,4);//此时complex()就已经结束了
    ...
    cout << complex(3,6);
}
同符号,不同类型的运算
  • 对于一个运算符有多个可能的运算,可以写多个函数
特殊重载
  • 一些特殊的符号,有重载的限制
    • 如 << ,最好使用全局函数修改

六 复习

面向对象程序正规思路:

  • 防卫式声明
  • 类声明
    • 私有区域、公共区域
    • 构造函数
      • (考虑默认值声明)
      • 考虑引用by reference还是传值by value
      • 初值列
    • 是否要改动,是否加const(很重要)?
    • 友元函数
    • 内联函数(写里面,写外面)?
  • 重载运算符(一般都是右边作用在左边)
    • 成员函数
      • by reference :+=
      • by value:对象+对象
    • 非成员函数
      • 前面不需要class名称,如<<
      • 多重传递,如ostream&传值后再返回ostream&可继续传值(cout << c1 <<endl)


第二部分摘要:

String类实现思路:;

  • 类,先放私有的指针类型数据char* m_data
  • 初始化:初值的构造函数
    • 非空:
      • 分配内存
      • 拷贝值strcpy()
    • 空:
      • 分配1内存
      • 指定*m_data=‘\0’
  • 拷贝构造函数
    • 分配内存
    • 拷贝值strcpy()
  • 拷贝赋值函数
    • 自检
    • 释放内存
    • 分配内存
    • 拷贝值strcpy()
    • 返回*this
  • 析构函数
    • 释放内存m_data
  • 字符串要用到cout中,则需要设计一个获得c风格的字符串函数。

七 三大函数Big Three

对象声明数据

  • 使用指针如char*,而不是数组(动态分配的思想)

    class String
    {
    public:
    	String(const char* cstr = 0);
        char* get_c_str() const { return m_data;}
    private:
    	char* m_data;
    }
    

拷贝构造 s2(s1)

class String
{
public:
	String(const char* cstr = 0);
    //---
	String(const String& str);	
    //---

private:
	char* m_data;
}

拷贝复制(赋值) s2 = s1

浅拷贝的危害:

只拷贝指针!!!如果对象直接拷贝,如s2 = s1,其中s2的数据指针,就变成了指向s1的数据指针,而这样却并没有将s2原来的数据内存删除掉,因此内存会泄露

因此我们需要深拷贝

class String
{
public:
	String(const char* cstr = 0);
    //---
    String& operator =  (const String& str);
    //---

private:
	char* 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;
    }
    

析构函数 ~s1()

常用于动态分配的释放

class String
{
public:
	String(const char* cstr = 0);
    //---
    ~String();
    //---

private:
	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::~String(){
        delete[] m_data;
    }
    

八 堆/栈/内存管理

堆 Stack / 栈 heap

  • 存在于作用域scope地一块内存空间memory space。

    • 调用函数,函数本身会形成stack来防止接受的参数以及返回地址
    • 函数本体内声明的任何变量,所使用的内存块都取走上述stack
    • 常见对象:
      • stack obejct:又称为auto object,它会被【自动】请理,scope在函数内
      • static objects。scope全局
      • global objects:可以视为一种static object。scope:全局
  • Heap,或所谓sysytem heap,是指由操作系统提供的一块global内存空间,程序可**动态分配(dynamic allocated)**从某中获得若干区块(blocks)

    • heap objects : 使用new进行分配,其必须使用delect结束生命周期,否则会造成内存泄漏
    class Complex{...}
    ...
    {
        //stack object所占用的空间来自stack
        Complex c1(1,2)
    	//new :占用的空间来自heap,dynamic allocated 
    	Complex* p = new Complex();
    }
    

new的过程(malloc改写)

先分配memory,再调用ctor

Complex* pc = new Complex(1,2);

编译器转化为:

//分配内存:其内部就是调用malloc
void* mem = operator new(sizeof(Complex));
//转型
pc = static_cast<Complex*>(mem);
//构造函数
pc -> Complex::Complex(1,2);

delete的过程(free的改写)

先调用dtor,再释放memory

delete pc;

编译器转化为:

Complex::~Complex(pc); //析构函数
operator delete(pc); //释放内存:其内部使用Free

内存块分配

动态分配Dynamic alloocated in VC
  • 红色:cookie,必须有,记录整块的大小,便于回收
    • 如第一个区域64位,16进制为0040,进一位得0041
  • 白色:调试模式下带的上面32+下面的8,非Debug则没有
  • 蓝色:填补,填空缺
  • 内存块一定是16的倍数:52->64
动态分配的array
  • 复数:其中含有两个double
    • 数据存在一起
Complex* p = new Complex[3];
array new 一定要搭配array delete
  • delete[]会唤起多次dtor,而delete只会唤起一次dtor

  • 如果用delete释放内存,则会造成存在指针的对象数组只被释放一次

  • 虽然没有指针都可以进行正确释放,但需要时刻保持正确的释放方法

第三部分

十:补充扩展

static

  • 函数构成

    • data members
    • static data members
    • non-static member functions
      • 通过this->又可以取到多个non-static data members(实例化多个对象)
    • static member functions:只能处理静态数据
  • 静态数据一定要设置初值

  • 调用方法:

    • Class Name调用
    • object调用
    class Acount{
    public:
    	static double m_rate;
    	static void set_rate(const double &x){m_rate = x;}
    }
        double Account::m_rate = 8.0;
    
    int main(){
        //Class Name调用
        Account::set_rate(5.0);
        
        //Object调用
        Account a;
        a.set_rate(7.0);
    }
    
  • 单例模式

    • 正常
    class A{
    public:
        static A& getInstance(return a;);
        setup(){...}
    private:
        A();
        A(const A& rhs);
        static A a;
        ...
    };
    
    • 更好的单例模式:
      • 更好的原因:只有有人调动他时,才会创建单例。
    class A{
    public:
        static A& getInstance();
        setup(){...}
    private:
        A();
        A(const A& rhs);
    }
    
    A& A::getInstance()
    {
        static A a;
        return a;
    }
    

cout

为什么能输出那么多东西?

  • 继承自ostream,所以看ostream
  • cout定义了很多函数用来接收各种各样的数据

template 模板类

//head
template<typename T>
class complex 
//body
{
	public:
		complex (T r = 0, T i = 0)
			:	re(r), im(i)
			{}
    	complex& opearator += (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,1);
    complex<int> c2;
    ...
}
  • funtion template
    • 方法->模板类->引数推导argument deduction

namespace

  • 防止写出同名的方法造成命名冲突或错误引用
namespace std
{
	...
}
  • 使用方法:

    • using directive

      using namespace std;
      cout<<...;
      
    • using declaration

      using std::cout;
      cout<<...;
      
    • std::cout<<...;
      

十一 组合与继承 三大武器

Composition 复合,表示has-a

A拥有b,功能都由b来完成

  • 寿命一致
  • Adapter设计模式示例:改造函数
template <class T>
    class queue{
        ...
    protected:
        deque<T> c;
    public:
        bool empty() const{ return c.empty();}
        size_type size() const {return c.size();}
        ...//返回c的各种方法
    }
  • Composition下的构造与析构关系
    • 构造由内而外
      • Container::Container(...) : Component(){...}
    • 析构由外而内
      • Container::~Container(...){... ~Component()};

Delegation 委托 Composition by reference

  • 寿命不同

  • 变动可以有弹性,不管指针指的方法怎么变都不会影响现在的类

  • 又称编译防火墙,不用重新编译String的部分,只用编译指rep的部分

  • Handle/Body桥接设计模式举例

    //file String.hpp
    class StringRep;
    class String{
    public:
    	String();
    	String(const char* s);
    	String(const String& s);
    	String &operator=(const String& s);
    	~String();
    	...
    private:
    	StringRep* rep; //pimpl
    }
    
    // file String.hpp
    #include "String.hpp"
    namespace {
        class StringRep(){
            ...
        }
    }
    

    委托可以对外接口一致,String接口永远不变,但是内部实现可以通过修改StringRep改变。

Inheritance 继承,表示is-a

  • 父类的数据被完整地继承下来

  • 三种继承方式(其他语言一般默认是public)

    • public:最重要
    • protected
    • private
  • 内存:

    • 外部包着内部
  • 构造与析构:

    • base class的dtor必须是virtual,否则会出现undefined behaviour
    • 构造由内而外
      • Container::Container(...) : Component(){...}
    • 析构由外而内
      • Container::~Container(...){... ~Component()};

十二 虚函数与多态 virtual functions

  • non-virtual函数:不希望重写

  • virtual函数:希望重写,且有默认定义

  • pure virtual函数:希望重写,且有默认定义

    class Shape{
    public:
    	virtual void draw() const = 0; //纯虚函数,pure vitual
        vitual void error(const std::string& msg); //非纯虚函数, impure virtual
        int objectID() const; //non-virtual
    }
    
  • 子类可以调用父类函数

    • myDoc继承自Document,Document方法可以被子类调用。myDoc.OnFileOpen()&&Document.OnFileOpen()
  • 继承+复合的构造与析构调用优先级?

    
    class Base 
    {
        public:
            Base() { cout << "Base 的 构造函数" << endl; }
            ~Base() { cout << "Base 的 析构函数" << endl; }
    };
     
    class Component 
    {
        public:
            Component() { cout << " Component 的构造函数" << endl; }
            ~Component() { cout << " Component 的析构函数" << endl; }
    };
     
    class Derived : public Base
    {
        public :
            Derived() { cout << "Derived 的构造函数" << endl; }
            ~Derived() { cout << "Derived 的析构函数" << endl; }
            Component c;
    };
    
    

    第一个图的结果:

    • 构造:Base > Component > derived
    • 析构:derived > Component > Base

    第二个:和前面同理,略。

功能最强大:Delegation + Inheritance

  • 举例:多窗口

  • 举例:三种view

User Interface设计(Observer观察者设计模式):

  • 定义一个存储多个Observer的容器,创建出的Observer都可以放在Observer中。
  • 需要提供一个注册和注销的方法Attach(注销还没写)
  • 需要提供一个notify函数,用来通知所有的子类更新
class Subject{
   int m_value; //要被观察的数据
   vector<Observer*> m_views; //观察者列表
public:
   void attach(Observer* obs)//注册
   {
       m_views.push_back(obs);
   }
   
   //修改值之后,要通知所有的Observer,让它们做出改变
   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); //调用Obeserver的方法,去更新Obeserver的显示。把自己指针传出去
   }
}

class Observer{
public:
   virtual void update(Subject* sub,int value) = 0; //纯虚函数,让子类的观察者真正去显示这个值 
}

十三 委托相关设计

Composite组合设计模式

如果要写一个File System,要装下一些File或者其他组件,可以用。

Prototype原型(机)设计模式

父类想创建未来才定义的子类。

  • 父类:

  • 子类:

    • 返回图片类型Type
    • 返回这个class到static的变量中

r Interface设计(Observer观察者设计模式):

  • 定义一个存储多个Observer的容器,创建出的Observer都可以放在Observer中。
  • 需要提供一个注册和注销的方法Attach(注销还没写)
  • 需要提供一个notify函数,用来通知所有的子类更新
class Subject{
   int m_value; //要被观察的数据
   vector<Observer*> m_views; //观察者列表
public:
   void attach(Observer* obs)//注册
   {
       m_views.push_back(obs);
   }
   
   //修改值之后,要通知所有的Observer,让它们做出改变
   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); //调用Obeserver的方法,去更新Obeserver的显示。把自己指针传出去
   }
}

class Observer{
public:
   virtual void update(Subject* sub,int value) = 0; //纯虚函数,让子类的观察者真正去显示这个值 
}

十三 委托相关设计

Composite组合设计模式

如果要写一个File System,要装下一些File或者其他组件,可以用。

[外链图片转存中…(img-mA4ZiRDg-1707740361650)]

Prototype原型(机)设计模式

父类想创建未来才定义的子类。

[外链图片转存中…(img-QkJLRJk2-1707740361650)]

  • 父类:

[外链图片转存中…(img-xzVsIRrv-1707740361651)]

  • 子类:
    • 返回图片类型Type
    • 返回这个class到static的变量中
  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值