C++习题(四)
题目综述
- 类的定义与基本操作(关于 Fraction类 的内容补全)
- 数组与函数的综合应用
- 类的定义与基本操作 (关于 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
(三)同时在上述类的定义基础上完善下列操作:
- 显示定义析构函数
- 获取分数的分子
- 获取分数的分母
- 实现分数的约分
- 实现对两个分数象进行通分
- 使用 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中的数据,按从小到大顺序重新排序。
要求依次实现:
- 编写 顺序查找法函数 和 折半查找法函数 ,分别在数组 a 和 b 中查找元素 17 所在的下标并输出
- 编写 判断素数函数 和 排序函数 ,并对容器 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;
}
(二)题目要求:
- 说明上述程序执行流程和输出结果
- 在 Point 类中完善获取点的横坐标、获取点的纵坐标成员函数,并在主函数中测试
- 通过友元函数实现平面上任意两个点的距离计算辅助函数
- 在 Circle 类中完善圆的面积计算与圆的周长计算成员函数,并在主函数中测试
分步骤处理:
- 说明上述程序执行流程和输出结果
- 完善 Point 类、Circle 类的内容
- 完善类外的函数
- 主函数测试
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;
}
- 运行结果: