看 侯捷老师 C++ 面向对象编程的笔记

Posted 张三和李四的家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看 侯捷老师 C++ 面向对象编程的笔记相关的知识,希望对你有一定的参考价值。

文章目录

c++ 面向对象编程

基于对象:面对的是单一类的设计。

面向对象: 面对的多重类之间的设计,类与类之间的关系。比如:继承,复合和委托。

基于对象

C 和c++

在C 语言中,使用数据+ 函数来构成变量。

在cpp 中, 使用数据成员+成员函数来构成对象。

class template 模板

template <typename T>
class complex

    complex(T r = 0, T i = 0)
        : re(r), im(i) 
    double real() const  return re;
    double imag() const  return im;
private:
	T re, im;    
;

定义在类体(class body)的函数,便自动为inline 函数。

我们可以通过将构造函数放到 private 区域中,这样类将不能实例化对象。只能通过public 中的静态成员函数调用。这是实现设计模式——单例模式的一种用法。

参数传递:pass by value 和 pass by reference

如果通过 value 传递将产生数据拷贝的现象,而reference 则是将实参的地址传递到函数中。少了一层拷贝的动作。传递引用主要是针对类的实体对象。如果是基本数据类型则必要性不大。

返回值:pass by value 和 pass by reference

不能将一个局部变量通过 reference 的形式进行返回。

inline complex
operator + (const complex& x, const complex& y)

	return complex(real(x) + real(y), image(x) + image(y));

可以以引用的方式返回

std::ostream& operator << (std::ostream& os, const complex& x) 

    return os << '(' << real(x) << ',' 
            << imag(x) << ')';

Big Three ,三个特殊的成员函数

构造函数,析构函数,赋值函数。

其中构造函数包含:普通构造函数和拷贝构造函数。

class String

public:
    String(const char * cstr = 0);
    String(const String& str);
    String& operator=(const String& str);
    ~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(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;

什么时候调用拷贝赋值,什么时候调用拷贝构造。

当A 使用B 来初始化A 这个变量时,调用拷贝构造函数。

String B("Hello");
String A(B);//拷贝构造,拷贝B 的数据,构造出A 的对象

将C 的数据赋值给D,调用拷贝赋值函数。

String C("Hello");
String D = C;//还是调用拷贝构造函数,使用C 的数据来初始化D 
D = C;//这调用的采用拷贝赋值,将C 的数据赋值D

使用一个对象来初始化另外一个对象的动作,调用的就是拷贝构造函数。

生命周期和作用域

申请在栈的局部变量会在作用域结束时,自动进行内存释放。

使用 new 申请在堆上的变量只有调用 delete 时,才会进行内存释放。

全局变量的生命周期为整个程序,可以看作一种 static object。

new 和delete 的关键字浅析

当执行 String * E = new String("zhangsan");

编译器会转换为:

String *E;
void* mem = operator new( sizeof(String));//分配内存
E = static_cast<String*>(mem);//转型
E->String::String("zhangsan");//构造函数

当执行delete E;

编译器会转化为:

String::~String(E);	//析构函数
operator delete(E); //释放内存

array new 一定要搭配 array delete

 m_data = new char[ strlen(str.m_data) +1 ];
 delete[] m_data;

面向对象

面向对象编程和面向对象设计。Object Oriented Programming Object Oriented Design

  • Inheritance 继承
  • Composition 复合
  • Delegation 委托

复合

我里面有另外一个东西(类),我和这个东西的关系就是复合。表示 has - a

//适配器模式
template <class T>
class queue
  ...
protected:
    deque<T> c;
public:    
    bool empty() const  return c.empty();
    size_tupe size() const  return c.size();
    reference front()  reutrn c.front();
    reference back() return c.back();
    void push(const value_type& x)  c.push_back(x);
    void pop()  c.pop_front();
;

deque是一个容器,是一个双向队列。可以两边进行先进先出的动作。而queue 是一个单向队列,内部功能都是借用已经实现好的功能来实现。

所以,这不仅是一种复合的类关系,还是一种名为适配器的设计模式。

复合模式下的构造和析构

构造函数调用——由内而外

Container 的构造函数首先调用 Component 的 default 构造函数,然后才执行自己。

Container::Container(...):Component() ...;

其中的 Component()是编译器提供的,如果不希望编译器调用默认的Component构造函数也可以自己去调用。就相当于给类成员赋值。

class Component

public:
    Component()         cout << "ctor one" << endl;    

    Component(const char * cstr)         cout << "ctor " << cstr << endl;    
;


class Containter

public:
    Containter()
    Containter(const char * cstr):m_part(cstr)
private:
    Component m_part;
;

int main()

    Containter con;
    Containter con1("Hello");
    return 0;

析构函数调用——由外而内

Container 的析构函数首先执行自己,然后才调用Component 的析构函数。

Container::~Container(...) .. ~Component();

委托(Delegation) Composition by reference

在需要的时候去初始化指针对象,将事情委托给你(StringRep)去做。

左边的类有一个右边的东西,只不过这个东西有点虚,是一个指针。这样的关系称为委托。

我拥有你这个东西,在任何一个我想要的时间点,我都可以去调用你来做事情,把任务委托给你。

两个类之间,什么时候是delegation,就是使用指针相连接的时候。

类成员对象或许会一开始就创建,但是类成员指针,可能等到使用的时候才去创建它,所以生命周期会不同步。

class StringRep;
class String
public:
    String();
    String(const char * str);
    String(const String& str);
    String& operator=(const String& str);
    ~String();
...
private:
    StringRep* rep;
;
namespace 
    class StringRep 
      friend class String;
        StringRep(const char *s);
        ~StringRep();
        int count;
        char * rep;
    ;

上图实现了一个共享数据的方式,其中的rep 指向的同一份数据,n 表示一共有多少个对象使用。这需要保证,a b c 三个对象都不能擅自去修改rep 指向的内容,如果有对象要修改则需要进行 copy on write 的动作,就是单独拷贝一份出去修改。

继承 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 的默认构造函数,然后才调用自己的。顺序是由内而外。一个个产生,类似于洋葱生长的过程中,由内而外的生成。

Derived::Dervied():Base() 

而析构函数则是,调用Derived的析构函数,然后才去调用Base 的析构函数。顺序是有外而内。一层层的析构,类似于剥洋葱。

Derived::~Derived()... ~Base()

Base class 的 dtor 必须是 virtual,否则会出现 undefined behavior。

Qt 中的扩展

//现在才知道  QMainWindow(parent) 这个代码段的作用是,调用父类指定的构造函数。如果不写的话,就只会调用父类默认的构造函数
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
    ...

继承下的虚函数

只有子类重写父类的虚函数时,才称为override。

作为一个父类,成员函数有三种选择。

non -virtual 函数: 你不希望 derived class 去重新定义(覆写,override)它。

virtual函数:你希望 derived class 去重新定义它,(有派生类自己特别的实现),且你对它已经有了默认的定义。

pure virtual 函数:你希望 derived class 一定要重新定义它,你对它没有默认的定义。

比如:

class Shape
public:
    virtual void draw() const = 0;//纯虚函数,因为 Shape 是一个抽象事物,没有 draw 函数可以去实体话它
    virtual void error(const std::string& errStr);//虚函数,不同的子类可能会出现不同的实现方式,所以,希望子类去根据自己的特征去override 它。
    int objectID() const;//普通成员函数
    ...
;

class Rectangle: public Shape...;
class Ellipse: public Shape...;

有一些函数没有办法先写出来,要让子类去实现它,这样的函数就要设计为 virtual function 。可以是空的虚函数,也可以是一个纯虚函数。

我先创建一个子类的对象,然后通过子类去调用父类的函数。当执行父类的OnFileOpen 函数,到 Serialize 时,发现子类有写这个函数,则会调用子类的 Serialize。然后再回到调用端。

这个函数做了一些固定的动作, 将其中一些关键的部分 (Serialize) 延缓到子类去实现。我们将这种做法称为 Template Method

具体要做哪些关键部分,这个就是你想要做的事情。

为什么会在父类的函数中调用子类的函数呢?

因为myDoc.OnFileOpen()的调用会被编译器转换为CDocument::OnFileOpen(&myDoc),其中的 Serialize(),背后是this->Seriallize(),其中的this 指的是myDoc

继承+复合关系下的构造和析构


猜一下,两种情况下的构造和析构的顺序?(#^.^#)

以上是关于看 侯捷老师 C++ 面向对象编程的笔记的主要内容,如果未能解决你的问题,请参考以下文章

C++面向对象编程

C++面向对象编程

[C++ 面向对象高级编程]string类的实现过程

[C++ 面向对象高级编程]Complex类的实现过程

侯捷P11面向对象编程

[C++ 面向对象高级编程]知识点补充1