1. 程式人生 > >C++基礎教程面向物件(學習筆記(17))

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()時都會重新初始化它們。