设计一个学生信息类
任务描述
本关任务:声明并定义一个学生信息类。
相关知识
为了完成本关任务,你需要掌握类的声明和定义。
类
在现实世界中,经常有属于同一类的对象。例如,你的自行车只是世界上很多自行车中的一辆。在面向对象软件中,也有很多共享相同特征的不同的对象,可以利用这些对象的相同特征为它们建立一个集合,而这个集合就称为类。
C++ 中类是把各种不同类型的数据(称为数据成员)和对数据的操作(成员函数)组织在一起而形成的用户自定义的数据类型。它提供了可重用性的好处。
类定义包括声明和实现两大部分。声明部分提供了对该类所有数据成员和成员函数的描述,而实现部分提供了所有成员函数的实现代码。
类的声明
类的声明,指的是描述一个类所拥有的结构。类的声明主要包含两个部分:成员变量和成员函数。
声明一个类的格式如下:
class 类名
{
public: // 类的公有成员
int A; // 公有的成员变量
void PublicFunc(); // 公有的成员函数
private: // 类的私有成员
long B; // 私有的成员变量
void PrivateFunc(); // 私有的成员函数
public: // public,private 块可以多次交替出现
double C;
}; // 注意不要忘了最后的分号
class 是 C++ 中新增的关键字,专门用来声明类的,后面紧跟着类名,类名的首字母一般大写,以和其他的标识符区分开。{}内部是类所包含的成员变量和成员函数,它们统称为类的成员。public 也是 C++ 的新增关键字,它只能用在类的定义中,表示访问权限,下面这里介绍两种访问性:
- public:公开访问性,代表这个成员能在类的定义以外的地方被使用
- private:私有访问性,代表这个成员只能在类的定义内被使用
例如:
class Test
{
public:
int a;
private:
int b;
};
int main()
{
Test t;
t.a = 10; // 访问公有成员变量,正确
t.b = 10; // 访问私有成员变量,错误
}
类的定义
如果说声明是书的目录,那么定义就是目录所指的具体内容。
类的定义,指的是根据声明具体实现类的功能,与一般的函数定义很相似。
比如:
class Test
{
public:
int a;
void PubFun();
private:
int b;
void PriFun();
}; // 声明一个带有两个成员函数的类
void Test::PubFun() // 定义公有的那个成员函数
{
a = 10;
b = 10; // b 是私有成员变量,只能在成员函数的定义中访问
}
void Test::PriFun() //定义私有的那个成员函数
{
a = 20;
b = 20; // b 是私有成员变量,只能在成员函数的定义中访问
}
定义普通函数与类成员函数只有一点不同:在函数名前面加了类名::的前缀。其中的::
称之为作用域运算符,它指明了成员函数所属的类。
不管是 public 成员函数,还是 private 成员函数,定义它们的方式都是相同的。
编程要求
在右侧编辑器中的Begin-End之间补充代码,声明并定义一个学生信息类 StInfo,该类有四个成员变量和两个函数,其访问性都为公有,具体要求如下:
学号:int SID
姓名:char *Name
班级:char *Class
手机号:char *Phone
给成员变量赋值的函数: void SetInfo(int sid,char name,char cla,char* phone)
打印学生信息的函数:void PrintInfo(),格式请参考测试说明。
.h
#include <iostream>
#include<cstring>
using namespace std;
class StInfo
{
/********* Begin *********/
//在此处声明StInfo类
public:
int SID;
char Name[20];
char Class[20];
char Phone[20];
void SetInfo(int sid,char *name,char* cla,char* phone);
void PrintInfo();
/********* End *********/
};
/********* Begin *********/
//在此处定义StInfo类
void StInfo::SetInfo(int a,char b[],char c[],char d[])
{
SID=a;
strcpy(Name,b);
strcpy(Class,c);
strcpy(Phone,d);
}
void StInfo::PrintInfo()
{
cout<<"学号:"<<SID<<endl;
cout<<"姓名:"<<Name<<endl;
cout<<"班级:"<<Class<<endl;
cout<<"手机号:"<<Phone<<endl;
}
/********* End *********/
.cpp
//设置编码格式
#ifdef UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif
#include "stinfo.h"
#include <iostream>
#include <string>
using namespace std;
int main()
{
int SID;
string str1,str2,str3;
cin >> SID >> str1 >> str2 >> str3;
char * Name=(char*)str1.c_str(),* Class=(char*)str2.c_str(),* Phone=(char*)str3.c_str();
StInfo info;
info.SetInfo(SID,Name,Class,Phone);
info.PrintInfo();
return 0;
}
设计一个长方形类
任务描述
本关任务:设计一个长方形类,通过访问它的成员变量来计算面积。
相关知识
为了完成本关任务,你需要掌握对象的创建和通过对象访问类的成员。
对象
类只是一种形式化的定义,要使用类提供的功能,必须使用类的实例,即对象,一个类可以定义多个对象,而对象要占据一定的内存空间。类和对象的关系就像整形和变量的关系。
每个对象都包含类中定义的各个数据成员的存储空间,共享类中定义的成员函数。对象的创建方法与声明一个普通变量相同,也采用类型名 变量名的格式。
例如:
class Test
{
//此处省略 Test 类成员
};
int main()
{
int a; // 声明一个普通变量
Test t1; // 创建一个类的对象
}
对象访问类的成员
通过对象也可以访问一个类的成员,通过.成员运算符,格式是对象名.成员名。
如果是数据成员,就可以对它进行赋值,如果是函数成员,就可以调用它。我们可以将其看做为一般变量,只是在变量名前面多了代表它所属对象的前缀。
例如:
#include <iostream>
class Test
{
public: // 两个公有成员
int a;
void Hello();
};
void Test::Hello() // 定义 Test 类的公有函数
{
std::cout << "Hello " << a << std::endl;
}
int main()
{
Test t1;
t1.a = 10; // 给 t1 对象的数据成员 a 赋值
t1.Hello(); // 调用 t1 对象的成员函数 hello
}
输出结果为:Hello 10
注意:访问成员时要注意成员的访问性,详情见上一关。
编程要求
在右侧编辑器中的Begin-End之间补充代码,设计 Rectangle 类和实现两个普通函数,类中有两个成员变量和两个函数,其中成员变量的访问性为私有,函数的访问性为公有,具体要求如下:
- 高度:int height
- 宽度:int width
- 设置长方形的高和宽函数:void Set(int h, int w)
- 获取长方形的面积函数:int GetArea()
- Rectangle GetRect(int h,int w)普通函数,h 、w 分别代表长方形的高宽,函数用来创建一个 Rectangle
对象并返回。 - int GetRectArea(Rectangle rect)普通函数,函数用来获取 rect 对象的面积。
.h
/********* Begin *********/
class Rectangle
{
//在此处实现Rectangle类
private:
int height;
int width;
public:
void Set(int h,int w);
int GetArea();
};
void Rectangle::Set(int h,int w)
{
height=h;
width=w;
}
int Rectangle::GetArea()
{
return height*width;
}
/********* End *********/
Rectangle GetRect(int h,int w)
{
/********* Begin *********/
//返回一个 h*w 的 Rectangle 对象
Rectangle rect;
rect.Set(h,w);
return rect;
/********* End *********/
}
int GetRectArea(Rectangle rect)
{
/********* Begin *********/
//返回 rect 对象的面积
return rect.GetArea();
/********* End *********/
}
.cpp
#include <iostream>
#include "usr.h"
using namespace std;
int main()
{
int h,w;
cin >> h >> w ;
Rectangle rec = GetRect(h,w);
cout<<"长方形的面积为:"<<GetRectArea(rec)<<endl;
}
构造函数 —— 学生信息类
任务描述
本关任务:设计一个带有构造函数和析构函数的学生类。
相关知识
构造函数、析构函数与赋值函数是每个类最基本的函数。他们太普通以致让人容易麻痹大意,其实这些貌似简单的函数在使用时要特别注意以免造成不必要资源浪费和产生意想不到的错误。
每个类只有一个析构函数和一个赋值函数,但是可以有多个构造函数(包含一个拷贝构造函数,其他的成为普通构造函数)。
下面我们就一起来学习构造函数和析构函数的基本使用。
构造函数
所谓构造函数,就是在对象构造的时候调用的函数。构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。
构造函数在定义类对象时自动调用,不需用户调用,也不能被用户调用。在对象使用前调用。如果类中没有定义构造函数,系统则会自动给出一个无参构造函数。
构造函数没有返回值,函数名必须与类名一致,一个类可以有多个构造函数,但是参数必须有差别(也就是所谓的重载)。
例如:
class Test
{
public:
Test(); // 无参数的构造函数
Test(int a); // 有一个 int 参数的构造函数
private:
Test(int a,int b); // 私有的两个参数的构造函数
};
Test::Test()
{ /* 此处省略一些初始化的工作 */}
Test::Test(int a)
{ /* …… */}
Test::Test(int a,int b)
{ /* …… */}
构造函数也会受访问性影响,在不同的作用范围,能调用的构造函数也会不同。
初始化成员
构造函数的一个重要任务就是给成员初始化,初始化成员有两种办法,一种是手动给成员赋值,另一种是使用初始化列表。这里介绍第二种,格式为:
类名::构造函数名(参数表): (成员初始化表){ 构造函数体 }
构造函数中的初始化列表只需要在参数列表的后面加一个冒号(:),然后将要初始化的成员按照成员名(参数)的格式排列在后面,个成员之间用逗号隔开。
例如:
class Test
{
public:
int A;
int B;
Test(int a);
};
Test::Test(int a)
:A(a),B(10) //给成员变量 A、B 初始化,不一定要和参数列表写在一行
{ /* …… */ }
其中成员的初始化顺序不是按照初始化列表中的顺序来的,而是按照成员声明的顺序来的,例如:
/* Test类的声明同上 */
Test::Test(int a)
:B(10),A(a) // 虽然 B 在前面,但还是 A 先初始化
{/* …… */}
Test::Test(int a)
:B(a),A(B) //此处 A 的初始化依赖了 B,然而是 A 先初始化,这就导致 A 得到了 B 中还没初始化的错误内容
{/* …… */}
析构函数
析构函数是一种特殊的成员函数,它会在每次删除所创建的对象时执行。它执行与构造函数相反的操作,通常用于撤消对象时的一些清理任务,有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。格式如下:
类名::~析构函数名(){}
例如:
class Test
{
public:
~Test(); // 析构函数
};
Test::~Test()
{/* 一些收尾的工作 */}
编程要求
在右侧编辑器中的Begin-End之间补充代码,编写一个学生类 Student,类中含有两个成员变量、两个构造函数和一个析构函数(访问性都为公有的),平台会调用你编写的函数来创建学生对象,具体要求如下:
学号:int SID
姓名:string Name
无参的构造函数:Student(),在函数中将学号初始化为0,姓名初始化为王小明。
带两个参数的构造函数:Student(int sid,string name);,在函数中分别用这两个参数设置内部的两个成员。
析构函数:~Student(),在函数中输出学号 姓名 退出程序的消息。
.h
#include<iostream>
#include<string>
using namespace std;
class Student
{
/********* Begin *********/
//在此处声明所需的成员
public:
int SID;
string Name;
Student();
Student(int sid,string name);
~Student();
/********* End *********/
};
/********* Begin *********/
//在此处定义成员函数
Student::Student()
{
SID = 0;
Name = "王小明";
}
Student::Student(int sid,string name)
{
SID = sid;
Name = name;
}
Student::~Student()
{
cout << Student::SID<< ' '<<Student::Name<<" 退出程序"<<endl;
}
/********* End *********/
.cpp
#include "usr.h"
#include<iostream>
#include<string>
using namespace std;
int main()
{
int i,j,k;
string name1,name2,name3;
cin >> i >> name1 >> j >> name2 >> k >> name3;
Student st1(i,name1);
Student st2(j,name2);
Student st3(k,name3);
{
Student st;
}
}
对象数组 —— 学生信息表
任务描述
本关任务:编写一个能管理多条学生信息的程序。
相关知识
为了完成本关任务,你需要掌握构造函数与析构函数的调用和对象数组的使用。
构造函数与析构函数的调用
构造函数不能直接调用,只能通过声明一个对象或者使用new 运算符动态创建对象时由系统自动调用。
例如:
class Test
{
public:
int A;
Test();
Test(int a);
};
/* 此处省略定义构造函数部分 */
int main()
{
Test t; // 调用无参构造函数
Test t2(10); // 调用带参构造函数
Test t3 = Test(10); // 同上
Test *t = new Test; // 动态创建对象,调用无参构造函数
Test *t2 = new Test(10); // 动态创建对象,调用带参构造函数
}
而析构函数则不同,它能够通过对象主动调用,并在以下两种情况下它会自动调用:
若一个对象被定义在一个函数体内,当这个函数结束时(声明的变量的生命周期结束)会自动调用。
若一个对象是使用 new 运算符动态创建,在使用 delete 释放时会自动调用。
例如:
/* Test类的声明接上 */
Test::~Test() // 修改一下析构函数,让它打印一条消息
{
cout << “Test的析构函数被调用” << endl;
}
int main()
{
cout << “p1” << endl;
{
Test t1(1); // t1 的生命周期就只在这个大括号内
} //因此在这个位置 t1 的析构函数就会被调用
cout << “p2” << endl;
Test *t2 = new Test(10);
delete t2; // t2 所指对象的析构函数在此被调用
cout<<“p3”<<endl;
{
Test *t3 = new Test;
} // t3 所指对象的析构函数并不会被调用,因为没有使用 delete 运算符
}
输出结果为:
p1
Test的析构函数被调用
p2
Test的析构函数被调用
p3
上述代码中 t1 对象的析构函数调用的位置有点微妙,它是在代码离开大括号}的那瞬间的位置被调用的,因为一个变量只在直接包含它的那层大括号的范围内存活。
对象数组
数组对象就是大批量实例化对象的一种方法,以往我们都是这样:Student stu实例化对象,如果有好几百个对象应该怎么办?
这时候就用到了对象数组,顾名思义,就是把所有要实例化的对象都放到一个组里面,然后直接实例化这个组,就像这样:Student stu[100],便可一次性实例化100个对象。
对象数组与一般的数组基本一致,只是多了两个过程:
在数组创建的时候对数组的每一个元素都调用了构造函数;
在数组生命结束的时候对数组的每一个元素都调用了析构函数。
如果使用 new 运算符来动态创建对象数组,也是同样的过程。
注意:在创建数组时如果不使用列表初始化语法对数组中的每一个元素调用构造函数,那么默认调用无参数的构造函数,因此也就要求这个类必须要有无参数的构造函数。
例如:
class Test1
{
public:
Test1();
~Test1();
};
Test1::Test1()
{
cout << “Test1的构造函数” <<endl;
}
Test1::~Test1()
{
cout << “Test1的析构函数” <<endl;
}
class Test2
{
public:
Test2(int a); // 没有无参数的构造函数
~Test2();
};
Test2::Test2(int a)
{
cout << “Test2的构造函数” <<endl;
}
Test2::~Test2()
{
cout << “Test2的析构函数” <<endl;
}
int main()
{
Test1 ts1[2]; // Test1 有无参构造函数,OK
Test2 ts2[2]; // 错误,Test2 没有无参构造函数
Test2 ts3[2]={Test2(10)}; // 这个也错误,因为只对第一个元素调用了构造函数,第二个还是会主动调用无参构造函数
Test2 ts4[2]={Test2(10),Test2(20)}; // 正确
}
如果删除那两行错误的声明,那么输出结果为:
Test1的构造函数
Test1的构造函数
Test2的构造函数
Test2的构造函数
Test2的析构函数
Test2的析构函数
Test1的析构函数
Test1的析构函数
为了方便查看,在输出结果中间空了一行,上面是创建数组时对元素调用构造函数产生的输出,下面是数组死亡时对每一个元素调用析构函数产生的输出。
编程要求
在右侧编辑器中的Begin-End之间补充代码,设计 Student 类并实现用于管理学生信息表(学生表的长度不超过5)的3个函数,成员变量和函数的访问性都为公有的,具体类结构和函数要求如下:
学号,int类型
姓名,string类型
分数,float类型
带参构造函数:Student(int sid,string name,float sco),分别用这三个参数设置内部的三个成员。
void Add(int sid,string name,float sco),函数用于向学生表的末尾添加一条学生记录。
void PrintAll(),输出学生表中所有的记录,格式为:学号 姓名 成绩。
void Average(),计算学生表中学生的平均成绩并输出,格式为:平均成绩 计算结果。
提示:学生表可以用全局对象数组来完成,定义全局对象数组和定义全局变量一样,即定义在最外层作用域。
.h
#include <string>
#include <iostream>
using namespace std;
/********* Begin *********/
class Student
{
//在此处声明所需的成员
public:
int SID;
string Name;
float Sco;
Student();
Student(int sid,string name,float sco)
:SID(sid),Name(name),Sco(sco)
{ }
};
Student::Student()
{
}
Student s[5];
int count = 0;
/********* End *********/
void Add(int sid,string name,float sco)
{
/********* Begin *********/
s[count] = Student(sid,name,sco);
count ++;
/********* End *********/
}
void PrintAll()
{
/********* Begin *********/
//打印出学生表中所有记录
for(int i=0;i<count;i++)
cout<<s[i].SID<<" "<<s[i].Name<<" "<<s[i].Sco<<endl;
/********* End *********/
}
void Average()
{
/********* Begin *********/
//计算并打印出学生表中的平均成绩
int j = 0;
float aver = 0.0;
for(int i=0;i<count;i++)
{
j = j+1;
aver += s[i].Sco;
}
cout<<"平均成绩 "<< aver/j<<endl;
/********* End *********/
}
.cpp
#include "usr.h"
#include <string>
using namespace std;
int main()
{
int i,j,k;
string name1,name2,name3;
float score1,score2,score3;
cin >> i >> name1 >> score1;
cin >> j >> name2 >> score2;
cin >> k >> name3 >> score3;
Add(i,name1,score1);
Add(j,name2,score2);
Add(k,name3,score3);
PrintAll();
Average();
}
静态成员 —— 模拟共享书店
任务描述
本关任务:假设有一个这样的共享书店,当客户进入书店时需要托管一定数量的书籍,而这些书籍将被书店内所有用户共享,当客户离开书店时,他便会把之前进入书店托管的书一起带走。现请你编写代码设计这个共享书店。
相关知识
对象的内存中包含了成员变量,不同的对象占用不同的内存,这使得不同对象的成员变量相互独立,它们的值不受其他对象的影响。例如有两个相同类型的对象 a、b,它们都有一个成员变量 name,那么修改 a 对象的 name 值不会影响 b 中的 name 值。
可是有时候我们希望在多个对象之间共享数据,即对象 a 改变了某份数据后对象 b 可以检测到。共享数据的典型使用场景是计数。在 C++ 中,我们可以使用静态成员变量来实现多个对象共享数据的目标。
下面我们就一起之来学习静态成员的声明、定义及使用。
静态成员
静态成员变量是一种特殊的成员变量,它用关键字 static 来修饰。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本,对它做出修改时所有对象都是可见的。
静态成员在类的所有对象中是共享的。声明一个静态成员与声明一个非静态成员(也叫实例成员)基本一致,只需要在声明的最前面加上一个 static 关键字即可。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。
例如:
class Test
{
public:
int A; // 实例成员
static int B; // 静态成员变量
static void Fun1(); // 静态成员函数
};
注意:静态成员也是有访问性的。
定义分成两部分,一是静态变量的初始化,二是静态函数的定义。
静态变量的初始化不能在类的定义中,但是可以在类的外部通过使用范围解析运算符::来重新声明静态变量从而对它进行初始化。而定义静态函数,那就与定义实例函数一样了。
例如:
class Test
{
public:
static string HelloStr;
static void Hello();
void World();
};
string Test::HelloStr = “Hello”; // 静态变量初始化
void Test::World() // 定义实例成员函数
{
cout<<“World”<<endl;
}
void Test::Hello() // 定义静态函数,与定义 World 函数形式一样
{
cout<<“Hello”<<endl;
}
访问静态成员
静态成员的访问有以下两种方法:
使用类型名::静态成员格式访问;
通过对象访问。
第一种访问方式可以将其看做是一个全局变量,只不过变量名要带上类型名::的前缀;第二种可以将其看做是对象中的一个实例成员。
例如:
/* Test类的定义同上文 */
int main()
{
cout << Test::HelloStr << endl; // 通过作用域运算符访问
Test::HelloStr = “World”; // 修改静态变量 HelloStr
Test t1;
cout << t1.HelloStr <<endl; // 通过对象访问
Test::Hello(); // 通过作用域运算符访问
t1.World(); // 通过对象访问
}
输出结果为:
Hello
World
Hello
World
注意第一、二行输出的差别,那是因为代码修改了所有对象共享的Test::HelloStr静态变量。
编程要求
在右侧编辑器中的Begin-End之间补充代码,设计一个 User 类(客户类),现有一个共享书店,该书店客户在进入书店时需要托管一定量的书籍,而这些书籍将由书店内所有用户共享,当客户离开书店时,他还是要将他进入书店时托管的书带走。设计时访问性可自主选择,具体要求如下:
姓名:string Name
托管的书籍量:int Books
带参构造函数:User(string name,int books),使用这两个参数初始化内部的两个成员,同时按照姓名 数量 进入的格式打印一条消息。
析构函数:~User(),按照姓名 数量 离开的格式打印一条消息。
静态成员函数:void GetState(),按照书店人数:用户总数,书店共享书数量:书籍总数,人均共享数量:人均书籍量的格式打印一条消息,其中人均书籍量只保留整数部分,具体请参考测试说明。
提示:可以增加 UserCount ,BookCount 两个静态变量用于记录已有用户数和已有书籍数。
.h
#include <string>
#include <iostream>
using namespace std;
/********* Begin *********/
class User
{
//在此处声明所需的成员
public:
static int UserCount;
static int BookCount;
string Name;
int Books;
User(string name,int books);
~User();
static void GetState();
};
int User::UserCount=0;
int User::BookCount=0;
User::User(string name,int books)
{
UserCount++;
Name=name;
Books=books;
cout<<Name<<" "<<Books<<" "<<"进入"<<endl;
BookCount + =Books;
}
User::~User()
{
UserCount--;
cout<<Name<<" "<<Books<<" "<<"离开"<<endl;
BookCount-=Books;
}
void User::GetState()
{
cout<<"书店人数:"<<User::UserCount<<","<<"书店共享书数量:"<<User::BookCount<<","<<"人均共享数量:"<<User::BookCount/User::UserCount<<endl;
}
//在此处定义成员函数
/********* End *********/
.cpp
#include "usr.h"
#include <string>
#include <iostream>
using namespace std;
int main()
{
string name1,name2,name3;
int a,b,c;
cin >> name1 >> a >> name2 >> b >> name3 >> c ;
User *u1 = new User(name1,a);
User *u2 = new User(name2,b);
User::GetState();
delete u1;
u1 = new User(name3,c);
User::GetState();
delete u2;
User::GetState();
delete u1;
User::GetState();
}