综合测验
在本章中,我们探讨了C ++的本质 - 面向对象编程!这是教程系列中最重要的一章。
总结
类允许您创建自己的数据类型,这些数据类型捆绑了处理该数据的数据和函数。类中的数据和函数称为成员。通过使用选择该类的成员。运算符(或者 - >如果您通过指针访问成员)。
访问说明符允许您指定谁可以访问类的成员。公共成员可以由任何人直接访问。私人成员只能由该班级的其他成员访问。当我们获得继承时,我们将在以后介绍受保护的成员。默认情况下,类的所有成员都是私有的,结构的所有成员都是公共的。
封装是将所有成员数据设为私有的过程,因此无法直接访问。这有助于保护您的班级免遭滥用。
构造函数是一种特殊类型的成员函数,允许您初始化类的对象。不带参数(或具有所有默认参数)的构造函数称为默认构造函数。如果用户未提供初始化值,则使用默认构造函数。您应该始终为您的类提供至少一个构造函数。
成员初始化列表允许您从构造函数中初始化成员变量(而不是分配成员变量值)。
在C ++ 11中,非静态成员初始化允许您在声明成员变量时直接指定它们的默认值。
在C ++ 11之前,构造函数不应该调用其他构造函数(它将编译,但不会按预期工作)。在C ++ 11中,允许构造函数调用其他构造函数(称为委托构造函数或构造函数链接)。
析构函数是另一种特殊的成员函数,允许您的类自我清理。应该从这里执行任何类型的释放或关闭例程。
所有成员函数都有一个隐藏的* this指针,指向要修改的类对象。大多数情况下,您不需要直接访问此指针。但是如果你需要,你可以。
将类定义放在与类同名的头文件中是一种很好的编程风格,并在与该类同名的.cpp文件中定义类函数。这也有助于避免循环依赖。
如果成员函数不修改类的状态,则可以(并且应该)成为const。Const类对象只能调用const成员函数。
静态成员变量在类的所有对象之间共享。虽然可以从类对象访问它们,但也可以通过范围解析运算符直接访问它们。
类似地,静态成员函数是没有* this指针的成员函数。他们只能访问静态成员变量。
Friend函数是被视为类的成员函数的函数(因此可以直接访问类的私有数据)。朋友类是类中所有成员都被视为友元函数的类。
可以创建匿名类对象,以便在表达式中进行求值,或传递或返回值。
您还可以在类中嵌套类型。这通常与与类相关的枚举使用,但如果需要,可以使用其他类型(包括其他类)。
QUIZ time
1a)编写一个名为Point2d的类。Point2d应包含两个类型为double的成员变量:m_x和m_y,两者都默认为0.0。提供构造函数和打印功能。
应运行以下程序:
#include <iostream>
int main()
{
Point2d first;
Point2d second(3.0, 4.0);
first.print();
second.print();
return 0;
}
这应该打印:
Point2d(0,0);
Point2d(3,4);
解决方案:
#include <iostream>
class Point2d
{
private:
double m_x;
double m_y;
public:
Point2d(double x = 0.0, double y = 0.0)
: m_x(x), m_y(y)
{
}
void print() const
{
std::cout << "Point2d(" << m_x << ", " << m_y << ")\n";
}
};
int main()
{
Point2d first;
Point2d second(3.0, 4.0);
first.print();
second.print();
return 0;
}
1b)现在添加一个名为distanceTo的成员函数,它将另一个Point2d作为参数,并计算它们之间的距离。给定两个点(x1,y1)和(x2,y2),它们之间的距离可以计算为sqrt((x1-x2)(x1-x2)+(y1-y2)(y1-y2))。sqrt函数位于标题cmath中。
应运行以下程序:
int main()
{
Point2d first;
Point2d second(3.0, 4.0);
first.print();
second.print();
std::cout << "Distance between two points: " << first.distanceTo(second) << '\n';
return 0;
}
这应该打印:
Point2d(0,0);
Point2d(3,4);
Distance between two points: 5
显示解决方案
1c)将函数distanceTo从成员函数更改为非成员友元函数,该函数将两个Point作为参数。同时将其重命名为“distanceFrom”。
应运行以下程序:
int main()
{
Point2d first;
Point2d second(3.0, 4.0);
first.print();
second.print();
std::cout << "Distance between two points: " << distanceFrom(first, second) << '\n';
return 0;
}
这应该打印:
Point2d(0,0);
Point2d(3,4);
Distance between two points: 5
#include <cmath>
#include <iostream>
class Point2d
{
private:
double m_x;
double m_y;
public:
Point2d(double x = 0.0, double y = 0.0)
: m_x(x), m_y(y)
{
}
void print() const
{
std::cout << "Point2d(" << m_x << " , " << m_y << ")\n";
}
friend double distanceFrom(const Point2d &x, const Point2d &y);
};
double distanceFrom(const Point2d &x, const Point2d &y)
{
return sqrt((x.m_x - y.m_x)*(x.m_x - y.m_x) + (x.m_y - y.m_y)*(x.m_y - y.m_y));
}
int main()
{
Point2d first;
Point2d second(3.0, 4.0);
first.print();
second.print();
std::cout << "Distance between two points: " << distanceFrom(first, second) << '\n';
return 0;
}
2)为这个类写一个析构函数:
class HelloWorld
{
private:
char *m_data;
public:
HelloWorld()
{
m_data = new char[14];
const char *init = "Hello, World!";
for (int i = 0; i < 14; ++i)
m_data[i] = init[i];
}
~HelloWorld()
{
// replace this comment with your destructor implementation
}
void print() const
{
std::cout << m_data;
}
};
int main()
{
HelloWorld hello;
hello.print();
return 0;
}
解决方案:
class HelloWorld
{
private:
char *m_data;
public:
HelloWorld()
{
m_data = new char[14];
const char *init = "Hello, World!";
for (int i = 0; i < 14; ++i)
m_data[i] = init[i];
}
~HelloWorld()
{
delete[] m_data;
}
void print() const
{
std::cout << m_data;
}
};
int main()
{
HelloWorld hello;
hello.print();
return 0;
}
3)让我们创建一个随机怪物生成器。这个应该很有趣。
3a)首先,让我们创建一个名为MonsterType的怪物类型的枚举。包括以下怪物类型:龙,地精,食人魔,兽人,骷髅,巨魔,吸血鬼和僵尸(Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, 和 Zombie)。添加额外的MAX_MONSTER_TYPES枚举,以便我们可以计算有多少个枚举器。
解决方案:
enum MonsterType
{
DRAGON,
GOBLIN,
OGRE,
ORC,
SKELETON,
TROLL,
VAMPIRE,
ZOMBIE,
MAX_MONSTER_TYPES
};
3b)现在,让我们创建我们的Monster类。我们的Monster将有4个属性(成员变量):一个类型(MonsterType),一个名称(std :: string),一个咆哮(std :: string)和一个生命点数(int)。创建一个包含这4个成员变量的Monster类。
#include <string>
enum MonsterType
{
DRAGON,
GOBLIN,
OGRE,
ORC,
SKELETON,
TROLL,
VAMPIRE,
ZOMBIE,
MAX_MONSTER_TYPES
};
class Monster
{
private:
MonsterType m_type;
std::string m_name;
std::string m_roar;
int m_hitPoints;
};
3c)枚举MonsterType特定于Monster,因此将类中的枚举作为公共声明移动。
#include <string>
class Monster
{
public:
enum MonsterType
{
DRAGON,
GOBLIN,
OGRE,
ORC,
SKELETON,
TROLL,
VAMPIRE,
ZOMBIE,
MAX_MONSTER_TYPES
};
private:
MonsterType m_type;
std::string m_name;
std::string m_roar;
int m_hitPoints;
};
3d)创建一个允许初始化所有成员变量的构造函数。
以下程序应编译:
int main()
{
Monster skele(Monster::SKELETON, "Bones", "*rattle*", 4);
return 0;
}
解决方案:
#include <string>
class Monster
{
public:
enum MonsterType
{
DRAGON,
GOBLIN,
OGRE,
ORC,
SKELETON,
TROLL,
VAMPIRE,
ZOMBIE,
MAX_MONSTER_TYPES
};
private:
MonsterType m_type;
std::string m_name;
std::string m_roar;
int m_hitPoints;
public:
Monster(MonsterType type, std::string name, std::string roar, int hitPoints)
: m_type(type), m_name(name), m_roar(roar), m_hitPoints(hitPoints)
{
}
};
int main()
{
Monster skele(Monster::SKELETON, "Bones", "*rattle*", 4);
return 0;
}
3e)现在我们希望能够打印我们的怪物,以便我们验证它是正确的。为此,我们需要编写一个将MonsterType转换为std :: string的函数。编写该函数(称为getTypeString()),以及print()成员函数。
以下程序应编译:
int main()
{
Monster skele(Monster::SKELETON, "Bones", "*rattle*", 4);
skele.print();
return 0;
}
并打印:
Bones the skeleton has 4 hit points and says rattle
#include <iostream>
#include <string>
class Monster
{
public:
enum MonsterType
{
DRAGON,
GOBLIN,
OGRE,
ORC,
SKELETON,
TROLL,
VAMPIRE,
ZOMBIE,
MAX_MONSTER_TYPES
};
private:
MonsterType m_type;
std::string m_name;
std::string m_roar;
int m_hitPoints;
public:
Monster(MonsterType type, std::string name, std::string roar, int hitPoints)
: m_type(type), m_name(name), m_roar(roar), m_hitPoints(hitPoints)
{
}
std::string getTypeString() const
{
switch (m_type)
{
case DRAGON: return "dragon";
case GOBLIN: return "goblin";
case OGRE: return "ogre";
case ORC: return "orc";
case SKELETON: return "skeleton";
case TROLL: return "troll";
case VAMPIRE: return "vampire";
case ZOMBIE: return "zombie";
}
return "???";
}
void print() const
{
std::cout << m_name << " the " << getTypeString() << " has " << m_hitPoints << " hit points and says " << m_roar << '\n';
}
};
int main()
{
Monster skele(Monster::SKELETON, "Bones", "*rattle*", 4);
skele.print();
return 0;
}
3f)现在我们可以创建一个随机怪物生成器。让我们来看看我们的MonsterGenerator类是如何工作的。理想情况下,我们会要求它给我们一个怪物,它会为我们创建一个随机的怪物。我们不需要多个MonsterGenerator。这是静态类(一个所有函数都是静态的)的良好候选者。创建一个静态MonsterGenerator类。创建一个名为generateMonster()的静态函数。这应该归还怪物。现在,让它返回匿名怪物(Monster :: SKELETON,“Bones”,“* rattle *”,4);
以下程序应编译:
int main()
{
Monster m = MonsterGenerator::generateMonster();
m.print();
return 0;
}
并打印:
Bones the skeleton has 4 hit points and says rattle
#include <iostream>
#include <string>
class Monster
{
public:
enum MonsterType
{
DRAGON,
GOBLIN,
OGRE,
ORC,
SKELETON,
TROLL,
VAMPIRE,
ZOMBIE,
MAX_MONSTER_TYPES
};
private:
MonsterType m_type;
std::string m_name;
std::string m_roar;
int m_hitPoints;
public:
Monster(MonsterType type, std::string name, std::string roar, int hitPoints)
: m_type(type), m_name(name), m_roar(roar), m_hitPoints(hitPoints)
{
}
std::string getTypeString() const
{
switch (m_type)
{
case DRAGON: return "dragon";
case GOBLIN: return "goblin";
case OGRE: return "ogre";
case ORC: return "orc";
case SKELETON: return "skeleton";
case TROLL: return "troll";
case VAMPIRE: return "vampire";
case ZOMBIE: return "zombie";
}
return "???";
}
void print() const
{
std::cout << m_name << " the " << getTypeString() << " has " << m_hitPoints << " hit points and says " << m_roar << '\n';
}
};
class MonsterGenerator
{
public:
static Monster generateMonster()
{
return Monster(Monster::SKELETON, "Bones", "*rattle*", 4);
}
};
int main()
{
Monster m = MonsterGenerator::generateMonster();
m.print();
return 0;
}
3g)现在,MonsterGenerator需要生成一些随机属性。要做到这一点,我们需要利用这个方便的功能:
// Generate a random number between min and max (inclusive)
// Assumes srand() has already been called
int getRandomNumber(int min, int max)
{
static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // static used for efficiency, so we only calculate this value once
// evenly distribute the random number across our range
return static_cast<int>(rand() * fraction * (max - min + 1) + min);
}
但是,因为MonsterGenerator直接依赖于这个函数,所以我们把它作为一个静态函数放在类中。
解决方案:
class MonsterGenerator
{
public:
// Generate a random number between min and max (inclusive)
// Assumes srand() has already been called
static int getRandomNumber(int min, int max)
{
static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // static used for efficiency, so we only calculate this value once
// evenly distribute the random number across our range
return static_cast<int>(rand() * fraction * (max - min + 1) + min);
}
static Monster generateMonster()
{
return Monster(Monster::SKELETON, "Bones", "*rattle*", 4);
}
};
3h)现在编辑函数generateMonster()以生成随机MonsterType(在0和Monster :: MAX_MONSTER_TYPES-1之间)和随机生命点(在1和100之间)。这应该是相当简单的。完成后,在函数内部定义两个大小为6的静态固定数组(名为s_names和s_roars),并使用您选择的6个名称和6个声音初始化它们。从这些数组中选择一个随机名称。
以下程序应编译:
#include <ctime> // for time()
#include <cstdlib> // for rand() and srand()
int main()
{
srand(static_cast<unsigned int>(time(0))); // set initial seed value to system clock
rand(); // If using Visual Studio, discard first random value
Monster m = MonsterGenerator::generateMonster();
m.print();
return 0;
}
#include <ctime> // for time()
#include <cstdlib> // for rand() and srand()
#include <iostream>
#include <string>
class Monster
{
public:
enum MonsterType
{
DRAGON,
GOBLIN,
OGRE,
ORC,
SKELETON,
TROLL,
VAMPIRE,
ZOMBIE,
MAX_MONSTER_TYPES
};
private:
MonsterType m_type;
std::string m_name;
std::string m_roar;
int m_hitPoints;
public:
Monster(MonsterType type, std::string name, std::string roar, int hitPoints)
: m_type(type), m_name(name), m_roar(roar), m_hitPoints(hitPoints)
{
}
std::string getTypeString() const
{
switch (m_type)
{
case DRAGON: return "dragon";
case GOBLIN: return "goblin";
case OGRE: return "ogre";
case ORC: return "orc";
case SKELETON: return "skeleton";
case TROLL: return "troll";
case VAMPIRE: return "vampire";
case ZOMBIE: return "zombie";
}
return "???";
}
void print() const
{
std::cout << m_name << " the " << getTypeString() << " has " << m_hitPoints << " hit points and says " << m_roar << '\n';
}
};
class MonsterGenerator
{
public:
// Generate a random number between min and max (inclusive)
// Assumes srand() has already been called
static int getRandomNumber(int min, int max)
{
static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // static used for efficiency, so we only calculate this value once
// evenly distribute the random number across our range
return static_cast<int>(rand() * fraction * (max - min + 1) + min);
}
static Monster generateMonster()
{
Monster::MonsterType type = static_cast<Monster::MonsterType>(getRandomNumber(0, Monster::MAX_MONSTER_TYPES - 1));
int hitPoints = getRandomNumber(1, 100);
static std::string s_names[6]{ "Blarg", "Moog", "Pksh", "Tyrn", "Mort", "Hans" };
static std::string s_roars[6]{ "*ROAR*", "*peep*", "*squeal*", "*whine*", "*hum*", "*burp*"};
return Monster(type, s_names[getRandomNumber(0, 5)], s_roars[getRandomNumber(0, 5)], hitPoints);
}
};
int main()
{
srand(static_cast<unsigned int>(time(0))); // set initial seed value to system clock
rand(); // If using Visual Studio, discard first random value
Monster m = MonsterGenerator::generateMonster();
m.print();
return 0;
}
3i)为什么我们将变量s_names和s_roars声明为静态?
回答:使s_names和s_roars静态导致它们只被初始化一次。否则,每次调用generateMonster()时都会重新初始化它们。