iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Software Development

三十天內用C++寫出一個小遊戲系列 第 20

Day 20 - Self-defined Data types(in C) 自訂資料型態

  • 分享至 

  • xImage
  •  

Intro

自訂資料型態可以是

  • 把不同 data type 合成成一個 複合的型態
  • 或是重新定義一個資料型態

雖然通常不太會寫到自訂資料型態

但是如果是在大型的專案中,常常會利用自訂資料型態來提高效率。


Outline

  • struct
  • typedef
  • struct with member functions
  • Randomization

struct

今天有 點A、B在一個二維平面(Cartesian coordinate system)上,我們要計算 vector AB,並 print out。

void vector(int x1, int y1, int x2, int y2, int& rx, int& ry) // call by reference
{
	rx = x2 - x1;
	ry = y2 - y1;
}

int main()
{
	int x1 = 0, x2 = 0;
	int y1 = 0, y2 = 0;
	int rx = 0, ry = 0; // 用來存回傳質
	vector(x1, y1, x2, y2, rx, ry);
	cout << rx << " " << ry << "\n";
	return 0;
}

雖然我們也可以這樣寫,但是因為變數太多,如果有更多的變數(x3, y3)的話就會搞得很亂。

所以在這邊,我們可能很想要把(x1, y1) (x2, y2)...等等的不同組 x y 把它每一個做成一個點,這時候就可以用上 struct 了!

功能

透過struct 可以讓我們把不同的資料型態,合成一種資料型態。用中文來說,就是把很多東西集合在一起,形成一個結構,這樣以後就可以自由的取用他了。(struct = structure)

  • 可以把 basic data type(int float..)、nonbasic data type(pointers, arrays)、或是再把 self-defined data type 再把他們 group 起來。
  • 合起來之後,這些item 可能就會擁有複數個屬性供我們使用。

宣告

struct Point(定義一個新的型態)
{
	int x; // x屬性
	int y; // y屬性
};

宣告之後,我們就可以使用他了!

  1. Declare variables with self-defined type name
  2. Assign values to both attributes by grouping values by curly brackets
  3. Access attributes through the dot operator

我們就可以這樣寫:

Point vector(Point A, Point B)
{
	Point vecXY;
	vecXY.x = B.x - A.x;
	vecXY.y = B.y - A.y;
	return vecXY;
}

int main()
{
	Point a = {0, 0}, b = {10, 20};
	Point vecAB = vector(a, b);
	cout << vecAB.x << " ";
	cout << vecAB.y << "\n";
	return 0;
}

可以把struct 想像成把一堆東西裝進去一個資料夾,然後再幫他們編號,等到我們要使用的時候就會使用 . 把他們取出來,後面的 x y 就是他們的編號

Definition of struct:

struct Struct name
{
	type1 field1; // member variable
	type2 field2;
	type3 field3;
	//more field
};

struct variable declaration:

struct name variable name;

e.g.

Point A;
Point B, C, thisIsAPoint;
Point staticPointArray[10];
Point* pointPtr = thisIsAPoint;
Point* dynamicPointArray = new Point[10];

Accessing struct attributes:

struct name . attribute name
a.b.c 
// c 是 b 的 attribute
// b 是 a 的 attribute

struct assignment:

  • 宣告的時候,要使用{}來指派attribute。
  • 可以不指派,不指派的質就會預設為0
struct Point {
	int x;
	int y;
	int z;
};

int main()
{
	Point A[100];
	for (int i = 0; i < 50; i++)
		A[i] = {i};
	for (int i = 0; i < 100; i++)
		cout << A[i].x << " " << A[i].y
				<< " " << A[i].z << "\n";
	return 0;
}

像這個程式,我們指派了前 50 個東西的 value,但是後面沒有指派。

結果會顯示(x, y, z):

前五十個: (i, 0, 0)

後五十個:開始出現不知道哪來的數字

  • function 可以回傳 struct ,這時候函式就是 Call by Value ,但是 struct 也可以 Call by reference。
struct Point 
{
	int x; 
	int y;
};

void reflect(Point& a)
{
	int temp = a.x;
	a.x = a.y;
	a.y = temp;
}

int main(int argc, char const *argv[])
{
	Point a = {10, 20};
	cout << a.x << " " << a.y << endl;
	reflect(a);
	cout << a.x << " " << a.y << endl;
	return 0;
}

Memory allocation for struct:

如果宣告一個 struct,記憶體是怎配置的呢?

struct Point 
{
	int x;
	int y;

};

int main()
{
	Point a[3];
	cout << sizeof(Point) << " " << sizeof(a) << "\n";

	cout << &a << "\n";
	for (int i = 0; i< 3; i ++)
		cout << &a[i] << " " << &a[i].x << " " << &a[i].y << "\n";
	Point* b = new Point[3];
	cout << sizeof(b) << "\n";
	delete [] b;
	b = nullptr;
	return 0;
}

透過這個程式可以知道:

  • sizeof(Point) 是 8 bytes (也就是 2 個 int)
  • sizeof(a) 是 24 個 bytes
  • 再下面可以看到 每個x y 都是連在一起存的
  • 阿因為 b 就是一個指標,所以 sizeof(b) 當然就是 8

typedef (type definition)

Definition of typedef:

typeof old type new type;

簡單說就是把 幫 old type 取一個新的名字,你用 old 或是 new 的時候都可以叫出來。除了可以方便閱讀以外,它還有下面這種好處。

Application:

如果今天我們有一個程式,寫了很多重複的東西,像是 3.14,理想氣體常數, etc。我們這時候可能會用 const 來宣告它(e.g., pi, G),且讓它不能被更改。

如果今天我們要計算匯率,可能會寫一個程式:

double us = 0;
double nt = 0;
cin >> us;
nt = us * 28;
cout << us << " " << nt;

但是問題來的,如果今天要把 double 全部改成 float,而且如果你今天這個程式是在計算全世界貨幣的匯率,這該怎麼辦?

這時候 typedef 就派上用場了:

typedef double Dollar;
Dollar us = 0;
Dollar nt = 0;
cin >> us;
nt = us * 28;
cout << us << " " << nt;

如果這個時候你想要改成 float ,直接改成 typedef float Dollar 就好了!

Type life cycle

可以在任何地方宣告 typedef,但是它只會存活到那一個 block 而已:struct 也是。多半大家都會寫在 using namespace std; 的後面。

global type; local variable

Application:

把 typedef 和 struct 混合在一起用

剛剛我們寫過

Point a = {0, 0};
Point b = {10, 20};
Point vecAB = vector(a, b);

但是實際上,如果以 Point 來表示 vector 其實蠻奇怪的,所以我們可以這樣做:

typedef Point Vector;

這樣我們就可以改成 :

Point a = {0, 0};
Point b = {10, 20};
Vector vecAB = vector(a, b);

這樣就會更好理解程式了。


ctime library

在很多 C++ stand library裡面的函數,會提供被typedef 定義,新的 data type。

  • clock()
    • 功能:計算從開始 program 的時候,經過了多少 system clock

    • application:

      #include<iostream>
      #include<ctime>
      using namespace std;
      
      int main(int argc, char const *argv[])
      {
      	*clock_t sTime* = clock();
      	for (int i = 0; i < 1000000000; ++i)
      		;
      	*clock_t eTime* = clock();
      
      	cout << sTime << " " << eTime << endl;
      	return 0;
      }
      

      這時候你可能會疑惑: what is clock_t?

    • 事實上,clock()回傳的type 被稱為 clock_t。在 library 裡面已經宣告了 typedef long int clock_t;,所以說 clock_t 就是 long int,所以上面的程式如果改成用 long int 宣告:long int sTime = clock();也是行的。原因是因為跟剛剛的常數一樣,像是 pi 或是其他常數,如果你某一天想要改成 long long int,這時候我們只需要改宣告 clock_t 的那一行就行了。

    • 但是上面程式 cout 的會是 system second的欸,那我們要怎麼知道到底是幾秒。其實就把兩個時間相減,再除以 CLOCKS_PER_SEC這個 const 就好了!我的電腦跑出來是 1.59583。

      cout << static_cast<double>(eTime - sTime) / CLOCKS_PER_SEC; 
      

cstring

  • strlen()

    • 他是回傳 size_t → 這個也是透過 typedef 定義的,原因跟 clock() 差不多。
    size_t strlen(const char* str);
    

struct with member functions

我們上面宣告的:

struct Point
{ 
	int x;
	int y
};

這兩個叫做 member variable 或者是 attribute(屬性)

那如我我們想對 Point 做一些事情要怎麼辦?

像是我們要計算這兩個點的距離。

double distOri (Point p)
{
	double dist = sqrt(pow(p.x, 2) + pow(p.y, 2));
	return dist;
}
// remember to include <cmath>

是可以這樣寫的,但是也可以直接放在 struct 裡面:

struct
{
	int x;
	int y;
	double distOri()
	{
		return sqrt(pow(x, 2) + pow(y, 2))
	}
}

可以把它像是這個機器一樣,螢幕顯示參數 x y,上面有一個搖桿可以處理這兩個參數。(小傑老師畫的xD 很可愛)


如果要呼叫這個函數的話:

int main()
{
	Point a = {3, 5};
	cout << a.distOri();
	return 0;
}

這時候可以把 a 想像成一個機器,按下按鈕(.distOri())就可以處理這些事情。

而 struct 裡面的函數,也可以寫成像是一般函數的 header and body 一樣。

struct
{
	int x;
	int y;
	double distOri();
}

double Point::disOri()
{
	return sqrt(pow(x, 2), pow(y, 2));
}

使用 member function 的優點就是,如果今天有很多個function,這樣使用 member function 的時候,程式可以比較好理解,且在 debug 或是美觀而言,也會比較好看。

Another example:

struct Point
{
	int x;
	int y;
};
void reflect(Point& a)
{
	int temp = a.x;
	a.x = a.y;
	a.y = temp;
}
void print(Point a)
{
	cout << a.x << " " << a.y << "\n";
}

int main(int argc, char const *argv[])
{
	Point a = {10, 20};
	Point b = {5, 2};
	print(a);
	print(b);
	reflect(a);
	print(a);
	print(b);
	return 0;
}

我們剛剛的 reflect ,也可以把他們改成 member function:

	struct Point
{
	int x;
	int y;
	void reflect();
	void print();
};
void Point::reflect()
{
	int temp = x;
	x =	y;
	y = temp;
}
void Point::print()
{
	cout << x << " " << y << "\n";
}

int main(int argc, char const *argv[])
{
	Point a = {10, 20};
	Point b = {5, 2};
	a.print();
	b.print();
	a.reflect();
	a.print();
	b.print();
	return 0;
}

雖然說兩者跑出來的東西其實是一樣的,但是如果有很多同一類的(對差不多的東西做某些事) function,就很推薦使用這個。

另外,用 member function 也是一個模組化很好的方式。

Randomization

Random numbers:

  • 我們在寫程式的時候會想要產生隨機的數字或是變數。
  • 在<cstdlib> 裡面有兩個function: srand()、rand(),就是專門在搞 Random 的。

int rand():

  • 回傳:它會回傳一個從 0 到 RAND_MAX 中隨機一個 integer。

  • 但是它回傳的 的是 "Pseudo-random" integer,也就是說他們雖然長的像是 random,但是實際上是有一個公式的:e.g., ri = (941324314 * r_(i-1) + 18293084) mod 32767 他是拿上一個 number 去做下一個亂數。

  • 所以我們唯一能決定的就是 random number seed (第一個),這時候就要使用

    void srand(unsigned int)

  • 那我們要怎保證每次傳給 srand() 的數字都不一樣?很多時候我們會使用 time(nullptr) 當作他的 argument。

    header:

    time_t time(time_t* timer);

    • 回傳:從 1970. 1. 1 至今經過了幾秒。

    • 使用:(須 include ctime)

      time_t t = time(nullptr)
      time(&t);
      srand(t);
      cout << t << "\n";
      
  • 如果想要讓我們的隨機數字在某個 range 裡面,就可以使用 %

    #include<iostream>
    #include<ctime>
    #include<cstdlib>
    using namespace std;
    
    int main(int argc, char const *argv[])
    {
    	srand(time(0));
    	int rn = 0;
    	for (int i = 0; i < 10; ++i)
    	{
    		rn = ((rand() % 10)) + 100;
    		//或是也可以: rn = (static_cast<double>(rand() % 501)) / 100;
    		cout << rn;
    	}
    	return 0;
    }
    

如果我們今天想要用 self-defined 的方式製作我們自己的 random number generator (也就是 rand()),其實也是可以的。

  • 產生隨機數字的公式是:https://chart.googleapis.com/chart?cht=tx&amp;chl=%24r_i%20%3D%20(a%20%20r_%7Bi-1%7D%20%2B%20b)%20mod%20c%24
  • 所以我們可以知道 attribute 有 a, b, c
//就可以這樣寫
struct Randomizer
{
	int a;
	int b;
	int c;
	int cur;
	int rand();
}
int Randomizer::rand()
{
	cur = (a * cur + b) + c;
	return cur;
}

使用:

int main()
{
	Randomizer r1 = {10, 4, 31, 0};
	for (int i = 0; i < 10; i++)
		cout << r1.rand() << " ";
	cout << endl;
	Randomizer r2 = {10, 7, 32, 0};
	for (int i = 0; i < 10; i++)
		cout << r2.rand() << " ";
	cout << endl;
	return 0;
}

r1 會跑出:4 13 10 11 21 28 5 23 17 19

r2 則會跑出:7 13 9 1 17 17 17 17 17 17

可以了解到,不同的參數會跑出不一樣的 random 的結果。

My Opinion

今天學的 struct 終於讓我知道,那些 . 到底是從哪裡來的了。像是我最近看遊戲的教學影片,裡面就用了很多 .getPosition .setPosition,現在才知道 wow 原來就是這麼一回事!


上一篇
Day 19 - C strings 字串,我好想吃串燒
下一篇
Day 21 - 我們這一班
系列文
三十天內用C++寫出一個小遊戲30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言