作为面向对象语言,C++提供了使用对象以及定义对象的工具,些工具叫做类。本节讲述与类以及对象的使用有关概念,如编写类定义,定义方法.............
我们都很熟悉Microsofe Excel提供了执行数学计算的功能,这个电子表格使用了两个基本类:Spreadsheet和SpreadsheetCell。每个Spreadsheet对象都包含了若干SpreadsheetCell对象。此外,SpreadsheetApplication类管理 Spreadsheet集合。
编写类有两个要素:定义类本身以及定义类的方法。
下面开始尝试编写一个简单的SpreadsheetCell类
Class SpreadsheetCell { public: void setValue(double inValue); double getValue() const; protected: double mValue; };每个类定义都以关键字class以及类名称开始。类定义是一条C++语句,因此必须用分号结束。类中的每个方法和成员都可以使用三种访向说明符之一来说明:public、protected和 private.访向说明符将应用于其后声明的所有方法和成员,直到遇到另一个访向说明符。在SpreadsheetCell类中,setValue()和getValue()方法是public访向,在mValue成员是protected访向。
类的默认访向说明符是private:在第一个访向说明符这前声明的所在有方法和成员的访向都是私有的。
Class SpreadsheetCell { void setValue(double inValue); // now has private access public: double getValue() const; protected: double mValue; };与类相似,在C++中结构(struct)以及共用体(union)也可以拥有方法。但是实际上,唯一的区别就是结构(struct)以及共用体(union)的默认访向说明符是public,而类的默认访向说明符是private。如SpreadsheetCell类可以用结构重写,所下所示:
struct SpreadsheetCell { void setValue(double inValue); double getValue() const; protected: double mValue; };在些,总结这三种访向说明符的含义。
访向说明 | 含义 |
使用场合 |
public | 任何代码都可以调用对象public方法或访向public成员 | 访向private和protected数据成员的方法。 |
protected | 类的任意方法都可以调用protected方法,或访向protected成员。子类的方法可以调用超类的protected方法或成员 | 多数数据成员都是protected |
private | 只有这个类的方法可以调用private方法,并访向private成员,子类中的方法不能访向超类的private方法或成员 | 只有在限制子类访向的时候才使用 |
前面SpreadsheetCell类定义中以让你创建类的对象,然而,如果我们试图调用SetValue()或getValue()方法,链接器将发出警告,指出方法没有定义。SpreadsheetCell类中两个方法的定义:
#include "SpreadsheetCell.h" void SpreadsheetCell::setValue( double inValue){ mValue = inValue; } void SpreadsheetCell::getValue() const{ return mValue; }注意:两个冒号::被称为作用域解析运算符(scope resolution operator).在此环境中,这个语法告诉编译器将要定义的setValue()方法是SpreadsheetCell类的一部分。此外还要注意当定义方法的时候不要重复使用访向说明符。
为了让SpreadsheetCell支持文本数据,下面对类定义进行修改:
Class SpreadsheetCell { public: void setValue(double inValue); double getValue() const; void setString(string, instring); string getString() const; protected: string doubleTostring(double inValue) const; double stringToDouble(string instring) const; double mValue; string mString };下面是这些方法实现:
#include "SpreadsheetCell.h" #include <iostream> #include <sstream> using namespace std; void SpreadsheetCell::setValue(double inValue){ mValue = inValue; mString = doubleToString(mValue); } double SpreadsheetCell::getValue() const{ return mValue; } void SpreadsheetCell::setString(string inString){ mString = inString; mValue = stringToDouble(mString); } string SpreadsheetCell::getString() const{ return mString; } string SpreadsheetCell::doubleToString(double inValue) const{ ostringstream ostr; ostr << inValue; return ostr.str(); } double SpreadsheetCell::stringToDouble(string inString) const{ double temp; istringstream istr(inString); istr >> temp; if (istr.fail() || !istr.eof()) { return 0; } return temp; }
注意:每个设置方法都调用了辅助方法来执行转换,这种技术使得mValue和mString都始终有效。
在堆栈中创建并使用了SpreadsheetCell的对象。
SpreadsheetCell myCell, anotherCell; myCell.setValue(8); anotherCell.setString("5.2"); cout << "cell 1: " << myCell.getValue() << endl; cout << "cell 2: " << anotherCell.getValue() << endl;创建对象类似于声明简单变量,区别在于变量类型是类名称,“myCell.setValue(8)”中的.称为“点”运算符。这个运算符允许你调用对象的方法。如果对象中有公有数据成员,也可用点运算访向。
还可用new动态分配对象:
SpreadsheetCell* myCellp = new SpreadsheetCell(); myCellp->setValue(3.7); cout << "cell 1: " << myCellp->getValue() << " " << myCellp->getString() << endl; delete myCellp; myCellp = nullptr;当在堆中创建对象时,通过“箭头”运算符调用对象的方法或访向数据成员。箭头运算符组合了解除引用运算符(*)以及访向方法或者成员运算符(.)。为了避免发生内存错误,我们可以使用智能指针:
shared_ptr<SpreadsheetCell> myCellp(new SpreadsheetCell()); myCellp->setValue(3.7); cout << "cell 1: " << myCellp->getValue() <<" "<< myCellp->getString() << endl;
对象的生命周期涉及到3个活动:创建、销毁以及赋值。理解对象什么时候被创建、销毁、赋值以及如何定制这些行为很重要。
在声明对象或者使用new、new[ ]显式分配空间的时候,对象就会被创建。当创建对象的时候,同时创建了内嵌的对象。例如:
#include <string> Class MyClass { protected: std::string mName; }; int main ( ){ MyClass obj; return 0; }
从语法上讲,构造函数是与类同名的一个方法。构造函数没有返回值,可以有也可以没有参数,没有参数的构造函数称为默认构造函数。
class SpreadsheetCell { public: SpreadsheetCell(double initialValue); // Remainder of the Class definition omitted for brevity };
就像你必须提供普通方法的实现那样,也必须提供构造函数的实现:
SpreadsheetCell::SpreadsheetCell(double initialValue){ setValue(initialValue); }
在堆栈中使用构造函数
在堆栈中分配SpreadsheetCell对象时,可以这样使用构造函数:
SpreadsheetCell myCell(4), anotherCell(3); cout << "Cell 1: "<<myCell.getValue()<<endl; cout << "Cell 2: "<<anotherCell.getValue()<<endl;注意,不要显式地调用SpreadsheetCell构造函数。例如:
SpreadsheetCell myCell.SpreadsheetCell(4); // WILL NOT COMPILE!在堆中使用构造函数
当动态分配SpreadsheetCell对象时,可以这样使用构造函数:
SpreadsheetCell* myCellp = new SpreadsheetCell(4); SpreadsheetCell* anotherCellp = nullptr; anotherCellp = new SpreadsheetCell(4); // ... do something with the cells delete myCellp; myCellp = nullptr; delete anotherCellp; anotherCellp = nullptr;
在一个类中可以提供多个构造函数。所有构造函数名称相同(类名),但不同构造函数具有不同数量的参数或不同的参数类型。在SpreadsheetCell类中,编写两个构造函数是有益的:
class SpreadsheetCell { public: SpreadsheetCell(double initialValue); SpreadsheetCell(string initialValue); //Remainder of the class definitin omitted for brevity };下面是第二个构造函数的实现:
// Here is the implementain of the second constructor SpreadsheetCell::SpreadsheetCell(string initialValue){ setString(initialValue); }下面是使用两个不同构造函数的代码:
SpreadsheetCell aThirdCell("test"); // uses string-arg ctor SpreadsheetCell aFourthCell(4.4); // uses double-arg ctor SpreadsheetCell* aThirdCellp = new SpreadsheetCell("4.4"); // string-arg ctor cout << "aThirdCell: " << aThirdCell.getValue() << endl; cout << "aFourthCell: " << aFourthCell.getValue() << endl; cout << "aThirdCellp: " << aThirdCellp->getValue() << endl; delete aThirdCellp; aThirdCellp = nullptr;
本节内容到现在为止,都是在构造函数内初始化数据成员,例如:
SpreadsheetCell::SpreadsheetCell( ) { mValue = 0; mString = ""; }
C++提供了另一种在构造函数中初始化数据成员的方法,叫做构造函数初始化器或ctor-initializer.下面的代码使用ctor-initializer语法重写了没有参数的SpreadsheetCell构造函数:
SpreadsheetCell::SpreadsheetCell( ) :mValue(0), mString(""){ }
当销毁对象的时候,会发生两件事:对象的析构函数被调用,释放对象占用的内存。在析构函数中可以行对象清理。如果没有声明析构函数,编译器将自动生成一个,析构函数会逐一销毁成员然后删除对象。当堆栈中的对象超出作用域时,意味着当前的函数、方法或者其他执行代码结束,对象会被销毁。下面的程序显示了这一行为:
int main() { SpreadsheetCell myCell(5); if (myCell.getValue() ==3){ SpreadsheetCell anotherCell(6); } // anotherCell is destroyed as this block ends; cout << "myCell:" <<myCell.getValue() <<endl; return 0; }// myCell is destroyed as this block ends.
在堆中分配的对象不会自动销毁,必须使用delete删除对象指针,从而调用析构函数并释放内存,下面的程序显示了这一行为:
int main( ){ SpreadsheetCell* cellPtr1 = new SpreadsheetCell(5); SpreadsheetCell* cellPtr2 = new SpreadsheetCell(6); cout << "cellPtr1: " <<cellPt1->getValue() <<endl; delete cleePtr1; //Destroys CellPtr1; cellPtr1 = nullptr; return 0; } // cellPtr2 is Not destroyed beacause delete was not called on it.
就像可将一个int值赋给另一个int一样,在C++中也可以将一个对象的值赋给另一个对象。例如:
SpreadsheetCell myCell(5), anotherCell; anotherCell = myCell;
本章讲述了C++为面向对象编程提供的基本工具:类和对象。首先回顾了编写类以及使用对象的基本语法,然后讲述了对象的生命周期:什么时候构建、销毁以及赋值,这些操作会调用哪些方法。