自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

C++程序員必讀:讓你的代碼更強(qiáng)大

開發(fā)
這篇文章提出了一些建議,能有引導(dǎo)我們寫出更加強(qiáng)壯的C++代碼,以避免產(chǎn)生災(zāi)難性的錯誤。即使、因?yàn)槠鋸?fù)雜性和項(xiàng)目團(tuán)隊(duì)結(jié)構(gòu),你的程序目前不遵循任何編碼規(guī)則,按照下面列出的簡單的規(guī)則可以幫助您避免大多數(shù)的崩潰情況。

Introduction

在實(shí)際的項(xiàng)目中,當(dāng)項(xiàng)目的代碼量不斷增加的時候,你會發(fā)現(xiàn)越來越難管理和跟蹤其各個組件,如其不善,很容易就引入BUG。因此、我們應(yīng)該掌握一些能讓我們程序更加健壯的方法。

這篇文章提出了一些建議,能有引導(dǎo)我們寫出更加強(qiáng)壯的代碼,以避免產(chǎn)生災(zāi)難性的錯誤。即使、因?yàn)槠鋸?fù)雜性和項(xiàng)目團(tuán)隊(duì)結(jié)構(gòu),你的程序目前不遵循任何編碼規(guī)則,按照下面列出的簡單的規(guī)則可以幫助您避免大多數(shù)的崩潰情況。

Background

先來介紹下作者開發(fā)一些軟件(CrashRpt),你可以http://code.google.com/p/crashrpt/網(wǎng)站上下載源代碼。CrashRpt 顧名思義軟件崩潰記錄軟件(庫),它能夠自動提交你電腦上安裝的軟件錯誤記錄。它通過以太網(wǎng)直接將這些錯誤記錄發(fā)送給你,這樣方便你跟蹤軟件問題,并及時修改,使得用戶感覺到每次發(fā)布的軟件都有很大的提高,這樣他們自然很高興。

 

 

圖 1、CrashRpt 庫檢測到錯誤彈出的對話框

在分析接收的錯誤記錄的時候,我們發(fā)現(xiàn)采用下文介紹的方法能夠避免大部分程序崩潰的錯誤。例如、局部變量未初始化導(dǎo)致數(shù)組訪問越界,指針使用前未進(jìn)行檢測(NULL)導(dǎo)致訪問訪問非法區(qū)域等。

我已經(jīng)總結(jié)了幾條代碼設(shè)計的方法和規(guī)則,在下文一一列出,希望能夠幫助你避免犯一些錯誤,使得你的程序更加健壯。

Initializing Local Variables

使用未初始化的局部變量是引起程序崩潰的一個比較普遍的原因,例如、來看下面這段程序片段:

  1. // Define local variables  
  2. BOOL bExitResult; // This will be TRUE if the function exits successfully  
  3. FILE* f; // Handle to file  
  4. TCHAR szBuffer[_MAX_PATH];   // String buffer  
  5.     
  6. // Do something with variables above...  

上面的這段代碼存在著一個潛在的錯誤,因?yàn)闆]有一個局部變量初始化了。當(dāng)你的代碼運(yùn)行的時候,這些變量將被默認(rèn)負(fù)一些錯誤的數(shù)值。例如bExitResult 數(shù)值將被負(fù)為-135913245 ,szBuffer 必須以“\0”結(jié)尾,結(jié)果不會。因此、局部變量初始化時非常重要的,如下正確代碼:

  1. // Define local variables  
  2.  
  3. // Initialize function exit code with FALSE to indicate failure assumption  
  4. BOOL bExitResult = FALSE; // This will be TRUE if the function exits successfully  
  5. // Initialize file handle with NULL  
  6. FILE* f = NULL; // Handle to file  
  7. // Initialize string buffer with empty string  
  8. TCHAR szBuffer[_MAX_PATH] = _T("");   // String buffer  
  9. // Do something with variables above...  

注意:有人說變量初始化會引起程序效率降低,是的,確實(shí)如此,如果你確實(shí)非常在乎程序的執(zhí)行效率,去除局部變量初始化,你得想好其后果。

Initializing WinAPI Structures

許多Windows API都接受或則返回一些結(jié)構(gòu)體參數(shù),結(jié)構(gòu)體如果沒有正確的初始化,也很有可能引起程序崩潰。大家可能會想起用ZeroMemory宏或者memset()函數(shù)去用0填充這個結(jié)構(gòu)體(對結(jié)構(gòu)體對應(yīng)的元素設(shè)置默認(rèn)值)。但是大部分Windows API 結(jié)構(gòu)體都必須有一個cbSIze參數(shù),這個參數(shù)必須設(shè)置為這個結(jié)構(gòu)體的大小。

看看下面代碼,如何初始化Windows API結(jié)構(gòu)體參數(shù):

  1. NOTIFYICONDATA nf; // WinAPI structure  
  2. memset(&nf,0,sizeof(NOTIFYICONDATA)); // Zero memory  
  3. nf.cbSize = sizeof(NOTIFYICONDATA); // Set structure size!  
  4. // Initialize other structure members  
  5. nf.hWnd = hWndParent;  
  6. nf.uID = 0;     
  7. nf.uFlags = NIF_ICON | NIF_TIP;  
  8. nf.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);  
  9. _tcscpy_s(nf.szTip, 128, _T("Popup Tip Text"));  
  10.         
  11. // Add a tray icon  
  12. Shell_NotifyIcon(NIM_ADD, &nf); 

注意:千萬不要用ZeroMemory和memset去初始化那些包括結(jié)構(gòu)體對象的結(jié)構(gòu)體,這樣很容易破壞其內(nèi)部結(jié)構(gòu)體,從而導(dǎo)致程序崩潰.

  1. // Declare a C++ structure  
  2. struct ItemInfo  
  3. {  
  4.   std::string sItemName; // The structure has std::string object inside  
  5.   int nItemValue;  
  6. };   
  7.  
  8. // Init the structure  
  9. ItemInfo item;  
  10. // Do not use memset()! It can corrupt the structure  
  11. // memset(&item, 0, sizeof(ItemInfo));  
  12. // Instead use the following  
  13. item.sItemName = "item1";  
  14. item.nItemValue = 0;   
  15.    這里最好是用結(jié)構(gòu)體的構(gòu)造函數(shù)對其成員進(jìn)行初始化.  
  16.  
  17. // Declare a C++ structure  
  18. struct ItemInfo  
  19. {  
  20.   // Use structure constructor to set members with default values  
  21.   ItemInfo()  
  22.   {  
  23.     sItemName = _T("unknown");  
  24.     nItemValue = -1;  
  25.   }  
  26.         
  27.   std::string sItemName; // The structure has std::string object inside  
  28.   int nItemValue;  
  29. };  
  30. // Init the structure  
  31. ItemInfo item;  
  32. // Do not use memset()! It can corrupt the structure  
  33. // memset(&item, 0, sizeof(ItemInfo));  
  34. // Instead use the following  
  35. item.sItemName = "item1";  
  36. item.nItemValue = 0;     

Validating Function Input

在函數(shù)設(shè)計的時候,對傳入的參數(shù)進(jìn)行檢測是一直都推薦的。例如、如果你設(shè)計的函數(shù)是公共API的一部分,它可能被外部客戶端調(diào)用,這樣很難保證客戶端傳進(jìn)入的參數(shù)就是正確的。

例如,讓我們來看看這個hypotethical DrawVehicle() 函數(shù),它可以根據(jù)不同的質(zhì)量來繪制一輛跑車,這個質(zhì)量數(shù)值(nDrawingQaulity )是0~100。prcDraw 定義這輛跑車的輪廓區(qū)域。

看看下面代碼,注意觀察我們是如何在使用函數(shù)參數(shù)之前進(jìn)行參數(shù)檢測:

  1. BOOL DrawVehicle(HWND hWnd, LPRECT prcDraw, int nDrawingQuality)  
  2.   {  
  3.     // Check that window is valid  
  4.     if(!IsWindow(hWnd))  
  5.       return FALSE;  
  6.    
  7.     // Check that drawing rect is valid  
  8.     if(prcDraw==NULL)  
  9.       return FALSE;  
  10.    
  11.     // Check drawing quality is valid  
  12.     if(nDrawingQuality<0 || nDrawingQuality>100)  
  13.       return FALSE;  
  14.      
  15.     // Now it's safe to draw the vehicle  
  16.    
  17.     // ...  
  18.    
  19.     return TRUE;  
  20.   } 

Validating Pointers

在指針使用之前,不檢測是非常普遍的,這個可以說是我們引起軟件崩潰最有可能的原因。如果你用一個指針,這個指針剛好是NULL,那么你的程序在運(yùn)行時,將報出異常。

  1. CVehicle* pVehicle = GetCurrentVehicle();  
  2.  
  3. // Validate pointer  
  4. if(pVehicle==NULL)  
  5. {  
  6.   // Invalid pointer, do not use it!  
  7.   return FALSE;  

#p#

Initializing Function Output

如果你的函數(shù)創(chuàng)建了一個對象,并要將它作為函數(shù)的返回參數(shù)。那么記得在使用之前把他復(fù)制為NULL。如不然,這個函數(shù)的調(diào)用者將使用這個無效的指針,進(jìn)而一起程序錯誤。如下錯誤代碼:

  1. int CreateVehicle(CVehicle** ppVehicle)    
  2.   {    
  3.     if(CanCreateVehicle())    
  4.     {    
  5.       *ppVehicle = new CVehicle();    
  6.       return 1;    
  7.     }        
  8.      
  9.     // If CanCreateVehicle() returns FALSE,    
  10.     // the pointer to *ppVehcile would never be set!    
  11.     return 0;    
  12.   }   

正確的代碼如下;

  1. int CreateVehicle(CVehicle** ppVehicle)    
  2.   {    
  3.     // First initialize the output parameter with NULL    
  4.     *ppVehicle = NULL;    
  5.      
  6.     if(CanCreateVehicle())    
  7.     {    
  8.       *ppVehicle = new CVehicle();    
  9.       return 1;    
  10.     }        
  11.      
  12.     return 0;    
  13.   }   

Cleaning Up Pointers to Deleted Objects

在內(nèi)存釋放之后,無比將指針復(fù)制為NULL。這樣可以確保程序的沒有那個地方會再使用無效指針。其實(shí)就是,訪問一個已經(jīng)被刪除的對象地址,將引起程序異常。如下代碼展示如何清除一個指針指向的對象:

  1. // Create object  
  2. CVehicle* pVehicle = new CVehicle();  
  3. delete pVehicle; // Free pointer  
  4. pVehicle = NULL; // Set pointer with NULL 

Cleaning Up Released Handles

在釋放一個句柄之前,務(wù)必將這個句柄復(fù)制偽NULL (0或則其他默認(rèn)值)。這樣能夠保證程序其他地方不會重復(fù)使用無效句柄??纯慈缦麓a,如何清除一個Windows API的文件句柄:

  1. HANDLE hFile = INVALID_HANDLE_VALUE;   
  2.  
  3. // Open file  
  4. hFile = CreateFile(_T("example.dat"), FILE_READ|FILE_WRITE, FILE_OPEN_EXISTING);  
  5. if(hFile==INVALID_HANDLE_VALUE)  
  6. {  
  7.   return FALSE; // Error opening file  
  8. }  
  9.  
  10. // Do something with file  
  11.  
  12. // Finally, close the handle  
  13. if(hFile!=INVALID_HANDLE_VALUE)  
  14. {  
  15.   CloseHandle(hFile);   // Close handle to file  
  16.   hFile = INVALID_HANDLE_VALUE;   // Clean up handle  
  17. }  

下面代碼展示如何清除File *句柄:

  1. // First init file handle pointer with NULL  
  2. FILE* f = NULL;  
  3.  
  4. // Open handle to file  
  5. errno_t err = _tfopen_s(_T("example.dat"), _T("rb"));  
  6. if(err!=0 || f==NULL)  
  7.   return FALSE; // Error opening file  
  8.  
  9. // Do something with file  
  10.  
  11. // When finished, close the handle  
  12. if(f!=NULL) // Check that handle is valid  
  13. {  
  14.   fclose(f);  
  15.   f = NULL; // Clean up pointer to handle  
  16. }  

Using delete [] Operator for Arrays

如果你分配一個單獨(dú)的對象,可以直接使用new ,同樣你釋放單個對象的時候,可以直接使用delete . 然而,申請一個對象數(shù)組對象的時候可以使用new,但是釋放的時候就不能使用delete ,而必須使用delete[]:

  1.  // Create an array of objects  
  2.  CVehicle* paVehicles = new CVehicle[10];  
  3.  delete [] paVehicles; // Free pointer to array  
  4.  paVehicles = NULL; // Set pointer with NULL  
  5. or  
  6.  // Create a buffer of bytes  
  7.  LPBYTE pBuffer = new BYTE[255];  
  8.  delete [] pBuffer; // Free pointer to array  
  9.  pBuffer = NULL; // Set pointer with NULL 

Allocating Memory Carefully

有時候,程序需要動態(tài)分配一段緩沖區(qū),這個緩沖區(qū)是在程序運(yùn)行的時候決定的。例如、你需要讀取一個文件的內(nèi)容,那么你就需要申請該文件大小的緩沖區(qū)來保存該文件的內(nèi)容。在申請這段內(nèi)存之前,請注意,malloc() or new是不能申請0字節(jié)的內(nèi)存,如不然,將導(dǎo)致malloc() or new函數(shù)調(diào)用失敗。傳遞錯誤的參數(shù)給malloc() 函數(shù)將導(dǎo)致C運(yùn)行時錯誤。如下代碼展示如何動態(tài)申請內(nèi)存:

  1. // Determine what buffer to allocate.  
  2. UINT uBufferSize = GetBufferSize();   
  3.  
  4. LPBYTE* pBuffer = NULL; // Init pointer to buffer  
  5.  
  6. // Allocate a buffer only if buffer size > 0  
  7. if(uBufferSize>0)  
  8.  pBuffer = new BYTE[uBufferSize]; 

為了進(jìn)一步了解如何正確的分配內(nèi)存,你可以讀下Secure Coding Best Practices for Memory Allocation in C and C++(http://www.codeproject.com/KB/tips/CBP_for_memory_allocation.aspx)這篇文章。

#p#

Using Asserts Carefully

Asserts用語調(diào)試模式檢測先決條件和后置條件。但當(dāng)我們編譯器處于release模式的時候,Asserts在預(yù)編階段被移除。因此,用Asserts是不能夠檢測我們的程序狀態(tài),錯誤代碼如下:

  1. #include <assert.h>  
  2.    
  3.  // This function reads a sports car's model from a file  
  4.  CVehicle* ReadVehicleModelFromFile(LPCTSTR szFileName)  
  5.  {  
  6.    CVehicle* pVehicle = NULL; // Pointer to vehicle object  
  7.  
  8.    // Check preconditions  
  9.    assert(szFileName!=NULL); // This will be removed by preprocessor in Release mode!  
  10.    assert(_tcslen(szFileName)!=0); // This will be removed in Release mode!  
  11.  
  12.    // Open the file  
  13.    FILE* f = _tfopen(szFileName, _T("rt"));  
  14.  
  15.    // Create new CVehicle object  
  16.    pVehicle = new CVehicle();  
  17.  
  18.    // Read vehicle model from file  
  19.  
  20.    // Check postcondition   
  21.    assert(pVehicle->GetWheelCount()==4); // This will be removed in Release mode!  
  22.  
  23.    // Return pointer to the vehicle object  
  24.    return pVehicle;  
  25.  } 

看看上述的代碼,Asserts能夠在debug模式下檢測我們的程序,在release 模式下卻不能。所以我們還是不得不用if()來這步檢測操作。正確的代碼如下;

  1. CVehicle* ReadVehicleModelFromFile(LPCTSTR szFileName, )  
  2.  {  
  3.    CVehicle* pVehicle = NULL; // Pointer to vehicle object  
  4.  
  5.    // Check preconditions  
  6.    assert(szFileName!=NULL); // This will be removed by preprocessor in Release mode!  
  7.    assert(_tcslen(szFileName)!=0); // This will be removed in Release mode!  
  8.  
  9.    if(szFileName==NULL || _tcslen(szFileName)==0)  
  10.      return NULL; // Invalid input parameter  
  11.  
  12.    // Open the file  
  13.    FILE* f = _tfopen(szFileName, _T("rt"));  
  14.  
  15.    // Create new CVehicle object  
  16.    pVehicle = new CVehicle();  
  17.  
  18.    // Read vehicle model from file  
  19.  
  20.    // Check postcondition   
  21.    assert(pVehicle->GetWheelCount()==4); // This will be removed in Release mode!  
  22.  
  23.    if(pVehicle->GetWheelCount()!=4)  
  24.    {   
  25.      // Oops... an invalid wheel count was encountered!    
  26.      delete pVehicle;   
  27.      pVehicle = NULL;  
  28.    }  
  29.  
  30.    // Return pointer to the vehicle object  
  31.    return pVehicle;  
  32.  } 

Checking Return Code of a Function

斷定一個函數(shù)執(zhí)行一定成功是一種常見的錯誤。當(dāng)你調(diào)用一個函數(shù)的時候,建議檢查下返回代碼和返回參數(shù)的值。如下代碼持續(xù)調(diào)用Windows API ,程序是否繼續(xù)執(zhí)行下去依賴于該函數(shù)的返回結(jié)果和返回參數(shù)值。

  1. HRESULT hres = E_FAIL;  
  2.     IWbemServices *pSvc = NULL;  
  3.     IWbemLocator *pLoc = NULL;  
  4.       
  5.     hres =  CoInitializeSecurity(  
  6.         NULL,   
  7.         -1,                          // COM authentication  
  8.         NULL,                        // Authentication services  
  9.         NULL,                        // Reserved  
  10.         RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication   
  11.         RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation    
  12.         NULL,                        // Authentication info  
  13.         EOAC_NONE,                   // Additional capabilities   
  14.         NULL                         // Reserved  
  15.         );  
  16.                         
  17.     if (FAILED(hres))  
  18.     {  
  19.         // Failed to initialize security  
  20.         if(hres!=RPC_E_TOO_LATE)   
  21.            return FALSE;  
  22.     }  
  23.       
  24.     hres = CoCreateInstance(  
  25.         CLSID_WbemLocator,               
  26.         0,   
  27.         CLSCTX_INPROC_SERVER,   
  28.         IID_IWbemLocator, (LPVOID *) &pLoc);  
  29.     if (FAILED(hres) || !pLoc)  
  30.     {  
  31.         // Failed to create IWbemLocator object.   
  32.         return FALSE;                 
  33.     }  
  34.      
  35.     hres = pLoc->ConnectServer(  
  36.          _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace  
  37.          NULL,                    // User name. NULL = current user  
  38.          NULL,                    // User password. NULL = current  
  39.          0,                       // Locale. NULL indicates current  
  40.          NULL,                    // Security flags.  
  41.          0,                       // Authority (e.g. Kerberos)  
  42.          0,                       // Context object   
  43.          &pSvc                    // pointer to IWbemServices proxy  
  44.          );  
  45.       
  46.     if (FAILED(hres) || !pSvc)  
  47.     {  
  48.         // Couldn't conect server  
  49.         if(pLoc) pLoc->Release();       
  50.         return FALSE;    
  51.     }  
  52.     hres = CoSetProxyBlanket(  
  53.        pSvc,                        // Indicates the proxy to set  
  54.        RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx  
  55.        RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx  
  56.        NULL,                        // Server principal name   
  57.        RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx   
  58.        RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx  
  59.        NULL,                        // client identity  
  60.        EOAC_NONE                    // proxy capabilities   
  61.     );  
  62.     if (FAILED(hres))  
  63.     {  
  64.         // Could not set proxy blanket.  
  65.         if(pSvc) pSvc->Release();  
  66.         if(pLoc) pLoc->Release();       
  67.         return FALSE;                 
  68.     }  

Using Smart Pointers

如果你經(jīng)常使用用享對象指針,如COM 接口等,那么建議使用智能指針來處理。智能指針會自動幫助你維護(hù)對象引用記數(shù),并且保證你不會訪問到被刪除的對象。這樣,不需要關(guān)心和控制接口的生命周期。關(guān)于智能指針的進(jìn)一步知識可以看看Smart Pointers - What, Why, Which?(http://ootips.org/yonat/4dev/smart-pointers.html) 和 Implementing a Simple Smart Pointer in C++(http://www.codeproject.com/KB/cpp/SmartPointers.aspx)這兩篇文章。

如面是一個展示使用ATL's CComPtr template 智能指針的代碼,該部分代碼來至于MSDN。

  1. #include <windows.h>  
  2. #include <shobjidl.h>   
  3. #include <atlbase.h> // Contains the declaration of CComPtr.  
  4. int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCEPWSTR pCmdLine, int nCmdShow)  
  5. {  
  6.     HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |   
  7.         COINIT_DISABLE_OLE1DDE);  
  8.     if (SUCCEEDED(hr))  
  9.     {  
  10.         CComPtr<IFileOpenDialog> pFileOpen;  
  11.         // Create the FileOpenDialog object.  
  12.         hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));  
  13.         if (SUCCEEDED(hr))  
  14.         {  
  15.             // Show the Open dialog box.  
  16.             hr = pFileOpen->Show(NULL);  
  17.             // Get the file name from the dialog box.  
  18.             if (SUCCEEDED(hr))  
  19.             {  
  20.                 CComPtr<IShellItem> pItem;  
  21.                 hr = pFileOpen->GetResult(&pItem);  
  22.                 if (SUCCEEDED(hr))  
  23.                 {  
  24.                     PWSTR pszFilePath;  
  25.                     hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);  
  26.                     // Display the file name to the user.  
  27.                     if (SUCCEEDED(hr))  
  28.                     {  
  29.                         MessageBox(NULL, pszFilePath, L"File Path", MB_OK);  
  30.                         CoTaskMemFree(pszFilePath);  
  31.                     }  
  32.                 }  
  33.                 // pItem goes out of scope.  
  34.             }  
  35.             // pFileOpen goes out of scope.  
  36.         }  
  37.         CoUninitialize();  
  38.     }  
  39.     return 0;  
  40. }   

Using == Operator Carefully

先來看看如下代碼;

  1. CVehicle* pVehicle = GetCurrentVehicle();  
  2.    
  3. // Validate pointer  
  4. if(pVehicle==NULL) // Using == operator to compare pointer with NULL  
  5.    return FALSE;   
  6.  
  7. // Do something with the pointer  
  8. pVehicle->Run(); 

上面的代碼是正確的,用語指針檢測。但是如果不小心用“=”替換了“==”,如下代碼;

  1. CVehicle* pVehicle = GetCurrentVehicle();  
  2.  
  3.  // Validate pointer  
  4.  if(pVehicle=NULL) // Oops! A mistyping here!  
  5.     return FALSE;   
  6.  
  7.  // Do something with the pointer  
  8.  pVehicle->Run(); // Crash!!!   

看看上面的代碼,這個的一個失誤將導(dǎo)致程序崩潰。

這樣的錯誤是可以避免的,只需要將等號左右兩邊交換一下就可以了。如果在修改代碼的時候,你不小心產(chǎn)生這種失誤,這個錯誤在程序編譯的時候?qū)⒈粰z測出來。

原文:http://blog.csdn.net/xxxluozhen/article/details/6611663

【編輯推薦】

  1. 掀起C++ 11的神秘面紗
  2. 老程序員10年技術(shù)生涯的思考 從C++到Java
  3. 程序員新招:Java與C++混合編程
  4. .NET、Mono與Java、C++性能測試大PK
  5. 一個實(shí)用主義者的觀點(diǎn):如何學(xué)習(xí)使用C++
責(zé)任編輯:陳貽新 來源: CSDN博客
相關(guān)推薦

2010-01-22 11:23:06

C++程序

2015-07-30 11:13:24

LinuxShell

2012-11-08 09:49:30

C++Java程序員

2024-11-21 15:48:40

2015-03-31 14:28:18

程序員程序員必讀的書-Linux

2013-12-13 13:38:32

C程序員

2021-02-26 10:41:59

C++程序員代碼

2011-06-24 14:47:43

服務(wù)器數(shù)據(jù)中心服務(wù)器硬件

2023-07-17 10:28:00

C/C++編程接口

2010-01-12 10:40:22

C++程序員

2010-01-14 18:07:30

C++語言

2016-03-25 11:57:23

Java程序員C++

2015-12-07 11:44:31

程序員厭倦工作

2015-12-09 09:17:11

情緒厭倦程序員

2010-01-12 14:30:41

C++程序

2011-03-30 09:26:20

c++程序員

2020-12-07 14:46:07

程序員代碼分析工具

2021-03-29 23:05:36

程序員工具靜態(tài)分析

2011-05-24 17:20:57

程序員

2009-05-21 15:58:12

程序員工作經(jīng)驗(yàn)職場
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號