1.简介
基于对象(Object Based)
- class without pointer members --Complex
- class with pointer members -------String
面向对象(Object Oriented)
- 学习Classes之间的关系
- 继承
- 复合
- 委托
C++历史
C++有很多版本,如 C++98,C++03,C++ 11,C++14
学习C++(其他语言也是)可以分为两个部分:
- C++语言
- C++标准库
经典的书:
- C++ Primer (C++第一个编译器的作者写的)
- C++ Programming Language (C++之父写的)
- Effective C++ (专家写的,以条款方式写的)
- The C++ Standard Library(写的C++的标准库)
- STL源码剖析(较深入的将标准库)
2.头文件与类的申明
防卫式申明!
#ifndef __COMPLEX__
#define __COMPLEX__
....
#endif
inline内联函数
函数在class本体里定义就形成了inline,inline像宏一样,函数如果是inline的,就会很快,所以所有的函数都写成inline是最好的,然而编译器没有能力把所有函数都做成inline,一般来说程序复杂,编译器就不能做成inline。inline function最后能不能做成inline看编译器的能力。
不在本体里定义inline的要加inline:
inline double imag(const complex& x)
{
...
}
访问级别(access level)
- public :外部能看到的
- private:只有自己能看到的,一般只有内部函数处理的数据要用private
- protect:受保护的
3.构造函数
比较熟悉,略
4.参数传递与返回值
定义函数 确定是否添加 const
- 对不会改变的参数前加const 避免误用修改了参数值
- 也会在整个函数前 加const
参数传递 (值传递 vs.引用传递)(pass by value vs. pass by reference(to const))
- by value :是数据整包传过去,数据有几个字节就传几个字节
- by reference(引用):带&,传引用相当与传指针的速度。
- by reference(to const):没有const时,传给对方的数据,对方可能修改后影响我,如果不想自己受到影响,就要加上const,表示此引用不会该改变数据,对方改变就会出错。
良好的习惯:参数传递尽量的传引用,速度快
返回值传递(return by value vs. return by reference(to const))
基本同上
友元 friend
熟悉,略
5.操作符重载和临时变量
熟悉,略
6.复习Complex类的实现过程
熟悉,略
7.三大函数:拷贝构造,拷贝复制,析构(big three)
//拷贝构造
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;
}
class with pointer members 必须有 copy ctor和copy op=
否则使用default copy ctor 或者 default op= 就会造成内存泄漏
默认拷贝 即是 浅拷贝!
如下图,b=a后,两者指向一个,容易出错,同时“world” memory leak!
//拷贝赋值函数
inline
String& String::operator=(const String& str)
{
//检测自我赋值 (self assignment)
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;
}
自我赋值!不止为了效率,也为了正确率!
8.堆、栈与内存管理
- 左移 “<<”运算符 只能写成 全局函数,不可以写成成员函数!
- Stack (栈)
- 是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址
- 在函数本体(function body)内声明的任何变量,其所使用的内存块都取自上述stack。
- 编译器自动开辟和释放
- Heap(堆)
- 或称 system heap,是指由操作系统提供的一块global内存空间,程序可动态分配(dynamic allocated)从某中获得若干区块(blocks)
- new 开辟 和 delete 释放
- 手动
class Complex{....};
...
...
{
// stack
Complex c1(1,2);
// heap
Complex* p = new Complex(3);
- stack objects的生命期 : 作用域(scope)结束时自动结束
- stack local objects的生命期 : 程序结束 即一直存在 命名时前加:关键字 static
- global objects的生命期:也是程序结束,定义为全局变量
- heap objects的生命期: delete 决定,如果不delete 就会memory leak !!浪费内存!很严重的问题!
因此主要讲解heap的 new 和 delete
- new:先分配 memory 在调用ctor(构造函数)
- delete : 先调用dtor(析构函数) ,再释放memory
- 细节如下: 可以看到malloc和free只是其中的一个步骤,在搞内存时!
动态分配所得的内存块(memoty block)in VC(编译器)
侯捷大师的讲解!!
要素过多:
- 带灰色的是调试模式,左边是“复数” 右边是“String”
- 申请的字节数 必须是16的倍数,因此pad部分称为 填充物!
- 头尾红色的是cookie(曲奇小饼干)用来标记长度 如41H 其实是64字节;多出来的1H表示获得状态,free时为0!
动态分配所得的array 基本同上!
array new 要搭配 array delete
- 如果没有搭配 就是左边的情况 这里只调用一次dtor ,然后就free了内存 所以造成了 下面2个的内存泄露
- 而像complex这种dtor什么也没干的 即使写错了也没事,因为它的dtor什么也没干,但这样很容易搞混 所以都要带!!
9.复习String类的实现过程
class String { public: String(const char* cstr =0 ); //带指针 考虑Big three //拷贝构造 String(const String& str); //拷贝赋值 String& operator=(const String& str); //析构函数 ~String(); char* get_c_str() const { return m_data; } 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'; } } inine String::~String() { delete[] m_data; } 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(&str==this) { return *this; } delete[] m_data; m_data= new char[strlen(str.m_data)+1]; strcpy(m_data,str.m_data); return *this; }
10.扩展补充:类模板,函数模板 及其他
进一步补充:static
静态数据 :需要类内申明 ,类外定义。
静态函数 没有this指针。有如下的两种调用方式。
进一步补充:把ctors放在private类
这里的Singleton和Meyers Singleton是两种设计模式
Singleton(单例)只存在一个,但上面的定义存在一个问题,即使不调用 也一直存在,比较浪费。于是
进一步补充:cout
继承自 ostream
进一步补充:class template 类模板
注意关键字 typename
进一步补充:function template 函数模板
关键字 class 还会自动推导
进一步补充:namespace(命名空间)
11、组合和继承
面向对象的编程
继承、复合、委托
Compisition(复合) 表示has -a
- Adapter是一种设计模式 (比较强的东西 修改 以适应当前的 任务)
- deque是双向队列,queue是队列,因此这里的六个函数,均能直接用deque的,不用自己重新写。
- 注意 其右上角的符合表示 实心菱形
内存的角度看 复合 40=16*2+4+4
Composition(复合)关系下的构造和析构
- 注意构造 调用Componue(组件)的默认构造,如果需要调用其他的需要自己去写。
- 以下从内存的角度看
Delegation(委托)/Composition by reference
- 用指针指向调用的class 也将任务委托过去
- 通过指针 叫 by reference 没有 by pointer的叫法
- 注意右上角符号 的空心,代表指针没那么实在,只有用到的时候才会调用,因此生命周期不一致
- 左右两部分 的设计模式叫Handle/Body!非常有名,因为增加了Body的弹性,而Handle面向用户看不出来变化,也叫做编译防火墙!!很牛的样子
- 这里Body在做“引用计算”,看左下角的具体过程,有三个共享“hello”字符串
Inheritance(继承) ,表示is-a
- 右侧图示表示
- 可以继承数组,而最有价值的点是虚函数!!!!
Inheritance(继承)关系下的构造和析构
- 依然是从内存的角度看,注意其先后顺序,和复合很像
12.虚函数与多态
- 函数的继承:继承的是调用权
Inheritance(继承)with virtual functions(虚函数)
- non-virtual函数:你不希望derived class 重新定义(override 覆写)它
- virtual函数:你希望derived class重新定义(override,覆写)它,且你对它已有默认定义
- pure virtual函数:你希望derived class一定要重新定义(override 覆写)它,你对它没有默认定义
Inheritance(继承)with virtual
- 左侧是父类,framework框架(可能很久前就写好的,一些常规性操作)
- 右侧是子类,后续自己实现的
- 大名鼎鼎的设计模式 Template Methos(java里的概念)
- 中间的粗灰线是 函数的调用过程(灰色框里是编译器的解释)
- 右下角的 子类对象调用父类函数!
Inheritance+Composition关系下的构造和析构
Delegation(委托)+Inheritance(继承)
- 功能最强大的组合
- 为了解决下面这样的问题,同一个东西的多种显示
- 左边放数据,右边是观察者
- 让左边可以有很多的右边,使用者可以很多的右边
- 开辟一个容器,用Observer指针可以指向很多个右边
- 右边用虚函数 可以让子类 可以干很多事情
13.委托相关设计
Delegation(委托)+Inheritance(继承)
用图代表代码
想用Primitive(基础物)和Composite防在同一个容器中
创建一个父类,二者均继承,用指针委托
问题:我现在要创建一个未来的Class对象怎么办?
Prototype
- 带下划线表示静态 LSAT
- 很复杂的一个例子,没太听懂,但老师一直拍案叫绝,以后要用的时候再细细琢磨吧
至此,第一系列的课程结束!
不得不说侯捷老师不愧为大师,很多知识点讲的深入浅出,拓展度也很大,C++果然很复杂。
路漫漫其修远兮,吾将上下而求索。