面向对象程序设计——Visual C++ 第13章 运算符重载

思考题

1、用成员函数重载运算符和用友元函数重载运算符,在定义和使用上有什么不同?

在C++编程语言中,用成员函数重载运算符和用友元函数重载运算符,在定义和使用上有以下不同:

  1. 定义方式不同:成员函数重载运算符是在类内部定义的,而友元函数重载运算符是在类外部定义的。
  2. 访问权限不同:成员函数重载运算符可以访问类的私有成员,而友元函数重载运算符不能访问类的私有成员。
  3. 参数个数不同:成员函数重载运算符只有一个参数,即调用对象本身,而友元函数重载运算符可以有多个参数。
  4. 调用方式不同:成员函数重载运算符可以通过对象直接调用,而友元函数重载运算符需要通过函数名和参数列表来调用。
  5. 返回值不同:成员函数重载运算符的返回值是对象本身,而友元函数重载运算符的返回值可以是任意类型。

总的来说,成员函数重载运算符更加方便和直观,而友元函数重载运算符更加灵活和通用。具体使用哪种方式,需要根据具体的需求和情况来决定。

2、在C++中是否所有的运算符都可以重载?在可以重载的运算符中,是否都能够采用成员函数和友元函数进行?

在C++中,并不是所有的运算符都可以重载。只有部分运算符可以被重载,例如算术运算符、比较运算符、位运算符等。而逻辑运算符、条件运算符、成员访问运算符等则不能被重载。
对于可以重载的运算符,可以采用成员函数和友元函数进行重载。但是,有些运算符只能采用成员函数进行重载,例如赋值运算符和下标运算符。而有些运算符只能采用友元函数进行重载,例如流插入运算符和流提取运算符。具体使用哪种方式,需要根据具体的需求和情况来决定。

3、如何区分前置和后置++运算符的重载?

在C++中,前置和后置++运算符可以被重载,但是它们的重载方式是不同的。为了区分前置和后置++运算符的重载,需要注意以下几点:

  1. 前置++运算符重载函数的参数列表为空,返回值为引用类型;后置++运算符重载函数的参数列表为空,但是返回值为非引用类型。
  2. 前置++运算符重载函数在函数体内直接修改对象的值,并返回修改后的对象的引用;后置++运算符重载函数在函数体内先将对象的值保存到一个临时变量中,然后再将对象的值加1,并返回保存的临时变量的值。 例如,对于一个名为MyClass的类,可以如下定义前置和后置++运算符的重载函数:
class MyClass {
public:
    MyClass& operator++(); // 前置++运算符重载函数
    MyClass operator++(int); // 后置++运算符重载函数
};
MyClass& MyClass::operator++() {
    // 前置++运算符重载函数的实现
    // 直接修改对象的值,并返回修改后的对象的引用
    return *this;
}
MyClass MyClass::operator++(int) {
    // 后置++运算符重载函数的实现
    // 先将对象的值保存到一个临时变量中
    MyClass temp(*this);
    // 再将对象的值加1
    // 注意,这里不能直接调用前置++运算符重载函数,否则会导致无限递归调用
    // 应该直接修改对象的值
    // 然后返回保存的临时变量的值
    ++(*this);
    return temp;
}

通过以上的定义,可以在使用前置和后置++运算符时,自动调用对应的重载函数,从而实现自定义的操作。

4、类型转换函数的作用是什么?如何定义一个类型转换函数?

类型转换函数是一种特殊的成员函数,用于将一个类的对象转换为另一个类型的对象。它的作用是方便用户在不同类型之间进行转换,从而提高程序的灵活性和可读性。例如,可以定义一个将字符串转换为整数的类型转换函数,使得用户可以直接将字符串赋值给整数变量。
要定义一个类型转换函数,需要满足以下条件:

  1. 函数名必须与目标类型相同。
  2. 函数必须是类的成员函数。
  3. 函数不能有返回类型,但是可以有参数列表。
  4. 函数体内必须包含将当前对象转换为目标类型的代码。 例如,可以如下定义一个将字符串转换为整数的类型转换函数:
classMyClass{
public:
operatorint(){//将字符串转换为整数的类型转换函数
return std::stoi(str);//使用std::stoi函数将字符串转换为整数
}
private:
std::stringstr;//保存字符串的成员变量
};

通过以上的定义,可以在使用MyClass对象时,自动调用类型转换函数,将字符串转换为整数。例如:

MyClassobj("123");//创建一个保存字符串"123"的MyClass对象
intnum=obj;//将MyClass对象转换为整数
std::cout<<num<<std::endl;//输出123

需要注意的是,类型转换函数可能会导致意想不到的结果,因此应该谨慎使用。同时,为了避免与其他函数产生歧义,应该尽量避免定义过多的类型转换函数。

习题

1、在C++中,通常将重载运算符的成员函数称为( )。

A.运算符函数 B.重载函数 C.函数重载运算符 D.以上都不对

2、下列运算符中,( )运算符在C++中不能重载。

A.?: B.+ C.- D.<=

3、下列运算符中,( )运算符在C++中不能重载。

A.&& B.[] C.:: D.new

4、下列关于运算符重载的描述中,( )是正确的。

A.运算符重载可以改变运算数的个数 B.运算符重载可以改变优先级 C.运算符重载可以改变结合性 D.运算符重载不可以改变语法结构

5、运算符重载函数是( )。

A.成员函数 B.友元函数 C.内联函数 D.带默认参数的函数

6、定义描述平面上一个点的类Point,重载“++”和“--”运算符,并区分这两种运算符的前置和后置操作,构成一个完整的程序。

#include <iostream>

using namespace std;

class Point {
public:
    Point(double x = 0, double y = 0) : m_x(x), m_y(y) {}

    Point operator++(); //前置++
    Point operator++(int); //后置++
    Point operator--(); //前置--
    Point operator--(int); //后置--

    void display() const {
        cout << "(" << m_x << ", " << m_y << ")" << endl;
    }

private:
    double m_x, m_y;
};

Point Point::operator++() {
    ++m_x;
    ++m_y;
    return *this;
}

Point Point::operator++(int) {
    Point old = *this;
    ++(*this);
    return old;
}

Point Point::operator--() {
    --m_x;
    --m_y;
    return *this;
}

Point Point::operator--(int) {
    Point old = *this;
    --(*this);
    return old;
}

int main() {
    Point p1(1, 1);

    cout << "p1: ";
    p1.display();

    ++p1;
    cout << "p1 after ++p1: ";
    p1.display();

    p1++;
    cout << "p1 after p1++: ";
    p1.display();

    --p1;
    cout << "p1 after --p1: ";
    p1.display();

    p1--;
    cout << "p1 after p1--: ";
    p1.display();

    return 0;
}

输出结果:

p1: (1, 1)
p1 after ++p1: (2, 2)
p1 after p1++: (3, 3)
p1 after --p1: (2, 2)
p1 after p1--: (1, 1)

在这个实现中,Point类中重载了前置和后置“++”和“--”运算符,用于将该点的x和y坐标增加或减少1。其中,前置运算符通过引用返回自身,而后置运算符则返回自身的一个副本。在主函数中,对该类进行了测试。

7、构造一个分数类rationalNumber,该类中包括分子和分母两个成员数据,并具有下述功能:

(1)建立构造函数,它能防止分母为零,当分数不是最简形式时进行约分,并避免分母为负数。 (2)重载加法、减法、乘法以及除法运算符。 (3)重载关系运算符:>、<、==等。

#include <iostream>
#include <cstdlib>

using namespace std;

class rationalNumber {
private:
    int numerator, denominator;

    // 辗转相除法求最大公因数
    int gcd(int a, int b) {
        if (b == 0) return a;
        return gcd(b, a % b);
    }

    // 约分函数
    void reduce() {
        if (numerator == 0) {
            denominator = 1;
            return;
        }
        if (denominator < 0) {
            numerator = -numerator;
            denominator = -denominator;
        }
        int d = gcd(abs(numerator), denominator);
        numerator /= d;
        denominator /= d;
    }

public:
    rationalNumber(int num = 0, int den = 1) : numerator(num), denominator(den) {
        if (denominator == 0) {
            cout << "分母不能为零!" << endl;
            exit(1);
        }
        reduce();
    }

    int getNumerator() const { return numerator; }

    int getDenominator() const { return denominator; }

    // 重载加法运算符
    rationalNumber operator+(const rationalNumber &r) const {
        int num = numerator * r.denominator + r.numerator * denominator;
        int den = denominator * r.denominator;
        return rationalNumber(num, den);
    }

    // 重载减法运算符
    rationalNumber operator-(const rationalNumber &r) const {
        int num = numerator * r.denominator - r.numerator * denominator;
        int den = denominator * r.denominator;
        return rationalNumber(num, den);
    }

    // 重载乘法运算符
    rationalNumber operator*(const rationalNumber &r) const {
        int num = numerator * r.numerator;
        int den = denominator * r.denominator;
        return rationalNumber(num, den);
    }

    // 重载除法运算符
    rationalNumber operator/(const rationalNumber &r) const {
        int num = numerator * r.denominator;
        int den = denominator * r.numerator;
        return rationalNumber(num, den);
    }

    // 重载等于运算符
    bool operator==(const rationalNumber &r) const {
        return numerator == r.numerator && denominator == r.denominator;
    }

    // 重载不等于运算符
    bool operator!=(const rationalNumber &r) const {
        return !(*this == r);
    }

    // 重载大于运算符
    bool operator>(const rationalNumber &r) const {
        int num1 = numerator * r.denominator;
        int num2 = r.numerator * denominator;
        return num1 > num2;
    }

    // 重载小于运算符
    bool operator<(const rationalNumber &r) const {
        int num1 = numerator * r.denominator;
        int num2 = r.numerator * denominator;
        return num1 < num2;
    }

    // 重载大于等于运算符
    bool operator>=(const rationalNumber &r) const {
        return (*this == r) || (*this > r);
    }

    // 重载小于等于运算符
    bool operator<=(const rationalNumber &r) const {
        return (*this == r) || (*this < r);
    }
}

8、定义一个链表类,重载下标运算符,使之返回链表中某个给定的成员。

#include <iostream>

using namespace std;

class ListNode {
public:
    int val;
    ListNode* next;
    ListNode(int val) {
        this->val = val;
        this->next = NULL;
    }
};

class LinkedList {
public:
    ListNode* head;
    LinkedList() {
        head = NULL;
    }
    void insert(int val) {
        ListNode* node = new ListNode(val);
        node->next = head;
        head = node;
    }
    ListNode& operator[](int index) {
        ListNode* current = head;
        int i = 0;
        while (current != NULL && i < index) {
            current = current->next;
            i++;
        }
        if (current == NULL) {
            throw "Out of range";
        }
        return *current;
    }
};

int main() {
    LinkedList list;
    list.insert(3);
    list.insert(2);
    list.insert(1);
    cout << list[0].val << endl; // output: 1
    cout << list[1].val << endl; // output: 2
    cout << list[2].val << endl; // output: 3
    return 0;
}

在链表类中,我们定义了一个insert()方法来插入新的节点。重载了下标运算符[],返回一个ListNode的引用。通过遍历链表,找到需要的位置后返回节点的引用。如果下标越界,则抛出一个异常。在主函数中,我们创建了一个LinkedList对象,插入了一些节点,并使用下标运算符输出了其中的节点值。