《C++PrimerPlus 6th Edition》第10章 对象和类 要点记录
三个月没更了,继续走起~
本章内容
- 过程性编程和OOP
- 类概念、类定义和实现
- 公有类访问和私有类访问
- 类的数据成员
- 类方法(类函数成员)
- 创建和使用类对象
- const成员函数
- this指针
- 创建对象数组
- 类作用域
- 抽象数据类型
10.1 过程性编程与面向对象编程
- OOP的主要目标之一:数据隐藏。因此,数据项通常放在私有部分,组成类接口的成员函数放在公有部分;否则,就无法从程序中调用这些函数。
10.2 抽象与类
- 内联方法:其定义位于类声明中的函数都将自动成为内联函数。当然,也可以在类声明之外定义成员函数,并使其成为内联函数。为此,只需在类实现部分(声明时不用)中定义函数时使用
inline
限定符即可。 - 类数据成员与类函数成员的区别:对于内部变量和类成员,每个类对象都有自己的存储空间;但同一个类的所有对象共享同一组类方法,即每种方法只有一个副本。
- 要创建类对象,可以声明类变量,也可以使用
new
为类对象分配存储空间。可以将对象作为函数的参数和返回值,也可以将一个对象赋给另一个。 - 对于特定格式设定的类函数,设计要旨为:将格式修改限定在实现文件中,以免影响程序的其他方面。给个模板自行体会:
void Stock::show(){ std::streamsize prec = std::cout.precision(3); //old value //... std::cout.precision(prec); //reset to old value //store original flags std::ios_base::fmtflags orig = std::cout.setf(std::ios_base::fixed); //... //reset to stored value std::cout.setf(orig, std::ios_base::floatfield); }
10.3 类的构造函数和析构函数
-
类数据部分的访问状态
通常是私有的(为了符合OOP的要旨),这意味着程序不能直接访问数据成员,从而只能通过成员函数来访问数据成员,因此需要构造函数将对象成功地初始化。 -
构造函数的参数表示的不是类成员,而是赋给类成员的值。因此,参数名不能与类成员相同,一种做法是在类数据成员名中使用m_前缀或后缀_
class A{ private: string m_name; long m_money; }; class B{ private: string name_; long money_; };
-
构造函数的使用
- 显式调用。
Stock food = Stock(para1, para2, ...);
- 隐式调用。
Stock food(para1, para2, ...);
- 动态分配。
Stock *pstock = new Stock(para1, para2, ...);
注:无法使用对象来调用构造函数,因为在构造函数构造对象前,对象是不存在的。
- 显式调用。
-
默认构造函数
- 在未提供显式初始值时,用来创建对象的构造函数。
Stock a; //调用默认构造函数
- 当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义构造函数后,程序员就必须为它提供默认构造函数。如果提供了非默认构造函数,但没有提供默认构造函数,则下面的声明将出错:
Stock stock1;
- 定义默认构造函数的两种方式:①给已有构造函数的所有参数提供默认值:
Stock(const string& co="Error", int n=0, double pr=0.0);
;②通过函数重载定义另一个构造函数——一个没有参数的构造函数:Stock();
。以上方法不能同时采用,因为只能有一个默认构造函数。
注:在设计类时,通常应提供对所有类成员做隐式初始化的默认构造函数。 - 隐式调用默认构造函数时,不要使用圆括号:
Stock first(para1, para2); //调用构造函数 Stock second(); //声明一个以Stock为返回类型的函数 Stock third; //隐式调用默认构造函数
- 在未提供显式初始值时,用来创建对象的构造函数。
-
析构函数:原型只能为:
~Stock()
-
关于构造函数与析构函数的调用时机,书上有个例子,看着就能理解了
#include<iostream> #include "stock10.h" int main(){ { using std::cout; cout<<"Using constructors to create new objects\n"; Stock stock1("NanoSmart", 12, 20.0); //syntax1 stock1.show(); Stock stock2 = Stock("Boffo Objects", 2, 2.0); //syntax2 stock2.show(); cout<<"Assigning stock1 to stock2:\n"; stock2 = stock1; cout<<"Listing stock1 and stock2:\n"; stock1.show(); stock2.show(); cout<<"Using a constructor to reset an object\n"; stock1 = Stock("Nifty Foods", 10, 50.0); //临时对象 cout<<"Revised stock1:\n"; stock1.show(); cout<<"Done!\n"; } return 0; }
解读:
- 大花括号的作用是便于在main()返回前能看见析构函数的调用。
- 对于
syntax2
,有两种方式来执行:①与syntax1
相同,创建一个对象,把其数据成员初始化为指定的值;②允许调用构造函数来创建一个临时对象,然后将该临时对象复制到stock2
中,并丢弃它。因此,丢弃临时对象时会执行临时对象的析构函数。 - 与结构赋值一样,在默认情况下,给类对象赋值时,将把一个对象的每个数据成员复制给另一个。
- 有些编译器可能要过一段时间才删除临时对象,因此析构函数的调用将延迟。
- 最先创建的对象将会最后被删除,这是因为自动变量放在了栈中。
- 以下两条语句有根本性的区别:
第一句是初始化,它创建指定值的对象,可能会创建临时对象(也可能不);第二条是赋值,它总会导致在赋值前创建一个临时对象,因此为了高效率尽量采用初始化方式。Stock stock2 = Stock("Boffo Objects", 2, 2.0); stock1 = Stock("Nifty Foods", 10, 50.0);
-
C++11列表初始化:
Stock hot_tip = {"Derivativess Plus Plus", 100, 45.0};
-
const成员函数:放在成员函数括号的后面,用于保证函数不会调用修改对象。如
void show() const;
,同样其函数定义的开头应为:void Stock::show() const;
,只要方法不修改调用对象,就应将其声明为const。 -
小结:
- 构造函数没有声明类型
- 如果构造函数只有一个参数,则将其对象初始化为一个与参数类型相同的值时,该构造函数将被调用:
第11章将介绍一种关闭这项特性的方式,因为它可能带来令人不愉快的意外。Bozo dribble = Bozo(44); // primary form Bozo roon(66); // secondary form Bozo tubby = 32; // special form for one-argument constructors
- 如果构造函数使用了
new
,则必须提供使用delete
的析构函数。
10.4 this指针
- this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)。
- 注意,要返回类对象时,应为
*this
,而非this
,后者是对象的地址。
10.5 对象数组
- 初始化对象数组的方案:首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的数组元素中。因此,要创建类对象数组,则这个类必须有默认构造函数。
10.6 类作用域
- 在类中直接用const声明数据成员是行不通的,因为在创建对象前,将没有用于存储值的空间。
- 两种创建作用域为类的常量的方法:
- 在类中声明一个枚举:
enum {Months = 12};
,这种方式声明枚举不会创建类数据成员。也就是说,所有对象都不包含枚举。另外,Months
只是个符号名称,在作用域为整个类的代码中遇到它时,编译器将用12
替换它。由于使用枚举只是为了创建符号常量,并不打算创建枚举类型的变量,因此不需要提供枚举名。 - 使用关键字
static
:static const int Months = 12;
这将创建一个名为Months
的常量,该常量将与其他静态变量存储在一起,而不是存储在对象中。
- 在类中声明一个枚举:
- 作用域枚举(C++11):(用
struct
代替class
也行,但无论如何都需要使用枚举名来限定枚举量)
作用:设置枚举的作用域为类,将enum class egg { Small, Medium, Large, Jumbo} enum class t_shirt { Small, Medium, Large, Xlarge} egg choice = egg::Large; t_shirt playboy = t_shirt::Large;
egg
和t_shirt
枚举的作用域区分开来,避免了冲突。
另外,C++11还提高了枚举的类型安全。有些情况下,常规枚举可以自动转换为整型,但作用域内枚举不能隐式地转换为整型,不过在必要时可以进行显式类型转换。
C++11中,枚举的底层整型类型默认为int
,但也可以自主做选择:enum class : short pizza{ Small, Medium, Large, Xlarge};
10.7 抽象数据类型(Abstract Data Type, ADT)
- 以栈为例举了个例子,数据结构中也提过。
待深入的问题
- 类的头文件、类定义文件以及类使用文件之间的链接方式
习题
见我的github(上传后会把链接打上)。
欢迎各位大佬们于评论区进行批评指正~