侯捷老师视频笔记整理_____C++面向对象开发上(基础部分)

C++面向对象开发上

培养正规的、大气的编程习惯

0. 面向对象三大特征 —— 封装、继承、多态

封装

  • 把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

继承

  • 基类(父类)——> 派生类(子类)

多态

  • 多态,是以封装和继承为基础,使得消息可以多种形式显示

0) C++ 多态分类及实现:

  1. 重载多态(Ad-hoc Polymorphism,编译期):函数、运算符重载
  2. 子类型多态(Subtype Polymorphism,运行期):虚函数
  3. 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
  4. 强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换
    The Four Polymorphisms in C++

1) 静态多态(编译期/早绑定) 函数重载

class A
{
public:
    void do(int a);
    void do(int a, int b);
};

2) 动态多态(运行期期/晚绑定)虚函数

  • 虚函数:

    用 virtual 修饰成员函数(含基类的虚构函数),使其成为虚函数.

  • 非虚函数:

  1. 普通(全局)函数(非类成员函数)
  2. 静态函数(static)
  3. 构造函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针
  4. 内联函数不能是表现多态性时的虚函数,解释见:虚函数(virtual)可以是内联函数(inline)吗?

一、C++编程简介

基础知识

曾学过procedural language (C 语言最佳),知道如何对程序编译、链接、执行。

变量(variables)

类型(types) : int, float, char, struct …

作用域(scope)

循环(loops) : while, for,

流程控制: if-else, switch-case

基于对象分类:

  1. 基于对象:一个class的编程 object based

  2. 面向对象:几个class的编程 object oriented

class的经典分类:

  1. class without pointer members ——>e.g: complex 复数
  2. class with pointer members  ——>e.g: string 字符串

class之间的关系:

  1. 继承inheritance、
  2. 复合composition
  3. 委托delegation

C++书籍(STL是标准库的前身)

基础:Language
《C++Primer》
《C++programming Language》
提高:Standard Library
《Effective C++ Third Edition》及中文
《The C++ Standard Library》
《STL源码剖析》

二、头文件与类的声明


//flie XXX.h
#ifndef __complex__
#define __complex__


#program once //编译器宏


// class的布局:

class base; //前置声明

class header{// class header
    // class body
     ...
}

#endif //XXX.h end

// XXX.cpp
#include"XXX.h"   //c
#include<cstdio>  //C++ 
#include<iostream>

三、构造函数

(1)inline内联函数:

特征

  1. 相当于把内联函数里面的内容写在调用内联函数处
  2. 相当于不用执行进入函数的步骤,直接执行函数体
  3. 相当于宏,却比宏多了类型检查,真正具有函数特性
  4. 编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
  5. 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
//--------------------------声明-----------------------------------------------
// 声明1(加 inline,建议使用)
inline int functionName(int first, int second,...);
// 声明2(不加 inline)
int functionName(int first, int second,...);’

//--------------------------定义-----------------------------------------------
// 类内定义并实现,隐式内联
class A {
    int doA() { return 0; }         // 隐式内联
}
// 类外定义,需要显式内联
class A {
    int doA();
    int functionName(int first, int second,...);
}
inline int A::doA() { return 0; }   // 需要显式内联
inline int functionName(int first, int second,...) {/** ...**/};// 需要显式内联

编译器对 inline 函数的处理步骤

  1. inline 函数体复制inline 函数调用点处
  2. 为所用inline函数中的局部变量分配内存空间;
  3. inline函数的输入参数和返回值映射到调用方法的局部变量空间中;
  4. 如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用GOTO)

优缺点 主要与宏定义比较

优点
>1. 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度
>2. 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
>3. 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
>4. 内联函数在运行时可调试,而宏定义不可以。

缺点
>1. 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间
>2. inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
>3. 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

(2)access level 访问级别

public 成员:可以被任意实体访问
private 成员:只允许被本类的成员函数访问
protected 成员:只允许被子类及本类的成员函数访问

private:数据的部分用尽量用private
public:函数的部分,大部分用public

(3)构造函数

创建一个对象的时候,构造函数自动被调用,构造函数可设置默认参数,并用冒号设置参数初始化列表:

pair(const T1& a, const T2& b) : first(a), second(b) {}

参数initializition list和在body里对参数赋值的区别:
一个是参数初始化;
一个是赋值,是一个执行的过程,多了计算量;
创建一个对象,可以有参数,也可以无参数,也可动态创建:

class complex *p = new complex(4); //动态创建

class定义了多个构造函数,就是重载overloading

(4)friend 友元类和友元函数:

  1. 能访问私有成员
  2. 破坏封装性
  3. 友元关系不可传递
  4. 友元关系的单向性
  5. 友元声明的形式及数量不受限制
  6. 相同class 的各objects 互friends (友元)
#include <iostream> 
class A { 
      friend class B; // Friend Class 
  }; 
  
  class B { 
  public: 
      void showA(A& x) { 
          std::cout << "A::a=" << x.a; 
      } 
  }; 
  //OR 
  class B; 
  class A { 
  public: 
      void showB(B&); 
  }; 
  
  class B { 
      friend void A::showB(B& x); // Friend function 
  }; 
  
  void A::showB(B& x) { 
      std::cout << "B::b = " << x.b; 
  } 

item23. 宁以 non-member、non-friend 替换 member 函数(可增加封装性、包裹弹性(packaging flexibility)、机能扩充性)

//member
class WebBrowser {
public:
    void clearCache();
    void clearHistory();
    void removeCookies();

    void clearEverything();  //调用上述的三个函数
};
//non-member
void clearBrowser (WebBrowser& wb)
{
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}

(5)static :

class Account {
public:
    static double m_rate;
    static void set_rate(const double& x) { m_rate = x; }
};
double Account::m_rate = 8.0;

int main() {
    Account::set_rate(5.0);
    Account a;
    a.set_rate(7.0);
}
// Meyers Singleton
class A {
public:
    static A& getInstance();
    setup() { ... }
private:
    A();
    A(const A& rhs);
};
A& A::getInstance()
{
    static A a;
    return a;
}

// Singleton
class A {
public:
    static A& getInstance(){ return a;} //???? 猜测结果
    static A& getInstance( return a; ); //???? ppt中
    setup() { ... }
private:
    A();
    A(const A& rhs);
    static A a;
};


//call  多线程中不安全.
A::getInstance().setup();

四、参数传递和返回值以及const

  1. 数据放在private
  2. 参数用reference,是否用const
  3. 在类的body里的函数是否加const
  4. 构造函数的 initial list
  5. return by reference,不能为local object.

指针和引用的区别:

  1. 都是地址的概念;指针是一个实体,指向一块内存,它的内容是所指内存的地址;引用则是某块内存的别名。
  2. 使用sizeof指针本身大小一般是(4),而引用则是被引用对象的大小;
  3. 引用不能为空,指针可以为空;指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
  4. 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
  5. 引用没有const,指针有constconst的指针不可变;
    具体指没有int& const a这种形式,而const int& a是有的,前者指引用本身即别名不可以改变,这是当然的,所以不需要这种形式,后者指引用所指的值不可以改变)
  6. 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
  7. 指针可以有多级指针(**p),而引用至于一级;
  8. 指针和引用使用自增(++)运算符的意义不一样;
  9. 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。
  10. 引用是类型安全的,而指针不是 (引用比指针多了类型检查)

const作用

  1. 修饰变量,说明该变量不可以被改变;

  2. 修饰指针,分为指向常量的指针和指针常量;

  3. 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;

  4. 修饰成员函数,说明该成员函数内不能修改成员变量。

//const 使用
// 类
class A
{
private:
    const int a;                // 常对象成员,只能在初始化列表赋值
public:
    // 构造函数
    A() : a(0) { };          // 初始化列表
    A(int x) : a(x) { };        // 初始化列表
    // const可用于对重载函数的区分
    int getValue();             // 普通成员函数
    int getValue() const;       // 常成员函数,不得修改类中的任何数据成员的值
    // 可以供常对象使用,否则报错,调用有可能被改变值,编译器报错。
};

void function()
{
    // 对象
    A b;                        // 普通对象,可以调用全部成员函数
    const A a;                  // 常对象,只能调用常成员函数、更新常成员变量
    const A *p = &a;            // 常指针
    const A &q = a;             // 常引用
    // 指针
    char greeting[] = "Hello";   
    char* p1 = greeting;                // 指针变量,指向字符数组变量
    const char* p2 = greeting;          // 指针变量,指向字符数组常量
    char* const p3 = greeting;          // 常指针,指向字符数组变量
    const char* const p4 = greeting;    // 常指针,指向字符数组常量
}
// 函数
void function1(const int Var);           // 传递过来的参数在函数内不可变
void function2(const char* Var);         // 参数指针所指内容为常量
void function3(char* const Var);         // 参数指针为常指针
void function4(const int& Var);          // 引用参数在函数内为常量
// 函数返回值
const int function5();      // 返回一个常数
const int* function6();     // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7();     // 返回一个指向变量的常指针,使用:int* const p = function7();

五、操作符重载与临时对象

1、成员函数带有隐藏的参数this,谁调用这个函数谁就是 this.临时对象 complex() ->typename();
2、在类外Complex Complex::operator+=(complex &c2) 这个是成员函数 operator+= 的实现,所以需要 Complex:: 具有this指针。例如:

inline complex&
__doapl(complex* ths, const complex& r)
{ //第一參數將會被改動   //第二參數不會被改動
   ths->re += r.re;
   ths->im += r.im;
return *ths;
}

inline complex&
class complex::operator += (const complex& r) //成员函数 this
{
  return __doapl (this, r);
}
//而下面属于运算符重载,不是成员函数的时候,就没有Complex:: 。
inline complex
operator - (const complex& x, double y)
{
  return complex (real (x) - y, imag (x));
}

六、复习Complex类的实现过程

七、三大函数:拷贝构造,拷贝赋值,析构

构造函数(可以重载,类内创建private)
拷贝构造函数
拷贝赋值函数
析构函数

常量成员函数 const修饰成员函数,防止常量对象进行调用出错。

class with pointer members 必須有copy ctor 和copy op=

一定要在operator= 中檢查是否self assignment

inline String& String::operator=(const String& str)
{
    if (this == &str) //自我赋值检查
        return *this;
    delete[] m_data;  //删除
    m_data = new char[ strlen(str.m_data) + 1 ]; //new
    strcpy(m_data, str.m_data); //copy
    return *this; //链式 返回引用
}

#include <iostream.h>
ostream& operator<<(ostream& os, const String& str)
{
    os << str.get_c_str();
    return os;
}

——》类含指针,就需要 拷贝构造、 拷贝赋值函数

浅拷贝——》拷贝构造函数 ,浅拷贝的影响:
1、造成内存泄漏;
2、造成有两个指针 指向同一块内存

深拷贝——》拷贝赋值函数
步骤:delete ;new; strcpy;
class里面有默认的拷贝构造和拷贝赋值函数。如果自己不定义一个拷贝构造函数,在调用拷贝构造函数的时候,就会调用默认的浅拷贝构造函数,就会造成问题,所以一定要自己定义拷贝构造函数——深拷贝。

八、堆,栈与内存管理

1、static local objects的生命周期

  • static的生命周期 :object的对象在scope结束以后仍然存在,直到整个程序结束;
  • 非static 的生命周期:object的对象在在scope结束以后就结束了。
  • global objects的生命周期:对象 objects 生命结束,就是什么时候析构函数被调用。

2、new——》operator new。

new动态创建对象,分三步:

  1. 先转化为operator new 函数,申请分配内存。
  2. 做类型转化。
  3. 调用构造函数

delete ——》operator delete。删除对象,分两步:

  1. 先调用析构函数,
  2. 再调用operator delete函数。

3、带中括号[ ]的new[ ]叫做array new,带中括号[ ]的delete[ ]  叫做array delete。

 动态分配所得到的数组array:complex *p = new complex[3];

new []  ——》delete[] ——》表示调用几次析构函数
 new 字符串 ——》delete 指针

delete[n] :array new一定要调用array delete,delete[n]会调用n次析构函数,而delete仅调用一次。

Stack,是存在于某作用域(scope) 的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack 用来放置它所接收的参数,以及返回地址。在函数本体(function body) 内声明的任何变量,其所使用的内存块都取自上述stack。

Heap,或谓system heap,是指由操作系统提供的一块global 内存空间,程序可动态分配(dynamic allocated) 从某中获得若干区块(blocks)。

{
    class Complex* p = new Complex;
    ...
    delete p; //若未删除,内存泄露,再也无法删除了,p退出作用域,作用域外无法看到p。
    
    //编译器实现如下
    Complex *pc;
    void* mem = operator new( sizeof(Complex) ); //分配内存
    pc = static_cast<Complex*>(mem); //转型
    pc->Complex::Complex(1,2); //构造函数
    
    Complex::~Complex(pc); // 析構函數
    operator delete(pc); // 釋放內存
}
{
    String* ps = new String("Hello"); 
    //编译器实现如下
    String* ps;
    void* mem = operator new( sizeof(String) ); //分配内存
    ps = static_cast<String*>(mem); //转型
    ps->String::String("Hello"); //构
}

九、复习String类的实现过程


class String
{
public:
    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';
        }
    }
    String(const String& str)
    {
        m_data = new char[ strlen(str.m_data) + 1 ];
        strcpy(m_data, str.m_data);
    }
    String& operator=(const String& str);
    ~String()
    {
        delete[] m_data;
    }
    char* get_c_str() const { return m_data; }
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;
}

十、扩展补充:类模板,函数模板,及其他

//class template,类模板
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&);
};
//函数模板
template <class T>
inline const T& min(const T& a, const T& b)
{
    return b < a ? b : a;
}

class stone
{ public:
    stone(int w, int h, int we)
        : _w(w), _h(h), _weight(we)
    { }
    bool operator< (const stone& rhs) const
    { return _weight < rhs._weight; }
private:
    int _w, _h, _weight;
};

//引数推导的结果,T 为stone,于是调用stone::operator<

stone r1(2,3), r2(3,3), r3;
r3 = min(r1, r2);

    (1)static :静态数据 不属于某一个对象,而非静态的是属于一个对象的,这种情况需要设置为静态数据。

            静态函数 没有this pointer,而非静态函数有 this pointer,可以用this去取数据,静态函数要处理数据只能处理静态数据静态数据一定要在class外面 定义。给变量赋值,使获得内存的过程叫定义。  

    (2)template:类模板  函数模板
    (3)namespace:

十 一、组合 has-a 与继承 is-a

由内而外template <class T>
struct Itr {
    T* cur;
    T* first;
    T* last;
    T** node;
    //Sizeof : 4 * 4
};

template <class T>
class deque {
protected:
    Itr<T> start;
    Itr<T> finish;
    T** map;
    unsigned int map_size;
    //Sizeof : 16 * 2 + 4 + 4
};

template <class T>
class queue {
protected:
    deque<T> c;
    //Sizeof : 40
};

//构造顺序: Itr->deque->queue 由内到外
//析构顺序: queue->deque->Itr 由外到内
//queue内存大小:queue的内存大小+deque的内存大小

(2)委托delegation,即composition by reference:在body中声明一个带指针的另一个类 composition by reference 生命时间:  classA 用一个指针指向classB,需要的时候才调用classB,而不是一直拥有classB。

//委托delegation
// file String.hpp
class StringRep;
class String {
private:
    StringRep* rep; // pimpl
};

// file String.cpp
#include "String.hpp"
namespace {
class StringRep {
    friend class String;  //friend
    StringRep(const char* s);
    ~StringRep();
    int count;
    char* rep;
};
}
String::String(){ ... }

  (3)继承Inheritance:(三种继承方式:public protected private)is-a,继承主要搭配虚函数来使用函数的继承:指的是继承函数的调用权,子类可以调用父类的函数

//继承中的     构造函数与析构调用顺序
//构造顺序: 父类->子类  由内到外
//析构顺序: 子类->父类  由外到内

//继承+组合模式  构造函数与析构调用顺序
Derived::Derived(...):Base(),Component() { ... };

Derived::~Derived(...){ ...  ~Component(), ~Base() };

十二、虚函数与多态

//虚函数 你期待derived class的行为
non-virtual 函数:不重新定义(override, 覆写它).
virtual 函数:重新定义(override, 覆写) 它,且你对它已有默认定义。
pure virtual 函数:必须重新定义(override 覆写)它,你对它没有默认定义。

(1)虚函数:virtual 纯虚函数:一定要重新定义。

 (A)Inheritance + composition下的构造和析构

 (B)delegation + Inheritance ——》 功能最强大的一种

实例:Template Method模式

//程序库开发人员
class Library
{
  public:
    //稳定 template method
    void Run()
    {
        Step1();
        Step2();//支持变化 ==> 虚函数的多态调用
        Step3();
        Step4(); //支持变化 ==> 虚函数的多态调用
        Step5();
    }
    virtual ~Library() {}

  protected:
    void Step1(){}//稳定
    void Step3(){}//稳定
    void Step5(){}//稳定

    virtual bool Step2() = 0; //变化
    virtual void Step4() = 0; //变化
};

//应用程序开发人员
class Application : public Library
{
  protected:
    virtual bool Step2(){    //... 子类重写实现
        return true;
    }

    virtual void Step4(){     //... 子类重写实现
    }
};

int main()
{
    Library *pLib = new Application();
    pLib->Run();

    delete pLib;
}

十三、委托相关设计

The End

ps : 学不动了,哈哈哈哈哈哈。

【1】大纲是Gayhub上热心网友的,进行了部分补充,非常感谢共享 。

​ 因个人水平有限,欢迎大家指导。 yzhu798#gmail.com 。2020.03.07

另:https://github.com/yzhu798/CodingInterviewsNotes 有一些其他大佬们的笔记,偷偷整理了下,大家可以去看看,狗头保命。

猜你喜欢

转载自www.cnblogs.com/yan1345/p/12436689.html