[TOC]
# 簡介
當我們創建對象的時候,這個對象應該有一個初始狀態,當對象銷毀之前應該銷毀自己創建的一些數據。
對象的初始化和清理也是兩個非常重要的安全問題,一個對象或者變量沒有初始時,對其使用后果是未知,同樣的使用完一個變量,沒有及時清理,也會造成一定的安全問題。c++為了給我們提供這種問題的解決方案,構造函數和析構函數,這兩個函數將會被編譯器自動調用,完成對象初始化和對象清理工作。
無論你是否喜歡,對象的初始化和清理工作是編譯器強制我們要做的事情,即使你不提供初始化操作和清理操作,編譯器也會給你增加默認的操作,只是這個默認初始化操作不會做任何事,所以編寫類就應該順便提供初始化函數。
為什么初始化操作是自動調用而不是手動調用?既然是必須操作,那么自動調用會更好,如果靠程序員自覺,那么就會存在遺漏初始化的情況出現
# 構造函數和析構函數
構造函數主要作用在于創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。
析構函數主要用于對象銷毀前系統自動調用,執行一些清理工作。
構造函數語法:
* 構造函數函數名和類名相同,沒有返回值,不能有void,但可以有參數。
~~~
ClassName(){}
~~~
析構函數語法:
* 析構函數函數名是在類名前面加”~”組成,沒有返回值,不能有void,不能有參數,不能重載。
~~~
~ClassName(){}
~~~
~~~
class Person{
public:
Person(){
cout << "構造函數調用!" << endl;
pName = (char*)malloc(sizeof("John"));
strcpy(pName, "John");
mTall = 150;
mMoney = 100;
}
~Person(){
cout << "析構函數調用!" << endl;
if (pName != NULL){
free(pName);
pName = NULL;
}
}
public:
char* pName;
int mTall;
int mMoney;
};
void test(){
Person person;
cout << person.pName << person.mTall << person.mMoney << endl;
}
~~~
# 注意點
1. 構造函數和析構函數權限必須是公有的
2. 構造函數可以重載
3. 構造函數沒有返回值,不能用void,構造函數可以有參數,析構函數沒有返回值,不能用void,沒有參數
4. 如果類有成員對象,先調用成員對象的構造函數,再調用自己的,析構順序反之
5. 成員對象的構造函數調用和定義順序一樣
# 構造函數的分類
* 按參數類型:分為無參構造函數和有參構造函數
* 按類型分類:普通構造函數和拷貝構造函數(復制構造函數)
拷貝構造是沒有對象的時候調用的
~~~
class Person{
public:
Person(){
cout << "no param constructor!" << endl;
mAge = 0;
}
//有參構造函數
Person(int age){
cout << "1 param constructor!" << endl;
mAge = age;
}
//拷貝構造函數(復制構造函數) 使用另一個對象初始化本對象
Person(const Person& person){
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
//打印年齡
void PrintPerson(){
cout << "Age:" << mAge << endl;
}
private:
int mAge;
};
//1. 無參構造調用方式
void test01(){
//調用無參構造函數
Person person1;
person1.PrintPerson();
//無參構造函數錯誤調用方式
//Person person2();
//person2.PrintPerson();
}
//2. 調用有參構造函數
void test02(){
//第一種 括號法,最常用
Person person01(100);
person01.PrintPerson();
//調用拷貝構造函數
Person person02(person01);
person02.PrintPerson();
//第二種 匿名對象(顯示調用構造函數)
Person(200); //匿名對象,沒有名字的對象
Person person03 = Person(300);
person03.PrintPerson();
//注意: 使用匿名對象初始化判斷調用哪一個構造函數,要看匿名對象的參數類型
Person person06(Person(400)); //等價于 Person person06 = Person(400);
person06.PrintPerson();
//第三種 =號法 隱式轉換
Person person04 = 100; //Person person04 = Person(100)
person04.PrintPerson();
//調用拷貝構造
Person person05 = person04; //Person person05 = Person(person04)
person05.PrintPerson();
}
~~~
b為A的實例化對象,`A a = A(b)` 和 A(b)的區別?
當A(b) 有變量來接的時候,那么編譯器認為他是一個匿名對象,當沒有變量來接的時候,編譯器認為你A(b) 等價于 `A b.`
注意:不能調用拷貝構造函數去初始化匿名對象,也就是說以下代碼不正確:
~~~
class Teacher{
public:
Teacher(){
cout << "默認構造函數!" << endl;
}
Teacher(const Teacher& teacher){
cout << "拷貝構造函數!" << endl;
}
public:
int mAge;
};
void test(){
Teacher t1;
//error C2086:“Teacher t1”: 重定義
Teacher(t1); //此時等價于 Teacher t1;
}
~~~
# 構造函數調用規則
默認情況下,c++編譯器至少為我們寫的類增加3個函數
1. 默認構造函數(無參,函數體為空)
2. 默認析構函數(無參,函數體為空)
3. 默認拷貝構造函數,對類中非靜態成員屬性簡單值拷貝
* 如果用戶定義拷貝構造函數,c++不會再提供任何默認構造函數
* 如果用戶定義了普通構造(非拷貝),c++不在提供默認無參構造,但是會提供默認拷貝構造
# 拷貝構造為什么是const引用
~~~
CBox(CBox initB);//復制構造函數一開始想到的原型
CBox cigar;
CBox myBox(cigar); //如果編寫這樣一條語句
//那么將生成這樣一條對復制構造函數的調用CBox::CBox(cigar);
~~~
因為實參是通過按值傳遞機制傳遞的。在可以傳遞對象cigar之前,編譯器需要安排創建該對象的副本。因此,編譯器為了處理復制構造函數的這條調用語句,需要調用復制構造函數來創建實參的副本。但是,由于是按值傳遞,第二次調用同樣需要創建實參的副本,因此還得調用復制構造函數,就這樣持續不休。最終得到的是對復制構造函數的無窮調用。(其實就是創建副本也是需要調用復制構造函數的)
所以解決辦法先是要將形參改為引用形參:
~~~
CBox (CBox &initB);
~~~
其實,這里,如果不去改變實參的值的話,不加const的效果和加const的效果是一樣的,而且不加const編譯器也不會報錯,因為函數的形參是引用,則調用函數時不需要復制實參,函數是直接訪問調用函數中的實參變量的。但是為了整個程序的安全,還是加上const,防止對實參的意外修改
# 返回值優化
局部變量,內存不釋放
relase地址一樣,debug不一樣
~~~
//局部對象以值的方式從函數返回
//vs Debug模式下會調用拷貝構造,relase模式下
Person show() {
//局部對象
Person p;
cout << "局部對象的地址: " << &p << endl;
return p;
}
int main() {
Person p1 = show();
cout << "m1對象的地址: " << &p1 << endl;
system("pause");
return EXIT_SUCCESS;
}
~~~
# 多個對象
構造函數和其他函數不同,除了有名字,參數列表,函數體之外還有初始化列表。
在類中定義的數據成員一般都是基本的數據類型。但是類中的成員也可以是對象,叫做對象成員。
C++中對對象的初始化是非常重要的操作,當創建一個對象的時候,c++編譯器必須確保調用了所有子對象的構造函數。如果所有的子對象有默認構造函數,編譯器可以自動調用他們。但是如果子對象沒有默認的構造函數,或者想指定調用某個構造函數怎么辦?
那么是否可以在類的構造函數直接調用子類的屬性完成初始化呢?但是如果子類的成員屬性是私有的,我們是沒有辦法訪問并完成初始化的。
解決辦法非常簡單:對于子類調用構造函數,c++為此提供了專門的語法,即構造函數初始化列表。
當調用構造函數時,首先按各對象成員在類定義中的順序(和參數列表的順序無關)依次調用它們的構造函數,對這些對象初始化,最后再調用本身的函數體。也就是說,先調用對象成員的構造函數,再調用本身的構造函數。
析構函數和構造函數調用順序相反,先構造,后析構。
~~~
//初始化列表可以指定調用構造函數
Person(string carName, string tracName, string name) : mTractor(tracName), mCar(carName), mName(name){
cout << "Person 構造函數!" << endl;
}
~~~
- c語言
- 基礎知識
- 變量和常量
- 宏定義和預處理
- 隨機數
- register變量
- errno全局變量
- 靜態變量
- 類型
- 數組
- 類型轉換
- vs中c4996錯誤
- 數據類型和長度
- 二進制數,八進制數和十六進制數
- 位域
- typedef定義類型
- 函數和編譯
- 函數調用慣例
- 函數進棧和出棧
- 函數
- 編譯
- sizeof
- main函數接收參數
- 宏函數
- 目標文件和可執行文件有什么
- 強符號和弱符號
- 什么是鏈接
- 符號
- 強引用和弱引用
- 字符串處理函數
- sscanf
- 查找子字符串
- 字符串指針
- qt
- MFC
- 指針
- 簡介
- 指針詳解
- 案例
- 指針數組
- 偏移量
- 間接賦值
- 易錯點
- 二級指針
- 結構體指針
- 字節對齊
- 函數指針
- 指針例子
- main接收用戶輸入
- 內存布局
- 內存分區
- 空間開辟和釋放
- 堆空間操作字符串
- 內存處理函數
- 內存分頁
- 內存模型
- 棧
- 棧溢出攻擊
- 內存泄露
- 大小端存儲法
- 寄存器
- 結構體
- 共用體
- 枚舉
- 文件操作
- 文件到底是什么
- 文件打開和關閉
- 文件的順序讀寫
- 文件的隨機讀寫
- 文件復制
- FILE和緩沖區
- 文件大小
- 插入,刪除,更改文件內容
- typeid
- 內部鏈接和外部鏈接
- 動態庫
- 調試器
- 調試的概念
- vs調試
- 多文件編程
- extern關鍵字
- 頭文件規范
- 標準庫以及標準頭文件
- 頭文件只包含一次
- static
- 多線程
- 簡介
- 創建線程threads.h
- 創建線程pthread
- gdb
- 簡介
- mac使用gdb
- setjump和longjump
- 零拷貝
- gc
- 調試器原理
- c++
- c++簡介
- c++對c的擴展
- ::作用域運算符
- 名字控制
- cpp對c的增強
- const
- 變量定義數組
- 盡量以const替換#define
- 引用
- 內聯函數
- 函數默認參數
- 函數占位參數
- 函數重載
- extern "C"
- 類和對象
- 類封裝
- 構造和析構
- 深淺拷貝
- explicit關鍵字
- 動態對象創建
- 靜態成員
- 對象模型
- this
- 友元
- 單例
- 繼承
- 多態
- 運算符重載
- 賦值重載
- 指針運算符(*,->)重載
- 前置和后置++
- 左移<<運算符重載
- 函數調用符重載
- 總結
- bool重載
- 模板
- 簡介
- 普通函數和模板函數調用
- 模板的局限性
- 類模板
- 復數的模板類
- 類模板作為參數
- 類模板繼承
- 類模板類內和類外實現
- 類模板和友元函數
- 類模板實現數組
- 類型轉換
- 異常
- 異常基本語法
- 異常的接口聲明
- 異常的棧解旋
- 異常的多態
- 標準異常庫
- 自定義異常
- io
- 流的概念和類庫結構
- 標準io流
- 標準輸入流
- 標準輸出流
- 文件讀寫
- STL
- 簡介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair對組
- 深淺拷貝問題
- 使用時機
- 常用算法
- 函數對象
- 謂詞
- 內建函數對象
- 函數對象適配器
- 空間適配器
- 常用遍歷算法
- 查找算法
- 排序算法
- 拷貝和替換算法
- 算術生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各種插件
- utf8編碼
- 制作安裝項目
- 編譯模式
- 內存對齊
- 快捷鍵
- 自動補全
- 查看c++類內存布局
- FFmpeg
- ffmpeg架構
- 命令的基本格式
- 分解與復用
- 處理原始數據
- 錄屏和音
- 濾鏡
- 水印
- 音視頻的拼接與裁剪
- 視頻圖片轉換
- 直播
- ffplay
- 常見問題
- 多媒體文件處理
- ffmpeg代碼結構
- 日志系統
- 處理流數據
- linux
- 系統調用
- 常用IO函數
- 文件操作函數
- 文件描述符復制
- 目錄相關操作
- 時間相關函數
- 進程
- valgrind
- 進程通信
- 信號
- 信號產生函數
- 信號集
- 信號捕捉
- SIGCHLD信號
- 不可重入函數和可重入函數
- 進程組
- 會話
- 守護進程
- 線程
- 線程屬性
- 互斥鎖
- 讀寫鎖
- 條件變量
- 信號量
- 網絡
- 分層模型
- 協議格式
- TCP協議
- socket
- socket概念
- 網絡字節序
- ip地址轉換函數
- sockaddr數據結構
- 網絡套接字函數
- socket模型創建流程圖
- socket函數
- bind函數
- listen函數
- accept函數
- connect函數
- C/S模型-TCP
- 出錯處理封裝函數
- 多進程并發服務器
- 多線程并發服務器
- 多路I/O復用服務器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反應堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函數
- libevent
- libevent簡介