一、复习题
1. 什么是类?
答:类是一种用户自定义的关于数据的抽象的组合,它描述了程序中数据的基本构成(数据成员)以及对数据的访问和操作(成员函数或方法)。
2. 类如何实现抽象、封装和数据隐藏?
答:类的定义就是把程序中的数据还原成一种基本数据模型和相应操作方式的抽象化过程。C++语言中把类内的数据成员和成员函数通过类的作用域以及3种基本访问控制(public、private和protected)进行了数据封装,从类的外部无法访问类的基本数据,而只能通过类的对象实体和类内的公有成员才能访问,这样类就实现了基本的数据隐藏和封装。
3. 对象和类之间的关系是什么?
答:面向对象的程序设计中,类是一种数据及其操作的模型定义,也就是一种数据的抽象。通过类可以生成很多对象实体,同一个类生成的每一个对象实体都能够在存储空间内拥有自己的存储空间单元,实现数据的存储和访问操作。因此可以认为类是抽象模型,对象是由类这个数据抽象模型实例化之后创建的真实数据单元。(我觉得可以这样理解,类是图纸,而对象是实物)
4. 除了类的函数成员是函数之外,类的函数成员与类的数据成员之间的区别还有什么?
答:从性质上看,类的数据成员用来描述类的基本数据构成,定义其存储空间和类型;而函数成员则是类的操作功能的定义。从存储上看,类的每一个对象都有自己的存储空间,存储自身的数据成员,而所有对象共用一组成员函数,即每种方法对于所有对象来说只有一个副本。
5. 定义一个类来表示银行账户。数据成员包括存储姓名、账户(两者使用字符串表示)和存款。成员函数执行如下操作。
a. 创建一个对象并将其初始化。
b. 显示储户姓名、账号和存款。
c. 存入参数指定的存款。
d. 取出参数指定的存款。
请提供类声明,而不用给出方法实现(编程练习7要求编写实现)。
答:使用string类需要包含头文件string。
// BankAccount类声明
class BankAccount
{
private:
char m_name[20]; // 姓名
char m_accid[20]; // 账号
double m_deposit; // 存款
public:
BankAccount(); // 默认构造函数
BankAccount(const char* name, const char* id, double deposit); // 构造函数
~BankAccount(); // 析构函数
void init_account(const char* name, const char* id, double deposit); // 创建一个对象并将其初始化
void get_info() const; // 显示信息
void save(double cash); // 存钱
void withdraw(double cash); // 取钱
};
6. 类的构造函数在何时调用?类的析构函数呢?
答:类的构造函数在创建类对象的时候自动调用,直接创建类的对象和通过new创建的动态对象两种情况都会自动调用构造函数。析构函数在类对象被回收、销毁时自动调用。对象在超过生命周期后会被自动回收和对动态对象使用delete回收两种情况都会自动调用析构函数。
7. 给出复习题5中银行账户类的构造函数的代码。
答:使用strcpy()函数需要包含头文件cstring。
// BankAccount类的构造函数声明
BankAccout::BankAccount() // 默认构造函数
{
strcpy(m_name, "");
strcpy(m_accid, "");
m_deposit = 0;
}
BankAccount::BankAccount(const char* name, const char* id, double deposit) // 构造函数重载
{
strcpy(m_name, name);
strcpy(m_accid, id);
m_deposit = deposit;
}
8. 什么是默认构造函数?拥有默认构造函数有什么好处?
答:默认构造函数就是没有参数的构造函数,或者所有参数都有默认值(缺省值)的构造函数。默认构造函数可以创建对象而不初始化,这种形式和基本数据类型相似,能够实现一种更加灵活和安全的对象创建方式。如果在定义类时没有定义构造函数,那么系统会自动创建一个默认构造函数,但是如果用户定义了其他任何一个构造函数,即必须手动定义一个默认构造函数。
9. 修改Stock类的定义(stock20.h中的版本),使之包含返回各个数据成员值的成员函数。注意返回公司名的成员函数不应该为修改数组提供便利,也就是说不能简单地返回string引用。
答:返回公司名称时,应使用 const string &,在类中定义函数,相当于在前面加了关键字online,自动设置为内联函数。
// stock20.h —— 超进化版本
#ifndef STOCK20_H_
#define STOCK20_H_
// 头文件
#include <string>
// using声明
using std::string;
// Stock类声明
class Stock
{
private:
string m_company;
int m_shares;
double m_share_val;
double m_tatal_val;
void set_tot() { m_total_val = m_shares * m_share_val; }
public:
Stock(); // 默认构造函数
Stock(const string& co, long n = 0, double pr = 0.0); // 构造函数
~Stock(); // 析构函数
const string& get_company() const { return m_company; } // 返回公司名称
int get_share_num() const { return m_shares; } // 返回股票数量
double get_share_val() const { return m_share_val; } // 返回股票单价
double get_total_val() const { return m_total_val; } // 返回股票总价
void buy(long num, double price); // 买进股票
void sell(long num, double price); // 出售股票
void updata(double price); // 更新股票价格
void show() const; // 显示股票信息
const Stock& topval(const Stock& s) const; // 返回股票总价高的对象
}
10. this和*this表示什么?
答:在C++类中this是一个特殊的指针,它指向类的对象。当创建一个类的对象,用它来调用成员函数(方法)的时候,把this指针隐式传递给该函数,然后就可以在函数中直接访问该对象,相当于显式使用指针访问(如:age = 12; 实际上为 this->age = 12;)。this代表该对象的地址,而*this代表该对象本身,和基本类型与指针的关系一样。
二、编程练习
1. 为复习题5描述的类提供方法定义,并编写一个小程序来演示所有特性。
答:三个文件,一个提供类声明的头文件(.h),一个提供类方法的.cpp文件,一个用于测试的包含main()函数的.cpp文件。(里面的细节我没有写的很到位,比如处理非法输入这些,读者可以自己添加这些代码当作练习,我的本意是把功能实现就行)。
头文件——BankAccount.h
// 头文件
#include "BankAccount.h"
// using声明
using std::cout;
using std::endl;
int main()
{
// 创建2个银行账户类对象,分别使用两个构造函数初始化
BankAccount BA1;
BankAccount BA2("Yu", "2101240010", 1500);
BA1.get_info();
cout << endl;
BA2.get_info();
// 使用各个函数
BA1.init_account("Zhang", "2101240007", 15000);
BA1.get_info();
BA1.save(500); // 存500
BA1.get_info();
BA1.withdraw(10000); //
BA1.get_info();
return 0;
}
main.cpp文件
// 头文件
#include "BankAccount.h"
// using声明
using std::cout;
using std::endl;
int main()
{
// 创建2个银行账户类对象,分别使用两个构造函数初始化
cout << "初始化: \n\n";
BankAccount BA1;
BankAccount BA2("Yu", "2101240010", 1500);
BA1.get_info();
cout << endl;
BA2.get_info();
// 使用各个函数
cout << "\n使用各个函数: \n\n";
BA1.init_account("Zhang", "2101240007", 15000);
BA1.get_info();
cout << endl;
BA1.save(500); // 存500
BA1.get_info();
cout << endl;
BA1.withdraw(10000); // 取10000
BA1.get_info();
return 0;
}
运行结果:
BankAccount.cpp文件
#define _CRT_SECURE_NO_WARNINGS
// 头文件
#include "BankAccount.h"
#include <cstring>
// using声明
using std::cout;
using std::endl;
BankAccount::BankAccount() // 默认构造函数
{
strcpy(m_name, "");
strcpy(m_accid, "");
m_deposit = 0;
}
BankAccount::BankAccount(const char* name, const char* id, double deposit) // 构造函数
{
strcpy(m_name, name);
strcpy(m_accid, id);
m_deposit = deposit;
}
BankAccount::~BankAccount() // 析构函数
{
// 无
}
void BankAccount::init_account(const char* name, const char* id, double deposit) // 创建一个对象并将其初始化
{
strcpy(m_name, name);
strcpy(m_accid, id);
m_deposit = deposit;
}
void BankAccount::get_info() const // 显示信息
{
cout << "Name: " << m_name << endl;
cout << "Account ID: " << m_accid << endl;
cout << "Deposit: " << m_deposit << endl;
}
void BankAccount::save(double cash) // 存钱
{
m_deposit += cash;
}
void BankAccount::withdraw(double cash) // 取钱
{
if (cash > m_deposit)
cout << "Not that much money.\n";
else if (cash < 0)
cout << "Enter negative number. Please re-enter.\n";
else
m_deposit -= cash;
}
2. 下面是一个非常简单的类定义。
Person.h头文件
// 头文件
#include <iostream> // 标准输入输出
#include <string> // string类
// using声明
using std::string;
// Person类声明
class Person
{
private:
static const int LIMIT = 25;
string lname; // 姓
char fname[LIMIT]; // 名
public:
Person() { lname = ""; fname[0] = '\0'; } // 默认构造函数——在类中直接定义,相当于内联函数(关键字inline)
Person(const string& ln, const char* fn = "Heyyou"); // 构造函数重载
void Show() const; // 先显示名,再显示姓
void FromalShow() const; // 先显示姓,再显示名
};
它使用了一个string对象和一个字符数组,用户可以比较它们的用法。请提供未定义的方法的代码,以完成这个类。
再编写一个使用这个类的程序,它使用了3种可能的构造函数(没有参数,一个参数,两个参数)的调用以及两种显示方法。
下面是一个使用这些构造函数和方法的例子。
Person one; // 使用默认构造函数
Person two(“Smythecraft”); // 使用有一个默认参数的#2
Person three(“Dimwiddy”, “Sam”); // 使用没有默认的one.Show()
cout << endl;
one.FormalShow();
// etc. for two and three
答:下面是两个文件,一个用来测试的main.cpp文件,一个提供类方法定义的Person.cpp文件。(注意一下,默认参数在函数声明中写明,函数定义中不写 不然编译器要报错的)
main.cpp文件
// 头文件
#include "Person.h"
int main()
{
Person p1; // 使用默认构造函数
Person p2("原"); // 使用一个参数的构造函数
Person p3("原", "神"); // 使用两个参数的构造函数
// 两个显示函数
p3.Show();
p3.FormalShow();
return 0;
}
Person.cpp文件
#define _CRT_SECURE_NO_WARNINGS
// 头文件
#include "Person.h"
#include <cstring> // strcpy()等c字符串函数函数
// using声明
using std::cout;
using std::endl;
Person::Person(const string& ln, const char* fn) // 构造函数重载
{
lname_ = ln;
strcpy(fname_, fn);
}
void Person::Show() const // 先显示名,再显示姓
{
cout << "name: " << fname_ << " " << lname_ << endl;
}
void Person::FormalShow() const // 先显示姓,再显示名
{
cout << "name: " << lname_ << " " << fname_ << endl;
}
3. 完成第9章的编程练习1,但要用正确的golf类声明替换那里的代码。用带合适参数的构造函数替换setgolf(golf&, const char*, int),以提供初始值。保留setgolf()的交互版本,但要用构造函数来实现它。例如,setgolf()的代码应该获得数据,将数据传递给构造函数来创建一个临时对象,并将其赋值给调用对象,即*this。
答:依旧是三个文件。
golf.h头文件
#pragma once
// 头文件
#include <iostream>
// golf类声明
class golf
{
private:
static const int Len = 40;
char fullname[Len];
int handicap;
public:
golf(const char* name, int hc); // 构造函数——代替了原setgofl()函数
golf(); // 默认构造函数(交互版本)
~golf() { } // 可以使用默认的析构函数
void set_handicap(int hc); // 设置handicap
void show_golf() const; // 显示信息
};
main.cpp测试文件
// 头文件
#include "golf.h"
// using声明
using std::cout;
using std::endl;
int main()
{
// 分别使用两种构造函数进行构造,并显示
golf g1;
cout << endl;
g1.show_golf();
cout << endl;
golf g2("原神", 888);
g2.show_golf();
return 0;
}
golf.cpp方法定义文件
#define _CRT_SECURE_NO_WARNINGS
// 头文件
#include "golf.h"
#include <cstring>
// using声明
using std::cin;
using std::cout;
using std::endl;
golf::golf(const char* name, int hc) // 构造函数——代替了原setgofl()函数
{
strcpy(fullname, name);
handicap = hc;
}
golf::golf() // 默认构造函数(交互版本)
{
// 获得数据
char name[Len];
cout << "Please enter the golf name: ";
cin.getline(name, Len);
cout << "Please enter the golf handicap: ";
int hc;
cin >> hc;
// 使用构造函数创建临时对象
golf g1(name, hc);
// 通过this指针进行赋值
*this = g1;
}
void golf::set_handicap(int hc) // 设置handicap
{
handicap = hc;
}
void golf::show_golf() const // 显示信息
{
cout << "Name: " << fullname << endl;
cout << "Handicap: " << handicap << endl;
}
4. 完成第9章的编程练习4,但将Sales结构体及相关的函数转换为一个类及其方法。用构造函数替换setSales(sales&,double[],int)函数。用构造函数实现setSales(Sales&)方法的交互版本。将类保留在名称空间SALES中。
答:三个文件。
Sales.h头文件
#pragma once
// 头文件
#include <iostream>
namespace SALES
{
// Sales类声明
class Sales
{
private:
static const int QUARTERS = 4;
double sales_[QUARTERS];
double average_; // 平均值
double max_; // 最大值
double min_; // 最小值
public:
Sales(); // 默认构造函数(交互版本)
Sales(const double ar[], int n); // 带参数的构造函数
void get_info() const; // 显示信息
};
}
main.cpp测试文件
// 头文件
#include "Sales.h"
// using编译指令
using namespace SALES;
// using声明
using std::cout;
using std::endl;
int main()
{
// 默认构造函数
Sales S1;
cout << endl;
S1.get_info();
// 带参数的构造函数
double arr[4] = { 123, 321, 444 };
Sales S2(arr, 3);
cout << endl;
S2.get_info();
return 0;
}
Sales.cpp方法定义文件
// 头文件
#include "Sales.h"
// using编译指令
using namespace SALES;
// using声明
using std::cin;
using std::cout;
using std::endl;
Sales::Sales() // 默认构造函数(交互版本)
{
// 输入每个季度的销售量
cout << "Please enter the sales volume for each season.\n";
for (int i = 0; i < QUARTERS; ++i)
{
cout << "No." << i + 1 << ": ";
cin >> sales_[i];
}
cout << endl;
// 寻找最大最小值和计算平均值
double max = sales_[0];
double min = sales_[0];
double sum = sales_[0];
for (int i = 1; i < QUARTERS; ++i)
{
if (sales_[i] > max)
max = sales_[i];
if (sales_[i] < min)
min = sales_[i];
sum += sales_[i];
}
// 赋值给对象
max_ = max;
min_ = min;
average_ = sum / QUARTERS;
}
Sales::Sales(const double ar[], int n) // 带参数的构造函数
{
// 存储每个季节的销售量
for (int i = 0; i < QUARTERS; ++i)
{
if (i < n)
sales_[i] = ar[i];
else
sales_[i] = 0;
}
// 找最大、最小值和计算平均值
double max = sales_[0];
double min = sales_[0];
double sum = sales_[0];
for (int i = 0; i < QUARTERS; ++i)
{
if (sales_[i] > max)
max = sales_[i];
if (sales_[i] < min)
min = sales_[i];
sum += sales_[i];
}
max_ = max;
min_ = min;
average_ = sum / QUARTERS;
}
void Sales::get_info() const // 显示信息
{
cout << "Every season sales: \n";
for (int i = 0; i < QUARTERS; ++i)
{
cout << "No." << i + 1 << ": " << sales_[i] << endl;
}
cout << "max: " << max_ << endl;
cout << "min: " << min_ << endl;
cout << "average: " << average_ << endl;
}
5. 考虑下面的结构体声明。
// customer结构声明
struct customer
{
char fullname[35];
double payment;
};
编写一个程序,它从栈中添加和删除customer结构体(栈用Stack类的声明表示)。每次customer结构体被删除时,其payment的值都将被添加到总数中,并报告总数。
注意,应该可以直接使用Stack类而不做修改;只需要修改typedef声明,使Item的类型为customer,而不是unsigned long即可。
答:大家别照着书照抄,自己动手写,养成习惯。下面依旧是三个文件。
Stack.h头文件
#pragma once
// 头文件
#include <iostream>
// customer结构声明
struct customer
{
char fullname[35];
double payment;
};
// 类型声明
typedef customer ElemType;
// Stack类声明
class Stack
{
private:
static const int SIZE = 10;
ElemType m_tmp[SIZE];
int m_top; // 栈顶
double m_total; // 记录出栈元素的payment总值
public:
Stack(); // 默认构造函数
~Stack() { }; // 析构函数
bool Pop(); // 出栈
bool Push(const ElemType& e); // 入栈
bool isempty() const; // 是否为空
bool isfull() const; // 是否为满
};
main.cpp测试文件
// 头文件
#include "Stack.h"
int main()
{
customer c1 = { "吴", 888 };
customer c2 = { "王", 777 };
customer c3 = { "余", 10888 };
customer c4 = { "张", 18888 };
// 创建空栈
Stack S1;
// 插入元素
S1.Push(c1);
S1.Push(c2);
S1.Push(c3);
S1.Push(c4);
// 删除元素
S1.Pop();
S1.Pop();
S1.Pop();
S1.Pop();
return 0;
}
Stack.cpp方法定义文件
// 头文件
#include "Stack.h"
// using声明
using std::cout;
using std::endl;
// 函数定义
Stack::Stack() // 默认构造函数
{
m_top = 0;
m_total = 0;
}
bool Stack::Pop() // 出栈
{
if (!isempty())
{
--m_top;
// 计算并报告总数
m_total += m_tmp[m_top].payment;
cout << "Total payment: " << m_total;
cout << endl;
return true;
}
else
{
return false;
}
}
bool Stack::Push(const ElemType& e) // 入栈
{
if (!isfull())
{
m_tmp[m_top++] = e;
return true;
}
else
{
return false;
}
}
bool Stack::isempty() const // 是否为空
{
return m_top == 0;
}
bool Stack::isfull() const // 是否为满
{
return m_top == SIZE;
}
6. 下面是一个类声明。
// Move类声明
class Move
{
private:
double x_;
double y_;
public:
Move(double a = 0, double b = 0); // sets x, y to a, b
void showmove() const; // shows current x,y values
Move add(const Move& m); // 创建一个新的Move对象,初始化为两个Move对象的x,y的和,然后返回这个新的对象
void reset(double a = 0, double b = 0); // 重置x、y为a与b
};
请提供成员函数的定义和测试这个类的程序。
答:依旧是三个文件。
Move.h头文件
#pragma once
// 头文件
#include <iostream>
// Move类声明
class Move
{
private:
double x_;
double y_;
public:
Move(double a = 0, double b = 0); // sets x, y to a, b
void showmove() const; // shows current x,y values
Move add(const Move& m); // 创建一个新的Move对象,初始化为两个Move对象的x,y的和,然后返回这个新的对象
void reset(double a = 0, double b = 0); // 重置x、y为a与b
};
main.cpp测试文件
// 头文件
#include "Move.h"
// using声明
using std::cout;
using std::endl;
int main()
{
// 默认构造函数
Move m1;
m1.showmove();
cout << endl;
// 带参数的构造函数
Move m2(8, 8);
m2.showmove();
cout << endl;
m1.reset(1, 1);
m1.showmove();
cout << endl;
Move m3 = m2.add(m1);
m3.showmove();
cout << endl;
return 0;
}
Move.cpp方法定义文件
// 头文件
#include "Move.h"
// using声明
using std::cout;
using std::endl;
Move::Move(double a, double b) // sets x, y to a, b
{
x_ = a;
y_ = b;
}
void Move::showmove() const // shows current x,y values
{
cout << "Coordination: " << "(" << x_ << ", " << y_ << ")\n";
}
Move Move::add(const Move& m) // 创建一个新的Move对象,初始化为两个Move对象的x,y的和,然后返回这个新的对象
{
double x = x_ + m.x_;
double y = y_ + m.y_;
Move tmp(x, y);
return tmp;
}
void Move::reset(double a, double b) // 重置x、y为a与b
{
x_ = a;
y_ = b;
}
7. Betelgeusean plorg在数据上有以下特征。
a. plorg的名称不超过19个字符。
b. plorg的满意指数(CI)是一个整数。
在操作上有以下特征。
a. 新的plorg将有名称,其CI值为50(默认值)。
b. plorg的CI可以修改。
c. plorg可以报告其名称CI。
请编写Plorg类的声明(包括数据成员和成员函数原型)来表示plorg,并编写成员函数的韩束定义,以演示Plorg类的所有特性。
答:依旧是三个文件。
Plorg.h头文件
#pragma once
// 头文件声明
#include <iostream>
// Plorg类声明
class Plorg
{
private:
char name_[20]; // 名称
int CI_; // 满意指数
public:
Plorg(const char* name = "原神", int CI = 100); // 默认构造函数
void set_CI(int CI); // 设置CI的值
void get_info() const; // 显示信息
};
main.cpp测试文件
// 头文件
#include "Plorg.h"
// using声明
using std::cout;
using std::endl;
int main()
{
// 默认构造函数
Plorg P1;
P1.get_info();
cout << endl;
// 带参数的构造函数
Plorg P2("光遇", 188);
P2.get_info();
cout << endl;
// 修改P2的CI
P2.set_CI(99);
P2.get_info();
cout << endl;
return 0;
}
Plorg.cpp文件
// 头文件
#include "Plorg.h"
#include <cstring>
// using声明
using std::cout;
using std::endl;
Plorg::Plorg(const char* name = "原神", int CI = 100) // 默认构造函数
{
strcpy(name_, name);
CI_ = CI;
}
void Plorg::set_CI(int CI) // 设置CI的值
{
CI_ = CI;
}
void Plorg::get_info() const // 显示信息
{
cout << "name: " << name_ << endl;
cout << "CI: " << CI_ << endl;
}
*8. 可以将简单列表描述如下。
a. 可以存储0或多个某种类型的列表。
b. 可以创建空表。
c. 可以在列表中添加数据项。
d. 可以确定列表是否为空。
e. 可确定列表是否已满。
f. 可以访问列表中的每一个数据线项,并对它执行某种操作。
可以看到,这个列表确实简单,例如它不允许插入或删除数据项。
可以设计一个List类来表示这种抽象类型。应提供头文件list.h和实现文件list.cpp,前者包含类定义,后者包含类方法的实现。还应该创建一个简短的程序来使用这个类。
该列表的规范很简单,旨在简化这个编程练习。可以选择使用数组或链表来实现该列表,但公有接口不应该依赖于所做的选择。也就是说,公有接口不应有数组索引、节点指针等。应使用通用概念来创建列表、在列表中添加数据项等操作。对于访问数据项以及执行操作,通常应使用以函数指针作为参数的函数来处理。
void visit(void (pf) (Item &));
其中,pf指向一个以引用作为参数的函数(而不是成员函数),Item是列表中数据项的类型。visit()函数将该函数用于列表的每个数据项。
答:其实别看题目说一堆,其是很简单,我的建议是直接看答案,然后自己写一遍。因为看题是真的看不懂,看了答案觉得上面的要求多次一举。
List.h头文件
#pragma once
// 头文件
#include <iostream>
// 类型声明
typedef int ElemType;
// 输出元素
void print_elem(const ElemType& e);
// List类声明
class List
{
private:
static const int SIZE = 10;
ElemType m_list[SIZE];
int m_tail; // 标记表尾
public:
List(); // 默认构造函数
bool add_elem(const ElemType& e); // 添加元素
bool isempty() const; // 是否为空
bool isfull() const; // 是否为满
void get_elem(void (*pf)(const ElemType& e)); // 访问元素
};
main.cpp测试函数
// 头文件
#include "List.h"
// using声明
using std::cout;
using std::endl;
int main()
{
List list1;
// 添加5个元素
for (int i = 0; i < 5; ++i)
{
list1.add_elem(i + 1);
}
// 显示元素
list1.get_elem(print_elem); // 参数是头文件中类外定义的显示函数
return 0;
}
List.cpp方法定义文件
// 头文件
#include "List.h"
// using声明
using std::cout;
using std::endl;
void print_elem(const ElemType& e)
{
cout << e << endl;
}
List::List() // 默认构造函数
{
m_tail = 0;
}
bool List::add_elem(const ElemType& e)
{
if (!isfull())
{
m_list[m_tail++] = e;
return true;
}
else
{
return false;
}
}
bool List::isempty() const // 是否为空
{
return m_tail == 0;
}
bool List::isfull() const // 是否为满
{
return m_tail == SIZE;
}
void List::get_elem(void (*pf)(const ElemType& e)) // 访问元素
{
for (int i = 0; i < m_tail; ++i)
{
pf(m_list[i]);
}
}