程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 提前認識軟件開發(13) 指針及結構體的使用

提前認識軟件開發(13) 指針及結構體的使用

編輯:關於C語言

指針在C語言中占有很重要的地位,同時也是學習C語言的難點所在。結構體屬於用戶自己建立的數據類型,在實際的軟件開發項目中應用很廣泛。

本文以實際的例子介紹了C語言中指針和結構體的使用方法,為進一步的學習和應用提供了有益的參考。

1.指針和結構體簡介

在C語言中,將地址形象化地稱為指針,意即通過它能夠找到以它為地址的內存單元。實際上,使用指針是對一個內存單元的間接訪問。例如,有一個變量Var的值為1,使用一個變量Var_Pointer存放變量Var在內存中的地址3000,通過該地址能夠找到變量Var在內存中的值,那麼這種間接訪問操作的示意圖如圖1所示。

圖1指針操作示意圖

在諸如數組這樣的數據結構中,所有的數據都是同一種類型,即不能存放不同類型(如整型和字符型)的數據。結構體(structure)的出現解決了這個問題,它允許用戶自己建立由不同類型數據組成的組合型的數據結構。

在實際的軟件開發項目中,指針和結構體都有很重要的應用,要成為一名合格的軟件開發工程師,一定要學會靈活運用指針和結構體來編寫C語言程序。

2.本文中使用的程序流程說明

本文中程序實現的功能為:從本地文件中讀取以約定格式組成的員工的信息記錄(包括工號、姓名和年齡,字段之間以“|”分隔),解析後將每個字段的內容輸出到屏幕上。流程圖如圖2所示。

圖2本程序流程圖

本程序文件命名為“Pointer.c”,使用的本地文件命名為“EmployeeInfo.ini”,文件裡面的內容為形如“工號|姓名|年齡”這樣的記錄,內容存放示例如圖3所示。

圖3文件內容存放示例圖

注意,在程序編譯運行的時候,要將本地文件存放到與“Pointer.c”同級目錄下,這樣才能夠讀取到記錄信息。

3.程序代碼

*  版本       修改時間     修改人           修改內容
    
********************************************************************
    
*   V1.0        20140416    周兆熊             創建
    
**********************************************************************/
    
#include <stdio.h>
    
#include <stdlib.h>
    
#include <string.h>
    
     
    
//字段最大長度
    
#define MAX_RET_BUF_LEN     (1024)
    
     
    
//數據類型
    
typedef unsigned char       UINT8;
    
typedef unsigned short int  UINT16;
    
typedef unsigned int        UINT32;
    
typedef signed   int        INT32;
    
typedef unsigned char       BOOL;
    
     
    
//參數類型
    
#define MML_INT8_TYPE       0
    
#define MML_INT16_TYPE      1
    
#define MML_INT32_TYPE      2
    
#define MML_STR_TYPE        3
    
     
    
#define  TRUE         (BOOL)1
    
#define  FALSE        (BOOL)0
    
     
    
//員工信息結構體
    
typedef struct
    
{
    
    UINT8  szEmployeeID[1024];      //員工工號
    
    UINT8  szEmployeeName[1024];    //員工姓名
    
    UINT32 iEmployeeAge;            //員工年齡
    
} T_EmployeeInfo;
    
     
    
/**********************************************************************
    
*功能描述:獲取字符串中某一個字段的數據
    
*輸入參數: iSerialNum-字段編號(為正整數)
    
             iContentType-需要獲取的內容的類型
    
             pSourceStr-源字符串
    
             pDstStr-目的字符串(提取的數據的存放位置)
    
             cIsolater-源字符串中字段的分隔符
    
             iDstStrSize-目的字符串的長度
    
*輸出參數:無
    
*返回值: TRUE-成功  FALSE-失敗
    
*其它說明:無
    
*修改日期        版本號            修改人         修改內容
    
* --------------------------------------------------------------
    
* 20140416         V1.0               zzx           創建
    
***********************************************************************/
    
BOOL GetValueFromStr(UINT16 iSerialNum, UINT8 iContentType, UINT8 *pSourceStr, UINT8 *pDstStr, UINT8 cIsolater, UINT32 iDstStrSize)
    
{
    
    UINT8  *pStrBegin                 = NULL;
    
    UINT8  *pStrEnd                   = NULL;
    
    UINT8   szRetBuf[MAX_RET_BUF_LEN] = {0}; //截取出的字符串放入該數組中
    
    UINT8  *pUINT8                    = NULL;
    
    UINT16 *pUINT16                   = NULL;
    
    UINT32 *pUINT32                   = NULL;
    
    UINT32  iFieldLen                 = 0;     //用於表示每個字段的實際長度
    
     
    

    if (pSourceStr == NULL)           //對輸入指針的異常情況進行判斷
    
    {
    
        return FALSE;
    
}
    
    //字段首
    
    pStrBegin = pSourceStr;
    
    while (--iSerialNum != 0)
    
    {
    
        pStrBegin = strchr(pStrBegin, cIsolater);
    
        if (pStrBegin == NULL)
    
        {
    
            return FALSE;
    
        }
    
        pStrBegin ++;
    
    }
    
     
    
    //字段尾
    
    pStrEnd = strchr(pStrBegin, cIsolater);
    
    if (pStrEnd == NULL)
    
    {
    
        return FALSE;
    
    }
    
     
    
    iFieldLen = (UINT16)(pStrEnd - pStrBegin);
    
    if(iFieldLen >= MAX_RET_BUF_LEN) //進行異常保護, 防止每個字段的值過長
    
    {
    
        iFieldLen = MAX_RET_BUF_LEN - 1;
    
    }
    
     
    
    memcpy(szRetBuf, pStrBegin, iFieldLen);
    
     
    
    //將需要的字段值放到pDstStr中去
    
    switch (iContentType)
    
    {
    
        case MML_STR_TYPE:                        //字符串類型
    
        {
    
            strncpy(pDstStr, szRetBuf, iDstStrSize);
    
            break;
    
        }
    
     
    
        case MML_INT8_TYPE:                       //字符類型
    
        {
    
            pUINT8   = (UINT8 *)pDstStr;
    
            *pDstStr = (UINT8)atoi(szRetBuf);
    
            break;
    
        }
    
     
    
        case MML_INT16_TYPE:                      // short int類型
    
        {
    
            pUINT16  = (UINT16 *)pDstStr;
    
            *pUINT16 = (UINT16)atoi(szRetBuf);
    
            break;
    
        }
    
     
    
        case MML_INT32_TYPE:                      // int類型
    
        {
    
            pUINT32  = (UINT32 *)pDstStr;
    
            *pUINT32 = (UINT32)atoi(szRetBuf);
    
            break;
    
        }
    
     
    
        default:                                  //一定要有default分支
    
        {
    
            return FALSE;
    
        }
    
    }
    
     
    
    return TRUE;
    
}
*修改日期        版本號       修改人        修改內容
    
* -------------------------------------------------------------------------------
    
* 20140416         V1.0          zzx           創建
    
****************************************************************/
    
INT32 main(void)
    
{
    
    UINT32  iInfoCount          = 0;      //該變量用於計算記錄條數
    
    UINT8   szContentLine[1024] = {0};       //用於存放從文件中獨到的每條記錄
    
    FILE   *hFile               = NULL;   //文件句柄指針
    
       
    
    //打開文件
    
    hFile = fopen("EmployeeInfo.ini", "r");
    
    if (!hFile)                     //打開失敗
    
    {
    
        printf("Open EmployeeInfo.ini failed!\n");
    
        return -1;                  //異常退出
    
    }
    
     
    
    while (NULL != fgets(szContentLine, sizeof(szContentLine), hFile))
    
    {
    
        T_EmployeeInfo t_EmployeeInfo = {0};
    
     
    
        iInfoCount ++;           //每讀取到一條記錄, 則記錄條數加1
    
     
    
        //獲取EmployeeID
    
        if (TRUE != GetValueFromStr(1, MML_STR_TYPE, szContentLine, t_EmployeeInfo.szEmployeeID, '|', sizeof(t_EmployeeInfo.szEmployeeID)))
    
        {
    
            printf("獲取第%d位員工的工號失敗.\n", iInfoCount);
    
            return -1;
    
        }
    
     
    
        //獲取EmployeeName
    
        if (TRUE != GetValueFromStr(2, MML_STR_TYPE, szContentLine, t_EmployeeInfo.szEmployeeName, '|', sizeof(t_EmployeeInfo.szEmployeeName)))
    
        {
    
            printf("獲取第%d位員工的姓名失敗.\n", iInfoCount);
    
            return -1;
    
        }
    
     
    
        //獲取EmployeeAge
    
        if (TRUE != GetValueFromStr(3, MML_INT32_TYPE, szContentLine, (UINT8 *)&(t_EmployeeInfo.iEmployeeAge), '|', sizeof(t_EmployeeInfo.iEmployeeAge)))
    
        {
    
            printf("獲取第%d位員工的年齡失敗.\n", iInfoCount);
    
            return -1;

    
        }
    
     
    
        //逐條打印每個員工的信息
    
        printf("第%d位員工的信息為:工號=%s, 姓名=%s,年齡=%d.\n", iInfoCount, t_EmployeeInfo.szEmployeeID, t_EmployeeInfo.szEmployeeName, t_EmployeeInfo.iEmployeeAge);
    
    }
    
     
    
    fclose(hFile);         //最後一定要關閉文件句柄
    
     
    
    return 0;
    
}

4.程序內容詳解

4.1員工信息結構體T_EmployeeInfo

typedef struct
    
{
    
    UINT8  szEmployeeID[1024];       //員工工號
    
    UINT8  szEmployeeName[1024];    //員工姓名
    
    UINT32 iEmployeeAge;            //員工年齡
    
} T_EmployeeInfo;

說明:

(1)因為文件中每條記錄包括了工號、姓名和年齡,所以結構體中要定義三個成員變量,其中工號和姓名是字符串類型,年齡為整型。

(2)注意成員變量的命名規則,字符串類型以“sz”開頭,整型以“i”開頭,方便對變量進行識別。同時,為了防止每個字段的內容過長,定義字符數組的長度為1024(不要超過MAX_RET_BUF_LEN的大小)。

4.2字段數據獲取函數GetValueFromStr

該函數的工作原理為:根據輸入的參數來從pSourceStr中獲取第iSerialNum字段的內容,存放到pDstStr中,各個字段以cIsolater分隔開來。

注意,在執行函數的主要邏輯之前,要對指針進行保護,即對指針的異常情況進行判斷(判斷其是否為空,具體見程序代碼)。在實際的軟件開發項目中,這一點是非常重要的。

該函數的工作步驟為:

第一步:獲取每個字段的字段首和字段尾指針。strchr函數用於查詢兩個字段之間cIsolater的地址,字段的首位指針值相減就得到該字段的長度,並使用memcpy函數將該字段值拷貝到szRetBuf中。為了防止源串中字段值過長,還對解析出來的字段長度進行了異常保護。該方法在實際的軟件開發項目中經常用到。

第二步:將解析出的字段值放到pDstStr中去。根據不同的數據類型(如字符串、整型等),將第一步獲得的字段值存放到pDstStr中。由於第一步的szRetBuf為字符數組,而某些字段值要求為整數,因此在要求參數類型為整型的case分支中使用了atoi函數。注意,switch語句一定要有default分支。

4.3主函數中的文件操作函數

在主函數(main)中,使用了文件操作函數fopen、fgets和fclose。

(1) fopen函數

在使用文件之前,先要將其打開,本程序以只讀的方式(該函數第二個參數為r)操作文件,防止對文件的錯誤寫入。

(2) fgets函數

該函數用於從文件中讀取一個字符串,其描述如下:

函數定義:char *fgets(char *s, int size, FILE *stream);

函數說明:fgets()用來從參數stream所指的文件內讀入字符並存到參數s所指的內存空間,直到出現換行字符、讀到文件尾或是已讀了size-1個字符為止,最後會加上NULL作為字符串結束。

返回值:若成功則返回s指針,返回NULL則表示有錯誤發生或內容讀取完成。

在本程序中,將從文件中讀取到的內容存放到szContentLine中。

(3) fclose函數

該函數用於在操作完文件之後關閉文件指針,防止對該文件的錯誤操作。fclose函數一定要與fopen函數配對。在使用完文件之後,一定要調用fclose函數將文件關閉。

4.4 GetValueFromStr函數的調用

以獲取員工年齡的調用為例加以說明,調用代碼如下:

if (TRUE != GetValueFromStr(3, MML_INT32_TYPE, szContentLine, (UINT8 *)&(t_EmployeeInfo.iEmployeeAge), '|', sizeof(t_EmployeeInfo.iEmployeeAge)))

{

printf("獲取第%d位員工的年齡失敗.\n", iInfoCount);

return -1;

}

(1) GetValueFromStr函數的定義為:BOOL GetValueFromStr(UINT16 iSerialNum, UINT8 iContentType, UINT8 *pSourceStr, UINT8 *pDstStr, UINT8 cIsolater, UINT32 iDstStrSize),調用的時候,實參3對應形參iSerialNum,實參MML_INT32_TYPE對應形參iContentType,實參szContentLine對應形參pSourceStr,實參&(t_EmployeeInfo.iEmployeeAge)對應形參pDstStr,實參'|'對應形參cIsolater,實參sizeof(t_EmployeeInfo.iEmployeeAge)對應形參iDstStrSize。

(2)在函數調用的時候,實參和形參類型要完全匹配,如GetValueFromStr函數要求第3個參數為字符型指針,則傳入參數szContentLine也要為同樣類型的指針(因為字符數組名就代表該字符數組的首地址,即指針,所以滿足要求)。對於第4個參數,因為年齡為整型數據,而要求傳入的實參為字符型指針,因此要在t_EmployeeInfo.iEmployeeAge前面添加&來表示指針,同時還要在前面添加(UINT8 *)將該指針類型轉換為字符類型。第5個參數要求為一個字符,因此實參為'|',注意不要將單引號寫成了雙引號(雙引號表示字符串)。

(3)如果獲取字段失敗,那麼直接返回-1,不再走下面的流程。這樣可確保每條打印出的信息都是正確的。

4.5字段信息的輸出打印

為了查看程序解析是否正確,需要在終端打印相關信息。直接使用結構體成員變量來輸出對應字段的值。

5.程序測試

在實際的軟件開發項目中,將測試分為正常測試和異常測試。正常測試是嚴格按照程序的要求來設計測試流程,異常測試的目的是看在不滿足程序要求時,得到的結果會是怎樣的。

(1)正常測試

按照圖3的文件內容來編寫EmployeeInfo.ini文件,並將之放到與“Pointer.c”同級目錄下。運行程序,得到的結果如圖4所示。

圖4正常測試的輸出結果

從輸出結果可以看出,程序對信息內容的解析是正確的,因此,指針和結構體變量的使用也是正確的。

(2)異常測試

在實際的軟件開發項目中,一定要進行大量的異常測試,以檢查程序的正確性。

1)未正確放置EmployeeInfo.ini文件

刪除EmployeeInfo.ini文件,或將它放到其它目錄下,則程序輸出結果如圖5所示。

圖5文件不存在時的輸出結果

2) EmployeeInfo.ini文件中的記錄內容不符合要求

將第二條記錄的字段分隔符“|”去掉,則程序輸出結果如圖6所示。

圖6第二條記錄的字段分隔符“|”去掉時的輸出結果

3) EmployeeInfo.ini文件中無內容

將EmployeeInfo.ini文件中的內容全部刪除掉,則程序輸出結果如圖7所示。

圖7 EmployeeInfo.ini文件中無內容時的輸出結果

還有很多異常的情況,這裡就不一一列舉了。

一般而言,在產品發布之前,一定要經過充分的測試。

6.總結

指針及結構體在軟件開發項目中是很常見的,掌握它們的使用方法是軟件開發工程師的必修課。

本文用實例來描述了指針及結構體的具體用法。“冰凍三尺,非一日之寒”,要想熟練掌握它們的用法,還需要我們多多地實踐,還需要我們不斷地練習和總結。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved