本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版 | |||
cs:programming:cpp:boolan_cpp:oop_a_week1 [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:boolan_cpp:oop_a_week1 [2024/01/14 13:46] (当前版本) – ↷ 页面programming:cpp:boolan_cpp:oop_a_week1被移动至cs:programming:cpp:boolan_cpp:oop_a_week1 codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======C++面向对象高级编程(上)第一周====== | ||
+ | 本页内容是我关于 //Boolan// C++ 开发工程师培训系列课程的笔记。\\ | ||
+ | <wrap em> | ||
+ | ---- | ||
+ | ====相关概念==== | ||
+ | ===C++的历史和版本=== | ||
+ | C++ 的演化过程 大致如下: | ||
+ | * 1979年,C++ 作为C语言的扩充(C with Class)面世,这一阶段主要是添加 C 语言对 OOP 的支持。 | ||
+ | * 1983年,C++ 的正式诞生。 | ||
+ | * 1998年,C++ 语言成为美国国家标准和国际标准:C++ 98 标准(1.0)。 | ||
+ | * 2003年,C++ 03:C++ Technical Report 1 (TR1)。 | ||
+ | * 2011年,C++ 11:标准 2.0。 | ||
+ | * 2014年,C++ 14。 | ||
+ | 其中主流标准版本为 C++ 98 和 C++ 11。 | ||
+ | |||
+ | ===C++的入门推荐读物=== | ||
+ | |||
+ | 普遍意义上来说,熟练使用 C++ 意味着必须熟悉**语法**和**标准库**,因此推荐书籍分为两类: | ||
+ | \\ | ||
+ | \\ | ||
+ | 语法类:C++ Primer 5th / The C++ Programming Language / Effective C++ | ||
+ | \\ | ||
+ | 标准库:The C++ Standard Library / STL 源码剖析 | ||
+ | |||
+ | ====头文件与类的声明==== | ||
+ | |||
+ | ===数据与函数=== | ||
+ | |||
+ | 在 C 语言中,不论是 // | ||
+ | \\ | ||
+ | 而在 C++ 中,// | ||
+ | |||
+ | ==类的分类== | ||
+ | |||
+ | 设计类的时候,我们一般把类分为两种: | ||
+ | * 数据中有指针的类 | ||
+ | * 数据中没有指针的类 | ||
+ | 这两种类的主要区别在于:没有指针的类,一般情况下我们可以让系统自动调用析构函数;但如果类中包含了指针,往往会涉及到内存管理问题,因此很多情况下需要自定义析构函数。 | ||
+ | |||
+ | ===Output=== | ||
+ | |||
+ | C++ 通过 ''< | ||
+ | <code cpp> | ||
+ | cout << i << endl; | ||
+ | </ | ||
+ | 而如果想使用 C 中的 '' | ||
+ | |||
+ | ===头文件的防卫式声明=== | ||
+ | |||
+ | 我们经常会遇到这样的情况:一个头文件被不同的程序文件所引用。\\ | ||
+ | \\ | ||
+ | 如果不对头文件进行任何处理,那么只要有两个或以上的文件包含同一头文件,那么编译器会返回 redefined 的错误。 | ||
+ | \\ | ||
+ | \\ | ||
+ | 为了处理这个问题,C++ 引入了 ''# | ||
+ | <code linenums: | ||
+ | #ifndef FILE_H //test if File has been defined | ||
+ | #define FILE_H // if not, process the definition work; else jump to the endif. | ||
+ | /* ... Declarations etc here ... */ | ||
+ | # | ||
+ | </ | ||
+ | 每个包含了 '' | ||
+ | |||
+ | ===头文件的布局=== | ||
+ | |||
+ | 除了上述的预处理器以外,我们把头文件剩余的内容分为三个部分:**前置声明**(// | ||
+ | <code linenums: | ||
+ | // forward declaration | ||
+ | class A; | ||
+ | class B; | ||
+ | //class declaration & definition | ||
+ | class A | ||
+ | { | ||
+ | /*function declaration*/ | ||
+ | } | ||
+ | //member function definition outside the class | ||
+ | void A:: | ||
+ | </ | ||
+ | \\ | ||
+ | 类声明& | ||
+ | |||
+ | ===Template 简介=== | ||
+ | |||
+ | 我们常常遇到一种情况:需要的函数功能完全相同,唯一不同的只是参数的类型。为了使得函数可以应对不同类型的参数,C++ 提供了模板来解决这个问题。\\ | ||
+ | \\ | ||
+ | 我们可以通过模板来声明一种“不确定”的类型: | ||
+ | <code cpp> | ||
+ | template< | ||
+ | </ | ||
+ | 接下来,我们就可以用 '' | ||
+ | <code linenums: | ||
+ | class complex | ||
+ | { | ||
+ | /*other codes*/ | ||
+ | T re, im; // define re, im as T type | ||
+ | /*other codes*/ | ||
+ | } | ||
+ | </ | ||
+ | 然后,我们就可以在建立的对象的时候同时定义我们希望指派给对象的参数类型了: | ||
+ | <code cpp linenums: | ||
+ | complex< | ||
+ | complex< | ||
+ | </ | ||
+ | ===Inline 函数=== | ||
+ | |||
+ | 在 2.4.中我们看到类的成员函数可以在类内部定义,也可以在内外部定义。**而在类内部定义的函数,都是** //Inline// **函数**。\\ | ||
+ | |||
+ | //Inline// 函数是在编译阶段就会执行的函数,省去了一般函数 overhead 的阶段,因此速度非常快。对于**一些大量反复使用**的,**结构不是很复杂**的函数,我们一般都使用 //Inline// 函数。\\ | ||
+ | \\ | ||
+ | 如果要声明一个函数为 // | ||
+ | <code cpp linenums: | ||
+ | Inline func | ||
+ | { | ||
+ | definitions here... | ||
+ | } | ||
+ | </ | ||
+ | <WRAP center round info 100%> | ||
+ | //Inline// 关键字只是我们对编译器提出的**建议**;**会不会将函数视为** //Inline// **是由编译器决定的**。 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===Access level=== | ||
+ | |||
+ | C++ 通过两个关键字来实现封装:'' | ||
+ | \\ | ||
+ | '' | ||
+ | |||
+ | ====构造函数==== | ||
+ | |||
+ | 构造函数 (// | ||
+ | * 构造函数的名字与类相同 | ||
+ | * 构造函数没有返回类型 | ||
+ | * 构造函数可以重载 | ||
+ | |||
+ | ===构造函数的初始化=== | ||
+ | |||
+ | 构造函数的初始化大致分为两类:**直接初始化**和**初始化后再赋值**。\\ | ||
+ | \\ | ||
+ | 直接初始化的格式可以写成: | ||
+ | <code cpp> | ||
+ | complex (double r = 0, double i = 0) : re (r), im(i); | ||
+ | </ | ||
+ | 其中括号内的内容定义了构造函数的 parameter 的类型,而等号后面的值则是构造函数默认 argument。冒号后面的内容则是指定了要对哪些类成员进行初始化。\\ | ||
+ | \\ | ||
+ | 而初始化后再赋值是先用构造函数对变量初始化,在通过拷贝的方式对变量赋值。如果用这种方式复写上面的例子,我们可以把上例写成这样: | ||
+ | <code cpp> | ||
+ | complex (double r = 0, double i = 0) | ||
+ | { | ||
+ | re = r; | ||
+ | im = i; | ||
+ | } | ||
+ | </ | ||
+ | 这样写是完全正确的,但是效率上可能比第一种要差:在这种初始化中,编译器对类成员先进行了初始化,然后再把值交给了类成员。 | ||
+ | |||
+ | ===构造函数的重载=== | ||
+ | |||
+ | 跟普通函数一样,为了对应不同的初始化参数,构造函数也可以有好多个。调用构造函数的时候,我们只需要输入对应的参数,系统就会自动调用对应的构造函数。\\ | ||
+ | \\ | ||
+ | 对于函数的重载(包括构造函数),在我们看来是一个函数,实际上在编译器中,是两个完全不同的函数。\\ | ||
+ | |||
+ | ===Singleton 设计模式简介=== | ||
+ | |||
+ | 绝大部分情况下我们都将构造函数放置于 '' | ||
+ | |||
+ | ====成员函数==== | ||
+ | |||
+ | |||
+ | ===常量成员函数=== | ||
+ | |||
+ | 常量成员函数是指被 '' | ||
+ | <code cpp linenums: | ||
+ | double real() const { return re; }; //notice the position of const. | ||
+ | </ | ||
+ | 而 '' | ||
+ | <code cpp linenums: | ||
+ | double real() { return re; }; | ||
+ | </ | ||
+ | 如果用户以一个 '' | ||
+ | <code cpp linenums: | ||
+ | const complex c1(2,1); | ||
+ | </ | ||
+ | 那么很可能就会出问题了。用户希望自己的数据不被修改,而设计者认为函数是可以修改用户的数据的,因此就矛盾了。如果这里加上 '' | ||
+ | |||
+ | ===友元函数=== | ||
+ | |||
+ | 友元函数并不是类的成员函数,但友元函数可以访问私有变量,只需要在类中用关键字 '' | ||
+ | <code cpp linenums: | ||
+ | firend complex& | ||
+ | </ | ||
+ | |||
+ | 对于相同类的不同对象,这些对象互为友元。 | ||
+ | |||
+ | ====参数传递==== | ||
+ | |||
+ | 参数传递分为**值传递**(Pass by Value)和**引用传递**(Pass by Reference)。\\ | ||
+ | \\ | ||
+ | 值传递在传递过程中,先对 argument 进行拷贝, 然后复制到 parameter 中。 而引用传递是直接传递 argument 的引用。因此,在绝大部分情况下,引用传递的效率要远远高于值传递(省去了复制的过程)。不难看出,引用传递也是设计程序中一个值得注意的关键点: | ||
+ | * 引用传递的效率高于值传递。 | ||
+ | * 引用传递比指针传递的形式更加漂亮。 | ||
+ | * 引用传递可以直接在局部的 Scope 中修改 argument。 | ||
+ | * 如果不希望修改引用传递,可以传递 reference to const。 | ||
+ | |||
+ | ===返回值传递=== | ||
+ | |||
+ | 返回值的传递也同样分为值传递和引用传递。不过相比参数传递,返回值传递还有两个需要注意的地方: | ||
+ | * 有些类型的传递只能用引用传递,比如 '' | ||
+ | * 如果需要传递的返回值是< | ||
+ | |||
+ | ====运算符重载==== | ||
+ | |||
+ | 运算符重载是我们用自己的方式定义运算符对自定义类型的操作。在这里,**运算符重载的本质实际上是函数的重载**。\\ | ||
+ | \\ | ||
+ | 按照课程中的例子,运算符被分为两种部分:成员函数版本和非成员函数版本。 | ||
+ | |||
+ | ===成员函数重载=== | ||
+ | \\ | ||
+ | 成员函数版本的运算符重载的声明如下: | ||
+ | <code cpp linenums: | ||
+ | inline complex& | ||
+ | complex:: | ||
+ | { | ||
+ | return __doapl(this, | ||
+ | } | ||
+ | </ | ||
+ | 我们注意到这里只有一个 parameter。其实这个函数隐式包含了另外一个 parameter:'' | ||
+ | \\ | ||
+ | \\ | ||
+ | 这里还有一点需要注意的是,这个函数是有返回值的。按理说我们传的引用,那么在函数里直接修改就好了,为什么要添加一个返回值?\\ | ||
+ | \\ | ||
+ | 设想一下我们有 '' | ||
+ | <code cpp linenums: | ||
+ | c1 + c2 + c3; | ||
+ | </ | ||
+ | 按照结合律,应该是 '' | ||
+ | |||
+ | ===非成员函数重载=== | ||
+ | |||
+ | 而对于教程中另外一类的运算符重载,我们就不用写成成员函数的版本了,因为相关操作并不需要对私有变量的操作来实现,比如: | ||
+ | <code linenums: | ||
+ | inline complex | ||
+ | operator + (const complex& | ||
+ | { | ||
+ | return complex (real(x) + real(y), imag(x) + imag(y)); | ||
+ | } | ||
+ | </ | ||
+ | 上例我们通过了 '' | ||
+ | \\ | ||
+ | 这里还需要注意的是,'' | ||
+ | |||
+ | ===输出流运算符的重载=== | ||
+ | |||
+ | 输出流运算符 ''<<'' | ||
+ | \\ | ||
+ | 当有连续输出对象的时候,我们也不能用 '' | ||
+ | \\ | ||
+ | \\ |