过程性编程首先考虑要遵循的步骤,然后考虑如何表示这些数据。
面向对象编程首先不仅考虑表示数据,还要考虑怎样使用数据,其中数据(数据成员)和使用数据(成员函数或方法)组合成了一个类,可以利用类创建它的实例——对象。
2. 抽象和类抽象是将问题的本质特征抽象出来,并根据特征来描述解决方案。
2.1 类型是什么指定基本类型完成了三项工作:
- 决定数据对象需要的内存数量
- 决定如何解释内存中的位
- 决定可使用数据对象执行的操作或方法
对于内置类型来说,这些有关操作的信息都内置到编译器中,但在C++中定义自定义的类型时,必须提供有关操作的信息。
2.2 C++中的类类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。
类规范由两个部分组成:
- 类声明:数据成员描述数据部分,成员函数(也叫方法)描述为公有接口
- 类方法定义:描述如何实现类成员函数
接口的概念:
接口是一个共享框架,供两个系统交互时使用。
对于类,我们说是公共接口。公共是使用类的程序,接口由类提供的方法组成。
提示:要使用某个类,必须了解其公共接口;要编写类,必须创建其公共接口。
通常,C++程序员将接口(类定义)放在头文件中,并将实现(类方法的代码)放在源代码文件中。
例子:
//头文件中包含类定义
#ifndef FLAG //防止重复包含类定义
#define FLAG
class Data //class关键字指出这些代码定义了一个类设计
{
private: //可以不必使用关键字private,类中刚开始默认是private访问控制
int x; //数据成员
float y; //数据成员
void set_xy() //成员函数
{
x = 1;
y = 1.2;
}
public:
void show(); //成员函数可以就地定义,也可以用原型表示,再在其他地方给出函数定义
}; //分号不能掉
#endif
private和public描述了对类成员的访问控制。所以类对象只能直接访问类成员的public(公有)部分,要想访问对象的private(私有)部分,需要通过公有成员函数。
数据隐藏:防止程序直接访问数据。用户只需要知道成员函数的功能。
封装:将实现细节放在一起并将它们与抽象分开。
类和结构的区别:C++扩展了结构的功能,它们之间唯一的区别在于:结构的默认访问类型是public,而类为private。
2.3 实现类成员函数为那些由类声明中的原型表示的成员函数提供代码。它有两个特殊的特征:
- 定义成员函数时,使用**作用域解析运算符(::)**来标识函数所属的类,因为函数属于类作用域
- 类方法可以访问类的private组件
定义位于类声明中的函数都将自动成为内联函数。也可以在类声明之外定义成员函数,将其成为内联函数,记得加上关键字inline。内联函数的特殊规则要求每个使用它的文件都对其进行定义,最简便的方法就是将内敛定义放在定义类的头文件中。
创建对象和声明变量一样。
要想使用对象的公有成员,得通过成员运算符(.)。在OOP中,调用成员函数被称发送消息。
创建的每个对象都有自己的存储空间,用于存储内部变量和类成员;但是同一个类的所有对象共享同一组方法。
2.4 使用类用户只需知道类的接口即可。
2.5 修改实现记住:修改方法的实现时,不应影响客户程序的其他部分。
2.6 小结类设计步骤:提供类声明、实现类成员函数、像使用基本类型一样使用类。
典型的类声明格式:
class ClassName //关键字class 和 自定义类名
{
private:
data member declarations //数据成员声明
public:
member function prototypes //成员函数原型
};
3. 类的构造函数和析构函数
C++目标之一是让使用类对象就像使用标准类型一样,但是类对象直接创建数据成员没有初始值,可能在调用成员函数时直接使用的是随机值,所以需要在创建对象时对它进行初始化。
C++为了解决这个问题提供了构造函数,目的是在创建对象时,自动对对象进行初始化。
构造函数不同于其他函数的特征:
- 名称与类名相同
- 没有声明类型
//假设有一个类Stock,类中有数据成员int x, float y, double z
//在类中已经定义构造函数原型:Stock(int x_, float y_, double z_ = 0);
//在类外定义构造函数如下:
Stock::Stock(int x_, float y_, double z_) //注意:构造函数的参数名不能跟类中数据成员名相同
{
x = x_;
y = y_;
z = z_;
}
3.2 使用构造函数
使用构造函数有两种方式:显式调用构造函数、隐式调用构造函数。
Stock A = Stock(1, 1.1, 1.1); //显式调用构造函数 Stock A(1, 1.1, 1.1); //隐式调用构造函数 //使用new创建对象,调用构造函数 Stock *A = new Stock(1, 1.1, 1.1);
注意:可以使用对象成员运算符调用方法,但是无法调用构造函数。
3.3 默认构造函数默认构造函数是在未提供显式初始值时,用来创建对象的构造函数。
如果类中没有提供任何构造函数,则C++自动提供默认构造函数。
定义默认构造函数的方式有两种:
- 给已有构造函数的所有参数提供默认值
- 通过函数重载定义一个没有参数的构造函数
当对象过期时,程序将自动调用一个特殊的成员函数——析构函数,来完成对象的清理工作。如果类中没有提供析构函数,则编译器隐式声明一个默认的构造函数。
如果构造函数使用的是new分配对象内存,则析构函数需要用delete释放对象内存。
析构函数不同于普通成员函数的特征:
- 函数名就是类名,类名前加~
- 没有声明类型
- 没有参数
在默认情况下,将一个对象赋给另一个对象时,会将原对象的每个数据成员内容赋给目标对象相应的数据成员。
构造函数还可以用于给新对象赋值,但是会创建一个临时对象。
C++11可以使用列表初始化类对象,只要列表中提供与某个构造函数的参数列表相匹配的内容。
const成员函数,为了确保成员函数不会修改对象,应在函数的括号后面加上关键字const。
另外,除了显式和隐式调用构造函数外,还可以使用赋值将对象初始化为一个值,前提是构造函数只接受一个参数。可关闭这个特性。
4. this指针为了解决调用成员函数来处理两个对象之间的比较问题,引入了this指针。当然也可以用运算符重载解决。
this指针指向用来调用成员函数的对象,每个成员函数都有this指针,注意this是对象的地址,*this才是对象本身。
例子:
//假设有类Stock,topval方法和数据total_val已经在类中声明
const Stock & Stock::topval(const Stock &s) const //函数返回的是const Stock类型的引用
{
if (s.total_val > total_val) //返回的是参数对象
return s;
else //返回调用方法的对象
return *this;
}
5. 对象数组
声明对象数组的方式与声明标准类型数组相同。
但是要注意对象数组的初始化,默认情况下声明对象数组,会调用默认的构造函数,也可以显示调用构造函数来为数组元素初始化,没有被初始化调用默认构造函数。
当然,如果类中有多个构造函数,可以为数组元素指定特定的构造函数。
例如:
const int Size = 4;
Stock stocks[Size] = {
Stock("NanoSmart", 12.5, 20),
Stock("NanoSmart", 12.5, 20),
Stock("NanoSmart", 12.5, 20),
Stock("NanoSmart", 12.5, 20)
}; //数组对象都初始化为了相同值
6. 类作用域
在类中定义的名称的作用域为整个类,这些名称在类中是已知的,在类外是未知的,需要根据上下文使用成员运算符(.)或间接成员运算符(->)或者作用域解析运算符(::)
6.1 作用域为类的常量在创建对象前,没有存储数据成员的值的空间。为了创建一个由所有对象共享的常量有两种方法:
- 在类中声明一个枚举,枚举为整型常量提供作用域为整个类的符号名称。只是为了创建符号常量,不需要提供枚举名。
- 使用关键字static,声明的常量将存储于静态区,而不是存储于对象中,所以只有一个常量共对象共享。
传统枚举可能出现在同一作用域的同名枚举量,从而引发冲突。
- 为了解决这个问题,C++11提供一种新枚举,其枚举量作用域为类。其声明格式如下:
enum class 枚举名 {枚举量列表}; //class可用struct代替
-
C++11提供了作用域内枚举的安全,例如:常规枚举量会自动转换为整型,但是作用域内枚举不行,但是必要时可以显示类型转换。
-
默认情况下,作用域内枚举的底层数据类型是int,但是也可以指定数据类型,例如:
enum class : short 枚举名 {枚举量列表};
ADT以通用方式描述数据类型,而没有引入语言或实现细节。类概念非常适合于ADT方法。



