C++中新手容易犯的十種編程錯誤匯總

前言

IT公司每年都會有一定的人員流動,相應地也會招一些應屆生補充進來,指導應屆生已經成為老員工的必修課瞭。平日裡會我們會經常幫新人排查代碼中的問題,在此過程中發現瞭C++新手容易犯的一些編程錯誤,在此簡單的總結一下,給新人們提供一個參考。

1、有些關鍵字在cpp文件中多寫瞭

對於C++類,一些關鍵字隻要寫在.h中就好,cpp中就不用再加上瞭,比如virtual、static等關鍵字,如果再cpp中多寫,編譯器會報錯。比如如下的虛接口與靜態成員變量的定義,隻要在頭文件中聲明就可以瞭。

class shape
{
    virtual Draw();
    //...
    static int nLevel;
}

2、函數參數的默認值寫到函數實現中瞭

帶有參數默認值的函數,默認值是加在函數聲明處的,函數實現處的參數是不需要帶上的。為瞭方便查看代碼,在函數實現處的參數中,將默認值註釋起來。正確的做法是,頭文件中有默認值:

BOOL CreateConf( const CString& strConfName, const BOOL bAudio = FALSE );

在函數實現處的參數中不用添加默認值:

BOOL CreateConf( const CString& strConfName, const BOOL bAudio/* = FALSE*/ );
{
    // ......
}

3、在編寫類的時候,在類的結尾處忘記添加”;”分號瞭

在類的結尾處忘記添加分號,編譯會報錯,新人們有可能找瞭半天也沒找出引起編譯錯誤的原因。其實很簡單,在類的結尾處忘記添加分號瞭。

class Shape
{
    // ...
};

4、隻添加瞭函數聲明,沒有函數實現

在添加類的函數時,隻在類的頭文件中添加瞭函數聲明,但在cpp中卻沒有添加函數的實現。如果其他地方調用到該函數,在編譯鏈接的時候會報 unresolved external symbol錯誤。因為沒有實現,所有沒有供鏈接使用的obj文件。

5、cpp文件忘記添加到工程中,導致沒有生成供鏈接使用的obj文件

在添加C++類時,我們一般會添加.h頭文件和一個.cpp源文件。結果忘記把.cpp文件添加到工程中瞭,即沒有參與編譯,沒有生成供鏈接使用的obj文件。如果有代碼調用到該C++類的接口,則在編譯鏈接的時候會報 unresolved external symbol錯誤,即鏈接不到該C++類對應的接口。

6、函數中返回瞭一個局部變量的地址或者引用

在函數中返回瞭一個局部變量的地址或者引用,而這個局部變量在函數結束時其生命周期就結束瞭,內存就被釋放瞭。當外部訪問到該變量的內存,會觸發內存訪問違例的異常,因為該變量的內存已經釋放瞭。比如如下的錯誤代碼:

char* GetResult()
{
    char chResult[100] = { 0 };
 
    // ......
 
    return chResult;
}

7、忘記將父類中的接口聲明virtual函數,導致多態沒有生效

代碼中本來要借助於C++多態的虛函數調用,調用子類實現的接口,結果忘記在父類中將對應的接口聲明為virtual,導致沒有調用到子類實現的函數。一定要記住,要實現多態下的函數調用,父類的相關接口必須聲明為virtual。

class Shape()
{
    // ...
 
    virtual void Draw();
 
    // ...
} 

8、該使用雙指針的地方,卻使用瞭單指針

有時我們需要調用一個接口去獲取某些數據,接口中將數據拷貝到傳入的參數對應的內存中,此時設計參數時會傳入指針或引用。我們在調用GetData之前定義瞭結構體指針p,並new出瞭對應的結構體對象內存,應該在定義GetData接口時應該使用雙指針(指針的指針)的,結果錯寫成瞭單指針。

有問題的代碼如下:

struct CodecInfo     // 編碼信息
{
    int nFrameRate;
 
    // ...
}
 
 
CodecInfo* pInfo = new CodecInfo;
 
GetAudioCodecPtr()->GetCodecInfo(pInfo);   // 調用AudioCodec::GetCodecInfo獲取編碼信息
 
 
AudioCodec::GetCodecInfo( CodecInfo* pInfo)  // 此處的參數不應該使用單指針
{
    memcpy(pInfo, m_codecInfo, sizeof(CodecInfo));
}

上圖中的AudioCodec::GetCodecInfo接口的參數不應該為單指針,應該用雙指針,修改後的代碼應該如下:

AudioCodec::GetCodecInfo( CodecInfo** pInfo)  // 此處的參數類型使用雙指針
{
    memcpy(*pInfo, m_codecInfo, sizeof(CodecInfo));
}

9、發佈exe程序時,忘記將exe依賴的C運行時庫和MFC庫帶上

比如新人用VS-MFC庫編寫一個測試用的工具軟件,結果在發佈release版本程序時,沒有將程序依賴的C運行時庫帶上,導致該工具軟件在某些電腦中啟動報錯,提示找不到C運行時庫:

因為程序中依賴瞭動態版本的運行時庫和MFC庫,在發佈程序時要將這些庫帶上。有些系統中沒有這些庫,程序啟動時就會報找不到庫,就會啟動失敗。

10、應該使用深拷貝,卻使用瞭淺拷貝

本來應該要進行深拷貝的,卻使用瞭淺拷貝(直接賦值),導致另個不同生命周期的C++對象指向瞭同一塊內存,一個對象將內存釋放後,另一個對象再用到這塊內存,就造成瞭內存訪問違例,產生異常。

有個經典的C++筆試題,讓我們實現String類的相關函數,其主要目的就是用來考察對深拷貝與淺拷貝的理解的。題目中給出String類的聲明:

class String{
public:
    String();
    String(const String & str);
    String(const char* str);
    String& operator=(String str);
    char* c_str() const;
    ~String();
    int size() const;
private:
    char* data;
};

讓寫出上述幾個函數的內部實現。這些函數的實現代碼如下:

//普通構造函數  
String::String(const char *str)
{
 if (str == NULL)
 {
  m_data = new char[1];// 得分點:對空字符串自動申請存放結束標志'\0'的,加分點:對m_data加NULL判斷  
  *m_data = '\0';
 }
 else
 {
  int length = strlen(str);
  m_data = new char[length + 1];// 若能加 NULL 判斷則更好
  strcpy(m_data, str);
 }
}
 
 
// String的析構函數  
String::~String(void)
{
 delete[] m_data; // 或delete m_data;  
}
 
 
//拷貝構造函數  
String::String(const String &other)// 得分點:輸入參數為const型  
{   
 int length = strlen(other.m_data);
 m_data = new char[length + 1];// 若能加 NULL 判斷則更好  
 strcpy(m_data, other.m_data);
}
 
 
//賦值函數  
String & String::operator = (const String &other) // 得分點:輸入參數為const型  
{
 if (this == &other)//得分點:檢查自賦值  
  return *this; 
 if (m_data)
     delete[] m_data;//得分點:釋放原有的內存資源  
 int length = strlen(other.m_data);
 m_data = new char[length + 1];//加分點:對m_data加NULL判斷  
 strcpy(m_data, other.m_data);
 return *this;//得分點:返回本對象的引用    
}

總結

到此這篇關於C++中新手容易犯的十種編程錯誤的文章就介紹到這瞭,更多相關C++新手易犯編程錯誤內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: