C++大学教程(第3版)译 侯普秀
《C++大学教程(第3版)》是2005年清华大学出版社出版的图书,作者是那格勒,译者是侯普秀。ISBN 9787302098492
这是我入门C++的第一本书,是在学校图书馆看见的,后买了一本。
第10章 函数重载
函数重载:就是在同一作用域内以相同的名称声明的多个函数。用函数的形参列表和参数个数、参数类型来区分。
例如
int fun(int i, char* str); int fun(char* str, int i); //short fun(int i, char* str);//error,注意,函数返回类型不能作为函数重载的区分 char fun(double d, long l); long fun(char, int=0);
10.3 不能仅仅根据函数返回值类型来函数重载
10.4 const限定的参数按值传递时不能构成重载,只是对一个函数的重新声明而已(可以这么做,但多余)
//可以重复声明 int fun(int i, char* str); int fun(const int i, char* str); //但不能重复定义 int fun(int i, char* str){} int fun(const int i, char* str){} //error: redefinition of 'int fun(int, char*)'|
10.5 const 限定的参数按指针传递时能够构成有效的函数重载
//OK int fun(int i, char* str); int fun(int i, const char* str); //OK int fun(int i, char* str){ return 0;} int fun(int i, const char* str){ return 0;}
10.6 const 限定的参数按引用传递时能够构成有效的函数重载
//ok int fun(int i, char* str); int fun(int& i, char* str); //ok int fun(int i, char* str){ return 0;} int fun(int& i, char* str){ return 0;}
10.7 同一类中可变成员函数与常量成员函数,能够函数重载。尽管名称与参数列表都相同。
class ADT { public: void func() { cout << "func()\n"; } //#1 void func() const { cout << "func() const\n"; } //#2 }; void test(ADT& refADT, const ADT& refConstADT){ refADT.func();//call #1 refConstADT.func();//call #2 } int main() { ADT a; test(a,a); } /* func() func() const */
10.11 运算符重载
运算符优先级表
重载运算符的名称始终是 operator@ 形式,其中@表示运算符。
1. 不可重载的运算符
. .* ->* :: ?:
2. =、[]、( )、->以及所有类型转换运算符,只能作为非静态成员函数重载。
3. 一般地,单目运算符重载为成员函数;双目运算符重载为friend函数。
4. 除了new、delete这两个较为特殊的运算符外,任何运算符不可以重载为static成员函数。
5. 不可以为重载运算符函数设置默认值,因此调用时不可以省略参数。
6. 重载的运算符必须保证其原有的操作数个数不变,和原有优先级和结合性不变。例如,“*”可以是一元运算符或二元运算符,而“=”只能重载为二元运算符。
一般形式
//.h中 String operator+ (...) //.cpp中 String String::operator+ (...) friend const String operator+ (const String& left, const String& right); // String a, b; // a + b; //等价于 operator+(a, b) ; 总是假定调用实例位于运算符的左侧。 //赋值操作符重载 String& operator= (const String& str); String& operator! (); friend ostream& operator<< (ostream& output, const Date& d) { ... return output; }
10.11.14 赋值运算符
class MyString { public: MyString(); ~MyString(); //构造函数 MyString(const char* str); //复制构造函数 MyString(const MyString& str); //赋值运算符 MyString& operator=(const MyString& str); } /////////////// cpp 文件 ///////////// //复制构造函数 MyString::MyString(const MyString& str) { ptrChar = new char[std::strlen(str.ptrChar)+1]; std::strcpy(ptrChar, str.ptrChar); } //赋值运算符重载 MyString& MyString::operator=(const MyString& rhs) { if (this == &rhs) return *this; // handle self assignment if(std::strlen(rhs.ptrChar) != std::strlen(ptrChar)) {//先进行 new 内存空间,在delete。以防new 抛出异常 char* ptrHold = new char[std::strlen(rhs.ptrChar)+1]; delete[] ptrChar; ptrChar = ptrHold; } std::strcpy(ptrChar, rhs.ptrChar); return *this; }
10.11.15 函数调用运算符
重载运算符不能拥有默认参数,但有一个例外就是函数调用运算符 operator() ()
//函数调用运算符,必须是成员函数 std::size_t operator() (const char* s = "+") const; ///////////////////// cpp 文件 ////////////////////// //重载运算符不能拥有默认参数,但有一个例外就是函数调用运算符 operator() () //函数调用运算符 std::size_t MyString::operator() (const char* s) const {//查找s出现的次数 size_t counter = 0; size_t len00 = std::strlen(s); size_t len01 = std::strlen(ptrChar); for(size_t i=0; i < len01 - len00 + 1; ++i) { if(0 == std::strncmp(ptrChar+i, s, len00)) ++counter; } return counter; }
10.11.16 下标运算符
//下标运算符 必须是成员函数,必须一个可变成员函数和一个常量成员函数 char& operator[] (std::size_t i) throw(const char* ); char operator[] (std::size_t i) const throw(const char* ); /////////// cpp //////////// const char* MyString::errMessage("index out of range."); char& MyString::operator[] (std::size_t i) throw(const char*) { if(i < 0 || i >= std::strlen(ptrChar)) throw errMessage; return ptrChar[i]; } char MyString::operator[] (std::size_t i) const throw(const char*) { if(i < 0 || i >= std::strlen(ptrChar)) throw errMessage; return ptrChar[i]; }
10.11.17 间接运算符 即 箭头运算符(->)
10.11.18 复合赋值运算符
例如:+=、-=、*=、/=
class MyString { //+ friend const MyString operator+ (const MyString& lhs, const MyString& rhs); public: //构造函数 MyString(const char* str); //复制构造函数 MyString(const MyString& str); //赋值运算符 MyString& operator=(const MyString& str); MyString& operator+=(const MyString& str); } ///////////// Cpp 文件 //////////// const MyString operator+ (const MyString& lhs, const MyString& rhs) { int len = std::strlen(lhs.ptrChar) + std::strlen(rhs.ptrChar) + 1; char* ptrHold = new char[len]; std::strcpy(ptrHold, lhs.ptrChar); std::strcat(ptrHold, rhs.ptrChar); MyString res(ptrHold); delete[] ptrHold; return res; } MyString& MyString::operator+=(const MyString& str) { return *this = *this + str; }
10.11.19 自增运算符和自减运算符
class A { public: A& operator++(); //prefix const A operator++(int); //postfix } // A a; ++a; // call prefix a++; // call postfix
例子:
//自增运算符和自减运算符 MyString& operator++(); //prefix const MyString operator++(int); //postfix /////cpp文件 ////// //自增运算符和自减运算符 MyString& MyString::operator++() //prefix { for(size_t i=0; i < std::strlen(ptrChar); ++i) { ++ptrChar[i]; } return *this; } const MyString MyString::operator++(int) //postfix { MyString rtn(*this); ++(*this); return rtn; }
10.11.20 根本无需考虑重装逗号运算符
10.11.21 不要重载 && 和 || 运算符
10.11.22 重载插入运算符
#ifndef MYSTRING_H #define MYSTRING_H #includeclass MyString { //用于cout输出MyString 例如 cout << s; friend std::ostream& operator<< (std::ostream& output, const MyString& str); //+ friend const MyString operator+ (const MyString& lhs, const MyString& rhs); public: MyString(); ~MyString(); //构造函数 MyString(const char* str); //复制构造函数 MyString(const MyString& str); //赋值运算符 MyString& operator=(const MyString& str); //函数调用运算符 std::size_t operator() (const char* s = "+") const; //下标运算符 char& operator[] (std::size_t i) throw(const char* ); char operator[] (std::size_t i) const throw(const char* ); //自增运算符和自减运算符 MyString& operator++(); //prefix const MyString operator++(int); //postfix protected: private: char* ptrChar; static const char* errMessage; void doCtor(const char* str); }; #endif // MYSTRING_H /////// cpp文件 ////// //friend 函数,用于输出MyString 例如 cout << s; std::ostream& operator<< (std::ostream& output, const MyString& str) { output << str.ptrChar << '\n'; return output; }
第11章 继承
继承是扩展现有类得到一个新类的过程。
术语:
基类 base -》派生类 derived
父类 father -》 子类 son
1. 派生类实例 包括继承的基类实例和自己派生的成员。
2. 继承通常用于在派生类和基类之间创建 is-a 关系。派生类的一个(is a)基类的特殊化。
3. 如何定义派生类
用 class 或 struct 关键字。class 默认是私有,struct 默认是公有。常用class
class DerivedString : String //private derivation { //... all class member (private by default) } //创建 is-a 关系的唯一途径是公有派生。最常用 class DerivedString : public String //public derivation { //... all class member (private by default) }
is-a 关系,意味着愿意把基类的公有接口都带到派生类中。
除非有特殊原因,否则应该总是使用公有派生。
4. 函数隐藏
函数隐藏是指子类中声明了一个与父类中函数名相同的函数(函数名相同,其他不要求)。(非virtual函数)
函数重载是在同一个作用域中;而函数隐藏是子类与父类之间的,是不同的作用域。
注意:子类中仍然存在该函数,只是隐藏了,可以用 类名::func 的方式调用。
class Base { public: void fun1(int a) //#2 { cout << a << endl; } }; class Derived : public Base { public: int fun1() // #1 { cout << "fun1()\n"; return 0; } }; int main() { Derived d; d.fun1(); // call #1 d.Base::fun1(1); // call #2 return 0; }
5 基类的构造函数总是在派生类的构造函数之前执行。
编译器需要知道调用基类的哪一个构造函数,若没有指定,默认调用基类的默认构造函数。(建议基类要有默认构造函数)
例如:
class DerivedString : public String { //... } ////// cpp //////// DerivedString::DerivedString() /* : String() */ { } DerivedString::DerivedString(const char* chars) : String(chars) { } DerivedString::DerivedString(const DerivedString& str) : String(str) { } DerivedString::DerivedString(const String& str) : String(str) { }
6. 派生类的析构函数总是先被执行,然后依次调用最近的基类的析构函数。
7. 永远都不能运行一个基类指针指向派生类对象数组。应该使用派生类自己的类型。
8. 多态
多态,相同的调用函数,但产生不同的调用结果。在基类指针指向派生类对象时,由具体指向的对象决定调用哪一个函数。特别在,函数形参是基类指针或引用时非常有用。
使用virtual函数实现。例如:
class GenericLoan { //... virtual double getMonthlyPayment() const; //... } std::ostream& operator<< (std::ostream& out, const GenericLoan& loan) { return out << loan.getMonthlyPayment(); // loan 是 GenericLoan 的派生类,会调用派生类的getMonthlyPayment() }
基类中将一个成员函数声明为虚函数,那么在所有派生类中该成员函数都会自动成为虚函数,无论是否显示添加关键字virtual。建议显示添加virtual,代码友好。
例子:
#ifndef GENERICLOAN_H #define GENERICLOAN_H #include多态的例子 - Loan/** 普用贷款 */ class GenericLoan { friend std::ostream &operator<< (std::ostream &stream, GenericLoan const &loan); public: GenericLoan(double principal, int length, double rate); virtual ~GenericLoan(); virtual double getMonthlyPayment() const ; protected: /** 贷款本金 */ double const principal; /** 贷款年数 */ int const length; /** 贷款年利率 */ double const rate; private: }; #endif // GENERICLOAN_H //-------------------------------- #include "GenericLoan.h" #include std::ostream &operator<< (std::ostream &stream, GenericLoan const &loan) { return stream << loan.getMonthlyPayment(); } GenericLoan::GenericLoan(double principal, int length, double rate):principal(principal),length(length),rate(rate) { //ctor } GenericLoan::~GenericLoan() { //dtor } double GenericLoan::getMonthlyPayment() const { return 0.00; } //-------------------------- #ifndef SIMPLEINTERESTLOAN_H #define SIMPLEINTERESTLOAN_H #include "GenericLoan.h" class SimpleInterestLoan : public GenericLoan { public: SimpleInterestLoan(double principal, int length, double rate); virtual ~SimpleInterestLoan(); double getMonthlyPayment() const; protected: private: }; #endif // SIMPLEINTERESTLOAN_H //-------------------------------------- #include "SimpleInterestLoan.h" SimpleInterestLoan::SimpleInterestLoan(double principal, int length, double rate):GenericLoan(principal, length, rate) { //ctor } SimpleInterestLoan::~SimpleInterestLoan() { //dtor } double SimpleInterestLoan::getMonthlyPayment() const { double monthlyRate = rate /12; int lengthInMonths = length * 12; return (principal * (monthlyRate * lengthInMonths + 1)) / lengthInMonths; } //------------------------------ #ifndef AMORTIZEDLOAN_H #define AMORTIZEDLOAN_H #include "GenericLoan.h" //动态利息,在付完利息后,下个月的贷款就是剩余的钱减去本金 class AmortizedLoan : public GenericLoan { public: AmortizedLoan(double principal, int length, double rate); double getMonthlyPayment() const; virtual ~AmortizedLoan(); protected: private: }; #endif // AMORTIZEDLOAN_H //------------------------------------- #include "AmortizedLoan.h" #include AmortizedLoan::AmortizedLoan(double principal, int length, double rate) : GenericLoan(principal, length, rate) { //ctor } double AmortizedLoan::getMonthlyPayment() const { double monthlyRate = rate / 12; int lengthInMonths = length * 12; double power = std::pow(1+monthlyRate, lengthInMonths); return (principal * monthlyRate * power) / (power - 1); } AmortizedLoan::~AmortizedLoan() { //dtor } //----------------------- //main.cpp #include #include "GenericLoan.h" #include "SimpleInterestLoan.h" #include "AmortizedLoan.h" using namespace std; int main() { double const principal = 300000.00; // 本金 int const length = 30; // 30年 double const rate = 0.08; // 利息 8.00% GenericLoan *allLoans[] = {new SimpleInterestLoan(principal,length,rate), new AmortizedLoan(principal, length,rate)}; size_t const dimension = sizeof(allLoans) / sizeof(*allLoans); cout << dimension << endl; for(size_t i=0; i< dimension; ++i) { cout << *allLoans[i] << endl; delete allLoans[i]; } //cout << gen.getMonthlyPayment() << endl; return 0; }
9. 重写 override
重写是指子类声明一个父类的virtual函数。要求函数名称和参数列表都一致,即函数签名一致。
class Base { public: virtual int fun1(); }; class Derived : public Base { public: //virtual void fun1(int a); // Hiding, not overriding 函数隐藏,不是函数重写 virtual void fun1();//这样是 重写。函数签名一致 };
10. 在基类的构造函数中调用虚函数 func() 将总是调用 Base::func() 函数,永远不会调用 Derived::func() , 即使 this 指向派生类对象。即多态性不适用于基类的构造函数。
11. 重写虚函数时,异常规范只能变得越来越严格。
12. 里氏替换原则
里氏替换原则(Liskov Substitute Principal):is-a 关系,无论基类指针或引用的实例出现在程序中的任何位置,都可以自由地用派生类实例来替代,并且可以观察到相同的行为结果。
13. 多重继承
class ClockRadio : public AlarmClock, public Radio
{
}
14. 虚基类
在派生过程中使用virtual,会使基类变成虚基类,并只会包含一个基类副本。若有两个基类,都要加上virtual
*******