對象類型的參數和返回值
使用對象類型作爲函數參數或者返回值, 可能會產生一些不必要的中間對象
(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++知識點總結(面向對象4-多繼承, 靜態成員static)
C++知識點總結(面向對象5-const成員, 拷貝構造函數)
C++知識點總結(面向對象6-隱式構造, 友元, 內部類, 局部類)
C++知識點總結(其他語法1-運算符重載)
C++知識點總結(其他語法2-模板, 類型轉換, C++11新特性)