1. 認識類與對象
(1)什么是類(class)?
類(class)是類型(type),是用戶自定義的類型。為什么不叫它type,因為借用Simula語言中的class關(guān)鍵字。
<1>為什么要有類?
基于便利性的考慮,現(xiàn)實世界中物(object)通常被分為幾種獨立的分類。
<2>基本概念
- 類:建筑圖紙
- 實例化:建造
- 對象/實例:樓房
<3>面向?qū)ο蟮乃拇筇卣?/h6>
- 1.抽象:抽出具體事物的普遍性的本質(zhì);比如分門別類:鳥類、哺乳類、魚類
解釋:抽象出來的一般與當前目標有關(guān)的方面,叫做類或者接口,抽象包括兩個方面:數(shù)據(jù)抽象(特診:對象/類的屬性,c++也叫做成員變量)和過程抽象(行為:類的方法,c++叫做成員函數(shù))
- 2.封裝:把數(shù)據(jù)與處理(函數(shù))包在一起;比如通信錄(增加、刪除)
解釋:封裝就是把屬性和方法都隱藏起來,保證數(shù)據(jù)的安全性,對外我們提供接口用來給我們信賴的對象訪問。如私有變量。
- 3.繼承:數(shù)據(jù)與處理(函數(shù))的傳承;財富與絕技、混血兒(膚色/頭發(fā)、 兩種語言
- 4.多態(tài):同一個事物(函數(shù))的多種形態(tài);手機鍵盤數(shù)字與字母、 電腦鍵盤功能鍵
<4>面向?qū)ο蟮奈宕笤瓌t
解釋:抽象出來的一般與當前目標有關(guān)的方面,叫做類或者接口,抽象包括兩個方面:數(shù)據(jù)抽象(特診:對象/類的屬性,c++也叫做成員變量)和過程抽象(行為:類的方法,c++叫做成員函數(shù))
解釋:封裝就是把屬性和方法都隱藏起來,保證數(shù)據(jù)的安全性,對外我們提供接口用來給我們信賴的對象訪問。如私有變量。
1.單一職責原則
2.開放封閉原則
3.替換原則
4.依賴倒置原則
5.接口隔離原則
2.類的定義與對象創(chuàng)建
(1)類的定義:與struct相似(C++)
<1>格式
class 類名{
成員變量/成員函數(shù)聲明;
};
注意:class定義最后的;一定不要忘記。
<2>構(gòu)成
- 數(shù)據(jù)成員(data member)/成員變量/屬性:作用:對象內(nèi)部數(shù)據(jù)和狀態(tài),只能在類定義中聲明,可以在成員函數(shù)中直接調(diào)用。
- 成員函數(shù)/方法:作用:對象相關(guān)的操作,可以在類內(nèi)實現(xiàn)或類外實現(xiàn)。成員函數(shù)可以自己調(diào)用內(nèi)部其他的成員函數(shù)。在一個類里面可以調(diào)用另一個類里面
public的所有的成員函數(shù)。
//成員函數(shù)可以自己調(diào)用內(nèi)部其他的成員函數(shù)
void Print(){
cout << name << '\t' << count << "\t¥" << price << "\t¥" << GetTotal() << "\t¥" << GetOff() << endl;
}
<3>作用域運算符:: -- 函數(shù)歸屬
在類中定義,在類外面進行實現(xiàn)。如果在類里面實現(xiàn)的話,不需要該格式
在類外實現(xiàn),使用該格式:
返回類型 類名::函數(shù)名
如:
void Student::Print(){}
<4>訪問限定符
private[默認]:私有
public:公有
protected:保護
實踐中,成員變量多數(shù)情況使用private或者protected,成員函數(shù)多數(shù)情況使用public。通常,通過成員函數(shù)改變對象的成員變量。
<5>類定義與類實現(xiàn)分離(.h和.cpp)
聲明和實現(xiàn)分開編寫。
- 頭文件
方式:#pragma once或者#ifndef #define #endif
作用:防止頭文件二次編譯 - 源文件 --實現(xiàn)
引用頭文件:include <>(標準庫)/include " "(自定義第三方庫)
<6>class與struct的區(qū)別
C++的class與struct的區(qū)別
- 1.默認的控制方式不同: struct是
public,class是private - 2.struct可以使用花括號
{}來初始化,class不可以(c++98不可以,c++11可以)
struct在C和C++中的區(qū)別
1.C++的struct可以添加成員函數(shù),而C不可以
2.C++的struct可以使用訪問控制關(guān)鍵字(public private protected),而C不可以。
3.C++的struct在定義對象時可以忽略,而在C中不可以。
SPos spos; // C++
struct SPos spos; // C/C++
(2)對象創(chuàng)建/實例化
<1>直接創(chuàng)建 --類作為類型定義變量 --棧上創(chuàng)建
//基本類型
int a=10;
int b(10);//等價于int b=10;
//類類型
class Demo{};
//創(chuàng)建變量格式:類名 對象名; // 創(chuàng)建對象,自動調(diào)用默認構(gòu)造函數(shù)
Demo d;
Student zhangsan("張三",true,21);//創(chuàng)建對象時可以直接賦值
//創(chuàng)建匿名對象格式:類名(); // 創(chuàng)建匿名對象
Demo();
<2>動態(tài)創(chuàng)建 --堆上創(chuàng)建
//基本類型
int* p = new int;
delete p;
p = NULL;
//對象指針new可以為對象設置初始值
int* p = new int(100);
cout << *p << endl;
//類類型
class Demo{};
//類名* 對象指針 = new 類名;// 調(diào)用默認構(gòu)造函數(shù)
Demo* d=new Demo;
Student* lisi =new Student("李四",false,22);//可以為對象設置初始值
delete 對象指針;
<3>動態(tài)創(chuàng)建數(shù)組 -- 堆上創(chuàng)建
//基本類型
int* pa = new int[10];
delete pa;// 只釋放p[0]
delete [] pa;// 釋放全部數(shù)組
//類類型
Demo* d = new Demo[10];
delete [] d;
d = NULL;
//注意:對象數(shù)組指針new不可以為對象設置初始值。
int* pa = new int[10](100);//錯誤:parenthesized initializer
注意:
1.空結(jié)構(gòu)體與空類的大小(sizeof)為1,主要在于初始化/實例化時,編譯器給變量/對象分配內(nèi)存(地址),內(nèi)存最小單位為1個字節(jié)。通常,sizeof(類型) == sizeof(變量)。
為什么空的class需要分配一個字節(jié)?
類的實例化就是在內(nèi)存中分配一塊地址,空類同樣可以被實例化,每個實例在內(nèi)存中都有一個獨一無二的地址,因此需要一塊內(nèi)存來存放該地址,而c++中可以申請的最小單位就是一個字節(jié)。
3.方法
(1)構(gòu)造函數(shù)
<1>語法
類名(參數(shù)){
函數(shù)體
}
<2>特點
1.構(gòu)造函數(shù)的函數(shù)名與類名相同
2.在對象被創(chuàng)建時自動執(zhí)行
3.沒有返回值類型、也沒有返回值
4.可以有多個構(gòu)造函數(shù)(構(gòu)造函數(shù)的重載),但是在調(diào)用時,會根據(jù)需要調(diào)用其中一個構(gòu)造函數(shù)。
<3>構(gòu)造函數(shù)
1.默認構(gòu)造函數(shù):沒有參數(shù)的寫法。當沒有編寫構(gòu)造函數(shù)時,如果創(chuàng)建對象,系統(tǒng)會自動調(diào)用默認的構(gòu)造函數(shù),但是該構(gòu)造函數(shù)的函數(shù)體空的,不能夠達到初始化的目的,編譯可能不會出錯,執(zhí)行時會吐核。但是如果定義了構(gòu)造函數(shù),不管是否合適,編譯器都不會調(diào)用默認的構(gòu)造函數(shù)。
2.構(gòu)造函數(shù)的重載:使用類名相同的構(gòu)造函數(shù),并且是有參數(shù)的,有賦值功能。主要用在從外邊傳入已經(jīng)分配好的動態(tài)內(nèi)存,提供一個接口。
<4>調(diào)用時機
1.使用類直接創(chuàng)建對象
2.new動態(tài)創(chuàng)建
<5>構(gòu)造函數(shù)的作用
1.給創(chuàng)建的對象建立一個標識符
2.為對象數(shù)據(jù)成員開辟內(nèi)存空間
3.完成對象數(shù)據(jù)成員的初始化
例子:
class Array{
private:
int* data;
int length;
public:
Array(){//構(gòu)造函數(shù)
data=NULL;
length=0;
}
Array(int* d,int l){//構(gòu)造函數(shù)的重載
data=d;
length=l;
}
};
int main{
//調(diào)用默認的構(gòu)造函數(shù)
Array temp;
//調(diào)用構(gòu)造函數(shù)的重載:
int* list=new int[10];//創(chuàng)建了動態(tài)分配內(nèi)存
for(int i=0;i<10;++i){
list[i]=i;
}
Array(list,10);//調(diào)用上面的構(gòu)造函數(shù)重載,從外邊傳入已經(jīng)分配好的動態(tài)內(nèi)存。
}
(2)初始化列表
初始化類的成員有兩種方式,一是使用初始化列表,二是在構(gòu)造函數(shù)體內(nèi)進行賦值操作。
主要是性能問題,使用初始化列表少了一次調(diào)用拷貝構(gòu)造函數(shù)的過程,這對于數(shù)據(jù)密集型的類來說,是非常高效的。
<1>語法
類名(參數(shù)):成員變量1(參數(shù)1),成員變量2(參數(shù)2){
//函數(shù)體
}
<2>作用
初始化非靜態(tài)成員變量
<3>說明
1.必須使用初始化列表的情況:
- 常量成員:因為常量只能初始化不能賦值,所以必須放在初始化列表里面。
- 引用類型,引用必須在定義的時候初始化,并且不能重新賦值,所以也要寫在初始化列表里面。
- 沒有默認構(gòu)造函數(shù)的類類型,因為使用初始化列表可以不必調(diào)用默認構(gòu)造函數(shù)來初始化,而是直接調(diào)用拷貝構(gòu)造函數(shù)初始化。
2.初始化列表與構(gòu)造函數(shù)內(nèi)部成員賦值的區(qū)別: 成員變量初始化與成員變量賦值
注意:能使用初始化列表的時候盡量使用初始化列表
例子:(一般都是寫成初始化列表的形式)
把上面的構(gòu)造函數(shù)寫成初始化列表的方式:
Array():data(NULL),length(0){}
Array(int* d,int l):data(d),length(l){}
<4>成員變量的初始化順序
class Demo1{};
class Demo2{};
class Demo3{};
class Test{
public:
Test():d1(),d3(),d2(){}
private:
Demo1 d1;
Demo2 d2;
Demo3 d3;
int main(){
Test test;
//初始化順序結(jié)果為:d1,d2,d3。
}
成員變量在使用初始化列表初始化時,與構(gòu)造函數(shù)中初始化成員列表的順序無關(guān),只與定義成員變量的順序有關(guān)。一般情況定義成員變量的順序和初始化成員列表順序一致。
<5>代碼(賬單bill)
#include <iostream>
#include <vector>
using namespace std;
class Record{
private:
string name;
int count;
float price;
float off;
public:
Record(string name,int count,float price):name(name),count(count),price(price),off(1){}
Record(string name,int count,float price,float off):name(name),count(count),price(price),off(off){}
float GetTotal(){
return count*price*off;
}
float GetOff(){
return count*price*(1-off);
}
void Print(){
cout << name << '\t' << count << "\t¥" << price << "\t¥" << GetTotal() << "\t¥" << GetOff() << endl;
}
};
class Bill{
private:
vector<Record> records;
public:
void Add(Record r){
records.push_back(r);
}
void Print(){
cout << "物品\t數(shù)量\t單價\t總價\t節(jié)省\n";
cout << "-------------------------------------" << endl;
float totalPrice = 0;
float totalOff = 0;
for(int i=0;i<records.size();++i){
records[i].Print();
totalPrice+= records[i].GetTotal();
totalOff+=records[i].GetOff();
}
cout << "-------------------------------------" << endl;
cout << "總價:¥" << totalPrice << '\t' << "總節(jié)省:¥" << totalOff << endl;
}
};
int main(){
/*
Record r("蘋果",3,3.5);
Record r2("桔子",4,5.5);
Record r3("梨",2,1.5);
Record("香蕉",4,4.5);
*/
Bill b;
b.Add(Record("蘋果",3,3.5));
b.Add(Record("桔子",4,5.5));
b.Add(Record("梨",2,1.5,0.7));
b.Add(Record("香蕉",4,4.5,0.8));
b.Print();
}
(3)析構(gòu)函數(shù)
構(gòu)造函數(shù)是必須的,但是析構(gòu)函數(shù)有時不需要??!,當沒有申請新的空間時,沒有必要編寫析構(gòu)函數(shù),來釋放空間,系統(tǒng)會自動調(diào)用系統(tǒng)的析構(gòu)函數(shù)。
<1>語法
~類名(){
函數(shù)體
}
<2>特點
1.析構(gòu)函數(shù)的函數(shù)名與類名相同
2.函數(shù)名前必須有一個~
3.沒有返回值類型、也沒有返回值
4.只能有一個析構(gòu)函數(shù)
5.沒有參數(shù)
<3>調(diào)用時機
1.對象離開作用域(作用域是以{ }開始和結(jié)束的)
2.delete:調(diào)用delete時執(zhí)行,與作用域無關(guān)。
<4>默認析構(gòu)函數(shù)
類中沒有顯式定義的析構(gòu)函數(shù),編譯器就會自動為該類型生成默認析構(gòu)函數(shù)
<5>作用
釋放對象所申請占有的資源
<6>析構(gòu)順序
析構(gòu)順序和聲明順序完全相反,最先構(gòu)造的最后析構(gòu)。
C++RAII機制:
RAII(資源的取得就是初始化,Resource Acquisition Is Initialization)。步驟:1 申請資源;2 使用資源;3 釋放資源。C++語言的一種管理資源、避免泄漏的慣用法。C++標準保證任何情況下,已構(gòu)造的對象最終會銷毀,即它的析構(gòu)函數(shù)最終會被調(diào)用。簡單的說,RAII 的做法是使用一個對象,在其構(gòu)造時獲取資源,在對象生命期控制對資源的訪問使之始終保持有效,最后在對象析構(gòu)的時候釋放資源。 -- 百度百科
new/delete與malloc()與free()的區(qū)別?
1.new/delete是C++關(guān)鍵字,需要編譯器支持。malloc/free是庫函數(shù),需要頭文件支持<stdlib.h>
2.new操作符內(nèi)存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉(zhuǎn)換,故new是符合類型安全性的操作符。而malloc內(nèi)存分配成功則是返回void * ,需要通過強制類型轉(zhuǎn)換將void指針轉(zhuǎn)換成我們需要的類型。
3.new和delete會自動調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù);但是malloc和free不會自動調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
4.使用new操作符申請內(nèi)存分配時無須指定內(nèi)存塊的大小,編譯器會根據(jù)類型信息自行計算。而malloc則需要顯式地指出所需內(nèi)存的尺寸。
5.new內(nèi)存分配失敗時,會拋出bac_alloc異常。malloc分配內(nèi)存失敗時返回NULL。
(4)this指針
this 是 C++ 中的一個關(guān)鍵字,也是一個 const 指針,它指向當前對象,通過它可以訪問當前對象的所有成員。所謂當前對象,是指正在使用的對象。例如對于stu.show(),stu 就是當前對象,this 就指向 stu。
<1>作用域
類的內(nèi)部。
<2>特點
- 類的一個自動生成、自動隱藏的私有成員
- 每個對象僅有一個this指針
- 當一個對象被創(chuàng)建時,this指針就存放指向?qū)ο髷?shù)據(jù)的首地址
- 不是對象本身的一部分,不會影響sizeof(對象)的結(jié)果
- 不能在 static 成員函數(shù)中使用
如果成員函數(shù)形參與成員變量同名,使用
this->做為前綴區(qū)分。
class Student{
public:
void setname(string name);
void setage(int age);
void show();
private:
string name;
int age;
};
void Student::setname(string name){
this->name = name;//創(chuàng)建的類中成員變量也有name,使用name=name是無效的
//它的形參是name,和成員變量name重名,如果寫作name = name;
//這樣的語句,就是給形參name賦值,而不是給成員變量name賦值
}
void Student::setage(int age){
this->age = age;
}
void Student::show(){
cout<<this->name<<"的年齡是"<<this->age<<endl;
}
void Student::printThis(){
cout<<this<<endl;
}
int main(){
Student *pstu = new Student;//創(chuàng)建了class指針
pstu -> setname("jack");//必須通過->來訪問
pstu -> setage(16);
pstu -> show();//jack的年齡是16
Student *pstu1 = new Student;
pstu1 -> printThis();
cout<<pstu1<<endl;
輸出一樣:0x7b17d8 0x7b17d8
Student *pstu2 = new Student;
pstu2 -> printThis();
cout<<pstu2<<endl;
輸出一樣:0x7b17f0 0x7b17f0
}
- 注意,
this是一個指針,要用->來訪問成員變量或成員函數(shù); -
this雖然用在類的內(nèi)部,但是只有在對象被創(chuàng)建以后才會給 this 賦值,并且這個賦值的過程是編譯器自動完成的,不需要用戶干預,用戶也不能顯式地給this賦值。本例中,this 的值和 pstu 的值是相同的。
<3>實質(zhì):
this實際上是成員函數(shù)的一個形參,在調(diào)用成員函數(shù)時將對象的地址作為實參傳遞給this。不過 this 這個形參是隱式的,它并不出現(xiàn)在代碼中,而是在編譯階段由編譯器默默地將它添加到參數(shù)列表中。
C++函數(shù)傳參的三種方式:
訪問內(nèi)存的方式:變量,指針,引用
1.傳值
//void Swap(int a,int b);
//Swap(n,m);
//等價于:int a=n;int b=m;(賦值操作,不能修改值)
2.傳地址/指針
////void Swap(int* a,int* b);
//Swap(&n,&m);
//等價于:int* a=&n;int* b=&m;(把地址傳進去,可以修改)
3.傳引用
//void Swap(int& a,int& b);
//Swap(n,m);
//等價于:int& a=n;int& b=m;(引用,可以修改)
(5)引用(別名)
<1>語法
- 聲明:
const 類型名& 對象名/類型名& 對象名 - 使用:與對象變量、基本類型變量一樣
int a=10;
int& b=a;
cout << "&a:" <<&a <<'\t'<<"a:"<<a<<endl;
cout << "&b:" <<&b <<'\t'<<"b:"<<b<<endl;
a=100;
cout << "&a:" <<&a <<'\t<<'"a:"<<a<<endl;
cout << "&b:" <<&b <<'\t'<<"b:"<<b<<endl;
//執(zhí)行結(jié)果:a和b的地址一樣,值也一直一樣,不管修改a的值或者b的值,都一樣
&a:0x61ff08 a:10
&b:0x61ff08 b:10
&a:0x61ff08 a:100
&b:0x61ff08 b:100
引用其實就是一個別名,
a與b代表的是相同的對象。一個變量可以有多個別名。
int a=10;
int& c=a;
int& d=a;
cout << "&a:" << &a <<" "<< a <<endl;
cout << "&c:" << &c <<" "<< c <<endl;
cout << "&d:" << &d <<" "<< d <<endl;
/執(zhí)行結(jié)果:
&a:0x61ff00 10
&c:0x61ff00 10
&d:0x61ff00 10
<2>何處使用引用?
- (1)函數(shù)的參數(shù)列表
//使用指針可以同樣實現(xiàn)
void Swap(int& n,int& m){//作為參數(shù)
//cout << "&n:" << &n << "\t&m:" << &m << endl;//同一塊內(nèi)存
int t=m;
m=n;
n=t;
}
int main(){
int n=10;
int m=100;
//cout << "&n:" << &n << "\t&m:" << &m << endl;//和上面是同樣的地址
Swap(n,m);
}
- (2)函數(shù)的返回值
//當函數(shù)有多個返回值時,return可以返回一個,其他的可以使用引用來返回和指針類似
int divide(int n,int m,int& mod){//return返回div,mod以引用的方式返回
int div=n/m;
mod=n%m;
return div;
}
int mod;
int div=divide(n,m,mod);
- (3)作為成員變量 -- 對象初始化時,必須顯示初始化
對象初始化時,必須顯示初始化
class Simple{
int& n;
public:
//引用成員變量必須在構(gòu)造函數(shù)初始化列表中初始化
Simple(int& n):n(n){}
void Print(){
cout << "&n:" << &n << '\t' << n << endl;
}
};
int main(){
int m=10;
Simple s(m);
s.Print();
m=100;
s.Print();
}
//執(zhí)行結(jié)果:
&n:0x61ff04 10
&n:0x61ff04 100
注意:如果成員變量是指針,也可以達到這種效果,但是如果是變量就不行了。
<3>引用的特點
- 1.引用必須初始化
int& b=a; - 2.引用必須使用變量來初始化,初始化常量是沒有意義的。
int& b=10;(錯誤) - 3.引用初始化后不能修改,只能是一個變量的別名,不能修改為其他變量的別名,可以修改其值。
int a=10,c=20;
int& b=a;
//下面的例子:修改b的值就是修改a的值,并沒有改變b是a別名的事實,只是改變了a或者b的值。
b=12;
//b=c;//錯誤。已經(jīng)是a的別名了,不能再修改稱為c的別名了
<4>為何使用引用?
- (1)避免對象復制
- (2)避免傳遞空指針
- (3)使用方便
<5>引用的作用
取代指針:都可以通過函數(shù)修改函數(shù)外部的值。
有時使用引用而不是指針:指針可能是空的,會吐核。
<6>引用和指針的區(qū)別
- 1.指針指向一塊內(nèi)存,它的內(nèi)容是所指內(nèi)存的地址;引用是某塊內(nèi)存的別名
- 2.引用只能在定義時被初始化一次,之后不可變;指針可變;(特點3)
- 3.引用不能為空,指針可以為空;
- 4.引用使用時無需解引用
*,指針需要解引用; - 5.
sizeof 引用得到的是所指向的變量/對象的大小,而sizeof 指針得到的是指針本身的大??; - 6.對于引用類型的成員變量,所屬類的大小是按照指針大小計算,自身大小按照自身類型計算。(計算類的sizeof時并不計算成員函數(shù),只計算成員變量)
//各種類型指針的大小(32位的編譯器是4,64位的是8)
int* pi =&i;
char* pc=&c;
bool* pb=&b;
float* pf=&f;
double*pd=&d;
cout << "sizeof(pi):" << sizeof(pi) <<endl;
cout << "sizeof(pc):" << sizeof(pc) <<endl;
cout << "sizeof(pb):" << sizeof(pb) <<endl;
cout << "sizeof(pf):" << sizeof(pf) <<endl;
cout << "sizeof(pd):" << sizeof(pd) <<endl;
//各種引用的大小
int& ri=i;
char& rc=c;
bool& rb=b;
float& rf=f;
double& rd=d;
cout << "sizeof(ri):" << sizeof(ri) <<endl;//4
cout << "sizeof(rc):" << sizeof(rc) <<endl;//1
cout << "sizeof(rb):" << sizeof(rb) <<endl;//1
cout << "sizeof(rf):" << sizeof(rf) <<endl;//4
cout << "sizeof(rd):" << sizeof(rd) <<endl;//8
引用類型的成員變量在計算類/對象的sizeof和單獨計算時是不同的。
class Reference{
int& ri;
char& rc;
bool& rb;
float& rf;
double& rd;
public:
Reference(int& i,char& c,bool& b,float& f,double& d):ri(i),rc(c),rb(b),rf(f),rd(d){}
void PrintSize(){
cout << "sizeof(ri):" << sizeof(ri) <<endl;//4
cout << "sizeof(rc):" << sizeof(rc) <<endl;//1
}
};
sizeof(Reference)=20;
(6)拷貝/復制構(gòu)造函數(shù)
<1>語法
類名(類名& 形參){
函數(shù)體
}
或者:
類名(const 類名& 形參){
函數(shù)體
}
只有拷貝構(gòu)造函數(shù)必須使用引用,其他所有函數(shù)使用引用只是避免對象傳參時多余的拷貝構(gòu)造。不使用引用會調(diào)用拷貝構(gòu)造函數(shù),但是該函數(shù)不使用引用,自己沒法調(diào)用自己啊。
<2>調(diào)用時機
1.手動調(diào)用
類名 對象名; // 調(diào)用默認構(gòu)造函數(shù)
類名 對象2 = 對象1; // 調(diào)用復制構(gòu)造函數(shù)
類名 對象3(對象1); // 調(diào)用復制構(gòu)造函數(shù)
2.自動調(diào)用
-
一個對象作為函數(shù)參數(shù),以值傳遞的方式傳入函數(shù)體
參數(shù)前沒有加引用,且沒有拷貝構(gòu)造函數(shù),會造成多次釋放,解決:拷貝構(gòu)造函數(shù) - 一個對象作為函數(shù)返回值,以值從函數(shù)返回
- 一個對象需要通過另外一個對象進行初始化
- 一個對象拷貝構(gòu)造,它的成員對象自動調(diào)用拷貝構(gòu)造
- 子對象拷貝構(gòu)造父對象自動調(diào)用拷貝構(gòu)造。
<3>實例
#include <iostream>
#include <cstring>
using namespace std;
class Array{
public:
int* data;
int size;
public:
Array():data(NULL),size(0){
cout << "Array Default Construction" << " this:" << this << " &data:"<< data <<endl;
}
Array(const Array& arr){//拷貝/賦值構(gòu)造函數(shù)(也可以寫成初始化列表的形式)
cout << arr.data << endl;
data=new int[arr.size];
memcpy(data,arr.data,sizeof(int)*arr.size);//int類型可以這樣拷貝
size=arr.size;
cout << "Array Copy Construction:" <<" this:" << this << " &data:"<< data <<endl;
}
~Array(){
cout << "Array Destruction Free address:" <<" this:" << this << " &data:"<< data << endl;
delete [] data;
data=NULL;
size=0;
}
void push_back(int val){
++size;
int* tmp=new int[size];
memcpy(tmp,data,sizeof(int)*(size-1));
tmp[size-1]=val;
delete [] data;
data=tmp;
}
int get_size(){
return size;
}
int& at(int index){
return data[index];
}
};
//在arr前面加上&可以解決,否則就會調(diào)用拷貝構(gòu)造函數(shù),
//來隱式建立一個新的對象,并在對象作用域結(jié)束的時候調(diào)用析構(gòu)函數(shù)來銷毀新建的對象。
void PrintArray(Array arr){//4.copy(對象d),free(d)
for(int i=0;i<arr.get_size();++i){
cout << arr.at(i) << endl;
}
}
Array CreateArr(){
//不加構(gòu)造函數(shù)編譯可以通過,由于沒有初始化分配內(nèi)存會導致吐核。
Array arr;//1.default(對象a)
arr.push_back(100);
arr.push_back(200);
arr.push_back(300);
return arr;//2.copy(對象b),free(a)
}
int main(){
Array arr=CreateArr();//3.copy(對象c),free(b)
for(int i=0;i<arr.get_size();++i){//修改返回值(+10)
int& t=arr.at(i);//必須使用引用來接返回值
t +=10;
}
PrintArray(arr);
}//5.free(c)
- 實例分析:name指針被分配一次內(nèi)存,但是程序結(jié)束時該內(nèi)存卻被釋放了兩次,會導致崩潰!
- 原因:這是由于編譯系統(tǒng)在我們沒有自己定義拷貝構(gòu)造函數(shù)時,會在拷貝對象時調(diào)用默認拷貝構(gòu)造函數(shù),進行的是淺拷貝!即對指針name拷貝后會出現(xiàn)兩個指針指向同一個內(nèi)存空間
- 解決:在對含有指針成員的對象進行拷貝時,必須要自己定義拷貝構(gòu)造函數(shù),使拷貝后的對象指針成員有自己的內(nèi)存空間,即進行深拷貝,這樣就避免了內(nèi)存泄漏發(fā)生。
- RVO和NRVO機制:
返回值優(yōu)化(Return Value Optimization,簡稱RVO),是這么一種優(yōu)化機制:當函數(shù)需要返回一個對象的時候,如果自己創(chuàng)建一個臨時對象用戶返回,那么這個臨時對象會消耗一個構(gòu)造函數(shù)(Constructor)的調(diào)用、一個復制構(gòu)造函數(shù)的調(diào)用(Copy Constructor)以及一個析構(gòu)函數(shù)(Destructor)的調(diào)用的代價。而如果稍微做一點優(yōu)化,就可以將成本降低到一個構(gòu)造函數(shù)的代價,也就是將內(nèi)容直接構(gòu)造到左值中,中間不生成臨時變量。(匿名對象)。
NRVO,即Named Return Value Optimization,有名字的返回值優(yōu)化。
class BigObject{};
BigObject foo(){return BigObject(); // RVO(匿名對象)}
BigObject bar(){
BigObject localObj;
return localObj; // NRVO(有名字的對象)
}
gcc/clang自動RVO/NRVO優(yōu)化,不執(zhí)行拷貝構(gòu)造函數(shù),所以在執(zhí)行時看不到執(zhí)行了拷貝構(gòu)造函數(shù)。當不忽略拷貝構(gòu)造函數(shù),可以在編譯命令添加選項禁止
-fno-elide-constructors;
VC在Debug環(huán)境下返回值執(zhí)行拷貝構(gòu)造函數(shù),在Release環(huán)境下實施RVO/NRVO優(yōu)化。
<4>默認拷貝構(gòu)造函數(shù)
如果沒有自己定義拷貝構(gòu)造函數(shù),編譯器會調(diào)用默認的拷貝構(gòu)造函數(shù),和之前不一樣的是(默認構(gòu)造函數(shù)和默認析構(gòu)函數(shù)不會做任何事情),但是默認的拷貝構(gòu)造函數(shù)會執(zhí)行淺拷貝,即只拷貝內(nèi)存地址.
- 作用:復制一個已經(jīng)存在的對象
- 本質(zhì):內(nèi)存拷貝(淺拷貝)
- 解決:必須要自己定義拷貝構(gòu)造函數(shù),使拷貝后的對象指針成員有自己的內(nèi)存空間,即進行深拷貝,這樣就避免了內(nèi)存泄漏發(fā)生。
- 問題:
1.一個類可以有多個拷貝構(gòu)造函數(shù),加上const。
2.拷貝構(gòu)造函數(shù)的參數(shù)必須是引用;
3.禁止使用拷貝構(gòu)造函數(shù):
目的:編寫的類不允許其他人拷貝。
兩種方法:
1)添加私有的拷貝構(gòu)造函數(shù)c++98:
private:
Simple(const Simple&);//私有的拷貝構(gòu)造函數(shù)聲明C++98
報錯:Simple::Simple(const Simple&)' is private within this context
2)拷貝構(gòu)造函數(shù)=delete;
Simple(const Simple&)=delete;//C++11
報錯:use of deleted function 'Simple::Simple(const Simple&)
<5>什么時候需要自己定義拷貝構(gòu)造函數(shù)
- 1.三大定律(The BigThree)。
- 2.一般來說你在類中進行了new操作,你就需要析構(gòu)函數(shù),在你需要析構(gòu)函數(shù)的類中,一般需要加上挎貝構(gòu)造函數(shù)和賦值函數(shù)。
- 3.下面三種對象需要調(diào)用拷貝構(gòu)造函數(shù)(有時也稱“復制構(gòu)造函數(shù)”)
1)。 一個對象作為函數(shù)參數(shù),以值傳遞的方式傳入函數(shù)體;
2)。一個對象作為函數(shù)返回值,以值傳遞的方式從函數(shù)返回;
3)。一個對象用于給另外一個對象進行初始化(常稱為復制初始化)
通常的原則是:①對于凡是包含動態(tài)分配成員或包含指針成員的類都應該提供拷貝構(gòu)造函數(shù);②在提供拷貝構(gòu)造函數(shù)的同時,還應該考慮重載"="賦值操作符號。
深拷貝和淺拷貝最根本的區(qū)別在于是否真正獲取一個對象的復制實體,而不是引用。
假設B復制了A,修改A的時候,看B是否發(fā)生變化:
- 如果B跟著也變了,說明是淺拷貝,拿人手短?。ㄐ薷亩褍?nèi)存中的同一個值)
- 如果B沒有改變,說明是深拷貝,自食其力?。ㄐ薷亩褍?nèi)存中的不同的值)
深拷貝(Memberwise Copy)與淺拷貝(Bitwise Copy):
- 淺拷貝:淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一個內(nèi)存空間,對帶有指針的類淺拷貝會引發(fā) memory leak,動態(tài)分配內(nèi)存的淺拷貝就是只拷貝了內(nèi)存地址,會造成二次/多少次釋放,解決方法:深拷貝。
- 深拷貝:深拷貝不但對指針進行拷貝,而且對指針指向的內(nèi)容進行拷貝,經(jīng)深拷貝后使這個增加的指針指向這個新的內(nèi)存。
(7)賦值運算符重載函數(shù)
- 類和結(jié)構(gòu)體可以直接賦值,但是數(shù)組不行。
<1>語法
類名& operater=(const 類名& 形參){
// 賦值操作
return *this;
}
只有拷貝構(gòu)造函數(shù)必須使用引用,其他所有函數(shù)使用引用只是避免對象傳參時多余的拷貝構(gòu)造
<2>調(diào)用時機
- 賦值
<3>默認賦值運算符重載函數(shù)
- 內(nèi)存拷貝(淺拷貝)
<4>理解
- 拷貝構(gòu)造函數(shù)用于解決初始化等三種情況而導致的內(nèi)存二次釋放問題;
- 賦值運算符重載函數(shù)用于解決類的賦值問題而導致的內(nèi)存二次釋放問題。
- 如果沒有定義賦值運算符重載,編譯器會調(diào)用默認的賦值操作(淺拷貝),如果自己定義了該函數(shù),則編譯器就會按照該函數(shù)來進行。
- 禁用賦值運算符重載函數(shù)與禁用拷貝構(gòu)造函數(shù)一樣。
初始化和賦值的區(qū)別:
1.普通情況下,初始化和賦值好像沒有什么特別去區(qū)分它的意義。int a=100;和int a;a=100之間仿佛沒有任何區(qū)別,但是對于c++的類就不同了:初始化不是賦值,初始化是創(chuàng)建變量的時候賦予其一個初始值,而賦值的含義是把對象的當前值擦除,用一個新值替代。
2.本質(zhì):賦值和初始化的區(qū)別:Simple s;Simple t;t=s;(賦值操作)。賦值是兩個對象都已經(jīng)構(gòu)造好了,初始化是其中一個對象還不存在。
3.出現(xiàn)的問題:
如果類中有指針的話,賦值操作還是會造成二次釋放(淺拷貝,調(diào)用了二次析構(gòu)函數(shù)),記住賦值操作不會調(diào)用拷貝構(gòu)造函數(shù),因為對象都已經(jīng)存在不需要再重新構(gòu)造。class k=t才會調(diào)用拷貝構(gòu)造函數(shù)。
<5>實例
class Test{
private:
int* p;
public:
Test():p(NULL){//默認構(gòu)造函數(shù)
cout <<this<< " Default Construction: "<< p<<endl;
}
Test(int n){//構(gòu)造函數(shù)重載
p=new int(n);
cout<<this <<" Construction: "<<p<<endl;
}
Test(const Test& s){//拷貝構(gòu)造函數(shù)
p=new int(*(s.p));
cout << this<<" Copy Construction: "<<p<<endl;
}
Test& operator=(const Test& s){//賦值運算符重載函數(shù)
if(this==&s) return *this;//1.判斷是否是自身賦值
if(NULL!=this->p){//2.刪除之前申請的內(nèi)存(防止之前的對象賦值過),防止內(nèi)存泄漏
delete p;
}
p=new int(*(s.p));//深拷貝
cout << this<<" Operator: "<<p<<endl;
return *this;//3.返回當前對象
}
~Test(){//析構(gòu)函數(shù)
cout << this<<" Destruction: "<<p<<endl;
delete p;
}
};
int main(){
Test s(10);//重載
//初始化操作
//Test s1=s;//拷貝
Test s2;//默認
//賦值操作不是初始化
s2=s;//賦值運算符重載函數(shù)
//(對于類來說,寫不寫賦值構(gòu)造函數(shù)都一樣),上面的等式可以寫成:s2.operate=(s);
//基本類型是沒有這種函數(shù)的。
}
對于類中的成員變量是非指針時,寫不寫賦值運算符都一樣。
<6>賦值運算符重載的禁用
和拷貝構(gòu)造函數(shù)的禁用一樣!??!
c++98和c++11兩種。
//(1)c++98
private:
類名& operator=(const 類名& 對象形參);//并且不在函數(shù)中實現(xiàn)
//(2)c++11
public:
類名& operator=(const 類名& 對象形參)=delete;
<7>三大定律(Rule of three/The Big Three)
如果類中明確定義下列其中一個成員函數(shù),那么必須連同其他二個成員函數(shù)編寫至類內(nèi),即下列三個成員函數(shù)缺一不可:
- 析構(gòu)函數(shù)(destructor)
- 拷貝構(gòu)造函數(shù)(copy constructor)
- 復制賦值運算符(copy assignment operator)
(8)友元細說
友元函數(shù)沒有 this 指針,因為友元不是類的成員。只有成員函數(shù)才有 this 指針。
<1>作用
非成員函數(shù)訪問類中的私有成員
<2>分類
- 1.全局友元函數(shù):
將全局函數(shù)聲明成友元函數(shù)。盡管友元函數(shù)的原型有在類的定義中出現(xiàn)過,但是友元函數(shù)并不是成員函數(shù),是全局函數(shù)。有兩種寫法:
//(1)在類中只寫【friend 函數(shù)聲明】,不寫其實現(xiàn),實現(xiàn)在類外面。
class 類名{
public:
friend void Func(參數(shù));
};
void Func(參數(shù)){
函數(shù)體;
}
//(2)在類中直接完成全局函數(shù)的實現(xiàn),在函數(shù)名前面加上friend。
class 類名{
public:
firend void Func(參數(shù)){
函數(shù)體;
}
};
- 2.友元類:
將整個類聲明為友元。類2中的所有成員都可以訪問類1中的私有成員。
class 類名1{
public:
friend 類名2;
};
- 3.友元成員函數(shù)
一個類中的某個成員函數(shù)需要訪問另一個類的私有成員,可以將這個成員函數(shù)寫成另一個類的友元成員函數(shù)。
需要分為.h和.cpp(聲明和實現(xiàn))兩個文件來編寫,有5個文件(兩個類的聲明,兩個類的實現(xiàn),及main函數(shù))。
注意:(1)如果一個文件需要知道類的成員信息,要include頭文件
(2)如果一個文件只需要只知道類名,可以使用類的前置聲明.
如果用反或者多用的話都會報錯。
<3>特點
- 單向性:
類A是類B的友元,但B不一定是類A的友元。 - 不可傳遞性
若類B是類A的友元,類C是B的友元,類C不一定是類A的友元 - 不能被繼承
父類是類A的友元,但是其父類的子類不是類A的友元。
(9)const限定符
<1>本質(zhì)
- 只讀(read only)
<2>const與變量和對象
- 格式
(1)const 類型 變量 = 初始值;
或者:類型 const 變量 = 初始值;//一般采用這種寫法,const在類型的后面
int const a=2;
(2)類型 const 對象;
- 理解
1.定義時必須初始化
2.全局作用域聲明的const變量默認作用域是定義所在文件
3.const對象只能調(diào)用const成員函數(shù)
4.const與宏定義#define的區(qū)別
<3>const與指針

<4>const與引用
類型 const& 變量=初始值
const 類型& 變量=初始值
int a=10;
const int& b=a;
b=20;//錯誤
a=20;//可以
引用對象的值不可以改變。
<5>const與函數(shù)的參數(shù)和返回值

- 1.const修飾函數(shù)參數(shù)
如果函數(shù)內(nèi)部不會改變參數(shù)的值,參數(shù)定義成const& 類型,否則定義成&類型。
void Func(const Simple& s){
s.Print();
}
(1)不加引用會導致調(diào)用拷貝構(gòu)造函數(shù)(默認淺拷貝),造成多次釋放,所以加&
(2)不加const只能接受非const成員,不能接受const成員和匿名成員,
(這就是為什么拷貝構(gòu)造函數(shù)的參數(shù)要加const的原因,因為不知道傳入的參數(shù)是什么)
int main(){
Func(s);//非const對象
Func(k);//const對象
Func(Simple(100));//匿名對象
}
- 2.const修飾函數(shù)的返回值
函數(shù)的返回值不能改變,常用于字符串/指針
//在上面的Simple類中編寫成員函數(shù)
const int Get(){//這種情況是沒有意義的,因為copy的一份返回值賦值給a。
return n;
}
int a=s.Get();
*********************************
const在字符串和指針中的使用
//在String類中編寫成員函數(shù)
const char* c_str(){
return str;
}
char* s=s.c_str();//不能接收,因為返回值是const char*。
const char* s=s.c_str();//必須使用const char* 來接收,返回值是一個常量指針。
<6>const修飾的成員變量和成員函數(shù)
- 1.const修飾成員變量
(1)不能在類聲明中初始化const數(shù)據(jù)成員變量或者基本類型(C++98不行),但是C++11可以。
(2)const成員變量只能在類構(gòu)造函數(shù)的初始化列表中初始化
class Simple{
int m=0;//C++98不允許成員變量在類聲明中直接初始化
int const n=0;
private:
Simple():n(0){}//const成員變量必須在初始化列表中初始化
Simple(int n):n(n){}
};
int main(){
Simple s;
Simple t=s;//拷貝構(gòu)造,可以使用拷貝構(gòu)造函數(shù)
t=s;//如果存在const成員變量,不能使用賦值運算符
}
(3)注意:在c++11中成員變量在類定義中有初始化,并且在構(gòu)造函數(shù)中也有初始化,編譯器會自動忽略類定義中的初始化。
(4)應用:const成員變量一般用于類定義后不可修改的信息,例如:學生學號
使用const成員變量不能省略構(gòu)造函數(shù)(引用類型的成員變量相同)
使用const成員變量不能使用賦值運算符重載函數(shù)
- 2.const修飾成員函數(shù):
(1)非const的成員函數(shù),可以修改成員變量的值,只能被非const的對象調(diào)用
(2)const成員函數(shù),不允許修改成員變量的值,可以被const對象和非const對象調(diào)用
class Simple{
int n;
public:
Simple(int n):n(n){}
void Print()const{//const成員函數(shù),const成員函數(shù)
cout << n << endl;
}
void Increase(){//非const的成員函數(shù)
++n;
}
};
int main(){
Simple s(10);//定義非const對象
s.Increase();//非const對象可以調(diào)用非const成員函數(shù)
s.Print();//非const對象也可以調(diào)用const成員函數(shù)
const Simple k(20);//定義const對象
k.Print();//const對象可以調(diào)用const成員函數(shù)
//k.Increase();//const對象不能調(diào)用非const成員函數(shù)
}
(1)全局函數(shù)是不能被const修飾的,const只能修飾成員函數(shù)。
(2) 必須在成員函數(shù)的聲明和定義后都加上const
<7>const修飾總結(jié)

(10)static限定符
<1>本質(zhì)
- 生存周期:整個程序的生存周期。
- 作用域:屬于類,不屬于對象
<2>語法
- 聲明
class 類名{
//靜態(tài)成員變量
static 類型 變量;
//靜態(tài)成員函數(shù)
static 返回類型 函數(shù)(形參列表);//在返回類型前加static
};
- 定義
//靜態(tài)成員變量
類型 類名::變量=賦值;///靜態(tài)成員變量必須在類外面定義初始化,且沒有static
//靜態(tài)成員函數(shù)
返回類型 類名::函數(shù)(形參列表){//注意:定義函數(shù)時沒有static
函數(shù)體;
}
靜態(tài)成員變量必須在類外面初始化,且沒有static限定符。
- 調(diào)用
(1)通過類名(Class Name)調(diào)用:
類名::函數(shù)(實參列表);
(2)通過對象(Object)調(diào)用:
對象.函數(shù)(實參列表);
<3>規(guī)則
-
static只能用于類的聲明中,定義不能標示為static - 非靜態(tài)是可以訪問靜態(tài)的方法和函數(shù)
- 靜態(tài)成員函數(shù)可以設置private,public,protected訪問權(quán)限
- 靜態(tài)成員變量必須在類外面定義初始化
- 靜態(tài)成員變量在對象之間可以共享數(shù)據(jù)(比如實現(xiàn)一個數(shù)據(jù)的遞增)
- 只有靜態(tài)成員函數(shù)才可以使用【類名::函數(shù)名/變量名】來訪問。
- 靜態(tài)成員變量的生存周期和函數(shù)一樣長
- 對象的大小不包含靜態(tài)成員變量
<4>禁忌
- 靜態(tài)成員函數(shù)不能訪問非靜態(tài)函數(shù)或者變量(用來修改靜態(tài)成員變量的值)
- 靜態(tài)成員函數(shù)不能使用
this關(guān)鍵字(因為屬于類,不屬于對象) - 靜態(tài)成員函數(shù)不能使用cv限定符(const與volatile)
<5>實例
class StaticSimple{
string name;
static int num;//行號(1)
int count;//記錄對象說了多少句話
public:
StaticSimple(const string& name):name(name),count(0){}//構(gòu)造函數(shù)
void Print(const string& s){//const不能修飾成員函數(shù),因為count是遞增的
cout << ++num<< " "<<name<<":\""<< s << "\""<< ++count <<endl;//(2)(5)
}
static void PrintComment(const string& s){
cout << ++num<<" "<<s<<endl;//不能使用count(3)
}
};
// 靜態(tài)成員必須在類外定義初始化
// static 關(guān)鍵字只能用在靜態(tài)成員變量的聲明前,不能用在定義初始化
/*static*/ int StaticSimple::num = 0;
int main(){
StaticSimple s("LerBon");
s.Print("Hello");
//StaticSimple::Print("Hello");//(4)
s.PrintComment("Hello World");
StaticSimple::PrintComment("Hello World");//(4)
StaticSimple t("Jobs");
t.Print("Apple");//靜態(tài)變量在對象之間可以共享數(shù)據(jù),num(行號)的自增
s.Print("What?");
t.Print("Big Apple");
s.Print("Ok!");
}
<6>static總結(jié)

<7>static const/static const限定符
static const/const static修飾的成員變量在類初始化必須是數(shù)字類型(int和char),把char歸到數(shù)值類型,因為里面存的是ascii碼。
class Test{
static const int a=10;
static const char e='a';
//static const float b=3.14;//不行,必須在類外初始化
};
/*static*/ const float Test::b=3.14;//要加const,不能加static,為了規(guī)范一般加上省略。
- 總結(jié)
| 變量類型 | 聲明位置 |
|---|---|
| 一般成員變量 | 在構(gòu)造函數(shù)初始化列表中初始化 |
| const成員常量 | 必須在構(gòu)造函數(shù)初始化列表中初始化 |
| static成員變量 | 必須在類外初始化 |
| static const/const static成員變量 | 變量聲明處或者類外初始化 |
<8> 類和結(jié)構(gòu)體的sizeof
- 見疑惑?。?!
(11) 內(nèi)聯(lián)函數(shù)
inline -- 宏定義的接班人
編譯時將函數(shù)體代碼和實參代替函數(shù)調(diào)用語句。
<1>本質(zhì)
- 內(nèi)聯(lián)函數(shù)的代碼直接替換函數(shù)調(diào)用,省去函數(shù)調(diào)用的開銷
<2>條件
- 一般用在代碼比較簡單的函數(shù)
<3>語法
- 關(guān)鍵字inline必須與函數(shù)實現(xiàn)/定義體放在一起才能使函數(shù)成為內(nèi)聯(lián),將inline放在函數(shù)聲明前面不起任何作用,通常內(nèi)聯(lián)函數(shù)定義在頭文件中(.h),定義在實現(xiàn)中會報錯。
inline void Tickets::Get(){//inline必須寫在頭文件里面
--count;
++getcount;
cout << name << " leave " << count << " have " << getcount << endl;
}
inline /*static*/ void Tickets::UpdateCount(int n){
count =n;
}
- 定義在類聲明之中的成員函數(shù)將自動地成為內(nèi)聯(lián)函數(shù);
- 內(nèi)聯(lián)是自己定義的,編譯器在執(zhí)行時是否當作內(nèi)聯(lián),取決于編譯器自己的優(yōu)化。
<4>慎用內(nèi)聯(lián)
- 如果函數(shù)體內(nèi)的代碼比較長,使用內(nèi)聯(lián)將導致內(nèi)存消耗代價較高
- 如果函數(shù)體內(nèi)出現(xiàn)循環(huán),那么執(zhí)行函數(shù)體內(nèi)代碼的時間要比函數(shù)調(diào)用的開銷大
- 不要隨便地將構(gòu)造函數(shù)和析構(gòu)函數(shù)的定義體放在類聲明中
<5>理解
- C++編譯器直接將函數(shù)體插入在函數(shù)調(diào)用的地方;
- 內(nèi)聯(lián)函數(shù)沒有普通函數(shù)調(diào)用時的額外開銷(壓棧,跳轉(zhuǎn),返回);
- 內(nèi)聯(lián)函數(shù)是由編譯器的一種請求,因此編譯器可能拒絕這種請求。
- 宏代碼片段由預處理器處理,進行簡單的文本替換,沒有任何編譯過程;
- C++編譯器能夠進行優(yōu)化,一些函數(shù)即使沒有inline聲明,也可能被編譯器內(nèi)聯(lián)編譯
(12)運算符重載
<1>為什么要進行運算符重載
運算符重載是為了解決類對象之間的運算的,通常的運算符只用于算術(shù)運算,如常量int之間,因為編譯器已經(jīng)定義了;而一個類的兩個對象之間成員進行運算必須重新定義,讓編譯器在遇到對象運算時能按我們要求的進行運算,這就是運算符重載的意義,即重定義運算符.運算符重載的聲明operator 關(guān)鍵字告訴編譯器,它是一個運算符重載。
<2>語法
- 成員函數(shù)運算符重載
返回值類型 operator 運算符(參數(shù)){
函數(shù)體
}
- 友元函數(shù)運算符重載
friend 返回值類型 operator 運算符(形參列表) {
函數(shù)體
}
<3> 數(shù)學類的運算符重載(復數(shù)類)
class Complex{
private:
int real;
int imag;
public:
Complex():real(0),imag(0){}
Complex(int real):real(real),imag(0){}//單個參數(shù)的構(gòu)造函數(shù)可以實現(xiàn)int自動轉(zhuǎn)化成Complex對象
Complex(int real,int imag):real(real),imag(imag){}
}
void Print(){
cout << real << "+" << imag << "i" <<endl;
}
- 算數(shù)運算符(
+、-、*、/、%)
//算數(shù)運算符,成員函數(shù)加法
Complex operator+(const Complex& a)const{//不用返回引用,對局部變量的引用是沒有意義的
Complex res;//不要返回局部變量的引用,可以返回成員變量的引用
res.real=real+a.real;
res.imag=imag+a.imag;
return res;
}
//簡寫
Complex operator+(const Complex& a)const{
return Complex(real+a.real,imag+a.imag);//匿名對象
}
//友元加法
friend Complex operator(const Complex& a,const Complex& b){
return Complex(a.real+b.real,a.imag+b.imag);
}
測試:
Complex c1(1,2);
Complex c2(2,3);
(c1+c2).Print();//匿名對象
(c1+1).Print();//1會默認轉(zhuǎn)換成復數(shù)
//c1.operator+(Complex(1)).Print();//成員函數(shù)運算符重載
//operator+(c1,Complex(1)).Print();//友元函數(shù)運算符重載
(1+c1).Print();
//1.operator+(c1);錯誤
//operator+(Complex(1),c1).Print();////友元函數(shù)運算符重載
- 關(guān)系運算符(
==、!=)
//關(guān)系運算符
bool operator==(const Complex& a)const{//不加const會在后面的友元!=中報錯
return real==a.real && imag==a.imag;
}
friend bool operator!=(const Complex& a,const Complex& b){//!=
return !(a==b);
}
cout << (c1!=c2) << endl;
//c1.operator!=(c2);
//opearator!=(c1,c2);
//c1.operator!=(c2,X);//不寫friend表示是類的成員函數(shù),會用.調(diào)用,
- 單目運算符(
+、-)
//單目算數(shù)運算
Complex operator+(){//取正
return *this;
}
Complex operator-(){//取反
return Complex(-real,-imag);
}
- 前綴自增自減運算符(
++、--)
//前綴自增運算
Complex operator++(){//前綴++
++real;
return *this;
}
friend Complex operator++(Complex& a){
++a.real;
return a;
}
- 后綴自增自減運算符(
++、--)
//后綴自增運算
Complex operator++(int){//后綴++
Complex res=*this;
++real;
return res;
}
friend Complex operator++(Complex& a,int){//(這個是局部變量使用引用不能返回)
Complex res=a;
++a.real;
return res;
}
<4>流運算符重載
流運算符只能使用友元函數(shù)實現(xiàn),因為函數(shù)的第一個參數(shù)必須是流對象,不是類創(chuàng)建的對象。一般在流運算符中不加endl,在外面控制回車。
//流運算符
friend ostream& operator<<(ostream& os,const Complex& c){
os << c.real << '+' << c.imag<<'i';
return os;
}
friend istream& operator>>(istream& is,Complex& c);//沒有const,因為c是改變的
};
istream& operator>>(istream& is,Complex& c){
is >> c.real >> c.imag;
return is;
}
測試:
cin >> c;//等同于 operator>>(cin,c);
cout << c << endl;
cin和cout中的c是字符character的意思。
<5>中括號[ ]運算符重載(下標運算符重載)
標準庫中的可以直接使用【】來打印和修改,但是自己定義的類是不能打印和修改的,因此需要重載。只有成員函數(shù)
class String{
char* str;
public:
//operator[]的返回類型通常為引用,否則返回值是不能修改的,因為他是一個常量,不是變量。
//返回值不加引用可以打印,但是不能修改其值。
char& operator[](int index){//返回值類型由成員變量的類型決定。
return str[index];
}
};
int main(){
String s2("hello");//自己定義的類必須重載[ ]
s2[0]=toupper(s2[0]);
cout << s2[0] << endl;
}
<6>小括號()的重載(函數(shù)調(diào)用運算符重載)
class Simple{
int n;
public:
Simple(int n):n(n){}
void operator()(int a){
cout << (n+a) << endl;
}
};
int main(){
Simple s(10);
s(12);//仿函數(shù),像函數(shù)
//s.operator()(12);
輸出:22
}
<7>運算符重載的規(guī)則
- 不能重載的運算符:成員運算符
.、作用域運算符::、sizeof、條件運算符?: - 不允許用戶自定義新的運算符,只能對已有的運算符進行重載
- 重載運算符不允許改變運算符原操作數(shù)的個數(shù);
- 重載運算符不能改變運算符的優(yōu)先級
- 重載運算符函數(shù)不能有默認的參數(shù),會導致參數(shù)個數(shù)不匹配
(13)構(gòu)造函數(shù)形參
<1>構(gòu)造函數(shù)形參的默認值
class Simple{
int n;
public:
//Simple():n(0){}
Simple(int n=0):n(n){}//加默認參數(shù),可以使Simple t有意義,在不寫其他構(gòu)造函數(shù)時不會報錯。
void operator()(int a){
n+=a;
}
friend ostream& operator<<(ostream& os,const Simple& s){
os<<s.n;
return os;
}
};
int main(){
Simple t;
cout << t <<endl;//0
Simple s(10);
cout << s << endl;//10
s(12);//s.operator()(12);
cout << s <<endl;//12
}
<2>禁止單參默認轉(zhuǎn)換
關(guān)鍵字:explicit
如果構(gòu)造函數(shù)只有一個參數(shù),使用時存在默認轉(zhuǎn)換的情況。如果在單參構(gòu)造函數(shù)前加上關(guān)鍵字explicit,可以禁止默認轉(zhuǎn)換的情況。
class A{
int n;
public:
explicit A(int i):n(i){
cout << "A(" << i << ")" << endl;
}
A(const A& m){//拷貝構(gòu)造函數(shù)
n=m.n;
cout << "Copy Construct" << endl;
}
A operator+(A b){
return A(n + b.n);//A(30) A(30)
}
};
int main(){
A a(10); //A(10)
A b(20);//A(20)
//錯誤
//1+a;//1.operator+(a);
//A operator+(const A&,const A&);//如果寫的是加法的友元函數(shù)重載就可以
//下面兩個會調(diào)用拷貝構(gòu)造函數(shù)
a.operator+(b);
a+b;
//在不加關(guān)鍵字時,下面是對的,1會默認轉(zhuǎn)換為A(1)為匿名對象,不會調(diào)用拷貝構(gòu)造函數(shù)。
//a+1;
//a.operator+(A(1));//默認轉(zhuǎn)換
//在不加explicit時,100會自動轉(zhuǎn)換為A(100),下面等式可以理解為A c=A(100),不會調(diào)用拷貝構(gòu)造函數(shù)。
A c=100;
}
因此:匿名對象初始化一個對象時,是不會調(diào)用拷貝構(gòu)造函數(shù)的。
