Nạp Chồng Toán Tử
Để dễ đọc và viết mã, việc nạp chồng một số toán tử được phép. Toán tử nạp chồng được viết bằng từ khóa operator
. Các toán tử sau có thể được nạp chồng:
- Nhị phân: +, -, /, *, %, <<, >>, ==, !=, <, >, <=, >=, =, +=, -=, /=, *=, %=, &=, |=, ^=, <<=, >>=, &&, ||, &, |, ^
- Đơn phân: +, -, ++, --, !, ~
- Toán tử gán: =
- Toán tử chỉ mục: []
Nạp chồng toán tử cho phép sử dụng ký hiệu toán tử (được viết dưới dạng biểu thức đơn giản) cho các đối tượng phức tạp - cấu trúc và lớp. Việc viết biểu thức sử dụng các toán tử đã nạp chồng giúp đơn giản hóa giao diện mã nguồn, vì triển khai phức tạp hơn được ẩn đi.
Ví dụ, hãy xem xét các số phức, bao gồm phần thực và phần ảo. Chúng được sử dụng rộng rãi trong toán học. Ngôn ngữ MQL5 không có kiểu dữ liệu để biểu diễn số phức, nhưng có thể tạo một kiểu dữ liệu mới dưới dạng structure or class. Khai báo cấu trúc complex và định nghĩa bốn phương thức thực hiện bốn phép toán số học:
//+------------------------------------------------------------------+
//| Cấu trúc cho các phép toán với số phức |
//+------------------------------------------------------------------+
struct complex
{
double re; // Phần thực
double im; // Phần ảo
//--- Hàm tạo
complex():re(0.0),im(0.0) { }
complex(const double r):re(r),im(0.0) { }
complex(const double r,const double i):re(r),im(i) { }
complex(const complex &o):re(o.re),im(o.im) { }
//--- Phép toán số học
complex Add(const complex &l,const complex &r) const; // Cộng
complex Sub(const complex &l,const complex &r) const; // Trừ
complex Mul(const complex &l,const complex &r) const; // Nhân
complex Div(const complex &l,const complex &r) const; // Chia
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Bây giờ, trong mã của chúng ta, có thể khai báo các biến biểu diễn số phức và làm việc với chúng.
Ví dụ:
void OnStart()
{
//--- Khai báo và khởi tạo biến kiểu complex
complex a(2,4),b(-4,-2);
PrintFormat("a=%.2f+i*%.2f, b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- Cộng hai số
complex z;
z=a.Add(a,b);
PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Nhân hai số
z=a.Mul(a,b);
PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Chia hai số
z=a.Div(a,b);
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Nhưng sẽ tiện hơn nếu sử dụng các toán tử thông thường "+", "-", "*" và "/" cho các phép toán số học thông thường với số phức.
Từ khóa operator
được sử dụng để định nghĩa một hàm thành viên thực hiện chuyển đổi kiểu. Các phép toán đơn phân và nhị phân cho các biến đối tượng lớp có thể được nạp chồng như các hàm thành viên không tĩnh. Chúng ngầm tác động lên đối tượng lớp.
Hầu hết các phép toán nhị phân có thể được nạp chồng như các hàm thông thường nhận một hoặc cả hai đối số là biến lớp hoặc con trỏ đến đối tượng của lớp này. Đối với kiểu complex của chúng ta, việc nạp chồng trong khai báo sẽ trông như sau:
//--- Toán tử
complex operator+(const complex &r) const { return(Add(this,r)); }
complex operator-(const complex &r) const { return(Sub(this,r)); }
complex operator*(const complex &r) const { return(Mul(this,r)); }
complex operator/(const complex &r) const { return(Div(this,r)); }
2
3
4
5
Ví dụ đầy đủ của script:
//+------------------------------------------------------------------+
//| Hàm bắt đầu chương trình script |
//+------------------------------------------------------------------+
void OnStart()
{
//--- Khai báo và khởi tạo biến kiểu complex
complex a(2,4),b(-4,-2);
PrintFormat("a=%.2f+i*%.2f, b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//a.re=5;
//a.im=1;
//b.re=-1;
//b.im=-5;
//--- Cộng hai số
complex z=a+b;
PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Nhân hai số
z=a*b;
PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Chia hai số
z=a/b;
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
}
//+------------------------------------------------------------------+
//| Cấu trúc cho các phép toán với số phức |
//+------------------------------------------------------------------+
struct complex
{
double re; // Phần thực
double im; // Phần ảo
//--- Hàm tạo
complex():re(0.0),im(0.0) { }
complex(const double r):re(r),im(0.0) { }
complex(const double r,const double i):re(r),im(i) { }
complex(const complex &o):re(o.re),im(o.im) { }
//--- Phép toán số học
complex Add(const complex &l,const complex &r) const; // Cộng
complex Sub(const complex &l,const complex &r) const; // Trừ
complex Mul(const complex &l,const complex &r) const; // Nhân
complex Div(const complex &l,const complex &r) const; // Chia
//--- Toán tử nhị phân
complex operator+(const complex &r) const { return(Add(this,r)); }
complex operator-(const complex &r) const { return(Sub(this,r)); }
complex operator*(const complex &r) const { return(Mul(this,r)); }
complex operator/(const complex &r) const { return(Div(this,r)); }
};
//+------------------------------------------------------------------+
//| Cộng |
//+------------------------------------------------------------------+
complex complex::Add(const complex &l,const complex &r) const
{
complex res;
//---
res.re=l.re+r.re;
res.im=l.im+r.im;
//--- Kết quả
return res;
}
//+------------------------------------------------------------------+
//| Trừ |
//+------------------------------------------------------------------+
complex complex::Sub(const complex &l,const complex &r) const
{
complex res;
//---
res.re=l.re-r.re;
res.im=l.im-r.im;
//--- Kết quả
return res;
}
//+------------------------------------------------------------------+
//| Nhân |
//+------------------------------------------------------------------+
complex complex::Mul(const complex &l,const complex &r) const
{
complex res;
//---
res.re=l.re*r.re-l.im*r.im;
res.im=l.re*r.im+l.im*r.re;
//--- Kết quả
return res;
}
//+------------------------------------------------------------------+
//| Chia |
//+------------------------------------------------------------------+
complex complex::Div(const complex &l,const complex &r) const
{
//--- Số phức rỗng
complex res(EMPTY_VALUE,EMPTY_VALUE);
//--- Kiểm tra số không
if(r.re==0 && r.im==0)
{
Print(__FUNCTION__+": số bằng không");
return(res);
}
//--- Biến phụ trợ
double e;
double f;
//--- Chọn biến thể tính toán
if(MathAbs(r.im)<MathAbs(r.re))
{
e = r.im/r.re;
f = r.re+r.im*e;
res.re=(l.re+l.im*e)/f;
res.im=(l.im-l.re*e)/f;
}
else
{
e = r.re/r.im;
f = r.im+r.re*e;
res.re=(l.im+l.re*e)/f;
res.im=(-l.re+l.im*e)/f;
}
//--- Kết quả
return res;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
Hầu hết các phép toán đơn phân cho các lớp có thể được nạp chồng như các hàm thông thường nhận một đối số là đối tượng lớp hoặc con trỏ đến nó. Thêm nạp chồng cho các phép toán đơn phân "-" và "!".
//+------------------------------------------------------------------+
//| Cấu trúc cho các phép toán với số phức |
//+------------------------------------------------------------------+
struct complex
{
double re; // Phần thực
double im; // Phần ảo
...
//--- Toán tử đơn phân
complex operator-() const; // Phủ định đơn phân
bool operator!() const; // Phủ định logic
};
...
//+------------------------------------------------------------------+
//| Nạp chồng toán tử "phủ định đơn phân" |
//+------------------------------------------------------------------+
complex complex::operator-() const
{
complex res;
//---
res.re=-re;
res.im=-im;
//--- Kết quả
return res;
}
//+------------------------------------------------------------------+
//| Nạp chồng toán tử "phủ định logic" |
//+------------------------------------------------------------------+
bool complex::operator!() const
{
//--- Phần thực và phần ảo của số phức có bằng không không?
return (re!=0 && im!=0);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Bây giờ chúng ta có thể kiểm tra giá trị của một số phức có bằng không và lấy giá trị âm:
//+------------------------------------------------------------------+
//| Hàm bắt đầu chương trình script |
//+------------------------------------------------------------------+
void OnStart()
{
//--- Khai báo và khởi tạo biến kiểu complex
complex a(2,4),b(-4,-2);
PrintFormat("a=%.2f+i*%.2f, b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- Chia hai số
complex z=a/b;
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- Số phức mặc định bằng không (trong hàm tạo mặc định re==0 và im==0)
complex zero;
Print("!zero=",!zero);
//--- Gán giá trị âm
zero=-z;
PrintFormat("z=%.2f+i*%.2f, zero=%.2f+i*%.2f",z.re,z.im, zero.re,zero.im);
PrintFormat("-zero=%.2f+i*%.2f",-zero.re,-zero.im);
//--- Kiểm tra lại số không
Print("!zero=",!zero);
//---
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Lưu ý rằng chúng ta không cần nạp chồng toán tử gán "=", vì structures of simple types có thể được sao chép trực tiếp vào nhau. Do đó, giờ đây chúng ta có thể viết mã cho các phép tính liên quan đến số phức theo cách thông thường.
Nạp chồng toán tử chỉ mục cho phép lấy giá trị của các mảng được bao bọc trong một đối tượng một cách đơn giản và quen thuộc, đồng thời góp phần cải thiện khả năng đọc của mã nguồn. Ví dụ, chúng ta cần cung cấp quyền truy cập vào một ký tự trong chuỗi tại vị trí được chỉ định. Chuỗi trong MQL5 là một kiểu riêng string, không phải mảng ký tự, nhưng với sự trợ giúp của phép toán chỉ mục đã nạp chồng, chúng ta có thể cung cấp công việc đơn giản và minh bạch trong lớp CString được tạo:
//+------------------------------------------------------------------+
//| Lớp để truy cập ký tự trong chuỗi như trong mảng ký tự |
//+------------------------------------------------------------------+
class CString
{
string m_string;
public:
CString(string str=NULL):m_string(str) { }
ushort operator[] (int x) { return(StringGetCharacter(m_string,x)); }
};
//+------------------------------------------------------------------+
//| Hàm bắt đầu chương trình script |
//+------------------------------------------------------------------+
void OnStart()
{
//--- Mảng để nhận ký tự từ chuỗi
int x[]={ 19,4,18,19,27,14,15,4,17,0,19,14,17,27,26,28,27,5,14,
17,27,2,11,0,18,18,27,29,30,19,17,8,13,6 };
CString str("abcdefghijklmnopqrstuvwxyz[ ]CS");
string res;
//--- Tạo một cụm từ bằng cách sử dụng ký tự từ biến str
for(int i=0,n=ArraySize(x);i<n;i++)
{
res+=ShortToString(str[x[i]]);
}
//--- Hiển thị kết quả
Print(res);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Một ví dụ khác về nạp chồng toán tử chỉ mục là các phép toán với ma trận. Ma trận đại diện cho một mảng động hai chiều, kích thước mảng không được xác định trước. Do đó, bạn không thể khai báo một mảng dạng array[][]
mà không chỉ định kích thước của chiều thứ hai, rồi sau đó truyền mảng này như một tham số. Một giải pháp khả thi là lớp đặc biệt CMatrix
, chứa một mảng các đối tượng lớp CRow
.
//+------------------------------------------------------------------+
//| Hàm bắt đầu chương trình script |
//+------------------------------------------------------------------+
void OnStart()
{
//--- Phép cộng và nhân ma trận
CMatrix A(3),B(3),C();
//--- Chuẩn bị mảng cho các hàng
double a1[3]={1,2,3}, a2[3]={2,3,1}, a3[3]={3,1,2};
double b1[3]={3,2,1}, b2[3]={1,3,2}, b3[3]={2,1,3};
//--- Điền giá trị vào ma trận
A[0]=a1; A[1]=a2; A[2]=a3;
B[0]=b1; B[1]=b2; B[2]=b3;
//--- Xuất ma trận vào nhật ký Experts
Print("---- Phần tử của ma trận A");
Print(A.String());
Print("---- Phần tử của ma trận B");
Print(B.String());
//--- Cộng ma trận
Print("---- Cộng ma trận A và B");
C=A+B;
//--- Xuất biểu diễn chuỗi đã định dạng
Print(C.String());
//--- Nhân ma trận
Print("---- Nhân ma trận A và B");
C=A*B;
Print(C.String());
//--- Bây giờ chúng ta cho thấy cách lấy giá trị theo kiểu mảng động matrix[i][j]
Print("Xuất giá trị của ma trận C theo từng phần tử");
//--- Duyệt qua các hàng của ma trận - đối tượng CRow - trong vòng lặp
for(int i=0;i<3;i++)
{
string com="| ";
//--- Tạo các hàng từ ma trận cho giá trị
for(int j=0;j<3;j++)
{
//--- Lấy phần tử ma trận theo số hàng và cột
double element=C[i][j]; // [i] - Truy cập vào CRow trong mảng m_rows[],
// [j] - Toán tử chỉ mục đã nạp chồng trong CRow
com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
}
com+="|";
//--- Xuất giá trị của hàng
Print(com);
}
}
//+------------------------------------------------------------------+
//| Lớp "Hàng" |
//+------------------------------------------------------------------+
class CRow
{
private:
double m_array[];
public:
//--- Hàm tạo và hàm hủy
CRow(void) { ArrayResize(m_array,0); }
CRow(const CRow &r) { this=r; }
CRow(const double &array[]);
~CRow(void){};
//--- Số phần tử trong hàng
int Size(void) const { return(ArraySize(m_array));}
//--- Trả về chuỗi với các giá trị
string String(void) const;
//--- Toán tử chỉ mục
double operator[](int i) const { return(m_array[i]); }
//--- Toán tử gán
void operator=(const double &array[]); // Một mảng
void operator=(const CRow & r); // Một đối tượng CRow khác
double operator*(const CRow &o); // Đối tượng CRow cho phép nhân
};
//+------------------------------------------------------------------+
//| Hàm tạo để khởi tạo hàng với mảng |
//+------------------------------------------------------------------+
void CRow::CRow(const double &array[])
{
int size=ArraySize(array);
//--- Nếu mảng không rỗng
if(size>0)
{
ArrayResize(m_array,size);
//--- Điền giá trị
for(int i=0;i<size;i++)
m_array[i]=array[i];
}
//---
}
//+------------------------------------------------------------------+
//| Phép gán cho mảng |
//+------------------------------------------------------------------+
void CRow::operator=(const double &array[])
{
int size=ArraySize(array);
if(size==0) return;
//--- Điền mảng với giá trị
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=array[i];
//---
}
//+------------------------------------------------------------------+
//| Phép gán cho CRow |
//+------------------------------------------------------------------+
void CRow::operator=(const CRow &r)
{
int size=r.Size();
if(size==0) return;
//--- Điền mảng với giá trị
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=r[i];
//---
}
//+------------------------------------------------------------------+
//| Toán tử nhân với hàng khác |
//+------------------------------------------------------------------+
double CRow::operator*(const CRow &o)
{
double res=0;
//--- Kiểm tra
int size=Size();
if(size!=o.Size() || size==0)
{
Print(__FUNCSIG__,": Không thể nhân hai ma trận, kích thước của chúng khác nhau");
return(res);
}
//--- Nhân các mảng theo từng phần tử và cộng các tích
for(int i=0;i<size;i++)
res+=m_array[i]*o[i];
//--- Kết quả
return(res);
}
//+------------------------------------------------------------------+
//| Trả về biểu diễn chuỗi đã định dạng |
//+------------------------------------------------------------------+
string CRow::String(void) const
{
string out="";
//--- Nếu kích thước mảng lớn hơn không
int size=ArraySize(m_array);
//--- Chỉ làm việc với số phần tử mảng khác không
if(size>0)
{
out="{";
for(int i=0;i<size;i++)
{
//--- Thu thập giá trị vào chuỗi
out+=StringFormat(" %G;",m_array[i]);
}
out+=" }";
}
//--- Kết quả
return(out);
}
//+------------------------------------------------------------------+
//| Lớp "Ma trận" |
//+------------------------------------------------------------------+
class CMatrix
{
private:
CRow m_rows[];
public:
//--- Hàm tạo và hàm hủy
CMatrix(void);
CMatrix(int rows) { ArrayResize(m_rows,rows); }
~CMatrix(void){};
//--- Lấy kích thước ma trận
int Rows() const { return(ArraySize(m_rows)); }
int Cols() const { return(Rows()>0? m_rows[0].Size():0); }
//--- Trả về giá trị cột dưới dạng hàng CRow
CRow GetColumnAsRow(const int col_index) const;
//--- Trả về chuỗi với giá trị ma trận
string String(void) const;
//--- Toán tử chỉ mục trả về hàng theo số thứ tự
CRow *operator[](int i) const { return(GetPointer(m_rows[i])); }
//--- Toán tử cộng
CMatrix operator+(const CMatrix &m);
//--- Toán tử nhân
CMatrix operator*(const CMatrix &m);
//--- Toán tử gán
CMatrix *operator=(const CMatrix &m);
};
//+------------------------------------------------------------------+
//| Hàm tạo mặc định, tạo mảng hàng có kích thước bằng không |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
{
//--- Số hàng bằng không trong ma trận
ArrayResize(m_rows,0);
//---
}
//+------------------------------------------------------------------+
//| Trả về giá trị cột dưới dạng CRow |
//+------------------------------------------------------------------+
CRow CMatrix::GetColumnAsRow(const int col_index) const
{
//--- Biến để lấy giá trị từ cột
CRow row();
//--- Số hàng trong ma trận
int rows=Rows();
//--- Nếu số hàng lớn hơn không, thực hiện phép toán
if(rows>0)
{
//--- Mảng để nhận giá trị của cột với chỉ số col_index
double array[];
ArrayResize(array,rows);
//--- Điền mảng
for(int i=0;i<rows;i++)
{
//--- Kiểm tra số cột cho hàng i - nó có thể vượt quá biên của mảng
if(col_index>=this[i].Size())
{
Print(__FUNCSIG__,": Lỗi! Số cột ",col_index,"> kích thước hàng ",i);
break; // row sẽ là đối tượng chưa khởi tạo
}
array[i]=this[i][col_index];
}
//--- Tạo hàng CRow dựa trên giá trị mảng
row=array;
}
//--- Kết quả
return(row);
}
//+------------------------------------------------------------------+
//| Cộng hai ma trận |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
{
//--- Số cột và hàng trong ma trận được truyền vào
int cols=m.Cols();
int rows=m.Rows();
//--- Ma trận để nhận kết quả cộng
CMatrix res(rows);
//--- Kích thước của ma trận phải khớp nhau
if(cols!=Cols() || rows!=Rows())
{
//--- Không thể cộng
Print(__FUNCSIG__,": Không thể cộng hai ma trận, kích thước của chúng khác nhau");
return(res);
}
//--- Mảng phụ trợ
double arr[];
ArrayResize(arr,cols);
//--- Duyệt qua các hàng để cộng
for(int i=0;i<rows;i++)
{
//--- Ghi kết quả cộng các chuỗi ma trận vào mảng
for(int k=0;k<cols;k++)
{
arr[k]=this[i][k]+m[i][k];
}
//--- Đặt mảng vào hàng của ma trận
res[i]=arr;
}
//--- Trả về kết quả cộng của ma trận
return(res);
}
//+------------------------------------------------------------------+
//| Nhân hai ma trận |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
{
//--- Số cột của ma trận đầu tiên, số hàng của ma trận được truyền vào
int cols1=Cols();
int rows2=m.Rows();
int rows1=Rows();
int cols2=m.Cols();
//--- Ma trận để nhận kết quả nhân
CMatrix res(rows1);
//--- Các ma trận phải tương thích
if(cols1!=rows2)
{
//--- Không thể nhân
Print(__FUNCSIG__,": Không thể nhân hai ma trận, định dạng không tương thích "
"- số cột trong thừa số đầu tiên phải bằng số hàng trong thừa số thứ hai");
return(res);
}
//--- Mảng phụ trợ
double arr[];
ArrayResize(arr,cols1);
//--- Điền các hàng trong ma trận nhân
for(int i=0;i<rows1;i++) // Duyệt qua các hàng
{
//--- Đặt lại mảng nhận
ArrayInitialize(arr,0);
//--- Duyệt qua các phần tử trong hàng
for(int k=0;k<cols1;k++)
{
//--- Lấy giá trị của cột k của ma trận m dưới dạng CRow
CRow column=m.GetColumnAsRow(k);
//--- Nhân hai hàng và ghi kết quả nhân vô hướng của vector vào phần tử thứ i
arr[k]=this[i]*column;
}
//--- Đặt mảng arr[] vào hàng thứ i của ma trận
res[i]=arr;
}
//--- Trả về tích của hai ma trận
return(res);
}
//+------------------------------------------------------------------+
//| Phép gán |
//+------------------------------------------------------------------+
CMatrix *CMatrix::operator=(const CMatrix &m)
{
//--- Tìm và đặt số hàng
int rows=m.Rows();
ArrayResize(m_rows,rows);
//--- Điền các hàng của chúng ta với giá trị của các hàng của ma trận được truyền vào
for(int i=0;i<rows;i++) this[i]=m[i];
//---
return(GetPointer(this));
}
//+------------------------------------------------------------------+
//| Biểu diễn chuỗi của ma trận |
//+------------------------------------------------------------------+
string CMatrix::String(void) const
{
string out="";
int rows=Rows();
//--- Tạo chuỗi từng chuỗi
for(int i=0;i<rows;i++)
{
out=out+this[i].String()+"\r\n";
}
//--- Kết quả
return(out);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
Xem thêm