0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

以良好的方式編寫C++ class

Linux愛好者 ? 來源:博客園 ? 作者:melonstreet ? 2022-06-29 14:07 ? 次閱讀

以良好的方式編寫C++ class

假設(shè)現(xiàn)在我們要實現(xiàn)一個復數(shù)類complex,在類的實現(xiàn)過程中探索良好的編程習慣。

① Header(頭文件)中的防衛(wèi)式聲明

complex.h:
#ifndef__COMPLEX__
#define__COMPLEX__
classcomplex
{

}
#endif

防止頭文件的內(nèi)容被多次包含。

② 把數(shù)據(jù)放在private聲明下,提供接口訪問數(shù)據(jù)

#ifndef__COMPLEX__
#define__COMPLEX__
classcomplex
{
public:
doublereal()const{returnre;}
doubleimag()const{returnim;}
private:
doubelre,im;
}
#endif

③ 不會改變類屬性(數(shù)據(jù)成員)的成員函數(shù),全部加上const聲明

例如上面的成員函數(shù):

doublereal()`const`{returnre;}
doubleimag()`const`{returnim;}

既然函數(shù)不會改變對象,那么就如實說明,編譯器能幫你確保函數(shù)的const屬性,閱讀代碼的人也明確你的意圖。

而且,const對象才可以調(diào)用這些函數(shù)——const對象不能夠調(diào)用非const成員函數(shù)。

④ 使用構(gòu)造函數(shù)初始值列表

classcomplex
{
public:
complex(doubler=0,doublei=0)
:re(r),im(i){}
private:
doubelre,im;
}

在初始值列表中,才是初始化。在構(gòu)造函數(shù)體內(nèi)的,叫做賦值。

⑤如果可以,參數(shù)盡量使用reference to const

為complex 類添加一個+=操作符:

classcomplex
{
public:
complex&operator+=(constcomplex&)
}

使用引用避免類對象構(gòu)造與析構(gòu)的開銷,使用const確保參數(shù)不會被改變。內(nèi)置類型的值傳遞與引用傳遞效率沒有多大差別,甚至值傳遞效率會更高。

例如,傳遞char類型時,值傳遞只需傳遞一個字節(jié);引用實際上是指針實現(xiàn),需要四個字節(jié)(32位機)的傳遞開銷。但是為了一致,不妨統(tǒng)一使用引用。

⑥ 如果可以,函數(shù)返回值也盡量使用引用

以引用方式返回函數(shù)局部變量會引發(fā)程序未定義行為,離開函數(shù)作用域局部變量被銷毀,引用該變量沒有意義。但是我要說的是,如果可以,函數(shù)應該返回引用。

當然,要放回的變量要有一定限制:該變量的在進入函數(shù)前,已經(jīng)被分配了內(nèi)存。以此條件來考量,很容易決定是否要放回引用。而在函數(shù)被調(diào)用時才創(chuàng)建出來的對象,一定不能返回引用。

說回operator +=,其返回值就是引用,原因在于,執(zhí)行a+=b時,a已經(jīng)在內(nèi)存上存在了。

operator + ,其返回值不能是引用,因為a+b的值,在調(diào)用operator +的時候才產(chǎn)生。

下面是operator+= 與’operator +’ 的實現(xiàn):

inlinecomplex&complex::operator+=(constcomplex&r)
{
this->re+=r->re;
this->im+=r->im;
return*this;
}
inlinecomplexoperator+(constcomplex&x,constcomplex&y)
{
returncomplex(real(x)+real(y),//新創(chuàng)建的對象,不能返回引用
imag(x)+imag(y));
}

operator +=中返回引用還是必要的,這樣可以使用連續(xù)的操作:

c3+=c2+=c1;

⑦ 如果重載了操作符,就考慮是否需要多個重載

就我們的復數(shù)類來說,+可以有多種使用方式:

complexc1(2,1);
complexc2;
c2=c1+c2;
c2=c1+5;
c2=7+c1;

為了應付怎么多種加法,+需要有如下三種重載:

inlinecomplexoperator+(constcomplex&x,constcomplex&y)
{
returncomplex(real(x)+real(y),
imag(x+imag(y););
}
inlinecomplexoperator+(constcomplex&x,doubley)
{
returncomplex(real(x)+y,imag(x));

inlinecomplexoperator+(doublex,constcomplex&y)
{
returncomplex(x+real(y),imag(y));
}

⑧ 提供給外界使用的接口,放在類聲明的最前面

這是某次面試中,面試官大哥告訴我的。想想確實是有道理,類的用戶用起來也舒服,一眼就能看見接口。

Class with pointer member(s):記得寫B(tài)ig Three

C++的類可以分為帶指針數(shù)據(jù)成員與不帶指針數(shù)據(jù)成員兩類,complex就屬于不帶指針成員的類。而這里要說的字符串類String,一般的實現(xiàn)會帶有一個char *指針。帶指針數(shù)據(jù)成員的類,需要自己實現(xiàn)class三大件:拷貝構(gòu)造函數(shù)、拷貝賦值函數(shù)、析構(gòu)函數(shù)。

classString
{
public:
String(constchar*cstr=0);
String(constString&str);
String&operator=(constString&str);
~String();
char*get_c_str()const{returnm_data};
private:
char*m_data;
}

如果沒有寫拷貝構(gòu)造函數(shù)、賦值構(gòu)造函數(shù)、析構(gòu)函數(shù),編譯器默認會給我們寫一套。然而帶指針的類不能依賴編譯器的默認實現(xiàn)——這涉及到資源的釋放、深拷貝與淺拷貝的問題。在實現(xiàn)String類的過程中我們來闡述這些問題。

①析構(gòu)函數(shù)釋放動態(tài)分配的內(nèi)存資源

如果class里有指針,多半是需要進行內(nèi)存動態(tài)分配(例如String),析構(gòu)函數(shù)必須負責在對象生命結(jié)束時釋放掉動態(tài)申請來的內(nèi)存,否則就造成了內(nèi)存泄露。

局部對象在離開函數(shù)作用域時,對象析構(gòu)函數(shù)被自動調(diào)用,而使用new動態(tài)分配的對象,也需要顯式的使用delete來刪除對象。而delete實際上會調(diào)用對象的析構(gòu)函數(shù),我們必須在析構(gòu)函數(shù)中完成釋放指針m_data所申請的內(nèi)存。下面是一個構(gòu)造函數(shù),體現(xiàn)了m_data的動態(tài)內(nèi)存申請:

/*String的構(gòu)造函數(shù)*/
inline
String::String(constchar*cstr=0)
{
if(cstr)
{
m_data=newchar[strlen(cstr)+1];//這里,m_data申請了內(nèi)存
strcpy(m_data,cstr);
}
else
{
m_data=newchar[1];
*m_data='';
}
}

這個構(gòu)造函數(shù)以C風格字符串為參數(shù),當執(zhí)行

String*p=newString("hello");

m_data向系統(tǒng)申請了一塊內(nèi)存存放字符串hello

e4d49818-f75f-11ec-ba43-dac502259ad0.png

析構(gòu)函數(shù)必須負責把這段動態(tài)申請來的內(nèi)存釋放掉:

inline
String::~String()
{
delete[]m_data;
}

②賦值構(gòu)造函數(shù)與復制構(gòu)造函數(shù)負責進行深拷貝

來看看如果使用編譯器為String默認生成的拷貝構(gòu)造函數(shù)與賦值操作符會發(fā)生什么事情。默認的復制構(gòu)造函數(shù)或賦值操作符所做的事情是對類的內(nèi)存進行按位的拷貝,也稱為淺拷貝,它們只是把對象內(nèi)存上的每一個bit復制到另一個對象上去,在String中就只是復制了指針,而不復制指針所指內(nèi)容?,F(xiàn)在有兩個String對象:

Stringa("Hello");
Stringb("World");

a、b在內(nèi)存上如圖所示:

e4f4a0d6-f75f-11ec-ba43-dac502259ad0.png

如果此時執(zhí)行

b=a;

淺拷貝體現(xiàn)為:

e5013936-f75f-11ec-ba43-dac502259ad0.png

存儲World的內(nèi)存塊沒有指針所指向,已經(jīng)成了一塊無法利用內(nèi)存,從而發(fā)生了內(nèi)存泄露。不止如此,如果此時對象a被刪除,使用我們上面所寫的析構(gòu)函數(shù),存儲Hello的內(nèi)存塊就被釋放調(diào)用,此時b.m_data成了一個野指針。

來看看我們自己實現(xiàn)的構(gòu)造函數(shù)是如何解決這個問題的,它復制的是指針所指的內(nèi)存內(nèi)容,這稱為深拷貝

/*拷貝賦值函數(shù)*/
inlineString&String::operator=(constString&str)
{
if(this==&str)//①
return*this;
delete[]m_data;//②
m_data=newchar[strlen(str.m_data)+1];//③
strcpy(m_data,str.m_data);//④
return*this
}

這是拷貝賦值函數(shù)的經(jīng)典實現(xiàn),要點在于:

① 處理自我賦值,如果不存在自我賦值問題,繼續(xù)下列步驟:② 釋放自身已經(jīng)申請的內(nèi)存③ 申請一塊大小與目標字符串一樣大的內(nèi)存④ 進行字符串的拷貝

對于a = b,②③④過程如下:

e50d80c4-f75f-11ec-ba43-dac502259ad0.pnge5193e50-f75f-11ec-ba43-dac502259ad0.pnge5230dae-f75f-11ec-ba43-dac502259ad0.png

同樣的,復制構(gòu)造函數(shù)也是一個深拷貝的過程:

inlineString::String(constString&str)
{
m_data=newchar[strlen(str)+1];
strcpy(m_data,str.m_data);
}

另外,一定要在operator = 中檢查是否self assignment 假設(shè)這時候確實執(zhí)行了對象的自我賦值,左右pointers指向同一個內(nèi)存塊,前面的步驟②delete掉該內(nèi)存塊造成下面的結(jié)果。當企圖對rhs的內(nèi)存進行訪問是,結(jié)果是未定義的。

e52c144e-f75f-11ec-ba43-dac502259ad0.png

static與類

① 不和對象直接相關(guān)的數(shù)據(jù),聲明為static

想象有一個銀行賬戶的類,每個人都可以開銀行賬戶。存在銀行利率這個成員變量,它不應該屬于對象,而應該屬于銀行這個類,由所有的用戶來共享。

static修飾成員變量時,該成員變量放在程序的全局區(qū)中,整個程序運行過程中只有該成員變量的一份副本。而普通的成員變量存在每個對象的內(nèi)存中,若把銀行利率放在每個對象中,是浪費了內(nèi)存。

② static成員函數(shù)沒有this指針

static成員函數(shù)與普通函數(shù)一樣,都是只有一份函數(shù)的副本,存儲在進程的代碼段上。不一樣的是,static成員函數(shù)沒有this指針,所以它不能夠調(diào)用普通的成員變量,只能調(diào)用static成員變量。普通成員函數(shù)的調(diào)用需要通過對象來調(diào)用,編譯器會把對象取地址,作為this指針的實參傳遞給成員函數(shù):

obj.func()--->Class::fun(&obj);

而static成員函數(shù)即可以通過對象來調(diào)用,也可以通過類名稱來調(diào)用。

③在類的外部定義static成員變量

另一個問題是static成員變量的定義。static成員變量必須在類外部進行定義:

classA
{
private:
staticinta;//①
}
intA::a=10;//②

注意①是聲明,②才是定義,定義為變量分配了內(nèi)存。

④static與類的一些小應用

這些可以用來應付一下面試,在實現(xiàn)單例模式的時候,static成員函數(shù)與static成員變量得到了使用,下面是一種稱為”餓漢式“的單例模式的實現(xiàn):

classA
{
public:
staticA&getInstance();
setup(){...};
private:
A();
A(constA&rhs);
staticAa;
}

這里把class A的構(gòu)造函數(shù)都設(shè)置為私有,不允許用戶代碼創(chuàng)建對象。要獲取對象實例需要通過接口getInstance?!别I漢式“缺點在于無論有沒有代碼需要a,a都被創(chuàng)建出來。下面是改進的單例模式,稱為”懶漢式“:

classA
{
public:
staticA&getInstance();
setup(){....};
private:
A();
A(constA&rsh);
...
};
A&A::getInstance()
{
staticAa;
returna;
}

“懶漢式”只有在真正需要a時,調(diào)用getInstance才創(chuàng)建出唯一實例。這可以看成一個具有拖延癥的單例模式,不到最后關(guān)頭不干活。很多設(shè)計都體現(xiàn)了這種拖延的思想,比如string的寫時復制,真正需要的時候才分配內(nèi)存給string對象管理的字符串。

原文標題:漫談 C++:良好的編程習慣與編程要點

文章出處:【微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4237

    瀏覽量

    61967
  • C++
    C++
    +關(guān)注

    關(guān)注

    21

    文章

    2085

    瀏覽量

    73302
  • Class
    +關(guān)注

    關(guān)注

    0

    文章

    52

    瀏覽量

    19676
  • CONST
    +關(guān)注

    關(guān)注

    0

    文章

    43

    瀏覽量

    8125

原文標題:漫談 C++:良好的編程習慣與編程要點

文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    匯編語言模塊調(diào)用C++函數(shù)實例

    現(xiàn)在編寫一個簡單的應用程序,提示用戶輸入整數(shù),通過移位的方式將其與 2 的幕 (21?2?) 相乘,并用填充前導空格的形式再次顯示每個乘積。輸入-輸出使用 C++。匯編模塊將調(diào)用 3 個 C+
    發(fā)表于 10-11 09:52 ?848次閱讀

    C語言實現(xiàn)面向?qū)ο蟮?b class='flag-5'>方式 C++中的class的運行原理

    這里主要介紹下在C語言中是如何實現(xiàn)的面向?qū)ο蟆V懒?b class='flag-5'>C語言實現(xiàn)面向?qū)ο蟮?b class='flag-5'>方式,再聯(lián)想下,C++中的class的運行原理是什么?
    發(fā)表于 10-21 09:00 ?1033次閱讀

    如何用C++編寫流水燈程序?

    為什么很少用C++開發(fā)單片機?如何用C++編寫流水燈程序?
    發(fā)表于 09-30 08:27

    C++ Class library and Automati

    This file has two distinct features: A complete C++ class library (source code) that gives access
    發(fā)表于 08-08 21:48 ?9次下載

    C++課件、習題及答案

    *1.1  從CC++*1.2  最簡單的C++程序 1.3  C++程序的構(gòu)成和書寫形式 1.4 
    發(fā)表于 09-08 09:35 ?108次下載
    <b class='flag-5'>C++</b>課件、習題及答案

    異常安全的C++代碼編寫

    關(guān)于C++中異常的爭論何其多也,但往往是一些不合事實的誤解。異常曾經(jīng)是一個難以用好的語言特性,幸運的是,隨著C++社區(qū)經(jīng)驗的積累,今天我們已經(jīng)有足夠的知識輕松編寫
    發(fā)表于 09-16 11:50 ?5次下載

    C++程序設(shè)計教程之C++的初步知識的詳細資料說明

    C++程序設(shè)計教程之C++的初步知識的詳細資料說明包括了:1. 從CC++,2 . 最簡單的C++程序,3 .
    發(fā)表于 03-14 14:48 ?31次下載
    <b class='flag-5'>C++</b>程序設(shè)計教程之<b class='flag-5'>C++</b>的初步知識的詳細資料說明

    使用C語言和C++編寫俄羅斯方塊的資料和源代碼免費下載

    本文檔的主要內(nèi)容詳細介紹的是使用C語言和C++編寫俄羅斯方塊的資料和源代碼免費下載。
    發(fā)表于 06-10 08:00 ?4次下載
    使用<b class='flag-5'>C</b>語言和<b class='flag-5'>C++</b><b class='flag-5'>編寫</b>俄羅斯方塊的資料和源代碼免費下載

    使用C++編寫的2048小游戲的論文和源代碼免費下載

    本文檔的主要內(nèi)容詳細介紹的是使用C++編寫的2048小游戲的論文和源代碼免費下載。
    發(fā)表于 07-01 10:26 ?18次下載
    使用<b class='flag-5'>C++</b><b class='flag-5'>編寫</b>的2048小游戲的論文和源代碼免費下載

    如何基于Keil、STM32用C++編寫流水燈程序?

    通常來說,在單片機上編程,要么匯編,要么C語言,而用C++進行開發(fā)的很少,那么究竟能不能用C++開發(fā)單片機呢? 答案肯定是可以的,下面講講基于Keil、STM32,用C++
    的頭像 發(fā)表于 04-30 16:10 ?3025次閱讀

    C++ STM32 編程 005 用c++編寫STM32程序的準備

    ? ? ? ? 由于我們使用的是 ARM 的工具鏈 是gcc的,所以,我們大可以用c++編寫程序,無論是 c++99 或c++11 還是 c
    發(fā)表于 12-02 14:36 ?5次下載
    <b class='flag-5'>C++</b> STM32 編程 005 用<b class='flag-5'>c++</b><b class='flag-5'>編寫</b>STM32程序的準備

    C++中struct和class的區(qū)別?

    C++中struct和class的區(qū)別是什么?C++中struct和class的最大區(qū)別在于: ? ? ? ? struct的成員默認是公有的, 而
    的頭像 發(fā)表于 03-10 17:41 ?691次閱讀

    CC++編寫環(huán)境下LabVIEW如何調(diào)用動態(tài)庫?

    C語言編寫的動態(tài)鏈接庫相比,不同的地方在于extern int “C” __declspec(dllexport) add(int x,int y) 這一導出語句,在C代碼中沒有”
    發(fā)表于 06-11 09:15 ?6815次閱讀
    <b class='flag-5'>C</b>和<b class='flag-5'>C++</b><b class='flag-5'>編寫</b>環(huán)境下LabVIEW如何調(diào)用動態(tài)庫?

    使用C++編寫通用庫并在 Rust 中使用它 (WASI)

    使用 C++ 編寫通用庫并在 Rust 中使用它 (WASI) WebAssembly 簡介 WebAssembly 是一種二進制指令格式,旨在成為一種低級虛擬機,可以在 Web 瀏覽器中接近本機
    的頭像 發(fā)表于 06-16 10:03 ?903次閱讀
    使用<b class='flag-5'>C++</b><b class='flag-5'>編寫</b>通用庫并在 Rust 中使用它 (WASI)

    c++入門后如何進階

    C++11 是下一個 C++ 標準,但我們通常稱之為現(xiàn)代 C++?,F(xiàn)代 C++ 也包括了 C++14 和
    發(fā)表于 07-21 08:56 ?281次閱讀
    <b class='flag-5'>c++</b>入門后如何進階