C++面向对象高级编程上
前2节
1、类就是把函数和数据包在一起,数据可以有很多份,但函数只有一份
2、string类的对象(object)的大小,其实每个对象中都只有一个指针,只不过是这个指针指向了某个具体存放字符的地址
3、当你设计的类中如果有指针的话,那么要特别小心
4、引用标准库的头文件用尖括号,引用自己的写的头文件用双引号
5、C中的与C++中的基本类似
6、在自己写的头文件中有防卫式声明
#ifndef _COMPLEX_
#DEFINE _complex_
#endif
7、头文件布局:先前置声明,在类声明,最后类定义
现在的类定义一般写成一个单独的源文件
8、一个类包含 class head,class body,
有些函数直接在 class body 中定义,也有一些定义在 body 之外
第3节
1、inline(内联)函数
1)在body之内定义的函数就是inline function,但最后是否真的变为inline function,不知道,一般来说复杂的函数编译器无法把他变为inline,简单的一般都可以。
2)在body之外的函数定义,前面加一个inline关键字,告诉编译器,你尽量把他变成inline.
2、构造函数 constructor ctor
2、构造函数
1)C++说,如果想创建一个对象,有一个函数会自动调用,就是构造函数。
2)构造函数不需要有返回值,因为他就是用来创建对象才被发明的。
3)构造函数有一个特殊用法,叫做初始列或初值列
例如你在一个complex类的构造函数中写法如下:
//第一种方式:初值列
public:
complex(double r=0,double i=0) : re(r),im(i)//初始化的方式
{ }
private:
double re,im;
对比这两种写法的不同,这两种最后的结果是一样的,简单来说就是
一个变量数值的设定有两个阶段,一个是初始化,一个是赋值,如果没有用第一种方式,但是效率相对是低的,语法就是在冒号后面写,
第二种方式:赋值
public:
complex(double r=0,double i=0)
{
re=r;//赋值的方式
im=i;
}
private:
double re,im;
3、关于默认实参
默认的意思就是:如果你在创建的时候,没有指明,那就用默认值,如果你有指明,那就用你的。
所有函数都可以写出它的默认值。
4、关于析构函数,不带指针的类,多半都不用写析构函数。
第4节
1、构造函数与private
如果构造函数放在private中,那么在创建的时候就不能被调用,一旦创建对象就会报错,那么这个类有什么用呢?
在设计模式中的单例模式(singleton)就用到这种方式。
外界不能创建这样的类,要通过一个成员函数才能完成创建对象
1、参数的传递,尽量用引用(by reference)
2、返回值的传递,也尽量用引用(by reference)
3、友元函数(朋友)拿数据是直接拿,会比 非友元函数 传递快一点
4、相同class的各个对象(object)互为友元(friend)
2、写一个类的时候:
1)数据尽量放在private
2)传递参数尽量用引用,要不要加const看具体情况
3)返回值尽量用引用,首先考虑,如果不行就在舍弃
4)在类的body中应该加const就要加,有些你不加就会报错
5)构造函数的特殊初始化形式,比普通初始化要快,尽量用,这个是构造函数的特殊用法(不能传递local变量或对象(也就是本函数内创建的变量),因为这个函数结束时,函数本身创建的对象或变量就销毁掉了,此时传递引用就出错了)
6)也就是说当返回类型为local object时,不能传引用
第5节
1、所有的成员函数,一定带着一个隐藏的this参数,不用写也不能写出来这个this,写出来就错了,但是可以在函数里用this,this是一个指针,
2、关于 << 输出流 output operator
一定是作用在左边身上,为什么要重载 << 因为很多年前写好的这个操作符中,肯定不认识你新写好的类,当你想输出的时候,必须要重载。
我们知道操作符有两种写法,一种是成员函数,一种是非成员函数,一般来说两种都可以考虑。
但是这个 << 操作符来说,这个重载必须写成全局函数,一定不能写成成员函数。右面是一个复数,左边是接收端,cout是一个object,他的类型是ostream,是ostream类的一个对象,
对于返回类型,如果每次只有一个输出,那么返回值类型可以设置为void,但是如果有连串输出的话,那么返回值类型就需要能继续接收<<传递来的值,所以返回值类型要设计为 ostream
ostream& operator << (ostream& os,const complex& x)
{
return os << '(' << real(x) << imag(x) << ')';
}
第6节
<< 操作符重载
在视频中的21:00开始讲的,听了好几遍还是迷迷糊糊
为什么不能?
cout << c1; // 式(1)
c1 << cout; // 式(2)
首先明白我们的目的是把c1给cout,第二点是 << 操作符一定是作用到左边身上去,所以不能写为成员函数,为了适应大家的写作习惯,需要用(1)来写
第7节
string 类
拷贝构造 copy ctor & 拷贝赋值 copy op =
编译器本身有一套拷贝构造和拷贝赋值,是一个位一个位(bit)进行复制,如果编译器给的这一套,够用,自己就不用重写,对于复数来说,没有必要写,按照编译器本身的那一套,也是会实部虚部都一个一个来复制。但是对于string类,里面有指针,就不可以。
所以如果你写的类里面带有指针,一定不能用编译器的那一套默认版本,要自己来定义。
第8节
作用域 scope 作用域内的对象又称为 auto object,在作用域结束之际会被自动清理
栈 stake 创建在栈区的对象称为 stack object,在作用域结束之际结束
堆 heap 有程序员创建,且必须由程序员亲自释放,否则会出现内存泄漏
静态 static 在任何地方定义的静态对象,在作用域结束之后仍然存在,直到程序结束
全局 global 定义在所有作用域之外的对象称为全局对象,可以视为静态对象的一种,
生命周期也是整个程序结束之后才结束。
第10节
1)static
* 静态的成员函数没有 this point,只能处理静态的数据
* 静态数据只有一份
* 如果类内有静态数据的声明,那么一定要在class外做定义(有人称之为 声明,设初值)
* 什么是定义:写一行代码会使得这里面的变量获得内存,这样叫做定义。
* 调用静态函数的方式有两种:①通过object调用,②通过class name 调用
* 注意非静态函数的调用,会有一个隐藏的操作,是这个对象的地址会被放在形参类表中变成this point,
* 对于静态函数,编译器就不会有这样的操作,也就是没有 this point
* 典型应用: singleton 单例模式,把构造函数放在private里,通过函数仅能创建唯一的一个对象
2)cout
*为什么cout可以打印各种各样的东西呢?
*因为cout是属于ostream这个父类的一个子类中的一个参数,在ostream中做了很多很多<<这个操作符的重载
*所以cout可以接受那么多种不同类型的数据,并打印出来。
3)class template & function template
*class template语法 在类的开头写上 template<typename T>
*function template语法 在函数开头写上 template<class T>
*class template用法 必须在使用时指明类型,如complex类:complex<int> c1;
*function template用法 在使用是不需要指明类型,编译器会对函数模板进行参数推导
4)namespace
*标准库所有的东西都被包括在std里面,并不一定要写的很长,可以分段来写,做后会结合在一起
*有三种用法:
*① 全开写法 在开头加上using namespace std
*② 单行开,比如只用cout,就在开头加上using std::cout
*③ 都不开,用一个写一个,避免了冲突,在使用时 std::cout
面向对象的三种关系
也就是 类与类之间的关系 主要有以下三种:
1、Composition 复合
* 比如A为队列,B为双端数组。
* 一个类A中有另一个类B的对象 has a primer plus,B可以满足A中的各种功能,B的功能比较强大,把B改装一下,就变成了另外一个,A表示一个容器。
* 存在这种情况就叫适配器(adapter),此处,A(队列)就是适配器,并不是都有的复合都有适配器。
A是container B是component
构造由内而外
析构由外而内
2、Delegation 委托(composition by reference)
为什么不明明是by pointer,却叫by reference呢,
因为就这么规定的,无论是什么情况都只讲by reference。
pointer to implimplementation (pimpl)
Handle/Body
* 与Composition也有一定关系
* A类中有一个指针指向B类,这个关系有点虚,在A想调用B的时候,也就A需要B的时候,再去创建B,然后就可以调用这个指针,来进行委托。
* 两个类之间用指针相连就是委托的关系,用指针相连,那么他们的寿命就不一致咯~而复合的关系的寿命是一致的
* 具有一定的弹性,A是不变的,可以只改变B来实现不同的功能。有时候也叫编译防火墙
3、Inheritance 继承
最有价值的是与虚函数搭配来使用
子类的对象,里面有父类的成分(part)在里面
这种先后关系,是编译器已经帮我们实现了
构造的时候,先调用父类的构造,完事之后,在调用子类的
析构的时候,先处理子类自己,然后再调用父类的洗后
C++面向对象高级编程下
第二节
conversion function
转换函数没有返回值类型,因为为了避免与后面的相矛盾
operate double() const
{
return(double)(分子/分母)
}
第三节
arguement 实参
第五节
小括号这个操作符就叫做function call operator(函数调用操作符)
第13节
学习建议:把C++中的数据结构和算法都要自己试一遍,数据结构已经有现成的容器,这些都要自己亲手写出小程序,测试一遍。
第17节
类中只要有一个虚函数,那么这个类就会在原有的占用内存的基础上,多4个字节(32位操作系统上),这个是一个指向虚函数的指针,而且不论你有多少个虚函数,都只有这一个指针。因为这个指针指向的是一个虚表,如果有n个虚函数,那么虚表中就有对应的n个地址,
当子类继承这个父类时,也会有这样一个指针,这个指针是属于来自父类的,不属于子类。虽然这个指针中存放的地址不一样。
动态绑定
1、动态绑定有两个常见用途
1、多态
2、模板方法模式
2、编译器看到符合以下三个条件就会把它动态绑定
1、通过一个指针来调用,
2、有一个向上转型的动作
3、调用虚函数
(*(p->vptr)[n])(p)
通过指针p找到它的虚指针vptr,在找到它的虚表(p->vptr),再找到虚表中的第n个,把它当成函数指针调用()也就是解引用,由于是通过p来调用(注意函数的调用是通过小括号来实现的),所以p就是this pointer(也就是解释了为什么小括号里存放的是p)。
第20节
const放在成员函数的后面,一般的全局函数是不能在参数列表后面放const