├── CppTech └── README.md ├── DBF ├── README.md ├── dbf2_suke.md └── dbf_test_suke.md ├── FAQ ├── FAQ_zhangxuan_0323.md └── README.md ├── README.md ├── Weixin └── README.md ├── handout ├── README.md ├── template.md ├── 第10章.md ├── 第1章.md ├── 第4章.md └── 第7章.md ├── pics ├── C1 │ ├── 公式4.png │ ├── 公式6.png │ ├── 公式7.png │ ├── 十进制小数→ R 进制小数.png │ ├── 十进制整数转换为R 进制整数.png │ ├── 原码.png │ ├── 数据信息.png │ ├── 程序中常用的数制.png │ └── 计算机硬件.png ├── C10 │ ├── STL的基本组件间的关系.png │ ├── 关联容器概念图.png │ ├── 函数对象概念图.png │ ├── 容器的分类1.png │ ├── 容器的分类2.png │ └── 迭代器的分类.png ├── C4 │ └── 联合体的内存分配.png ├── C7 │ ├── Derived类对象d的存储结构示意图示意图.png │ └── 虚基类举例.png ├── qrcode.jpg └── test │ └── terminator.jpg ├── plan.md └── 分工.md /CppTech/README.md: -------------------------------------------------------------------------------- 1 | # CppTech C++技术文章 2 | -------------------------------------------------------------------------------- /DBF/README.md: -------------------------------------------------------------------------------- 1 | # Debug For Fun 2 | - [第一期 调试的乐趣](dbf_test_suke.md) 3 | - [第二期 数组与指针](dbf2_suke.md) 4 | -------------------------------------------------------------------------------- /DBF/dbf2_suke.md: -------------------------------------------------------------------------------- 1 | # Debug For Fun 2 | 3 | 苏克 4 | 5 | 2016年3月31日 6 | 7 | ## 第二期:数组与指针 8 | 9 | ### 前言 10 | 11 | C语言很容易让你在开枪时伤着自己的脚,C++使这种情况很少发生。但是,一旦发生这种情况,它很可能轰掉你整条腿。 12 | —— Bjarne Stroustrup(C++之父) 13 | 14 | ### 关于数组和指针:sizeof() 15 | 16 | ```C++ 17 | #include 18 | int main(void) 19 | { 20 | char str[] = "world"; 21 | char * pstr = "world"; 22 | printf("%d %d",sizeof(str),sizeof(pstr)); 23 | getchar(); 24 | return 0; 25 | } 26 | ``` 27 | 28 | 如图所示的一段代码,它的输出是什么呢——请大家先自己猜测一个答案,然后再亲自验证一下,也许结果会让你大吃一惊! 29 | 30 | 函数sizeof()应该是返回其参数的“size”。但是它对于数组(用 char A[]形式定义)和指针(char* A 定义)的表现是截然不同的。 31 | 32 | 在上面的例子里,sizeof(str)的结果是6——这是字符串"world"的长度(别忘了后面还有一个表示结束的'\0')。但是sizeof(pstr)的值却是4(32位编译器下的结果)——事实上,由于pstr是一个指针,sizeof并不能得到它究竟指向怎样的一个或者一段数据,所以只能返回这个指针自己的大小(在32位平台下结果是4),甚至和这个指针指向的元素类型无关。大家可以试试这样的一段代码: 33 | 34 | ```c++ 35 | void* p; 36 | cout << sizeof(p) << endl; 37 | ``` 38 | 39 | 其结果是4——一个指针变量的大小。从上面的例子之中我们可以看到,数组虽然和指针有着千丝万缕的联系,但实际使用时是有区别的,稍不小心便可能“轰掉你整条腿”——请看下面的例子,你能通过各种调试手段找到其中的问题么? 40 | 41 | ```c++ 42 | #include 43 | const int MAXN = 100; 44 | int arr[MAXN]; 45 | 46 | void clear(int a[MAXN]) 47 | { 48 | memset(a, 0, sizeof(a)); 49 | } 50 | 51 | int main() 52 | { 53 | f(arr); 54 | } 55 | ``` 56 | 57 | 提示:我们可以猜测这段代码的用意是用函数clear来把数组arr中的所有元素清零。大家可以先通过输出arr的元素的方式来看他有没有达到自己的预想目的;再在函数clear()中输出一些调试信息来看他究竟犯了什么错误。 58 | 59 | 那之后,相信你就可以回答下面这个问题: 60 | 61 | 当函数接受一个数组的名字作为参数时,函数体内部对这个参数的认知究竟是“数组”还是“指针”? 62 | 63 | 此外,你有什么好的方法可以帮助他解决这个问题么?请把你的结论和想法发表到讨论区和大家分享!优秀的回答将有可能被摘录进入课程讨论精华之中,供以后所有的学生参考。 64 | 65 | ### 下标[] 66 | 67 | 对于数组/指针来说,“取下标/取元素符号”a[i]永远被这样理解:*(a+i)。细心的同学会发现,这里面的“a+i”是一个指针类型和一个整数的加法运算。指针类型是如何和整数相加的?对于指向不同数据类型的指针,这个加法的运算规则又有什么不同? 68 | 69 | 请大家通过编写程序,得到恰当的输出信息,回答这个问题。请把你的结果发表到讨论区和大家一起分享! 70 | 71 | ## 参与到DBF模块的制作中来 72 | 73 | 作为管理DBF模块的助教,我希望可以给学有余力的同学们提供更多C++相关的,或者编程相关的知识、技巧。你对这个模块有什么期待?你想要看到关于什么内容的文章,想要更深入地了解哪些方面的技巧?请把你的宝贵意见发表到讨论区,参与到课程的制作中来! -------------------------------------------------------------------------------- /DBF/dbf_test_suke.md: -------------------------------------------------------------------------------- 1 | # Debug For Fun 之 调试的乐趣 2 | 3 | 苏克 4 | 2016-03-17 5 | 6 | ## 系列前言 7 | 8 | OJ(*Online Judgement,在线评测系统*)是冰冷的机器,它会一丝不苟地将你程序所有不正确的地方指出,并返回给你一个冰冷的**Wrong Answer**。调试(Debug)本身就是编程过程中一个重要的部分——无论是课程里这些简单的小程序,还是具有复杂架构的工程项目,都可能需要程序员在调试上花费很多精力。 9 | 10 | 笔者在初学C++编程时,也曾经为了改正一个小小的问题冥思苦想很久,最后找出问题所在的一刻才恍然大悟——这种豁然开朗的成就感也算是编程的乐趣所在吧!在这个模块里,笔者希望可以通过一些简单的题目来向大家展示C++初学者(或者编程初学者)容易犯的错误,以及一些好用的编程技巧。 11 | 12 | ## 第一期:边界情况(Cornor Case) 13 | 14 | ### 前言 15 | 16 | 在做数学题时,经常会有需要讨论“特殊情况”的题目。例如, 17 | 18 | 任何实数的0次方都是0么? 19 | 20 | 答案是否定的——0的0次方是一个未定义的数,它没有意义。这件事情就是所有涉及到“某个数的0次方”的数学问题的一个边界条件。任何一个数学家在面对某个数的0次方时,都要仔细地考虑:这个底数是否可能是0?这个“某个数的0次方”是否可能没有意义?如果忘记考虑这一边界条件,就可能犯下大错。 21 | 22 | 下面用一道编程题目来说明这个问题。 23 | 24 | ### 例题:进制转换 25 | (出处:清澄Tsinsen网络自动评测系统 公共题库) 26 | 27 | 点我进入题目页面 28 | #### 问题描述 29 | 十六进制数是在程序设计时经常要使用到的一种整数的表示方式。它有0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F共16个符号,分别表示十进制数的0至15。十六进制的计数方法是满16进1,所以十进制数16在十六进制中是10,而十进制的17在十六进制中是11,以此类推,十进制的30在十六进制中是1E。 30 | 给出一个非负整数,将它表示成十六进制的形式。 31 | #### 输入格式 32 | 输入包含一个非负整数a,表示要转换的数。0<=a<=2147483647 33 | #### 输出格式 34 | 输出这个整数的16进制表示。 35 | #### 样例输入 36 | 30 37 | #### 样例输出 38 | 1E 39 | 40 | #### 分析 41 | 入门任何一门语言,尤其是入门编程最重要的就是多加练习。笔者非常真心地建议每一个读者都自己思考一下这道题的做法,最好是亲自写了代码之后,到上面提到的这道题的出处那里试着提交并且评测一下试试,接着再来看下面的“剧透”内容。注意——需要先在那里注册登陆,才能看到题目。不过这一切是免费的,请尽管去试试吧! 42 | 43 | 进制转换只要采用短除法就可以简单地实现——例如把十进制数转换为十六进制数,只要把这个数除以16,得到的余数就是这个数16进制表示的最低位。之后对得到的商重复这个过程(直到某一次得到的商变成了0),就从低位到高位地得到了这个数的16进制表示。希望大家仔细想想,把这个过程理解清楚。 44 | 45 | 例如,对于上面样例输入的30,先有 46 | 47 | 30 ÷ 16 = 1 余 14 // 14就是16进制下的E。接下来对商“1”继续这个过程: 48 | 1 ÷ 16 = 1 余 0 <-- 算法终止! 49 | 50 | 把两次计算得到的商倒序放置,就得到**1E**,与样例输出符合。 51 | 52 | 笔者做这道题的时候,很快地便沿着这个思路开始写,写出了下面这样的代码: 53 | 54 | ```c++ 55 | #include 56 | using namespace std; 57 | int main(int argc, char **argv) 58 | { 59 | int a, w[20], digi = 0; 60 | cin >> a; 61 | while(a) 62 | { 63 | w[digi] = a % 16; // 用数组w来记录每次得到的商 64 | digi++; 65 | a = a / 16; 66 | } 67 | for(int j = digi - 1; j >= 0; j--) 68 | { 69 | if(w[j] >= 10) 70 | cout << char('A' + w[j] - 10); // 把大于9的数转化为16进制的字母 71 | else 72 | cout << w[j]; 73 | } 74 | cout << endl; 75 | return 0; 76 | } 77 | ``` 78 | 79 | 当然了,OJ很快地就给笔者的程序标记了**WrongAnswer**。那之后,笔者想了很久很久,终于把问题解决了。读者可以试着找出程序的问题所在,并提出一个可行的解决方案,发到课程的评论区和其他学习者们讨论交流。相信这一定难不倒你! 80 | 81 | ### 思考题:反方向的进制转换 82 | 题目出处:清澄Tsinsen网络自动评测系统 公共题库 83 | 84 | 点我进入题目页面 85 | #### 问题描述 86 | 从键盘输入一个不超过8位的正的十六进制数字符串,将它转换为正的十进制数后输出。 87 | 注:十六进制数中的10~15分别用大写的英文字母A、B、C、D、E、F表示。 88 | #### 样例输入 89 | FFFF 90 | #### 样例输出 91 | 65535 92 | #### 分析 93 | 下面是笔者写的一份**有bug的程序**。大家可以找出其中存在的问题,并提出可行的解决方案么!欢迎在题目讨论区交流~ 94 | 95 | ```c++ 96 | #include 97 | using namespace std; 98 | 99 | int num[15]; 100 | 101 | int write_in() 102 | { 103 | char x; 104 | int i = 0; 105 | while(scanf("%c", &x) != EOF && x != '\n') 106 | { 107 | i++; 108 | if(x >= 'A' && x <= 'Z') 109 | num[i] = x - 'A' + 10; 110 | else 111 | num[i] = x - '0'; 112 | } 113 | return i; 114 | } 115 | 116 | int main(int argc, char *argv[]) 117 | { 118 | int digit = write_in(); 119 | int n = 0; 120 | for(int i = 0; i <= digit; ++i) 121 | { 122 | n = n * 16 + num[i]; 123 | } 124 | cout << n << endl; 125 | return 0; 126 | } 127 | ``` 128 | 129 | **提示**:下面是几种常见的“边界情况”(当然了,笔者所列一定不完全,也欢迎大家在讨论区发帖对其进行补充): 130 | 131 | 1,极端数据,例如数字处理问题中的0、字符串处理问题中可能存在的“空行”等等。 132 | 133 | 2,过大的数据:超过了数据类型可以表达的最大范围的数据,例如int可以表示-2147483648到2147483647中的整数,超过该范围的数都会变成错误的值。 134 | 135 | 3,一些复杂问题中的极端情况。 136 | 137 | -------------------------------------------------------------------------------- /FAQ/FAQ_zhangxuan_0323.md: -------------------------------------------------------------------------------- 1 | ## 讨论精华 第一期 2 | ---- 3 | 张轩 2016-3-23 4 | 5 | ###OJ操作问题 6 | ---- 7 | - Q:我的编程作业总是0分,这个作业到底具体要求是什么呢,求同学给个有分的例子。 PS:这种计算机运行代码的检查方式难道不是太死板了么 8 | - A:作为黑盒测试,作业会给出若干组测试数据,系统会将你的程序运行若干次,每次输入是不同的。如果你每次对应的输出都是正确的,就可以得到满分;得到0分一般是算法有比较明显的问题,或者程序本身编译无法通过。建议在本机进行调试。关于第二个问题,合理的测试数据可以充分检验该程序的正确性、高效性、稳定性等性能,也是所有程序设计比赛中通用的检查办法。尽管代码风格本身是重要的,但最重要的无疑是正确性。 9 | - Q:话是这样说不错 但举个例子,这次作业的第三个编程题,要求输出菱形的那个 我有一句 cout << "输入菱形阶数" << endl; 电脑算错,因为按照要求我多输出了这句话,但这不是一个程序的正常提示么,我去掉这句话后就得满分了 Orz 10 | - A:就人性化而言加一句提示是好的,但系统目前能做到的只能是比对你的输出是否完全和要求的输出一致,提示信息也算作输出的一部分。请谅解~ 11 | 12 | ####问题点评 13 | - 大家或许曾经没有接触过黑盒测试,对黑盒测试的一些性能有所不理解,希望大家在OJ上遇到任何问题时,首先查看我们的[lab FAQ](http://www.xuetangx.com/courses/course-v1:TsinghuaX+00740043X+2016_T1/45aaae55fff144c79706e971dee36b50/),里面对很多问题都给了比较充分的解释。 14 | 15 | ###编程作业问题 16 | ---- 17 | - Q:请问将范围增大之后之所以算不出来是因为超出int值的范围了吗……如果是的话该怎么解决呢? 18 | 19 | ---- 20 | - A:(同学的解答)1.当n的数据范围扩大之后,会爆int,可以使用long long; 2.如果你的算法有问题的话,可能会导致TLE; 21 | 22 | --- 23 | 24 | - A:(助教的解答)int是一个方面,需要用long long int支持更大的数据,更多的则是算法的问题。 25 | 26 | 与此密切相关的问题是算法的复杂性。在本章习题中要求使用递归的方法编写斐波那契数列的求解,那么,我们可以根据递归算法考察一下n=5时程序是如何运行的。 27 | 28 | f(5) = f(4) + f(3)——① 29 | 30 | 接下来根据递归,我们需要求解f(4),f(4) = f(3) + f(2) 31 | 32 | 好,这里我们发现又需要再次求解f(3)。但这个求解的结果能否被①式所利用呢?答案是不可以,我们并没有把这个计算的结果保存下来。 33 | 34 | 可以想见,当n很大时,实际上这样的冗余计算是非常多的 35 | 36 | 多到什么程度呢?数学上可以证明,它的计算量是n的指数型函数。“指数爆炸”这一概念,如果学过高中数学的话应该有所了解。也就意味着,当n并不是很大时,实际上计算量已经过大了 37 | 38 | 计算机的计算能力也是有限的,当计算量过大时,所需的时间也会过长,导致无法在期望的时间内给出结果。 39 | 40 | 初学者对此做简单了解即可,对此有兴趣可以参考“时间复杂度”概念 41 | 42 | 至于解决方法,可以尝试使用“递推”的思路哦。 43 | 44 | ---- 45 | 46 | - A:(助教的解答)您说的应该是斐波那契那道题的选做版本吧!能找到做对它的思路,说明你在编程能力之上,也有着算法设计的能力。 47 | 48 | 事实上,这两种做法的区别远不仅仅在于是否涉及到了函数递归,因而才有极大的运算速度的区别。 49 | 50 | 例如使用简单的函数递归的话,想要计算f(5),则要递归进行f(4)和f(3);而为了计算f(4),又要递归进行f(3)和f(2)……注意,f(3)单单在这么短的时间里就出现了两次。出现两次意味着什么?意味着有一个你明明知道是多少的数,却又从f(0)和f(1)一点点向上计算了至少两次。这就造成了时间的浪费。 51 | 52 | 如果像你的最后一个做法一样,将它们的数值计算下来并递推,就不会有时间的浪费——每个数都只计算了一次。 53 | 54 | 事实上,还可以在不抛弃递归的基础上做好这道题。大家可以这样试试: 55 | 56 | 定义一个全局数组,将其初始化为所有的元素都为0。每次如果计算完一个f(x),就把f[x]的值保存为这个值。其实,这就是用一个数组帮你记住“是否已经算过了这个值”。这样的话,就可以在每次进入f(x)时,先检查自己是否算过这个值,如果算过就不需要进行冗余的递归啦! 57 | 58 | 下面给一个这种思路的示例(非完全程序,请自行补充剩余部分)。 59 | 60 | long long f[100]; 61 | 62 | long long fibonacci(int x) { 63 | if (f[x] > 0) { // 计算过f(x)的值 64 | return f[x]; 65 | } 66 | return f[x] = fibonacci(x - 1) + fibonacci(x - 2); 67 | } 68 | 69 | int main() { 70 | for (int i = 0; i < 100; i++) 71 | f[i] = 0; 72 | f[0] = 1; 73 | f[1] = 1; 74 | 75 | return 0; 76 | } 77 | 78 | ####问题点评 79 | - 这道选作题目涉及到了时间复杂度的概念,可能也是第一次大家会频繁出现“TLE”的题目。它告诉我们,对一个算法的评价,仅靠正确性是不够的,足够的效率也是必须的。 80 | - 对此有兴趣的同学可以参见时间复杂度的相关知识,这里提供一篇[Wikipedia的词条链接](https://en.wikipedia.org/wiki/Time_complexity) 81 | 82 | ###语法细节问题 83 | --- 84 | - Q:例题3-2中, 85 | 86 | value+=static_cast(power(2,i)); 87 | static_cast 是什么意思啊? 88 | - A:表示将变量强制转换为int类型。int可以更换成其它的数据类型。注意power函数的返回值本应是double 89 | 90 | --- 91 | 92 | - Q:std::ends和' '有什么区别?为什么我在第二章编程作业里用std::ends代替' ',反馈结果是错误的? 93 | - A:ends输出的是'\0',相当于字符串的终止符;' '则相当于字符串中的一个空格,二者区别很大。 94 | 95 | 不同的系统对二者的输出显示是不同的,windows输出ends时会带一个空格,但linux不会。 96 | 97 | 一般oj采用linux环境的比较多。建议需要输出空格时还是直接输出' '吧。 98 | 99 | --- 100 | 101 | - Q:换行操作endl与\n有什么差别,为什么有两个,用哪一个比较好?"\n"和'\n'有什么差别,仅仅只是"\n"的后面多了一个’\0‘的字符? 102 | - A: 103 | 104 | **1 endl和'\n'的差别** 105 | 106 | std::endl可以看作是只能和cout配合使用的一个东西——实际上它是一个函数,其作用是在其对应的位置添加一个换行符'\n'。例如: 107 | 108 | cout << "hello world!" << endl; 109 | 实际上和 110 | 111 | cout << "hello world!\n"; 112 | 在结果上没有差别。 113 | 114 | 不过,硬要说的话其实是有一点不同的:在std::endl的实现中,它做了一次“刷新输出缓冲区”的工作。不清楚这个工作是做什么的并不要紧——实际上我也不清楚——不过频繁地采用std::endl代替'\n'可能会由于这额外的工作而浪费一些时间。大家可以分别用两种方法输出大量(非常大量)的换行,看看运行的时间是不是真的有差距。 115 | 116 | 你有一个用词非常好:换行操作。可以试试这样的一行语句: 117 | 118 | std::endl(cout); 119 | 猜猜它的功能,然后去试试看猜的对不对吧!不过要理解这个语句的原理可能就稍显复杂了,感兴趣的同学可以自己查阅一下相关的资料。这里是[一篇博客文章](http://soft.zdnet.com.cn/software_zone/2008/0118/710903.shtml),感兴趣的话可以阅读一下,相信可以加深你对cout“输出语句”的认识。 120 | 121 | **2 "\n"和'\n'的区别** 122 | 123 | 这个区别可就大了——前者用""包裹起来,说明它是一个字符串,一般情况下可以说它是一个char数组。而后者则用''包裹,这代表着它是一个字符,也就是char。如果要说它们之间有什么差别的话,不如说它们只是碰巧长得比较像一样——前者是一句话,可惜它只有一个字“好”;而后者则是单一个‘好’字。程序世界对于“话”和‘字’的区分(实际上是C++对“类型”的区分)可是相当严格的,绝对不要弄混。该用一句话的地方就不能用‘字’,而如果有人只要一个字,你也不能塞给他一句“话”——即使这句话只有一个字也不行。 124 | 125 | 举个例子:头文件中有这样一个函数strcmp(const char* str1, const char* str2),它的功能是接受两个字符串,如果它们相等则返回0,否则返回非0的值。此时可绝对不能 126 | 127 | cout << strcmp('\n', "\n"); // ERROR!! 128 | 129 | 这就是那个该用一句话的地方——只给一个字是不可以的。 130 | 131 | 说的有点绕了,可能把简单的问题搞复杂了。当然了,你说的差别其实也很对,前者实际上要多一个标志char数组(或许也可以直接称之为字符串)结束的标志'\0'。 132 | 133 | -------------------------------------------------------------------------------- /FAQ/README.md: -------------------------------------------------------------------------------- 1 | # FAQ 课程常见问题&讨论区精华 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xuetangx cpp 2 | 3 | 学堂在线C++语言程序设计(2016春)课程,由助教维护 4 | 5 | ### [CppTech](./CppTech) 6 | 每周相关技术文章,拓展视野! 7 | ### [DBF](./DBF) 8 | 好玩的程序都在这里! 9 | ### [FAQ](./FAQ) 10 | 本页面定期总结讨论区精华 11 | 12 | ### [Handout](./handout) 13 | 课程讲义markdown版本,将在一整个学期逐步完善 14 | 15 | ### [Weixin](./Weixin) 16 | 公众号,每周发布新鲜文章 17 | ![C++公众号](./pics/qrcode.jpg) 18 | 19 | 20 | ### 课程链接 21 | - 自助课程: 22 | - 基础:[http://www.xuetangx.com/courses/TsinghuaX/00740043X/2015_T1/info](http://www.xuetangx.com/courses/TsinghuaX/00740043X/2015_T1/info) 23 | 24 | - 进阶:http://www.xuetangx.com/courses/TsinghuaX/00740043_2X/2015_T1/info 25 | 26 | - 证书课程: 27 | - 基础:http://www.xuetangx.com/courses/course-v1:TsinghuaX+00740043X+2016_T1/info 28 | 29 | - 进阶:http://www.xuetangx.com/courses/course-v1:TsinghuaX+00740043_2X+2016_T1/info 30 | 31 | ### Contributors 32 | - 梁嘉骏 33 | - 葛泽睿 34 | - 鲁盼 35 | - 唐玉涵 36 | - 张轩 37 | - 李北辰 38 | - 苏克 39 | -------------------------------------------------------------------------------- /Weixin/README.md: -------------------------------------------------------------------------------- 1 | # 微信公众号文章 2 | -------------------------------------------------------------------------------- /handout/README.md: -------------------------------------------------------------------------------- 1 | # handouts 讲义 2 | -------------------------------------------------------------------------------- /handout/template.md: -------------------------------------------------------------------------------- 1 | # C++的特点和程序实例 (大标题,一般就是小节标题) 2 | 3 | ## C++的产生和发展 (二级标题) 4 | 5 | - item1 6 | - item2 7 | 8 | 9 | 1. item1 10 | 2. item2 11 | 12 | ### (三级标题,如此类推) 13 | 14 | #### 几点注意 15 | - **图片:** 16 | 17 | 在pics里创建各章(c1,c2)图片目录,**注意图片命名**,要求不能以1.png,2.jpg这种,力求直观 18 | 19 | ![这是一个图片](../pics/test/terminator.jpg) 20 | - **代码:** 21 | ```cpp 22 | #include 23 | using namespace std; 24 | int main() { 25 | int day; 26 | cin >> day; 27 | switch (day) { 28 | case 0: cout << "Sunday" << endl; break; 29 | case 1: cout << "Monday" << endl; break; 30 | case 2: cout << "Tuesday" << endl; break; 31 | case 3: cout << "Wednesday" << endl; break; 32 | case 4: cout << "Thursday" << endl; break; 33 | case 5: cout << "Friday" << endl; break; 34 | case 6: cout << "Saturday" << endl; break; 35 | default: 36 | cout<<"Day out of range Sunday .. Saturday"<< endl; break; 37 | } 38 | return 0; 39 | } 40 | ``` 41 | - **讲义会有加粗,高亮的文字**, 我们对应用**加粗**就可以了 42 | 43 | - 注意有些特殊符号可能会在markdown需要转义 44 | -------------------------------------------------------------------------------- /handout/第10章.md: -------------------------------------------------------------------------------- 1 | # 第十章 泛型程序设计与C++标准模板库 2 | 3 | ## 本章主要内容 4 | - 泛型程序设计的概念 5 | - 与标准模板库有关的概念和术语 6 | - 迭代器 7 | - 容器 8 | - 函数对象 9 | - 算法 10 | 11 | 12 | ## 泛型程序设计的基本概念 13 | - 编写不依赖于具体数据类型的程序 14 | - 将算法从特定的数据结构中抽象出来,成为通用的 15 | - C++的模板为泛型程序设计奠定了关键的基础 16 | 17 | ### 术语:概念 18 | - 用来界定具备一定功能的数据类型。例如: 19 | - 将“可以比大小的所有数据类型(有比较运算符)”这一概念记为```Comparable``` 20 | - 将“具有公有的复制构造函数并可以用‘```=```’赋值的数据类型”这一概念记为```Assignable``` 21 | - 将“可以比大小、具有公有的复制构造函数并可以用‘=’赋值的所有数据类型”这个概念记作```Sortable```。 22 | - 对于两个不同的概念A和B,如果概念A所需求的所有功能也是概念B所需求的功能,那么就说概念B是概念A的子概念。例如: 23 | - ```Sortable```既是```Comparable```的子概念,也是```Assignable```的子概念 24 | 25 | ### 术语:模型 26 | - 模型(model):符合一个概念的数据类型称为该概念的模型,例如: 27 | - ```int```型是```Comparable```概念的模型。 28 | - 静态数组类型不是```Assignable```概念的模型(无法用“```=```”给整个静态数组赋值) 29 | 30 | ### 用概念做模板参数名 31 | - 很多STL的实现代码就是使用概念来命名模板参数的。 32 | - 为概念赋予一个名称,并使用该名称作为模板参数名。 33 | - 例如 34 | - 表示```insertionSort```这样一个函数模板的原型: 35 | - ```template ``` 36 | - ```void insertionSort(Sortable a[], int n);``` 37 | 38 | 39 | ## STL简介 40 | **标准模板库(Standard Template Library,简称STL)提供了一些非常常用的数据结构和算法。*** 41 | 42 | ### STL简介 43 | - 标准模板库(Standard Template Library,简称STL)定义了一套概念体系,为泛型程序设计提供了逻辑基础 44 | - STL中的各个类模板、函数模板的参数都是用这个体系中的概念来规定的。 45 | - 使用STL的模板时,类型参数既可以是C++标准库中已有的类型,也可以是自定义的类型——只要这些类型是所要求概念的模型。 46 | 47 | ### STL的基本组件 48 | - 容器(container) 49 | - 迭代器(iterator) 50 | - 函数对象(function object) 51 | - 算法(algorithms) 52 | 53 | ### STL的基本组件间的关系 54 | - Iterators(迭代器)是算法和容器的桥梁。 55 | - 将迭代器作为算法的参数、通过迭代器来访问容器而不是把容器直接作为算法的参数。 56 | - 将**函数对象**作为算法的参数而不是将函数所执行的运算作为算法的一部分。 57 | - 使用STL中提供的或自定义的迭代器和函数对象,配合STL的算法,可以组合出各种各样的功能。 58 | 59 | ![图片1](../pics/C10/STL的基本组件间的关系.png) 60 | 61 | ### STL的基本组件——容器(container) 62 | - 容纳、包含一组元素的对象。 63 | - 基本容器类模板 64 | - 顺序容器 65 | - array(数组)、vector(向量)、deque(双端队列)、forward_list(单链表)、list(列表) 66 | - (有序)关联容器 67 | - set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射) 68 | - 无序关联容器 69 | - unordered_set (无序集合)、unordered_multiset(无序多重集合) 70 | - unordered_map(无序映射)、unorder_multimap(无序多重映射) 71 | - 容器适配器 72 | - stack(栈)、queue(队列)、priority_queue(优先队列) 73 | - 使用容器,需要包含对应的头文件 74 | 75 | ### STL的基本组件——迭代器(iterator) 76 | - 迭代器是泛化的指针,提供了顺序访问容器中每个元素的方法 77 | - 提供了顺序访问容器中每个元素的方法; 78 | - 可以使用“```++```”运算符来获得指向下一个元素的迭代器; 79 | - 可以使用“```*```”运算符访问一个迭代器所指向的元素,如果元素类型是类或结构体,还可以使用“```->```”运算符直接访问该元素的一个成员; 80 | - 有些迭代器还支持通过“```--```”运算符获得指向上一个元素的迭代器; 81 | - 迭代器是泛化的指针:指针也具有同样的特性,因此指针本身就是一种迭代器; 82 | - 使用独立于STL容器的迭代器,需要包含头文件。 83 | 84 | ### STL的基本组件——函数对象(function object) 85 | - 一个行为类似函数的对象,对它可以像调用函数一样调用。 86 | - 函数对象是泛化的函数:任何普通的函数和任何重载了“```()```” 运算符的类的对象都可以作为函数对象使用 87 | - 使用STL的函数对象,需要包含头文件 88 | 89 | ### STL的基本组件——算法(algorithms) 90 | - STL包括70多个算法 91 | - 例如:排序算法,消除算法,计数算法,比较算法,变换算法,置换算法和容器管理等 92 | - 可以广泛用于不同的对象和内置的数据类型。 93 | - 使用STL的算法,需要包含头文件。 94 | - 例10-1从标准输入读入几个整数,存入向量容器,输出它们的相反数 95 | 96 | ### 例10-1:STL程序实例 97 | transform算法 98 | - transform算法的一种实现: 99 | ``` cpp 100 | template 101 | OutputIterator transform(InputIterator first, InputIterator last, OutputIterator result, UnaryFunction op) { 102 | for (;first != last; ++first, ++result) 103 | *result = op(*first); 104 | return result; 105 | } 106 | ``` 107 | - transform算法顺序遍历```first```和```last```两个迭代器所指向的元素; 108 | - 将每个元素的值作为函数对象```op```的参数; 109 | - 将```op```的返回值通过迭代器```result```顺序输出; 110 | - 遍历完成后```result```迭代器指向的是输出的最后一个元素的下一个位置,transform会将该迭代器返回 111 | 112 | 113 | ## 迭代器 114 | - 迭代器是算法和容器的桥梁 115 | - 迭代器用作访问容器中的元素 116 | - 算法不直接操作容器中的数据,而是通过迭代器间接操作 117 | - 算法和容器独立 118 | - 增加新的算法,无需影响容器的实现 119 | - 增加新的容器,原有的算法也能适用 120 | 121 | ### 输入流迭代器和输出流迭代器 122 | - 输入流迭代器 123 | ```istream_iterator``` 124 | - 以输入流(如```cin```)为参数构造 125 | - 可用```*(p++)```获得下一个输入的元素 126 | - 输出流迭代器 127 | ```ostream_iterator``` 128 | - 构造时需要提供输出流(如```cout```) 129 | - 可用```(*p++) = x```将```x```输出到输出流 130 | - 二者都属于适配器 131 | - 适配器是用来为已有对象提供新的接口的对象 132 | - 输入流适配器和输出流适配器为流对象提供了迭代器的接口 133 | 134 | ### 例10-2从标准输入读入几个实数,分别将它们的平方输出 135 | ``` cpp 136 | //10_2.cpp 137 | #include 138 | #include 139 | #include 140 | using namespace std; 141 |   142 | //求平方的函数 143 | double square(double x) { 144 | return x * x; 145 | } 146 | int main() { 147 | //从标准输入读入若干个实数,分别将它们的平方输出 148 | transform(istream_iterator(cin), istream_iterator(), 149 | ostream_iterator(cout, "\t"), square); 150 | cout << endl; 151 | return 0; 152 | } 153 | ``` 154 | 155 | ### 迭代器的分类 156 | ![图片2](../pics/C10/迭代器的分类.png) 157 | 158 | ### 迭代器支持的操作 159 | - 迭代器是泛化的指针,提供了类似指针的操作(诸如```++```、```*```、```->```运算符) 160 | - 输入迭代器 161 | - 可以用来从序列中读取数据,如输入流迭代器 162 | - 输出迭代器 163 | - 允许向序列中写入数据,如输出流迭代器 164 | - 前向迭代器 165 | - 既是输入迭代器又是输出迭代器,并且可以对序列进行单向的遍历 166 | - 双向迭代器 167 | 与前向迭代器相似,但是在两个方向上都可以对数据遍历 168 | - 随机访问迭代器 169 | - 也是双向迭代器,但能够在序列中的任意两个位置之间进行跳转,如指针、使用vector的```begin()```、```end()```函数得到的迭代器 170 | 171 | ### 迭代器的区间 172 | - 两个迭代器表示一个区间:```[p1, p2)``` 173 | - STL算法常以迭代器的区间作为输入,传递输入数据 174 | - 合法的区间 175 | - ```p1```经过```n```次```(n > 0)```自增```(++)```操作后满足```p1 == p2``` 176 | - 区间包含```p1```,但不包含```p2``` 177 | 178 | ### 例10-3 综合运用几种迭代器的示例 179 | 程序涉及到输入迭代器、输出迭代器、随机访问迭代器这三个迭代器概念,并且以前两个概念为基础编写了一个通用算法。 180 | ``` cpp 181 | //10_3.cpp 182 | #include 183 | #include 184 | #include 185 | #include 186 | using namespace std; 187 |   188 | //将来自输入迭代器的n个T类型的数值排序,将结果通过输出迭代器result输出 189 | template 190 | void mySort(InputIterator first, InputIterator last, OutputIterator result) { 191 | //通过输入迭代器将输入数据存入向量容器s中 192 | vector s; 193 | for (;first != last; ++first) 194 | s.push_back(*first); 195 | //对s进行排序,sort函数的参数必须是随机访问迭代器 196 | sort(s.begin(), s.end()); 197 | copy(s.begin(), s.end(), result); //将s序列通过输出迭代器输出 198 | } 199 | 200 | int main() { 201 | //将s数组的内容排序后输出 202 | double a[5] = { 1.2, 2.4, 0.8, 3.3, 3.2 }; 203 | mySort(a, a + 5, ostream_iterator(cout, " ")); 204 | cout << endl; 205 | //从标准输入读入若干个整数,将排序后的结果输出 206 | mySort(istream_iterator(cin), istream_iterator(), ostream_iterator(cout, " ")); 207 | cout << endl; 208 | return 0; 209 | } 210 | ``` 211 | 运行结果: 212 | ``` cpp 213 | 0.8 1.2 2.4 3.2 3.3 214 | 2 -4 5 8 -1 3 6 -5 215 | -5 -4 -1 2 3 5 6 8 216 | ``` 217 | 218 | ### 迭代器的辅助函数 219 | - ```advance(p, n)``` 220 | - 对```p```执行```n```次自增操作 221 | - ```distance(first, last)``` 222 | - 计算两个迭代器```first```和```last```的距离,即对```first```执行多少次“```++```”操作后能够使得```first == last``` 223 | 224 | 225 | ## 容器的基本功能与分类 226 | - 容器类是容纳、包含一组元素或元素集合的对象。 227 | - 基于容器中元素的组织方式:顺序容器、关联容器 228 | - 按照与容器所关联的迭代器类型划分:可逆容器随机访问容器 229 | 230 | ### 容器的基本功能与分类 231 | - 容器 232 | - 顺序容器 233 | - array(数组)、vector(向量)、deque(双端队列)、forward_list(单链表)、list(列表) 234 | - (有序)关联容器 235 | - set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射) 236 | - 无序关联容器 237 | - unordered_set(无序集合)、unordered_multiset(无序多重集合) 238 | - unordered_map(无序映射)、unorder_multimap(无序多重映射) 239 | 240 | ### 容器的分类 241 | ![图片3](../pics/C10/容器的分类1.png) 242 | ![图片4](../pics/C10/容器的分类2.png) 243 | 244 | ### 容器的通用功能 245 | - 容器的通用功能 246 | - 用默认构造函数构造空容器 247 | - 支持关系运算符:```==```、```!=```、```<```、```<=```、```>```、```>=``` 248 | - ```begin()```、```end()```:获得容器首、尾迭代器 249 | - ```clear()```:将容器清空 250 | - ```empty()```:判断容器是否为空 251 | - ```size()```:得到容器元素个数 252 | - ```s1.swap(s2)```:将```s1```和```s2```两容器内容交换 253 | - 相关数据类型(```S```表示容器类型) 254 | - ```S::iterator```:指向容器元素的迭代器类型 255 | - ```S::const_iterator```:常迭代器类型 256 | 257 | ### 对可逆容器的访问 258 | - STL为每个可逆容器都提供了逆向迭代器,逆向迭代器可以通过下面的成员函数得到: 259 | - ```rbegin()``` :指向容器尾的逆向迭代器 260 | - ```rend()```:指向容器首的逆向迭代器 261 | - 逆向迭代器的类型名的表示方式如下: 262 | - ```S::reverse_iterator```:逆向迭代器类型 263 | - ```S::const_reverse_iterator```:逆向常迭代器类型 264 | 265 | ### 随机访问容器 266 | - 随机访问容器支持对容器的元素进行随机访问 267 | - ```s[n]```:获得容器```s```的第```n```个元素 268 | 269 | 270 | ## 顺序容器的基本功能 271 | ### 顺序容器 272 | - 向量(vector) 273 | - 双端队列(deque) 274 | - 列表(list) 275 | - 单向链表(forward_list) 276 | - 数组(array) 277 | - 元素线性排列,可以随时在指定位置插入元素和删除元素。 278 | - 必须符合Assignable这一概念(即具有公有的拷贝构造函数并可以用“```=```”赋值)。 279 | - array对象的大小固定,forward_list有特殊的添加和删除操作。 280 | 281 | #### 注: 282 | - 向量、双端队、列表和单向链表在逻辑上可看作是一个长度可扩展的数组。 283 | 284 | 285 | ### 顺序容器的接口(不包含单向链表(forward_list)和数组(array)) 286 | - 构造函数 287 | - 赋值函数 288 | - ```assign``` 289 | - 插入函数 290 | - ```insert```, ```push_front```(只对list和deque),```push_back```,```emplace```,```emplace_front``` 291 | - 删除函数 292 | - ```erase```,```clear```,```pop_front```(只对list和deque),```pop_back```,```emplace_back``` 293 | - 首尾元素的直接访问 294 | - ```front```,```back``` 295 | - 改变大小 296 | - ```resize``` 297 | 298 | ### 例10-4 顺序容器的基本操作 299 | ``` cpp 300 | //10_4.cpp 301 | #include 302 | #include 303 | #include 304 | 305 | //输出指定的顺序容器的元素 306 | template 307 | void printContainer(const char* msg, const T& s) { 308 | cout << msg << ": "; 309 | copy(s.begin(), s.end(), ostream_iterator(cout, " ")); 310 | cout << endl; 311 | } 312 |   313 | int main() { 314 | //从标准输入读入10个整数,将它们分别从s的头部加入 315 | deque s; 316 | for (int i = 0; i < 10; i++) { 317 | int x; 318 | cin >> x; 319 | s.push_front(x); 320 | } 321 | printContainer("deque at first", s); 322 | //用s容器的内容的逆序构造列表容器l 323 | list l(s.rbegin(), s.rend()); 324 | printContainer("list at first", l); 325 | ``` 326 | ### 例10-4 顺序容器的基本操作 327 | ``` cpp 328 | //将列表容器l的每相邻两个元素顺序颠倒 329 | list::iterator iter = l.begin(); 330 | while (iter != l.end()) { 331 | int v = *iter; 332 | iter = l.erase(iter); 333 | l.insert(++iter, v); 334 | } 335 | printContainer("list at last", l); 336 | //用列表容器l的内容给s赋值,将s输出 337 | s.assign(l.begin(), l.end()); 338 | printContainer("deque at last", s); 339 | return 0; 340 | } 341 | ``` 342 | - 运行结果如下: 343 | ``` cpp 344 | 0 9 8 6 4 3 2 1 5 4 345 | deque at first: 4 5 1 2 3 4 6 8 9 0 346 | list at first: 0 9 8 6 4 3 2 1 5 4 347 | list at last: 9 0 6 8 3 4 1 2 4 5 348 | deque at last: 9 0 6 8 3 4 1 2 4 5 349 | ``` 350 | 351 | 352 | ## 顺序容器的特性 353 | - 顺序容器:向量、双端队列、列表、单向链表、数组 354 | 355 | ### 向量(Vector) 356 | - 特点 357 | - 一个可以扩展的动态数组 358 | - 随机访问、在尾部插入或删除元素快 359 | - 在中间或头部插入或删除元素慢 360 | - 向量的容量 361 | - 容量```(capacity)```:实际分配空间的大小 362 | - ```s.capacity()```:返回当前容量 363 | - ```s.reserve(n)```:若容量小于```n```,则对```s```进行扩展,使其容量至少为```n``` 364 | 365 | ### 双端队列(deque) 366 | - 特点 367 | - 在两端插入或删除元素快 368 | - 在中间插入或删除元素慢 369 | - 随机访问较快,但比向量容器慢 370 | 371 | ### 例10-5 奇偶排序 372 | 先按照从大到小顺序输出奇数,再按照从小到大顺序输出偶数。 373 | ``` cpp 374 | // 头部分省略 375 | int main() { 376 | istream_iterator i1(cin), i2; //建立一对输入流迭代器 377 | vector s1(i1, i2); //通过输入流迭代器从标准输入流中输入数据 378 | sort(s1.begin(), s1.end()); //将输入的整数排序 379 | deque s2; 380 | //以下循环遍历s1 381 | for (vector::iterator iter = s1.begin(); iter != s1.end(); ++iter) 382 | { 383 | if (*iter % 2 == 0) //偶数放到s2尾部 384 | s2.push_back(*iter); 385 | else //奇数放到s2首部 386 | s2.push_front(*iter); 387 | } 388 | //将s2的结果输出 389 | copy(s2.begin(), s2.end(), ostream_iterator(cout, " ")); 390 | cout << endl; 391 | return 0; 392 | } 393 | ``` 394 | 395 | ### 列表(list) 396 | - 特点 397 | - 在任意位置插入和删除元素都很快 398 | - 不支持随机访问 399 | - 接合(splice)操作 400 | - ```s1.splice(p, s2, q1, q2)```:将```s2```中```[q1, q2)```移动到```s1```中```p```所指向元素之前 401 | ``` cpp 402 | // 头部分省略 403 | int main() { 404 | string names1[] = { "Alice", "Helen", "Lucy", "Susan" }; 405 | string names2[] = { "Bob", "David", "Levin", "Mike" }; 406 | //用names1数组的内容构造列表s1 407 | list s1(names1, names1 + 4); 408 | //用names2数组的内容构造列表s2 409 | list s2(names2, names2 + 4); 410 |   411 | //将s1的第一个元素放到s2的最后 412 | s2.splice(s2.end(), s1, s1.begin()); 413 | list::iterator iter1 = s1.begin(); //iter1指向s1首 414 | advance(iter1, 2); //iter1前进2个元素,它将指向s1第3个元素 415 | list::iterator iter2 = s2.begin(); //iter2指向s2首 416 | ++iter2; //iter2前进1个元素,它将指向s2第2个元素 417 | list::iterator iter3 = iter2; //用iter2初始化iter3 418 | advance(iter3, 2); //iter3前进2个元素,它将指向s2第4个元素 419 | //将[iter2, iter3)范围内的结点接到s1中iter1指向的结点前 420 | s1.splice(iter1, s2, iter2, iter3); 421 |   422 | //分别将s1和s2输出 423 | copy(s1.begin(), s1.end(), ostream_iterator(cout, " ")); 424 | cout << endl; 425 | copy(s2.begin(), s2.end(), ostream_iterator(cout, " ")); 426 | cout << endl; 427 | return 0; 428 | } 429 | ``` 430 | 431 | ### 单向链表(forward_list) 432 | - 单向链表每个结点只有指向下个结点的指针,没有简单的方法来获取一个结点的前驱; 433 | - 未定义```insert```、```emplace```和```erase```操作,而定义了```insert_after```、```emplace_after```和```erase_after```操作,其参数与```list```的```insert```、```emplace```和```erase```相同,但并不是插入或删除迭代器```p1```所指的元素,而是对```p1```所指元素之后的结点进行操作; 434 | - 不支持```size```操作。 435 | 436 | ### 数组(array) 437 | - array是对内置数组的封装,提供了更安全,更方便的使用数组的方式 438 | - array的对象的大小是固定的,定义时除了需要指定元素类型,还需要指定容器大小。 439 | - 不能动态地改变容器大小 440 | 441 | ### 顺序容器的比较 442 | - STL所提供的顺序容器各有所长也各有所短,我们在编写程序时应当根据我们对容器所需要执行的操作来决定选择哪一种容器。 443 | - 如果需要执行大量的随机访问操作,而且当扩展容器时只需要向容器尾部加入新的元素,就应当选择向量容器vector; 444 | - 如果需要少量的随机访问操作,需要在容器两端插入或删除元素,则应当选择双端队列容器deque; 445 | - 如果不需要对容器进行随机访问,但是需要在中间位置插入或者删除元素,就应当选择列表容器list或forward_list; 446 | - 如果需要数组,array相对于内置数组类型而言,是一种更安全、更容易使用的数组类型。 447 | 448 | 449 | ## 顺序容器的插入迭代器与适配器 450 | ### 顺序容器的插入迭代器 451 | - 用于向容器头部、尾部或中间指定位置插入元素的迭代器 452 | - 包括前插迭代器(front_inserter)、后插迭代器(back_insrter)和任意位置插入迭代器(inserter) 453 | - 例: 454 | ``` cpp 455 | list s; 456 | back_inserter iter(s); 457 | *(iter++) = 5; //通过iter把5插入s末尾 458 | ``` 459 | ### 顺序容器的适配器 460 | - 以顺序容器为基础构建一些常用数据结构,是对顺序容器的封装 461 | - 栈(stack):最先压入的元素最后被弹出 462 | - 队列(queue):最先压入的元素最先被弹出 463 | - 优先级队列(priority_queue):最“大”的元素最先被弹出 464 | 465 | ### 栈和队列模板 466 | - 栈模板 467 | ``` cpp 468 | template > class stack; 469 | ``` 470 | - 队列模板 471 | ``` cpp 472 | template > class queue; 473 | ``` 474 | - 栈可以用任何一种顺序容器作为基础容器,而队列只允许用前插顺序容器(双端队列或列表) 475 | 476 | ### 栈和队列共同支持的操作 477 | - ```s1 op s2``` ```op```可以是```==```、```!=```、```<```、```<=```、```>```、```>=```之一,它会对两个容器适配器之间的元素按字典序进行比较 478 | - ```s.size()``` 返回```s```的元素个数 479 | - ```s.empty()``` 返回```s```是否为空 480 | - ```s.push(t)``` 将元素```t```压入到```s```中 481 | - ```s.pop()``` 将一个元素从```s```中弹出,对于栈来说,每次弹出的是最后被压入的元素,而对于队列,每次被弹出的是最先被压入的元素 482 | - 不支持迭代器,因为它们不允许对任意元素进行访问 483 | 484 | ### 栈和队列不同的操作 485 | - 栈的操作 486 | - ```s.top()``` 返回栈顶元素的引用 487 | - 队列操作 488 | - ```s.front()``` 获得队头元素的引用 489 | - ```s.back()``` 获得队尾元素的引用 490 | 491 | ### 例10-7 利用栈反向输出单词 492 | ``` cpp 493 | //10_7.cpp, 省略头部分 494 | int main() { 495 | stack s; 496 | string str; 497 | cin >> str; //从键盘输入一个字符串 498 | //将字符串的每个元素顺序压入栈中 499 | for (string::iterator iter = str.begin(); iter != str.end(); ++iter) 500 | s.push(*iter); 501 | //将栈中的元素顺序弹出并输出 502 | while (!s.empty()) { 503 | cout << s.top(); 504 | s.pop(); 505 | } 506 | cout << endl; 507 | return 0; 508 | } 509 | ``` 510 | 运行结果如下: 511 | ``` cpp 512 | congratulations 513 | snoitalutargnoc 514 | ``` 515 | ### 优先级队列 516 | - 优先级队列也像栈和队列一样支持元素的压入和弹出,但元素弹出的顺序与元素的大小有关,每次弹出的总是容器中最“大”的一个元素。 517 | ``` cpp 518 | template > class priority_queue; 519 | ``` 520 | - 优先级队列的基础容器必须是支持随机访问的顺序容器。 521 | - 支持栈和队列的```size```、```empty```、```push```、```pop```几个成员函数,用法与栈和队列相同。 522 | - 优先级队列并不支持比较操作。 523 | - 与栈类似,优先级队列提供一个```top```函数,可以获得下一个即将被弹出元素(即最“大”的元素)的引用。 524 | 525 | ### 例10-8 细胞分裂模拟 526 | 一种细胞在诞生(即上次分裂)后会在500到2000秒内分裂为两个细胞,每个细胞又按照同样的规律继续分裂。 527 | ``` cpp 528 | // 10.8.cpp, 头部分省略 529 | const int SPLIT_TIME_MIN = 500; //细胞分裂最短时间 530 | const int SPLIT_TIME_MAX = 2000; //细胞分裂最长时间 531 |   532 | class Cell; 533 | priority_queue cellQueue; 534 |   535 | class Cell { //细胞类 536 | private: 537 | static int count; //细胞总数 538 | int id; //当前细胞编号 539 | int time; //细胞分裂时间 540 | public: 541 | Cell(int birth) : id(count++) { //birth为细胞诞生时间 542 | //初始化,确定细胞分裂时间 543 | time = birth + (rand() % (SPLIT_TIME_MAX - SPLIT_TIME_MIN))+ SPLIT_TIME_MIN; 544 | } 545 | int getId() const { return id; } //得到细胞编号 546 | int getSplitTime() const { return time; } //得到细胞分裂时间 547 | bool operator < (const Cell& s) const //定义“<” 548 | { return time > s.time; } 549 | void split() { //细胞分裂 550 | Cell child1(time), child2(time); //建立两个子细胞 551 | cout << time << "s: Cell #" << id << " splits to #" 552 | << child1.getId() << " and #" << child2.getId() << endl; 553 | cellQueue.push(child1); //将第一个子细胞压入优先级队列 554 | cellQueue.push(child2); //将第二个子细胞压入优先级队列 555 | } 556 | }; 557 | int Cell::count = 0; 558 | int main() { 559 | srand(static_cast(time(0))); 560 | int t; //模拟时间长度 561 | cout << "Simulation time: "; 562 | cin >> t; 563 | cellQueue.push(Cell(0)); //将第一个细胞压入优先级队列 564 | while (cellQueue.top().getSplitTime() <= t) { 565 | cellQueue.top().split(); //模拟下一个细胞的分裂 566 | cellQueue.pop(); //将刚刚分裂的细胞弹出 567 | } 568 | return 0; 569 | } 570 | ``` 571 | - 运行结果如下: 572 | ``` cpp 573 | Simulation time: 5000 574 | 971s: Cell #0 splits to #1 and #2 575 | 1719s: Cell #1 splits to #3 and #4 576 | 1956s: Cell #2 splits to #5 and #6 577 | 2845s: Cell #6 splits to #7 and #8 578 | 3551s: Cell #3 splits to #9 and #10 579 | 3640s: Cell #4 splits to #11 and #12 580 | 3919s: Cell #5 splits to #13 and #14 581 | 4162s: Cell #10 splits to #15 and #16 582 | 4197s: Cell #8 splits to #17 and #18 583 | 4317s: Cell #7 splits to #19 and #20 584 | 4686s: Cell #13 splits to #21 and #22 585 | 4809s: Cell #12 splits to #23 and #24 586 | 4818s: Cell #17 splits to #25 and #26 587 | ``` 588 | 589 | 590 | ## 关联容器分类和的基本功能 591 | ### 关联容器的特点和接口 592 | - 关联容器的特点 593 | - 每个关联容器都有一个键(key) 594 | - 可以根据键高效地查找元素 595 | - 接口 596 | - 插入:```insert``` 597 | - 删除:```erase``` 598 | - 查找:```find``` 599 | - 定界:```lower_bound```、```upper_bound```、```equal_range``` 600 | - 计数:```count``` 601 | 602 | ### 关联容器概念图 603 | ![图片5](../pics/C10/关联容器概念图.png) 604 | 605 | ### 四种关联容器 606 | - 单重关联容器(set和map) 607 | - 键值是唯一的,一个键值只能对应一个元素 608 | - 多重关联容器(multiset和multimap) 609 | - 键值是不唯一的,一个键值可以对应多个元素 610 | - 简单关联容器(set和ultiset 611 | - 容器只有一个类型参数,如```set```、```multiset```,表示键类型 612 | - 容器的元素就是键本身 613 | - 二元关联容器(map和multimap) 614 | - 容器有两个类型参数,如```map```、```multimap```,分别表示键和附加数据的类型 615 | - 容器的元素类型是```pair```,即由键类型和元素类型复合而成的二元组 616 | 617 | ### 无序关联容器 618 | - C++11新标准中定义了4个无序关联容器 619 | - unordered_set、unordered_map、unordered_multiset、unordered_multimap 620 | - 不是使用比较运算符来组织元素的,而是通过一个哈希函数和键类型的==运算符。 621 | - 提供了与有序容器相同的操作 622 | - 可以直接定义关键字是内置类型的无序容器。 623 | - 不能直接定义关键字类型为自定义类的无序容器,如果需要,必须提供我们自己的```hash```模板。 624 | 625 | 626 | ## 集合(set) 627 | 集合用来存储一组无重复的元素。由于集合的元素本身是有序的,可以高效地查找指定元素,也可以方便地得到指定大小范围的元素在容器中所处的区间。 628 | 629 | ### 例10-9 630 | 输入一串实数,将重复的去掉,取最大和最小者的中值,分别输出小于等于此中值和大于等于此中值的实数 631 | ``` cpp 632 | //10_9.cpp 633 | #include 634 | #include 635 | #include 636 | #include 637 | using namespace std; 638 | 639 | int main() { 640 | set s; 641 | while (true) { 642 | double v; 643 | cin >> v; 644 | if (v == 0) break; //输入0表示结束 645 | //尝试将v插入 646 | pair::iterator,bool> r=s.insert(v); 647 | if (!r.second) //如果v已存在,输出提示信息 648 | cout << v << " is duplicated" << endl; 649 | } 650 | //得到第一个元素的迭代器 651 | set::iterator iter1=s.begin(); 652 | //得到末尾的迭代器 653 | set::iterator iter2=s.end(); 654 | //得到最小和最大元素的中值 655 | double medium=(*iter1 + *(--iter2)) / 2; 656 | //输出小于或等于中值的元素 657 | cout<< "<= medium: " 658 | copy(s.begin(), s.upper_bound(medium), ostream_iterator(cout, " ")); 659 | cout << endl; 660 | //输出大于或等于中值的元素 661 | cout << ">= medium: "; 662 | copy(s.lower_bound(medium), s.end(), ostream_iterator(cout, " ")); 663 | cout << endl; 664 | return 0; 665 | } 666 | ``` 667 | 运行结果如下: 668 | ``` cpp 669 | 1 2.5 5 3.5 5 7 9 2.5 0 670 | 5 is duplicated 671 | 2.5 is duplicated 672 | <= medium: 1 2.5 3.5 5 673 | >= medium: 5 7 9 674 | ``` 675 | 676 | 677 | ## 映射(map) 678 | - 映射与集合同属于单重关联容器,它们的主要区别在于,集合的元素类型是键本身,而映射的元素类型是由键和附加数据所构成的二元组。 679 | - 在集合中按照键查找一个元素时,一般只是用来确定这个元素是否存在,而在映射中按照键查找一个元素时,除了能确定它的存在性外,还可以得到相应的附加数据。 680 | 681 | ### 例10-10 682 | 有五门课程,每门都有相应学分,从中选择三门,输出学分总和 683 | ``` cpp 684 | //10_10.cpp 685 | #include 686 | #include 687 | #include 688 | #include 689 | using namespace std; 690 | int main() { 691 | map courses; 692 | //将课程信息插入courses映射中 693 | courses.insert(make_pair("CSAPP", 3)); 694 | courses.insert(make_pair("C++", 2)); 695 | courses.insert(make_pair("CSARCH", 4)); 696 | courses.insert(make_pair("COMPILER", 4)); 697 | courses.insert(make_pair("OS", 5)); 698 | int n = 3; //剩下的可选次数 699 | int sum = 0; //学分总和 700 | while (n > 0) { 701 | string name; 702 | cin >> name; //输入课程名称 703 | map::iterator iter = courses.find(name);//查找课程 704 | if (iter == courses.end()) { //判断是否找到 705 | cout << name << " is not available" << endl; 706 | } else { 707 | sum += iter->second; //累加学分 708 | courses.erase(iter); //将刚选过的课程从映射中删除 709 | n--; 710 | } 711 | } 712 | cout << "Total credit: " << sum << endl; //输出总学分 713 | return 0; 714 | } 715 | ``` 716 | 运行结果如下: 717 | ``` cpp 718 | C++ 719 | COMPILER 720 | C++ 721 | C++ is not available 722 | CSAPP 723 | Total credit: 9 724 | ``` 725 | 726 | ### 例10-11 727 | 统计一句话中每个字母出现的次数 728 | ``` cpp 729 | // 10_11.cpp 730 | #include 731 | #include 732 | #include 733 | using namespace std; 734 | int main() { 735 | map s; //用来存储字母出现次数的映射 736 | char c; //存储输入字符 737 | do { 738 | cin >> c; //输入下一个字符 739 | if (isalpha(c)){ //判断是否是字母 740 | c = tolower(c); //将字母转换为小写 741 | s[c]++; //将该字母的出现频率加1 742 | } 743 | } while (c != '.'); //碰到“.”则结束输入 744 | //输出每个字母出现次数 745 | for (map::iterator iter = s.begin(); iter != s.end(); ++iter) 746 | cout << iter->first << " " << iter->second << " "; 747 | cout << endl; 748 | return 0; 749 | } 750 | ``` 751 | 752 | 753 | ## 多重集合(multiset)与多重映射(multimap) 754 | - 多重集合是允许有重复元素的集合,多重映射是允许一个键对应多个附加数据的映射。 755 | - 多重集合与集合、多重映射与映射的用法差不多,只在几个成员函数上有细微差异,其差异主要表现在去除了键必须唯一的限制。 756 | 757 | ### 例10-12 上课时间查询 758 | ``` cpp 759 | //10_12.cpp 760 | #include 761 | #include 762 | #include 763 | #include 764 | using namespace std; 765 | int main() { 766 | multimap courses; 767 | typedef multimap::iterator CourseIter; 768 |   769 | //将课程上课时间插入courses映射中 770 | courses.insert(make_pair("C++", "2-6")); 771 | courses.insert(make_pair("COMPILER", "3-1")); 772 | courses.insert(make_pair("COMPILER", "5-2")); 773 | courses.insert(make_pair("OS", "1-2")); 774 | courses.insert(make_pair("OS", "4-1")); 775 | courses.insert(make_pair("OS", "5-5")); 776 | //输入一个课程名,直到找到该课程为止,记下每周上课次数 777 | string name; 778 | int count; 779 | do { 780 | cin >> name; 781 | count = courses.count(name); 782 | if (count == 0) 783 | cout << "Cannot find this course!" << endl; 784 | } while (count == 0); 785 | //输出每周上课次数和上课时间 786 | cout << count << " lesson(s) per week: "; 787 | pair range = courses.equal_range(name); 788 | for (CourseIter iter = range.first; iter != range.second; ++iter) 789 | cout << iter->second << " "; 790 | cout << endl; 791 | 792 | return 0; 793 | } 794 | ``` 795 | 运行结果如下: 796 | ``` cpp 797 | JAVA 798 | Cannot find this course! 799 | OS 800 | 3 lesson(s) per week: 1-2 4-1 5-5 801 | ``` 802 | 803 | 804 | ## 函数对象 805 | - 一个行为类似函数的对象 806 | - 可以没有参数,也可以带有若干参数 807 | - 其功能是获取一个值,或者改变操作的状态。 808 | - 例 809 | - 普通函数就是函数对象 810 | - 重载了“```()```”运算符的类的实例是函数对象 811 | 812 | ### 函数对象概念图 813 | ![图片6](../pics/C10/函数对象概念图.png) 814 | 815 | ### 例10-13、例10-14: 816 | - 使用两种方式定义表示乘法的函数对象 817 | - 通过定义普通函数(例10-13) 818 | - 通过重载类的“```()```”运算符(例10-14) 819 | 用到以下算法: 820 | ``` cpp 821 | template 822 | Type accumulate(InputIterator first, InputIterator last, Type val, BinaryFunction binaryOp); 823 | ``` 824 | - 对```[first, last)```区间内的数据进行累“```加```”,```binaryOp```为用二元函数对象表示的“```加```”运算符,```val```为累“```加```”的初值 825 | 826 | ### 例10-13 827 | ``` cpp 828 | #include 829 | #include //包含数值算法头文件 830 | using namespace std; 831 | 832 | //定义一个普通函数 833 | int mult(int x, int y) { return x * y; }; 834 | 835 | int main() { 836 | int a[] = { 1, 2, 3, 4, 5 }; 837 | const int N = sizeof(a) / sizeof(int); 838 | cout << "The result by multipling all elements in a is " 839 | << accumulate(a, a + N, 1, mult) 840 | << endl; 841 | return 0; 842 | } 843 | ``` 844 | 845 | ### 例10-14 846 | ``` cpp 847 | //10_14.cpp 848 | #include 849 | #include //包含数值算法头文件 850 | using namespace std; 851 | class MultClass{ //定义MultClass类 852 | public: 853 | //重载操作符operator() 854 | int operator() (int x, int y) const { return x * y; } 855 | }; 856 | int main() { 857 | int a[] = { 1, 2, 3, 4, 5 }; 858 | const int N = sizeof(a) / sizeof(int); 859 | cout << "The result by multipling all elements in a is " 860 | << accumulate(a, a + N, 1, MultClass()) //将类multclass传递给通用算法 861 | << endl; 862 | return 0; 863 | } 864 | ``` 865 | 866 | ### STL提供的函数对象 867 | - 用于算术运算的函数对象: 868 | - 一元函数对象(一个参数) :```negate``` 869 | - 二元函数对象(两个参数) :```plus```、```minus```、```multiplies```、```divides```、```modulus``` 870 | - 用于关系运算、逻辑运算的函数对象(要求返回值为```bool```) 871 | - 一元谓词(一个参数):```logical_not``` 872 | - 二元谓词(两个参数):```equal_to```、```not_equal_to```、```greater```、```less```、```greater_equal```、```less_equal```、```logical_and```、```logical_or``` 873 | 874 | ### 例10-15 利用STL标准函数对象 875 | ``` cpp 876 | //10_15.cpp 877 | #include 878 | #include //包含数值算法头文件 879 | #include //包含标准函数对象头文件 880 | using namespace std; 881 | int main() { 882 | int a[] = { 1, 2, 3, 4, 5 }; 883 | const int N = sizeof(a) / sizeof(int); 884 | cout << "The result by multipling all elements in A is “ 885 | << accumulate(a, a + N, 1, multiplies()) 886 | << endl; //将标准函数对象传递给通用算法 887 | return 0; 888 | } 889 | ``` 890 | 891 | ### 例10-16利用STL中的二元谓词函数对象 892 | ``` cpp 893 | // 10_16.cpp 894 | #include 895 | #include 896 | #include 897 | #include 898 | using namespace std; 899 | 900 | int main() { 901 | int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 }; 902 | const int N = sizeof(intArr) / sizeof(int); 903 | vector a(intArr, intArr + N); 904 |   cout << "before sorting:" << endl; 905 | copy(a.begin(),a.end(),ostream_iterator(cout,"\t")); 906 | cout << endl; 907 |   908 | sort(a.begin(), a.end(), greater()); 909 |   910 | cout << "after sorting:" << endl; 911 | copy(a.begin(),a.end(),ostream_iterator(cout,"\t")); 912 | cout << endl; 913 | return 0; 914 | } 915 | ``` 916 | 917 | 918 | ## 函数适配器 919 | - 绑定适配器:```bind1st```、```bind2nd``` 920 | - 将```n```元函数对象的指定参数绑定为一个常数,得到```n-1```元函数对象 921 | - 组合适配器:```not1```、```not2``` 922 | - 将指定谓词的结果取反 923 | - 函数指针适配器:```ptr_fun``` 924 | - 将一般函数指针转换为函数对象,使之能够作为其它函数适配器的输入。 925 | - 在进行参数绑定或其他转换的时候,通常需要函数对象的类型信息,例如```bind1st```和```bind2nd```要求函数对象必须继承于```binary_function```类型。但如果传入的是函数指针形式的函数对象,则无法获得函数对象的类型信息。 926 | - 成员函数适配器:```ptr_fun```、```ptr_fun_ref``` 927 | - 对成员函数指针使用,把n元成员函数适配为```n + 1```元函数对象,该函数对象的第一个参数为调用该成员函数时的目的对象 928 | - 也就是需要将“```object->method()```”转为“```method(object)```”形式。将“```object->method(arg1)```”转为二元函数“```method(object, arg1)```”。 929 | 930 | ### 绑定适配器 931 | - ```binder2nd```的实例构造通常比较冗长,```bind2nd```函数用于辅助构造```binder2nd```,产生它的一个实例。 932 | - ```binder1st```和```bind1st```,将一个具体值绑定到二元函数的第一个参数。 933 | 934 | ### 例10-17:函数适配器实例——找到数组中第一个大于40的元素 935 | ``` cpp 936 | //10_17.cpp 937 | #include 938 | #include 939 | #include 940 | #include 941 | using namespace std; 942 |   943 | int main() { 944 | int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 }; 945 | const int N = sizeof(intArr) / sizeof(int); 946 | vector a(intArr, intArr + N); 947 | vector::iterator p = find_if(a.begin(), a.end(), bind2nd(greater(), 40)); 948 | if (p == a.end()) 949 | cout << "no element greater than 40" << endl; 950 | else 951 | cout << "first element greater than 40 is: " << *p << endl; 952 | return 0; 953 | } 954 | ``` 955 | #### 注: 956 | find_if算法在STL中的原型声明为: 957 | ```cpp 958 | template 959 | InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred); 960 | ``` 961 | 它的功能是查找数组```[first, last)```区间中第一个```pred(x)```为真的元素。 962 | 963 | ### 组合适配器 964 | - 对于一般的逻辑运算,有时可能还需要对结果求一次逻辑反。 965 | - ```unary_negate和binary_negate```实现了这一适配功能。STL还提供了```not1```和```not2```辅助生成相应的函数对象实例,分别用于一元谓词和二元谓词的逻辑取反。 966 | 967 | ### 例10-18 ptr_fun、not1和not2产生函数适配器实例 968 | ``` cpp 969 | // 10_18.cpp 970 | #include 971 | #include 972 | #include 973 | #include 974 | using namespace std; 975 | 976 | bool g(int x, int y) { 977 | return x > y; 978 | } 979 |   980 | int main() { 981 | int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 }; 982 | const int N = sizeof(intArr) / sizeof(int); 983 | vector a(intArr, intArr + N); 984 | vector::iterator p; 985 | p = find_if(a.begin(), a.end(), bind2nd(ptr_fun(g), 40)); 986 | if (p == a.end()) 987 | cout << "no element greater than 40" << endl; 988 | else 989 | cout << "first element greater than 40 is: " << *p << endl; 990 | p = find_if(a.begin(), a.end(), not1(bind2nd(greater(), 15))); 991 | if (p == a.end()) 992 | cout << "no element is not greater than 15" << endl; 993 | else 994 | cout << "first element that is not greater than 15 is: " << *p << endl; 995 |   996 | p = find_if(a.begin(), a.end(), bind2nd(not2(greater()), 15)); 997 | if (p == a.end()) 998 | cout << "no element is not greater than 15" << endl; 999 | else 1000 | cout << "first element that is not greater than 15 is: " << *p << endl; 1001 | return 0; 1002 | } 1003 | ``` 1004 | 1005 | ### 例10-19 成员函数适配器实例 1006 | ``` cpp 1007 | //10_19.cpp 1008 | #include 1009 | #include 1010 | #include 1011 | #include 1012 | using namespace std; 1013 |   1014 | struct Car { 1015 | int id; 1016 | Car(int id) { this->id = id; } 1017 | void display() const { cout << "car " << id << endl; } 1018 | }; 1019 |   1020 | int main() { 1021 | vector pcars; 1022 | vector cars; 1023 | for (int i = 0; i < 5; i++) 1024 | pcars.push_back(new Car(i)); 1025 | for (int i = 5; i < 10; i++) 1026 | cars.push_back(Car(i)); 1027 | cout << "elements in pcars: " << endl; 1028 | for_each(pcars.begin(), pcars.end(), std::mem_fun(&Car::display)); 1029 | cout << endl; 1030 |   1031 | cout << "elements in cars: " << endl; 1032 | for_each(cars.begin(), cars.end(), std::mem_fun_ref(&Car::display)); 1033 | cout << endl; 1034 |   1035 | for (size_t i = 0; i < pcars.size(); ++i) 1036 | delete pcars[i]; 1037 |   1038 | return 0; 1039 | } 1040 | ``` 1041 | 1042 | ## 算法 1043 | ### STL算法特点 1044 | - STL算法本身是一种函数模版 1045 | - 通过迭代器获得输入数据 1046 | - 通过函数对象对数据进行处理 1047 | - 通过迭代器将结果输出 1048 | - STL算法是通用的,独立于具体的数据类型、容器类型 1049 | 1050 | ### STL算法分类 1051 | - 不可变序列算法 1052 | - 可变序列算法 1053 | - 排序和搜索算法 1054 | - 数值算法 1055 | 1056 | ### 不可变序列算法 1057 | - 不直接修改所操作的容器内容的算法 1058 | - 用于查找指定元素、比较两个序列是否相等、对元素进行计数等 1059 | - 例: 1060 | ``` cpp 1061 | template 1062 | InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred); 1063 | ``` 1064 | 查找```[first, last)```区间内```pred(x)```为真的首个元素 1065 | 1066 | ### 可变序列算法 1067 | - 可以修改它们所操作的容器对象 1068 | - 包括对序列进行复制、删除、替换、倒序、旋转、交换、分割、去重、填充、洗牌的算法及生成一个序列的算法 1069 | - 例: 1070 | ``` cpp 1071 | template 1072 | InputIterator find_if(ForwardIterator first, ForwardIterator last, const T& x); 1073 | ``` 1074 | 将```[first, last)```区间内的元素全部改写为```x```。 1075 | 1076 | ### 排序和搜索算法 1077 | - 对序列进行排序 1078 | - 对两有序序列进行合并 1079 | - 对有序序列进行搜索 1080 | - 有序序列的集合操作 1081 | - 堆算法 1082 | - 例: 1083 | ``` cpp 1084 | template 1085 | void sort(RandomAccessIterator first, RandomAccessIterator last, UnaryPredicate comp); 1086 | ``` 1087 | 以函数对象```comp```为“```<```”,对 ```[first, last)```区间内的数据进行排序 1088 | 1089 | ### 数值算法 1090 | - 求序列中元素的“```和```”、部分“```和```”、相邻元素的“```差```”或两序列的内积 1091 | - 求“``````和”的“```+```”、求“差”的“```-```”以及求内积的“```+```”和“```·```”都可由函数对象指定 1092 | - 例: 1093 | - ```template``` 1094 | - ```OutputIterator partial_sum(InputIterator first, InputIterator last, OutputIterator result, BinaryFunction op);``` 1095 | - 对```[first, last)```内的元素求部分“```和```”(所谓部分“```和```”,是一个长度与输入序列相同的序列,其第```n```项为输入序列前```n```个元素的“和”),以函数对象```op```为“```+```”运算符,结果通过```result```输出,返回的迭代器指向输出序列最后一个元素的下一个元素 1096 | 1097 | ### 算法应用举例 1098 | - 例10-20——例10-23演示了几类算法的应用。 1099 | - 详见教材第10章 1100 | 1101 | 1102 | ## 小结 1103 | - 本章主要内容 1104 | - 泛型程序设计的概念 1105 | - 与标准模板库有关的概念和术语 1106 | - 迭代器 1107 | - 容器 1108 | - 函数对象 1109 | - 算法 1110 | - 本章学习目标 1111 | - 初步了解泛型程序设计的概念 1112 | - 学会C++标准模板库(STL)的使用方法 1113 | -------------------------------------------------------------------------------- /handout/第1章.md: -------------------------------------------------------------------------------- 1 | 2 | # 第1章 3 | 4 | ## 本章主要内容 5 | ### 一、计算机系统 6 | - 计算机系统由硬件、软件组成; 7 | - 指令系统是硬件和软件的界面。 8 | 9 | ### 二、计算机语言和程序设计方法 10 | - 计算机语言 11 | - 程序员与计算机沟通的语言; 12 | - 描述解决问题的方法和相关数据。 13 | - 计算机语言的级别 14 | - 二进制代码构成的机器语言; 15 | - 使用助记符的汇编语言; 16 | - 使用类似英语单词和语句的高级语言; 17 | - C++是面向对象的高级语言 18 | - C++支持的程序设计方法 19 | - 面向过程的程序设计方法; 20 | - 面向对象的程序设计方法; 21 | - 泛型程序设计方法。 22 | 23 | ### 三、C++程序的开发过程 24 | - 算法设计 25 | - 源程序编辑 26 | - 编译 27 | - 连接 28 | - 运行调试 29 | 30 | ### 四、信息在计算机中的表示与存储 31 | - 计算机中的数据用二进制表示; 32 | - 逻辑数据、字符数据用二进制编码表示。 33 | 34 | ### 学习建议 35 | - 看教学视频; 36 | - 做练习题; 37 | - 完成“实验一” 38 | 39 | 40 | 41 | ## 计算机系统基本概念 42 | ### 计算机硬件 43 | ![图片1](../pics/C1/计算机硬件.png) 44 | 45 | ### 计算程序语言 46 | - 计算机解决问题是程序控制的; 47 | - 程序就是操作步骤; 48 | - 程序要使用语言来表达。 49 | 50 | ### 机器语言 51 | - 计算机能识别的是机器语言; 52 | - 机器语言指令是由0和1编码的; 53 | - 例如: 54 | 加法指令可能是“0001”。 55 | 56 | ### 计算机指令系统 57 | - 机器硬件能够识别的语言(机器语言)的集合; 58 | - 它是软件和硬件的主要界面。 59 | 60 | ### 计算机软件 61 | - 是一系列按照特定顺序组织的计算机数据和指令的集合。一般来讲软件被划分为系统软件、应用软件和介于这两者之间的中间件; 62 | - 软件包括程序和文档。 63 | 64 | ### 计算机程序 65 | - 指令的序列; 66 | - 描述解决问题的方法和数据。 67 | 68 | 69 | 70 | 71 | 72 | ## 计算机语言和程序设计方法 73 | ### 最初的计算机语言——机器语言 74 | - 由二进制代码构成 75 | - 计算机硬件可以识别 76 | - 可以表示简单的操作 77 | - 例如:加法、减法、数据移动等等 78 | 79 | ### 汇编语言 80 | - 将机器指令映射为助记符 81 | - 如ADD、SUB、mov等; 82 | - 抽象层次低,需要考虑机器细节。 83 | 84 | ### 高级语言 85 | - 关键字、语句容易理解; 86 | - 有含义的数据命名和算式; 87 | - 抽象层次较高; 88 | - 例如,算式:```a+b+c/d``` 89 | - 屏蔽了机器的细节; 90 | - 例如,这样显示计算结果:```cout< 201 | 202 | 203 | ### 十进制小数→ R 进制小数: 204 | - “乘 以R 取整”法。 205 | 206 | ![图片5](../pics/C1/十进制小数→ R 进制小数.png) 207 | 208 | 所以 209 | 210 | 211 | ### 二、八、十六进制的相互转换 212 | - 1位八进制数相当于3位二进制数; 213 | - 1位十六进制数相当于4位二进制数,例如: 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | ## 数据在计算机中的编码表示 222 | ### 二进制数的编码表示 223 | - 需要解决的问题:负数如何表示? 224 | - 最容易想到的方案: 225 | 0:表示“+”号; 226 | 1:表示“-”号。 227 | - 原码 228 | - "符号──绝对值"表示的编码 229 | 230 | 例如: 231 | 232 | ![图片6](../pics/C1/原码.png) 233 | 234 | 235 | - 原码的缺点: 236 | - 零的表示不惟一 237 | 238 | ![公式4](../pics/C1/公式4.png) 239 | 240 | 241 | - 进行四则运算时,符号位须单独处理,运算规则复杂。 242 | - 补码 243 | - 符号位可作为数值参加运算; 244 | - 减法运算可转换为加法运算; 245 | - 0的表示唯一。 246 | - 补码的原理 247 | - 模数: 248 | n位二进制整数的模数为 ; 249 | n位二进制小数的模数为 2。 250 | - 补数: 251 | - 一个数减去另一个数(加一个负数),等于第一个数加第二个数的补数,例(时钟指针): 8+(-2)=8+10 ( mod 12 )=6; 252 | - 一个二进制负数可用其模数与真值做加法 (模减去该数的绝对值) 求得其补码,例(时钟指针):-2+12=10。 253 | - 补码的计算 254 | - 借助于“反码”作为中间码; 255 | - 负数的反码与原码有如下关系: 256 | 符号位不变(仍用1表示),其余各位取反(0变1,1变0),例如: 257 | 258 | ![公式6](../pics/C1/公式6.png) 259 | 260 | - 正数的反码与原码表示相同,正数的补码与原码相同; 261 | - 反码只是求补码时的中间码; 262 | - 负数的补码由该数反码的末位加 1 求得。 263 | - 对补码再求补即得到原码。 264 | - 补码的优点: 265 | - 0的表示唯一; 266 | - 符号位可作为数值参加运算; 267 | - 补码运算的结果仍为补码。 268 | 269 | ### 实数的浮点表示 270 | - 计算机中通常采用浮点方式表示小数; 271 | - 实数 N 用浮点形式可表示为: ![公式7](../pics/C1/公式7.png) 272 | - E:2的幂,N:阶码 273 | - M:N的尾数 274 | 275 | ### 字符在计算机中的表示 276 | - 字符在计算机中是通过编码表示的; 277 | - 例如: 278 | ASCII码是一种常用的西文字符编码:用7位二进制数表示一个字符,最多可以表示个字符; 279 | - 《GB 18030-2005 信息技术 中文编码字符集》是中国国家标准。 280 | 281 | 282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /handout/第4章.md: -------------------------------------------------------------------------------- 1 | # 第4章 类与对象 2 | 3 | ## 本章主要内容 4 | - 程序中的对象是现实中对象的模拟,具有属性和功能/行为; 5 | - 抽象出同一类对象的共同属性和行为,形成类,对象是类的实例; 6 | - 类将数据和处理数据的函数封装在一起,隐藏内部细节,提供对外访问接口; 7 | - 定义对象时,可以通过构造函数进行初始化; 8 | - 删除对象时,可以通过析构函数释放资源; 9 | - 一个类的对象可以由其他类的对象组合而成,即类的成员可以是其他类的对象; 10 | - 在这一章,我们还将学习结构体、联合体和枚举类。 11 | 12 | 13 | ## 面向对象程序设计的基本特点 14 | ### 抽象 15 | - 对同一类对象的共同属性和行为进行概括,形成类。 16 | - 先注意问题的本质及描述,其次是实现过程或细节。 17 | - 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。 18 | - 代码抽象:描述某类对象的共有的行为特征或具有的功能。 19 | - 抽象的实现:类。 20 | 21 | - 抽象实例——钟表 22 | - 数据抽象: 23 | ``` cpp 24 | int hour,int minute,int second 25 | ``` 26 | - 代码抽象: 27 | ```cpp 28 | setTime(),showTime() 29 | ``` 30 | ``` cpp 31 | class Clock { 32 | public: 33 | void setTime(int newH, int newM, int newS); 34 | void showTime(); 35 | private: 36 | int hour, minute, second; 37 | }; 38 | ``` 39 | 40 | ### 封装 41 | - 将抽象出的数据、代码封装在一起,形成类。 42 | - 目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。 43 | - 实现封装:类声明中的{} 44 | - 例: 45 | ``` cpp 46 | class Clock { 47 | public: void setTime(int newH, int newM, int newS); 48 | void showTime(); 49 | private: int hour, minute, second; 50 | }; 51 | ``` 52 | 53 | ### 继承 54 | - 在已有类的基础上,进行扩展形成新的类。 55 | - 详见第7章 56 | 57 | ### 多态 58 | - 多态:同一名称,不同的功能实现方式。 59 | - 目的:达到行为标识统一,减少程序中标识符的个数。 60 | - 实现:重载函数和虚函数——见第8章 61 | 62 | 63 | 64 | ## 类和对象的定义 65 | - 对象是现实中的对象在程序中的模拟。 66 | - 类是同一类对象的抽象,对象时类的某一特定实体。 67 | - 定义类的对象,才可以通过对象使用类中定义的功能。 68 | 69 | ### 设计类就是设计类型 70 | - 此类型的“合法值”是什么? 71 | - 此类型应该有什么样的函数和操作符? 72 | - 新类型的对象该如何被创建和销毁? 73 | - 如何进行对象的初始化和赋值? 74 | - 对象作为函数的参数如何以值传递? 75 | - 谁将使用此类型的对象成员? 76 | 77 | ### 类定义的语法形式 78 | ``` cpp 79 | class 类名称 80 | { 81 | public: 82 | 公有成员(外部接口) 83 | private: 84 | 私有成员 85 | protected: 86 | 保护型成员 87 | } 88 | ``` 89 | 90 | ### 类内初始值 91 | - 可以为数据成员提供一个类内初始值 92 | - 在创建对象时,类内初始值用于初始化数据成员 93 | - 没有初始值的成员将被默认初始化。 94 | - 类内初始值举例 95 | ``` cpp 96 | class Clock { 97 | public: 98 | void setTime(int newH, int newM, int newS); 99 | void showTime(); 100 | private: 101 | int hour = 0, minute = 0, second = 0; 102 | }; 103 | ``` 104 | 105 | ### 类成员的访问控制 106 | - 公有类型成员 107 | - 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。 108 | - 私有类型成员 109 | - 在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。 110 | - 如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。 111 | - 保护类型成员 112 | - 与private类似,其差别表现在继承与派生时对派生类的影响不同,详见第七章。 113 | 114 | ### 对象定义的语法 115 | - 类名 对象名; 116 | - 例:```cpp Clock myClock;``` 117 | 118 | ### 类成员的访问权限 119 | - 类中成员互相访问 120 | - 直接使用成员名访问 121 | - 类外访问 122 | - 使用“对象名.成员名”方式访问 public 属性的成员 123 | 124 | ### 类的成员函数 125 | - 在类中说明函数原型; 126 | - 可以在类外给出函数体实现,并在函数名前使用类名加以限定; 127 | - 也可以直接在类中给出函数体,形成内联成员函数; 128 | - 允许声明重载函数和带默认参数值的函数。 129 | 130 | ### 内联成员函数 131 | - 为了提高运行时的效率,对于较简单的函数可以声明为内联形式。 132 | - 内联函数体中不要有复杂结构(如循环语句和switch语句)。 133 | - 在类中声明内联成员函数的方式: 134 | - 将函数体放在类的声明中。 135 | - 使用inline关键字。 136 | 137 | 138 | 139 | ## 类和对象程序举例 140 | ### 例4-1 钟表类 141 | - 类的定义 142 | ``` cpp 143 | #include 144 | using namespace std; 145 | class Clock{ 146 | public: 147 | void setTime(int newH = 0, int newM = 0, int newS = 0); 148 | void showTime(); 149 | private: 150 | int hour, minute, second; 151 | } 152 | ``` 153 | - 成员函数的实现 154 | ``` cpp 155 | void Clock::setTime(int newH, int newM, int newS) { 156 | hour = newH; 157 | minute = newM; 158 | second = newS; 159 | } 160 | void Clock::showTime() { 161 | cout << hour << ":" << minute << ":" << second; 162 | } 163 | ``` 164 | - 对象的使用 165 | ``` cpp 166 | int main() { 167 | Clock myClock; 168 | myClock.setTime(8, 30, 30); 169 | myClock.showTime(); 170 | return 0; 171 | } 172 | ``` 173 | 174 | 175 | 176 | 177 | ## 构造函数基本概念 178 | ### 构造函数的作用 179 | - 在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态。 180 | - 例如: 181 | - 希望在构造一个Clock类对象时,将初试时间设为0:0:0,就可以通过构造函数来设置。 182 | 183 | ### 构造函数的形式 184 | - 函数名与类名相同; 185 | - 不能定义返回值类型,也不能有return语句; 186 | - 可以有形式参数,也可以没有形式参数; 187 | - 可以是内联函数; 188 | - 可以重载; 189 | - 可以带默认参数值。 190 | 191 | ### 构造函数的调用时机 192 | - 在对象创建时被自动调用 193 | - 例如: 194 | ``` cpp 195 | Clock myClock(0,0,0); 196 | ``` 197 | 198 | ### 默认构造函数 199 | - 调用时可以不需要实参的构造函数 200 | - 参数表为空的构造函数 201 | - 全部参数都有默认值的构造函数 202 | - 下面两个都是默认构造函数,如在类中同时出现,将产生编译错误: 203 | ``` cpp 204 | Clock(); 205 | Clock(int newH=0,int newM=0,int newS=0); 206 | ``` 207 | 208 | ### 隐含生成的构造函数 209 | - 如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数 210 | - 参数列表为空,不为数据成员设置初始值; 211 | - 如果类内定义了成员的初始值,则使用内类定义的初始值; 212 | - 如果没有定义类内的初始值,则以默认方式初始化; 213 | - 基本类型的数据默认初始化的值是不确定的。 214 | 215 | ### “=default” 216 | - 如果程序中未定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。如果此时依然希望编译器隐含生成默认构造函数,可以使用“```=default```”。 217 | - 例如 218 | ``` cpp 219 | class Clock { 220 | public: 221 | Clock() =default; //指示编译器提供默认构造函数 222 | Clock(int newH, int newM, int newS); //构造函数 223 | private: 224 | int hour, minute, second; 225 | }; 226 | ``` 227 | 228 | 229 | 230 | ## 构造函数例题(1) 231 | ### 例4_1修改版1 232 | ``` cpp 233 | //类定义 234 | class Clock { 235 | public: 236 | Clock(int newH,int newM,int newS);//构造函数 237 | void setTime(int newH, int newM, int newS); 238 | void showTime(); 239 | private: 240 | int hour, minute, second; 241 | }; 242 | 243 | //构造函数的实现: 244 | Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM), second(newS) { 245 | } 246 | //其它函数实现同例4_1 247 | 248 | int main() { 249 | Clock c(0,0,0); //自动调用构造函数 250 | c.showTime(); 251 | return 0; 252 | } 253 | ``` 254 | 255 | ## 构造函数例题(2) 256 | ### 例4_1修改版2 257 | ``` cpp 258 | class Clock { 259 | public: 260 | Clock(int newH, int newM, int newS); //构造函数 261 | Clock(); //默认构造函数 262 | void setTime(int newH, int newM, int newS); 263 | void showTime(); 264 | private: 265 | int hour, minute, second; 266 | }; 267 | Clock::Clock(): hour(0),minute(0),second(0) { }//默认构造函数 268 | //其它函数实现同前 269 | 270 | int main() { 271 | Clock c1(0, 0, 0); //调用有参数的构造函数 272 | Clock c2; //调用无参数的构造函数 273 | …… 274 | } 275 | ``` 276 | 277 | ## 委托构造函数 278 | 类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时,为了避免代码重复,可以使用委托构造函数。 279 | ### 回顾 280 | Clock类的两个构造函数: 281 | ``` cpp 282 | Clock(int newH, int newM, int newS) : hour(newH),minute(newM), second(newS) { //构造函数 283 | } 284 | Clock::Clock(): hour(0),minute(0),second(0) { }//默认构造函数 285 | ``` 286 | 287 | ### 委托构造函数 288 | - 委托构造函数使用类的其他构造函数执行初始化过程 289 | - 例如: 290 | ``` cpp 291 | Clock(int newH, int newM, int newS): hour(newH),minute(newM), second(newS){ 292 | } 293 | Clock(): Clock(0, 0, 0) { } 294 | ``` 295 | 296 | ## 复制构造函数 297 | ### 复制构造函数定义 298 | 复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象。 299 | ``` cpp 300 | class 类名 { 301 | public : 302 | 类名(形参);//构造函数 303 | 类名(const 类名 &对象名);//复制构造函数 304 | // ... 305 | }; 306 | 类名::类( const 类名 &对象名)//复制构造函数的实现 307 | { 函数体 } 308 | ``` 309 | 310 | ### 隐含的复制构造函数 311 | - 如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数。 312 | - 这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。 313 | 314 | ### “=delete” 315 | - 如果不希望对象被复制构造 316 | - C++98做法:将复制构造函数声明为```private```,并且不提供函数的实现。 317 | - C++11做法:用“```=delete```”指示编译器不生成默认复制构造函数。 318 | - 例: 319 | ``` cpp 320 | class Point { //Point 类的定义 321 | public: 322 | Point(int xx=0, int yy=0) { x = xx; y = yy; } //构造函数,内联 323 | Point(const Point& p) =delete; //指示编译器不生成默认复制构造函数 324 | private: 325 | int x, y; //私有数据 326 | }; 327 | ``` 328 | 329 | ### 复制构造函数被调用的三种情况 330 | - 定义一个对象时,以本类另一个对象作为初始值,发生复制构造; 331 | - 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造; 332 | - 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用```return```语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。 333 | - 这种情况也可以通过移动构造避免不必要的复制(第6章介绍) 334 | 335 | 336 | 337 | ## 例4-2 Point类的完整程序 338 | ``` cpp 339 | class Point { //Point 类的定义 340 | public: 341 | Point(int xx=0, int yy=0) { x = xx; y = yy; } //构造函数,内联 342 | Point(const Point& p); //复制构造函数 343 | void setX(int xx) {x=xx;} 344 | void setY(int yy) {y=yy;} 345 | int getX() const { return x; } //常函数(第5章) 346 | int getY() const { return y; } //常函数(第5章) 347 | private: 348 | int x, y; //私有数据 349 | }; 350 | 351 | //复制构造函数的实现 352 | Point::Point (const Point& p) { 353 | x = p.x; 354 | y = p.y; 355 | cout << "Calling the copy constructor " << endl; 356 | } 357 |   358 | //形参为Point类对象void fun1(Point p) { 359 | cout << p.getX() << endl; 360 | } 361 | 362 | //返回值为Point类对象Point fun2() { 363 | Point a(1, 2); 364 | return a; 365 | } 366 | int main() { 367 | Point a(4, 5); 368 | Point b(a); //用a初始化b。 369 | cout << b.getX() << endl; 370 | fun1(b); //对象b作为fun1的实参 371 | b = fun2(); //函数的返回值是类对象 372 | cout << b.getX() << endl; 373 | return 0; 374 | } 375 | ``` 376 | 377 | ## 析构函数 378 | - 完成对象被删除前的一些清理工作。 379 | -- 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。 380 | - 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数,其函数体为空。 381 | - 构造函数和析构函数举例 382 | ``` cpp 383 | #include 384 | using namespace std; 385 | class Point { 386 | public: 387 | Point(int xx,int yy); 388 | ~Point(); 389 | //...其他函数原型 390 | private: 391 | int x, y; 392 | }; 393 | ``` 394 | 395 | ## 类的组合 396 | ### 组合的概念 397 | - 类中的成员是另一个类的对象。 398 | - 可以在已有抽象的基础上实现更复杂的抽象。 399 | 400 | ### 类组合的构造函数设计 401 | - 原则:不仅要负责对本类中的基本类型成员数据初始化,也要对对象成员初始化。 402 | - 声明形式: 403 | ``` cpp 404 | 类名::类名(对象成员所需的形参,本类成员形参) 405 | :对象1(参数),对象2(参数),...... 406 | { 407 | //函数体其他语句 408 | } 409 | ``` 410 | ### 构造组合类对象时的初始化次序 411 | - 首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序。 412 | - 成员对象构造函数调用顺序:按对象成员的声明顺序,先声明者先构造。 413 | - 初始化列表中未出现的成员对象,调用用默认构造函数(即无形参的)初始化 414 | - 处理完初始化列表之后,再执行构造函数的函数体。 415 | 416 | 417 | 418 | ## 类组合程序举例 419 | - 例4-4 类的组合,线段(Line)类 420 | ``` cpp 421 | //4_4.cpp 422 | #include 423 | #include 424 | using namespace std; 425 | class Point { //Point类定义 426 | public: 427 | Point(int xx = 0, int yy = 0) { 428 | x = xx; 429 | y = yy; 430 | } 431 | Point(Point &p); 432 | int getX() { return x; } 433 | int getY() { return y; } 434 | private: 435 | int x, y; 436 | }; 437 | 438 | Point::Point(Point &p) { //复制构造函数的实现 439 | x = p.x; 440 | y = p.y; 441 | cout << "Calling the copy constructor of Point" << endl; 442 | } 443 | 444 | //类的组合 445 | class Line { //Line类的定义 446 | public: //外部接口 447 | Line(Point xp1, Point xp2); 448 | Line(Line &l); 449 | double getLen() { return len; } 450 | private: //私有数据成员 451 | Point p1, p2; //Point类的对象p1,p2 452 | double len; 453 | }; 454 | 455 | //组合类的构造函数 456 | Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) { 457 | cout << "Calling constructor of Line" << endl; 458 | double x = static_cast(p1.getX() - p2.getX()); 459 | double y = static_cast(p1.getY() - p2.getY()); 460 | len = sqrt(x * x + y * y); 461 | } 462 | Line::Line (Line &l): p1(l.p1), p2(l.p2) {//组合类的复制构造函数 463 | cout << "Calling the copy constructor of Line" << endl; 464 | len = l.len; 465 | } 466 | 467 | //主函数 468 | int main() { 469 | Point myp1(1, 1), myp2(4, 5); //建立Point类的对象 470 | Line line(myp1, myp2); //建立Line类的对象 471 | Line line2(line); //利用复制构造函数建立一个新对象 472 | cout << "The length of the line is: "; 473 | cout << line.getLen() << endl; 474 | cout << "The length of the line2 is: "; 475 | cout << line2.getLen() << endl; 476 | return 0; 477 | } 478 | ``` 479 | 480 | 481 | ## 前向引用声明 482 | - 类应该先声明,后使用 483 | - 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。 484 | - 前向引用声明只为程序引入一个标识符,但具体声明在其他地方。 485 | - 例: 486 | ``` cpp 487 | class B; //前向引用声明 488 | class A { 489 | public: 490 | void f(B b); 491 | }; 492 | class B { 493 | public: 494 | void g(A a); 495 | }; 496 | ``` 497 | 498 | ### 前向引用声明注意事项 499 | - 使用前向引用声明虽然可以解决一些问题,但它并不是万能的。 500 | - 在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。 501 | - 当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。 502 | - 例 503 | ``` cpp 504 | class Fred; //前向引用声明 505 | class Barney { 506 | Fred x; //错误:类Fred的声明尚不完善 507 | }; 508 | class Fred { 509 | Barney y; 510 | }; 511 | ``` 512 | 513 | ## 结构体 514 | - 结构体是一种特殊形态的类 515 | - 与类的唯一区别:类的缺省访问权限是```private```,结构体的缺省访问权限是```public``` 516 | - 结构体存在的主要原因:与C语言保持兼容 517 | - 什么时候用结构体而不用类 518 | - 定义主要用来保存数据、而没有什么操作的类型 519 | - 人们习惯将结构体的数据成员设为公有,因此这时用结构体更方便 520 | 521 | ### 结构体的定义 522 | ``` cpp 523 | struct 结构体名称 { 524 | 公有成员 525 | protected: 526 | 保护型成员 527 | private: 528 | 私有成员 529 | }; 530 | ``` 531 | ### 结构体的初始化 532 | - 如果一个结构体的全部数据成员都是公共成员,并且没有用户定义的构造函数,没有基类和虚函数(基类和虚函数将在后面的章节中介绍),这个结构体的变量可以用下面的语法形式赋初值 533 | ``` cpp 534 | 类型名 变量名 = { 成员数据1初值, 成员数据2初值, …… }; 535 | ``` 536 | 537 | ### 例4-7用结构体表示学生的基本信息 538 | ``` cpp 539 | #include 540 | #include 541 | #include 542 | using namespace std; 543 | 544 | struct Student { //学生信息结构体 545 | int num; //学号 546 | string name; //姓名,字符串对象,将在第6章详细介绍 547 | char sex; //性别 548 | int age; //年龄 549 | }; 550 | 551 | int main() { 552 | Student stu = { 97001, "Lin Lin", 'F', 19 }; 553 | cout << "Num: " << stu.num << endl; 554 | cout << "Name: " << stu.name << endl; 555 | cout << "Sex: " << stu.sex << endl; 556 | cout << "Age: " << stu.age << endl; 557 | return 0; 558 | } 559 | ``` 560 | 运行结果: 561 | ``` cpp 562 | Num: 97001 563 | Name: Lin Lin 564 | Sex: F 565 | Age: 19 566 | ``` 567 | 568 | 569 | ## 联合体 570 | ### 声明形式 571 | ``` cpp 572 | union 联合体名称 { 573 | 公有成员 574 | protected: 575 | 保护型成员 576 | private: 577 | 私有成员 578 | }; 579 | ``` 580 | 581 | ### 特点: 582 | - 成员共用同一组内存单元 583 | - 任何两个成员不会同时有效 584 | 585 | ### 联合体的内存分配 586 | - 举例说明: 587 | ``` cpp 588 | union Mark { //表示成绩的联合体 589 | char grade; //等级制的成绩 590 | bool pass; //只记是否通过课程的成绩 591 | int percent; //百分制的成绩 592 | }; 593 | ``` 594 | 595 | ![联合体的内存分配](../pics/C4/联合体的内存分配.png) 596 | 597 | ### 无名联合 598 | - 例: 599 | ``` cpp 600 | union { 601 | int i; 602 | float f; 603 | } 604 | ``` 605 | 在程序中可以这样使用: 606 | ``` cpp 607 | i = 10; 608 | f = 2.2; 609 | ``` 610 | 611 | ### 例4-8使用联合体保存成绩信息,并且输出。 612 | ``` cpp 613 | #include 614 | #include 615 | using namespace std; 616 | class ExamInfo { 617 | private: 618 | string name; //课程名称 619 | enum { GRADE, PASS, PERCENTAGE } mode;//计分方式 620 | union { 621 | char grade; //等级制的成绩 622 | bool pass; //只记是否通过课程的成绩 623 | int percent; //百分制的成绩 624 | }; 625 | public: 626 | //三种构造函数,分别用等级、是否通过和百分初始化 627 | ExamInfo(string name, char grade) 628 | : name(name), mode(GRADE), grade(grade) { } 629 | ExamInfo(string name, bool pass) 630 | : name(name), mode(PASS), pass(pass) { } 631 | ExamInfo(string name, int percent) 632 | : name(name), mode(PERCENTAGE), percent(percent) { } 633 | void show(); 634 | } 635 | 636 | void ExamInfo::show() { 637 | cout << name << ": "; 638 | switch (mode) { 639 | case GRADE: cout << grade; break; 640 | case PASS: cout << (pass ? "PASS" : "FAIL"); break; 641 | case PERCENTAGE: cout << percent; break; 642 | } 643 | cout << endl; 644 | } 645 | 646 | int main() { 647 | ExamInfo course1("English", 'B'); 648 | ExamInfo course2("Calculus", true); 649 | ExamInfo course3("C++ Programming", 85); 650 | course1.show(); 651 | course2.show(); 652 | course3.show(); 653 | return 0; 654 | } 655 | ``` 656 | 运行结果: 657 | ``` cpp 658 | English: B 659 | Calculus: PASS 660 | C++ Programming: 85 661 | ``` 662 | 663 | 664 | 665 | ## 枚举类 666 | ### 枚举类定义 667 | - 语法形式 668 | ``` cpp 669 | enum class 枚举类型名: 底层类型 {枚举值列表}; 670 | ``` 671 | - 例: 672 | ``` cpp 673 | enum class Type { General, Light, Medium, Heavy}; 674 | enum class Type: char { General, Light, Medium, Heavy}; 675 | enum class Category { General=1, Pistol, MachineGun, Cannon}; 676 | ``` 677 | ### 枚举类的优势 678 | - 强作用域,其作用域限制在枚举类中。 679 | - 例:使用```Type```的枚举值```General```: 680 | ``` cpp 681 | Type::General 682 | ``` 683 | - 转换限制,枚举类对象不可以与整型隐式地互相转换。 684 | - 可以指定底层类型 685 | - 例: 686 | ``` cpp 687 | enum class Type: char { General, Light, Medium, Heavy}; 688 | ``` 689 | 690 | ### 例4-9 枚举类举例 691 | ``` cpp 692 | #include 693 | using namespace std; 694 | enum class Side{ Right, Left }; 695 | enum class Thing{ Wrong, Right }; //不冲突 696 | int main() 697 | { 698 | Side s = Side::Right; 699 | Thing w = Thing::Wrong; 700 | cout << (s == w) << endl; //编译错误,无法直接比较不同枚举类 701 | return 0; 702 | } 703 | ``` 704 | 705 | ## 小结 706 | 707 | ### 主要内容 708 | - 面向对象的基本概念、类和对象的声明、构造函数、析构函数、内联成员函数、复制构造函数、类的组合 709 | 710 | ### 达到的目标 711 | - 掌握面向对象的基本概念; 712 | - 掌握类设计的思想、类和对象声明的语法; 713 | - 理解构造函数、复制构造函数和析构函数的作用和调用过程,掌握相关的语法; 714 | - 理解内联成员函数的作用,掌握相关语法; 715 | - 理解类的组合在面向对象设计中的意义,掌握类组合的语法。 716 | - 了解枚举类 717 | 718 | 719 | -------------------------------------------------------------------------------- /handout/第7章.md: -------------------------------------------------------------------------------- 1 | # 第七章 类的继承 2 | 3 | 4 | ## 本章主要内容 5 | - 继承与派生的基本概念 6 | - 单继承与多继承 7 | - 类成员的访问控制 8 | - 派生类对象的构造和析构 9 | - 派生类与基类对象的类型转换 10 | - 类成员的标识与访问 11 | - 虚继承 12 | 13 | 14 | ## 继承的基本概念和语法 15 | ### 继承与派生概述 16 | - 继承与派生是同一过程从不同的角度看 17 | - 保持已有类的特性而构造新类的过程称为**继承** 18 | - 在已有类的基础上新增自己的特性而产生新类的过程称为**派生**。 19 | - 被继承的已有类称为**基类**(或父类) 20 | - 派生出的新类称为**派生类**(或子类) 21 | - 直接参与派生出某类的基类称为**直接基类** 22 | - 基类的基类甚至更高层的基类称为**间接基类** 23 | 24 | ### 继承与派生的目的 25 | - 继承的目的:实现设计与代码的重用。 26 | - 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。 27 | 28 | ### 单继承时派生类的定义 29 | - 语法 30 | ``` cpp 31 | class 派生类名:继承方式 基类名 32 | { 33 | 成员声明; 34 | } 35 | ``` 36 | - 例 37 | ``` cpp 38 | class Derived: public Base 39 | { 40 | public: 41 | Derived (); 42 | ~Derived (); 43 | }; 44 | ``` 45 | 46 | ### 多继承时派生类的定义 47 | - 语法 48 | ``` cpp 49 | class 派生类名:继承方式1 基类名1,继承方式2 基类名2,... 50 | { 51 | 成员声明; 52 | } 53 | ``` 54 | **注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。** 55 | 56 | - 例 57 | ``` cpp 58 | class Derived: public Base1, private Base2 59 | { 60 | public: 61 | Derived (); 62 | ~Derived (); 63 | }; 64 | ``` 65 | 66 | ### 派生类的构成 67 | - 吸收基类成员 68 | - 默认情况下派生类包含了全部基类中除构造和析构函数之外的所有成员。 69 | - C++11规定可以用using语句继承基类构造函数。 70 | - 改造基类成员 71 | - 如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就隐藏或覆盖了外层同名成员 72 | - 添加新的成员 73 | - 派生类增加新成员使派生类在功能上有所发展 74 | 75 | 76 | ## 继承方式简介及公有继承 77 | ### 不同继承方式的影响主要体现在: 78 | - 派生类成员对基类成员的访问权限 79 | - 通过派生类对象对基类成员的访问权限 80 | 81 | ### 三种继承方式 82 | - 公有继承 83 | - 私有继承 84 | - 保护继承 85 | 86 | ### 公有继承(public) 87 | - 继承的访问控制 88 | - **基类的```public```和```protected```成员:访问属性在派生类中保持不变**; 89 | - **基类的```private```成员:不可直接访问**。 90 | - 访问权限 91 | - 派生类中的成员函数:可以直接访问基类中的```public```和```protected```成员,但不能直接访问基类的```private```成员; 92 | - 通过派生类的对象:只能访问```public```成员。 93 | 94 | ### 例7-1 公有继承举例 95 | ``` cpp 96 | //Point.h 97 | #ifndef _POINT_H 98 | #define _POINT_H 99 | class Point { //基类Point类的定义 100 | public: //公有函数成员 101 | void initPoint(float x = 0, float y = 0) 102 | { this->x = x; this->y = y;} 103 | void move(float offX, float offY) 104 | { x += offX; y += offY; } 105 | float getX() const { return x; } 106 | float getY() const { return y; } 107 | private: //私有数据成员 108 | float x, y; 109 | }; 110 | #endif //_POINT_H 111 | 112 | //Rectangle.h 113 | #ifndef _RECTANGLE_H 114 | #define _RECTANGLE_H 115 | #include "Point.h" 116 | class Rectangle: public Point { //派生类定义部分 117 | public: //新增公有函数成员 118 | void initRectangle(float x, float y, float w, float h) { 119 | initPoint(x, y); //调用基类公有成员函数 120 | this->w = w; 121 | this->h = h; 122 | } 123 | float getH() const { return h; } 124 | float getW() const { return w; } 125 | private: //新增私有数据成员 126 | float w, h; 127 | }; 128 | #endif //_RECTANGLE_H 129 | 130 | #include 131 | #include 132 | using namespace std; 133 | #include “Rectangle.h” 134 | int main() { 135 | Rectangle rect; //定义Rectangle类的对象 136 | //设置矩形的数据 137 | rect.initRectangle(2, 3, 20, 10); 138 | rect.move(3,2); //移动矩形位置 139 | cout << "The data of rect(x,y,w,h): " << endl; 140 | //输出矩形的特征参数 141 | cout << rect.getX() <<", " 142 | << rect.getY() << ", " 143 | << rect.getW() << ", " 144 | << rect.getH() << endl; 145 | return 0; 146 | } 147 | ``` 148 | 149 | 150 | ## 私有继承和保护继承 151 | ### 私有继承(private) 152 | - 继承的访问控制 153 | - **基类的```public```和```protected```成员:都以```private```身份出现在派生类中**; 154 | - **基类的```private```成员:不可直接访问**。 155 | - 访问权限 156 | - 派生类中的成员函数:可以直接访问基类中的```public```和```protected```成员,但不能直接访问基类的```private```成员; 157 | - 通过派生类的对象:不能直接访问从基类继承的任何成员。 158 | 159 | ### 例7-2 私有继承举例 160 | ``` cpp 161 | //Point.h 162 | #ifndef _POINT_H 163 | #define _POINT_H 164 | class Point { //基类Point类的定义 165 | public: //公有函数成员 166 | void initPoint(float x = 0, float y = 0) 167 | { this->x = x; this->y = y;} 168 | void move(float offX, float offY) 169 | { x += offX; y += offY; } 170 | float getX() const { return x; } 171 | float getY() const { return y; } 172 | private: //私有数据成员 173 | float x, y; 174 | }; 175 | #endif //_POINT_H 176 | 177 | //Rectangle.h 178 | #ifndef _RECTANGLE_H 179 | #define _RECTANGLE_H 180 | #include "Point.h" 181 | class Rectangle: private Point { //派生类定义部分 182 | public: //新增公有函数成员 183 | void initRectangle(float x, float y, float w, float h) { 184 | initPoint(x, y); //调用基类公有成员函数 185 | this->w = w; 186 | this->h = h; 187 | } 188 | void move(float offX, float offY) { Point::move(offX, offY); } 189 | float getX() const { return Point::getX(); } 190 | float getY() const { return Point::getY(); } 191 | float getH() const { return h; } 192 | float getW() const { return w; } 193 | private: //新增私有数据成员 194 | float w, h; 195 | }; 196 | #endif //_RECTANGLE_H 197 | 198 | #include 199 | #include 200 | using namespace std; 201 | 202 | int main() { 203 | Rectangle rect; //定义Rectangle类的对象 204 | rect.initRectangle(2, 3, 20, 10); //设置矩形的数据 205 | rect.move(3,2); //移动矩形位置 206 | cout << "The data of rect(x,y,w,h): " << endl; 207 | cout << rect.getX() <<", " //输出矩形的特征参数 208 | << rect.getY() << ", " 209 | << rect.getW() << ", " 210 | << rect.getH() << endl; 211 | return 0; 212 | } 213 | ``` 214 | 215 | ### 保护继承(protected) 216 | - 继承的访问控制 217 | - **基类的```public```和```protected```成员:都以```protected```身份出现在派生类中**; 218 | - **基类的```private```成员:不可直接访问**。 219 | - 访问权限 220 | - 派生类中的成员函数:可以直接访问基类中的```public```和```protected```成员,但不能直接访问基类的```private```成员; 221 | - 通过派生类的对象:不能直接访问从基类继承的任何成员。 222 | 223 | ### protected 成员的特点与作用 224 | - 对建立其所在类对象的模块来说,它与```private```成员的性质相同。 225 | - 对于其派生类来说,它与```public```成员的性质相同。 226 | - 既实现了数据隐藏,又方便继承,实现代码重用。 227 | 228 | ### protected 成员举例(补7-1) 229 | ``` cpp 230 | class A { 231 | protected: 232 | int x; 233 | }; 234 | 235 | int main() { 236 | A a; 237 | a.x = 5; //错误 238 | } 239 | class A { 240 | protected: 241 | int x; 242 | }; 243 | class B: public A{ 244 | public: 245 | void function(); 246 | }; 247 | void B:function() { 248 | x = 5; //正确 249 | } 250 | ``` 251 | 252 | - 如果派生类有多个基类,也就是多继承时,可以用不同的方式继承每个基类。 253 | 254 | ### 多继承举例(补7-2) 255 | ``` cpp 256 | class A { 257 | public: 258 | void setA(int); 259 | void showA() const; 260 | private: 261 | int a; 262 | }; 263 | class B { 264 | public: 265 | void setB(int); 266 | void showB() const; 267 | private: 268 | int b; 269 | }; 270 | class C : public A, private B { 271 | public: 272 | void setC(int, int, int); 273 | void showC() const; 274 | private const: 275 | int c; 276 | }; 277 | 278 | void A::setA(int x) { 279 | a=x; 280 | } 281 | void B::setB(int x) { 282 | b=x; 283 | } 284 | void C::setC(int x, int y, int z) { 285 | //派生类成员直接访问基类的 286 | //公有成员 287 | setA(x); 288 | setB(y); 289 | c = z; 290 | } 291 | //其他函数实现略 292 | int main() { 293 | C obj; 294 | obj.setA(5); 295 | obj.showA(); 296 | obj.setC(6,7,9); 297 | obj.showC(); 298 | // obj.setB(6); 错误 299 | // obj.showB(); 错误 300 | return 0; 301 | } 302 | ``` 303 | 304 | 305 | ## 类型转换 306 | - 公有派生类对象可以被当作基类的对象使用,反之则不可。 307 | - 派生类的对象可以隐含转换为基类对象; 308 | - 派生类的对象可以初始化基类的引用; 309 | - 派生类的指针可以隐含转换为基类的指针。 310 | - 通过基类对象名、指针只能使用从基类继承的成员。 311 | 312 | ### 例7-3 类型转换规则举例 313 | ``` cpp 314 | #include 315 | using namespace std; 316 | class Base1 { //基类Base1定义 317 | public: 318 | void display() const { 319 | cout << "Base1::display()" << endl; 320 | } 321 | }; 322 | class Base2: public Base1 { //公有派生类Base2定义 323 | public: 324 | void display() const { 325 | cout << "Base2::display()" << endl; 326 | } 327 | }; 328 | class Derived: public Base2 { //公有派生类Derived定义 329 | public: 330 | void display() const { 331 | cout << "Derived::display()" << endl; 332 | } 333 | }; 334 | 335 | void fun(Base1 *ptr) { //参数为指向基类对象的指针 336 | ptr->display(); //"对象指针->成员名" 337 | } 338 | int main() { //主函数 339 | Base1 base1; //声明Base1类对象 340 | Base2 base2; //声明Base2类对象 341 | Derived derived; //声明Derived类对象 342 | 343 | fun(&base1); //用Base1对象的指针调用fun函数 344 | fun(&base2); //用Base2对象的指针调用fun函数 345 | fun(&derived); //用Derived对象的指针调用fun函数 346 | 347 | return 0; 348 | } 349 | ``` 350 | 351 | ## 派生类的构造函数 352 | 353 | ### 默认情况 354 | - 基类的构造函数不被继承; 355 | - 派生类需要定义自己的构造函数。 356 | 357 | ### C++11规定 358 | - 可用using语句继承基类构造函数。 359 | - 但是只能初始化从基类继承的成员。 360 | - 派生类新增成员可以通过类内初始值进行初始化。 361 | - 语法形式: 362 | - ```cpp using B::B;``` 363 | 364 | ### 建议 365 | - 如果派生类有自己新增的成员,且需要通过构造函数初始化,则派生类要自定义构造函数。 366 | 367 | ### 若不继承基类的构造函数 368 | - 派生类新增成员:派生类定义构造函数初始化; 369 | - 继承来的成员:自动调用基类构造函数进行初始化; 370 | - 派生类的构造函数需要给基类的构造函数**传递参数**。 371 | 372 | ### 单继承 373 | - 派生类只有一个直接基类的情况,是单继承。单继承时,派生类的构造函数只需要给一个直接基类构造函数传递参数。 374 | 375 | ### 单继承时构造函数的定义语法 376 | ``` cpp 377 | 派生类名::派生类名(基类所需的形参,本类成员所需的形参): 378 | 基类名(参数表), 本类成员初始化列表 379 | { 380 | //其他初始化; 381 | }; 382 | ``` 383 | 384 | ### 单继承时的构造函数举例(补7-3) 385 | ``` cpp 386 | #include 387 | using namespace std; 388 | class B { 389 | public: 390 | B(); 391 | B(int i); 392 | ~B(); 393 | void print() const; 394 | private: 395 | int b; 396 | }; 397 | 398 | B::B() { 399 | b=0; 400 | cout << "B's default constructor called." << endl; 401 | } 402 | B::B(int i) { 403 | b=i; 404 | cout << "B's constructor called." << endl; 405 | } 406 | B::~B() { 407 | cout << "B's destructor called." << endl; 408 | } 409 | void B::print() const { 410 | cout << b << endl; 411 | } 412 | 413 | class C: public B { 414 | public: 415 | C(); 416 | C(int i, int j); 417 | ~C(); 418 | void print() const; 419 | private: 420 | int c; 421 | }; 422 | C::C() { 423 | c = 0; 424 | cout << "C's default constructor called." << endl; 425 | } 426 | C::C(int i,int j): B(i), c(j){ 427 | cout << "C's constructor called." << endl; 428 | } 429 | 430 | C::~C() { 431 | cout << "C's destructor called." << endl; 432 | } 433 | void C::print() const { 434 | B::print(); 435 | cout << c << endl; 436 | } 437 | 438 | int main() { 439 | C obj(5, 6); 440 | obj.print(); 441 | return 0; 442 | } 443 | ``` 444 | 445 | ### 多继承 446 | - 多继承时,有多个直接基类,如果不继承基类的构造函数,派生类构造函数需要给所有基类构造函数传递参数。我们来看一下语法规定 447 | 448 | ### 多继承时构造函数的定义语法 449 | ``` cpp 450 | 派生类名::派生类名(参数表) : 451 | 基类名1(基类1初始化参数表), 452 | 基类名2(基类2初始化参数表), 453 | ... 454 | 基类名n(基类n初始化参数表), 455 | 本类成员初始化列表 456 | { 457 | //其他初始化; 458 | }; 459 | ``` 460 | 461 | ### 派生类与基类的构造函数 462 | - 当基类有默认构造函数时 463 | - 派生类构造函数可以不向基类构造函数传递参数。 464 | - 构造派生类的对象时,基类的默认构造函数将被调用。 465 | - 如需执行基类中带参数的构造函数 466 | - 派生类构造函数应为基类构造函数提供参数。 467 | 468 | ### 多继承且有对象成员时派生的构造函数定义语法 469 | ``` cpp 470 | 派生类名::派生类名(形参表): 471 | 基类名1(参数), 基类名2(参数), ..., 基类名n(参数), 472 | 本类成员(含对象成员)初始化列表 473 | { 474 | //其他初始化 475 | }; 476 | ``` 477 | 478 | ### 构造函数的执行顺序 479 | 1. 调用基类构造函数。 480 | - 顺序按照它们被继承时声明的顺序(从左向右)。 481 | 2. 对初始化列表中的成员进行初始化。 482 | - 顺序按照它们在类中定义的顺序。 483 | - 对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。 484 | 3. 执行派生类的构造函数体中的内容。 485 | 486 | 487 | ## 派生类构造函数举例 488 | ### 例7-4 派生类构造函数举例 489 | ``` cpp 490 | #include 491 | using namespace std; 492 | class Base1 {//基类Base1,构造函数有参数 493 | public: 494 | Base1(int i) 495 | { cout << "Constructing Base1 " << i << endl; } 496 | }; 497 | class Base2 {//基类Base2,构造函数有参数 498 | public: 499 | Base2(int j) 500 | { cout << "Constructing Base2 " << j << endl; } 501 | }; 502 | class Base3 {//基类Base3,构造函数无参数 503 | public: 504 | Base3() 505 | { cout << "Constructing Base3 *" << endl; } 506 | }; 507 | 508 | class Derived: public Base2, public Base1, public Base3 { 509 | public: 510 | Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) 511 | //此处的次序与构造函数的执行次序无关 512 | { } 513 | private: 514 | Base1 member1; 515 | Base2 member2; 516 | Base3 member3; 517 | }; 518 | 519 | int main() { 520 | Derived obj(1, 2, 3, 4); 521 | return 0; 522 | } 523 | ``` 524 | 525 | 526 | ## 派生类复制构造函数 527 | ### 派生类未定义复制构造函数的情况 528 | - 编译器会在需要时生成一个隐含的复制构造函数; 529 | - 先调用基类的复制构造函数; 530 | - 再为派生类新增的成员执行复制。 531 | 532 | ### 派生类定义了复制构造函数的情况 533 | - 一般都要为基类的复制构造函数传递参数。 534 | - 复制构造函数只能接受一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。 535 | - 基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用 536 | - 例如: 537 | ``` cpp 538 | C::C(const C &c1): B(c1) {…} 539 | ``` 540 | 541 | 542 | ## 派生类的析构函数 543 | - 析构函数不被继承,派生类如果需要,要自行声明析构函数。 544 | - 声明方法与无继承关系时类的析构函数相同。 545 | - 不需要显式地调用基类的析构函数,系统会自动隐式调用。 546 | - 先执行派生类析构函数的函数体,再调用基类的析构函数。 547 | 548 | ### 例7-5 派生类对象析构举例 549 | ``` cpp 550 | #include 551 | using namespace std; 552 | class Base1 { 553 | public: 554 | Base1(int i) 555 | { cout << "Constructing Base1 " << i << endl; } 556 | ~Base1() { cout << "Destructing Base1" << endl; } 557 | }; 558 | class Base2 { 559 | public: 560 | Base2(int j) 561 | { cout << "Constructing Base2 " << j << endl; } 562 | ~Base2() { cout << "Destructing Base2" << endl; } 563 | }; 564 | class Base3 { 565 | public: 566 | Base3() { cout << "Constructing Base3 *" << endl; } 567 | ~Base3() { cout << "Destructing Base3" << endl; } 568 | }; 569 | 570 | class Derived: public Base2, public Base1, public Base3 { 571 | public: 572 | Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) 573 | { } 574 | private: 575 | Base1 member1; 576 | Base2 member2; 577 | Base3 member3; 578 | }; 579 | 580 | int main() { 581 | Derived obj(1, 2, 3, 4); 582 | return 0; 583 | } 584 | ``` 585 | 586 | ## 访问从基类继承的成员 587 | ### 作用域限定 588 | 当派生类与基类中有相同成员时: 589 | - 若未特别限定,则通过派生类对象使用的是派生类中的同名成员。 590 | - 如要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名和作用域操作符(::)来限定。 591 | 592 | ### 例7-6 多继承同名隐藏举例 593 | ``` cpp 594 | #include 595 | using namespace std; 596 | class Base1 { 597 | public: 598 | int var; 599 | void fun() { cout << "Member of Base1" << endl; } 600 | }; 601 | class Base2 { 602 | public: 603 | int var; 604 | void fun() { cout << "Member of Base2" << endl; } 605 | }; 606 | class Derived: public Base1, public Base2 { 607 | public: 608 | int var; 609 | void fun() { cout << "Member of Derived" << endl; } 610 | }; 611 | 612 | int main() { 613 | Derived d; 614 | Derived *p = &d; 615 | 616 | //访问Derived类成员 617 | d.var = 1; 618 | d.fun(); 619 | 620 | //访问Base1基类成员 621 | d.Base1::var = 2; 622 | d.Base1::fun(); 623 | 624 | //访问Base2基类成员 625 | p->Base2::var = 3; 626 | p->Base2::fun(); 627 | 628 | return 0; 629 | } 630 | ``` 631 | 632 | ### 二义性问题 633 | - 如果从不同基类继承了同名成员,但是在派生类中没有定义同名成员,“**派生类对象名或引用名.成员名**”、“**派生类指针->成员名**”访问成员存在二义性问题 634 | - 解决方式:用类名限定 635 | 636 | ### 二义性问题举例 637 | ``` cpp 638 | class A { 639 | public: 640 | void f(); 641 | }; 642 | class B { 643 | public: 644 | void f(); 645 | void g() 646 | }; 647 | class C: public A, piblic B { 648 | public: 649 | void g(); 650 | void h(); 651 | }; 652 | ``` 653 | 如果定义:```C c1```; 654 | 则 ```c1.f()``` 具有二义性, 655 | 而 ```c1.g()``` 无二义性(同名隐藏)。 656 | 657 | ### 例7-7 多继承时的二义性和冗余问题 658 | ``` cpp 659 | //7_7.cpp 660 | #include 661 | using namespace std; 662 | class Base0 { //定义基类Base0 663 | public: 664 | int var0; 665 | void fun0() { cout << "Member of Base0" << endl; } 666 | }; 667 | class Base1: public Base0 { //定义派生类Base1 668 | public: //新增外部接口 669 | int var1; 670 | }; 671 | class Base2: public Base0 { //定义派生类Base2 672 | public: //新增外部接口 673 | int var2; 674 | }; 675 | 676 | class Derived: public Base1, public Base2 { 677 | public: 678 | int var; 679 | void fun() 680 | { cout << "Member of Derived" << endl; } 681 | }; 682 |   683 | int main() { //程序主函数 684 | Derived d; 685 | d.Base1::var0 = 2; 686 | d.Base1::fun0(); 687 | d.Base2::var0 = 3; 688 | d.Base2::fun0(); 689 | return 0; 690 | } 691 | ``` 692 | - Derived类对象d的存储结构示意图示意图 693 | ![图片1](../pics/C7/Derived类对象d的存储结构示意图示意图.png) 694 | 695 | ## 虚基类 696 | - 需要解决的问题 697 | - 当派生类从多个基类派生,而这些基类又共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性 698 | - 虚基类声明 699 | - 以virtual说明基类继承方式 700 | - 例: 701 | ``` cpp 702 | class B1:virtual public B 703 | ``` 704 | - 作用 705 | - 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题 706 | - 为最远的派生类提供唯一的基类成员,而不重复产生多次复制 707 | - 注意: 708 | - 在第一级继承时就要将共同基类设计为虚基类。 709 | 710 | ### 例7-8 虚基类举例 711 | ![图片2](../pics/C7/虚基类举例.png) 712 | ``` cpp 713 | #include 714 | using namespace std; 715 | class Base0 { 716 | public: 717 | int var0; 718 | void fun0() { cout << "Member of Base0" << endl; } 719 | }; 720 | class Base1: virtual public Base0 { 721 | public: 722 | int var1; 723 | }; 724 | class Base2: virtual public Base0 { 725 | public: 726 | int var2; 727 | }; 728 | 729 | class Derived: public Base1, public Base2 { 730 | //定义派生类Derived 731 | public: 732 | int var; 733 | void fun() { 734 | cout << "Member of Derived" << endl; 735 | } 736 | }; 737 | 738 | int main() { 739 | Derived d; 740 | d.var0 = 2; //直接访问虚基类的数据成员 741 | d.fun0(); //直接访问虚基类的函数成员 742 | return 0; 743 | } 744 | ``` 745 | ### 虚基类及其派生类构造函数 746 | - 建立对象时所指定的类称为**最远派生类**。 747 | - 虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。 748 | - 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出,则表示调用该虚基类的默认构造函数。 749 | - 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。 750 | 751 | ### 有虚基类时的构造函数举例(补7-4) 752 | ``` cpp 753 | #include 754 | using namespace std; 755 | 756 | class Base0 { 757 | public: 758 | Base0(int var) : var0(var) { } 759 | int var0; 760 | void fun0() { cout << "Member of Base0" << endl; } 761 | }; 762 | class Base1: virtual public Base0 { 763 | public: 764 | Base1(int var) : Base0(var) { } 765 | int var1; 766 | }; 767 | class Base2: virtual public Base0 { 768 | public: 769 | Base2(int var) : Base0(var) { } 770 | int var2; 771 | }; 772 | ``` 773 | ### 有虚基类时的构造函数举例(补7-4) 774 | ``` cpp 775 | class Derived: public Base1, public Base2 { 776 | public: 777 | Derived(int var) : Base0(var), Base1(var), Base2(var) 778 | { } 779 | int var; 780 | void fun() 781 | { cout << "Member of Derived" << endl; } 782 | }; 783 | 784 | int main() { //程序主函数 785 | Derived d(1); 786 | d.var0 = 2; //直接访问虚基类的数据成员 787 | d.fun0(); //直接访问虚基类的函数成员 788 | return 0; 789 | } 790 | ``` 791 | 792 | -------------------------------------------------------------------------------- /pics/C1/公式4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/公式4.png -------------------------------------------------------------------------------- /pics/C1/公式6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/公式6.png -------------------------------------------------------------------------------- /pics/C1/公式7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/公式7.png -------------------------------------------------------------------------------- /pics/C1/十进制小数→ R 进制小数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/十进制小数→ R 进制小数.png -------------------------------------------------------------------------------- /pics/C1/十进制整数转换为R 进制整数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/十进制整数转换为R 进制整数.png -------------------------------------------------------------------------------- /pics/C1/原码.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/原码.png -------------------------------------------------------------------------------- /pics/C1/数据信息.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/数据信息.png -------------------------------------------------------------------------------- /pics/C1/程序中常用的数制.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/程序中常用的数制.png -------------------------------------------------------------------------------- /pics/C1/计算机硬件.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C1/计算机硬件.png -------------------------------------------------------------------------------- /pics/C10/STL的基本组件间的关系.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C10/STL的基本组件间的关系.png -------------------------------------------------------------------------------- /pics/C10/关联容器概念图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C10/关联容器概念图.png -------------------------------------------------------------------------------- /pics/C10/函数对象概念图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C10/函数对象概念图.png -------------------------------------------------------------------------------- /pics/C10/容器的分类1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C10/容器的分类1.png -------------------------------------------------------------------------------- /pics/C10/容器的分类2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C10/容器的分类2.png -------------------------------------------------------------------------------- /pics/C10/迭代器的分类.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C10/迭代器的分类.png -------------------------------------------------------------------------------- /pics/C4/联合体的内存分配.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C4/联合体的内存分配.png -------------------------------------------------------------------------------- /pics/C7/Derived类对象d的存储结构示意图示意图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C7/Derived类对象d的存储结构示意图示意图.png -------------------------------------------------------------------------------- /pics/C7/虚基类举例.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/C7/虚基类举例.png -------------------------------------------------------------------------------- /pics/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/qrcode.jpg -------------------------------------------------------------------------------- /pics/test/terminator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuetangx-cpp/main/bb07e07a1cda1729a4c9dd4ed54bad77b554c262/pics/test/terminator.jpg -------------------------------------------------------------------------------- /plan.md: -------------------------------------------------------------------------------- 1 | ## 工作内容 2 | 3 | 4 | ### 基本工作 5 | 6 | 基本工作是保证学生正常学习的部分 7 | 8 | - 视频 9 | - 留意同学反映错误,记录,反映到制作方,得到正确版本,重新上传。(工作周期视公司人力而定) 10 | 11 | - 题目 12 | - 每章包括若干客观题(选择题或者填空题), 和在线编程题 13 | - 每章发布前把该章题目落实 14 | - 答案正确性,**唯一性** 15 | - 题目的难易 16 | - 题目知识点是否和视频很好关联,质量是否好 17 | - 题目的有趣性 18 | - online judge 题目维护,系统保证正常运行(对OJ比较熟悉的同学可以认领这一块工作) 19 | 20 | - 讨论区 21 | - 及时解答同学提出的疑问,尽可能详细,给出参考资料 22 | - 适当一些引导性的讨论 23 | - 鼓励激励同学,维持学习热情 24 | - QQ群上的问题顺手解决 25 | 26 | - 考试 27 | - 出题(客观题+编程题) 28 | - 题目检验 29 | - 成绩校验和向公司证书落实 30 | 31 | ### 额外工作 32 | - 公众号相关 33 | 上学期开始的,主要发布讲义,效果比较差,希望这学期能把公众号的文章质量做好。 34 | 35 | - DebugForFun(DBF) 36 | 助教贡献有代码给大家找bug,并有解释 37 | 38 | - 技术拓展 39 | 鼓励学生贡献C++开发周边文章,主要是附上文章链接,文章简介和个人阅读心得。 40 | 41 | - 资料归档 42 | 将线上讲义markdown化上传到github 43 | 44 | ### 课程链接 45 | - 自助课程: 46 | - 基础:[http://www.xuetangx.com/courses/TsinghuaX/00740043X/2015_T1/info](http://www.xuetangx.com/courses/TsinghuaX/00740043X/2015_T1/info) 47 | 48 | - 进阶:http://www.xuetangx.com/courses/TsinghuaX/00740043_2X/2015_T1/info 49 | 50 | - 证书课程: 51 | - 基础:http://www.xuetangx.com/courses/course-v1:TsinghuaX+00740043X+2016_T1/info 52 | 53 | - 进阶:http://www.xuetangx.com/courses/course-v1:TsinghuaX+00740043_2X+2016_T1/info 54 | 55 | ### 重要时间点 56 | - 基础篇各章发布时间 57 | - 2.22 第一章 绪论 58 | - 2.29 第二章 C++简单程序设计(一) 59 | - 3.7 第二章 C++简单程序设计(二) 60 | - 3.14 第三章 函数 61 | - 3.21 第四章 类与对象 62 | - 3.28 第五章 数据的共享与保护 63 | - 4.4 第六章 数组、指针与字符串(一) 64 | - 4.11 第六章 数组、指针与字符串(二) 65 | 66 | - 进阶篇各章发布时间 67 | - 4.18 第七章 继承与派生 68 | - 4.25 第八章 多态性 69 | - 5.2 第九章 模板与群体数据 70 | - 5.9 第十章 泛型程序设计与C++标准模板库 71 | - 5.16 第十一章 流类库与输入/输出 72 | - 5.23 第十二章 异常处理 73 | 74 | - 考试相关 75 | - 4.4 第六章开始通知基础篇考试内容,形式,时间 76 | - 4.25 基础篇考试 77 | - 5.16 第十一章开始时进阶篇考试内容,形式,时间 78 | - 6.6 进阶篇考试 79 | 80 | ### 其他程序设计类课程 81 | 82 | https://www.coursera.org/course/gameprogramming 83 | 84 | https://www.coursera.org/learn/c-plus-plus-a/ 85 | 86 | http://www.xuetangx.com/courses/course-v1:TsinghuaX+30240233X_tv+2015_T1/about 87 | 88 | https://www.coursera.org/specializations/java-programming 89 | 90 | http://www.xuetangx.com/courses/course-v1:PekingX+04831750.1x+2015T1/about 91 | 92 | # 分工: 93 | 94 | 基础工作主要是题目的审核和质量提高。 95 | 96 | 额外工作大家应该多发挥自己的想法,热情去维护建设我们的课程,之前我只是定了两个小模块,大家可以再想一下,有更好的可以提出来,原来的形式也可以改善下。 97 | 98 | 大家先了解下工作,分工我会再详细分。我们的工作在第三周要开始了。 99 | -------------------------------------------------------------------------------- /分工.md: -------------------------------------------------------------------------------- 1 | 每次的更新和题目审核应针对下一章,例如第三章的更新日期为3.14,则这两项工作应该3.14日0:00前完成,确保下一周的资源到位。 2 | 3 | 各工作模块的负责人: 4 | 5 | 1. (DBF)DebugForFun: 鲁盼,李北辰, 苏克 6 | - **每周** 更新一次 7 | - **每周** 上传一些好玩的程序供大家学习 8 | 9 | 2. (CppTech)C++技术文章: 唐玉涵 葛泽睿 张轩 10 | - **每周** 更新一次 11 | - 内容应为本周知识点相关的技术文章,附上链接即可 12 | 13 | 3. (讨论精华)热点问题总结:李北辰 张轩 14 | - 将讨论区的热点,精彩问题**每周**总结到FAQ 15 | 16 | 4. (weixin)公众号: 唐玉涵 葛泽睿 鲁盼 苏克 17 | - 同步发布课程更新信息 18 | - **每周** 发一篇文章(内容不限,与编程,MOOC,C++相关即可,内容可取自DebugForFun,C++技术文章,热点问题总结,可向其他助教要稿件) 19 | 20 | 5. (announcement)课程更新页面: 李北辰 张轩 21 | - **每周** 更新后在课程更新页面更新信息,卖萌,更新课件 22 | - 搜集一些好玩,带点技术梗的漫画发布 23 | 24 | 6. (OJ)OJ系统保障,编程题目的更新 梁嘉骏 25 | 26 | 7. (online-homework)题目审查**(所有人)** 27 | - t1 唐玉涵 葛泽睿 28 | - t2 鲁盼,李北辰 29 | - t3 张轩,苏克 30 | - 各章分工 31 | - 基础篇 32 | - 3 t1 33 | - 4 t2 34 | - 5 t3 35 | - 6 t1 36 | - 进阶篇 37 | - 7 t2 38 | - 8 t3 39 | - 9 t1 40 | - 10 t2 41 | - 11 t3 42 | - 12 t1 43 | 44 | 题目审查的工作: 45 | 46 | 1. 确保题目正确,答案不存在争议(尽量不要用填空题,因为答案匹配采用简单字符串对比) 47 | 2. 强化现有题目质量,加强题目**与视频的联系**。(不好的题目请换题,尽量保证每个视频对应有1-2个题目) 48 | 3. 尽量为每条题目的答案**附上解释** 49 | 4. 将所有按章题目(题干,答案,解释)**汇总到word文档(第XXX章客观题汇总.doc)**,期末归档 50 | 5. 每章题目的审查,修改**务必在该章学习开始前时完成**。(见重要时间节点) 51 | 6. **对负责的章节的编程题的考察提点意见**,更新由梁嘉骏完成 52 | 53 | 8. 在线答疑:**(所有人)** 54 | 每个人每**两天至少登一次讨论区**回答问题,鼓励一下等(讨论区是助教考核的重要指标,大家多活跃活跃!) 55 | 56 | 9. 每个视频下的讲义markdown化上传github **(所有人)** 57 | - 分工和题目一样,另附上第一第二章的分工 58 | - 1 t2 59 | - 2 t3 60 | - 模板见 [template.md](handout/template.md)。 61 | 62 | ### 重要时间点 63 | - 基础篇各章发布时间 64 | - 2.22 第一章 绪论 65 | - 2.29 第二章 C++简单程序设计(一) 66 | - 3.7 第二章 C++简单程序设计(二) 67 | - 3.14 第三章 函数 68 | - 3.21 第四章 类与对象 69 | - 3.28 第五章 数据的共享与保护 70 | - 4.4 第六章 数组、指针与字符串(一) 71 | - 4.11 第六章 数组、指针与字符串(二) 72 | 73 | - 进阶篇各章发布时间 74 | - 4.18 第七章 继承与派生 75 | - 4.25 第八章 多态性 76 | - 5.2 第九章 模板与群体数据 77 | - 5.9 第十章 泛型程序设计与C++标准模板库 78 | - 5.16 第十一章 流类库与输入/输出 79 | - 5.23 第十二章 异常处理 80 | 81 | - 考试相关 82 | - 4.4 第六章开始通知基础篇考试内容,形式,时间 83 | - 4.25 基础篇考试 84 | - 5.16 第十一章开始时进阶篇考试内容,形式,时间 85 | - 6.6 进阶篇考试 86 | --------------------------------------------------------------------------------