題目要求:設計一個電子表格,該電子表格有兩個類:Spreadsheet和SpreadsheetCell。每個Spreadsheet都包含了若干個SpreadsheetCell,此外SpreadsheetApplication類管理Spread集合。
step1 : 從最小單元SpreadsheetCell瞭解類和對象
1 編寫類定義和類方法
考慮實際電子表格允許存儲的對象可以是數字,可以是文本數據。如果接收文本數據,電子表格將轉換爲數字。簡單的
編程實現如下:
SpreadsheetCell.h 類方法定義
#SpreadsheetCell.h
#ifndef SPREADSHEETCELL_H_
#define SPREADSHEETCELL_H_
#include<string>
class SpreadsheetCell
{
public:
void setValue(double inDouble);
void setString(const std::string& inString);
const std::string& getString() const;
private:
double mValue;
std::string mString;
std::string doubleToString(double inDouble) const;
double stringToDouble(const std::string& inString) const;
};
#endif
SpreadsheetCell.cpp 類方法實現
#SpreadsheetCell.cpp
#include "SpreadsheetCell.h"
#include <iostream>
#include <sstream>
using namespace std;
void SpreadsheetCell::setValue(double inDouble){
mString = inDouble;
}
void SpreadsheetCell::setString(const string& inString){
mString = inString;
mValue = stringToDouble(inString);
}
const string& SpreadsheetCell::getString() const{
return mString;
}
string SpreadsheetCell::doubleToString(double inDouble) const{
ostringstream ostr;
ostr << inDouble;
return ostr.str();
}
double SpreadsheetCell::stringToDouble(const string& inString) const{
double dValue;
istringstream istr(inString);
istr >> dValue;
if(istr.fail() || !istr.eof()){
return 0;
}else{
return dValue;
}
}
2 編寫對象的創建和使用
在堆棧中的對象調用.method(args):
SpreadsheetCell mCell;
mCell.setValue(5.0);
cout << "mString " << mCell.getString() << endl;
在堆中的對象調用->method(args)
SpreadsheetCell mCell = new SpreadsheetCell();
mCell -> setValue(5.0);
cout << "mStirng " << mCell -> getString() << endl;
delete mCell;
mCell = nullptr;
二者的主要區別:在堆棧中創建的對象無需手動管理內容,創建的對象內存固定;在堆中創建的對象可以動態分配內存,但必須在創建時使用new表達式(首先通過operator new爲SpreadsheetCell對象分配內存,然後爲這個對象調用構造函數,此處構造函數使用默認構造函數),在銷燬時使用delete表達式(首先調用SpreadsheetCell對象的析構函數,然後調用operator delete釋放內存)。
在堆中使用對象時,爲避免發生內存錯誤,強烈推薦使用智能指針。
auto mCell = make_unique<SpreadsheetCell>();
mCell -> setValue(5.0)
cout << "mString " << mCell -> getString() << endl;
3 對象的生命週期:創建、銷燬、賦值
1 編寫構造函數
//note the var defination order
SpreadsheetCell::SpreadsheetCell(double inDouble):mValue(inDouble), mString(doubleToString(inDouble)){
}
SpreadsheetCell::SpreadsheetCell(double inDouble){
setValue(inDouble);
}
SpreadsheetCell::SpreadsheetCell(const std::string& inString){
setString(inString);
}
在堆棧中調用構造函數:
SpreadsheetCell mCell(5.0);
在堆中調用構造函數:
SpreadsheetCell *mCell = new SpreadsheetCell(5.0);
delete mCell;
mCell = nullptr;
//more efficient way
auto smartmCell = make_unipue<SpreadsheetCell>(5.0);
注:無論是在堆中還是在類的對象中聲明指針,但沒有立即初始化對象,都應將指針初始化爲nullptr,如果不賦初值nullptr,此時指針未定義,進而可能導致無法預料或難以診斷的內存錯誤。
2 特殊的構造函數——複製構造函數
SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src){
mValue = src.mValue;
mString = src.mString;
}
複製構造函數允許所創建的對象是另一個對象的精確副本。如果沒有編寫複製構造函數,c++會自動生成一個,用源對象相應數據成員的值初始化新對象的每個數據成員,如果數據成員是對象,意味着調用複製構造函數。
注:c++傳遞函數參數的默認方式是按值傳遞,這意味着函數或者方法接收某個值或者對象的副本。因此,無論什麼時候給函數或者方法傳遞一個對象時,編譯器都會調用新對象的複製構造函數進行初始化。當函數或者方法返回對象時,也會調用複製構造函數,在此情況下,編譯器使用複製構造函數創建一個臨時的、沒有名稱的對象。可將傳遞的參數作爲const引用,從而避免複製構造函數的開銷。按引用傳遞只需要複製對象的地址,不需要複製對象的全部內容,因此比按值傳遞的效率更高,當按引用傳遞某個對象時,使用對象引用的函數或方法可以修改原始對象,如果只是爲了提高效率才使用按引用傳遞,可將對象設置爲const以排除這種可能。
3 默認析構函數
當銷燬對象時,首先調用析構函數,然後釋放對象佔用的內存。在析構函數中可以執行對象的清理,如釋放動態內存或關閉句柄文件。當堆棧中的對象超出作用域時,意味着當前的函數,方法或者代碼塊結束,對象被銷燬。沒有智能指針的幫助,在堆中分配的內存不會自動銷燬,必須使用delete刪除對象指針,從而調用析構函數,並釋放內存。
practice makes perfect!