cppPrimer第十四章习题

14.1 什么情况下重载的运算符与内置运算符有所区别?在什么情况下重载的运算符又与内置运算符一样
  1. 重载的运算符和内置运算符在对象求值顺序短路求值属性上不同。
  2. 在优先级、结合律、运算对象的数量上相同。
14.2 为Sales_data编写重载的输入、输出、加法和复合赋值运算符

Sales_data.h

#pragma once
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
#include<string>
using std::ostream;
using std::istream;
using std::string;
using std::cin;
using std::cout;
using std::cerr;
using std::endl;

class Sales_data; //必须要提前声明,告诉编译器,后面有出现Sales_data是合法的。

//重载输入运算符
istream& operator>>(istream&, Sales_data&);
//重载输出运算符,只读Sales_data
ostream& operator<<(ostream&, const Sales_data&);
//重载加法运算符
Sales_data operator+(const Sales_data&, const Sales_data&);


class Sales_data {
	//声明友元函数,这里声明了之后,外面最好还是要保留一下初始声明
	//友元函数可以访问类中所有级别的成员

	friend istream& operator>>(istream&, Sales_data&);
	friend ostream& operator<<(ostream&, const Sales_data&);
	friend Sales_data operator+(const Sales_data&, const Sales_data&);

public:
	
	//Sales_data() = default;
	
	Sales_data(const string& s, unsigned n, double p) :
		bookNo(s), units_sold(n), revenue(p* n) {}
	//定义了一个默认构造函数(因为此时三个成员变量都有了默认初始值),令其与只接受一个string实参的构造函数功能相同
	Sales_data(string s = ""):Sales_data(s,0,0) {}
	
	Sales_data(istream& is):Sales_data()
	{
		//编译器跑到Sales_data.cpp文件中找函数的实现(定义)
		is >> *this; //从键盘上输入的数据输入到调用该函数的对象中
	}
	
	//重载符合赋值运算符
	Sales_data& operator+=(const Sales_data&);
	
	string isbn() const;
	Sales_data& combine(const Sales_data& rhs);
	
private:
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
	inline
	double avg_price() const;
		
};

#endif

Sales_data.cpp

#include "Sales_data.h" // 通过这个包含就带有一系列的#include<iostream>、using std::....;

string Sales_data::isbn() const
{
	return bookNo;
}
inline //内联函数,可以在调用点处展开,直接将调用替换为结果值
double Sales_data::avg_price() const
{
	return units_sold ? revenue / units_sold : 0;
}

Sales_data& Sales_data::combine(const Sales_data& rhs) 
{
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this;
}

istream& operator>>(istream& is, Sales_data& item)
{
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = item.units_sold * price;
	return is;
}

ostream& operator<<(ostream& os, const Sales_data& item)
{
	os << item.isbn() << " " << item.units_sold << " "
		<< item.revenue << " " << item.avg_price();
	return os;
}

Sales_data operator+(const Sales_data& lhs, const Sales_data& rhs)
{
	Sales_data sum = lhs;
	//法二:sum.combine(rhs);
	sum += rhs; //调用类重载的+=运算符
	/*法三:
	sum.units_sold += rhs.units_sold;
	sum.revenue += rhs.revenue;*/
	return sum;
}

Sales_data& Sales_data::operator+=(const Sales_data& rhs)
{
	//复合赋值运算符一般先+,后执行=
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;

	return *this; //返回左侧运算对象的引用
}
14.3 string和vector都定义了重载的以比较各自的对象,假设svec1和svec2是存放string的vector,确定在下面的表达式中分别使用了哪个版本的
(a)"cobble" == "stone";		
(b)svec1[0] == svec2[0];
(c)svec1 == svec2;
(d)svec1[0] == "stone";

(a)都不是;(b)string © vector (d) string

14.4 如何确定下列运算符是否应该是类的成员
(a)%	(b)%=	(c)++	(d)->	(e)<<	(f)&&	(g)==	(h)()

(a)%是算术运算符,是具有对称性的运算符,一般是普通的非成员函数

(b)%=是复合赋值运算符,一般来说应该是类的成员函数

©++是改变对象状态的运算符,通常应该是类的成员

(d)->为成员访问箭头,必须为类的成员,左侧对象必然是该类的成员

(e)<<是位移运算符一般是普通的非成员函数

(f)&&与运算符,是具有对称性的运算符可能转换任意一段的运算对象,应该是普通的非成员函数

(g)==关系运算符左右两端运算对象通常可互换,应该是普通的非成员函数

(h)()是调用运算符,左侧运算对象与类类型直接相关,应该是类的成员

14.10 对于Sales_data的输入运算符来说,如果给定了下面的输入将发生什么情况?
(a) 0-201-99999-9 10 24.95
(b) 10 24.95 0-210-999999-9

(a)正常将键盘输入传给Sales_data对象并计算revenue

(b)price读取的竟然是24.95被切掉的.95 ,造成输出不正确

14.14 你觉得为什么调用operator+=来定义operator+比其他方法更有效

利用复合赋值运算符来实现算术运算符可以避免直接对对象成员操作

14.15

我选择编写的是日期Date类,不应该含有其他算术运算符,因为,两个日期相加没什么意义。

14.21 编写Sales_data类的+和+=运算符,使得+执行的加法操作而+=调用+,相比于14.3节和14.4节对这两个运算符的定义、本题定义有何缺点?
Sales_data operator+(const Sales_data& lhs, const Sales_data& rhs)
{
	Sales_data sum = lhs;
	sum.units_sold += rhs.units_sold;
	sum.revenue += rhs.revenue;
	return sum;
}

Sales_data& Sales_data::operator+=(const Sales_data& rhs)
{
	Sales_data sum = *this+sum;
    this->units_sold = sum.units_sold;
    this->revenue = sum.revenue;
}

如果让+执行实际加法操作,而+=调用+则会使用到额外的局部变量,造成额外的资源消耗。

14.29 为什么不定义const 版本的递增和递减运算符?

因为递增和递减运算符一般来说都是要修改成员的值,而如果定义成const版本则无法对成员修改

14.31 我们的StrBlobPtr类没定义拷贝构造函数、赋值运算符及析构函数,为什么?

StrBlobPtr和ConstStrBlobPtr中的weak_ptr只是对指针的弱引用,因此直接销毁该类指针不影响指针引用的对象,而且可以随意赋值。所以拷贝控制成员只要使用编译器合成的版本即可。

14.32 定义一个类令其含有指向StrBlob对象的指针,为这个类定义重载的箭头运算符。
#include"StrBlob.h"

class StrBlobPtr;
class StrBlobPtr_pointer
{
public:
	StrBlobPtr_pointer() = default;
	StrBlobPtr_pointer(StrBlobPtr* p) :pointer(p) {}

	StrBlobPtr& operator*() const
	{
		return *pointer;
	}
	StrBlobPtr* operator->() const
	{
		return pointer;
	}
private:
	//给予默认值
	StrBlobPtr* pointer = nullptr;
};
14.33 一个重载的函数调用运算符应该接受几个运算对象

没有限制,根据函数对象具体所需要的参数个数来定

14.39 编写一个类令其检查某个给定的string对象是否与一个阈值相等,使用该对象编程,统计并报告长度在1至9之间的单词有多少个、长度在10以上的单词又有多少个。
#include<iostream>
#include<string>
#include<fstream>
#include<sstream>
#include<vector>
#include<algorithm>

using std::find_if;
using std::istringstream;
using std::vector;
using std::ifstream;
using std::string;
using std::cout;
using std::endl;
using std::cin;

class Comp {
public:
	Comp(int i = 0) :val(i) {}
	bool operator()(const string& s)
	{
		if (s.size() == val)
			return true;
		return false;
	}
private:
	int val;
};

void p14_38()
{
	ifstream ifs("test1.txt");
	vector<string> svec;
	string s;
	while (ifs >> s)
		svec.push_back(s);
	int count = 0;
	//计算长度在0-9之间的单词
	for (int i = 1; i != 10; ++i)		
		count += std::count_if(svec.begin(), svec.end(), Comp(i)); 
	cout << count << " " << svec.size() - count << endl;
}
14.41 你认为C++11新标准为什么要增加lambda?对于你自己来说,什么情况下会使用lambda,什么情况下会使用类?
  1. 便于那些只用于一个地方某种特定操作使用lambda而不需要定义函数提供外部使用
  2. 当操作步骤只有一条语句并且是针对性操作时会使用lambda
    当操作复杂,且需要提供外部使用的时候使用类来重载函数调用运算符更好。
14.42 使用标准库函数对象及适配器定义一条表达式,令其

(a) 统计大于1024的值有多少个

(b)找到第一个不等于pooh的字符串

©将所有的值乘以2

std::count_if(ivec.cbegin(), ivec.cend(), std::bind(std::greater<int>(),_1, 1024));
std::find_if(svec.cbegin(), svec.cend(), std::bind(std::not_equal_to<std::string>(),_1,"pooh"));
//对ivec.begin()到ivec.end()之间元素指向最后一个参数提供的操作,并写入到以ivec.begin()开始的位置。
std::transform(ivec.begin(), ivec.end(), ivec.begin() std::bind(std::multiplies<int>(), _1, 2));         
    
14.43 使用标准库函数对象判断一个给定的int值能否被int容器中的所有元素整除
void p14_43()
{
	vector<int> ivec = { 2, 4, 6, 8, 10, 12 };
	int count = 0, g;
	cin >> g;
	//在[begin(),end())间【只要有一个】元素使得第三个函数对象为true就返回true(非0)
	bool b = std::any_of(ivec.begin(), ivec.end(), std::bind(std::modulus<int>(), std::placeholders::_1, g));
	//b为true表示至少有一个数不能被g整除
	if (b)
		cout << "No!" << endl;
	else
		cout << "Yes!" << endl;
}
14.45 编写 一个转换运算符将一个Sales_data对象分别转换成string和double,你认为这些运算符的返回值应该是什么?
//定义类型转换运算符,无参,无返回值
	explicit operator string() const { return bookNo; }
	explicit operator double() const { return avg_price(); }
14.46 你认为应该为Sales_data类定义上面两种类型转换运算符吗?应该把他们声明成explicit吗?为什么?

我认为不应该定义上面两种类型转换运算符,因为没有什么意义,如果实在要定义应该是explicit,否则会造成歧义,编译器不知道隐式转换选哪个。

14.47 说明下面这两个类型转换运算符的区别。
struct Integral{
	operator const int();
    operator int() const;
};

operator const int(); 这个是指允许从非const的Intergral对象隐式转换为 const int

operator int() const;是指允许任何Intergral对象隐式转换为int

14.48 在7.5.1节的练习7.40曾经选择并编写了一个类,你认为它应该含有向bool的类型转换运算符吗?如果是,解释原因并说明该运算符是否应该是explicit的;如果不是,解释原因

应该含有向bool 的类型转换运算符,表示日期的数字是否合法。

应该要有explicit,否则隐式转换回造成歧义。

14.50 在初始化ex1和ex2过程中,可能用到哪些类型转换序列?说明初始化是否正确并说明原因
struct LongDouble {
	LongDouble(double = 0.0) {}
	operator double() 
	{
		return 3.14;
	}
	operator float()
	{
		return 9.9;
	}
};
void p14_50()
{
	LongDouble ldObj; 
	int ex1 = ldObj;
	float ex2 = ldObj;
}

ldObj使用了LongDouble(double = 0.0);正确初始化

ex1初始化失败,因为ldObj可以调用两个转换运算符operator double()operator float(),存在二义性

ex2初始化成功,调用operator float()这个转换运算符将LongDouble转换为float

14.51 在调用calc的过程中,可能用到哪些类型转换序列。说明最佳可行函数是被如何选出来的。
void calc(int)
{
	cout << "calc(int)" << endl;
}
void calc(LongDouble)
{
	cout << "calc(LongDouble)" << endl;
}

double dval = 1.2;
calc(dval);

如果调用void calc(int)需要标准类型转换将实参从double转换为int(算术转换)

如果调用void calc(LongDouble)需要类定义的转换运算符将double转换为类类型LongDouble

算术转换优先级更高,所以选择void calc(int)

转换的优先级如下:

  1. 精确匹配
  2. const转换
  3. 类型提升(short->int)
  4. 算术转换(int->double)
  5. 类类型转换
14.52 在下面的加法表达式中分别选用了哪个operator+?列出候选函数、可行函数及为每个可行函数的实参执行的类型转换
class SmallInt {
	friend SmallInt operator+(const SmallInt&, const SmallInt&);
public:
	SmallInt(int n = 0) :
		val(n)//将int转换为SmallInt对象的转换构造函数
	{
		cout << "SmallInt(int n = 0)" << endl;
	}
	operator int() const 
	{ 
		cout << "SmallInt.operator int()" << endl;
		return val; 
	} // 将SmallInt转换为int
private:
	size_t val;
};
struct LongDouble {
    //用于演示的成员operator+;通常情况下+是个非成员
	LongDouble operator+(const smallInt&);
    LongDouble(double = 0.0) {}
	operator double() 
	{
		return 3.14;
	}
	operator float()
	{
		return 9.9;
	}
};

LongDouble operator+(LongDouble&, double);
SmallInt si;
LongDouble ld;
ld = si + ld;
ld = ld + si;

对于ld = si + ld;:

  1. SmallInt转换为int,LongDouble转换为double,double转换为int,调用int的加法,得到的结果调用转换构造函数

  2. SmallInt转换为int,LongDouble转换为float,float转换为int,调用int的加法,得到的结果调用转换构造函数

  3. SmallInt转换为int,int转换为double;LongDouble转换为double,调用double的加法,得到结果调用转换构造函数

    三种方式都可以,造成二义性

对于ld = ld + si;:

  1. 可以将SmallInt转换成int,int再转换成double,随后调用LongDouble operator+(LongDouble&, double);
  2. SmallInt转换为int,LongDouble转换为double,double转换为int,调用int的加法,得到的结果调用转换构造函数
  3. SmallInt转换为int,LongDouble转换为float,float转换为int,调用int的加法,得到的结果调用转换构造函数
  4. SmallInt转换为int,int转换为double;LongDouble转换为double,调用double的加法,得到结果调用转换构造函数
  5. 直接调用LongDouble operator+(const smallInt&);精确匹配
14.53 假设我们定义了如522页所示的SmallInt,判断下面的加法表达式是否合法。如果合法,使用了哪个加法运算符?如果不合法,应该怎样修改代码才能使其合法?
SmallInt s1;
double d = s1 + 3.14;

不合法,将s1转换为int,int转换为double调用内置类型的加法运算符或者SmallInt operator+(const SmallInt&, const SmallInt&)成员运算符都可以,造成二义性。应该修改代码为:

SmallInt s1;
double d = static_cast<int>(s1) + 3.14;

或者

SmallInt s1;
double d = operator+(si,3.14);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值