本文档是C++课程设计的笔记。

开始之前

在开始课程之前,对本课程的要求及考核内容做如下说明。

学分:2学分

学时:15学时,学生上机时间约17学时

教室已申请调整,请随后查看教务系统中的变更。

个人作业:对抗项目编程,注意独立完成,会进行查重。记录过程和成果,文档化呈现和展示。

需要通过Git进行版本管理,记录下此过程中的版本变更细节并上传至Gitee,或者写出有关这个项目的博客。

flowchart LR
    A[可编译] --> B[无运行错误]
    B --> C[运行可靠]
    C --> D[自我完善]

设计报告有格式要求。

成绩组成:

内容 占比
平时成绩(代码提交记录,在线测试等) 20%
个人设计(含设计报告) 40%
上机考试 40%

再谈程序设计基础

本节是对上学期的程序设计基础的补充,即面向对象编程的内容。

类和对象

曾经我们提到过struct,也就是结构体这个数据结构。而class作为类,与之类似,但有所不同。让我们先来回顾一下struct.

struct A
{
    int a;
};		// Declare

A a;	// Definition
/*******************************************************/
struct
{
    int a;
}a;		// Declaration and Definition, yet not good
/*******************************************************/
typedef struct{int a;}	A;	// Define this as a new Type

结构体只涉及到了已有变量的打包,结构体中可以嵌套结构体,结构体中的每个数据作为成员变量。

类除了拥有成员变量以外,还会需要有对这些变量进行操作的函数,这些函数允许其他程序员进行调用,而非为这个类重新编写函数,以避免混淆和不必要的麻烦。

以一个三角形为例,如果我们用结构体。

struct Triangle
{
    double	A;
    double	B;
    double	C;
};
// A, B, C refer to the length of edge

bool	is_Triangle(Triangle Tra);	// Declaration
/******************************************************/
bool	is_Triangle(Triangle Tra)
{
    // Codes
}	// Definition

这样我们只存到了这个三角形的边长,我们无法直接通过个这结构体来判断其是否可以构成三角形,其周长、和面积,而需要单独在外面声明与实现与之相关的函数。

在此,请回顾之前笔记中函数这一部分的内容,并复习函数的声明、实现和调用。

而如果我们将成员和与这些成员相关方法集成到这个结构体中成为一个“类”,那么就集成在一起,在使用的时候直接通过类来进行调用就可以实现目的。

让我们通过代码来实践(以三角形类作为例子):

/******************************************************
File:	Triangle.h
******************************************************/
#ifndef __TRIANGLE_H__
#define __TRIANGLE_H__

#include <cstdio>
#include <cmath>

#define SWAP(A, B) if(A > B) Swap(A, B)

inline void Swap(double & d_Num1, double & d_Num2);
// This is to simplify the code as I want to keep the edges sorted

enum	Triangle_Type
{
	Right_Angled,
	Acute_Angled,
	Obtuse_Angled
};


class	Triangle
{
	public:
		// Open to Others
		// The two function below always exist
		Triangle();	// Constructor
		~Triangle();	// Destroyer
		
		bool		set_Triangle_Edge(double Edge_A, double Edge_B, double Edge_C);
		double		get_Triangle_Perimeter();
		double		get_Triangle_Area();
		Triangle_Type	get_Triangle_Type();
	private:
		// ONLY in this class
		double		m_d_Edge_A;
		double		m_d_Edge_B;
		double		m_d_Edge_C;
	protected:
		// Can be inherited
		bool		is_Triangle(double Edge_A, double Edge_B, double Edge_C);
};

#endif
/******************************************************
File:	Triangle.cpp
******************************************************/
#include "Triangle.h"

inline void Swap(double & d_Num1, double & d_Num2)
{
	double	d_Temp	= d_Num1;
	d_Num1		= d_Num2;
	d_Num2		= d_Temp;
	return;
}

Triangle::Triangle()
{
	printf("Triangle has been constructed and Initialized.\n");
	m_d_Edge_A	= 0.0;
	m_d_Edge_B	= 0.0;
	m_d_Edge_C	= 0.0;
}

Triangle::~Triangle()
{
	printf("Triangle with Edges %lf %lf %lf is being destroyed.\n",
		m_d_Edge_A, m_d_Edge_B, m_d_Edge_C);
}

bool	Triangle::set_Triangle_Edge(double Edge_A, double Edge_B, double Edge_C)
{	
	SWAP(Edge_A, Edge_B);
	SWAP(Edge_B, Edge_C);
	SWAP(Edge_A, Edge_B);

    if (!is_Triangle(Edge_A, Edge_B, Edge_C))
	{
		return false;
	}
    
	m_d_Edge_A	= Edge_A;
	m_d_Edge_B	= Edge_B;
	m_d_Edge_C	= Edge_C;
	
	return true;
}

double	Triangle::get_Triangle_Perimeter()
{
	return m_d_Edge_A + m_d_Edge_B + m_d_Edge_C;
}

double	Triangle::get_Triangle_Area()
{
	double	d_Average	= (m_d_Edge_A + m_d_Edge_B + m_d_Edge_C) / 2.0;
	return sqrt(d_Average * (d_Average - m_d_Edge_A) * (d_Average - m_d_Edge_B)
		* (d_Average - m_d_Edge_C));
}

Triangle_Type Triangle::get_Triangle_Type()
{
	double	d_Check1	= m_d_Edge_A * m_d_Edge_A + m_d_Edge_B * m_d_Edge_B;
	double	d_Check2	= m_d_Edge_C * m_d_Edge_C;
	
	if (d_Check1 > d_Check2)
	{
		return Acute_Angled;
	}
	
	if (d_Check1 == d_Check2)
	{
		return Right_Angled;
	}
	
	return Obtuse_Angled;
}

bool	Triangle::is_Triangle(double Edge_A, double Edge_B, double Edge_C)
{
	if (Edge_A + Edge_B <= Edge_C)
	{
		return false;
	}
	
	return true;
}
/******************************************************
File:	main.cpp
******************************************************/
#include "Triangle.h"

int main()
{
	Triangle	Tri;
	
	if (!Tri.set_Triangle_Edge(3, 4, 5))
	{
		printf("Error\n");
	}
	
	printf("The Area of this Triangle: %lf\n", Tri.get_Triangle_Area());
	printf("The Perimeter of this Triangle: %lf\n", Tri.get_Triangle_Perimeter());
	
	Triangle_Type	Tri_Type	= Tri.get_Triangle_Type();
	
	printf("The Type of this Triangle: %d\n", Tri_Type);
	
	return 0;
}

通过工程文件对这个类进行声明、实现和调用,构建后运行的结果如下:

Triangle has been constructed and Initialized.
The Area of this Triangle: 6.000000
The Perimeter of this Triangle: 12.000000
The Type of this Triangle: 0
Triangle with Edges 3.000000 4.000000 5.000000 is being destroyed.
三角形类的运行结果

类的成员

构造函数和析构函数

在此前的代码中,已经有了构造函数和析构函数。

Triangle::Triangle()
{
    // Modified default constructer
}

Triangle::Triangle(double Edge_A, double Edge_B, double Edge_C)
{
    // Modified with parameters
}

Triangle::Triangle(const Triangle & Tri_Cpy)
{
    // Modified with An Object to be copied
}

// Usually, constructer is used to initialize the values of member variables, allocate memory space for dynamic variables, pointers, etc.

Triangle::~Triangle()
{
    // Modified default destructer
} 

// To recycle any unused memory which is allocated in this class

深拷贝和浅拷贝

让我们来看接下来的代码,这段代码定义了一个字符串的类。

// File: istring.h
#ifndef __ISTRING_H_
#define __ISTRING_H_

#include <cstdio>
#include <cstring>

class istring
{
	public:
		istring();
		istring(const char * str_Input);
		istring(const istring & istr_Cpy);
		void		show_String();
		~istring();
	private:
		char *		m_str_Buffer;
		unsigned int	m_n_Length;
	protected:
};

#endif
// File: istring.cpp
#include "istring.h"

istring::istring()
{
	m_str_Buffer	= NULL;
	m_n_Length	= 0;
}

istring::istring(const char * str_Input)
{
	m_n_Length	= strlen(str_Input);
	m_str_Buffer	= new char[m_n_Length + 1];
	
	strcpy(m_str_Buffer, str_Input);
}

istring::istring(const istring & istr_Cpy)
{
	m_n_Length	= istr_Cpy.m_n_Length;
    /*
    m_str_Buffer	= istr_Cpy.m_str_Buffer;		// This is considered as SHALLOW COPY, which is dangerous
    */
	m_str_Buffer	= new char[m_n_Length + 1];
	
	strcpy(m_str_Buffer, istr_Cpy.m_str_Buffer);	// This is considered as DEEP COPY
	return;
}

void istring::show_String()
{
	printf("%s\n", m_str_Buffer);
}

istring::~istring()
{
	if (m_str_Buffer && m_n_Length > 0)
	{
		delete	m_str_Buffer;
		m_str_Buffer	= NULL;
		m_n_Length	= 0;
	}
}
// File: main.cpp
#include "istring.h"

int main()
{
    istring	istr_1("Hello");
	istring	istr_2("Kitty");
	istring	istr_3(istr_1);
    
    // istr_1	= istr_1 + istr_2;	<- This will result in Compile Error, We will solve it later.
	
	istr_1.show_String();
	istr_3.show_String();
	return 0;
}

在这样的代码中,如果在上文我们使用浅拷贝的方式,两个对象的指针指向了同一块内存,那么在接下来的修改和析构的过程中会产生冲突,尤其在析构的时候会对同一段内存区域进行两次释放,这是不合法的,所以通常而言,当我们的类中出现指针类的东西, 我们就更应该使用深拷贝而非浅拷贝。

继承与派生

在自然界中,人和兔子等都是动物,它们都有一些共同特性,如身高体重体温等,但在具体细节上又有差异,如果对每个物种都单独写一个类,那就会造很多重复的轮子,不如给有共性的内容写一个框架,到具体的类的时候再添加上具体的细节。

flowchart LR
	A[生物] --[动物特征]--> B[动物]
	A --[植物特征]--> C[植物]
	A --[微生物特征]--> D[微生物]
	B --[猩猩特征]--> E[猩猩]
	B --[狗特征]--> F[狗]
	B --[人特征]--> G[人]
	G --[老师特征]--> H[老师]
	G --[学生特征]--> I[学生]
	G --[辅导员特征]--> J[辅导员]
	G --[后勤特征]--> K[后勤]
	G --[领导特征]--> L[领导]
	B --[马特征]--> M[马]
	B --[鱼特征]--> N[鱼]
	G --[人鱼特征]--> O[人鱼]
	N --[人鱼特征]--> O
	G --[人马特征]--> P[人马]
	M --[人马特征]--> P

子类都包括父类的特征,子类相比父类都有更加丰富的子类独有的特征,也许与基类不一样,但有可能相比基类也有变化。

在继承和派生的过程中的代码及问题,我们可以通过以下的内容进行更深入的理解,注释部分使用大语言模型辅助完成。

// File: preinclude.h
#pragma once

typedef	char		int_8;
typedef	unsigned char	uint_8;
typedef short		int_16;
typedef unsigned short	uint_16;
typedef int		int_32;
typedef unsigned int	uint_32;


// Include C Header


// Include C++ Standard Template Library Header
#include <iostream>
using namespace std;
// File: CAnimal.h
#ifndef CANIMAL_H
#define CANIMAL_H

#include "preinclude.h"

// Base class for all animals.
class CAnimal
{
	public:
		CAnimal();
		~CAnimal();
		
		// Virtual function for making a sound.
		// Declared as virtual to allow for polymorphism.
		virtual void	Cry();

		// Setters and getters for properties.
		void		Set_Year(int Yr);
		int		n_Get_Year();

	private:
		char		m_str_Name[20];
		int		m_n_Age;
		int		m_n_Weight;

	protected:
};

#endif
// File: CAnimal.cpp
#include "CAnimal.h"

// Constructor: Called when a CAnimal object is created.
CAnimal::CAnimal()
{
	cout << "CAnimal Created" << endl;
}

// Destructor: Called when a CAnimal object is destroyed.
CAnimal::~CAnimal()
{
	cout << "CAnimal Deleted" << endl;
}

// Default implementation for the Cry method.
void	CAnimal::Cry()
{
	cout << "Animal is Crying" << endl;
}

// Sets the age of the animal.
void	CAnimal::Set_Year(int Yr)
{
	this -> m_n_Age	= Yr;
}

// Gets the age of the animal.
int	CAnimal::n_Get_Year()
{
	return this -> m_n_Age;
}
// File: CDog.h
#ifndef CDOG_H
#define CDOG_H

#include "preinclude.h"
#include "CAnimal.h"

// Enumeration for the fur color of a dog.
enum	FurColor
{
	Black, White, Yellow, Brown
};

// CDog class, inherits from CAnimal using virtual inheritance
// to prevent the diamond problem in multiple inheritance scenarios.
class CDog : virtual public CAnimal
{
	public:
		CDog();
		~CDog();
		
		// Overrides the base class Cry() method.
		void		Cry();

	protected:
	private:
		FurColor	color;
		// CAnimal	CA_Test;
		// This member would be constructed before the base class part.
};

#endif
// File: CDog.cpp
#include "CDog.h"

// Constructor: Called when a CDog object is created.
CDog::CDog()
{
	cout << "CDog Created" << endl; 
}

// Destructor: Called when a CDog object is destroyed.
CDog::~CDog()
{
	cout << "CDog Deleted" << endl;
}

// CDog's specific implementation of the Cry method.
void	CDog::Cry()
{
	cout << "Bark!" << endl;
}
// File: CCat.h
#ifndef CCAT_H
#define CCAT_H

#include "CAnimal.h"

// CCat class, inherits from CAnimal using virtual inheritance
// to prevent the diamond problem in multiple inheritance scenarios.
class CCat : virtual public CAnimal
{
	public:
		CCat();
		~CCat();

		// Overrides the base class Cry() method.
		void	Cry();

	protected:
	private:
		bool	Gender;
		
};

#endif
// File: CCat.cpp
#include "CCat.h"

// Constructor: Called when a CCat object is created.
CCat::CCat()
{
	cout << "CCat Created" << endl;
}

// Destructor: Called when a CCat object is destroyed.
CCat::~CCat()
{
	cout << "CCat Deleted" << endl;
}

// CCat's specific implementation of the Cry method.
void	CCat::Cry()
{
	cout << "Meow!" << endl;
}
// File: CCatDog.h
#ifndef CCATDOG_H
#define CCATDOG_H
#include "preinclude.h"
#include "CDog.h"
#include "CCat.h"

// CCatDog class, demonstrates multiple inheritance from CCat and CDog.
class CCatDog : public CDog, public CCat
{
	public:
		CCatDog();
		~CCatDog();

		/*
		 * [Method 2] Override the function in the derived class to resolve ambiguity.
		 * This is the recommended approach. By providing a custom Cry() implementation
		 * in CCatDog, we can define its unique behavior and control which parent's
		 * version to call, if any.
		 * 
		 * To enable this method, simply uncomment the block below.
		 */
		
		void Cry();
		
		
	protected:
};

#endif
// File: CCatDog.cpp
#include "CCatDog.h"

// Constructor: Called when a CCatDog object is created.
CCatDog::CCatDog()
{
	cout << "CCatDog Created" << endl;
}

// Destructor: Called when a CCatDog object is destroyed.
CCatDog::~CCatDog()
{
	cout << "CCatDog Deleted" << endl;
}

CCatDog::Cry()
{
	cout << "A CatDog is crying in its own way!" << endl;
	// You can optionally call the parent implementations here
	// CDog::Cry();
	// CCat::Cry();
}
// File: main.cpp
#include "CCatDog.h"
#include <iostream>
using namespace std;

int main()
{	
	// --- Testing CDog Class ---
	cout << "--- Testing CDog Class ---" << endl;
	CDog *	cd	= new CDog;
	cd -> Cry(); // Calls the overridden Cry() in CDog
	cd -> CAnimal::Cry(); // Forcefully calls the base class CAnimal's Cry()
	delete	cd;
	cd		= NULL;
	
	cout << endl;
	
	// --- Testing CCatDog Multiple Inheritance ---
	cout << "--- Testing CCatDog Multiple Inheritance ---" << endl;
	CCatDog * ccd	= new CCatDog;

	// [Method 1] Use the scope resolution operator "::" to resolve ambiguity.
	// When CCatDog does not override Cry(), a direct call is ambiguous.
	// We can explicitly specify which parent's version to call.
	cout << "Calling Cry() from specific parents:" << endl;
	ccd -> CDog::Cry(); // Explicitly call Cry() inherited from CDog
	ccd -> CCat::Cry(); // Explicitly call Cry() inherited from CCat
	
	// Since both CCat and CDog virtually inherit from CAnimal,
	// there is only one instance of CAnimal in CCatDog.
	// Thus, calling CAnimal members directly is not ambiguous.
	ccd -> CAnimal::Cry();
	delete ccd;
	ccd		= NULL;
	
	cout << endl;
	
	// --- Testing Polymorphism ---
	cout << "--- Testing Polymorphism ---" << endl;
	CDog		dg;
	CAnimal		ca;
	CDog *		pdg;
	CAnimal *	pam;
	
	// A base class pointer can point to a derived class object.
	pam		= &dg;		// OK: CAnimal pointer to a CDog object
	
	// A derived class pointer cannot point to a base class object (without explicit casting).
	// pdg		= &ca; 		// ILLEGAL: Cannot assign CAnimal* to CDog*
	
	// pam points to a CDog object, and Cry() is a virtual function (should be declared virtual in CAnimal).
	// Therefore, this will call CDog's Cry() method, demonstrating polymorphism.
	pam -> Cry();
	
	return 0;
}

这是这段代码执行的结果:

--- Testing CDog Class ---
CAnimal Created
CDog Created
Bark!
Animal is Crying
CDog Deleted
CAnimal Deleted

--- Testing CCatDog Multiple Inheritance ---
CAnimal Created
CDog Created
CCat Created
CCatDog Created
Calling Cry() from specific parents:
Bark!
Meow!
Animal is Crying
CCatDog Deleted
CCat Deleted
CDog Deleted
CAnimal Deleted

--- Testing Polymorphism ---
CAnimal Created
CDog Created
CAnimal Created
Bark!
CAnimal Deleted
CDog Deleted
CAnimal Deleted