面向对象程序设计——Visual C++ 第13章 运算符重载
思考题
1、用成员函数重载运算符和用友元函数重载运算符,在定义和使用上有什么不同?
在C++编程语言中,用成员函数重载运算符和用友元函数重载运算符,在定义和使用上有以下不同:
- 定义方式不同:成员函数重载运算符是在类内部定义的,而友元函数重载运算符是在类外部定义的。
- 访问权限不同:成员函数重载运算符可以访问类的私有成员,而友元函数重载运算符不能访问类的私有成员。
- 参数个数不同:成员函数重载运算符只有一个参数,即调用对象本身,而友元函数重载运算符可以有多个参数。
- 调用方式不同:成员函数重载运算符可以通过对象直接调用,而友元函数重载运算符需要通过函数名和参数列表来调用。
- 返回值不同:成员函数重载运算符的返回值是对象本身,而友元函数重载运算符的返回值可以是任意类型。
总的来说,成员函数重载运算符更加方便和直观,而友元函数重载运算符更加灵活和通用。具体使用哪种方式,需要根据具体的需求和情况来决定。
2、在C++中是否所有的运算符都可以重载?在可以重载的运算符中,是否都能够采用成员函数和友元函数进行?
在C++中,并不是所有的运算符都可以重载。只有部分运算符可以被重载,例如算术运算符、比较运算符、位运算符等。而逻辑运算符、条件运算符、成员访问运算符等则不能被重载。
对于可以重载的运算符,可以采用成员函数和友元函数进行重载。但是,有些运算符只能采用成员函数进行重载,例如赋值运算符和下标运算符。而有些运算符只能采用友元函数进行重载,例如流插入运算符和流提取运算符。具体使用哪种方式,需要根据具体的需求和情况来决定。
3、如何区分前置和后置++运算符的重载?
在C++中,前置和后置++运算符可以被重载,但是它们的重载方式是不同的。为了区分前置和后置++运算符的重载,需要注意以下几点:
- 前置++运算符重载函数的参数列表为空,返回值为引用类型;后置++运算符重载函数的参数列表为空,但是返回值为非引用类型。
- 前置++运算符重载函数在函数体内直接修改对象的值,并返回修改后的对象的引用;后置++运算符重载函数在函数体内先将对象的值保存到一个临时变量中,然后再将对象的值加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、类型转换函数的作用是什么?如何定义一个类型转换函数?
类型转换函数是一种特殊的成员函数,用于将一个类的对象转换为另一个类型的对象。它的作用是方便用户在不同类型之间进行转换,从而提高程序的灵活性和可读性。例如,可以定义一个将字符串转换为整数的类型转换函数,使得用户可以直接将字符串赋值给整数变量。
要定义一个类型转换函数,需要满足以下条件:
- 函数名必须与目标类型相同。
- 函数必须是类的成员函数。
- 函数不能有返回类型,但是可以有参数列表。
- 函数体内必须包含将当前对象转换为目标类型的代码。 例如,可以如下定义一个将字符串转换为整数的类型转换函数:
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
对象,插入了一些节点,并使用下标运算符输出了其中的节点值。