C++知識點總結(面向對象6-隱式構造, 友元, 內部類, 局部類)

對象類型的參數和返回值

使用對象類型作爲函數參數或者返回值, 可能會產生一些不必要的中間對象
(1)對象類型做函數參數時

void test1(Car car) { // 對象類型作爲參數
	
}
int main() {
	Car car1;
	test1(car1);
}

把外面的對象傳給裏面的對象時, 會產生一個新的對象(不必要的中間對象), 因爲相當於void test1(Car car = car1)利用一個已經存在的對象, 構建出了一個新的對象, 所以就是拷貝構造.
解決這個問題: 傳引用或指針

void test1(Car &car) {

}
int main() {
	Car car1;
	test1(car1);
}

相當於void test1(Car &car = car1) 就直接引用着外面的car1對象, 這樣就不會產生新的對象, 即將car1的地址值傳給了car這個引用去保存, 因爲引用的本質就是指針, 所以就是地址傳遞, 不會直接將對象傳過去去構建一個新的對象.
(2)對象類型做返回值時:

Car test2() {
	Car car;
	return car;
}
int main() {
	Car car2;
	car2 = test2();
}

當把car返回時, 會多出一個拷貝構造, 因爲在函數裏面定義的car對象的內存在teat2所在的棧空間, 要把test2棧空間裏面的對象返回到main函數的棧空間裏, 而它的內存在離開函數後就銷燬了, 所以要拷貝.
它的做法是:提前將test2裏面的car對象拷貝構造出一個新的對象, 而這個新對象的內存是在main函數裏面的.
(3)編譯器優化

Car test2() {
	Car car;
	return car;
}
int main() {
	Car car3 = test2();
}

只會調用1次拷貝構造, 直接給car3

匿名對象(臨時對象)

匿名對象:沒有變量名, 沒有被指針指向的對象, 用完馬上調用析構
(1)編譯器優化(瞭解)
匿名對象作爲參數
(2)編譯器優化(瞭解)
直接返回匿名對象

隱式構造

C++裏面存在隱式構造的現象 : 某些情況下, 會隱式調用單參數的構造函數
(1)Person p1 = 20;
調用了單參數的構造函數, 等價於Person p1(20);
(2)

void test1(Person person) {

}
int main() {
	test1(20);
}

相當於Person person = 20; 調用單參數的構造函數構建對象
(3)

Person test2() {
	return 40;
}
int main() {
	test2();
}

隱式構造, 調用單參數構造函數構建出對象
(4)

Person p1;
p1 = 40;

只有對象纔可以賦值給對象, 所以40會發生隱式構造, 相當於Person(40), 調用單參數的構造函數構建出一個臨時對象.
(5)可以通過關鍵字explicit(明確的意思)來禁止掉隱式構造

class Person {
	int m_age;
public:
	explicit Person(int age) : m_age(age) {}
};

(6)有些地方也叫轉換構造, 因爲會將40直接轉換成Person對象.

編譯器自動生成的構造函數

C++的編譯器在某些特定的情況下, 會給類自動生成無參的構造函數, 比如
(1)成員變量在聲明的同時進行了初始化

class Person {
public:
	int m_price = 5;
}

等價於

class Person {
public:
	int m_price;
	Person() {
		m_price = 5;
	}
}

編譯器會自動生成構造函數, 初始化是在構造函數中做的.
(2)有定義虛函數
因爲一旦有虛函數的話, 這個對象會多出4個字節來存放虛表地址.
在創建完Person對象後, 要給Person對象的最前面的4個字節賦虛表地址.
在這裏插入圖片描述
vftable = virtual function table
(3)虛繼承了其他類
class Student : virtual public Person {

}
一旦虛繼承了某個類, 那麼當時候某個類創建出來的對象最前面4個字節存儲着虛表地址
(4)包含了對象類型的成員, 而且這個成員有構造函數(自定義或編譯器生成)

class Car {
public:
	int m_price;
	Car() {}	
};
class Person {
	Car car;
}
int main() {
	Person person;
}

創建完person對象之後, person對象裏面要搞一個Car對象, 就要調用Car的無參的構造函數,
所以這個時候編譯器就會爲Person這個類生成一個構造函數, 在構造函數裏面調用Car的 構造函數去初始化car
(5)父類有構造函數(編譯器生成或自定義)

class Person {
public:
	Person() {}
};
class Student : public Person {
public:
};

因爲子類要優先調用父類的構造函數, 所以編譯器會爲子類自動生成構造函數, 在構造函數裏調用父類的構造函數.
總結:對象創建後, 需要做一些額外操作時(比如內存操作(爲成員變量賦值), 函數調用), 編譯器一般都會爲其生成無參的構造函數.

友元

友元函數包括友元函數和友元類
(1)如果將函數A(非成員函數)聲明爲類C的友元函數, 那麼在函數A內部就能直接訪問類C對象的所有成員.

class Point {
	friend Point add(Point, Point);
}

(2)如果將類A聲明爲類C的友元類, 那麼在類A的所有成員函數內部都能直接訪問類C對象的所有成員.
friend class [類名];

內部類

如果將類A定義在類C的內部, 那麼類A就是一個內部類(嵌套類)
內部類的特點:
(1)支持public, protected, private 權限

class Person {
public:
	class Car {
		int m_price;
	};
};
int main() {
	Person::Car car1;
	// 創建了一個car對象
}

(2)內部類的成員函數可以直接訪問其外部類對象的所有成員(反過來則不行)
(3)成員函數可以直接不帶類名, 不帶對象名訪問其外部類的static成員

class Point {
private:
    static void test1() {
        cout << "Point::test1()" << endl;
    }
    static int ms_test2;
    int m_x;
    int m_y;
public:
    class Math {
    public:
        void test3() {
            cout << "Point::Math" << endl;
            test1();
            ms_test2 = 10;

            Point point;
            point.m_x = 10;
            point.m_y = 20;
        }
    };
};

(4)不會影響外部類的內存佈局, 僅僅是訪問權限變了而已, 內存佈局是不變的.(編譯器特性)
(5)可以在外部類內部聲明, 在外部類外面定義.

局部類

在一個函數內部定義的類稱之爲局部類.
局部類的特點:
(1)作用域僅限於所在的函數內部
(2)其所有的成員必須定義(聲明和實現)在類內部, 不允許定義static成員變量
因爲static成員變量時使用前必須放在類外面初始化, 而局部類又要求必須放在裏面, 矛盾.
(3)成員函數不能直接訪問函數的局部變量(static變量除外)
因爲函數的局部變量, 只有在調用函數時才分配存儲空間, 那麼在調用對象的成員函數訪問其外部函數的局部變量時, 該局部變量還沒有分配存儲空間, 所以報錯
在成員函數的棧空間裏不能訪問其他的棧空間裏面的變量.
(4)局部類不影響其外部函數的內存佈局.因爲類的定義是代碼, 存放在代碼段, 不會開闢棧空間, 只有在創建對象時, 纔會開闢棧空間(前提對象裏面有成員變量). 類的定義和執行函數不同.
類放在函數裏面或外面僅僅是訪問權限的變化.


其他C++系列文章:

C++知識點總結(基礎語法1-函數重載, 默認參數)
C++知識點總結(基礎語法2-內聯函數, const, 引用)
C++知識點總結(面向對象1-類和對象, this指針, 內存佈局)
C++知識點總結(面向對象2-構造函數, 初始化列表)

C++知識點總結(面向對象3-多態)

C++知識點總結(面向對象4-多繼承, 靜態成員static)
C++知識點總結(面向對象5-const成員, 拷貝構造函數)
C++知識點總結(面向對象6-隱式構造, 友元, 內部類, 局部類)
C++知識點總結(其他語法1-運算符重載)
C++知識點總結(其他語法2-模板, 類型轉換, C++11新特性)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章