Prev: C++Ⅴ:类与对象
Next: C++Ⅶ:继承 & 多态
1 为类创建单独文件
1.1 创建新类
一般地,在单独的文件里定义你要用的新类是好习惯。这会让维护和阅读代码更简单。 要这样做的话,在CodeBlocks中这样做: 点击File->New->Class... 给你的新类起个名,取消选择"Has destructor"并选择"Header and implementation file shall be in same folder",然后点击"Create"按钮。 注意到有两个新文件已经添加到你的项目了: 新文件就是我们新类的模板。MyClass.h是头文件。MyClass.cpp是源文件。 Part题: 通常,哪两个文件定义一个类? A. 头文件和主文件 B. 类文件和源文件 C. 头文件和源文件 D. 头文件和体文件 |
1.2 源文件 & 头文件(一)
头文件 (.h) 包含了函数声明(原型)和变量声明。 它现在包含我们新的MyClass类的模板,有一个默认构造函数。 MyClass.h
类的实现和方法体放在源文件 (.cpp) 中。 目前它只包括一个空的构造函数。 MyClass.cpp
头文件中的#ifndef和#define语句接下来的课程讲。 Part题: 头文件作用是? A. 函数原型和变量声明 B. 只有构造函数 C. 函数源码 D. 什么都没有 |
1.3 作用域解析运算符
源文件 (.cpp) 中的双冒号叫做[ruby=Scope Resolution Operator]作用域解析运算符[/ruby],在构造函数的定义中有用到:
作用域解析运算符用来定义一个特定的类的已经声明了的成员函数。记住,我们在头文件中定义构造函数原型。 所以基本上讲,MyClass::MyClass()指的是MyClass()成员函数——或者在本例中,MyClass类的构造函数。 Part题: 用选项填空,创建一个属于demo类的函数test。
|
1.4 源文件 & 头文件(二)
要在main中使用我们的类的话,我们需要包含头文件。 举例,如果在main中用我们新创建的MyClass:
头文件声明了类(或者不管实现的是什么)会"做什么",而cpp源文件定义了它"如何做"。 Part题: 要使用类,main.cpp中要包含什么? A. 类的源文件 B. 源文件和头文件都要 C. 类的头文件 |
2 析构函数
2.1 析构函数(一)
还记得构造函数么?它们是当对象被创建时调用的特殊成员函数。 [ruby=Destructors]析构函数[/ruby]也是特殊函数。它们会在当对象被销毁或删除时调用。 对象在离开定义域时会被销毁,或者将delete表达式应用到指向这个类对象的指针时会被删除。 Part题: 下面关于C++中构造函数和析构函数的表述中,哪条正确? A. 构造函数在对象被创建时调用;析构函数当其被删除时调用 B. 构造函数在当对象被删除时调用 C. 析构函数在当对象被创建时调用 |
2.2 析构函数(二)
析构函数的名字也与类名一致,只是有一个波浪号 (~)前缀。析构函数不能返回值,也不能有参数。
析构函数在程序中释放资源时非常有用。这可以包括关闭文件、释放内存等等。 Part题: 什么是析构函数符号? |
2.3 析构函数(三)
举个例子,我们来给我们的MyClass类声明一个析构函数,在MyClass.h头文件中:
这个我们的MyClass类声明了一个析构函数。 Part题: 用选项填空,给MyClass类声明一个析构函数。
|
2.4 析构函数(四)
在头文件中声明析构函数之后,我们可以在源文件MyClass.cpp中写实现了:
注意到我们使用了<iostream>头文件,以来使用cout。 Part题: 用选项填空,给类MyClass声明一个析构函数,其向屏幕输出文字。
|
2.5 析构函数(五)
因为析构函数不能有参数,所以其不能被重载。 每个类只会有一个析构函数。 析构函数不是必须的;如果不需要用就可以不用定义。 Part题: 下面的表述哪些正确? A. 析构函数不能重载 B. 析构函数永远返回double C. 析构函数不能有任何参数 D. 析构函数无名称 |
2.6 析构函数(六)
回到main吧。
我们包含了类的头文件并创建了那个类型的对象。这会有如下输出:
程序运行时,它先创建一个对象并调用构造函数。程序执行完成后,对象被销毁,析构函数被调用。 我们在构造函数中输出了"Constructor",在析构函数中输出了"Destructor"。 Part题: 析构函数... A. ...在对象被删除时调用 B. ...在构造函数没有被调用时调用 C. ...是成员变量 |
3 箭头成员运算符
3.1 #ifndef & #define
我们给我们的类创建了单独的头文件和源文件,头文件如下:
ifndef意味[ruby=if not defined]如果没有被定义[/ruby]。第一对语句告诉程序,如果还未定义MyClass头文件,就定义它。 endif结束这个条件。 这会防止同一个头文件被包含超过一次。 Part题: #ifndef和#define语句的意图是什么? A. 让程序跑得更快 B. 防止同一头文件在一个文件内被包含多于一次 C. 是std命名空间的需要 |
3.2 成员函数
我们来给类创建一个示例函数myPrint()。 MyClass.h
MyClass.cpp
因为myPrint()是普通成员函数,所以在声明和定义中都必须标注其返回类型。 Part题: 用选项填空,在Sally类中创建myPrint函数原型。
|
3.3 点运算符
接着来创建一个MyClass类型的对象,然后用点 (.) 运算符调用其myPrint()函数:
Part题: 用选项填空,声明一个Sally类对象,并用点 (.) 运算符调用其myPrint成员函数。
|
3.4 指针(四)
我们也可以用指针访问对象成员。 下面的指针指向obj对象。
指针类型是MyClass,其指向那个类型的对象。 Part题: 填空声明一个指向obj的指针:
|
3.5 箭头成员运算符
[ruby=arrow member selection operator]箭头成员选择运算符[/ruby]用于用指针访问对象成员。
当使用对象时,用点成员选择运算符 (.)。 Part题: 填上缺失的箭头运算符来通过sallyPtr调用myPrint()函数。
|
4 常量对象
4.1 常量
常量是有固定值的表达式。在程序运行时,它不能被改变。 用const关键字定义常量。
任何常量都必须在创建时初始化。 Part题: 填空声明double类型的常量var。
|
4.2 常量对象(一)
如内置类型一样,我们也可以用const关键字创建常量对象。
所有的const变量都必须在创建时初始化。对类而言,初始化由构造函数完成。如果函数不是用带参构造函数创建的,那么必须提供一个公有默认构造函数——如果没有提供公有默认构造函数,则会产生编译错误。 当const对象通过构造函数创建时,你不能修改对象的成员变量。这既包括对public成员变量的修改,也包括通过成员函数修改成员变量。 当你用const声明对象时,在对象生命期内无法改变其数据成员。 Part题: 用选项填空,声明一个Student类型的对象st,并调用其printAge()函数。
|
4.3 常量对象(二)
只有非const对象才能调用非const函数。 一个常量对象无法调用普通函数。因此,要使用常量对象,必须需要一个常量函数。 要声明一个函数为const成员,函数原型的右圆括号后,必须跟const关键字。对于那些在类定义外定义的const成员函数,const关键字必须在函数原型和定义中都要有。举例: MyClass.h
MyClass.cpp
现在myPrint()函数是一个常成员函数了。既然如此,我们的常量对象可以调用它了:
Part题: 填上缺失的关键字,为Student类声明一个常成员函数printAge()。
|
4.4 常量对象(三)
尝试在常量对象内调用普通函数会产生错误。 另外,当任何const成员函数尝试改变成员变量或调用非const成员函数时产生编译错误。 定义常量对象和常函数确保对应的数据成员不会被意外修改。 Part题: 常成员函数... A. ...不能被调用 B. ...能修改非const数据成员 C. ...不能修改任何非const数据成员 |
5 成员初始化
5.1 成员初始化(一)
回忆一下,常量是值无法改变的变量,所有const变量都必须在创建时初始化。 C++提供了一种顺手的语法用来初始化类成员,叫做[ruby=member initializer list]成员初始化列表[/ruby](也叫[ruby=constructor initializer]构造函数初始化器[/ruby])。 Part题: 常量... A. ...必须不被初始化 B. ...不能被改变 C. ...能被修改 |
5.2 成员初始化(二)
考虑下面的类:
此类有两个成员变量,regVar和constVar。它也有一个构造函数接受两个参数,用于初始化成员变量。 运行此代码会产生错误,因为它其中的一个成员变量是常量,不能在声明后改变值。 在这种例子中,成员初始化列表能用来给成员变量赋值。
注意到上面语法,初始化列表跟着构造函数形参。列表由冒号 (:) 开始,接着列出了要初始化的变量和要赋的值,以逗号分隔。 用语法变量(值)赋值。 初始化列表消除了在构造函数体内显式赋值的必要。而且,初始化列表不需以分号结束。 Part题: 你有一个类Student,有两个成员age和num。填空用对应的值在构造函数初始化器中初始化成员。
|
5.3 成员初始化(三)
让我们用独立头文件和源文件重写前例。 MyClass.h
MyClass.cpp
我们在构造函数中添加了cout语句来输出成员变量的值。 下一步就是在main创建一个我们类的对象,并用构造函数赋值。
构造函数用于创建对象,通过成员初始化列表用形参给成员变量赋值。 Part题: 用选项填空,在构造函数初始化列表中初始化成员,然后在构造函数体内输出。
|
5.4 成员初始化(四)
成员初始化列表可以用在普通变量上,必须用在常量上。即使成员变量不是常量,用成员初始化语法也很有用。 Part题: 常成员变量... A. ...必须在构造函数体内初始化 B. ...必须在构造函数初始化列表中初始化 C. ...怎么初始化随你 |
6 组合(第一部分)
6.1 组合(一)
在现实生活中,复杂对象基本是用更小更简单的对象建造的。举例,一辆车用金属框架、引擎、轮胎,和大量其他部件组装。这个过程叫[ruby=composition]组合[/ruby]。 在C++中,对象组合是通过将类作为其他类的成员而完成的。 这个示例程序演示了组合的使用。它包含Person和Birthday类,每个Person都会有一个Birthday对象作为其成员。 Birthday:
我们的Birthday类有三个成员变量。它也有通过成员初始化列表初始化成员的构造函数。 为了简洁,这个类在单文件中声明。你也可以分开源文件和头文件。 Part题: 填空在构造函数初始化列表中初始化Birthday类成员。
|
6.2 组合(二)
让我们给Birthday类也加一个printDate()函数。
这给我们的Birthday类添加了printDate()函数。 Part题: 用选项填空,创建类People,有三个私有整型成员:birthMonth,birthDay,birthYear。
|
6.3 组合(三)
接着,我们可以创建Person类了,其包含Birthday类。
Person类有name和Birthday成员,和一个初始化它们的构造函数。 确保包含了对应的头文件。 下一节继续讲组合! Part题: 用选项填空,声明People类,有两个私有成员:类型string的name和类型Birthday的dateOfBirth。别忘记包含string类型的头文件和Birthday.h。
|
7 组合(第二部分)
7.1 组合(四)
现在,我们的Person类有Birthday类的成员了:
组合常被用在有包含关系的对象上,如“[ruby=Person]人[/ruby]有[ruby=Birthday]生日[/ruby]”。 Part题: 填空声明People的构造函数,接受两个参数并初始化其私有成员:name和dateOfBirth。
|
7.2 组合(五)
让我们来给Person类添加一个printInfo()函数,输出对象的数据:
注意到我们可以调用成员bd的printDate()函数,因为它是Birthday类型,且定义了那个函数。 Part题: 用选项填空,定义printInfo()函数,输出[ruby="People's"]“人”的[/ruby][ruby=name]姓名[/ruby]和[ruby=birthdate]生日[/ruby],用dateOfBirth的printDate()函数。
|
7.3 组合(六)
现在我们有了Birthday类和Person类,可以去main,创建一个Birthday对象,并传给Person对象了。
我们创建了Birthday对象,代表日期1985年2月21日。接着,我们创建了一个Person对象,并将Birthday对象传给其构造函数。最终,我们用Person对象的printInfo()函数输出其数据。 一般来说,组合旨在将每个类保持得相对简单明了,只执行一项任务。这也允许每个子对象变得自包含,确保了重用性(我们可在其他类中使用Birthday类)。 Part题: 用选项填空,声明一个People类型的对象,其接受一个字符串作为第一个参数,一个Birthday对象作为第二个参数。在传递给People构造函数前,声明一个Birthday对象birthObj。
|
8 friend关键字
8.1 友元函数(一)
通常,一个类的私有成员不能被其外界访问。 然而,声明一个非成员函数为类的[ruby=friend]友元[/ruby]允许它访问这个类的私有成员。这是通过在类中包含一个这个外部函数的声明,并用friend关键字放在声明前来达成的。 下例中,someFunc(),不是类的成员函数,是MyClass的友元,可以访问其私有成员。
注意到我们给函数传递对象时,我们需要用&运算符通过引用传递。 Part题: 什么是声明友元的关键字? |
8.2 友元函数(二)
someFunc()函数在类外以正常函数的方式定义。它接受一个MyClass类型的对象作为其形参,并有权访问那个对象的私有数据成员。
someFunc()函数改变了对象的私有成员并输出其值。 要使其对象可访问,类需要在其定义内将函数声明为友元。如果类没有“将访问权限给出”,你就不能“使”这个函数成为类的友元。 Part题: 填上缺失的关键字,使函数foo成为MyClass类的友元。
|
8.3 友元函数(三)
现在我们可以在main中创建对象并调用someFunc()函数了。
someFunc()有权修改对象的私有成员并输出其值。 友元函数的典型用例是可能涉及访问两个不同类的私有成员的函数。 你可以在任意数量的类中声明一个函数是其友元。 Part题: 友元函数... A. ...只能修改公有成员 B. ...不能修改类的私有成员 C. ...可以修改包括私有成员在内的类的所有成员 |
9 this关键字
9.1 this(一)
C++中每个对象都可访问自己的地址,通过一种重要的指针,叫做this指针。 在成员函数中,this可以用来指代调用对象。 我们来创建一个示例类。
友元函数没有this指针,因为友元不是类的成员。 Part题: 用选项填空,声明一个Hannah类,有一个一参构造函数,一个printCrap()函数,和一个私有整型变量。
|
9.2 this(二)
printInfo()方法提供了三种输出类的成员变量的方式。
三种方式输出相同。 this是一个指向对象的指针,所以箭头选择符用于选择成员变量。 Part题: 对于一个名为mem的成员,选择两项可以正确输出它的方式。 A. cout << this->mem; B. cout << this>>mem; C. cout << mem; D. cout ** mem; |
9.3 this(三)
要看到结果,我们可以创建一个我们类的对象并调用成员函数。
所有三种访问成员变量的方式都有效。 注意到只有成员函数有this指针。 Part题: 什么是存储当前对象地址的关键字? |
9.4 this(四)
你也许在想可以直接指定变量时,为什么要用this关键字。 this关键字在[ruby=operator overloading]运算符重载[/ruby]中有举足轻重的地位,接着我们会讲到。 Part题: 对于类MyClass,有一个私有成员名为mem,填空在printValue()函数中通过this关键字输出其值。
|
10 运算符重载
10.1 运算符重载(一)
C++中大多运算符都可以被重定义或重载。 因此,用户定义的类型也可以使用各种运算符(例如你可以将两个对象相加)。 下表列出可以重载的运算符。 不能重载的运算符包括 :: | .* | . | ?: Part题: 用选项填空,声明类Sally,公有范围内只有构造函数。
|
10.2 运算符重载(二)
让我们声明一个示例类,用于演示运算符重载:
我们的类有两个构造函数和一个成员变量。 我们会重载+运算符,允许我们相加两个我们的类对象。 Part题: 填空声明类Sally,有两个构造函数,一个是默认构造函数(无参),另一个有一个整型参数。
|
10.3 运算符重载(三)
重载的运算符是函数,由关键字operator后跟要重载运算符的符号定义。 被重载的运算符与其他函数类似,有返回类型和形参列表。 在我们的例子中,我们会重载+运算符。它会返回一个我们类的对象并接受一个我们类的对象作为其形参。
现在,我们要定义函数的行为。 Part题: 哪个选项是在C++中重载运算符的关键字? A. operator B. this C. overload_it D. friend |
10.4 运算符重载(四)
我们需要我们的+运算符返回一个新的MyClass对象,其成员变量等于两个对象成员变量之和。
在这,我们声明了一个新res对象。之后我们将当前对象(this)和形参对象(obj)的成员变量之和赋给res对象的成员变量var。res对象作为结果返回。 这给我们在main中创建对象并用被重载的+运算符相加对象的能力。
有了重载运算符,你可以使用任何需要的自定义逻辑了。然而,无法改变运算符优先级、组合或操作数数量。 Part题: 用选项填空,定义Test类的被重载的+运算符。
|
本章测试
1. 填空声明Student类的析构函数。
2. 填空声明指向st的指针,st类型是Student,之后通过指针调用printAge()。
3. 填上缺失的关键字,声明一个Person类型的常量对象:
4. 对于类P,其有一个double型常成员weight,填空在构造函数初始化列表中初始化weight。
5. 什么是允许你指定一个函数是类的友元函数的关键字? A. this B. friendly C. friend D. make_friend 6. 对于类Test,其有两个私有成员名为mem和mem2,填空在printValues()函数内使用this关键字输出其值。
7. C++中重载运算符的关键字是? |