阅读笔记《c程序员从校园到职场》第七章指针和结构体(代码片段)

CodingNote CodingNote     2022-11-07     302

关键词:

原文地址:让你提前认识软件开发(13):指针及结构体的使用

CSDN博客 https://blog.csdn.net/zhouzhaoxiong1227/article/details/23872995


 

【文章摘要】

        指针在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.程序代码

/**********************************************************************
*版权所有 (C)2014, Zhou Zhaoxiong。
*文件名称: Pointer.c
*内容摘要:用于演示指针和结构体操作
**********************************************************************/
#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-失败 *其它说明:无 **********************************************************************
*/ 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; /****************************************************************
*功能描述: 主函数 * *输入参数: 无 * *输出参数: 无 * *返回值 :无
***************************************************************
*/ 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的大小)。

PS: 字符串 character string

 

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.总结

        指针及结构体在软件开发项目中是很常见的,掌握它们的使用方法是软件开发工程师的必修课。

       本文用实例来描述了指针及结构体的具体用法。“冰冻三尺,非一日之寒”,要想熟练掌握它们的用法,还需要我们多多地实践,还需要我们不断地练习和总结。

 







阅读笔记《c程序员从校园到职场》第五章内存操作(代码片段)

参考:  让你提前认识软件开发(8):memset()与memcpy()函数 https://blog.csdn.net/zhouzxi/article/details/22478081让你提前认识软件开发(10):字符串处理函数及异常保护 https://blog.csdn.net/zhouzxi/article/details/22976307  查看详情

阅读笔记《c程序员从校园到职场》第三章程序的样式(大括号)(代码片段)

参考:https://blog.csdn.net/zhouzhaoxiong1227/article/details/22820533 一、.初始化数组变量       在实际的软件开发项目中,变量在使用前应初始化,防止未经初始化的变量被引用。      &n 查看详情

阅读笔记《c程序员从校园到职场》第六章配置文件,makefile文件(part2)

 Contents:1.配置文件(通常以ini结尾)2.makefile文件(Linux) PS:这篇文章的内容,不太理解。  一、配置文件 本文以一个实际的小软件为例,介绍了C语言中配置文件的读取方法和重要的文件操作函数的使用方法... 查看详情

阅读笔记《c程序员从校园到职场》第六章常用文件操作函数(part1)(代码片段)

参考链接:https://blog.csdn.net/zhouzhaoxiong1227/article/details/24926023让你提前认识软件开发(18):C语言中常用的文件操作函数总结及使用方法演示代码-CSDN博客  Contents:1.C语言中常用的文件操作函数总结(1)fopen  作用:打开... 查看详情

c内存操作---自《c程序员从校园到职场》

1. memset andmemcpy /**********************************************/2.strcatandstrncat /*********************************************/3.strcpyandstrncpy /********************* 查看详情

黑马程序员c++教程从0到1入门编程笔记1数据类型运算符程序流程结构数组函数指针结构体(代码片段)

黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难文章目录1、C++初识1.1第一个c++程序1.2注释1.3变量1.4常量1.5关键字1.6标识符命名规则2数据类型2.1整型2.2sizeof关键字2.3实型(浮点型)(科学... 查看详情

c语言笔记初级篇第七章:结构体相关

目录(1)结构体的声明,定义,初始化和成员访问A:什么是结构体B:结构体声明和定义C:结构体的初始化D:成员访问(2)结构体传参(3)结构体自引用(4)内存对齐࿰... 查看详情

c语言笔记初级篇第七章:结构体相关

目录(1)结构体的声明,定义,初始化和成员访问A:什么是结构体B:结构体声明和定义C:结构体的初始化D:成员访问(2)结构体传参(3)结构体自引用(4)内存对齐࿰... 查看详情

c语言学习笔记整理(代码片段)

C语言学习笔记整理一.数据在内存中的存储1.1数据类型介绍1.2整形在内存中的存储1.3大小端字节序介绍1.4浮点型在内存中的存储解析二.指针详细介绍2.1字符指针2.2指针数组2.3数组指针2.4数组传参和指针传参2.5函数指针2.6函数指针... 查看详情

c和指针第10章结构和联合

...,但是传入整个结构体效率很低,可以传入指向结构体的指针来提高效率。如果不希望程序对结构体变量改变可以加入const关键词。typedefstruct{intid;intnum;charname[100];}Produts;//传入指针,加const修饰,防止程序修改voidtest(Produtsconst*ptr);... 查看详情

c语言关于结构体做参数传递?

...A双指针C语言结构体传参小-黯原创关注7点赞·2315人阅读目录C语言结构体传参1.普通传参1.1测试代码1.2测试结果1.3结果分析2.单指针传参2.1修改结构体数据2.1.1测试代码2.1.2测试结果2.1.3结果分析2.2修改结构体地址2.2.1测试代... 查看详情

c++文档阅读笔记-differencebetweencstructuresandc++structures(代码片段)

这里来讨论struct在C和C++的异同。在C++中struct和class极其相似。C和C++的不同C结构体C++结构体只有成员变量,没有成员函数。不仅有成员变量,还有成员函数。不运行构造函数。可以使用构造函数。... 查看详情

c++文档阅读笔记-differencebetweencstructuresandc++structures(代码片段)

这里来讨论struct在C和C++的异同。在C++中struct和class极其相似。C和C++的不同C结构体C++结构体只有成员变量,没有成员函数。不仅有成员变量,还有成员函数。不运行构造函数。可以使用构造函数。... 查看详情

c++文档阅读笔记-differencebetweencstructuresandc++structures(代码片段)

这里来讨论struct在C和C++的异同。在C++中struct和class极其相似。C和C++的不同C结构体C++结构体只有成员变量,没有成员函数。不仅有成员变量,还有成员函数。不运行构造函数。可以使用构造函数。... 查看详情

c语言结构体里的成员数组和指针

本文通过阅读陈皓的文章总结 http://coolshell.cn/articles/11377.html1、所谓变量只是内存中抽象的一个名字,在静态编译时都会转换成相应的内存地址,我们的变量都会在编译的时候被编译器放入内存区中2、当访问结构体成员变量... 查看详情

从校园到职场,如果是你会和我一样吗?

...章目录写在前面A初入职场--蒙着眼忐忑中向前摸索B回到校园--被时间逼着做出选择C再入职场--眼里有光、心中有理想最后的最后写在前面    关于离开校园后步入职场这个过程,原本早就想写一写的,但大概是半年... 查看详情

golang入门到项目实战golang结构体指针

参考技术A结构体指针和普通的变量指针相同,我先来回顾一下普通变量的指针,例如:运行结果实例运行结果我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址,例如:运行结果从运行结果,我们发现p_... 查看详情

在golang里如何实现结构体成员指针到结构体自身指针的转换

...一个经典的宏定义,可以将结构体struct内部的某个成员的指针转化为结构体自身的指针。下面是一个例子,通过FIELD_OFFSET宏计算结构体内一个字段的偏移,函数getT可以从一个F*的指针获得对应的T*对象。structF{intc;intd;}structT{inta;in... 查看详情