高级程序设计-课堂重点提要¶
课程概述¶
课程目标¶
- 进一步的程序 抽象 和 复用 技术.
- 提升程序设计的熟练程度和规模, 为后续专业课程提供基础.
- 掌握一下的基本程序设计的思想和做法:
- 面向对象设计
- 泛型(类属)程序涉及和基于STL编程
- 程序异常处理
- 事件(消息)驱动的程序设计
- 基于"文档-视"结构的应用框架
- 函数式和逻辑式程序设计
- 编程语言: c++
重点
- 面向对象设计
- 泛型程序设计
课程网站¶
- 网站: cslabcms.nju.edu.cn
- 课程名称: 高级程序设计1班
- 发布课件/通知/提交作业/交流,讨论
课程考核¶
- 平时作业(10%)
- 课后练习
-
课程网站发布, 在规定时间内提交
-
上机考核(25%)
-
OJ系统当场出题, 当场提交
-
7+ 选最高的6次
- 80分钟内满分
- 80-110分钟线性衰减, 当天提交60%
-
课程项目(25%)
-
2个较大的程序(10% + 15%)[8周一个项目]
-
课程网站发布, 在规定的时间内提交
-
期末笔试(40%)
- 手写编程(70%+)
省去一些前期的基础知识
之前记的一些笔记过于琐碎, 就不放进来了.
Lec 3¶
拷贝构造函数¶
shallow copy 浅拷贝
//deep copy
class string{
char *str;
public:
string (const &string s) {
this->str = new char*[defaultSize];
strcpy(str, s.str);
}
}
初始化列表拷贝成员对象类 vs. 在拷贝函数内部对成员对象类进行赋值
自定义的拷贝构造默认调用的是成员对象类的 默认构造函数 来对成员对象初始化.
前者是自定义初始化, 后者是先使用默认初始化, 再进行赋值.
const关键字
常成员函数只约束数据成员值的修改(指针指向的变量时可以访问并修改的)
对于void f(const Date &d)
类型的写法, 函数内部只能调用对象的常成员函数.
结构化
典型的结构化语言: C 不带goto
的顺序 分支 循环 流程控制
典型的非结构化语言: 汇编 goto
范式
Lec 4 继承-派生类¶
继承的一些要求
定义派生类时一定要见到基类的定义
默认情况下, 基类的友元关系不会继承
继承与封装的矛盾
由于派生类不能直接访问基类的private
成员, 因此引入protected
关键字来解决派生类访问父类的一些成员, 同时也保证了数据的封装性(外部仍然不能访问).
一般情况下, 把不太可能发生变动的, 有可能被派生类使用的, 不宜对实例用户公开 的成员声明为protected
.
注意区分在外部访问还是在内部访问
void f()
是外部通过一个B类的实例进行访问;
class C:public B
是在C类的内部(定义)进行访问.
派生类成员标识符的作用域
对于基类而言, 派生类成员标识符的作用域是 嵌套在 基类作用域中的.
如果派生类中定义了与基类同名的成员, 则基类的成员名在派生类的作用域内不直接可见(被隐藏)
//class B : public A
void h() {
f();
A::f(); //通过显式的制定作用域来区分
}
关于继承的覆盖问题
问题情境:
在基类中已经有方法Robot::move(char direction)
, 如果在其派生类中写如上图的方法, 那么, 对于派生类对象的实例r
的r->move('D')
会成功吗?
结论是, 在默认的情况下, r并没有int move(char direction)
这一方法, 也就是说, 派生类实例无法调用基类的同名函数, 即使函数的构型(参数, 返回值)不同.
联系继承类的作用域概念, 这样的场景可以类比以下代码:
int main() {
int foo = 1;
for(int foo = 2; foo < 10; foo++) {
printf("%d\n", foo);
}
}
foo
变量被覆盖了.因此在内部是没法访问外部的foo
的. 避免的方法就是尽量不要起同名的变量.
但是对于函数, 多了参数列表和返回两种属性, 注意到Robot::move(char direction)
与SuperRobot::1::move(char direction, int pace)
并不相同, 结合函数重载的知识, 这为什么不属于函数重载呢?
答案是: 作用域不同. 函数重载时要求两个函数具有相同的作用于的, 而派生类的函数是基类的扩展, 两个函数的作用域并不相同.
解决这样的同名的矛盾的方法, 比较常见的是显式指出函数的作用域, 例如`rob1->Robot::move('D')这样的写法.
但是, 如果想不加名空间限制就直接调用rob1->move(char direction)
这样的写法呢? 这就要求在class SuperRobot1
的public的声明中添加一句using Robot::move
, 这样, 在SuperRobot的实例中, 就可以同时调用rob1->move(char direction)
和rob1->move(char direction, int pace)
两种方法.
最后, 具体的PPT参考:
在去年的Advanced Programming的OJ中, 就出现过这样的刁钻样例, 要求外部不加名空间限制符直接调用, 因此这时候就体现出了
using
的重要性了.
override vs. overload
override 覆盖 对函数的重写
overload 重载
Warning
注意: 在A的派生类B中override函数f()不能算作是函数名重载
!: 作用域不同!!
继承方式
派生类的属性:
-
不可直接访问
: 在派生类的内部(定义时)也不能访问基类的该成员 -
private
: 在派生类的内部(定义时)可以访问, 但是外部(实例化时)不可访问, 该派生类的派生类不能在内部直接访问 -
protected
: 在派生类的内部(定义时)可以访问, 但是外部(实例化时)不可访问, 该派生类的派生类内部可以直接访问 -
public
: 在派生类的内部可以访问, 外部也可以访问, 派生类也可以访问.
子类型
在C++中, 把类看做类型, 把以public方式继承的派生类看做是基类的子类型
其实就相当于该派生类就是基类的扩展, 具有基类的所有功能, 因此可以有以下操作:
class B : public A
B b;
A a;
a = b;
A *p = &b;
构造函数和析构函数的顺序
如果一个类D既有基类B, 又有成员对象类M, 则
创建D类对象时, B->M->D
析构时相反.
赋值和拷贝构造
默认情况下, 对基类的赋值操作和拷贝构造采用默认拷贝构造;
除非显式指出.
Lec 5 虚函数 动态绑定 抽象类¶
对虚函数的几点说明
-
静态成员函数不能是虚函数.
-
构造函数不能是虚函数, 但是析构函数(往往)可以是虚函数.
-
只要在基类中说明了虚函数, 在派生类中同型构的函数成员都是虚函数(不必写virtual)
-
只有通过指针或引用访问对象类的虚函数时才进行动态绑定.
-
基类的构造函数和析构函数中对虚函数的调用不进行动态绑定
因为基类在构造阶段并不清楚派生类的情况. 同样, 当析构阶段, 派生类已经析构, 不能在调用其函数.
Tip
注意override的含义:"覆盖"
Lec 6 多继承 聚合/组合¶
继承vs. 组合
-
is-a-kind-of : 继承
-
is-a-part-of : 组合
Lec 7 操作符重载¶
=操作符重载
-
注意防止自己赋值给自己
-
如果自定义了拷贝构造函数, 最好也同时自定义赋值函数.