類
- 前言
- 1.類的宣告和定義
- 2.this指標
- 2.1 vs和gcc下的this指標
- 3.類的大小
- 記憶體對齊
- 4.C和C++在命名物件呼叫函式的區別
- 5.建構式
- 6.運算子多載
- 6.1運算子多載入門
- 6.2賦值多載和拷貝構造的小比較
- 7.建構式,解構式,賦值多載比較
- 總結
前言
類猶如一張圖紙一樣,類實體化出物件就是拿這張圖紙建房子,
C++中struct,class都是類的關鍵字,他們的區別就是struct的默認訪問限定符不同,struct是public(公有),class是private(私有),
如果一個類中在沒有寫任何東西時都會自動生成下面6個默認成員函式
1.建構式(初始化)
2.解構式(清理)
3.拷貝構造(拷貝賦值)
4.賦值多載(拷貝賦值)
5.普通物件取地址多載(取地址多載)
6.const物件取地址多載(取地址多載)
1.類的宣告和定義
對于函式而言:函式的實作就是定義;
對于成員變數:宣告就是告訴變數的型別,名稱,但是沒有分配空間,真正分配到空間的時候才是成員變數的定義,即類的實體化
//這里是Stack.h
#include<iostream>
#include<assert.h>
//hpp表示類的宣告和實作寫在一起
class Stack
{
public:
//不能寫到private
Stack(int cap = 5)
:_size(0)
,_cap(cap)
{
_a = (int*)malloc(sizeof(int) * cap);
}
void push(int val);
void pop();
int top();
bool empty();
//宣告和定義一起寫編譯器默認會認為是行內函式
~Stack()
{
free(_a);
_a = nullptr;
_size = _cap = 0;
}
private:
void CheckCacity();
private:
int* _a;
int _cap;
int _size;
};
//這里是Stack.c
#include"Stack.h"
void Stack::push(int val)
{
CheckCacity();
_a[_size++] = val;
}
void Stack::pop()
{
assert(_size != 0);
_size--;
}
int Stack::top()
{
assert(_size != 0);
return _a[_size-1];
}
bool Stack::empty()
{
return _size == 0;
}
//私有函式的實作和宣告也能分離
void Stack::CheckCacity()
{
if (_size == _cap)
{
int newcap = _cap == 0 ? 2 : _cap * 2;
_cap = newcap;
//這里不做判斷了
int* tmp = (int*)realloc(_a, _cap * sizeof(int));
if (tmp == nullptr)
{
std::cout << "擴容失敗,程式退出\n";
exit(1);
}
_a = tmp;
}
}
這是一個類的基本實作,以下幾點需要注意,
1.私有函式的實作和宣告也能分離!
2.hpp通常是實作和宣告在一起的,
3.在類內部定義的函式默認是行內函式,所以我們通常會把一些使用頻率高,代碼簡潔的函式放到類內部實作,其余的在.cpp當中實作,
4.Stack::push,表示push這個函式是在Stack這個域當中,往后push內部訪問成員變數會默認帶上this指標,
5.建構式和解構式都需要在public,我們的成員變數不希望外部直接修改,所以放在private當中,我們提供public的介面給外部使用,
2.this指標
class Date
{
int _year;
int _month;
int _day;
public:
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
void Print()
{
std::cout << _year << "/" << _month << "/" << _day << std::endl;
}
};
int main()
{
Date d1(2021,10,10);
Date d2(2222,2,2);
d1.Print();
return 0;
}

觀察上面日期類Date,在沒有編譯前Date是檔案,編譯后他是指令放到代碼段當中,那么我們的Print怎么知道我們是這里的d1還是d2呼叫的呢?
非靜態函式呼叫的時候,會默認給我們加上一個this指標,實際上呼叫的時候是這樣的
2.1 vs和gcc下的this指標
vs下是將this指標放入ecx暫存器當中,通常也可以放入堆疊中,由于需要大量使用,所以放入暫存器也是很好的選擇,
linux下是由暫存器rdi存盤this指標
3.類的大小
計算類的大小和計算類實體化的物件的大小是一樣的,
如:int a; sizeof(int) == sizeof(a);
類實體化出來的物件的大小只包括成員變數,因為每個物件的成員變數可能都是不一樣的,但是每個物件呼叫的成員函式可以是同一份,如果每個物件都存盤一份就會比較臃腫,所以編譯時他就會變成指令存到代碼段當中,

上述的結果是:12,這里跟計算結構體的大小一樣,需要進行記憶體對齊,
記憶體對齊
這里我們來復習以下記憶體對齊 記憶體對齊規則:首先得掌握結構體的對齊規則:
- 第一個成員在與結構體變數偏移量為0的地址處,
- 其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處, 對齊數 = 編譯器默認的一個對齊數 與 該成員大小的較小值, VS中默認的值為8
- 結構體總大小為最大對齊數(每個成員變數都有一個對齊數)的整數倍,
- 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍,
試試計算下題的結構體大小吧:
struct A
{
char c;
int a;
int e;
};
struct B
{
char i;
struct A a;//A當中的最大對齊數是4
double q;
};
int main()
{
/*std::cout << sizeof(A)<<std::endl;
std::cout << sizeof(B);*/
printf("%d", sizeof(struct B));
return 0;
}
result:24 如下圖

4.C和C++在命名物件呼叫函式的區別
c寫的在命名空間中就還要通過域名才能呼叫函式,但是如果是cpp的話,就可以直接通過物件直接呼叫,因為物件就是在那個域當中的!
namespace ljhC
{
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
};
void Print(Date* _this)
{
std::cout << _this->_year << "/" << _this->_month << "/" << _this->_day << std::endl;
}
}
namespace ljhCPP
{
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
void Print()
{
std::cout << _year << "/" << _month << "/" << _day << std::endl;
}
};
}
int main()
{ //C實作,呼叫函式需要加上域名
ljhC::Date d1;
ljhC::Print(&d1);
//C++實作,d2這個物件已經在ljhCPP這個域中,可以直接呼叫內部公有函式
ljhCPP::Date d2;
d2.Print();
return 0;
}
5.建構式
建構式不是構造物件,是初始化物件,
注意建構式會自己生成,如果寫了一個就不會再生成默認的
空類常用于標志

只有成員變數的類通常用來多載operator()

6.運算子多載
6.1運算子多載入門
1. 作為類成員的多載函式時,其形參看起來比運算元數目少1成員函式的,運算子有一個默認的形參this,限定為第一個形參,
2. .* 、:: 、sizeof 、?: 、. 注意以上5個運算子不能多載,這個經常在筆試選擇題中出現,
3. 不能通過連接其他符號來創建新的運算子:比如operator@
這里我們演示全域的用法,operator==來舉例,說明并非所有的運算子多載都可以用于全域,有寫就不行,如operator=,
全域的用法有很多限制,在類外面為了訪問成員變數我們不得不設定成公有的,但是實際上這樣子是不好的!!!
#include<iostream>
using namespace std;
class Date
{
public:
int _year;
int _month;
int _day;
Date()
{}
Date(const Date& d)
{
cout << "Date(const Date& d)"<<endl;
}
};
//全域operator=
bool operator==(const Date& d1, const Date& d2) {
return d1._day == d2._day
&& d1._month == d2._month
&& d1._year == d2._year;
}
int main()
{
Date d1;
Date d2;
//全域的用法!
cout<<operator==(d1,d2);
return 0;
}
由于上述原因,所以我們通常將運算子多載放入類內部實作,類內部就可以訪問私有成員變數,
這個時候呼叫函式也不能用operator==(d1,d2);我們就要用d1.operator==(d2) 或者 d1 == d2,后面這種可讀性強,我們推薦這種,
#include<iostream>
using namespace std;
class Date
{
int _year;
int _month;
int _day;
public:
Date()
{}
Date(const Date& d)
{
cout << "Date(const Date& d)"<<endl;
}
bool operator==(const Date& d2) {
return _day == d2._day
&& _month == d2._month
&& _year == d2._year;
}
};
//全域operator=
int main()
{
Date d1;
Date d2;
//可讀性差
cout<<d1. operator==(d2)<<endl;
//可讀性強
cout << (d1 == d2);
return 0;
}
6.2賦值多載和拷貝構造的小比較
拷貝構造傳參時需要注意傳參考,避免無窮遞回呼叫:在傳參若不加參考則又是一次拷貝構造
賦值多載注意一下幾點:
- 引數型別
- 回傳值
- 檢測是否自己給自己賦值(無意義)
- 回傳*this
- 一個類如果沒有顯式定義賦值運算子多載,編譯器也會生成一個,完成物件按位元組序的值拷貝,
賦值多載就是在用一個物件去對另外一個物件進行賦值,對于內置型別,下面用int舉例,
int a =0;
int b =0;
a=b;//這個就是一個賦值
對于一個自定義型別,我們需要多載operator=,用日期類舉個例子
#include<iostream>
using namespace std;
class Date
{
int _year;
int _month;
int _day;
public:
Date()
{}
Date& operator=(const Date& d)
{
//這里沒有實作,只是證明呼叫該函式
cout << "Date& operator=(const Date& d)"<<endl;
return *this;
}
Date(const Date& d)
{
//這里沒有實作,只是證明呼叫該函式
cout << "Date(const Date& d)"<<endl;
}
};
int main()
{
Date d1;
Date d2;
d1 = d2;//這個就是賦值構造,兩個物件都已經存在 d1.operator=(d2);
Date d3 = d1;//這個就是拷貝構造,Date d3(d1);
return 0;
}
結果:我們可以看到賦值物件是需要兩個物件存在才可以賦值的,
7.建構式,解構式,賦值多載比較
由于過于相似,就不一一說了,
建構式:編譯器默認對于內置型別(基礎型別)是不做處理的,對于自定義型別(class ,struct,union)就會進行呼叫他們的建構式,如果他們的建構式沒有對基礎型別有初始化,那么也都是隨機值!
解構式:解構式對于日期類的,一般沒有堆上面開辟的不需要寫解構式,但對于常見的資料結構,如堆疊(陣列動態開辟),佇列(鏈表動態開辟),我們就需要對于堆上的空間做處理,
編譯器生成的解構式:呼叫自定義型別的解構式,內置型別不做處理,所以對于堆疊想要對int*進行free的時候我們也要手動操作,
賦值多載:跟拷貝構造的行為類似,內置型別成員會完成值拷貝,自定義型別成員換會呼叫他的賦值多載,注意:賦值多載是在兩個已經定義出來的物件間的拷貝復制,
Date d2;
Date d1 =d2;//這個就是拷貝構造
總結
本章節對幾個默認的成員函式進行了解釋,下一期會詳細講講一些類的細節,易錯點,
看到這里不妨一鍵三連喲!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/316710.html
標籤:其他
上一篇:使用Layered分層視窗實作視頻會議中的桌面區域共享
下一篇:致敬!再見了!LayUI !



