C++基礎教程面向物件(學習筆記(17))
綜合測驗
在本章中,我們探討了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()時都會重新初始化它們。