《面向对象程序设计》知识整理
上了胡兰青老师的《面向对象程序设计》、说是面向对象,其实就是专讲 C++ 的面向对象。胡老师的专业能力很厉害,知识点讲得很严密。
为了应付刁钻和故意设坑的期末考,特开此文复习(虽然最后期末考还是考砸了)。
类&对象
-
类访问修饰符
public
,private
,protected
。(默认private
)
-
构造函数和析构函数
- 离开函数时先销毁后定义的类。
- 构造函数先调用父类,析构函数先调用子类。
-
拷贝构造函数
- 形如
A::A(const A &obj)
。(必须加引用,一般加上const
) - 以下两种初始化对于其他类型是等价的,对于类类型有区别:
A a(2)
是直接初始化,调用构造函数。A b = a
是拷贝初始化,调用拷贝构造函数(等价于A b(a)
)。
- 应用场景
- 函数的参数为类的对象时(将外层调用的值拷贝给参数)
- 函数的返回值是类的对象时(将
return
来的值拷贝给一个临时对象) - 对象需要通过另外一个对象进行初始化(常见形式)
- 默认构造函数使用
浅拷贝
:对于动态成员(比如指针),指向同一位置。 - 如果想防止默认拷贝发生,可以声明(不定义)一个私有的拷贝构造函数。这样,一旦调用编译器就会报错。
- 形如
-
静态成员
- 无论创建多少个类的对象,静态成员都只有一个副本。
- 静态成员
- 我们不能把静态成员变量的初始化放置在类的定义中。
- 在类的外部必须用
::
重新定义一遍静态变量(分配地址)
- 静态成员函数
- 静态成员函数只能访问静态成员数据、静态成员函数和类外部的函数。
- 静态成员函数没有
this
指针。
- 注意:
static
成员函数不能为virtual
- 不能存在
static
和non-static
成员函数有相同的名字和参数 static
成员函数不能被声明成const
、volatile
或者const volatile
。
-
友元函数
- 类的友元函数是定义在类外部,但有权访问类的所有私有成员和保护成员。
- 尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
- 友元也可以是一个类,该类被称为友元类。
-
内联函数
inline
- 定义在类内的缺省函数都是内联的。
- 类中如果未给定义默认不内联,但可以在类外定义时加上
inline
。
继承
-
已有的类称为基类,新的类称为派生类。
- 形式:
class <派生类名>: <继承方式> <基类名>
- 形式:
-
访问和控制继承
访问 | public | protected | private |
---|---|---|---|
本类 | yes | yes | yes |
派生类 | yes | yes | no |
类外 | yes | no | no |
-
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
-
可以用
A::
在派生类里调用基类函数和数据。- 所以一个普通类成员函数里也可以用这个格式。
-
继承类型
- 公有继承(常见继承)。
- 保护继承:基类的公有和保护成员都变为派生类的保护成员。
- 私有继承:基类的公有和保护成员都变为派生类的私有继承。
-
构造函数相关
- 调用子类的构造函数时,需要在初始化列表里指定基类的构造函数。
- 如果不指定,默认是基类的不带参构造。
- 一定是先调用基类构造函数,再初始化派生类的数据。
- 连续继承时,
A->B->C
,C
不指定A
的构造。 - 如果是多继承,按定义时顺序触发。
-
多继承
class <派生类名>:<继承方式1><基类名1>, <继承方式2><基类名2>,…
-
基类和派生类的指针转化
static_cast<type-id>(expression)
上行转化安全,下行转化不安全。dynamic_cast<type-id>(expression)
都安全。- 指针类转化错误,指针值为
nullptr
- 引用类转化错误,会
throw
一个异常。
- 指针类转化错误,指针值为
重载
-
运算符重载
A operator <运算符>(const A &a)const
- 后一个
const
表示*this
里的数据不能改。
-
强转重载
operator int()const{}
- 注意开头不跟类型;少了const会编译错误
多态
-
虚函数
- 虚函数 是在基类中使用关键字
virtual
声明的函数。 - 虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。
- 在基类的构造函数和析构函数里,只能调用基类的虚函数(不会动态联编)。
- 坑点1:函数定义必须完全一样才能触发此效果。
- 比如基类和派生类函数中有一个加了
const
,就无法重载了。
- 比如基类和派生类函数中有一个加了
- 坑点2:基类通过虚函数可以调用派生类,但无法获得派生类数据。
- 如果基类指针指向派生类,如果基类函数
F1()
里调用了基类虚函数F2()
,F2()
也会执行动态绑定。特别的,构造函数和析构函数内部调用的F2()
不会动态绑定。 - 只有类的成员函数才能声明为虚函数。
- 静态成员函数,内联函数和构造函数不能是虚函数。
- 析构函数通常是虚函数。
- 纯虚函数
- 在虚函数声明的结尾加上
= 0
。 - 派生类必须写出对应函数的定义。
- 含有纯虚函数的类是抽象类,不能实例化。
- 在虚函数声明的结尾加上
- 虚函数 是在基类中使用关键字
-
虚继承
- 如遇到“环装继承”:形如
A->B, A->C, (B,C)->D
。- 可以在定义
B
和C
时用: class <派生类名>: virtual <继承方式> <基类名>
- 只生成
A
的一个副本。
- 可以在定义
- 如遇到“环装继承”:形如
模板
- 模板的声明或定义只能在全局、命名空间或类范围内进行,不能在局部范围内。
- 模板函数调用时参数传递不具有隐式的类型转化。
class
和typename
这两个关键词等价。- C++ 对于重载函数的编译顺序
- 先找参数完全匹配的普通函数(非由模板实例化得到的函数)。
- 再找参数完全匹配的模板函数。
- 再找实参经过自动类型转换后能够匹配的普通函数。
- 函数模板
template <class 形参名,class 形参名,...> <返回类型> <函数名>(参数列表)
- 类模板
- `template<class 形参名,class 形参名,…> class 类名 { … };
异常
catch
到后,先进行值传递,再进行子过程里的销毁1
2
3
4
5
6
7
8
9try{
A a;
e = Exception();
throw e;
}
catch(Exception& m)
{
cout<<"catch exception"<<endl;
}call A()
exception()
构造一个异常对象e,局部的。exception(const exception&)
复制一个异常对象(全局的匿名对象),m引用它。~exception()
销毁局部的异常对象e~A()
- 进入
catch
模块 ~exception()
销毁全局的异常对象
- 标准库异常类
include<stdexcept>
- 在函数末尾加上
throw(A)
表示该函数只会抛出A
类的异常。- 如果为空,表明该函数不会抛出异常。
- 在构造函数和析构函数里加
throw
是危险行为,因为不会进行内存回收。
词汇翻译
- 封装 encapsulation
- 继承 inheritance
- 声明 declaration
- 定义 definition
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.