C++习题(四)

C++习题(四)

题目综述

  1. 类的定义与基本操作(关于 Fraction类 的内容补全)
  2. 数组与函数的综合应用
  3. 类的定义与基本操作 (关于 Point类、Circle类 的内容补全)

题目详解

一、类的定义与基本操作(关于 Fraction类 的内容补全)

(一)题目代码部分:
class Fraction
 {
	//数据成员,访问控制属性默认是私有
	int m_numerator = 0; // 分子默认为0; C++11
	int m_denominator = 1; //分母默认为1;
public: //公有成员函数
	Fraction(int above = 0, int below = 1) :m_numerator(above), m_denominator(below) 
	{
		cout << "Constructor called" << endl;
	}
	Fraction(const Fraction& rhs) :m_numerator(rhs.m_numerator),m_denominator(rhs.m_denominator)
	 {
		cout << "Copy constructor called" << endl;
	}
};
Fraction divide1(const Fraction& divident, const Fraction& divisor) 
{
	return Fraction(divident.getnumerator() * divisor.getdenominator(), divident.getdenominator() * divisor.getnumerator());
}
Fraction divide2(Fraction divident, Fraction divisor) 
{
	Fraction result(divident.getnumerator() * divisor.getdenominator(),divident.getdenominator() * divisor.getnumerator());
	return result;
}
(二)说明执行下列语句后,分别执行的什么操作会输出什么?

Fraction a;

创建了 Fraction类 对象 a,采用默认构造函数
即 a.m_numerator = 0 ,a.m_denominator = 1

——————————————————————————————→ 输出一次 “Constructor called ”

Fraction b(a);

创建了 Fraction类 对象 b,采用拷贝构造函数
即 b.m_numerator = 0== ,b.m_denominator = 1

——————————————————————————————→ 输出一次 “Copy constructor called ”

Fraction c = Fraction(3, 2);

创建了 Fraction类 对象(3,2),采用默认构造函数
创建了 Fraction类 对象c,通过 “ = ” 赋值运算将对象(3,2)赋值给 c,不采用构造函数
即 c.m_numerator = 3 ,c.m_denominator = 2

——————————————————————————————→ 输出一次 “Constructor called ”

Fraction d1(2, 3), d2(4, 5);

创建了 Fraction类 对象d1(2,3),d2(4,5)采用拷贝构造函数
即 d1.m_numerator = 2 ,d1.m_denominator = 3

——————————————————————————————→ 输出一次 “Constructor called ”
即 d2.m_numerator = 4 ,d2.m_denominator = 5
——————————————————————————————→ 输出一次 “Constructor called ”

★ Fraction e1 = divide1(d1, d2);

先运行divide1函数:
★ 由于divide1函数中形参是引用类型,直接使用d1,d2,不需要再创建新的类对象
通过创建一个无名类的对象作为函数返回值
即 ( ).m_numerator = 10 ,( ).m_denominator = 12

——————————————————————————————→ 输出一次 “Constructor called ”
然后将函数返回值直接赋值给 Fraction类 对象e1
即 e1.m_numerator = 10 ,e1.m_denominator = 12

★★★ 本来由于divide1函数返回值是类对象,在函数输出时应该将输出值放到暂存空间里,会调用拷贝构造函数,输出一次 “Copy constructor called ”,但此时VS2019的编译器对调用情况作了优化,故此处没有调用拷贝构造函数

★ Fraction e2 = divide2(d1, d2);

先运行divide2函数:
★ 由于divide1函数中形参是 Fraction 类型,要先创建形参对象 divident 和 divisor
★★ 函数做形参实例化时是从右向左进行结合的,故先创建 divisor 后创建 divident

1.创建了 Fraction类 对象 divisor ,通过拷贝构造函数
即 divisor.m_numerator = 4 ,divisor.m_denominator = 5

——————————————————————————————→ 输出一次 “Copy constructor called ”

2.创建了 Fraction类 对象 divident ,通过拷贝构造函数
即 divident.m_numerator = 2 ,divident.m_denominator = 3

——————————————————————————————→ 输出一次 “Copy constructor called ”

3.创建了 Fraction类 对象 result ,通过默认构造函数
即 result.m_numerator = 10 ,result.m_denominator = 12

——————————————————————————————→ 输出一次 “Constructor called ”
★★★ 由于divide2函数返回值是类对象,在函数输出时应该将输出值放到暂存空间里,会调用拷贝构造函数
——————————————————————————————→ 输出一次 “Copy constructor called ”

4.创建了 Fraction类 对象 e2 ,将函数返回值直接赋值给 Fraction类 对象e2
即 e2.m_numerator = 10 ,e2.m_denominator = 12


(三)同时在上述类的定义基础上完善下列操作:
  1. 显示定义析构函数
  2. 获取分数的分子
  3. 获取分数的分母
  4. 实现分数的约分
  5. 实现对两个分数象进行通分
  6. 使用 operator / 操作符重载实现两个分数的除法运算
分步骤处理:
①完善Fraction类的内容
  • 显示定义析构函数:

    ~Fraction(){ cout "Destructor of endl; }
    
  • 获取分数的分子和分母函数:

    ★ 注意:后面的函数 divide1 中都传入了 Fraction 的const引用类型,而且当中显然已经规定了获取分子的函数名称为 getnumerator() ,获取分母的函数名称为 getdenominator(),故只能按这两个名字来写函数,而且要用 const 来修饰这两个常量成员函数

    //获取分子
    int getnumerator() const { return m_numerator; }
    
    //获取分母
    int getdenominator() const { return m_denominator; }
    
  • 修改分数的分子和分母函数:

    //修改分子
    void change_numerator(int new_numerator)
    {
    	this->m_numerator = new_numerator;
    }
    //修改分母
    void change_denominator(int new_denominator)
    {
    	this->m_denominator = new_denominator;
    }
    
  • ★★★ 约分函数:

    ★ 约分的关键点:找到分子和分母的最大公因数,找到之后分子分母同时除以最大公因数即可

    1.先用递归法写找两个数最大公因数的函数 gcd:

    //求两个数的最大公因数
    int gcd(int num1, int num2)
    {
    	int bigger = max(num1, num2);
    	int smaller = min(num1, num2);
    	//通过递归的方式找最大公因数
    	if (bigger % smaller == 0) { return smaller;}
    	else
    	{
    		//两个数的最大公因数是小的数和两数余数的最大公因数	 
    		return gcd(smaller, bigger % smaller); 
    	}
    }
    

    2.约分函数:

    //将单个分式约分
    Fraction Fraction_reduce()
    {
    	//本质上要先找到分子和分母之间的最大公因数,然后分子分母同时除去最大公因数
    	//先找出最大因子
    	int divisor = gcd(m_numerator, m_denominator);  
    
    	//约分,即分子分母同时除以最大因子
    	this->m_numerator /= divisor;
    	this->m_denominator /= divisor;
    
    	//返回分式
    	return *this;
    }
    
②完善Fraction类外函数的内容
  • 使用 operator / 操作符重载实现两个分数的除法运算:

    //除号运算符重载
    Fraction operator/ (const Fraction& divident,const Fraction& divisor)
    {
    	return Fraction(divident.getnumerator() * divisor.getdenominator(), //分子部分
    	divident.getdenominator() * divisor.getnumerator());    //分母部分
    }
    
  • ★★★ 实现两个分数的通分函数:

    ★ 通分的关键点:找到两个分式分母的最小公倍数,然后把两个分式的分子分母都乘以最小公倍数

    1.利用刚刚写好的最大公因数 gcd,用两个数相乘再除以公因数即可得到最小公倍数:

    //两个数的最小公倍数等于两整数之积除以最大公约数
    int lcm(int num1, int num2)
    {
    	return num1 * num2 / gcd(num1, num2);
    }
    

    2.通分函数:

    //将两个分式通分函数
    void find_common_denominator(Fraction& num1, Fraction& num2)
    {
    	//本质上先找输入分式分母和自己分母的最小公倍数,然后自己分子分母同乘最小公倍数
    	//先找出最小公倍数因子
    	int diviser = lcm(num1.getdenominator(), num2.getdenominator());
    
    	//对num1通分,即分子分母同时乘以因子
    	num1.change_numerator(num1.getnumerator() * diviser);
    	num1.change_denominator(num1.getdenominator() * diviser);
    
    	//再对num2通分
    	num2.change_numerator(num2.getnumerator() * diviser);
    	num2.change_denominator(num2.getdenominator() * diviser);
    }
    

二、数组与函数的综合应用

已知:int a[5] = { 19,67,24,11,17 }, b[5] = { 2,3,9,17,59 };
编写程序查找数组中是否存在某个指定元素,同时将数组a和数组b中的素数不重不漏地合并到一个vector容器c中,然后按照下标访问的方式手动对容器c中的数据,按从小到大顺序重新排序。
要求依次实现:
  1. 编写 顺序查找法函数折半查找法函数 ,分别在数组 a 和 b 中查找元素 17 所在的下标并输出
  2. 编写 判断素数函数排序函数 ,并对容器 c 中的结果进行输出
分步骤处理:
①针对数组内元素的查找函数
  • 顺序查找函数:通过遍历数组的每一个元素,看是否和目标值对应即可

    //1.顺序查找函数
    //传入参数:数组名,数组长度,目标值
    int CommonSearch(int obj[],int nums,int target)
    {
    	//通过循环,一个一个找
    	for (int i = 0; i < nums; ++i)
    	{
    		if (obj[i] == target){ return i;}
    	}
    	cout << "数组 " << obj << "中没有 " << target << "这个数!" << endl;
    }
    
  • 折半查找 ( 二分查找 ) 函数:

    ★ 注意:能进行二分查找的数组本身要求有序 !!

    要先对输入的数组进行排序 : obj = Sort ( obj , nums );
    Sort 函数输入数组名和数组长度 nums 即可
    这个排序函数与后面的 “ 用下标法对 c 从小到大排序 ” 类似,故在此不展示

    ★ 二分查找核心:通过比大小整半地缩小搜素区间

    先设定 低位( 起始位 )下标 low = 0,高位( 结束位 )下标 high = nums,中间值下标 mid = 0
    此时搜素区间为:[ obj [ low ] , obj [ high ] ]

    缩小区间的方式: 看每次循环的目标值 target 和中间值 obj [ mid ] 的大小情况

    ① target > obj [ mid ] :
    说明 target 只会出现在中间值后面,即出现在区间 ( obj [ mid ] , obj [ high ] ]
    此时只需要将 mid 的下一位赋给 low,就可改变原来的区间长度: low = mid + 1;
    同时改写 mid : mid = ( low( 刚刚改好的 ) + high ) / 2;

    ② target < obj [ mid ] :
    说明 target 只会出现在中间值前面,即出现在区间 ( obj [ low ] , obj [ mid ] ]
    此时只需要将 mid 的上一位赋给 high,就可改变原来的区间长度: high = mid - 1;
    同时改写 mid : mid = ( low + high( 刚刚改好的 ) ) / 2;

    ③ target == obj [ mid ] :
    说明找到了目标值,直接返回下标 mid 即可:return mid;

    //2.折半查找函数
    //传入参数:数组名,数组长度,目标值
    int BinarySearch(int obj[], int nums, int target)
    {
    	//折半查找(二分查找)需要先对数组进行排序,只有有序数组才可以进行二分法查找
    	obj = Sort(obj, nums);
    	//先找中间的数下标
    	int low = 0, high = nums - 1;
    	int mid = 0;
    	while (low <= high)
    	{
    		//改变中间值
    		mid = mid = (low + high) / 2;
    
    		//如果此时中间值下标对应的数就是目标值则输出下标 mid
    		if (obj[mid] == target)
    		{
    			return mid;
    		}
    
    		//如果发现中间值下标对应的数比目标值要小
    		//说明目标值应该在更大的那一边
    		else if (obj[mid] < target)
    		{
    			//此时要修改搜素区间
    			//把新搜素区间最小值改成中间值 + 1
    			low = mid + 1;
    		}
    
    		//如果发现中间值下标对应的数比目标值要大
    		//说明目标值应该在更小的那一边
    		else
    		{
    			//此时要修改搜素区间
    			//把新搜素区间最大值改成中间值 - 1
    			high = mid - 1;
    		}
    	}
    	cout << "数组 " << obj << "中没有 " << target << "这个数!" << endl;
    }
    

②将数组 a 和数组 b 中的素数不重不漏地合并到一个 vector 容器 c 中
  • 判断数据是否为素数的函数:

    原理之前已经在上机练习 2 中写过(第 8 题),★链接:19C20B-C班 第二次上机习题

    //1.判断数据是否为素数的函数
    bool is_PrimeNumber(int number)
    {
    	//现在判断数 number 是否为素数
    	//判断一个数 number 是否为素数,就从 2 开始遍历 i ,看看 number 能不能被 i 除尽
    	for (int i = 2; i < number; ++i)
    	{
    		//当 number 不能被此时的 i 除尽时,说明此时的 i 不是 number 的倍数
    		if ((number % i) != 0)
    		{
    			//如果这样的i(不是 number 的倍数的 i)又恰好是 number 前面那个数
    			//说明此时 i 前面所有的数都不是 number 的倍数
    			//那么这个时候 number 就肯定是素数了
    			if (i == (number - 1))
    			{
    				return true;
    			}
    		}
    		else
    		{
    			//else的情况就是 i 是 number 的倍数
    			//则 number 必不是素数
    			return false;
    		}
    	}
    }
    
  • 合并两数组中不重复的素数函数:

    ★ 注意:题目要求 “ 不重复 ”

    我的实现方法:
    ①遍历第一个数组,把第一个数组的素数放到容器 c 里
    ②遍历第二个数组,先别管啥重不重复,都和 ① 一样先给它放到 c 里
    ③ ★★★放了之后遍历现在 c 中除了刚刚放进来的以外所有元素 ,如果发现有和这个元素相同的,就再把刚刚放进来的删掉,如果没有就不管

    //2.合并两数组中不重复的素数
    //传入参数:数组 A 名(指针),数组 A 长度,数组 B 名(指针),数组 B 长度
    vector<int> add_PrimeNumber(int* a, int a_nums,int* b, int b_nums)
    {
    	//创建好 vector 容器 c
    	vector<int> c;
    
    	//用循环遍历两个数组
    	for (int i = 0; i < a_nums; ++i)
    	{
    		//先把 A 数组里的所有素数加到 c 中
    		if ( is_PrimeNumber(a[i]) )
    		{
    			c.push_back(a[i]);
    		}
    		
    	}
    	for (int j = 0; j < b_nums; ++j)
    	{
    		//再看 B 数组
    		if ( is_PrimeNumber(b[j]) )
    		{
    			//不管 c 里面有没有这个数,都先写入 c 里面
    			c.push_back(b[j]);
    			
    			//写入后再判定 c 之前是否已经有这个数了,有的话再把加的数删回去
    			for (int m = 0; m < c.size() - 1; ++m)
    			{
    				if (b[j] == c[m]) { c.pop_back(); }
    			}
    			
    		}
    	}
    	return c;
    }
    

③用下标法对 c 从小到大排序
  • 交换排序法: 通过不停比较和交换两次循环中 i 下标的值和 j 下标值的大小关系进行排序

    ★ 注意:题目要求从小到大排序

    交换排序法:
    ①两次遍历,每当完成一次内层 j 的遍历,通过比较与交换的方式,就相当于找到了当前外层循环 i 下标对应值和后面所有数的最小值,而且已经将这个最小值交换到了 i 位置的元素上
    就是相当于已经将 i 位置前面所有元素从小到大拍好了
    ②这样走完外层循环后,整个数组就完成了排序

    /*任务三、用下标法对 c 从小到大排序*/
    //传入参数:vector容器名
    vector<int> Sort_for_vector(vector<int>& c)
    {
    	int nums = c.size();
    	//采用交换排序法
    	for (int i = 0; i < nums; ++i)
    	{
    		for (int j = i + 1; j < nums; ++j)
    		{
    			//如果后面的数比前面的小,就把两数交换
    			//保证一轮循环走完,i下标的数是i后面最小的数
    			if (c[i] > c[j])
    			{
    				int ntemp = c[i];
    				c[i] = c[j];
    				c[j] = ntemp;
    			}
    		}
    	}
    	return c;
    }
    
④主函数调试部分:
int main()
{
	int a[5] = { 19,67,24,11,17 }, b[5] = { 2,3,9,17,59 };
	cout << "定义数组a,b:" << endl <<
		"a[5] = { 19,67,24,11,17 }" << endl <<
		"b[5] = { 2,3,9,17,59 }" << endl;

	cout << "查询数组a,b中是否有17,有的话返回下标:" << endl;
	cout << "①数组a情况: " << CommonSearch(a, 5, 17) << endl;
	cout << "②数组b情况: " << BinarySearch(b, 5, 17) << endl;
	cout << endl;

	vector<int> c;

	cout << "现在将 a,b 数组中素数不重复不遗漏添加到 vector 容器 c 中" << endl;
	c = add_PrimeNumber(a, 5, b, 5);


	cout << "输出目前的 c:" << endl;
	for (int i = 0; i < c.size(); ++i)
	{
		cout << c[i] << " ";
	}
	cout << endl;

	cout << "现在将c中数据由小到大排序:" << endl;
	c = Sort_for_vector(c);
	for (int i = 0; i < c.size(); ++i)
	{
		cout << c[i] << " ";
	}
	cout << endl;

	return 0;
}

运行结果:

在这里插入图片描述


三、类的定义与基本操作 (关于 Point类、Circle类 的内容补全)

(一)题目代码部分:
class Point 
{
	double m_x = 0, m_y = 0;
public:
	Point(double x=0, double y=0) : m_x(x), m_y(y) { cout <<"Constructor of Point"<< endl; }
	Point(const Point& p) :m_x(p.m_x), m_y(p.m_y) { cout <<"Copy constructor of Point"<<  endl; }
	~Point() { cout <<"Destructor of Point"<< endl; }
};

class Circle 
{
	Point m_center; double m_radius = 1.0;
public:
	Circle(double r = 1, const Point& p = Point() ) :m_center(p), m_radius(r) 
	{
		cout <<"Constructor of Circle"<< endl;
	}
	~Circle() { cout <<"Destructor of Circle"<< endl; }
};

int main()
{
	Circle a(2, Point(1, 1));
	cout << "end" << endl;
	return 0;
}
(二)题目要求:
  1. 说明上述程序执行流程和输出结果
  2. 在 Point 类中完善获取点的横坐标、获取点的纵坐标成员函数,并在主函数中测试
  3. 通过友元函数实现平面上任意两个点的距离计算辅助函数
  4. 在 Circle 类中完善圆的面积计算与圆的周长计算成员函数,并在主函数中测试
分步骤处理:
  1. 说明上述程序执行流程和输出结果
  2. 完善 Point 类、Circle 类的内容
  3. 完善类外的函数
  4. 主函数测试
1.说明上述程序执行流程和输出结果

1. Circle a(2, Point(1, 1));

用 2,Point(1,1)创建 Cirle 类对象 a,首先要调用Circle类的构造函数来构建(1,1):
——————————————————————————————→ 输出一次 “Constructor of Point ”

由于在 Circle类 的构造函数为:
Circle( double r = 1, const Point& p = Point( ) ) :m_center( p ), m_radius( r )

故构建Circle类对象必须要先创建 Point 类对象 p
所以在程序运行的过程中
会先调用 Point 类的拷贝构造函数,创建临时对象:

——————————————————————————————→ 输出一次 “Copy constructor of Point ”
然后完成 Circle 类对象 a 的构建:
——————————————————————————————→ 输出一次 “Constructor of Circle ”

★ 总结:“ 类中有类 ” 时的构造顺序是 “ 由内而外 ”

2. cout << “end” << endl;

输出 end 并换行

3. return 0;

主函数运行结束,需要释放所有在主函数中创建变量所使用的内存空间

而此时需要释放的是 Circle 类对象a的内存空间,调用析构函数 ~Circle( ) :
——————————————————————————————→ 输出一次 “Destructor of Circle ”

目前已经拆解了 Circle 类对象 a 的空间,但空间中的内容并未完全释放
★★ 因为 Circle 类对象在内存中占的空间是用来存放 1 个 double 型数据和 1 个 Point 类对象
而这个 Point 类对象的空间无法通过 Circle 类的析构函数来释放

所以还得通过 Point 类的析构函数把这部分空间完全释放:
——————————————————————————————→ 输出一次 “Destructor of Point ”
这样才能将 Circle 类对象 a 的空间完全释放

★ 总结:“ 类中有类 ” 时的析构顺序是 “ 由外而内 ”


2.完善 Point 类、Circle 类的内容
(1)在 Point 类中完善获取点的横坐标、获取点的纵坐标成员函数:

第一问里完善获取分子分母的写法类似,注意加 const 即可

//获取横坐标
double get_x() const { return m_x; }
//获取纵坐标
double get_y() const { return m_y; }
(2)在 Point 类中写入计算两点距离的友元函数声明:

注意:这里只写声明不写实现,实现写在类外

private:
	//友元函数
	//计算两点距离函数
	friend double Distance(const Point& A,const Point& B);
(3)在 Circle 类中完善圆的面积计算与圆的周长计算成员函数:
  • 声明部分:

    //计算面积函数
    double S();
    //计算周长函数
    double C();
    
  • 实现部分:

    //Circle类中计算面积函数实现部分
    double Circle::S()
    {
    	return m_radius * m_radius * PI;
    }
    //Circle类中计算周长函数实现部分
    double Circle::C()
    {
    	return 2 * m_radius * PI;
    }
    
3.完善类外的函数
  • 即完善计算两点间距离的函数:
//计算两点间距离函数
double Distance(const Point& A, const Point& B)
{
	double x1 = A.m_x, y1 = A.m_y;
	double x2 = B.m_x, y2 = B.m_y;

	return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}
4.主函数测试
  • 主函数部分:
int main()
{
	Circle a(2, Point(1, 1));
	cout << "输入半径为2,圆心坐标为(1,1)的圆" << endl;
	cout << "该圆周长为:" << a.C() << endl;
	cout << "该圆面积为:" << a.S() << endl;
	cout << endl;

	Point A(1, 1);
	Point B(1, 5);
	cout << "输入A(1,1),B(1,5)两点坐标:" << endl;
	cout << "两点的距离 |AB| = " << Distance(A, B) << endl;


	cout << "end" << endl;
	return 0;
}
  • 运行结果:

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值