4.1 对象与类的基本内容

OOP特性:抽象、封装和数据隐藏、多态、继承和代码的可重用性。抽象是通往用户定义类型的捷径,在C++中,用户定义类型指的是实现抽象接口的类设计。类将数据表示和操纵数据的方法组合成一个整洁的包。接口是一个共享框架,供两个系统交互时使用。这里系统就是类对象,对于类来说,是公共接口。公共(public)指使用类的程序,而接口就是由编写类的人提供的方法。

类一般由两个部分组成:

  • 类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口,
  • 类方法定义:描述如何实现类成员函数。通常,我们将接口放在头文件中,并将实现(类方法的代码)放在源代码文件中。成员函数可以就地定义,也可以用原型表示。
#ifndef FRUIT_H_
#define FRUIT_H_
#include <string>
#include <iostream>
class fruit{
	private:
		std::string name_;
		long weight_;
		double price_;
		double all_value;
		void set_all(){ 
			all_value = weight_*price_;
		} 
	public:
		fruit();
		fruit(const std::string & name, long weight, double price);
//		fruit(const std::string & str = "None", long wgt = 0, double prc = 0.0);
		~fruit();
		void sell(long weight);
		void purchase(long weight, double price);
		void adjust(double price);
		void show() const;
		void show_all() {
			set_all(); 
			std::cout << "all_value:" << all_value << std::endl; 
		} 		
}; 
#endif

1 访问控制

关键字private和public描述对成员的访问控制,使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数或友元函数来访问对象的私有成员。因此,公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。防止程序直接访问数据被称为数据隐藏。类设计尽可能将公有接口与实现细节分开,公有接口表示设计的抽象组件。将实现细节放在一起并将他们与抽象分开被称为封装。数据隐藏是一种封装,将实现细节隐藏在私有部分也是一种封装。将类函数定义和类声明放在不同的文件中也是封装的例子。

无论类成员是数据成员还是成员函数,都可以在类的公有部分或私有分布中声明它。由于数据隐藏是OOP主要目标之一,因此数据项通常放在私有部分,组成类接口的成员函数放在公有部分。通常也可以使用私有成员函数来处理不属于公有接口的实现细节。不必在类声明中使用关键字private,因为这是类对象的默认访问控制。

2 实现类成员函数

与常规函数定义类似,但还有两个特征:定义成员函数时,使用作用域解析运算符::来标识函数所属类;类方法可以访问类的private组件。由于成员函数属于同一个类,他们不必使用作用域解析运算符,就可以相互使用,是可见的。然而,在声明和方法定义之外使用类成员函数,就另当别论了。实现类成员函数是创建类的第二部分,将他们放在一个独立的文件中,需包含头文件。

3 内联函数

其定义位于类声明中的函数都将自动成为内联函数。也可以在类声明之外定义成员函数,并使其成为内联函数,需要在实现部分中定义函数时使用inline。

内联函数的特殊规则要求在每个使用他们的文件中都要对其进行定义,确保内联定义对多个文件程序中的所有文件都可用,最简便的方法是:将内联定义放在定义类的头文件中。也可将内联函数放在一个独立的文件中。

总之,在类声明中定义方法等同于用原型替换方法定义,然后在类声明的后面将定义改写成内联函数。

4 类对象调用类方法

将类名视为类型名,即可创建对象(类的实例)。对象通过成员运算符使用成员函数。所创建的每个对象都有自己的存储空间,用于存储其内部变量和类成员,但同一个类的所以对象共享同一组类方法,即每种方法只有一个副本。所有的类对象调用的同种函数成员将执行同一个代码块,只是将这些代码用于不同的数据。在OOP中,调用成员函数被称为发送消息,因此将同样的信息发送给两个不同对象,将调用同一个方法,但该方法被用于两个不同的对象。

5 类的构造函数和默认构造函数

构造函数用于创建对象,将值赋给它们的数据成员,它的名称与类名相同。程序声明对象时,自动调用构造函数。它没有声明类型的(没有返回值)。构造函数的参数表示的不是类成员,而是赋给类成员的值,所以参数名不能与类成员名称相同。它的原型(声明)位于类声明的公有部分。

在完成构造函数的声明和定义之后,就有两种使用构造函数来初始化对象的方式:

  • 方式一:隐式地调用构造函数:Fruit f1(实参1,实参2...);
  • 方式二:显式地调用构造函数:Fruit f2 = Fruit(实参1,实参2...);
  • 方式三:使用对象指针:Fruit f3 = new Fruit(实参1,实参2...);

注意:c++允许编译器对第二种方式解释不同的行为:一种行为与方式一一样,还有一种行为是让构造函数创建临时对象,然后将临时对象赋给f2,并丢弃临时对象,即为临时对象调用析构函数。

若在声明对象时未提供初始化值,形如Fruit f1;Fruit f2 = Fruit();,在有构造函数的条件下,就必须提供默认构造函数(用于未提供初始化值的对象声明),否则声明对象会报错。当然,类中并没有任何声明和定义构造函数,c++自动提供默认构造函数。Fruit::Fruit(){},这是C++自动提供的默认构造函数版本。

定义默认构造函数的方式也有两种(不要同时采用,只能有一个默认构造函数):

  • 给已有参数的构造函数添加默认值
  • 利用函数重载,定义一个没有参数的构造函数

6 类的析构函数

完成对象过期后的清理工作。若构造函数中没有使用new,则析构函数实际上没有需要完成的工作,程序员可以不提供,由编译器生成一个隐式析构函数即可。Fruit的析构函数原型:~Fruit();,无返回值和声明类型,要加~。

通常不显式的调用析构函数(有例外情况),静态存储类对象的析构函数在程序结束时自动被调用;自动存储类对象的析构函数在程序执行完代码块时自动被调用;通过new创建的对象,它在栈内存或自由存储区,当使用delete释放内存时,其析构函数将自动被调用;临时对象将在其结束使用时自动调用析构函数。

由于自动变量被放在栈中,因此最后创建的对象将最先被删除,最先创建的对象最后被删除。

7 常量成员函数

将const关键字放在函数的括号后面,可保证函数不会修改调用对象。函数原型为void show() const;,函数头为void fruit::show() const。可以满足常量对象的调用。

8 this指针

this指针指向用来调用成员函数的对象。如果方法需要引用整个调用对象,则可以使用表达式*this。

9 类作用域

在类中声明的名称(类数据成员名和类成员函数名)的作用域都为整个类,作用域为整个类的名称只在该类中已知的,在类外是不可知的。在类声明另外,类作用域意味着不能从外部直接访问类的成员,公有成员也是如此的。要想调用公有成员函数,必须通过对象。同样,定义成员函数,必须使用作用域解析运算符。

通常想创建所有对象共享的常量,即类作用域常量,由于声明类只是描述了对象的形式,并没有创建对象,因此在创建对象前,并没有用于存储的空间。但仍有两种办法实现:

  • 在类中声明一个枚举。在类声明中声明的枚举的作用域为整个类,因此可以用枚举为整型常量提供作用域为整个类的符号名称。用这种方式声明枚举并不会创建类数据成员,也就是说,所有对象中都不包含枚举。编译器会用12代替Months。
  • 使用关键字static。常量将与其他静态变量存储在一起,而不是存储在对象中。
private:
// 方式一
    enum{ Months = 12};
    double cost[Months];
// 方式二
    static const int Months = 12;
    double cost[Months];

10 构造函数的成员初始化列表语法

对于常量数据成员,可以对它进行初始化,但不能给它赋值。如何理解呢?调用构造函数时,对象将在大括号内的代码之前创建和分配成员变量内存,然后进入括号中进行常规赋值并存储到内存中。对于常量成员来说,必须在执行到构造函数体前(创建对象时)进行初始化,这时就用到了成员初始化列表。

class Basket{
    private:
        enum {SIZE = 10};
        const int bsize;
        ...
    public:
        Basket(int bs = SIZE);
        ...
};


Basket::Basket(int bs) : bsize(bs),xxx(xxx) // 初始化成员列表语法
{
    ... = ...
}

注意:

  • 只有构造函数才可以用到初始化列表语法
  • 对于常量数据成员和被声明为引用类的类成员,都必须用这种语法
  • 对于其他普通的数据成员,使用该语法可以提高效率
  • 数据成员被初始化的顺序与他们出现在类声明中的顺序相同,与初始化列表中的排列顺序无关。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vinkuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值