第10章 对象和类
建议下载下来用Typora软件阅读markdown文件
10对象和类
- 类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口–>C++程序员将接口(类定义)放在头文件中
- 类方法定义:描述如何实现成员函数–>并将实现(类方法代码)放在源代码文件中
细节:
- 使用#ifndef等来访问多次包含同一个文件
- 将类的首字母大写
- 控制访问关键字:private public protected
- C++对结构进行了扩展,使之具有与类相同的特性。他们之间唯一的区别是:结构的默认访问类型是public,类为private
- 通常,数据成员被放在私有部分中=>数据隐藏;成员函数被放在公有部分中=>公有接口
实现类成员方法
成员函数两个特殊的特征:
- 定义类成员函数时。使用作用域解析符(::)来标识函数所属的类;
- 类方法可以访问类的private组件。
1 | class Stock |
- set_tot()只是实现代码的一种方式,而不是公有接口的组成部分,因此这个类将其声明为私有成员函数(即编写这个类的人可以使用它,但编写带来来使用这个类的人不能使用)。
- 内联方法,
其定义位于类声明中的函数都将自动成为内联函数。因此Stock::set_tot()是一个内联函数。
在类声明之外定义内联函数
1
2
3
4
5
6
7
8
9
10
11class Stock
{
private:
...
void set_tot();
public:
...
};
inline void Stock::set_tot(){
total_val = shares * share_val;
}内联函数有特殊规则,要求每个使用它们的文件都对其进行定义。确保内联定义对多个文件程序中的所有文件都可用的最简便方法是:将内联定义放在头文件中
- 如何将类方法应用于对象?(对象,数据和成员函数)
所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员。但同一个类的所有对象共享一组类方法,即每种方法只有一个副本。
- 要使用类,要创建类对象,可以声明类变量,也可以使用new为类对象分配存储空间。
- 实现了一个使用stock00接口和实现文件的程序后,将其与stock00.cpp一起编译,并确保stock00.h位于当前文件夹中
- 类成员函数(方法)可通过类对象来调用。为此,需要使用成员运算符句点。
类的构造函数和析构函数
构造函数
原因:数据部分的访问状态是私有的,这意味着程序不能直接访问数据成员(私有部分数据)。程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数才能将对象初始化。—类构造函数
- 声明和定义构造函数
1
2//construtor prototype with some default argument
Stock(const string &co,long n=0,double pr=0.0);//原型
原型声明位于类声明的公有部分。
构造函数可能的一种定义
1 | Stock::Stock(const string &co,long n,double pr) |
- 注意“参数名co,n,pr不能与类成员相同.构造函数的参数表示不是类成员,而是赋给类成员的值。
- 区分参数名和类成员:一种常见的做法是在数据成员名中使用m_前缀 string m_company;;另外一种常见的做法是,在成员名中使用后缀_ string company_;
使用构造函数
显式调用
1
Stock food = Stock1("World cabbage",250,1.25);
隐式调用
1
2
3Stock garment("Furry Mason",50,2.5);
//等价于
Stock garment= Stock("Furry Mason",50,2.5);每次创建类对象(甚至使用new动态分配内存)时,C++都是用类结构函数。
1
Stock *pstock= new Stock("Electroshock Games",18,19.0);//对象没有名称,但可以使用指针来管理对象
默认构造函数
未提供显示初始值是,用来创建对象的构造函数。例:
1 | Stock fluffy_the_cat;//use the default constructor |
- 当且今当没有定义任何构造函数时,编译器才会提供默认构造函数。
- 为类定义了构造函数后,程序员就必须为它提供默认构造函数
- 如果提供了非默认构造函数(如Stock(const string &co,long n,double pr);),但没有提供构造函数,下面声明将出错(禁止创建未初始化对象)
1
Stock stock1;
如何定义默认构造函数:
方法1:给已有构造函数函数的所有参数提供默认值
1 | Stock(const string &co="Error",int n=0,double pr=0.0); |
方法2:通过函数重载来定义另外一个构造函数—一个没有参数的构造函数
1 | Stock(); |
为Stock类提供一个默认构造函数:
1 | //隐式初始化所有对象,提供初始值 |
使用默认构造函数:
1
2
3Stock first;//隐式地调用默认的构造函数
Stock first = Stock();//显式地
Stock * prelief=new Stock;//隐式地然而不要被废默认构造函数的隐式形式所误导:
1
2
3Stock first("Concrete Conglomerate");//调用构造函数
Stcok second(); //声明一个函数
Stock third; //调用默认构造函数
析构函数
- 对象过期是,程序将自动调用该特殊的成员函数。析构函数完成清理工作
- 如果构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。
什么时候调用析构函数?这由编译器决定,不应在代码中显示地调用析构函数
- 如果创建的是静态存储类对象,其析构函数将在程序结束时自动被调用。
2 .如果创建的是自动存储类对象,则其析构函数将在程序执行完代码块时(该对象是在其中定义的)自动被调用。 - 如果对象是通过new创建的,则它将驻留在栈内存或自由存储区中,当使用delete来释放内存时,其析构函数将自动被调用。
程序可以创建临时变量对象来完成特定操作,在这种情况下,程序将在结束对该对象的使用时自动调用其析构函数。 - 总的来说:类对象过期时(需要被销毁时),析构函数将自动被调用。因此必须有一个析构函数。如果程序员没有提供析构函数,编译器将隐式地声明一个默认构造函数。
C++列表初始化
只要提供与某个构造函数的参数列表匹配的内容,并用大括号将它们括起。
1 | Stock hot_tip = {"Derivatives Plus Plus",100 ,45.0};//构造函数 |
前两个声明中,用大括号括起的列表与下面的构造函数匹配:
1 | Stock(const string &co,long n=0,double pr=0.0);//原型 |
因此,用该构造函数来创建这两个对象。创建对象jock时,第二和第三个参数将默认值为0和0.0。第三个声明与默认构造函数匹配,因此将使用该构造函数创建对象temp。
const成员函数
1 | void Stock::show() const;//promises not to change invoking object |
以这种方式声明和定义的类成员函数被称为const成员函数。就像应景可能将const引用和指针作函数参数一样,只要类方法不修改调用对象,就应该将其声明为const
this指针
有的方法可能涉及两个对象,在这种情况下需要使用C++的this指针(比如运算符重载)
提出问题:如何实现:定义一个成员函数,查看两个Stocl对象,并返回股价高的那个对象的引用。
- 最直接的方法是,返回一个引用,该引用指向股价总值较高的对象,因此,用于比较的方法原型如下:
1
const Stock & topval(const Stock & s) const;//该函数隐式地访问一个对象,并返回其中一个对象
- 第一个const:由于返回函数返回两个const对象之一的引用,因此返回类型也应为const引用
- 第二个const:表明该函数不会修改被显式访问的对象
- 第三个const:表明该函数不会修改被隐式访问的对象
调用:
1 | top = stock1.topval(stock2);//隐式访问stock1,显式访问stock2 |
- this 指针用来指向调用成员函数的对象(this被作为隐藏参数传递给方法)。
- 每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象
- 如果方法需要引用整个调用对象,可一个使用表达式
*this
。
实现:
1 | const Stock & topval(const Stock & s) const |
创建对象数组
1 | Stock stocks[4]={ |
类作用域
回顾:
全局(文件)作用域,局部(代码块)作用域
可以在全局变量所属的任何地方使用它,而局部变量只能在其所属的代码块中使用。函数名称的作用域也可以是全局的,但不能是局部的。
类作用域
在类中定义的名称(如类数据成员和类成员函数名)的作用域为整个类。
类作用域意味着不能从外部直接访问类成员,公有函数也是如此。也就是说,要用调用公有成员函数,必须通过对象。
使用类成员名时,必须根据上下文使用,直接成员运算符(.),间接成员运算符(->)或者作用域解析符(::)
作用域为类的常量
下面是错误代码
1
2
3
4
5
6class Bakery
{
private:
const int Months=12;//错误代码
double cots[Months];
}这是行不通的,因为声明类只是描述了对象的形式,并没有创建对象。因此在创建对象之前,并没有用于存储值的空间。
解决:
- 方法一:使用枚举
1
2
3
4
5
6class Bakery
{
private:
enum{Months=12};
double costs[Months];
}
- 这种方式声明枚举并不会创建类数据成员。也就是说,所有对象都不包含枚举。另外,Months只是一个符号名称,在作用域为整个类的代码中遇到他时,编译器将用12来替换它。
- 方法二:使用关键字static
1
2
3
4
5
6class Bakery
{
private:
static const int Months=12;
double costs[Months];
}
- 这将创建一个名为Months的常量,该常量与其他静态变量存储在一起,而不是存储在对象中。因此,只有一个Months常量,被所有Bakery对象共享。
作用域内枚举(C++11)
传统的枚举存在一些问题,其中之一是两个枚举定义的枚举量可能发生冲突。
1 | enum egg{Small,Medium,Large,XLarge}; |
这将无法通过编译因为egg Small和t_shirt Small位于相同的作用域内,他们将发生冲突。
新枚举
1
2enum class egg{Small,Medium,Large,XLarge};
enum class t_shirt{Small,Medium,Large,Xlarge};作用域为类后,不同枚举定义的枚举量就不会发生冲突了。
class也可以用关键字struct来代替
使用:
1 | egg choice = egg::Large; |
注意:作用域内枚举不能隐式地转换为整型,下面代码错误
1 | int ring = Floyd;//错误 |
但是必要时可以进行显式转换
1 | int Floyd = int(t_shirt::Small); |
抽象数据类型ADT(Abstract Data Type)
- 以抽象的方式描述数据类型,而没有引入语言和细节