###前言
C++是物件導向的程式語言,因此有類的概念。
類的定義是在標頭檔案,實現在原始檔中,這裡為了方便,都寫在原始檔中。
###建構函式
-
系統會自動建立預設的無慘建構函式,但是一旦提供了有參的,預設的就會去掉了。
-
構造有兩種寫法。
class Teacher { public: Teacher(){ cout << "無參建構函式" << endl; } Teacher(char* name, int age){ cout << "有參建構函式" << endl; this->name = name; this->age = age; }
private: char* name; int age; };
void main(){ Teacher t1 = Teacher("nan",20); Teacher t2("lu", 18); system("pause"); }
####建構函式的初始化屬性列表
現在有Student類,裡面有Teacher的私有成員兩個。Student構造的時候需要初始化Teacher,但是又不能直接訪問Teacher類的私有屬性。
因此只能通過初始化列表的方式來呼叫。相當於Java利用super(...)呼叫父類的建構函式初始化父類。
class Teacher
{
public:
Teacher(char* name, int age){
this->name = name;
this->age = age;
}
Teacher(){
}
private:
char* name;
int age;
};
class Student{
public:
Student(int id, char* t1_name, int t1_age, char*t2_name, int t2_age)
:t1(t1_name, t1_age), t2(t2_name, t2_age)
{
//t1.name = t1_name;不可以這樣賦值,因為Teacher的私有屬性不可以訪問
}
private:
int id;
Teacher t1;
Teacher t2;
};
void main(){
Student s(1, "wu", 20, "li", 18);
system("pause");
}
複製程式碼
###解構函式
解構函式:當物件要被系統釋放時(例如函式棧退出的時候),解構函式被呼叫 作用:善後處理,例如釋放動態分配的記憶體。
class Teacher
{
public:
Teacher(){
cout << "無參建構函式" << endl;
name = (char*)malloc(sizeof(char)* 100);
strcpy(name, "lu");
age = 18;
}
~Teacher(){
cout << "解構函式" << endl;
free(name);
}
private:
char* name;
int age;
};
void fun(){
Teacher t;
}
void main(){
fun();
system("pause");
}
複製程式碼
###拷貝建構函式
-
系統預設的拷貝建構函式就是拷貝值的,跟下面的寫法一樣。
-
拷貝建構函式被呼叫的場景有(實質上都是第一種的變種),有印象即可:
一、宣告時賦值(只有宣告的時候才會呼叫) 二、作為引數傳入,實參給形參賦值(引用傳值不會呼叫,因為指向的都是同一片記憶體,不存在拷貝問題) 三、作為函式返回值返回,給變數初始化賦值 複製程式碼
例子如下:
class Teacher
{
public:
Teacher(char* name, int age){
cout << "有參建構函式" << endl;
this->name = name;
this->age = age;
}
Teacher(const Teacher &obj){
cout << "拷貝建構函式" << endl;
this->name = obj.name;
this->age = obj.age;
}
private:
char* name;
int age;
};
Teacher fun(Teacher t){
return t;
}
void main(){
Teacher t1 = Teacher("nan", 20);
Teacher t2 = t1;
Teacher t3 = fun(t1);
system("pause");
}
複製程式碼
####淺拷貝
預設的拷貝建構函式實現是淺拷貝,即值拷貝,如下所示:
class Teacher
{
public:
Teacher(){
this->name = (char*)malloc(sizeof(char)* 100);
strcpy(name, "lu");
this->age = age;
}
~Teacher(){
cout << "解構函式" << endl;
free(name);
}
Teacher(const Teacher &obj){
cout << "拷貝建構函式" << endl;
this->name = obj.name;
this->age = obj.age;
}
private:
char* name;
int age;
};
void fun(){
Teacher t1;
Teacher t2 = t1;
}
void main(){
fun();
system("pause");
}
複製程式碼
值拷貝帶來的問題:
如果有動態記憶體分配的時候,如果單純是值拷貝,析構的時候就會析構兩次。
例如程式碼中的char* name,t1、t2都指向了同一個地址,那麼析構的時候就會free兩次,因為一個記憶體地址不能釋放兩次,因此會觸發異常中斷。
複製程式碼
如下圖所示:
####深拷貝
重寫覆蓋預設的淺拷貝,自己寫一個深拷貝:
t2的name是新分配的記憶體,因此t1、t2指向的是兩片不同的記憶體,因此析構的時候分別釋放,互不干擾,解決了淺拷貝的問題。
class Teacher
{
public:
Teacher(){
this->name = (char*)malloc(sizeof(char)* 100);
strcpy(name, "lu");
this->age = age;
}
~Teacher(){
cout << "解構函式" << endl;
free(name);
}
Teacher(const Teacher &obj){
cout << "拷貝建構函式" << endl;
int len = strlen(obj.name);
this->name = (char*)malloc(sizeof(char)* (len + 1));//+1是因為結束符
strcpy(this->name, obj.name);
this->age = obj.age;
}
private:
char* name;
int age;
};
void fun(){
Teacher t1;
Teacher t2 = t1;
}
void main(){
fun();
system("pause");
}
複製程式碼
如下圖所示:
###動態記憶體分配
- C++中使用new和delete進行動態記憶體分配,這時候構造和析構會呼叫。
- C中使用malloc和free進行動態記憶體分配,構造和析構不會呼叫。
例子:
void main(){
cout << "C++中使用new和delete進行動態記憶體分配" << endl;
Teacher* t1 = new Teacher("wu", 20);
delete t1;
cout << "C中使用malloc和free進行動態記憶體分配" << endl;
Teacher* t2 = (Teacher*)malloc(sizeof(Teacher));
free(t2);
system("pause");
}
複製程式碼
陣列的話,delete的時候需要加上[]:
int* arr1 = (int*)malloc(sizeof(int)* 10);
arr1[0] = 1;
free(arr1);
int* arr2 = new int[10];
arr2[0] = 1;
delete[] arr2;
複製程式碼
###類的靜態屬性和函式
- 類的靜態屬性必須放在全域性的地方初始化。
- 非靜態、靜態函式可以訪問靜態屬性,但是靜態函式只能訪問靜態屬性。(跟Java一樣)
- 靜態的屬性和方法可以直接通過 類名:: 來訪問(如果是public的話)。也可以通過 物件:: 來訪問。
例子:
class Test
{
public:
void m1(){
count++;
}
static void m2(){
count++;
}
private:
static int count;
};
//靜態屬性初始化
int Test::count = 0;
void main(){
//Test::count
Test::m2();
system("pause");
}
複製程式碼
###類的大小(sizeof)
C++的記憶體分割槽:
棧
堆
全域性(靜態、全域性)
常量區(字串)
程式程式碼區
複製程式碼
結構體有位元組對齊的概念,那麼類的普通屬性與結構體相同的記憶體佈局。
例如下面的結構體大小是32 = 4 x 8:
struct C{
public:
int i;//8個位元組(由於位元組對齊,與double一樣為8個位元組)
int j;//8個位元組
int k;//8個位元組
double x;//8個位元組
void(*test)();//指標是4個位元組,但是存放在程式碼區
};
複製程式碼
例如下面的類的大小是24(這裡沒搞懂,先放一放,以後回來再補)
class A{
public:
int i;//8個位元組(由於位元組對齊,與double一樣為8個位元組)
int j;//8個位元組
int k;//8個位元組
double t;//8個位元組
static int m;//4個位元組,但是存在於全域性靜態區
void test(){//4個位元組,相當於函式指標的寫法,但是存放在程式碼區
cout << "列印" << endl;
}
};
複製程式碼
###類的this指標常函式
- const寫在函式後面。
- 此時的const修飾的是this指標,this指標本類就是指標常量,不能修改值。
- 此處增加了const的修飾之後,tihs指標更加是一個常量指標,指標指向的值不能修改。
例子:
class Teacher{
private:
char* name;
int age;
public:
Teacher(char* name, int age){
this->name = name;
this->age = age;
}
//常函式,修飾的是this
//既不能改變指標的值,又不能改變指標指向的內容
//const Teacher* const this
void myprint() const{
printf("%#x\n", this);
//改變屬性的值
//this->name = "yuehang";
//改變this指標的值
//this = (Teacher*)0x00009;
cout << this->name << "," << this->age << endl;
}
};
複製程式碼
this,當前物件的指標
C++函式是共享的(多個物件共享程式碼區中的一個函式),必須要有能夠標識當前物件是誰的辦法,那就是this指標。
例如我們用結構體去模擬類的時候,就需要自定義this指標了。所以說,在JNI開發的時候,JniEnv在C語音裡面是二級指標(結構體實現,C語言中結構體沒有tihs指標),在C++中是一級指標(類實現,類有this指標)
擴充:
Java記憶體分割槽:
- JVM Stack(基本資料型別、物件引用)
- Native Method Stack(本地方法棧)
- 方法區
- 程式計數區
- 直接記憶體
熱修復的基本概念:拿到錯誤的函式的指標,指向正確的函式的指標。(兩個函式都已經載入的情況下)
###友元函式、友元函式
主要作用:類、類與函之間的資料共享。比如類已經寫好了,不想去改動類的結構(比如訪問許可權,那麼最好的辦法就是使用友元)
在友元函式中可以訪問類的私有的屬性。
例如,fun是類A的友元函式,因此fun中可以訪問A的私有屬性。
class Test{
friend void fun(Test &t, int i);//友元函式宣告
public:
Test(int i){
this->i = i;
}
private:
int i;
};
//友元函式實現,可以是全域性的,也可以是其他類的
void fun(Test &t, int i){
t.i = i;
}
void main(){
Test t(10);
fun(t, 20);
system("pause");
}
複製程式碼
同理,B是A的友元類,因此B可以訪問A的私有屬性:
class A{
friend class B;
private:
int i;
};
class B
{
public:
void test(){
a.i = 20;
}
private:
A a;
int i;
};
複製程式碼
擴充:在Java(Java是C++寫的嘛)中Class就可以理解為Object的“友元類”,Class類就可以訪問Object的任何成員。
###運算子過載
運算子過載的本質還是函式的呼叫。
現在有一個類:
class Point
{
public:
Point(int x, int y){
this->x = x;
this->y = y;
}
public:
int x;
int y;
};
複製程式碼
需要過載“+”:
Point operator+(Point &p1, Point &p2){
return Point(p1.x + p2.x, p2.y + p2.y);
}
複製程式碼
注意,如果Point的x、y屬性都是私有的話,就需要用到友元函式了,例如類已經寫好了,不想破壞類的結構:
class Point
{
friend Point operator+(Point &p1, Point &p2);
public:
Point(int x, int y){
this->x = x;
this->y = y;
}
private:
int x;
int y;
};
Point operator+(Point &p1, Point &p2){
return Point(p1.x + p2.x, p2.y + p2.y);
}
複製程式碼
如果是在類內部的運算子過載,因為有this指標,過載的時候可以省略一個引數,類可以直接訪問自己的私有成員:
class Point
{
public:
Point(int x, int y){
this->x = x;
this->y = y;
}
Point operator+(Point &p){
return Point(this->x + p.x, this->y + p.y);
}
private:
int x;
int y;
};
複製程式碼
如果覺得我的文字對你有所幫助的話,歡迎關注我的公眾號:
我的群歡迎大家進來探討各種技術與非技術的話題,有興趣的朋友們加我私人微信huannan88,我拉你進群交(♂)流(♀)。