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

#include 

class 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 

/** 普用贷款 */
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;
}
多态的例子 - Loan

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










*******

相关