01. 动态数组的实现
tvector.h
#pragma once
#include <iostream>
// 编写类模板的时候,类的声明和函数的定义必须写在同一个文件中
// 命名空间: 解决作用域的二义性问题
namespace S
{
// 动态数组类: 内部维护了一个可以动态增长的[数组]
template <class T>
class tvector
{
private:
// 一个指针,指向了保存数据的空间
T* m_Data = nullptr;
// 数组当前能够保存的最大个数
int m_MaxCount = 0;
// 数组当前已经保存了的元素个数
int m_CurrentCount = 0;
private:
// 提供一个新的大小,用于重新分配空间
bool renew(int size);
public:
// 构造函数:参数是想要申请的缓冲区大小
tvector(int size = 10);
// 析构函数:释放申请的堆空间
~tvector();
// 在数组的指定位置添加一个元素
bool insert(int index, T item);
// 在数组的指定位置删除一个元素,并返回删除的元素
T erase(int index);
// 设置元素的值
void set(int index, T item) { m_Data[index] = item; }
// 获取元素的值
T get(int index) { return m_Data[index]; }
// 获取当前的元素个数
int length() { return m_CurrentCount; }
};
// 构造函数的实现
template <class T>
tvector<T>::tvector(int size)
{
// 默认没有保存东西
this->m_CurrentCount = 0;
// 初始化最大能存储的个数
this->m_MaxCount = size;
// 堆空间的申请和指向,最好添加判断
this->m_Data = new T[size];
}
// 析构函数:释放申请的堆空间
template <class T>
tvector<T>::~tvector()
{
// 如果指针不为空,就释放
if (this->m_Data != nullptr)
delete[] this->m_Data;
}
// 提供一个新的大小,用于重新分配空间
template <class T>
bool tvector<T>::renew(int size)
{
// 1. 申请出一块 size 大小的空间
T* Temp = new T[size];
// 2. 判断空间是否申请成功
if (!Temp) return false;
// 3. 将旧空间的内容拷贝到新空间
memcpy(Temp, m_Data, m_MaxCount * sizeof(T));
// 4. 释放旧的空间,并重新指向
delete[] m_Data;
m_Data = Temp;
// 5. 重新设置大小并返回 true
m_MaxCount = size; return true;
}
// 在数组的指定位置添加一个元素
template <class T>
bool tvector<T>::insert(int index, T item)
{
// 1. 判断添加数据的位置是否有效
if (index < 0 || index > m_CurrentCount)
{
// 1 2 3 4 5 6 7
// ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
return false;
}
// 2. 判断当前数组的最大容量是否足够
if (m_CurrentCount == m_MaxCount)
{
// 2.1 如果不够就需要重新申请更大的空间
if (!renew(m_MaxCount + 5))
return false;
}
// 1 2 3 4 5 6 7
// 1 2 X 3 4 5 6 7
// 3. 通过循环,从后向前依次移动数组元素
for (int i = m_CurrentCount - 1; i >= index; --i)
m_Data[i + 1] = m_Data[i];
// 4. 将想要添加的数据直接进行赋值
m_Data[index] = item;
// 5. 增加当前元素个数并返回 true
m_CurrentCount++; return true;
}
// 在数组的指定位置删除一个元素,并返回删除的元素
template <class T>
T tvector<T>::erase(int index)
{
// 1. 判断传入的位置是否合理
// 0 1 2 3 4 5
// 0 2 3 4 5
// 2. 保存被删除的元素
T data = m_Data[index];
// 3. 通过循环从前往后移动元素
for (int i = index; i < m_CurrentCount - 1; ++i)
m_Data[i] = m_Data[i + 1];
// 4. 减少当前的元素个数并返回被删元素
--m_CurrentCount; return data;
}
}
以上是类模板,以下是实现动态数组中保存int型数据的程序
MyVector.h
// 防止头文件的重复包含,当前文件不管被包含多少次,
// 最终只会被编译一次,是由 VS 提供的,不跨平台。
#pragma once
// 命名空间: 解决作用域的二义性问题
namespace S
{
// 动态数组类: 内部维护了一个可以动态增长的[数组]
class vector
{
private:
// 一个指针,指向了保存数据的空间
int* m_Data = nullptr;
// 数组当前能够保存的最大个数
int m_MaxCount = 0;
// 数组当前已经保存了的元素个数
int m_CurrentCount = 0;
private:
// 提供一个新的大小,用于重新分配空间
bool renew(int size);
public:
// 构造函数:参数是想要申请的缓冲区大小
vector(int size = 10);
// 析构函数:释放申请的堆空间
~vector();
// 在数组的指定位置添加一个元素
bool insert(int index, int item);
// 在数组的指定位置删除一个元素,并返回删除的元素
int erase(int index);
// 设置元素的值
void set(int index, int item) { m_Data[index] = item; }
// 获取元素的值
int get(int index) { return m_Data[index]; }
// 获取当前的元素个数
int length() { return m_CurrentCount; }
};
}
MyVector.cpp
#include "MyVector.h"
#include <iostream>
// 所有成员函数的实现也必须在同一个命名空间中
namespace S
{
// 构造函数的实现
vector::vector(int size)
{
// 默认没有保存东西
this->m_CurrentCount = 0;
// 初始化最大能存储的个数
this->m_MaxCount = size;
// 堆空间的申请和指向,最好添加判断
this->m_Data = new int[size];
}
// 析构函数:释放申请的堆空间
vector::~vector()
{
// 如果指针不为空,就释放
if (this->m_Data != nullptr)
delete[] this->m_Data;
}
// 提供一个新的大小,用于重新分配空间
bool vector::renew(int size)
{
// 1. 申请出一块 size 大小的空间
int* Temp = new int[size];
// 2. 判断空间是否申请成功
if (!Temp) return false;
// 3. 将旧空间的内容拷贝到新空间
memcpy(Temp, m_Data, m_MaxCount * sizeof(int));
// 4. 释放旧的空间,并重新指向
delete[] m_Data;
m_Data = Temp;
// 5. 重新设置大小并返回 true
m_MaxCount = size; return true;
}
// 在数组的指定位置添加一个元素
bool vector::insert(int index, int item)
{
// 1. 判断添加数据的位置是否有效
if (index < 0 || index > m_CurrentCount)
{
// 1 2 3 4 5 6 7
// ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
return false;
}
// 2. 判断当前数组的最大容量是否足够
if (m_CurrentCount == m_MaxCount)
{
// 2.1 如果不够就需要重新申请更大的空间
if (!renew(m_MaxCount + 5))
return false;
}
// 1 2 3 4 5 6 7
// 1 2 X 3 4 5 6 7
// 3. 通过循环,从后向前依次移动数组元素
for (int i = m_CurrentCount - 1; i >= index; --i)
m_Data[i + 1] = m_Data[i];
// 4. 将想要添加的数据直接进行赋值
m_Data[index] = item;
// 5. 增加当前元素个数并返回 true
m_CurrentCount++; return true;
}
// 在数组的指定位置删除一个元素,并返回删除的元素
int vector::erase(int index)
{
// 1. 判断传入的位置是否合理
// 0 1 2 3 4 5
// 0 2 3 4 5
// 2. 保存被删除的元素
int data = m_Data[index];
// 3. 通过循环从前往后移动元素
for (int i = index; i < m_CurrentCount - 1; ++i)
m_Data[i] = m_Data[i + 1];
// 4. 减少当前的元素个数并返回被删元素
--m_CurrentCount; return data;
}
}
main.cpp
#include <iostream>
#include "tvector.h"
#include "MyVector.h"
using namespace std;
int main()
{
S::tvector<double> vec(1); // 分配一字节大小
for (int i = 10; i >= 0; --i)
vec.insert(0, i*1.1);
for (int i = 0; i < vec.length(); ++i)
cout << vec.get(i) << " ";
printf("\n");
vec.erase(4);
for (int i = 0; i < vec.length(); ++i)
cout << vec.get(i) << " ";
printf("\n");
return 0;
}
02. vector 容器的使用
// 1. 包含容器对应的头文件,并使用名称空间
#include <vector>
using namespace std;
#include <iostream>
int main()
{
// 2. 因为是一个模板,所以需要在实例化时指定类型
vector<int> vec1(10); // 初始化 10 个元素大小
vector<int> vec2(10, 2); // 初始化 10 个 2
vector<int> vec3 = { 1, 2, 3, 4, 5 }; // 使用后面的值进行初始化
vector<int> vec4(vec3); // 拷贝构造函数
// 3. 增加容器的元素
vec3.push_back(10); // 在末尾添加元素
vec3.insert(vec3.begin(), 1); // 在开头位置添加元素
// 4. 删除元素
vec3.pop_back(); // 删除结尾的元素
vec3.erase(vec3.begin() + 3); // 删除第二个元素
// 5. 如何查看和修改元素
vec3[0] = 100; // 使用下标修改
vec3.front() = 200; // 修改首元素的值
vec3.back() = 300; // 修改尾元素的值
vec3.at(1) = 400; // 获取对应位置的元素,但是会判断是否越界
// 6. 遍历容器的几种方式
// 1. 常规遍历,用下标
// 这个方式一般只用于显示和修改元素数据(不增加或删除)
for (int i = 0; i < vec3.size(); ++i)
printf("%d ", vec3[i]);
printf("\n");
// 2. 常规遍历,用迭代器 auto 自动类型识别
for (auto i = vec3.begin(); i != vec3.end(); ++i)
printf("%d ", *i);
printf("\n");
// 3. 使用列表循环,将 vec3 的所有元素依次赋值给 i
// 不添加引用就是值拷贝,不能修改原来的数据
// 这个方式一般只用于显示和修改元素数据(不增加或删除)
for (auto & i : vec3)
printf("%d ", i);
printf("\n");
// 4. 使用 while 的方式进行遍历
auto begin = vec3.begin();
while (begin != vec3.end())
{
printf("%d ", *begin);
++begin;
}
printf("\n");
// 在循环的过程中,不能随意的增加和删除元素
// 会导致迭代器无效,使程序崩溃
for (auto i = vec3.begin(); i != vec3.end(); ++i)
{
// 删除时需要接收返回的新迭代器
i = vec3.insert(vec3.begin(), 10);
}
// 1 2 3 4 5 6 7
// b e
// end 迭代器不能用于解引用
return 0;
}
03. 在控制台打印一个点
// 1. 添加 windows.h 头文件使用其中的函数
#include <windows.h>
#include <iostream>
// 在控制台中,高度和宽度的比是 2:1,所以设置控制台光标位置的
// 时候,需要将宽度*2。在使用数组处理逻辑的时候, x 代表的是
// 行数,y代表的是列数,但是在控制台中,x代表的是列数,y代表
// 的是行数。在具体的使用过程中,为了让数组和控制台的显示对应,
// 通常需要调换 x 和 y 的位置。
/* 数组 控制台
0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 * * * * * 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0
*/
// 在指定的坐标使用指定的颜色打印指定的字符
void WriteChar(short x, short y, WORD color, const char* s)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 通过函数的调用实现移动光标到指定位置
// 1: 想要修改的是哪一个输出缓冲区的光标位置
// 2: 想要将光标移动到控制台的哪一个位置
SetConsoleCursorPosition(OutputHandle, { y * 2, x });
// 为接下来需要打印的文字提供颜色,可以使用 RGB 搭配出一些简单
// 的颜色,不要使用较暗的颜色,应该选用亮色(黄色、绿色、白的)
SetConsoleTextAttribute(OutputHandle, color);
// 打印指定的图形
printf(s);
}
// 隐藏光标
void HindCursor(bool Hide = true)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 定义结构体,用于保存光标的信息,参数二表示是否显示
CONSOLE_CURSOR_INFO Cursor = { 1, !Hide };
// 调用函数设置光标的属性
SetConsoleCursorInfo(OutputHandle, &Cursor);
}
int main()
{
HindCursor(false);
WriteChar(1, 1, 0xF, "■");
WriteChar(1, 2, 0x1 | 8, "□");
WriteChar(1, 3, 0x2 | 8, "●");
WriteChar(1, 4, 0x4 | 8, "●");
return 0;
}
04. 打印一个会自动移动的点
// 1. 添加 windows.h 头文件使用其中的函数
#include <windows.h>
#include <iostream>
// 在控制台中,高度和宽度的比是 2:1,所以设置控制台光标位置的
// 时候,需要将宽度*2。在使用数组处理逻辑的时候, x 代表的是
// 行数,y代表的是列数,但是在控制台中,x代表的是列数,y代表
// 的是行数。在具体的使用过程中,为了让数组和控制台的显示对应,
// 通常需要调换 x 和 y 的位置。
/* 数组 控制台
0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 * * * * * 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0
*/
// 在指定的坐标使用指定的颜色打印指定的字符
void WriteChar(short x, short y, WORD color, const char* s)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 通过函数的调用实现移动光标到指定位置
// 1: 想要修改的是哪一个输出缓冲区的光标位置
// 2: 想要将光标移动到控制台的哪一个位置
SetConsoleCursorPosition(OutputHandle, { y * 2, x });
// 为接下来需要打印的文字提供颜色,可以使用 RGB 搭配出一些简单
// 的颜色,不要使用较暗的颜色,应该选用亮色(黄色、绿色、白的)
SetConsoleTextAttribute(OutputHandle, color);
// 打印指定的图形
printf(s);
}
// 隐藏光标
void HindCursor(bool Hide = true)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 定义结构体,用于保存光标的信息,参数二表示是否显示
CONSOLE_CURSOR_INFO Cursor = { 1, !Hide };
// 调用函数设置光标的属性
SetConsoleCursorInfo(OutputHandle, &Cursor);
}
int main()
{
HindCursor(false);
for (int i = 0; i < 10; ++i)
{
// 擦除之前的痕迹,绘制移动后的图形
WriteChar(1, i - 1, 0xF, " ");
// 在第二行移动,方向为右边
WriteChar(1, i, 0xF, "■");
// 通过函数暂停程序达到停顿的效果, 0.1s
Sleep(100);
}
return 0;
}
05. 一个受控的点
// 1. 添加 windows.h 头文件使用其中的函数
#include <windows.h>
#include <iostream>
#include <conio.h>
// 在控制台中,高度和宽度的比是 2:1,所以设置控制台光标位置的
// 时候,需要将宽度*2。在使用数组处理逻辑的时候, x 代表的是
// 行数,y代表的是列数,但是在控制台中,x代表的是列数,y代表
// 的是行数。在具体的使用过程中,为了让数组和控制台的显示对应,
// 通常需要调换 x 和 y 的位置。
/* 数组 控制台
0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 * * * * * 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0
*/
// 在指定的坐标使用指定的颜色打印指定的字符
void WriteChar(short x, short y, WORD color, const char* s)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 通过函数的调用实现移动光标到指定位置
// 1: 想要修改的是哪一个输出缓冲区的光标位置
// 2: 想要将光标移动到控制台的哪一个位置
SetConsoleCursorPosition(OutputHandle, { y * 2, x });
// 为接下来需要打印的文字提供颜色,可以使用 RGB 搭配出一些简单
// 的颜色,不要使用较暗的颜色,应该选用亮色(黄色、绿色、白的)
SetConsoleTextAttribute(OutputHandle, color);
// 打印指定的图形
printf(s);
}
// 隐藏光标
void HindCursor(bool Hide = true)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 定义结构体,用于保存光标的信息,参数二表示是否显示
CONSOLE_CURSOR_INFO Cursor = { 1, !Hide };
// 调用函数设置光标的属性
SetConsoleCursorInfo(OutputHandle, &Cursor);
}
// 标识方向
#define 上 0
#define 下 1
#define 左 2
#define 右 3
// 点要保存自己的坐标,所以需要定义变量,点的移动是
// 根据用户设置的方向决定的,所以还需要保存方向。
COORD Coord = { 1, 1 };
int Dir = 下; // 方向默认为右
// 获取用户的输入,修改方向
void GetDir()
{
// 游戏本身不能被阻塞,所以使用过的函数不能是阻塞函数
if (_kbhit())
{
// 这是一个非阻塞函数,一旦按下任何按键,返回 true
// _getch 是无回显输入,不能使用带回显的函数
switch (_getch())
{
case 'w': Dir = 上; break;
case 's': Dir = 下; break;
case 'a': Dir = 左; break;
case 'd': Dir = 右; break;
}
}
}
int main()
{
HindCursor(false);
while (true)
{
// 获取用户的输入
GetDir();
// 清除之前的内容
WriteChar(Coord.X, Coord.Y, 0xF, " ");
// 根据点的方向移动点的位置
switch (Dir)
{
case 上: Coord.X--; break;
case 下: Coord.X++; break;
case 左: Coord.Y--; break;
case 右: Coord.Y++; break;
}
// 回值移动之后的内容
WriteChar(Coord.X, Coord.Y, 0xF, "■");
// 通过函数暂停程序达到停顿的效果, 0.1s
Sleep(100);
}
return 0;
}
06. 控制台的鼠标事件
// 1. 添加 windows.h 头文件使用其中的函数
#include <windows.h>
#include <iostream>
// 在控制台中,高度和宽度的比是 2:1,所以设置控制台光标位置的
// 时候,需要将宽度*2。在使用数组处理逻辑的时候, x 代表的是
// 行数,y代表的是列数,但是在控制台中,x代表的是列数,y代表
// 的是行数。在具体的使用过程中,为了让数组和控制台的显示对应,
// 通常需要调换 x 和 y 的位置。
/* 数组 控制台
0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 * * * * * 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 * 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0
*/
// 在指定的坐标使用指定的颜色打印指定的字符
void WriteChar(short x, short y, WORD color, const char* s)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 通过函数的调用实现移动光标到指定位置
// 1: 想要修改的是哪一个输出缓冲区的光标位置
// 2: 想要将光标移动到控制台的哪一个位置
SetConsoleCursorPosition(OutputHandle, { y * 2, x });
// 为接下来需要打印的文字提供颜色,可以使用 RGB 搭配出一些简单
// 的颜色,不要使用较暗的颜色,应该选用亮色(黄色、绿色、白的)
SetConsoleTextAttribute(OutputHandle, color);
// 打印指定的图形
printf(s);
}
// 隐藏光标
void HindCursor(bool Hide = true)
{
// 句柄: 在windwos中句柄用于标识某一种东西,这个例子中,句柄
// 被用于标识当前控制台的输出缓冲区,用于显示具体的内同
HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// 定义结构体,用于保存光标的信息,参数二表示是否显示
CONSOLE_CURSOR_INFO Cursor = { 1, !Hide };
// 调用函数设置光标的属性
SetConsoleCursorInfo(OutputHandle, &Cursor);
}
int main()
{
HindCursor(); // 隐藏光标
// 获取控制台输入缓冲区的句柄
HANDLE InputHandle = GetStdHandle(STD_INPUT_HANDLE);
// 开启鼠标事件的接受: ENABLE_MOUSE_INPUT
SetConsoleMode(InputHandle, ENABLE_MOUSE_INPUT);
DWORD Number = 0;
// 用于保存读取到的事件
INPUT_RECORD InputRecord = { 0 };
// 编写一个循环,不断的获取当前鼠标的事件
// - 1: 想要读取哪一个控制台的输入事件
// - 2: 一个结构体,保存了获取到的输入事件
// - 3: 数量,表示提供的结构体有几个(1)
// - 4: 当前读取到了几个事件
while (ReadConsoleInput(InputHandle, &InputRecord, 1, &Number))
{
// 接受的事件不止有鼠标类型,所以需要进行判断
if (InputRecord.EventType == MOUSE_EVENT)
{
// 获取到鼠标的坐标
COORD MouseCoord = InputRecord.Event.MouseEvent.dwMousePosition;
// 为所有的鼠标事件进行输出坐标的处理
CHAR Buffer[0x100] = { 0 };
// printf 将格式化后的内容填充控制台输出中
// 将格式化后的内容填充到提供的缓冲区中
sprintf_s(Buffer, "(%3d, %3d)", MouseCoord.X, MouseCoord.Y);
// 始终打印在屏幕的 0,0位置
WriteChar(0, 0, 0xF, Buffer);
// 通常来讲,鼠标的绘制应该直接反映在与绘图相关的数组中,
// 数组在游戏中扮演的地图,绘制的图形可以是障碍物或者事物。
// 鼠标事件有非常多,我们可以单独响应某一个事件
if (InputRecord.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
{
// 鼠标的左键按下了,就执行这里
WriteChar(MouseCoord.Y, MouseCoord.X / 2, 0xF, "●");
}
else if (InputRecord.Event.MouseEvent.dwButtonState == RIGHTMOST_BUTTON_PRESSED)
{
// 鼠标的左键按下了,就执行这里
WriteChar(MouseCoord.Y, MouseCoord.X / 2, 0xF, " ");
}
}
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// 如果控制台无法接收到鼠标事件,就进行设置: 控制台标题右键 -> 属性 ->
// 选项 -> 取消快速编辑模式的勾选。
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return 0;
}