{"id":15047413,"url":"https://github.com/harvestlamb/cpp_houjie","last_synced_at":"2025-05-16T09:03:29.028Z","repository":{"id":43642655,"uuid":"227598613","full_name":"harvestlamb/Cpp_houjie","owner":"harvestlamb","description":"侯捷C++课程PPT及代码,动手学起来","archived":false,"fork":false,"pushed_at":"2019-12-12T12:37:09.000Z","size":15688,"stargazers_count":1465,"open_issues_count":1,"forks_count":492,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-05-16T09:03:25.628Z","etag":null,"topics":["code","course","cpp","cpp11"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/harvestlamb.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-12-12T12:11:28.000Z","updated_at":"2025-05-16T01:12:46.000Z","dependencies_parsed_at":"2022-08-27T05:41:30.054Z","dependency_job_id":null,"html_url":"https://github.com/harvestlamb/Cpp_houjie","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harvestlamb%2FCpp_houjie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harvestlamb%2FCpp_houjie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harvestlamb%2FCpp_houjie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harvestlamb%2FCpp_houjie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/harvestlamb","download_url":"https://codeload.github.com/harvestlamb/Cpp_houjie/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254501554,"owners_count":22081528,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code","course","cpp","cpp11"],"created_at":"2024-09-24T20:57:59.197Z","updated_at":"2025-05-16T09:03:29.005Z","avatar_url":"https://github.com/harvestlamb.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"#include \u003ciostream\u003e\n#include \"complex.h\"\n\nusing namespace std;\n\nostream\u0026\noperator \u003c\u003c (ostream\u0026 os, const complex\u0026 x)\n{\n  return os \u003c\u003c '(' \u003c\u003c real (x) \u003c\u003c ',' \u003c\u003c imag (x) \u003c\u003c ')';\n}\n\nint main()\n{\n  complex c1(2, 1);\n  complex c2(4, 0);\n\n  cout \u003c\u003c c1 \u003c\u003c endl;\n  cout \u003c\u003c c2 \u003c\u003c endl;\n  \n  cout \u003c\u003c c1+c2 \u003c\u003c endl;\n  cout \u003c\u003c c1-c2 \u003c\u003c endl;\n  cout \u003c\u003c c1*c2 \u003c\u003c endl;\n  cout \u003c\u003c c1 / 2 \u003c\u003c endl;\n  \n  cout \u003c\u003c conj(c1) \u003c\u003c endl;\n  cout \u003c\u003c norm(c1) \u003c\u003c endl;\n  cout \u003c\u003c polar(10,4) \u003c\u003c endl;\n  \n  cout \u003c\u003c (c1 += c2) \u003c\u003c endl;\n  \n  cout \u003c\u003c (c1 == c2) \u003c\u003c endl;\n  cout \u003c\u003c (c1 != c2) \u003c\u003c endl;\n  cout \u003c\u003c +c2 \u003c\u003c endl;\n  cout \u003c\u003c -c2 \u003c\u003c endl;\n  \n  cout \u003c\u003c (c2 - 2) \u003c\u003c endl;\n  cout \u003c\u003c (5 + c2) \u003c\u003c endl;\n  \n  return 0;\n}\n\n## 1.拷贝赋值函数 ##\n\n![](https://i.imgur.com/B6W7jnL.png)\n\n如上图所示，在重载“=”赋值运算符时需要检查自我赋值，原因如下：\n\n![](https://i.imgur.com/ymJ6YD2.png)\n\n如果没有自我赋值检测，那么自身对象的m_data将被释放，m_data指向的内容将不存在，所以该拷贝会出问题。\n\n## 2.探索new操作 ##\n\n![](https://i.imgur.com/CoqboeP.png)\n\n## 3.探索delete操作 ##\n\n![](https://i.imgur.com/LF05Wt3.png)\n\n## 4.探索创建对象的内存分配情况 ##\n\n这里以两个类做出说明：\n\n\tclass complex\n\t{\n\t\tpublic:\n\t\t\tcomplex (double r = 0, double i = 0)\n\t\t\t: re (r), im (i)\n\t\t\t{ }\n\t\t\tcomplex\u0026 operator += (const complex\u0026);\n\t\t\tdouble real () const { return re; }\n\t\t\tdouble imag () const { return im; }\n\t\tprivate:\n\t\t\tdouble re, im;\n\t\t\tfriend complex\u0026 __doapl (complex*,\n\t\t\tconst complex\u0026);\n\t};\n\n\n\tclass String\n\t{\n\t\tpublic:\n\t\t\tString(const char* cstr = 0);\n\t\t\tString(const String\u0026 str);\n\t\t\tString\u0026 operator=(const String\u0026 str);\n\t\t\t~String();\n\t\t\tchar* get_c_str() const { return m_data; }\n\t\tprivate:\n\t\t\tchar* m_data;\n\t};\n\n创建这两个对象后，编译器(VC)给两个对象分配内存如下：\n![](https://i.imgur.com/1Ud7vVP.png)\n\n左边两个是类complex在调试模式和release模式下的编译器内存分配。在debug模式下，编译器给complex对象内存插入了头和尾（红色部分），4*8 + 4大小的信息部分（灰色部分），绿色部分是complex对象实际占用的空间，计算后只有52字节，但VC以16字节对齐，所以52最近的16倍数是64，还应该填补12字节的空缺（青色pad部分）。对于release部分的complex对象，只添加了信息头和伟部分。string类的分析基本一样。\n\n接下来看对于数组对象，VC编译器是如何分配的：\n![](https://i.imgur.com/G4eXrRE.png)\n\n类似的，编译器给对象增加了一些冗余信息部分，对于complex类对象，由于数组有三个对象，则存在8个double，然后编译器在3个complex对象前插入“3”用于标记对象个数。String类的分析方法也类似。\n\n下面这张图说明了为何删除数组对象需要使用delete[]方法：\n![](https://i.imgur.com/vNvDftQ.png)\n\n\n## 5.String类\n\nString.h\n\n\t#ifndef __MYSTRING__\n\t#define __MYSTRING__\n\t\n\tclass String\n\t{\n\tpublic:                                 \n\t   String(const char* cstr=0);                     \n\t   String(const String\u0026 str);                    \n\t   String\u0026 operator=(const String\u0026 str);         \n\t   ~String();                                    \n\t   char* get_c_str() const { return m_data; }\n\tprivate:\n\t   char* m_data;\n\t};\n\t\n\t#include \u003ccstring\u003e\n\t\n\tinline\n\tString::String(const char* cstr)\n\t{\n\t   if (cstr) {\n\t      m_data = new char[strlen(cstr)+1];\n\t      strcpy(m_data, cstr);\n\t   }\n\t   else {   \n\t      m_data = new char[1];\n\t      *m_data = '\\0';\n\t   }\n\t}\n\t\n\tinline\n\tString::~String()\n\t{\n\t   delete[] m_data;\n\t}\n\t\n\tinline\n\tString\u0026 String::operator=(const String\u0026 str)\n\t{\n\t   if (this == \u0026str)\n\t      return *this;\n\t\n\t   delete[] m_data;\n\t   m_data = new char[ strlen(str.m_data) + 1 ];\n\t   strcpy(m_data, str.m_data);\n\t   return *this;\n\t}\n\t\n\tinline\n\tString::String(const String\u0026 str)\n\t{\n\t   m_data = new char[ strlen(str.m_data) + 1 ];\n\t   strcpy(m_data, str.m_data);\n\t}\n\t\n\t#include \u003ciostream\u003e\n\tusing namespace std;\n\t\n\tostream\u0026 operator\u003c\u003c(ostream\u0026 os, const String\u0026 str)\n\t{\n\t   os \u003c\u003c str.get_c_str();\n\t   return os;\n\t}\n\t\n\t#endif\n\nstring_test.cpp\n\n\t#include \"string.h\"\n\t#include \u003ciostream\u003e\n\t\n\tusing namespace std;\n\t\n\tint main()\n\t{\n\t  String s1(\"hello\"); \n\t  String s2(\"world\");\n\t    \n\t  String s3(s2);\n\t  cout \u003c\u003c s3 \u003c\u003c endl;\n\t  \n\t  s3 = s1;\n\t  cout \u003c\u003c s3 \u003c\u003c endl;     \n\t  cout \u003c\u003c s2 \u003c\u003c endl;  \n\t  cout \u003c\u003c s1 \u003c\u003c endl;      \n\t}\n\n\n## C++中的explicit ##\n\nC++中， 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数)， 承担了两个角色。 1 是个构造器 ，2 是个默认且隐含的类型转换操作符。\n\n所以， 有时候在我们写下如 AAA = XXX， 这样的代码， 且恰好XXX的类型正好是AAA单参数构造器的参数类型， 这时候编译器就自动调用这个构造器， 创建一个AAA的对象。\n\n这样看起来好象很酷， 很方便。 但在某些情况下（见下面权威的例子）， 却违背了我们（程序员）的本意。 这时候就要在这个构造器前面加上explicit修饰， 指定这个构造器只能被明确的调用/使用， 不能作为类型转换操作符被隐含的使用。\n\nexplicit构造函数是用来防止隐式转换的。请看下面的代码：\n\n\tclass Test1\n\t{\n\tpublic:\n\t    Test1(int n)\n\t    {\n\t        num=n;\n\t    }//普通构造函数\n\tprivate:\n\t    int num;\n\t};\n\tclass Test2\n\t{\n\tpublic:\n\t    explicit Test2(int n)\n\t    {\n\t        num=n;\n\t    }//explicit(显式)构造函数\n\tprivate:\n\t    int num;\n\t};\n\tint main()\n\t{\n\t    Test1 t1=12;//隐式调用其构造函数,成功\n\t    Test2 t2=12;//编译错误,不能隐式调用其构造函数\n\t    Test2 t2(12);//显式调用成功\n\t    return 0;\n\t}\n\nTest1的构造函数带一个int型的参数，代码23行会隐式转换成调用Test1的这个构造函数。而Test2的构造函数被声明为explicit（显式），这表示不能通过隐式转换来调用这个构造函数，因此代码24行会出现编译错误。\n\n普通构造函数能够被隐式调用。而explicit构造函数只能被显式调用。\n\n## 2.虚函数 ##\n\n![](https://i.imgur.com/rLgBzuh.png)\n\n\n\n\n\n## 1.转换函数 ##\n\n![](https://i.imgur.com/D2CbMvC.png)\n任何Fraction需要被转换为double类型的时候，自动调用double()函数进行转换。如上图所示，编译器在分析double d = 4 + f过程中判断4为整数，然后继续判断f，观察到f提供了double()函数，然后会对f进行double()操作，计算得到0.6，再与4相加，最后得到double类型的4.6。\n\n## 2.explicit与隐式转换 ##\n\n![](https://i.imgur.com/6hPhBVv.png)\n上图中定义了一个类，叫Fraction，类里面重载了“+”运算符，在f+4操作过程中，“4”被编译器隐式转换（构造函数）为Fraction对象，然后通过Fraction重载的“+”运算符参与运算。\n\n![](https://i.imgur.com/CJMA6oJ.jpg)\n如上图所示，在Fraction中增加了double()函数，将Fraction的两个成员变量进行除法运算，然后强制转化为double类型并返回结果，在f+4重载过程中编译器将报错，可以做出如下分析：\n\n1、首先4被隐式转化（构造函数）为Fraction对象，然后通过重载的“+”运算符与“f”进行运算返回一个Frction对象；\n\n2、首先4被隐式转化（构造函数）为Fraction对象，然后通过重载的“+”运算符与“f”运算后对进行double运算，最返回一个Frction对象；\n\n3、。。。\n\n所以编译器有至少两条路可以走，于是产生了二义性，报错。\n![](https://i.imgur.com/fZU3aYV.png)\n如上图所示，在构造函数Franction前加入explict关键字，隐式转换将取消，所以在执行d2 = f + 4过程中，f将调用double函数转换为0.6，然后与4相加变成4.6，由于构造函数取消隐公式转换，4.6无法转换为Fraction，于是将报错。\n\n下图为C++ stl中操作符重载和转换函数的一个应用：\n![](https://i.imgur.com/01jFo9x.png)\n\n## 3.智能指针 ##\n\n下面这张图很好地说明了智能指针的内部结构和使用方法：\n![](https://i.imgur.com/UenFAl5.jpg)\n智能指针在语法上有三个很关键的地方，第一个是保存的外部指针，对应于上图的T* px，这个指针将代替传入指针进行相关传入指针的操作；第二个是重载“*”运算符，解引用，返回一个指针所指向的对象；第三个是重载“-\u003e”运算符，返回一个指针，对应于上图就是px。\n\n迭代器也是一种智能指针，这里也存在上面提到的智能指针的三个要素，分别对应于下图的红色字体和黄色标注部分：\n![](https://i.imgur.com/Gsty5Hz.jpg)\n\n下面将仔细分析迭代器重载的“*”和“-\u003e”重载符：\n\n![](https://i.imgur.com/8TiNyqF.jpg)\n\n创建一个list迭代器对象，list\u003cFoo\u003e::iterator ite;这里的list用于保存Foo对象，也就是list模板定义里的class T，operator\\*()返回的是一个(\\*node).data对象，node是\\_\\_link\\_type类型，然而\\_\\_link\\_type又是\\_\\_list\\_node\u003cT\\\u003e\\*类型，这里的T是Foo，所以node是\\_\\_list\\_node\u003cFoo\\\u003e*类型,所以\\(\\*node\\).data得到的是Foo类型的一个对象，而\u0026\\(operator\\*\\(\\)\\)最终得到的是该Foo对象的一个地址，即返回Foo\\* 类型的一个指针。\n\n## 4.仿函数 ##\n\n![](https://i.imgur.com/DhX59Kd.jpg)\n重上图可以看到，每个仿函数都是某个类重载“()”运算符，然后变成了“仿函数”，实质还是一个类，但看起来具有函数的属性。每个仿函数其实在背后都集成了一个奇怪的类，如下图所示，这个类不用程序员手动显式声明。\n![](https://i.imgur.com/EKpcd2X.jpg)\n标准库中的仿函数也同样继承了一个奇怪的类：\n![](https://i.imgur.com/GqWy1Yz.jpg)\n这个类的内容如下图所示，只是声明了一些东西，里面没有实际的变量或者函数，具体的内容将在STL中讨论。\n![](https://i.imgur.com/G5iodMK.png)\n\n## 5.namespace ##\n\n![](https://i.imgur.com/J2k7KGj.png)\n\n## 6.类模板 ##\n\n![](https://i.imgur.com/j1zJLWl.png)\n\n## 7.函数模板 ##\n\n![](https://i.imgur.com/J5MLXNd.png)\n与类模板不同的是，函数模板在使用是不需要显式地声明传入参数类型，编译器将自动推导类型。\n\n## 8.成员模板 ##\n\n![](https://i.imgur.com/Y9FBR6k.png)\n成员模板在泛型编程里用得较多，为了有更好的可扩展性，以上图为例，T1往往是U1的基类，T2往往是U2的基类，可以看下面这个例子：\n![](https://i.imgur.com/FVN4ww1.jpg)\n通过这种方法，只要传入的U1和U2的父类或者祖类是T1和T2，那么通过这样的方式可以实现继承和多态的巧妙利用，但反之就不行了。这样的方式在STL中用得很多：\n![](https://i.imgur.com/7ig5lAg.png)\n\n## 9.模板偏化 ##\n\n正如其名，模板偏化指的是模板中指定特定的数据类型，这和泛化是不同的：\n![](https://i.imgur.com/maGjkzC.png)\n当然，模板偏化也有程度之分，可以部分类型指定，称之为偏特化：\n![](https://i.imgur.com/1qfrg4O.png)\n![](https://i.imgur.com/ixantUB.png)\n\n## 10.模板模板参数 ##\n\n![](https://i.imgur.com/TUIdZpP.png)\n![](https://i.imgur.com/wulBSIu.png)\n![](https://i.imgur.com/sUNnTCc.png)\n\n## 11.可变数目模板参数 ##\n\n![](https://i.imgur.com/YqS87h4.jpg)\n过多内容将在C++11课程中讲解，这里暂时只做介绍。\n\n## 12.auto关键字和增强型for循环\n\n![](https://i.imgur.com/C3Gmlaa.png)\n![](https://i.imgur.com/HwUMzfD.png)\n过多内容将在C++11课程中讲解，这里暂时只做介绍\n\n## 13.reference ##\n\nreference可以看做是某个被引用变量的别名。\n![](https://i.imgur.com/pYlpmg2.png)\n![](https://i.imgur.com/3rfj4at.png)\n![](https://i.imgur.com/iVYlytf.jpg)\n\n## 14.虚指针和虚函数表 ##\n\n![](https://i.imgur.com/nIkKZhP.jpg)\n如上图所示，定义了三个类，A、B和C，B继承于A,C继承于B，A中有两个虚函数，B中有一个，C中也有一个。编译器将A的对象a在内存中分配如上图所示，只有两个成员变量m\\_data1和m\\_data2，与此同时，由于A类有虚函数，编译器将给a对象分配一个空间用于保存虚函数表，这张表维护着该类的虚函数地址（动态绑定），由于A类有两个虚函数，于是a的虚函数表中有两个空间（黄蓝空间）分别指向A::vfunc1()和A::vfunc2()；同样的，b是B类的一个对象，由于B类重写了A类的vfunc1()函数，所以B的虚函数表（青色部分）将指向B::vfunc1()，同时B继承了A类的vfunc2()，所以B的虚函数表（蓝色部分）将指向父类A的A::vfunc2()函数；同样的，c是C类的一个对象，由于C类重写了父类的vfunc1()函数，所以C的虚函数表（黄色部分）将指向C::vfunc1()，同时C继承了超类A的vfunc2()，所以B的虚函数表（蓝色部分）将指向A::vfunc2()函数。同时上图也用C语言代码说明了编译器底层是如何调用这些函数的，这便是面向对象继承多态的本质。\n\n## 15.this指针 ##\n\n![](https://i.imgur.com/PH9T0ss.jpg)\nthis指针其实可以认为是指向当前对象内存地址的一个指针，如上图所示，由于基类和子类中有虚函数，this-\u003eSerialize()将动态绑定，等价于(*(this-\u003evptr)[n])(this)。可以结合上节虚指针和虚函数表来理解，至于最后为什么这样写是正确的，下面小结将会解释。\n\n## 16.动态绑定 ##\n\n![](https://i.imgur.com/C97NUFQ.jpg)\n![](https://i.imgur.com/lDi3V3I.jpg)\n\n## 17.重载delete和new操作符 ##\n\n![](https://i.imgur.com/h5tWgeY.png)\n![](https://i.imgur.com/EYF1OCI.png)\n![](https://i.imgur.com/XZg8al9.png)\n![](https://i.imgur.com/rqX4a3j.jpg)\n![](https://i.imgur.com/WtkG1Kv.jpg)\n![](https://i.imgur.com/EUhdjmM.jpg)\n\n## 18.重载delete()和new()操作符 ##\n\n![](https://i.imgur.com/c8jAMdu.png)\n![](https://i.imgur.com/Sd2vMWM.jpg)\n![](https://i.imgur.com/3Mgirvx.jpg)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharvestlamb%2Fcpp_houjie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharvestlamb%2Fcpp_houjie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharvestlamb%2Fcpp_houjie/lists"}