Day 20 - Self-defined Data types(in C) 自订资料型态

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&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 原来就是这麽一回事!


<<:  二元树之 IF 下策 - DAY 18

>>:  如何在 Angular 取得当前页面的绝对路径

Day34 ATT&CK for ICS - Impair Process Control(2)

T0856 Spoof Reporting Message 攻击者欺骗回传报告的内容,为了不让自己的...

【Day2】[资料结构]-阵列Array

阵列(Array)是一种常见的资料结构,常用来处理相同类型的有序资料,并存放在连续的记忆体空间中。但...

Day 5 - 使用JWT Token帮Laravel 8.0做Authentication

Introduce 为了API的安全性,本次跟各位介绍透过JWT Token来帮API做身分验证,简...

Day 10 - 智慧城市Go Smart Award 经历(4) - 展览

这是回顾Go Smart Award的最後一集,  整个比赛的内容也在颁奖典礼举行的同时, 配合在...

讯息是怎麽进到网际网路的(一)?封包的传递路径:区域网路

我们现在知道,你所传递的讯息,会被包裹起来丢到网路上的节点中转运,直至抵达目的地。那具体会通过怎麽样...