编写一个用户友好的程序,要求用户输入2个整数,然后显示这2个数字的和、差、积、商、整数除法的余数。
解答:
int main() {
// 声明变量
int num1, num2;
// 欢迎信息和指引
std::cout << "欢迎使用整数计算器!" << std::endl;
// 获取用户输入
std::cout << "请输入第一个整数: ";
std::cin >> num1;
std::cout << "请输入第二个整数: ";
std::cin >> num2;
// 计算并显示结果
std::cout << "\n计算结果:" << std::endl;
std::cout << "和: " << num1 << " + " << num2 << " = " << num1 + num2 << std::endl;
std::cout << "差: " << num1 << " - " << num2 << " = " << num1 - num2 << std::endl;
std::cout << "积: " << num1 << " × " << num2 << " = " << num1 * num2 << std::endl;
// 防止除数为零的情况
if (num2 != 0) {
std::cout << "商: " << num1 << " ÷ " << num2 << " = " << static_cast<double>(num1) / num2 << std::endl;
std::cout << "整除的余数: " << num1 << " % " << num2 << " = " << num1 % num2 << std::endl;
} else {
std::cout << "商: 无法计算(除数不能为零)" << std::endl;
std::cout << "整除的余数: 无法计算(除数不能为零)" << std::endl;
}
return 0;
}关键点:
- 使用
static_cast<double>进行类型转换以获得浮点除法结果 - 检查除数为零的情况以避免程序崩溃
- 提供用户友好的界面
修改程序,询问用户想要执行哪种运算,计算在函数中进行。
解答:
// 计算函数,接收两个数字和运算符作为参数
double calculate(int num1, int num2, char op) {
switch(op) {
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
if (num2 != 0) {
return static_cast<double>(num1) / num2;
} else {
std::cout << "错误:除数不能为零!" << std::endl;
return 0;
}
case '%':
if (num2 != 0) {
return num1 % num2;
} else {
std::cout << "错误:除数不能为零!" << std::endl;
return 0;
}
default:
std::cout << "错误:无效的运算符!" << std::endl;
return 0;
}
}
int main() {
int num1, num2;
char op;
std::cout << "输入第一个数字:";
std::cin >> num1;
std::cout << "输入第二个数字:";
std::cin >> num2;
std::cout << "要执行的操作?( +, -, *, /, % ):";
std::cin >> op;
double result = calculate(num1, num2, op);
if ((op == '/' || op == '%') && num2 == 0) {
// 错误已在函数中处理
} else if (op == '+' || op == '-' || op == '*' || op == '/' || op == '%') {
std::cout << num1 << op << num2 << " = ";
if (op == '/' && num1 % num2 != 0) {
std::cout << result << std::endl;
} else {
std::cout << static_cast<int>(result) << std::endl;
}
}
return 0;
}关键点:
- 使用
switch语句处理不同的运算符 - 将计算逻辑封装在独立的函数中
- 错误处理(除零检查、无效运算符)
编写程序允许输入实数数组的内容,然后显示输入的值。
解答:
int main() {
double tab[100]; // 静态分配的数组
int size;
std::cout << "请输入数组大小(最大100):";
std::cin >> size;
if (size <= 0 || size > 100) {
std::cout << "错误:数组大小必须在1到100之间" << std::endl;
return 1;
}
std::cout << "请输入 " << size << " 个实数:" << std::endl;
for (int i = 0; i < size; i++) {
std::cout << "元素 " << i + 1 << ": ";
std::cin >> tab[i];
}
std::cout << "\n数组内容:" << std::endl;
for (int i = 0; i < size; i++) {
std::cout << "tab[" << i << "] = " << tab[i] << std::endl;
}
return 0;
}创建过程 AfficherTableau,允许显示数组的所有元素。
解答:
void AfficherTableau(const double tableau[], int taille) {
std::cout << "\n数组内容:" << std::endl;
for (int i = 0; i < taille; i++) {
std::cout << "tableau[" << i << "] = " << tableau[i] << std::endl;
}
}关键点:
- 使用
const关键字确保函数不会修改数组 - 数组作为参数传递时会退化为指针
创建函数 SaisirNbElements,询问用户要输入的元素数量。
解答:
int SaisirNbElements() {
int taille;
std::cout << "请输入数组大小(最大100):";
std::cin >> taille;
while (taille <= 0 || taille > 100) {
std::cout << "错误:数组大小必须在1到100之间" << std::endl;
std::cout << "请重新输入数组大小:";
std::cin >> taille;
}
return taille;
}添加过程 SaisirTableau。
解答:
void SaisirTableau(double tableau[], int taille) {
std::cout << "请输入 " << taille << " 个实数:" << std::endl;
for (int i = 0; i < taille; i++) {
std::cout << "元素 " << i + 1 << ": ";
std::cin >> tableau[i];
}
}修改程序使用动态分配:要输入的元素数量等于数组的大小。
解答:
int main() {
int nb;
double *tab = NULL;
nb = SaisirNbElements();
// 动态分配内存
tab = new double[nb];
if (tab == NULL) {
std::cout << "错误:内存分配失败!" << std::endl;
return 1;
}
SaisirTableau(tab, nb);
AfficherTableau(tab, nb);
// 释放内存
delete[] tab;
tab = NULL;
return 0;
}关键点:
- 使用
new进行动态内存分配 - 使用
delete[]释放数组内存 - 将指针设为
NULL防止悬空指针
编写函数 AdditionneTableaux,允许对两个相同大小的数组进行逐项求和。
解答:
// 两个数组的求和
void AdditionneTableaux(const double tab1[], const double tab2[], double result[], int taille) {
for (int i = 0; i < taille; i++) {
result[i] = tab1[i] + tab2[i];
}
}
// 三个数组的求和(函数重载)
void AdditionneTableaux(const double tab1[], const double tab2[], const double tab3[], double result[], int taille) {
for (int i = 0; i < taille; i++) {
result[i] = tab1[i] + tab2[i] + tab3[i];
}
}关键点:
- 函数重载允许同名函数有不同的参数列表
- 结果存储在独立的数组中
TableauDouble类的UML设计:
+-----------------------------------+
| TableauDouble |
+-----------------------------------+
| - Pt : double* |
| - Taille : unsigned int |
| - Nom : string |
+-----------------------------------+
| + TableauDouble(taille, nom, val) |
| + TableauDouble(const TableauDouble&)|
| + ~TableauDouble() |
| + GetTaille() : unsigned int |
| + SetTaille(taille : unsigned int)|
| + GetNom() : string |
| + SetNom(nom : const string&) |
| + GetElement(i : unsigned int) : double |
| + SetElement(i : unsigned int, v : double) |
| + Plus(td : const TableauDouble&) : TableauDouble |
| + ProduitScalaire(td : const TableauDouble&) : double |
| + Afficher() : void |
| + Copie(tableau : const TableauDouble&) |
| + operator=(t : const TableauDouble&) : TableauDouble& |
+-----------------------------------+
回答: TableauDouble类与其字段类之间的关系是组合关系(composition),多重性为1:1。这是因为:
- TableauDouble对象完全拥有其double数组的生命周期
- 数组不能独立于TableauDouble对象存在
- 当TableauDouble对象被销毁时,数组也会被销毁
回答: 3个字段都应该使用private可见性:
Pt:指向动态分配内存的指针,外部不应直接访问Taille:数组大小,应通过getter/setter方法控制访问Nom:数组名称,应通过getter/setter方法控制访问
回答: SetTaille方法需要实现以下步骤:
- 释放当前动态分配的内存 (
delete[] Pt) - 更新Taille字段为新值
- 分配新的内存空间 (
Pt = new double[Taille]) - 初始化新数组的元素(通常为0)
回答: 不应该使用参数值传递来传递TableauDouble类型的对象,因为:
- TableauDouble包含动态分配的内存
- 值传递会触发拷贝构造函数,创建整个数组的副本
- 这会导致不必要的内存分配和数据复制,影响性能
- 应该使用引用传递(
const TableauDouble&)
回答: 需要编写析构函数是因为:
- TableauDouble类使用动态内存分配(
new) - 如果不手动释放内存(
delete[]),会导致内存泄漏 - 析构函数确保对象销毁时自动释放所占用的内存
- 在UML类图中显示析构函数表明该类管理资源,需要特殊的清理操作
函数注释:
// QUESTION 2-a
// Code à commenter suivant l'ordre
//1- Que va faire, en gros, cette méthode?
// Cette méthode copie le contenu d'un tableau dans l'objet courant
//1- Pourquoi le const? Est il obligatoire?
// Le const garantit que le tableau source ne sera pas modifié. C'est une bonne pratique.
//1- Pourquoi la référence?
// La référence évite la copie inutile de l'objet lors du passage de paramètre
void TableauDouble::Copie(const TableauDouble &tableau)
{
if( this != &tableau ) // 5- A quoi sert cette ligne? (dans un main, essayer "a.Copie(a);"
// Cette ligne évite l'auto-assignation (a.Copie(a))
{
Nom = tableau.Nom; //2- Copie du nom
Taille = tableau.Taille; //2- Copie de la taille
delete[] Pt; //3- Libération de l'ancienne mémoire
Pt = new double[Taille]; //3- Allocation de nouvelle mémoire
for(unsigned int i=0; i<Taille; i++) //4- Boucle de copie
Pt[i] = tableau.Pt[i]; //4- Copie élément par élément
}
}实现:
unsigned int TableauDouble::GetTaille() const
{
return Taille;
}
void TableauDouble::SetNom(const string &nom)
{
Nom = nom;
}实现:
void TableauDouble::SetTaille( unsigned int taille )
{
// Libérer l'ancien tableau
delete[] Pt;
// Mettre à jour la taille
Taille = taille;
// Allouer un nouveau tableau avec la nouvelle taille
Pt = new double[Taille];
// Initialiser les éléments à 0
for(unsigned int i=0; i<Taille; i++)
Pt[i] = 0.0;
}实现:
TableauDouble::TableauDouble( const TableauDouble &t)
{
Nom = t.Nom;
Taille = t.Taille;
Pt = new double[Taille];
for(unsigned int i=0; i<Taille; i++)
Pt[i] = t.Pt[i];
}为什么必需: 拷贝构造函数对TableauDouble类是必需的,因为:
- 默认拷贝构造函数只进行浅拷贝
- 这会导致多个对象共享同一块内存
- 当对象销毁时会出现双重释放的错误
- 自定义拷贝构造函数确保深拷贝,每个对象有独立的内存
实现:
TableauDouble TableauDouble::Plus(const TableauDouble &td) const
{
// 确定两个数组中较小的大小
unsigned int min_taille = (this->Taille < td.Taille) ? this->Taille : td.Taille;
// 创建新的TableauDouble对象存储结果
TableauDouble result(min_taille, this->Nom + "+" + td.Nom);
// 执行逐项相加
for (unsigned int i = 0; i < min_taille; i++)
{
result.SetElement(i, this->GetElement(i) + td.GetElement(i));
}
return result;
}实现:
double TableauDouble::ProduitScalaire(const TableauDouble &td) const
{
// 如果大小不同,使用较小的大小
unsigned int min_taille = (this->Taille < td.Taille) ? this->Taille : td.Taille;
double resultat = 0.0;
for (unsigned int i = 0; i < min_taille; i++)
{
resultat += this->GetElement(i) * td.GetElement(i);
}
return resultat;
}代码执行顺序分析:
TableauDouble w(1); // 1. 调用构造函数创建w
w = a.Plus(b); // 2. 调用Plus方法创建临时对象
w.Affiche(); // 3. 调用Affiche方法
// 4. 临时对象析构
// 5. w对象析构问题: 这个代码不工作是因为:
- Plus方法返回一个临时对象
- 默认的赋值操作符只进行浅拷贝
- 临时对象销毁时释放了内存
- w对象指向已释放的内存,导致错误
实现:
TableauDouble & TableauDouble::operator=(const TableauDouble &t)
{
if( this != &t )
{
// Instructions pour désallouer l'objet en cours
delete[] Pt;
// Puis réallouer à la taille de 't'
Nom = t.Nom;
Taille = t.Taille;
Pt = new double[Taille];
// Puis copier, valeurs par valeurs les éléments de t
for(unsigned int i=0; i<Taille; i++)
Pt[i] = t.Pt[i];
}
return (*this);
}关键点:
- 检查自赋值 (
this != &t) - 释放当前对象的内存
- 分配新内存并复制数据
- 返回当前对象的引用以支持链式赋值
- 基本I/O操作:
cin、cout的使用 - 函数设计:参数传递、返回值、错误处理
- 数组操作:静态和动态内存分配
- 内存管理:
new、delete[]的正确使用
- 面向对象设计:封装、类的设计原则
- 资源管理:RAII(Resource Acquisition Is Initialization)原则
- 三法则:如果需要自定义析构函数,通常也需要自定义拷贝构造函数和赋值操作符
- 深拷贝vs浅拷贝:理解动态内存分配下的拷贝语义
- 操作符重载:赋值操作符的正确实现模式
- 总是检查动态内存分配是否成功
- 遵循RAII原则管理资源
- 使用const限定符保护不应修改的数据
- 实现完整的拷贝语义(拷贝构造函数和赋值操作符)
- 避免内存泄漏和悬空指针