看 侯捷老师 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++ 面向对象编程的笔记的主要内容,如果未能解决你的问题,请参考以下文章