1. 程式人生 > >字元和字串處理(2)

字元和字串處理(2)

2.5 安全字串函式

不安全的字串函式

Strsafe函式

Safe CRT函式

(C執行庫)

strcpy, wcscpy, _tcscpy, _mbscpy, strcpy , lstrcpy, _tccpy, _mbccpy

StringCchCopy  StringCbCopy

StringCchCopyEx  StringCbCopyEx

strcpy_s

strcat, wcscat , _mbscat, strcat, lstrcat, , strcatbuff, strcatchain, _tccat, _mbccat

StringCchCat  StringCbCat

StringCchCatEx  StringCbCatEx

strcat_s

wnsprintf, wsprintf, sprintf, swprintf, _stprintf

StringCchPrintf  StringCbPrintf

StringCchPrintfEx StringCbPrintfEx

_snprintf_s

_snwprintf_s

目標緩衝區太小時,不發生截斷

目標緩衝區太小時,會發生截斷

不發生截斷

★String Safe函式:微軟提供的內聯形式函式,可以當做API(在strsafe.h檔案中,注意要在包含其他檔案之後,才包含該檔案!

★Safe CRT函式:C執行庫

 【SafeString程式】自定義錯誤處理函式

/*---------------------------------------------------------------------------------------
  功能:實現課本P19頁,自定義函式呼叫失敗處理程式
----------------------------------------------------------------------------------------*/
#include <tchar.h>
#include <stdlib.h>
#include <windows.h>
#include <stdint.h>
#include <crtdbg.h> //要用到_CrtSetReportMode函式
#include <strsafe.h>
//自定義函式呼叫失敗的處理程式——只有在Debug版下才有效,Release中所有的引數將被傳入NULL或0。
//當某個函式呼叫失敗,系統會呼叫該函式,同時傳入“錯誤描述文字”、出錯的函式名稱,檔名及出錯所在行數。
void InvalidParameterHandle(PCTSTR expression, PCTSTR function, PCTSTR file, unsigned int line, uintptr_t /*pReserved*/)
{
    _tprintf(_T("expression %s,\nfunction %s,\nfile %s,\nline %d\n"),expression,function,file,line);
}

int _tmain()
{
    _CrtSetReportMode(_CRT_ASSERT, 0); //禁用“除錯斷言失敗”對話方塊
    TCHAR szBefore[5] = { _T('B'), _T('B'), _T('B'), _T('B') ,'\0'};
    TCHAR szBuffer[10] = { _T('-'), _T('-'), _T('-'), _T('-'), _T('-'),
                           _T('-'), _T('-'), _T('-'), '\0' };
    TCHAR szAfter[5] = { _T('A'), _T('A'), _T('A'), _T('A'), '\0' };

    //註冊函式呼叫失敗的處理程式
    _set_invalid_parameter_handler(InvalidParameterHandle);

    //源字串10個字元(不含\0),目標緩衝區,只能容納9個,會出錯(發生錯誤時,不彈出Debug
    //Assertion Failure對話方塊而是呼叫自定義的InvalidParameterHandle函式)
    errno_t ret = _tcscpy_s(szBuffer, _countof(szBuffer), _T("0123456789")); 
    return 0; 
}

2.6 Str Safe函式的介紹(須含包strsafe.h檔案)——目標緩衝區太小時,會發生截斷

(1)StringCchXXXX函式——其中的Cch表示字元的個數,可用_countof(pszDest)計算

StringCchCopy——複製一個字串到緩衝區,但要求提供目標緩衝區的長度,以確保寫入資料不會超出緩衝區的末尾使用該函式替代以下函式:strcpy、wcscpy、_tcscpy、lstrcpy、StrCpy等函式。

引數

描述

LPTSTR pszDest

緩衝區,用於接收拷貝過來的字串

size_t cchDest

①目標緩衝區的大小(字元個數)——_countof(pszDest)

②該值必須大於或等於 lstrlen(pszSrc) + 1(待拷貝字串的字元+'\0')

③這個數不能超過 STRSAFE_MAX_CCH。

LPCTSTR pszSrc

待拷貝的字串

返回值

①S_OK:字串正常拷貝

②STRSAFE_E_INVALID_PARAMETER:cchDest 引數的值為 0或cchDest 引數的值大於 STRSAFE_MAX_CCH。

③STRSAFE_E_INSUFFICIENT_BUFFER:因緩衝區空間不足導致失敗;結果被截斷,但仍然包含'\0'結尾;如果截斷操作可以被接受,則不一定被看作是失敗,即這時的返回值也可認為是可接受的。

StringCchCat——將一個字串拼接到另一個字元,使用該函式替代以下函式:strcat、 wcscat、_tcsat、lstrcat、StrCat和StrCatBuff等函式。

引數

描述

LPTSTR pszDest

①目標緩衝區,同時包含第一個字串

②該緩衝區應該大於或等於(lstrlen(pszDest) +lstrlen(pszSrc) + 1)*sizeof(TCHAR)

size_t cchDest

①目標緩衝區的大小(字元個數)——_countof(pszDest)

②該緩衝區必須大於或等於lstrlen(pszDest) +lstrlen(pszSrc) + 1(兩個字串的字元總和+'\0')

③這個數不能超過 STRSAFE_MAX_CCH。

LPCTSTR pszSrc

第2個字串

返回值

①S_OK:字串正常拼接

②STRSAFE_E_INVALID_PARAMETER:cchDest 引數的值為 0或cchDest 引數的值大於 STRSAFE_MAX_CCH;目標緩衝區空間已滿。

③STRSAFE_E_INSUFFICIENT_BUFFER:因緩衝區空間不足導致失敗;結果被截斷,但仍然包含'\0'結尾;如果截斷操作可以被接受,則不一定被看作是失敗。

StringCchLength——用於確定字串是否超過了規定的長度,以字元為計算單位(不含\0),用來替換strlen、wcslen和_tcslen函式。

引數

描述

LPCTSTR psz

指向待檢查的字串

size_t cchMax

①psz 引數裡最大允許的字元數量,包括'\0'

②這個數不能超過 STRSAFE_MAX_CCH。

size_t *pcch

①psz 引數指向字串的字元個數,不包括'\0',字串長度返回這引數中。

②這個值只有在 psz 指標不為 NULL,且函式成功時有效

返回值

①S_OK:psz 指向的字串不為空,且字串的長度(包括'\0')小於等於 cchMax

②STRSAFE_E_INVALID_PARAMETER:psz 指向空字串;cchMax 的值大於STRSAFE_MAX_CCH;psz 指向的字串的字元個數超過 cchMax。

StringCchPrintf——把資料格式化寫入到指定的緩衝區裡,使用該函式替代以下函式:

Sprintf、swprintf、 _stprintf、wsprintf、wnsprintf、_snprintf、_snwprintf和 _sntprintf等函式

引數

描述

LPTSTR pszDest

指定格式化資料將要寫入的緩衝區

size_t cchDest

①緩衝區大小,應該設定足夠大,以容納字串和結束標記('\n')

②最大允許的字元數是 STRSAFE_MAX_CCH

LPCTSTR pszFormat

①格式化字串,與 pirntf 的格式化字串一致

②這個值只有在 psz 指標不為 NULL,且函式成功時有效

...

可變引數,引數的個數取決 pszFormat 引數

返回值

①S_OK:psz 表示有足夠的空間將拷貝到 pszDest,沒有發生截斷

②STRSAFE_E_INVALID_PARAMETER:cchDest 的值為 0 或大於 STRSAFE_MAX_CCH。

③STRSAFE_E_INSUFFICIENT_BUFFER:由於緩衝區空間不足而導致的複製失敗;結果被截斷,當仍然包含'\0'結尾;如果截斷操作可以被接受,則不一定被看作是失敗。

(2)StringCcbXXXX函式:其中的Ccb表示目標緩衝區的位元組數,可用sizeof(pszDest)

(3)StringCchXXXXEx和StringCcbEx等擴充套件版本的函式,(見課本P22頁)

 

2.7 Unicode與ANSI字串的轉換

(1)MultiByteToWideChar——多位元組字串轉為Unicode字串

引數

描述

UINT uCodePage

指定與ANSI字串關聯的內碼表。

CP_ACP:指定為當前系統內碼表

CP_OEMCP 當前系統OEM內碼表

DWORD dwFlags

指定如何處理沒有轉換的字元,一般設為0。

PCTSTR pMultiByteStr

要轉換的字串

int cbMultiByte

要轉換字串的長度(位元組數),當-1時,函式會自動判斷源字串的長度

PWSTR pWideCharStr

轉換後的Unicode字串存入的目標緩衝區

int cchWideChar

目標緩衝區最大的長度(字元數)

返回值

函式失敗,返回0,可呼叫GetLastError函式

函式成功,兩種情況:

A、cchWideChar設為0,則返回值為轉換後寬字元數(含\0)。

B、cchWideChar設為非0,返回寫入目標緩衝區的字元數(包括字串結尾的NULL);

★★轉換的步驟

  ①計算所需目標緩衝區的大小:-先將目標緩衝區為NULL,長度為0,表示不轉換隻統計

                    iSize = MultiByteToWideChar(CP_ACP,0,pMultiByteStr,-1,NULL,0);

   ②分配iSize*sizeof(wchar_t)大小的目標緩衝區。

   ③再次呼叫MultiByteToWideChar(CP_ACP,0,pMultiByteStr,-1, pWideCharStr,iSize);

   ④使用轉換後的字串

   ⑤釋放Unicode字串記憶體塊

(2)WideCharToMultiByte——寬位元組字串轉為多位元組字串

引數

描述

UINT uCodePage

指定新字串相關聯的ANSI內碼表。

CP_ACP:指定為當前系統內碼表

CP_OEMCP 當前系統OEM內碼表

CP_UTF8:將寬位元組轉為UTF_8

DWORD dwFlags

指定如何處理沒有轉換的字元,一般設為0。

PCWSTR pWideCharStr

要轉換的字串

int cchWideChar

要轉換字串的長度(字元數),當-1時,函式會自動判斷源字串的長度

PSTR pMultiByteStr

轉換後的多位元組字串存入的目標緩衝區

int cbMultiByte

目標緩衝區最大的長度(位元組數)

pDefaultChar

在uCodePage指定的內碼表中找不到對應的字元時,用該字元來替換,如果設為NULL,則會用系統預設的字元,一般是?替換。

pfUsedDefaultChar

轉換過程中,如果至少有一pDefaultChar指定的字元替換,則這裡會被設為TRUE,否則,設為FALSE。可以通過這個變數,驗證轉換是否成功。該引數一般設為NULL

返回值

函式失敗,返回0,可呼叫GetLastError函式

函式成功,兩種情況:

A、cbMultiByte設為0,則返回值為轉換後所需的位元組數(含\0)。

B、cbMultiByte設為非0,返回寫入目標緩衝區的位元組數(包括字串結尾的NULL);

★★轉換的步驟——與MultiByteToWideChar相似,唯一不同的是第1 次呼叫時返回值就是本身所需的位元組數,所以無需進行乘法運算。

(3)利用ATL庫中的函式來轉換字符集

①使用時先#include <atlconv.h>,然後呼叫巨集USES_CONVERSION(只需呼叫一次)

②巨集A2T、A2W、W2T、W2A進行轉換

   ★2表示to,W表示Unicode,A表示ANSI(或MBCS),T表示通用型別(看UNICODE巨集)

   ★使用 ATL 轉換巨集,在棧上分配記憶體,由於不用釋放臨時空間,所以使用起來非常方便。但使用時要注意幾點:

    A:只適合於進行短字串的轉換;

    B:不要試圖在一個次數比較多的迴圈體內進行轉換;

    C:不要試圖對字元型檔案內容進行轉換,因為檔案尺寸一般情況下是比較大的;

    D:對情況 B 和 C,要使用 MultiByteToWideChar和 WideCharToMultiByte;

【AnsiAndUnicode】多位元組與Unicode之間的轉換

/*------------------------------------------------------------------------------------
   多位元組與Unicode之間的轉換
-------------------------------------------------------------------------------------*/
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <locale.h>
#include <atlconv.h>  //ATL庫提供了更簡便的字符集轉換函式


//Ansi轉化Unicode
TCHAR* AnsiToUnicode(PCSTR pMuliByteStr)
{
    int iCount;
    TCHAR* pRet;

    //計算所需緩衝區大小(字元數)
    iCount = MultiByteToWideChar(CP_ACP, 0, pMuliByteStr,-1,NULL,0);//返回字元個數(含\0)
    pRet = (TCHAR*)malloc(iCount*sizeof(wchar_t)); //分配目標緩衝區大小    
    MultiByteToWideChar(CP_ACP, 0, pMuliByteStr, -1, pRet, iCount);

    return pRet;
}

//Unicode轉化Ansi
char* UnicodeToAnsi(wchar_t* pWideChar)
{
    int iCount;
    char* pRet;

    //計算所需緩衝區大小(位元組數)
    iCount = WideCharToMultiByte(CP_ACP, 0, pWideChar, -1, NULL, 0,NULL,NULL);//返回位元組數(含\0)
    pRet = (char*)malloc(iCount); //分配目標緩衝區大小    
    WideCharToMultiByte(CP_ACP, 0,pWideChar,-1,pRet,iCount,NULL,NULL);

    return pRet;
}

//UnicodeToUTF8
char* UnicodeToUTF8(wchar_t* pWideChar)
{
    int iCount;
    char* pRet;

    //計算所需緩衝區大小(位元組數)
    iCount = WideCharToMultiByte(CP_UTF8, 0, pWideChar, -1, NULL, 0, NULL, NULL);//返回位元組數(含\0)
    pRet = (char*)malloc(iCount); //分配目標緩衝區大小    
    WideCharToMultiByte(CP_UTF8, 0, pWideChar, -1, pRet, iCount, NULL, NULL);

    return pRet;
}

//UTF8轉化Unicode
TCHAR* UTF8ToUnicode(PCSTR pMuliByteStr)
{
    int iCount;
    TCHAR* pRet;

    //計算所需緩衝區大小(字元數)
    iCount = MultiByteToWideChar(CP_UTF8, 0, pMuliByteStr, -1, NULL, 0);//返回字元個數(含\0)
    pRet = (TCHAR*)malloc(iCount*sizeof(wchar_t)); //分配目標緩衝區大小    
    MultiByteToWideChar(CP_UTF8, 0, pMuliByteStr, -1, pRet, iCount);

    return pRet;
}

int _tmain()
{
    setlocale(LC_ALL, "chs");

    //Ansi字元
    char pStringA[] = "123ABC漢字абвгде╔╗╚╝╠╣╦";
    printf("Ansi轉化為Unicode\n");
    printf("轉換前pStringA:%s\t長度:%d(bytes)\n", pStringA,sizeof(pStringA));

    wchar_t* pStringB = AnsiToUnicode(pStringA);
    wprintf(L"轉換後pStringB:%s\t長度:%d(bytes)\n", pStringB,(lstrlenW(pStringB)+1)*sizeof(wchar_t));

    printf("\n將Unicode(pStringB)再轉為Ansi\n");
    char* pStringC = UnicodeToAnsi(pStringB);
    printf("轉換後pStringC:%s\t長度:%d(bytes)\n", pStringC, (strlen(pStringC) + 1));

    printf("\nUnicode(pStringB)轉為UTF-8\n");
    char* pStringD = UnicodeToUTF8(pStringB);
    wprintf(L"轉換後pStringD:%s\t長度:%d(bytes)\n", pStringD,(strlen(pStringD) + 1));

    printf("將UTF-8(pStringD)轉換後的UTF-16\n");
    wchar_t* pStringE = UTF8ToUnicode(pStringD);
    wprintf(L"轉換後pStringE:%s\t長度:%d(bytes)\n", pStringE, (lstrlenW(pStringE) + 1)*sizeof(wchar_t));

    USES_CONVERSION; //要使用ATL庫,需呼叫該巨集,只須呼叫一次。
    //使用ATL庫進行字符集轉換
    printf("\n利用ATL庫的A2W將Ansi轉化為Unicode\n");
    wchar_t* pTmp = A2W(pStringA);
    wprintf(L"轉換前pStringA:%s\t長度:%d(bytes)\n", pTmp, (lstrlenW(pTmp) + 1)*sizeof(wchar_t));

    printf("\n利用ATL庫的W2A將Unicode轉化為ANSI\n");
    char* pTmpChar = W2A(pTmp);
    printf("轉換前pTmp    :%s\t長度:%d(bytes)\n", pTmpChar, (strlen(pTmpChar) + 1));

    free(pStringB);
    free(pStringC);
    free(pStringD);
    free(pStringE);
    return 0;
}

2.8 其他與字串有關的函式

(1)IsTextUnicode——判斷檔案是否包含Unicode或ANSI(利用統計規律,並不精確

引數

描述

LPVOID lpBuffer

要測試的字串,其緩衝區地址

int cb

lpBuffer指向的位元組數(注意是不是字元數)

LPINT lpi

是個in/out型別的,傳入時指定哪些測試專案,傳出時為符合哪個測試專案。如果為NULL,表示執行每一項測試。

返回值

TRUE或FALSE

(2)系統本地語言支援(National Language Support)

  ①GetACP函式:可以檢索系統中ANSI內碼表識別符號。

  ②GetCpInfo函式:可以得到內碼表中的詳細資訊:最大字元大小、預設字元、前置符(詳見CPINFO結構體)

③IsDBCSLeadByte函式:可以判定所給字元是否是雙位元組字元的第一個位元組。

④獲取系統安裝的語言版本,系統區域設定等資訊

函式

描述

GetSystemDefaultLCID

獲取系統預設的區域設定ID;具體可以對照一下:控制面板è區域和語言選項→高階;

GetUserDefaultLCID

獲取當前使用者預設的區域設定ID; 具體可以對照一下:控制面板è區域和語言選項→區域選項

GetLocaleInfo

根據區域設定ID來獲取本地語言名稱

GetSystemDefaultLangID

取得系統預設ID 對應的國家地區

GetUserDefaultLangID

為當前使用者取得預設語言ID

【StringReverse程式】反轉字串(動態連結庫中提供ANSI和Unicode兩種版本的匯出函式)

/*---------------------------------------------------------------------------------------
匯出ANSI和Unicode 動態連結庫函式
---------------------------------------------------------------------------------------*/
#include <windows.h>
#include "..\ReverseDLL\ReserveDll.h"
#include <tchar.h>
#include <stdio.h>

//靜態呼叫動態庫
#pragma comment(lib,"..\\Debug\\reverseDll.lib")

int _tmain()
{
    //Unicode版的翻轉
    printf("Unicode版的翻轉\n");
    wchar_t szWideChar[] = L"I am a Student.";
    printf("翻轉前字串:%ws\n", szWideChar);
    StringReverseW(szWideChar, lstrlenW(szWideChar));
    printf("翻轉後字串:%ws\n", szWideChar);

    printf("\nANSI版的翻轉\n");
    char szAnsiBuffer[] = "You are a teacher!";
    printf("翻轉前字串:%s\n", szAnsiBuffer);
    StringReserseA(szAnsiBuffer, strlen(szAnsiBuffer));
    printf("翻轉後字串:%s\n", szAnsiBuffer);
    int a = sizeof("A");
    return 0;
}
#pragma once
#include <windows.h>

#ifdef _cplusplus
    #ifdef API_EXPORT 
        #define EXPORT   extern "C" __declspec(dllexport)          //當頭檔案供動態庫本身使用時
        #else
        #define EXPORT    extern "C" __declspec(dllimport)         //當頭檔案供呼叫庫的程式使用時
    #endif
#else
    #ifdef API_EXPORT 
        #define EXPORT   __declspec(dllexport)                      //當頭檔案供動態庫本身使用時
        #else
        #define EXPORT   __declspec(dllimport)                      //當頭檔案供呼叫庫的程式使用時
    #endif
#endif

EXPORT BOOL StringReverseW(PWSTR pWideCharStr, DWORD cchLength);
EXPORT BOOL StringReserseA(PSTR pMultiByteStr, DWORD cchLength);

#ifdef UNICODE
#define StringReverse  StringReverseW
#else
#define StringReverse  StringReserseA
#endif // DEBUG
#define API_EXPORT
#include "ReserveDll.h"

int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
    return TRUE;
}

EXPORT BOOL StringReverseW(PWSTR pWideCharStr, DWORD cchLength)
{
    //獲取字串最後一個字元的指標
    PWSTR pEndOfStr = pWideCharStr + wcsnlen_s(pWideCharStr, cchLength) - 1;

    wchar_t cCharT; //臨時變數

    //迴圈直到到達字串中間的字元
    while (pWideCharStr <pEndOfStr)
    {
        //交換字元
        cCharT = *pWideCharStr;    //將變數儲存在臨時變數中
        *pWideCharStr = *pEndOfStr;//交換
        *pEndOfStr = cCharT;

        pWideCharStr++;
        pEndOfStr--;
    }
    return TRUE;
}

EXPORT BOOL StringReserseA(PSTR pMultiByteStr, DWORD cchLength)
{
    int nLenofWideChar;
    PWSTR pWideCharStr;
    BOOL fOk = FALSE;

    //計算字元的個數以建立容納Unicode字串
    nLenofWideChar = MultiByteToWideChar(CP_ACP, 0, pMultiByteStr, cchLength, NULL, 0);

    //在呼叫程序的預設堆上申請一塊不可移動的記憶體
    pWideCharStr = (PWSTR)HeapAlloc(GetProcessHeap(), 0, nLenofWideChar*sizeof(wchar_t));
    if (pWideCharStr == NULL)
        return fOk;

    //轉換為Unicode字串
    MultiByteToWideChar(CP_ACP, 0, pMultiByteStr, cchLength, pWideCharStr, nLenofWideChar);

    //呼叫StringReverseW函式進行實際的轉換
    fOk = StringReverseW(pWideCharStr,cchLength);

    if (fOk)
    {
        //將Unicode再轉換為Ansi
        WideCharToMultiByte(CP_ACP, 0, pWideCharStr, cchLength, pMultiByteStr,
                          (int)strlen(pMultiByteStr), NULL, NULL);
    }

    //釋放記憶體
    HeapFree(GetProcessHeap(), 0, pWideCharStr);

    return fOk;
}