之前有写过一个 Point 的 structure (其实就是二维阵列上的向量)
那我们今天来做一个 multi-dimensional vector
struct MyVector
{
int n;
int* m; // 为了要动态的储存 因为是 n 维向量,你不知道会是多少
void init(int dim);
void print();
};
void MyVector::init(int dim) // dim = dimension
{
n = dim;
m = new int[n];
for (int i = 0; i < n; ++i) // initialization
m[i] = 0;
}
void MyVector::print(
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
int main()
{
MyVector v;
v.init(3);
v.m[0] = 3;
v.print(); // (3, 0, 0)
delete [] v.m;
return 0;
}
这样子写其实已经可以满足我们原本想要的需求,但是还是有几个缺点
所以我们可能希望我们写的 structure 可以
这时候 class 就可以派上用场了,因为它可以:
在使用之前,我们必须先知道:
variable 分为两种
function 分为两种
class MyVector
{
int n;
int* m; // 为了要动态的储存 因为是 n 维向量,你不知道会是多少
void init(int dim);
void print();
};
void MyVector::init(int dim) // dim = dimension
{
n = dim;
m = new int[n];
for (int i = 0; i < n; ++i) // initialization
m[i] = 0;
}
void MyVector::print(
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
int main()
{
MyVector v;
v.init(3);
v.m[0] = 3;
v.print(); // (3, 0, 0)
delete [] v.m;
return 0;
}
这时候你会发现,好像没办法 compile
主要是因为 class 需要设定 visbility (class 与 struct 最大差别)
我们必须在 class 里面设定三种 member
在预设值下,所有的 member 都是 private,所以我们要做打开或是关起来这件事情。
就像这样:
class MyVector
{
private:
int n;
int* m; // 为了要动态的储存 因为是 n 维向量,你不知道会是多少
public:
void init(int dim);
void print();
};
void MyVector::init(int dim) // dim = dimension
{
n = dim;
m = new int[n];
for (int i = 0; i < n; ++i) // initialization
m[i] = 0;
}
void MyVector::print(
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
int main()
{
MyVector v;
v.init(5); // 这时候就可以
v.m[0] = 3;
v.print(); // (3, 0, 0)
delete [] v.m; // 这样不行
return 0;
}
像是在 main function 里面,如果我们要存取 init 这个函数,因为我们已经把它改成 public ,因此如果你宣告他就可行,但是下面的 delete [] v.m
,因为我们把 m 设置成 private ,这时候就不能叫到他了(只能在 class 里面存取他)。
data hiding (Encapsulation 封包)**
简单说,我们把一坨东西封包起来(像是手机或是电视),再告诉使用者要怎麽使用(说明书),你没办法拿他做其他的事情(就像是电视就只能看电视)。
Instance function overloading: (函式多载)
也就是说可以传入多种情况,函式都可以运行,且可做不同结果
class MyVector
{
private:
int n;
int* m; // 为了要动态的储存 因为是 n 维向量,你不知道会是多少
public:
void init();
void init(int dim);
void init(int dim, int value)
};
void MyVector::init()
{
n = 0;
m = nullptr;
}
void MyVector::init(int dim) // dim = dimension
{
init(dim, 0);
}
void MyVector::init(int dim, int value)
{
n = dim;
m = new int[n];
for (int i = 0; i < n; i++)
m[i] = value;
}
除此之外,class 也可以:
还记得我们刚刚想要完成的几件事吗?
我们目前大概只完成了第2 3 项,1 4 则需要下面的方式来达成。
他是一个在 class 中的 function,可以做到: (在物件被建立的时候)
因此他可以达成我们 自动呼叫且初始化的功能。
特性:
Constructor 的名字就跟 class 一样。
class MyVector
{
private:
int n;
int* m;
public:
MyVector(); // Constructor
MyVector(int dim);
MyVector(int dim, int value)
};
且他不会回传任何东西(连 void 都没有)。
可以 overloading
没有 parameter 的 constructor (Myvector
)会被称为 default constructor,如果你没有建立一个 constructor的话,系统会自己帮你建立一个(也就是说 class 里面一定会做一个 constructor),且里面不会做任何事情
所以把 constructor 加到我们刚刚做的程序里:
class MyVector
{
private:
int n;
int* m;
public:
MyVector init();
MyVector inti(int dim, int value = 0); // 如果只传一个 parameter -> value 用 0 来做
void print;
};
MyVector::MyVector()
{
n = 0;
m = nullptr;
}
Myvector::Myvector(int dim, int value)
{
n = dim;
m = new int[n];
for (int i = 0; i < n; i++)
m[i] = value;
}
void Myvector::print()
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
使用:
int main()
{
Myvector v1(1);
Myvector v2(3, 8); // 现在宣告的时候就可以顺便初始化
v1.print();
v2.print();
return 0;
}
这样子我们就可以做到自动的初始化了
剩下的 release 动态配置的空间则会使用到 destructor →
destructors:
Define:
class MyVector
{
private:
int n;
int* m;
public:
~MyVector();
};
MyVector::~Myvector()
{
delete [] m;
}
MyVector::MyVector(int dim, int value)
{
n = dim;
m = new int[n];
for (int i = 0; i < n; i++)
m[i] = value;
}
int main()
{
if (true)
MyVector v1(1);
return 0;
}
如此一来,我们宣告一个 object 的时候,就不会发生 memory leak?
Order?
class A
{
public:
A(){ cout << "A\n";}
~A(){ cout << "a\n";}
};
class B
{
private:
A a;
public:
B(){ cout << "B\n";}
~B(){ cout << "b\n";}
};
int main()
{
B b;
return 0;
}
萤幕会印出:
A
B
b
a
这就是如果有 class 包含在 class 里面的 constructor 和 destructor 呼叫的顺序。
在大多数的情况下,instance variable 会是 private 的,因此为了存取他们,我们必须使用 getter & setter 来对他们做一些事。
class MyVector
{
private:
int n;
int* m;
public:
int getN { return n; }
void setN(int v){ n = v; }
};
我们可以想像,如果今天我们想要开一个权限(像是你手机的密码),你不可能会跟陌生人说吧,所以一定是开给你感情比较好的朋友。
朋友可以是:
像是下面这个 class
class MyVector
{
//....
friend void test();
friend class Test;
};
我们可以知道 friend 的几个特性: (以上面为例):
所以我们可以在 test 里面使用 n
void test{
MyVector v;
v.n = 100; // 因为是朋友!
cout << v.n
}
在 Test 里面也可以使用:
class Test{
public:
void test(MyVector){
v.n = 203;
cout << v.n;
}
};
在 class 里面,每一个 object 都拥有自己的 instance variables 和 functions。
(这时候就会称这些 variables & functions 是 object-specific)
但是反过来,member variable 或是 function 可以是一个 class 的属性( attribute) 或是 operation。
(这个时候这些 variable 和 function 就会被称为 class specific)
他们就会被称为 static members (静态成员: 保持不变)
举个例子: 像是 windows 中的视窗,每一个视窗都是一个 object。这些视窗都有自己的名称、自己专属的大小 这些特性就会被称为 object-specific attribute。而每一个视窗,都会有一些相同的东西,像是每个视窗都会有 title bar、这些 bar 都有一样的颜色、都会有 — [ ] X (缩小 / 全萤幕/ 关闭) 等按钮,这些他们拥有的相同属性,就可以称为一个 class-specific attribute。
class Window
{
private:
int width;
int height;
int locationX;
int locationY;
int status; // 0: min, 1:usual, 2: max
static int barColor; // 0: gray....
//.....
public:
static int getBarColor();
static void setBarColor(int color);
//....
};
另外,我们必须在 global 的环境下 初始化一个 static variable(因为全部都要用),像是这样:
int Window::barColor = 0; // default
int Window::getBarColor()
{
return barColor;
}
void Window::setBarColor(int color)
{
barColor = color;
}
那如果我们要在 int main() 存取 static member要用
class name::member name
如果是要存取 instance 的 member:
object name.member name
使用 static member:
int main()
{
Window w;
cout << Window::getBarColor();
cout << "\n";
Window::setBarColor(1);
return 0;
}
所以现在我们就有了四种 member type:
他们俩者的关系是这样:
w.getBarColor()
这样子透过 object 存取 static member,但是 非常不建议 这麽使用,最好用 class 呼叫他
如果你今天要写一个每一个 object 都要使用的特性或是 function,这时候就要使用 static member → 可以维持一致性,
不要 object 来呼叫 static member
尽量用 class 来呼叫:
int Window::getBarColor()
{
return Window::barColor;
}
instance :
找出有几个人被 constuct
class A
{
private:
static int count;
public:
A() {A::count++; } // 因为被大家共用,所以只要有人被建立的时候,就++
static int getCount(){ return A::count; }
};
int A::count = 0;
int main(int argc, char const *argv[])
{
A a1, a2, a3;
cout << A::getCount() << "n"; // 3
return 0;
}
instance:
找出有几个人目前还活着(alive)
class A
{
private:
static int count;
public:
A() {A::count++; }
~A() {A::count--; }
static int getCount(){ return A::count; }
};
int A::count = 0;
int main(int argc, char const *argv[])
{
if (true)
A a1, a2, a3;
cout << A::getCount() << "n"; // 0
return 0;
}
另外,在中间的那一行就是前面所说的,static variable 要在 global 初始化。
因为 class 是一种我们自定义的 data type
且 pointer 可以指向任何一种 data type
instnace:
int main()
{
MyVector v(5);
MyVector* ptrv = &v; // object pointer
return 0;
}
object pointer 的使用?
因为 pointer 就是存取 object (例如说 a ) 的位置(其实就是代表 a 的意思),所以我们可以用 *ptrA
去存取 a 中的 function,像是这样: (*ptrA).print()
但是有另一个更简单存取的方式,就是直接用 ->
所以上面的存取就可以写成 ptrA ->print();
WHY OBJECT POINTERS?
当我们要做一个 object array 的时候,可以用 pointer 来延迟 constructor 的呼叫,就会害我们失去初始化的机会。
像是这个:
int main()
{
MyVector v[3]; // an object array
v[0].print(); // run-time error 因为 m 是 nullptr
return 0;
}
如果我们先呼叫了v[3],会因为我们没办法初始化,会导致 array 里面n = 0, m = nullptr。
所以可以用 Dynamic object arrays 这个方法来解决:
int main()
{
MyVector* ptrV = new MyVector(5); //呼叫 constructor
ptrV->print();
delete ptrV;
return 0;
}
❌ 因为我们宣告了 5 个 object,可是只叫了一个 pointer
int main()
{
MyVector* ptrV = new MyVector[5]; // 宣告动态 array
ptrV[0].print(); // run-time error
delete [] ptrV;
return 0;
}
✅ 我们宣告了 5 个 pointer,每一次都 create一个 object,再去做 constructor (中间的 for loop),就可以完成。
int main()
{
MyVector* ptrArray[5]; //no constructor invocation
for(int i = 0; i < 5; i++)
ptrArray[i] = new MyVector(i + 1); // constructor
ptrArray[0]->print();
// some delete statements
return 0;
}
Passing object into a function
如果我们今天要写一个程序,让我们把每一个 vector 的质相加
MyVector sum(MyVector v1, MyVector v2, MyVector v3 )
{
// assume that their dimensions are identical
int n = v1.getN();
int* sov = new int [n]; //sov = sum of vectors
for (int i = 0; i < n; ++i)
sov[i] = v1.getM(i) + v2.getM(i) + v3.getM(i); // 把他们相加
MyVector sumOfVec(n, sov); // constructor -> 後面要写
return sumOfVec;
}
int MyVector::getN() { return n; }
int MyVector::getM(int i) { return m[i]; }
MyVector::MyVector(int d, int v[]) // sov 是一个 array
{
n = d;
for (int i = 0; i < n; ++i)
m[i] = v[i];
}
在这个程序里面,有 4 个 MyVector object 被创造,但是如果有更多的 object ,这时候就有点麻烦。
所以可以改成用 pointer 来写:
MyVector sum(MyVector* v1, MyVector* v2, MyVector* v3)
{
int n = v1->getN();
int* sov = new int [n];
for (int i = 0; i < n; ++i)
sov[i] = v1->getM[i] + v2->getM[i] + v3->getM[i];
MyVector sumOfVec(n, sov);
return sumOfVec;
}
如此一来,我们就只需要创造一个 object 就好了。
但是很有可能因为 object 不够多,所以用 pointer 的时候花的时间会更多,这样就不太划算,因此建议 object 比较少的时候可以直接使用 object 来比较快。
Passing object references
MyVector sum(const MyVector& v1,const MyVector& v2,const MyVector& v3)
{
int n = v1->getN();
int* sov = new int [n];
for (int i = 0; i < n; ++i)
sov[i] = v1.getM[i] + v2.getM[i] + v3.getM[i];
MyVector sumOfVec(n, sov);
return sumOfVec;
}
同样的,我们也可以传入 reference ,而下面就把 references 当作一般变数,直接用 .getM[i]
就可以了。
而在 argument 的部分,因为我们不想要这些 reference 被更改,所以我们要用 const
来保护他们不被更改。
很多时候我们写出来的程序,不是为了要做出甚麽功能,而是要避免一些事情的发生。
class A
{
private:
int i;
public:
A() { cout << "A"; }
};
void f(A a1, A a2, A a3)
{
A a4;
}
int main()
{
A a1, a2, a3; // AAA
cout << "\n===\n";
f(a1, a2, a3); // A
return 0;
}
这段程序会传出:
AAA
===
A
为什麽当我们呼叫 f 的时候,只会传出 A 而已? 而不是传出 4 个 A(4 次 constructor)
In general, when we pass by value, a local variable will be created.
int main()
{
A a1, a2, a3; // AAA
cout << "\n===\n";
A a4 = a1; // nothing
return 0;
}
那如果我们把程序改成这样,就会甚麽东西都不会传出。
这是因为:
Creating an object by "copying" and object is a special operation. It happens:
when we pass an object into a function using call by value mechanism.
f(a1, a2, a3);
when we assign an object to another object.
A a4 = a1;
when we create an object with another object as the argument of the constructor.
A a5(a1);
COPY CONSTRUCTOR
这一个机制会被称为 "copy constructor" (也就是用 copy object 的方式来建立 object)(这也是一个 default copy constructor),他甚麽事情都不会做。我们也可以手动的设定他要做甚麽事情:
class A
{
private:
int i;
public:
A() { cout << "A"; }
A( const A& a) { cout << "a"; }
};
void f(A a1, A a2, A a3)
{
A a4;
}
int main()
{
A a1, a2, a3; // AAA
cout << "\n===\n; // ===
f(a1, a2, a3); // aaaA
A a4(a1); // a
A a4 = a1; // a
return 0;
}
像是在 f(a1, a2, a3);
的时候,就会因为 copy constructor 被启动,所以就会印出 a
。而像下面的 A a4(a1);
,也是会印出 a
; A a4 = a1;
的结果也是相同的。
我们自己也可以手动的写出 copy constructor,大概会长:
MyVector::MyVector(const MyVector& v)
{
n = v.n;
m = v.m;
}
Shallow copy :
这个就是一般的 default copy constructor 会做的事情(前提: member中没有 array 或是 pointer)。
那如果有 array 或是 pointer,因为 m 这个指标是指向一块空间,但是如果是一个 array 的话,就会产生不同的指标指向同样的空间的情况。
int main()
{
MyVector v1(5, 1);
MyVector v2(v1); //??
}
这时候记忆体中会变成:
这时候如果我们改了 v1 的时候,v2 都会一起被改。
Deep copy:
为了避免我们上述讲的情形,我们就需要在 copy 的时候把一个一个 element 改掉。
首先我们要手动的宣告一个 dynamic array, 让 m 储存他的地址。最後在把 v.m[i]
的值取出来。
MyVector::MyVector(const MyVector& v)
{
n = v.n;
m = new int[n]; // deep copy
for(int i = 0; i < n; i++)
m[i] = v.m[i];
}
以前学 python 的时候碰到 class 的机率很少,几乎没写过。
现在终於知道在干嘛了。
>>: [13th][Day25] kubernetes & docker
前言 今天要来讨论一些更进阶的程序写法,比较偏向效能方面的优化,怎麽写可以让效能变好、扩充容易,而不...
题目 An array is monotonic if it is either monotone ...
前言 在this Or That?中提到了许多对於this的误解,并且也对於这些误解做了一些解释,我...
tags: 铁人赛 SDK AWS Python 前言 杨过先背起全真教心法之後,才去练古墓派招式。...
Toasts 有时又叫做 Snackbar 用来提醒用户一些小事的元件 也有几种不同的使用情境,在 ...