跳至主要内容

CH11 Class

備註

本文為 2021-Fall 學期旁聽台大資管系孔令傑教授開授的 Programming Design 所記錄的課程筆記。課程內容程式碼可以參閱我的 Github repo: C++ Programming-Design-2021-Fall

Basic concepts

Some drawbacks of struct

  1. 我們可能會忘記inittialize struct內的變數
  2. 我們可能在struct裡new一塊記憶體空間但卻忘記在main function中release
  3. struct內定義的內容可能被其他人已不正確的方式使用
  4. 當struct中的變數與變數之間有所關聯,當其中一者該改時其他變數也要跟著更改,其他人不了解的人可能會不知道要一起更改 ...

In C++, a class can:

  1. 定義member functions,當物件創建或消滅時會自動被呼叫
  2. 隱藏部分member, 不讓他人隨意更改,只開放部分的member給他人使用,以保障別人對member的存取方式。

Instance vs. static variables/functions

In a class, we can define member variables and member functions:

  • Instance variables (default).
  • Static variables.
  • Instance functions (default).
  • Static functions.

Class Visibility

  • Public members can be accessed anywhere.
  • Private members can be accessed only in the class.
  • Protected members will be discussed later in this semester. By default, all members’ visibility level is private.
    By setting visibility, we can hide/open our instance members.
class MyVector 
{
private:
int n;
int* m;
public:
void init(int dim);
void print();
};

int main()
{
MyVector v;
v.init(5); // OK!
delete [] v.m; // compilation error, 不能存取m!
return 0;
}

void MyVector::init(int dim)
{
n = dim;
m = new int[n];
for(int i = 0; i < n; i++)
m[i] = 0;
}
void MyVector::print()
{
cout << "(";
for(int i = 0; i < n - 1; i++)
cout << m[i] << ", ";
cout << m[n-1] << ")\n";
}

Data hiding

當一個member為hiding,我們就可以控制它如何被使用而不是被隨意亂用。
Public member functions are often called interfaces. All others should communicate with the class through interfaces.

  • Many classes with all instance variables private and all instance functions public.

Encapsulation(封裝)

封裝的概念為把多個members包成一包,且可以hiding,來決定該怎麼使用members。簡單來說,我們把data包進一個黑盒子裡面,只提供一個controlled interfaces給main function來存取這些資料

For OOP, there are three main characteristics/functionalities:

  • Encapsulation
  • Inheritance
  • Polymorphism

Instance function overloading

We can overload an instance function with different parameters.

class MyVector { 
private:
int n;
int* m;
public:
void init();
void init(int dim);
void init(int dim, int value);
void print();
};

Constructors and the destructor

constructor

A constructor is an instance function of a class. However, a constructor will be invoked automatically when the object is created. Usually it is used to initialize the object.

  • Constructor的名稱跟class名稱相同,且沒有回傳值(也沒有回傳void),因此宣告時前面不用加回傳值型態,也不用加void直接寫名稱就可以了
  • Constructor可以overloading
  • 沒有傳入parameter的constructor為default constructor
  • Constructor不能被呼第兩次
  • Constructor不能被programmer人為呼叫
  • 若沒有自行定義constructor,compiler會呼叫一個沒有任何動作的constructor

Constructors for MyVector

  • If any member variable needs an initial value when an object is created, you should write a constructor to initialize it.
  • Use constructor overloading to provide flexibility.

::在這邊為class scope,用法(class::name),用來表示右邊的name屬於class的成員

class MyVector
{
private:
int n;
int* m;
public:
MyVector();
MyVector(int dim, int 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;
}

int main()
{
MyVector v1(1);
MyVector v2(3, 8);
v1.print(); // (0)
v2.print(); // (8, 8, 8)
return 0;
}

Destructors

A destructor is invoked right before an object is destroyed.

  • It must be public and have no parameter.
  • To define your own destructor, use ~.
  • Typically we release dynamically allocated space in a destructor.
class MyVector 
{
private:
int n;
int* m; public:
// ...
~MyVector();
};

MyVector:: ~MyVector()
{
delete [] m;
}

When a class has other classes as types of instance variables, when are all the constructors/destructors invoked?
11_1.cpp


Friends and static members

Getters and setters

In most cases, instance variables are private. For them to be accessed, sometimes people implement getters and setters for them.
但若把所有的private member都加上getter, setter就失去private的意義了

class MyVector 
{
private:
int n;
int* m;
public:
// ...
int getN() { return n; }
void setN(int v) { n = v; }
};

friend for functions and classes

To “open” private members, another way is to declare “friends.” One class can allow its friends to access its private members. Its friends can be global functions or other classes.

class MyVector 
{
// ...
friend void test(); // test函數中可存取MyVector的private member
friend class Test; // Test的member function中可存取MyVector的private member
};

void test() {
MyVector v;
v.n = 100; // syntax error if not a friend
cout << v.n; // syntax error if not a friend
}

class Test {
public:
void test(MyVector v) {
v.n = 200; // syntax error if not a friend
cout << v.n; // syntax error if not a friend
}
};

friend 被寫在private或public沒有差別

friend for functions and classes

friend可以用來幫助達到data hiding,因為若所有member都為public的話直接使用struct就好了,當private member應該被特定的class或function存取而非給所有人存取時,我們應該用friend而不是寫getter/setter

Static members

A member variable/function may be an attribute/operation of a class.

  • When the attribute/operation is class-specific rather than object-specific (全部object共用的概念).
  • A class-specific attribute/operation should be identical for all objects.
  • These variables/functions are called static members(靜態).

Static members: an example

We have to initialize a static variable globally. To access static members, use

class name::member name.

int Window::barColor = 0; // default 
int Window::getBarColor()
{
return barColor;
}

void Window::setBarColor(int color)
{
barColor = color;
}

int main()
{
Window w; // not used
cout << Window::getBarColor();
cout << "\n";
Window::setBarColor(1);
return 0;
}
class A 
{
private:
static int count;
public:
A() { A::count++; }
static int getCount()
{ return A::count; }
};

int A::count = 0;
int main()
{
A a1, a2, a3;
cout << A::getCount() << "\n"; // 3
return 0;
}

Object pointers and the copy constructor

Object pointers

A pointer may point to an object, i.e., store the address of an object.

a.print() where a is an object and print() is an instance function.
If we have a pointer ptrA pointing to the object a, we may write *(ptrA).print() to invoke the instance function print(). (*ptrA returns the object a.)

C++ offers the member access operator ->. 直接存取object pointer所指到的member

(*ptrA).print() is equivalent to ptrA->print().

Why object pointers?

  • 當我們想建立object array時每個object的default constructor會自動被呼叫,但我們可能不想用default constructor來initialize object,宣告object array就沒辦法辦到呼叫self-defined constructor
    • 建立object pointer array就不會自動呼叫constructor,當要initialize object時將這些object pointers 指向動態配置的object記憶體空間
  • Passing a pointer into a function can be more efficient than passing the object.
    • A pointer can be much smaller than an object.
    • Copying a pointer is easier than copying an object.

Dynamic object arrays

  • Object pointers allow us to do dynamic memory allocation.
int main() 
{
MyVector* ptrV = new MyVector(5);
ptrV->print();
delete ptrV;
return 0;
}
  • To delay the invocation of constructors, we create an object pointer array.
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(); // (0)
// some delete statements
return 0;
}

Passing object pointers into a function

We may pass pointers rather than objects into this function:

MyVector sum(MyVector* v1, MyVector* v2, MyVector* v3)
{
// assume that their dimensions are identical
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;
}

Passing object references

We may also pass references:

MyVector cenGrav(MyVector& v1, MyVector& v2, MyVector& v3)
{
// assume that their dimensions are identical
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;
}

Copying an object

Creating an object by “copying” an object is a special operation.

  • call-by-value mechanism.
  • assign an object to another object.
  • create an object with another object as the argument of the constructor.

When this happens, the tructor will be invoked.

  • compiler會自動加一個default copy constructor,他做的事情就是把member variable一個一個的複製過去

Copy constructors

我們可以自定義copy constructor In the C++ standard, the parameter must be a constant reference.

class A 
{
private:
int i;
public:
A() { cout << "A"; }
A(const A& a) { cout << "a"; } //copy constructor
};

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
return 0;
}

Copy constructors for MyVector

default copy constructor做的是shallow copy,若object member中沒有array/pointer的話直接用default copy constructor就足夠,但若有array/pointer的話則會造成copied object中的member array/pointer與原object指向同一塊記憶體空間,更動其中一個object中的array/pointer時另一個object也會被更動。

MyVector::MyVector(const MyVector& v) 
{
n = v.n;
m = v.m;
}

Deep copy

To correctly copy a vector (by creating new values), we need to write our own copy constructor. In the self-defined copy constructor, we manually create another dynamic array, set its elements’ values according to the original array, and use m to record its address.

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];
}

Reference