C++筆記 | 第3課 類
文章目錄
類(class)
類是將數據和相應對這些數據的操作函數進行封裝,並設置訪問權限。class
不同於struct
、union
,struct
、union
是純數據類型,但不包括函數(操作)
類是對象的抽象,而對象是類的實例。類是抽象的,不佔用內存,而對象是具體的,佔用存儲空間。類是用於創建對象的藍圖,它是一個定義包括在特定類型的對象中的方法和變量的軟件模板 (模子)。
類可以繼承和派生,並引出了訪問權限控制和調整、保護成員、成員函數名靜態和動態束定(虛函數)等一系列問題。
//類 舉例
class Date
{
public:
//可以把成員函數直接在類說明中定義,這樣成員函數若符合條件,將自動成爲內聯函數
void set(int d, int m, int y)
{ day=d; month=m; year = y; }
int isLeapYear()
{ return(year%4==0 && year%100!=0) || (year%400==0);}
void display()
{ cout <<month<< ":"<<day<< ":"<<year<<endl; }
private:
int day, month, year;
};
構造函數(constructor)和析構函數(destructor)
類建立後往往需要初始化。
構造函數是與類同名的成員函數。
沒有定義構造函數編譯器會自動生成一個構造函數(公有構造函數)。
與類同名帶前綴~
的成員函數被稱爲是析構函數。
構造函數/析構函數不能在程序中顯式地直接被調用,而是在對象創建和撤消時隱式地自動被調用。
構造函數可以多個同名(函數重載),以達到各種初始化的目的。
可以把構造函數改寫成帶缺省參數的函數:如Myclass(int i=0);
。
可以由缺省值構造函數代替轉換構造函數
同名的構造函數必須在類的public
聲明中逐個列出——如果想定義對象而不賦初值,必須再加一個無參構造函數。(用new
申請某一個類的動態對象數組時,在該類中必須能夠匹配到沒有形參的或缺省參數的構造函數)
構造函數/析構函數沒有返回類型。
析構函數執行順序與構造函數執行順序相反,一一對應。
class Circle
{
public:
Circle(double x = 0, double y = 0, double r = 0) : x_(x), y_(y), r_(r) { cout << "Circle(double, double, double) called\n"; }// :x_(x) 的寫法效率高一些
~Circle() { cout << "~Circle() called\n"; }
void xSetValue(double x) { x_ = x; }
void ySetValue(double y) { y_ =y; }
void rSetValue(double r) { r_ = r; }
void display() const
{
cout << "O(" << x_ << ',' << y_ << ")" << '\t' << "radius=" << r_ <<endl;
}
void getArea() const
{
cout << "area=" << 3.14 * r_ * r_ <<endl;
}
private:
double x_;
double y_;
double r_;
};
內聯構造函數
可以通過說明時把構造函數體直接放在類內,使之成爲內聯構造函數。符合內聯函數的限制條件的類的構造函數體或成員函數體放在類內,該函數將自動成爲內聯函數. 目的:提高執行效率.
在類外可以通過在類的成員函數前加inline
,使之成爲內聯構造函數
//類中
public:
Myclass(int i=0);
// 類外函數定義
inline Myclass::Myclass(int i)
{
cout << "Constructor called. "<<i <<endl;
}
複製構造函數
複製(拷貝)構造函數是一種特殊的構造函數,其形參只有一個且是本類對象的引用。
作用是:使用一個已經存在的對象(由複製構造函數的參數指定),去構造並初始化同類的一個新對象。
class Point
{
public:
Point(int x = 0, int y = 0):x_(x),y_(y){}
Point(Point &p):x_(p.x_),y_(p.y_){}
private:
int x_,y_;
}
int main(){
Point A(4,5);
Point B(A);
}
淺複製與深複製
淺複製:複製指針時將地址直接複製,使得不同結構體/對象的指針指向一塊內存
深複製:重新申請一塊內存,再將原來的數據拷貝一份放在新申請的內存裏。
在聲明一個類時,如果不寫複製構造函數,編譯器會自動爲此類生成一個複製構造函數,但這種自動生成的複製構造函數一般都是淺複製,只是把一個對象中的各個成員一一對應賦值給另一個對象的各個成員,包括指針成員的值。如果一個類中含有指針成員,這樣會導致兩個對象的指針成員指向同一塊動態內存,往往會造成析構對象執行析構函數釋放動態內存時,產生致命錯誤。因此含有指針成員的複製構造函數一般需要自己寫.
常成員函數
不修改類中任何成員值的函數,可以定義爲常成員函數。
在函數名()
後加上修飾符const
,可確保此函數中不會有任何修改類的成員的行爲,否則編譯時就會出現錯誤信息。
void display() const
{
cout << "O(" << x_ << ',' << y_ << ")" << '\t' << "radius=" << r_ << endl;
}
mutable關鍵字
在聲明類中數據時使用mutable
關鍵字,使得該數據在常成員函數中可以被修改。
如:使用mutable int count;
在類中創建一個計數器變量。
靜態成員
同一個類中的不同對象之間共享一個數據,可以用來傳遞信息。
private: static int num;
區分:成員,常成員,靜態成員,常靜態成員參看CSDN | C++筆記 | 類數據成員 const static
友元
讓不是本類成員函數的其它函數也能直接訪問本類的私有成員方法是:在類內逐個列出其友元函數,每個函數說明前面加上關鍵字friend
。每一個友元都必須在此類中聲明,這有利於此類對訪問權限的控制。
/*
編程實現:定義Boat與Car兩個類,兩者都有weight成員表示重量,
併爲每個類設計構造函數(可賦初值也可以不賦初值),
設置新值函數Set, 打印成員值函數Print。
再定義兩個類共同的一個友元函數TotalWeight(Boat B, Car C),計算B、C兩者的重量之和。
*/
#include <iostream>
#include <stdlib.h>
using namespace std;
class Boat;
class Car;
void outputTotalWeight(const Boat &boat, const Car &car);
class Boat
{
public:
Boat(double weight = 0) : weight_(weight) {}
~Boat() {}
friend void outputTotalWeight(const Boat &boat, const Car &car);
void weightSetValue(double weight) { weight_ = weight; }
void display() { cout << "boat.weight=" << weight_ << endl; }
private:
double weight_;
};
class Car
{
public:
Car(double weight = 0) : weight_(weight) {}
~Car() {}
friend void outputTotalWeight(const Boat &boat, const Car &car);
void weightSetValue(double weight) { weight_ = weight; }
void display() { cout << "car.weight=" << weight_ << endl; }
private:
double weight_;
};
void outputTotalWeight(const Boat &boat, const Car &car)
{
cout << "The total weight is " << boat.weight_ + car.weight_ << "." << endl;
}
int main()
{
Boat boat(1);
Car car(2);
outputTotalWeight(boat, car);
system("pause");
}
嵌套類
class enclose
{
public:
class inner
{
public:
void innerFunc();
private:
int innerData;
};
void encloseFunc();
private:
int encloseData;
};
類inner
被稱爲類enclose
的嵌套類,受類enclose
的作用域限制,另一個類中也可以嵌套一個同名的inner
類,也可以直接定義一個同名的inner
類,這些inner
類相互之間既無影響也無任何關聯。
現在要想使用inner
類,必須加前綴enclose::
例如,在類外定義inner
的成員函數,必須寫成:void enclose::inner::innerFunc()
而想用inner
類定義對象,必須寫成:enclose::inner innerObject;
類的向前引用
與函數的向前引用類似(有些編譯器不需要)
class B;
class A
{...};//其中使用了類B
class B
{...};
this指針
this
指針只能在成員函數內使用。
類的靜態成員函數中無this
指針。
this
指針不能被更新。
類的組合
類的數據成員是另一個類的對象(在已有的類基礎上抽象實現更復雜的類)
例如類class Triangle
的成員爲三個class Point
的對象
指向類的靜態成員的指針
int *p = & Student::count;
可以直接通過指針訪問公有靜態數據成員
指向靜態成員函數的函數指針
//class中的public靜態成員函數:
static void display(){cout...;}
//main()函數
void(*pFunc)() = Student::display;
pFunc( ); // 相當於執行了display()這一函數
類與對象數組
Student group[3] = {Student(17,甲),Student(18,乙),Student(19,丙)};//定義“三人行必有我師焉”小組
類與動態對象
/*
構建一個球類Ball,其成員爲球心座標(x,y,z)和球半徑r,
並設計構造函數(可以賦初值也可以不賦初值),複製構造函數,析構函數(打印信息,表示其被調用),
設置新值成員函數Set( ), 取球心座標成員函數GetX( )、GetY( )、GetZ( ),
取球半徑成員函數GetR( ),打印成員值成員函數Print(),計算球體積成員函數Volume()。
並用此類分別定義一個長度爲10的靜態對象數組,一個長度爲20的動態對象數組,
每個對象的球心座標(x,y,z)和球半徑r由隨機數rand()產生(球半徑r的值應該爲正數),
打印每個球對象的(x,y,z)、r和體積;
然後分別將對象數組按照其r值從小到大排序,並打印排序後每個對象的(x,y,z)、r和體積;
並計算打印出所有對象的平均(x,y,z)、平均r和平均體積。
(並釋放動態對象數組,注意觀察析構順序與構造順序的差異。)
//隨機數樣例
#include <stdlib.h>
void main()
{
int i, x, y, z, r;
srand(0); // srand爲隨機數序列賦初值,可以隨意給初值
for (i = 0; i < 10; i++)
{
x = rand();
y = rand();
z = rand();
r = abs(rand());
}
}
*/
#include <stdlib.h>
#include <iostream>
using namespace std;
class Ball {
private:
double x, y, z;
double r;
public:
Ball(double xx = 0, double yy = 0, double zz = 0, double rr = 0)
: x(xx), y(yy), z(zz), r(rr) {}
Ball(Ball &b) : x(b.x), y(b.y), z(b.z), r(b.r) {}
~Ball() { cout << "~Ball() called.\n"; }
void xSet(double xx) { x = xx; }
void ySet(double yy) { y = yy; }
void zSet(double zz) { z = zz; }
void rSet(double rr) { r = rr; }
double xValue() { return x; }
double yValue() { return y; }
double zValue() { return z; }
double rValue() { return r; }
double volume() { return 4.0 / 3 * 3.1415926 * r * r * r; }
void display() {
cout << "center point (" << x << "," << y << "," << z << ")" << '\t'
<< "radius = " << r << '\t' << "volume = " << this->volume() << endl;
}
};
/*選擇排序——元素p[a]~p[b]升序排序*/
void selesort(Ball p[], int a, int b) {
int i, j, k;
Ball d; //用來交換的變量
for (i = a; i <= b - 1; i++) //對最後一個元素p[n-1]不用操作
{
k = i; //記錄現在到哪裏了
for (j = i + 1; j <= b; j++)
if (p[j].rValue() < p[k].rValue()) //比較的是半徑
k = j; //從i的下一個開始找,如果有比i小元素(第j個)的就讓k爲j
//本質目的是找出i後面最小的一個
if (k != i) //如果i項不是最小的,那麼換!
{
d = p[i]; //排序的是元素
p[i] = p[k];
p[k] = d;
}
}
}
int main() {
Ball a[10];
Ball *b = new Ball[20];
double sumx = 0, sumy = 0, sumz = 0, sumr = 0, sumv = 0;
// a
sumx = sumy = sumz = sumr = sumv = 0;
for (int i = 0; i <= 10 - 1; i++) {
a[i].xSet(rand() / 100.0);
sumx += a[i].xValue();
a[i].ySet(rand() / 100.0);
sumy += a[i].yValue();
a[i].zSet(rand() / 100.0);
sumz += a[i].zValue();
a[i].rSet(abs(rand()) / 10000.0);
sumr += a[i].rValue();
sumv += a[i].volume();
a[i].display();
}
selesort(a, 0, 9);
for (int i = 0; i <= 10 - 1; i++) a[i].display();
cout << "AVERAGE:" << endl;
cout << "center point (" << sumx << "," << sumy << "," << sumz << ")" << '\t'
<< "radius = " << sumr << '\t' << "volume = " << sumv << endl;
// b
sumx = sumy = sumz = sumr = sumv = 0;
for (int i = 0; i <= 20 - 1; i++) {
b[i].xSet(rand() / 100.0);
sumx += b[i].xValue();
b[i].ySet(rand() / 100.0);
sumy += b[i].yValue();
b[i].zSet(rand() / 100.0);
sumz += b[i].zValue();
b[i].rSet(abs(rand()) / 10000.0);
sumr += b[i].rValue();
sumv += b[i].volume();
b[i].display();
}
selesort(b, 0, 19);
for (int i = 0; i <= 20 - 1; i++) b[i].display();
cout << "AVERAGE:" << endl;
cout << "center point (" << sumx << "," << sumy << "," << sumz << ")" << '\t'
<< "radius = " << sumr << '\t' << "volume = " << sumv << endl;
// END
delete[] b;
system("pause");
}