大家好,欢迎来到IT知识分享网。
示例一:
<!-- stock.h --> #ifndef STOCK1_H_ #define STOCK1_H_ #include <string> class Stock { private: std::string company; long shares; double share_val; double total_val; void set_tot() { total_val = shares * share_val; } public: Stock();// default constructor Stock(const std::string& co, long n = 0, double pr = 0.0); ~Stock();// noisy destructor void buy(long num, double price); void sell(long num, double price); void update(double price); void show(); }; #endif
<!-- stock.cpp --> #include <iostream> #include "stock.h" // constructors (verbose versions) Stock::Stock() {// default constructor std::cout << "Default constructor called\n"; company = "no name"; shares = 0; share_val = 0.0; total_val = 0.0; } Stock::Stock(const std::string& co, long n, double pr) { std::cout << "Constructor using " << co << " called\n"; company = co; if (n < 0) { std::cout << "Number of shares can't be negative; " << company << " shares set to 0.\n"; shares = 0; } else shares = n; share_val = pr; set_tot(); } // class destructor Stock::~Stock() {// verbose class destructor std::cout << "Bye, " << company << "!\n"; } // other methods void Stock::buy(long num, double price) { if (num < 0) { std::cout << "Number of shares purchased can't be negative. " << "Transaction is aborted.\n"; } else { shares += num; share_val = price; set_tot(); } } void Stock::sell(long num, double price) { using std::cout; if (num < 0) { cout << "Number of shares sold can't be negative. " << "Transaction is aborted.\n"; } else if (num > shares) { cout << "You can't sell more than you have! " << "Transaction is aborted.\n"; } else { shares -= num; share_val = price; set_tot(); } } void Stock::update(double price) { share_val = price; set_tot(); } void Stock::show() { using std::cout; using std::ios_base; // set format to #. ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield); std::streamsize prec = cout.precision(3); cout << "Company: " << company << " Shares: " << shares << '\n'; cout << " Share Price: $" << share_val; // set format to #. cout.precision(2); cout << " Total Worth: $" << total_val << '\n'; // restore original format cout.setf(orig, ios_base::floatfield); cout.precision(prec); }
<!-- usestock.cpp --> // compile with stock.cpp #include <iostream> #include "stock.h" int main() { { using std::cout; cout << "Using constructors to create new objects\n"; Stock stock1("NanoSmart", 12, 20.0);// syntax 1 stock1.show(); Stock stock2 = Stock("Boffo Objects", 2, 2.0);// syntax 2 stock2.show(); cout << "Assigning stock1 to stock2:\n"; stock2 = stock1; cout << "Listing stock1 and stock2:\n"; stock1.show(); stock2.show(); cout << "Using a constructor to reset an object\n"; stock1 = Stock("Nifty Foods", 10, 50.0);// Stock("Nifty...) is a temp object cout << "Revised stock1:\n"; stock1.show(); cout << "Done\n"; } return 0; }
Using constructors to create new objects Constructor using NanoSmart called Company: NanoSmart Shares: 12 Share Price: $20.000 Total Worth: $240.00 Constructor using Boffo Objects called Company: Boffo Objects Shares: 2 Share Price: $2.000 Total Worth: $4.00 Assigning stock1 to stock2: Listing stock1 and stock2: Company: NanoSmart Shares: 12 Share Price: $20.000 Total Worth: $240.00 Company: NanoSmart Shares: 12 Share Price: $20.000 Total Worth: $240.00 Using a constructor to reset an object Constructor using Nifty Foods called Bye, Nifty Foods!// 析构了临时对象! Revised stock1: Company: Nifty Foods Shares: 10 Share Price: $50.000 Total Worth: $500.00 Done Bye, NanoSmart!// 析构了stock2(被赋值为stock1了)! Bye, Nifty Foods!// 析构了新stock1!
为何没有打印Bye, Boffo Objects! ?读懂源码及下述说明,即可理解。
在默认情况下,给类对象赋值时,将把一个对象的成员复制给另一个。在这个例子中,stock2原来的内容将被覆盖。
函数main()结束时,其局部变量(stock1和stock2)将消失。由于这种自动变量被放在栈中,因此最后创建的对象将最先被删除,最先创建的对象将最后被删除(“NanoSmart”最初位于stock1中,但随后被传输到stock2中,然后stock1被重置为“Nifty Food”)。
示例二:
<!-- stack.h --> #ifndef STACK_H_ #define STACK_H_ typedef unsigned long Item; class Stack { private: enum { MAX = 10 };// constant specific to class Item items[MAX];// holds stack items int top;// index for top stack item public: Stack(); bool isempty() const; bool isfull() const; // push() returns false if stack already is full, true otherwise bool push(const Item& item);// add item to stack // pop() returns false if stack already is empty, true otherwise bool pop(Item& item);// pop top into item }; #endif
<!-- stack.cpp --> #include "stack.h" Stack::Stack() {// create an empty stack top = 0; } bool Stack::isempty() const { return top == 0; } bool Stack::isfull() const { return top == MAX; } bool Stack::push(const Item& item) { if (top < MAX) { items[top++] = item; return true; } return false; } bool Stack::pop(Item& item) { if (top > 0) { item = items[--top]; return true; } return false; }
const成员函数:
const Stock land = Stock("Kludgehorn Properties"); land.show();←编译器拒绝
对于当前的C++来说,编译器将拒绝第二行。这是什么原因呢?因为show( )的代码无法确保调用对象不被修改——调用对象和const一样,不应被修改。我们以前通过将函数参数声明为const引用或指向const的指针来解决这种问题。但这里存在语法问题:show( )方法没有任何参数。相反,它所使用的对象是由方法调用隐式地提供的。需要一种新的语法——保证函数不会修改调用对象。C++的解决方法是将const关键字放在函数的括号后面。也就是说,show()声明应像这样:
void show() const; // 不允许改变【调用对象】 同样,函数定义的开头应像这样: void Stock::show() const {...}; // 不允许修改调用对象
以这种方式声明和定义的类函数被称为const成员函数。就像应尽可能将const引用和指针用作函数形参一样,只要类方法不修改调用对象,就应将其声明为const。
this:
一般来说,所有的类方法都将this指针设置为调用它的对象的地址。
所以,可以有如下代码:
const Stock & Stock::topval(const Stock & s) const { if (s.total_val > total_val) return s; // 参数对象 else return *this; // 调用的对象 }
示例三:(继承)
// tabtenn.h #ifndef TABTENN_H_ #define TABTENN_H_ #include <string> using std::string; class TableTennisPlayer {// simple base class private: string firstname; string lastname; bool hasTable; public: TableTennisPlayer(const string& fn = "none", const string& ln = "none", bool ht = false); void Name() const; bool HasTable() const { return hasTable; }; void ResetTable(bool v) { hasTable = v; }; friend std::ostream& operator<<(std::ostream& os, const TableTennisPlayer& tenn); }; #endif
//tabtenn.cpp -- simple base-class methods #include "tabtenn.h" #include <iostream> TableTennisPlayer::TableTennisPlayer(const string& fn, const string& ln, bool ht) : firstname(fn), lastname(ln), hasTable(ht) {} void TableTennisPlayer::Name() const { std::cout << lastname << ", " << firstname; } std::ostream& operator<<(std::ostream& os, const TableTennisPlayer& tenn) { os << "I'm player:" << tenn.lastname << std::endl; return os; }
class RatedPlayer : public TableTennisPlayer { private: unsigned int rating;// 添加一个数据成员 public: RatedPlayer(unsigned int r = 0, const string & fn = "none", const string & ln = "none", bool ht = false); RatedPlayer(unsigned int r, const TableTennisPlayer & tp); unsigned int Rating() const { return rating; }// 添加一个方法 void ResetRating(unsigned int r) { rating = r; }// 添加一个方法 friend std::ostream& operator<<(std::ostream& os, const RatedPlayer& rp); };
冒号指出RatedPlayer类的基类是TableTennisplayer类。上述特殊的声明头表明TableTennisPlayer是一个公有基类,这被称为公有派生。派生类对象包含基类对象。使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和protected方法访问。
上述代码完成了哪些工作呢?Ratedplayer对象将具有以下特征:
1. 派生类对象存储了基类的数据成员(派生类继承了基类的实现);
2. 派生类对象可以使用基类的方法(派生类继承了基类的接口)。
因此,RatedPlayer对象可以存储运动员的姓名及其是否有球桌。另外,RatedPlayer对象还可以使用TableTennisPlayer类的Name()、hasTable()和ResetTable()方法。
派生类构造函数必须使用基类构造函数。不直接使用默认也会使用基类默认构造函数。
派生类不继承基类的构造函数。
创建派生类对象时,程序首先创建基类对象。从概念上说,这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表语法来完成这种工作。例如,下面是一个RatedPlayer构造函数的代码:
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht) { rating = r; }
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
RealPlayer构造函数将把实参“Mallory”、“Duck”和true赋给形参fn、In和ht,然后将这些参数作为实参传递给TableTennisPlayer构造函数,后者将创建一个嵌套TableTennisPlayer对象,并将数据“Mallory”、“Duck”和true存储在该对象中。然后,程序进入RealPlayer构造函数体,完成RealPlayer对象的创建,并将参数r的值(即1140)赋给rating成员。
默认情况下:
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht) { rating = r; } 以上等同于以下: RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht) : TableTennisPlayer() { rating = r; }
下面来看派生类第二个构造函数的代码:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp) : TableTennisPlayer(tp) { rating = r; }
由于tp的类型为TableTennisPlayer &,因此将调用基类的复制构造函数。基类没有定义复制构造函数,编译器将自动生成一个默认的。在这种情况下,执行成员复制的隐式复制构造函数是合适的,因为这个类没有使用动态内存分配(string成员确实使用了动态内存分配,成员复制将使用string类的复制构造函数来复制string成员)。
如果愿意,也可以对派生类成员使用成员初始化列表语法。在这种情况下,应在列表中使用成员名,而不是类名。所以,第二个构造函数可以按照下述方式编写:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp) :TableTennisPlayer(tp), rating(r) { }
有关派生类构造函数的要点如下:
- 首先创建基类对象;
- 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;
- 派生类构造函数应初始化派生类新增的数据成员。
这个例子没有提供显式构造函数,因此将使用隐式构造函数。
释放对象的顺序与创建对象的顺序相反,即首先执行派生类的析构函数,然后自动调用基类的析构函数。
派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。
基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象:
RatedPlayer rplayer(1140, "Mallory", "Duck", true); TableTennisPlayer & rt = rplayer; TableTennisPlayer * pt = &rplayer; rt.Name(); pt->Name();
可以将派生对象赋给基类对象:
RatedPlayer olaf1(1840, "Olaf", "Loaf", true); TableTennisPlayer winner; winner = olaf1;// 将派生对象赋值给基类对象
在这种情况下,程序将使用基类(而非派生类)的隐式重载赋值运算符:
TableTennisPlayer & operator=(const TableTennisPlayer &) const;
基类引用指向的也是派生类对象,因此olaf1的基类部分被复制给winner。
访问控制:protected
private和protected之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。
RatedPlayer类可以直接访问基类用protected修饰的成员数据。
最好对类数据成员采用私有访问控制,不要使用保护访问控制;同时通过基类方法使派生类能够访问基类数据。
友元的“继承”:
// rplayer.cpp ——实现RatedPlayer类中的方法 ... std::ostream& operator<<(std::ostream& os, const RatedPlayer& rp) { os << (const TableTennisPlayer&) rp; os << "My rating is:" << rp.rating << std::endl; return os; }
因为友元不是成员函数,所以不能使用作用域解析运算符来指出要使用哪个函数。这个问题的解决方法是使用强制类型转换,以便匹配原型时能够选择正确的函数。因此,代码将参数const RatedPlayer &转换成类型为const TableTennisPlayer &的参数。
std::ostream& operator<<(std::ostream& os, const RatedPlayer& rp) { os << (const TableTennisPlayer&) rp; ... }
os << (const TableTennisPlayer&) rp; 就相当于os << aBaseClz,只有基类的友元定义了此运算符,而在派生类的友元中能调用基类重载的友元运算符。对于此例,派生类的友元函数能调用基类友元函数重载的运算符<<。
由于友元函数并非类成员,因此不能继承。然而,可以通过强制类型转换,将派生类引用或指针转换为基类引用或指针,然后使用转换后的指针或引用来调用基类的友元函数。
也可以使用运算符dynamic_cast<>来进行强制类型转换:
os << dynamic_cast<const TableTennisPlayer &> (rp);
以公有方式派生的类的对象可以通过多种方式来使用基类的方法:
- 派生类对象自动使用继承而来的基类方法,如果派生类没有重新定义该方法。
- 派生类的构造函数自动调用基类的构造函数。
- 派生类的构造函数自动调用基类的默认构造函数,如果没有在成员初始化列表中指定其他构造函数。
- 派生类构造函数显式地调用成员初始化列表中指定的基类构造函数。
- 派生类方法可以使用作用域解析运算符来调用公有的和受保护的基类方法。
- 派生类的有元函数可以通过强制类型转换,将派生类引用或指针转换为基类引用或指针,然后使用该引用或指针来调用基类的友元函数。
示例四:(虚函数&纯虚函数)
// brass.h #ifndef BRASS_H_ #define BRASS_H_ #include <string> class Brass { private: std::string fullName; long acctNum; double balance; public: Brass(const std::string& s = "Nullbody", long an = -1, double bal = 0.0); void Deposit(double amt); double Balance() const; virtual void ViewAcct() const; virtual void Withdraw(double amt); virtual ~Brass() {} }; class BrassPlus : public Brass { private: double maxLoan; double rate; double owesBank; public: BrassPlus(const std::string& s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.11125); BrassPlus(const Brass& ba, double ml = 500, double r = 0.11125); virtual void ViewAcct() const; virtual void Withdraw(double amt); void ResetMax(double m) { maxLoan = m; } void ResetRate(double r) { rate = r; }; void ResetOwes() { owesBank = 0; } }; #endif
对于以上程序清单,需要说明的有下面几点:
- BrassPlus类在Brass类的基础上添加了3个私有数据成员和3个公有成员函数;
- Brass类和BrassPlus类都声明了ViewAcct()和Withdraw()方法,但BrassPlus对象和Brass对象的这些方法的行为是不同的;
- Brass类在声明ViewAcct()和Withdraw()时使用了新关键字virtual。这些方法被称为虚方法(virtual method);
- Brass类还声明了一个虚析构函数,虽然该析构函数不执行任何操作。
两个ViewAcct()原型表明将有2个独立的方法定义。基类版本的限定名为Brass::ViewAcct(),派生类版本的限定名为BrassPlus::ViewAcct()。程序将使用对象类型来确定使用哪个版本:
Brass dom("Dominic Banker", 11224, 4183.45); BrassPlus dot("Dorothy Banker", 12118, 2592.00); dom.ViewAcct();// 使用Brass::ViewAcct() dot.ViewAcct();// 使用BrassPlus::ViewAcct()
因为在两个类中是行为相同的方法(Deposit()和Balance()),所以只在基类中声明。
使用 virtual 相对复杂点。如果方法是通过引用或指针而不是对象调用的,它将确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。
下面比较ViewAcct()是虚的和不是虚的的程序情况:
<!-- 非虚方法的ViewAcct()行为:--> [方法根据引用类型来选择] Brass dom("Dominic Banker", 11224, 4183.45); BrassPlus dot("Dorothy Banker", 12118, 2592.00); Brass & b1_ref = dom; Brass & b2_ref = dot; b1_ref.ViewAcct(); // 使用Brass::ViewAcct() b2_ref.ViewAcct(); // 使用Brass::ViewAcct() <!-- 虚方法的ViewAcct()行为:--> [方法根据对象类型来选择] Brass dom("Dominic Banker", 11224, 4183.45); BrassPlus dot("Dorothy Banker", 12118, 2592.00); Brass & b1_ref = dom; Brass & b2_ref = dot; b1_ref.ViewAcct(); // 使用Brass::ViewAcct() b2_ref.ViewAcct(); // 使用BrassPlus::ViewAcct()
虚函数的这种行为非常方便。因此,经常在基类中将派生类会重新定义的方法声明为虚方法。方法在基类中被声明为虚的后,它在派生类中将自动成为虚方法。然而,在派生类声明中使用关键字virtual来指出哪些函数是虚函数也不失为一个好办法。
如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的。这样,程序将根据对象类型而不是引用或指针的类型来选择方法版本。为基类声明一个虚析构函数也是一种惯例。
使用虚方法指南:
如果希望派生类能够重新定义方法,则应在基类中将方法定义为虚的,这样可以启用晚期联编(动态联编);如果不希望重新定义方法,则不必将其声明为虚的,这样虽然无法禁止他人重新定义方法,但表达了这样的意思:您不希望它被重新定义。
下面的代码表现出了多态:
Brass* p_clients[CLIENTS]; ... for (i = 0; i< CLIENTS; i++) { p_clients[i]->ViewAcct(); }
基类声明了一个虚析构函数。这样做是为了确保释放派生对象时,按正确的顺序调用析构函数。
为何需要虚析构函数?
如果析构函数不是虚的,则将只调用对应于指针类型的析构函数。对于程序清单,这意味着只有Brass的析构函数被调用,即使指针指向的是一个BrassPlus对象。如果析构函数是虚的,将调用相应对象类型的析构函数。因此,如果指针指向的是BrassPlus对象,将调用BrassPlus的析构函数,然后自动调用基类的析构函数。因此,使用虚析构函数可以确保正确的析构函数序列被调用。对于程序清单,这种正确的行为并不是很重要,因为析构函数没有执行任何操作。然而,如果BrassPlus包含一个执行某些操作的析构函数,则Brass必须有一个虚析构函数,即使该析构函数不执行任何操作。
请看下面的代码段,这里假定每个函数都调用虚方法ViewAcct():
void fr(Brass & rb); // 使用rb.ViewAcct() void fp(Brass * pb); // 使用pb->ViewAcct() void fv(Brass b); // 使用b.ViewAcct() int main() { Brass b("Billy Bee", , 10000.0); BrassPlus bp("Betty Beep", , 12345.0); fr(b); // 使用Brass::ViewAcct() fr(bp); // 使用BrassPlus::ViewAcct() fp(b); // 使用Brass::ViewAcct() fp(bp); // 使用BrassPlus::ViewAcct() fv(b); // 使用Brass::ViewAcct() fv(bp); // 使用Brass::ViewAcct()!!!!!! }
按值传递导致只将BrassPlus对象的Brass部分传递给函数fv()。
友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数。
如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本。
如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如果只重新定义一个版本,则另外两个版本将被隐藏,派生类对象将无法使用它们。
class Dwelling { public: // 三个重载的showperks() virtual void showperks(int a) const; virtual void showperks(double x) const; virtual void showperks() const; ... }; class Hovel : public Dwelling { public: // 三个重新定义的showperks() virtual void showperks(int a) const; virtual void showperks(double x) const; virtual void showperks() const; ... };
抽象类(abstract base class, ABC)&纯虚函数
virtual void Move(int nx, ny) = 0;
以上就是一个纯虚函数,相比虚函数,在声明时最后面的多了个“=0”。
在原型中使用=0指出类是一个抽象类。当类声明中包含纯虚函数时,则不能创建该类的对象。
总之,ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征,使用常规虚函数来实现这种接口。
// acctabc.h #ifndef ACCTABC_H_ #define ACCTABC_H_ #include <iostream> #include <string> // 抽象基类 class AcctABC { private: std::string fullName; long acctNum; double balance; protected: struct Formatting { std::ios_base::fmtflags flag; std::streamsize pr; }; const std::string& FullName() const { return fullName; } long AcctNum() const { return acctNum; } Formatting SetFormat() const; void Restore(Formatting& f) const; public: AcctABC(const std::string& s = "Nullbody", long an = -1, double bal = 0.0); void Deposit(double amt); virtual void Withdraw(double amt) = 0; // 纯虚函数 double Balance() const { return balance; }; virtual void ViewAcct() const = 0; // 纯虚函数 virtual ~AcctABC() {} }; // Brass Account Class class Brass :public AcctABC { public: Brass(const std::string& s = "Nullbody", long an = -1, double bal = 0.0) : AcctABC(s, an, bal) {} virtual void Withdraw(double amt); virtual void ViewAcct() const; virtual ~Brass() {} }; //Brass Plus Account Class class BrassPlus : public AcctABC { private: double maxLoan; double rate; double owesBank; public: BrassPlus(const std::string& s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.10); BrassPlus(const Brass& ba, double ml = 500, double r = 0.1); virtual void ViewAcct() const; virtual void Withdraw(double amt); void ResetMax(double m) { maxLoan = m; } void ResetRate(double r) { rate = r; }; void ResetOwes() { owesBank = 0; } }; #endif
以上,类AcctABC因为包含有纯虚函数,所以它是一个抽象类,是不能直接实例化的。
Brass类和BrassPlus类都继承自AcctABC这个抽象类,所以要想能实例化Brass和BrassPlus,它们就必须在原型中声明对应的普通虚函数并去实现它。
可以将ABC看作是一种必须实施的接口。ABC要求具体派生类覆盖其纯虚函数——迫使派生类遵循ABC设置的接口规则。这种模型在基于组件的编程模式中很常见,在这种情况下,使用ABC使得组件设计人员能够制定“接口约定”,这样确保了从ABC派生的所有组件都至少支持ABC指定的功能。
继承和动态内存分配:
假设基类使用了动态内存分配(DMA):
class baseDMA { private: char * label; int rating; public: baseDMA(const char * l = "null", int r = 0); baseDMA(const baseDMA & rs); virtual ~baseDMA(); baseDMA & operator=(const baseDMA & rs); };
声明中包含了构造函数使用new时需要的特殊方法:析构函数、复制构造函数和重载赋值运算符。
第一种情况:派生类不使用new。
// 派生类不用到动态内存分配 class lacksDMA : public baseDMA { private: char color[40]; public: ... };
是否需要为lackDMA类定义显式析构函数、复制构造函数和赋值运算符呢?不需要!
第二种情况:派生类使用new
// 派生类用到动态内存分配 class hasDMA : public baseDMA { private: char * style; ← 将在构造函数中使用new public: ... };
在这种情况下,必须为派生类定义显式析构函数、复制构造函数和赋值运算符。
》》》详细为什么?详见我的另一Blog:第13章-cpp类继承
总之,当基类和派生类都采用动态内存分配时,派生类的析构函数、复制构造函数、赋值运算符都必须使用相应的基类方法来处理基类元素。这种要求是通过三种不同的方式来满足的。对于析构函数,这是自动完成的;对于构造函数,这是通过在初始化成员列表中调用基类的复制构造函数来完成的;如果不这样做,将自动调用基类的默认构造函数。对于赋值运算符,这是通过使用作用域解析运算符显式地调用基类的赋值运算符来完成的。
动态内存分配的示例:
// dma.h #ifndef DMA_H_ #define DMA_H_ #include <iostream> // Base Class Using DMA class baseDMA { private: char* label; int rating; public: baseDMA(const char* l = "null", int r = 0); baseDMA(const baseDMA& rs); virtual ~baseDMA(); baseDMA& operator=(const baseDMA& rs); friend std::ostream& operator<<(std::ostream& os, const baseDMA& rs); }; // derived class without DMA class lacksDMA :public baseDMA { private: enum { COL_LEN = 40 }; char color[COL_LEN]; public: lacksDMA(const char* c = "blank", const char* l = "null", int r = 0); lacksDMA(const char* c, const baseDMA& rs); friend std::ostream& operator<<(std::ostream& os, const lacksDMA& rs); }; // derived class with DMA class hasDMA :public baseDMA { private: char* style; public: hasDMA(const char* s = "none", const char* l = "null", int r = 0); hasDMA(const char* s, const baseDMA& rs); hasDMA(const hasDMA& hs); ~hasDMA(); hasDMA& operator=(const hasDMA& rs); friend std::ostream& operator<<(std::ostream& os, const hasDMA& rs); }; #endif
// dma.cpp #include "dma.h" #include <cstring> // baseDMA methods baseDMA::baseDMA(const char* l, int r) { label = new char[std::strlen(l) + 1]; std::strcpy(label, l); rating = r; } baseDMA::baseDMA(const baseDMA& rs) { label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; } baseDMA::~baseDMA() { delete[] label; } baseDMA& baseDMA::operator=(const baseDMA& rs) { if (this == &rs) return *this; delete[] label; label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; return *this; } std::ostream& operator<<(std::ostream& os, const baseDMA& rs) { os << "Label: " << rs.label << std::endl; os << "Rating: " << rs.rating << std::endl; return os; } // lacksDMA methods lacksDMA::lacksDMA(const char* c, const char* l, int r) : baseDMA(l, r) { std::strncpy(color, c, 39); color[39] = '\0'; } lacksDMA::lacksDMA(const char* c, const baseDMA& rs) : baseDMA(rs) { std::strncpy(color, c, COL_LEN - 1); color[COL_LEN - 1] = '\0'; } std::ostream& operator<<(std::ostream& os, const lacksDMA& ls) { os << (const baseDMA&)ls; os << "Color: " << ls.color << std::endl; return os; } // hasDMA methods hasDMA::hasDMA(const char* s, const char* l, int r) : baseDMA(l, r) { style = new char[std::strlen(s) + 1]; std::strcpy(style, s); } hasDMA::hasDMA(const char* s, const baseDMA& rs) : baseDMA(rs) { style = new char[std::strlen(s) + 1]; std::strcpy(style, s); } hasDMA::hasDMA(const hasDMA& hs) : baseDMA(hs) { // invoke base class copy constructor style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); } hasDMA::~hasDMA() { delete[] style; } hasDMA& hasDMA::operator=(const hasDMA& hs) { if (this == &hs) return *this; baseDMA::operator=(hs); // copy base portion delete[] style; // prepare for new style style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); return *this; } std::ostream& operator<<(std::ostream& os, const hasDMA& hs) { os << (const baseDMA&)hs; os << "Style: " << hs.style << std::endl; return os; }
测试——编译并运行:
// usedma.cpp -- inheritance, friends, and DMA // compile with dma.cpp #include <iostream> #include "dma.h" int main() { using std::cout; using std::endl; baseDMA shirt("Portabelly", 8); lacksDMA balloon("red", "Blimpo", 4); hasDMA map("Mercator", "Buffalo Keys", 5); cout << "Displaying baseDMA object:\n"; cout << shirt << endl; cout << "Displaying lacksDMA object:\n"; cout << balloon << endl; cout << "Displaying hasDMA object:\n"; cout << map << endl; lacksDMA balloon2(balloon); cout << "Result of lacksDMA copy:\n"; cout << balloon2 << endl; hasDMA map2; map2 = map; cout << "Result of hasDMA assignment:\n"; cout << map2 << endl; return 0; }
Displaying baseDMA object: Label: Portabelly Rating: 8 Displaying lacksDMA object: Label: Blimpo Rating: 4 Color: red Displaying hasDMA object: Label: Buffalo Keys Rating: 5 Style: Mercator Result of lacksDMA copy: Label: Blimpo Rating: 4 Color: red Result of hasDMA assignment: Label: Buffalo Keys Rating: 5 Style: Mercator
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/140669.html