精华内容
下载资源
问答
  • 致 Python 初学者

    万次阅读 多人点赞 2019-11-10 00:03:26
    Anaconda Python 的主要用例包括数学、统计学、工程数据分析、机器学习以及其他相关应用。Anaconda 捆绑有 Python 商业科学使用场景当中的各类常用——包括 SciPy、NumPy 以及 Numba 等等,同时通过一套定制化...
    展开全文
  • C语言:各种函数用法

    千次阅读 2019-08-28 14:53:03
    1.函数调用 函数调用有三种方式: 1.作为语句调用: ...2.c语言函数函数的定义不能被嵌套,函数调用可以被嵌套 函数的默认值不允许为局部变量 建立自定义函数的目的是: 1.调用时只需要明白函数的功...

    1.函数调用

    函数调用有三种方式:

    1.作为语句调用:

    2.作为函数表达式调用:

    3.作为函数参数调用:

    https://blog.csdn.net/zhou1432590267/article/details/79028095

     

    2.c语言函数:

    函数的定义不能被嵌套,函数调用可以被嵌套

    函数的默认值不允许为局部变量

    建立自定义函数的目的是:

    1.调用时只需要明白函数的功能即可,提高了程序的可读性。 

    2.效率而言,调用函数往往会导致效率下降。

    3.多次调用可能减少篇幅,一次调用的反而会增加。肯定不会减少内存。

     

     

    1.rand()函数

    rand函数功能为获取一个伪随机数(伪随机数的概念下面会有介绍)。

    一、函数名: 

    rand();

    二、声明:

    int rand();

    三、所在头文件:

    stdlib.h

    四、功能:

    返回一个伪随机数。之所以说是伪随机数,是因为在没有其它操作下,每次执行同一个程序,调用rand得到的随机数序列是固定的(不是真正的“随机”)。

    五、为了使rand的结果更“真”一些,也就是令其返回值更具有随机性(不确定性),C语言在stdlib.h中还提供了srand函数,通过该函数可以设置一个随机数种子,一般用当前时间的毫秒数来做参数。通过time(NULL)可以获取到当前时间的毫秒值(该函数位于time.h)中。

    六、使用rand的流程可以总结为:

    1 调用srand(time(NULL))设置随机数种子。

    2 调用rand函数获取一个或一系列随机数。

    需要注意的是,srand只需要在所有rand调用前,被调用一次即可,没必要调用多次。

    七、以下是随机获取10个整型值并输出的例子,辅助理解。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    #include <stdio.h>

    #include <stdlib.h>

    #include <time.h>

     

    int main()

    {

        int i;

         

        srand(time(NULL));//设置随机数种子。

         

        for(i = 0; i < 10; i ++)//运行10次。

            printf("%d\n"rand());//每次获取一个随机数并输出。

         

        return 0;

    }

     

    八.rand函数产生一个0到RAND_MAX的伪随机数,这里的RAND_MAX因不同的实现而异,但RAND_MAX至少为32767。

    • 有些时候,用户需要获取一定更小范围中的一随机数,如得到一组100以内的随机数,就可以采用rand()%100这种方法,使得结果在100以内。

      • %是模运算,表示整除 右操作数 取余数

      • 如果想得到【1,100】之间的随机数,则需要对上面的加法进行加工,写成:

        (rand()%100)+1;  //先得到0-99的随机数,然后加1,可得1-100区间内的数

      • 例:rand()%100;表示获得一个100以内的随机数,其结果在[0-99]中

    2.strand函数

    二、srand()

    srand()函数需要的头文件仍然是:<stdlib.h>

    srand()函数原型:void srand (usigned int seed);

    srand()用来设置rand()产生随机数时的随机数种子。参数seed是整数,通常可以利用time(0)或geypid(0)的返回值作为seed。

    使用rand()和srand()产生1-100以内的随机整数:srand(time(0));

        int number1 = rand() % 100;

    3、使用rand()和srand()产生指定范围内的随机整数的方法

    “模除+加法”的方法

    因为,对于任意数,0<=rand()%(n-m+1)<=n-m

    因此,0+m<=rand()%(n-m+1)+m<=n-m+m

    因此,如要产生[m,n]范围内的随机数num,可用:

    int num=rand()%(n-m+1)+m;

    其中的rand()%(n-m+1)+m算是一个公式,记录一下方便以后查阅。

    比如产生10~30的随机整数:

    srand(time(0));

    int a = rand() % (21)+10

    4.bool函数

    布尔型(bool)变量的值只有 真 (true) 和假 (false)。

    布尔型变量可用于逻辑表达式,也就是“或”“与”“非”之类的逻辑运算和大于小于之类的关系运算,逻辑表达式运算结果为真或为假。

    bool可用于定义函数类型为布尔型,函数里可以有 return TRUE; return FALSE 之类的语句。

    布尔型运算结果常用于条件语句:if (逻辑表达式){如果是 true 执行这里;}else{如果是 false 执行这里;};

    扩展资料

    使用bool逻辑型变量的优点:

    1、 提高程序的可读性

    bool类型的变量只可能有两个值true或false,在没有统一的布尔类型在大型的工程项中特别是用到第三方程序库时,可能使用不同的手段模拟布尔类型以提交代码的可读性,这样会使得代码有些混乱,C语言中引入了bool内置类型,解决了代码的一致性问题。

    2、提高程序的性能

    bool在绝大多数编译器编译时都将其实现为1字节,即sizeof(bool)的值为1,加上其只有两个值的值域{true, false},是C语言中最小的数据类型了。

    虽然char、unsigned char和signed char类型在C语言中没有特定的实现要求,但一般也实现为一个字节,这样看来与bool类型从内存空间的占用上并没有性能上的差异。

    5.sprintf()函数

    转自:http://nnssll.blog.51cto.com/902724/198237/
    1、该函数包含在stdio.h的头文件中。
    2、sprintf和平时我们常用的printf函数的功能很相似。sprintf函数打印到字符串中,而printf函数打印输出到屏幕上。sprintf函数在我们完成其他数据类型转换成字符串类型的操作中应用广泛。
    3、sprintf函数的格式:
    int sprintf( char *buffer, const char *format [, argument,...] );
    除了前两个参数固定外,可选参数可以是任意个。buffer是字符数组名;format是格式化字符串(像:"%3d%6.2f%#x%o",%与#合用时,自动在十六进制数前面加上0x)。只要在printf中可以使用的格式化字符串,在sprintf都可以使用。其中的格式化字符串是此函数的精华。
    4、char str[20];
     double f=14.309948;
    sprintf(str,"%6.2f",f);
    可以控制精度
    5、char str[20];
    int a=20984,b=48090;
    sprintf(str,"%3d%6d",a,b);
    str[]="20984 48090"
    可以将多个数值数据连接起来。
    6、char str[20];
    char s1[5]={'A','B','C'};
    char s2[5]={'T','Y','x'};
    sprintf(str,"%.3s%.3s",s1,s2);
    可以将多个字符串连接成字符串
    %m.n在字符串的输出中,m表示宽度,字符串共占的列数;n表示实际的字符数。%m.n在浮点数中,m也表示宽度;n表示小数的位数。
    7、可以动态指定,需要截取的字符数
    char s1={'A','B','C'};
    char s2={'T','Y','x'};
    sprintf(str,"%.*s%.*s",2,s1,3,s2);
    sprintf(s, "%*.*f", 10, 2, 3.1415926);
    8、sprintf(s, "%p", &i);
    可以打印出i的地址
    上面的语句相当于
    sprintf(s, "%0*x", 2 * sizeof(void *), &i);
    9、sprintf的返回值是字符数组中字符的个数,即字符串的长度,不用在调用strlen(s)求字符串的长度。

    6.strcpy函数

    这是C语言里面复制字符串的库函数, 函数声明包括在专门处理字符串的头文件<string.h>中:
    char * strcpy( char * dst, const char * src );
    这个函数把字符串src复制到一分配好的字符串空间dst中,复制的时候包括标志字符串结尾的空字符一起复制。操作成功,返回dst,否则返回NULL.

    其一般形式为:strcpy(字符数组1,字符串2) 
    strcpy是“字符串复制函数”。
    作用:是将字符串2复制到字符数组1中去。例如:
    char str1[10],str2[]={″China″};
    strcpy(str1,”china”); 

    例子:strcpy(字符数组1,字符数组2)

    字符数组1的长度要大于字符数组2

    (1)字符数组1必须定义得足够大,以便容纳被复制的字符串。字符数组1的长度不应小于字符串2的长度。
    (2)“字符数组1”必须写成数组名形式(如str1),
    “字符串2”可以是字符数组名,也可以是一个字符串常量。如:strcpy(str1,″China″); 
    (3)复制时连同字符串后面的′\0′一起复制到字符数组1中。
    (4)可以用strncpy函数将字符串2中前面若干个字符复制到字符数组1中去。
    例如:strncpy(str1,str2,2);作用是将str2中前面2个字符复制到str1中去,然后再加一个‘\0’。

    (5)不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组。如:
       str1=″China″;  不合法    
       str1=str2;       不合法
    用strcpy函数只能将一个字符串复制到另一个字符数组中去。
    用赋值语句只能将一个字符赋给一个字符型变量或字符数组元素。
    下面是合法的使用:
    char a[5],c1,c2; 
    c1=′A′;  c2=′B′;
    a[0]=′C′; a[1]=′h′; a[2]=′i′; 
    a[3]=′n′; a[4]=′a′; 

    7.strcat函数
    其一般形式为:strcat(字符数组1,字符数组2)
    strcat的作用是连接两个字符数组中的字符串,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值——字符数组1的地址。

    例如:

    char str1[30]={″People′s  Republic  of  ″};
    char str2[]={″China″};
    printf(″%s″,strcat(str1,str2));  
    输出:
    People′s Republic of China  

     

    8. strcmp函数 
    其一般形式为:strcmp(字符串1,字符串2) 
    strcmp的作用是比较字符串1和字符串2。
    例如:strcmp(str1,str2);
              strcmp(″China″,″Korea″);
              strcmp(str1,″Beijing″);

    比较的结果由函数值带回
    (1) 如果字符串1=字符串2,函数值为0。
    (2) 如果字符串1>字符串2,函数值为一正整数。
    (3) 如果字符串1<字符串2,函数值为一负整数。
    注意:对两个字符串比较,不能用以下形式:
    if(str1>str2) 
    printf(″yes″);
    而只能用
    if(strcmp(str1,str2)>0) 
    printf(″yes″)。


    9.strncmp函数

      原型:extern int strcmp(char *s1,char * s2);    
         用法:#include <string.h>
         功能:比较字符串s1和s2的前n个字符。
         说明:
                     返回值:当s1<s2时,返回值<0
                     返回值:当s1=s2时,返回值=0
                     返回值:当s1>s2时,返回值>0 

     

      strncmp代码实现:

    int strncmp(const char *s1, const char *s2, size_t  len)
    {
       //判断str1与str2指针是否为NULL
         assert(s1!=NULL && s2 !=NULL);

        while(len--) 
       {
               if(*s1 == 0 || *s1 != *s2)
                   return *s1 - *s2;
             
               s1++;
               s2++;
          }
          return 0;
    }

    10.fseek函数

      int fseek( FILE *stream, long offset, int origin );
      第一个参数stream为文件指针
      第二个参数offset为偏移量,整数表示正向偏移,负数表示负向偏移
      第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
      SEEK_SET: 文件开头
      SEEK_CUR: 当前位置
      SEEK_END: 文件结尾
      其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2.
      简言之:
      fseek(fp,100L,0);把fp指针移动到离文件开头100字节处;
      fseek(fp,100L,1);把fp指针移动到离文件当前位置100字节处;
      fseek(fp,100L,2);把fp指针退回到离文件结尾100字节处。(根据评论来看,应该是 fseek(fp,-100L,2) )

    11.gets函数

    gets()函数用于从缓冲区中读取字符串,其原型如下:

    char *gets(char *string);

    gets()函数从流中读取字符串,直到出现换行符或读到文件尾为止,最后加上NULL作为字符串结束。所读取的字符串暂存在给定的参数string中。

    【返回值】若成功则返回string的指针,否则返回NULL。

    注意:由于gets()不检查字符串string的大小,必须遇到换行符或文件结尾才会结束输入,因此容易造成缓存溢出的安全性问题,导致程序崩溃,可以使用fgets()代替。

    12.字符串转整数
    1 用atoi函数。
    atoi的功能就是将字符串转为整型并返回。其声明为
    int atoi(char *str);
    比如atoi("1234");会返回整型1234。

    2 用sscanf
    sscanf与标准格式化输入函数scanf类似,不过源并非是标准输入,而是字符串。
    用sscanf可以处理更复杂的字符串。
    比如字符串char * str = "a=1, b=2";
    定义int a,b;后可以用
    sscanf(str,"a=%d, b=%d",&a,&b);
    来将a,b值提取,计算后,a=1, b=2。

     

    展开全文
  • 基于Python的数据分析

    万次阅读 多人点赞 2019-02-25 15:50:02
    Python进行数据分析的学习方法及步骤; 随着大数据和人工智能时代的到来,网络和信息技术开始渗透到人类日常生活的方方面面,产生的数据量也呈现指数级增长的态势,同时现有数据的量级已经远远超过了目前人力所能...

    下面来介绍一下基于Python的数据分析,主要介绍数据分析的概念、数据分析流程、Python优势、常用模块的用途以及使用
    Python进行数据分析的学习方法及步骤;

    随着大数据和人工智能时代的到来,网络和信息技术开始渗透到人类日常生活的方方面面,产生的数据量也呈现指数级增长的态势,同时现有数据的量级已经远远超过了目前人力所能处理的范畴。在此背景下,数据分析成为数据科学领域中一个全新的研究
    课题。在数据分析的程序语言选择上,由于Python语言在数据分析和处理方面的优势,大量的数据科学领域的从业者使用Python
    来进行数据科学相关的研究工作。

    1、数据分析的概念

    数据分析是指用适当的分析方法对收集来的大量数据进行分析,提取有用信息和形成结论,对数据加以详细研究和概括总结的过程。随着信息技术的高速发展,企业生产、收集、存储和处理数据的能力大大提高,同时数据量也与日俱增。把这些繁杂的数据通过数据分析方法进行提炼,以此研究出数据的发展规律和预测趋势走向,进而帮助企业管理层做出决策。

    2、数据分析的流程

    数据分析是一种解决问题的过程和方法,主要的步骤有需求分析、数据获取、数据预处理、分析建模、模型评价与优化、部署:

    1)需求分析

    数据分析中的需求分析是数据分析环节中的第一步,也是非常重要的一步,决定了后续的分析方法和方向。主要内容是根据业务、生产和财务等部门的需要,结合现有的数据情况,提出数据分析需求的整体分析方向、分析内容,最终和需求方达成一致。

    2)数据获取

    数据获取是数据分析工作的基础,是指根据需求分析的结果提取、收集数据。数据获取主要有两种方式:网络爬虫获取和本地获取。网络爬虫获取指的是通过Python编写爬虫程序合法获取互联网中的各种文字、语音、图片和视频等信息;本地获取指的是通过计算机工具获取存储在本地数据库中的生产、营销和财务等系统的历史数据和实时数据。

    3)数据预处理

    数据预处理是指对数据进行数据合并、数据清洗、数据标准化和数据变换,并直接用于分析建模的这一过程的总称。其中,数据合并可以将多张互相关联的表格合并为一张;数据清洗可以去掉重复、缺失、异常、不一致的数据;数据标准化可以去除特征间的量纲差异;数据交换则可以通过离散化、哑变量处理等技术满足后期分析与建模的数据要求。在数据分析过程中,数据预处理的各个过程互相交叉,并没有固定的先后顺序。

    4)分析建模

    分析建模是指通过对比分析、分组分析、交叉分析、回归分析等分析方法,以及聚类模型、分类模型、关联规则、智能推荐等模型和算法,发现数据中的有价值信息,并得出结论的过程。

    5)模型评价与优化

    模型评价是指对于已经建立的一个或多个模型,根据其模型的类别,使用不同的指标评价其性能优劣的过程。模型的优化则是指模型性能在经过模型评价后已经达到了要求,但在实际生产环境应用过程中,发现模型的性能并不理想,继而对模型进行重构与优化的过程。

    6)部署

    部署是指将数据分析结果与结论应用至实际生产系统的过程。根据需求的不同,部署阶段可以是一份包含了现状具体整改措施的数据分析报告,也可以是将模型部署在整个生产系统的解决方案。在多数项目中,数据分析员提供的是一份数据分析报告或者一套解决方案,实际执行与部署的是需求方。

    3、Python是功能强大的数据分析工具

    Python具有丰富和强大的库,它常被称为胶水语言,能够把用其他语言制作的各种模块很轻松地连接在一起,是一门更易学、更严谨的程序设计语言,常用于数据分析、机器学习、矩阵运算、科学数据可视化、数字图像处理、网络爬虫、Web应用等;R语言常用于统计分析、机器学习、科学数据可视化等;MATLAB则用于矩阵运算、数值分析、科学数据可视化、机器学习、符号运算、数字图像处理及信号处理等。可以看出,以上三种语言均可进行数据分析。

    4、Python进行数据分析的优势

    Python是一门应用非常广泛的计算机语言,在数据科学领域具有无可比拟的优势。Python正在逐渐成为数据科学领域的主流语言。Python数据分析具有以下几方面优势:

    1》语法简单精炼。对于初学者来说,比起其他编程语言,Python更容易上手;

    2》有许多功能强大的库。结合在编程方面的强大实力,可以只使用Python这一种语言就可以去构建以数据为中心的应用程序;

    3》不仅适用于研究和原型构建,同时也适用于构建生产系统。研究人员和工程技术人员使用同一种编程工具,能给企业带来显著的组织效益,并降低企业的运营成本;

    4》Python程序能够以多种方式轻易地与其他语言的组件“粘接”在一起。例如,Python的C语言API可以帮助Python程序灵活地调用C程序,这意味着用户可以根据需要给Python程序添加功能,或者在其他环境系统中使用Python;

    5》Python是一个混合体,丰富的工具集使它介于系统的脚本语言和系统语言之间。Python不仅具备所有脚本语言简单和易用的特点,还提供了编译语言所具有的高级软件工程工具。

    5、Python数据分析常用类库介绍

    Python拥有IPython、Num Py、Sci Py、pandas、Matplot⁃lib、scikit-learn和Spyder等功能齐全、接口统一的库,能为数据分析工作提供极大的便利。其中,Num Py主要有以下特点:

    1)具有快速高效的多维数组对象ndarray;
    2)具有对数组执行元素级计算及直接对数组执行数学运算的函数;
    3)具有线性代数运算、傅里叶变换及随机数生成的功能;
    4)能将C、C++、Fortran代码集成到Python;
    5)可作为算法之间传递数据的容器。

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • C语言assert函数完全攻略

    千次阅读 多人点赞 2018-10-14 00:30:17
    断言assert函数C语言assert函数完全攻略 对于断言,相信大家都不陌生,大多数编程语言也都有断言这一特性。简单地讲,断言就是对某种假设条件进行检查。在 C 语言中,断言被定义为宏的形式(assert(expression))...

    断言assert函数,C语言assert函数完全攻略

    对于断言,相信大家都不陌生,大多数编程语言也都有断言这一特性。简单地讲,断言就是对某种假设条件进行检查。在 C 语言中,断言被定义为宏的形式(assert(expression)),而不是函数,其原型定义在<assert.h>文件中。其中,assert 将通过检查表达式 expression 的值来决定是否需要终止执行程序。也就是说,如果表达式 expression 的值为假(即为 0),那么它将首先向标准错误流 stderr 打印一条出错信息,然后再通过调用 abort 函数终止程序运行;否则,assert 无任何作用。

    默认情况下,assert 宏只有在 Debug 版本(内部调试版本)中才能够起作用,而在 Release 版本(发行版本)中将被忽略。当然,也可以通过定义宏或设置编译器参数等形式来在任何时候启用或者禁用断言检查(不建议这么做)。同样,在程序投入运行后,最终用户在遇到问题时也可以重新起用断言。这样可以快速发现并定位软件问题,同时对系统错误进行自动报警。对于在系统中隐藏很深,用其他手段极难发现的问题也可以通过断言进行定位,从而缩短软件问题定位时间,提高系统的可测性。

    尽量利用断言来提高代码的可测试性

    在讨论如何使用断言之前,先来看下面一段示例代码:

     
    1. void *Memcpy(void *dest, const void *src, size_t len)
    2. {
    3. char *tmp_dest = (char *)dest;
    4. char *tmp_src = (char *)src;
    5. while(len --)
    6. *tmp_dest ++ = *tmp_src ++;
    7. return dest;
    8. }

    对于上面的 Memcpy 函数,毋庸置疑,它能够通过编译程序的检查成功编译。从表面上看,该函数并不存在其他任何问题,并且代码也非常干净。

    但遗憾的是,在调用该函数时,如果不小心为 dest 与 src 参数错误地传入了 NULL 指针,那么问题就严重了。轻者在交付之前这个潜在的错误导致程序瘫痪,从而暴露出来。否则,如果将该程序打包发布出去,那么所造成的后果是无法估计的。

    由此可见,不能够简单地认为“只要通过编译程序成功编译的就都是安全的程序”。当然,编译程序也很难检查出类似的潜在错误(如所传递的参数是否有效、潜在的算法错误等)。面对这类问题,一般首先想到的应该是使用最简单的if语句进行判断检查,如下面的示例代码所示:

     
    1. void *Memcpy(void *dest, const void *src, size_t len)
    2. {
    3. if(dest == NULL)
    4. {
    5. fprintf(stderr,"dest is NULL\n");
    6. abort();
    7. }
    8. if(src == NULL)
    9. {
    10. fprintf(stderr,"src is NULL\n");
    11. abort();
    12. }
    13. char *tmp_dest = (char *)dest;
    14. char *tmp_src = (char *)src;
    15. while(len --)
    16. *tmp_dest ++ = *tmp_src ++;
    17. return dest;
    18. }

    现在,通过“if(dest==NULL)与if(data-src==NULL)”判断语句,只要在调用该函数的时候为 dest 与 src 参数错误地传入了NULL指针,这个函数就会检查出来并做出相应的处理,即先向标准错误流 stderr 打印一条出错信息,然后再调用 abort 函数终止程序运行。

    从表面看来,上面的解决方案应该堪称完美。但是,随着函数参数或需要检查的表达式不断增多,这种检查测试代码将占据整个函数的大部分(这一点从上面的 Memcpy 函数中就不难看出)。这样代码看起来非常不简洁,甚至可以说很“糟糕”,而且也降低了函数的执行效率。

    面对上面的问题,或许可以利用 C 的预处理程序有条件地包含或不包含相应的检查部分进行解决,如下面的代码所示:

     
    1. void *MemCopy(void *dest, const void *src, size_t len)
    2. {
    3. #ifdef DEBUG
    4. if(dest == NULL)
    5. {
    6. fprintf(stderr,"dest is NULL\n");
    7. abort();
    8. }
    9. if(src == NULL)
    10. {
    11. fprintf(stderr,"src is NULL\n");
    12. abort();
    13. }
    14. #endif
    15. char *tmp_dest = (char *)dest;
    16. char *tmp_src = (char *)src;
    17. while(len --)
    18. *tmp_dest ++ = *tmp_src ++;
    19. return dest;
    20. }

    这样,通过条件编译“#ifdef DEBUG”来同时维护同一程序的两个版本(内部调试版本与发行版本),即在程序编写过程中,编译其内部调试版本,利用其提供的测试检查代码为程序自动查错。而在程序编完之后,再编译成发行版本。

    上面的解决方案尽管通过条件编译“#ifdef DEBUG”能产生很好的结果,也完全符合我们的程序设计要求,但是仔细观察会发现,这样的测试检查代码显得并不那么友好,当一个函数里这种条件编译语句很多时,代码会显得有些浮肿,甚至有些糟糕。

    因此,对于上面的这种情况,多数程序员都会选择将所有的调试代码隐藏在断言 assert 宏中。其实,assert 宏也只不过是使用条件编译“#ifdef”对部分代码进行替换,利用 assert 宏,将会使代码变得更加简洁,如下面的示例代码所示:

     
    1. void *MemCopy(void *dest, const void *src, size_t len)
    2. {
    3. assert(dest != NULL && src !=NULL);
    4. char *tmp_dest = (char *)dest;
    5. char *tmp_src = (char *)src;
    6. while(len --)
    7. *tmp_dest ++ = *tmp_src ++;
    8. return dest;
    9. }

    现在,通过“assert(dest !=NULL&&src !=NULL)”语句既完成程序的测试检查功能(即只要在调用该函数的时候为 dest 与 src 参数错误传入 NULL 指针时都会引发 assert),与此同时,对 MemCopy 函数的代码量也进行了大幅度瘦身,不得不说这是一个两全其美的好办法。

    实际上,在编程中我们经常会出于某种目的(如把 assert 宏定义成当发生错误时不是中止调用程序的执行,而是在发生错误的位置转入调试程序,又或者是允许用户选择让程序继续运行等)需要对 assert 宏进行重新定义。

    但值得注意的是,不管断言宏最终是用什么样的方式进行定义,其所定义宏的主要目的都是要使用它来对传递给相应函数的参数进行确认检查。如果违背了这条宏定义原则,那么所定义的宏将会偏离方向,失去宏定义本身的意义。与此同时,为不影响标准 assert 宏的使用,最好使用其他的名字。例如,下面的示例代码就展示了用户如何重定义自己的宏 ASSERT:

     
    1. /*使用断言测试*/
    2. #ifdef DEBUG
    3. /*处理函数原型*/
    4. void Assert(char * filename, unsigned int lineno);
    5. #define ASSERT(condition)\
    6. if(condition)\
    7. NULL; \
    8. else\
    9. Assert(__FILE__ , __LINE__)
    10. /*不使用断言测试*/
    11. #else
    12. #define ASSERT(condition) NULL
    13. #endif
    14. void Assert(char * filename, unsigned int lineno)
    15. {
    16. fflush(stdout);
    17. fprintf(stderr,"\nAssert failed: %s, line %u\n",filename, lineno);
    18. fflush(stderr);
    19. abort();
    20. }

    如果定义了 DEBUG,ASSERT 将被扩展为一个if语句,否则执行“#define ASSERT(condition) NULL”替换成 NULL。

    这里需要注意的是,因为在编写 C 语言代码时,在每个语句后面加一个分号“;”已经成为一种约定俗成的习惯,因此很有可能会在“Assert(__FILE__,__LINE__)”调用语句之后习惯性地加上一个分号。实际上并不需要这个分号,因为用户在调用 ASSERT 宏时,已经给出了一个分号。面对这种问题,我们可以使用“do{}while(0)”结构进行处理,如下面的代码所示:

     
    1. #define ASSERT(condition)\
    2. do{ \
    3. if(condition)\
    4. NULL; \
    5. else\
    6. Assert(__FILE__ , __LINE__);\
    7. }while(0)
    8.  
    9. 现在,将不再为分号“;”而担心了,调用示例如下:
    10. void Test(unsigned char *str)
    11. {
    12. ASSERT(str != NULL);
    13. /*函数处理代码*/
    14. }
    15. int main(void)
    16. {
    17. Test(NULL);
    18. return 0;
    19. }

    很显然,因为调用语句“Test(NULL)”为参数 str 错误传入一个 NULL 指针的原因,所以 ASSERT 宏会自动检测到这个错误,同时根据宏 __FILE__ 和 __LINE__ 所提供的文件名和行号参数在标准错误输出设备 stderr 上打印一条错误消息,然后调用 abort 函数中止程序的执行。运行结果如图 1 所示。



    图 1 调用自定义 ASSERT 宏的运行结果


    如果这时候将自定义 ASSERT 宏替换成标准 assert 宏结果会是怎样的呢?如下面的示例代码所示:

     
    1. void Test(unsigned char *str)
    2. {
    3. assert(str != NULL);
    4. /*函数处理代码*/
    5. }

    毋庸置疑,标准 assert 宏同样会自动检测到这个 NULL 指针错误。与此同时,标准 assert 宏除给出以上信息之外,还能够显示出已经失败的测试条件。运行结果如图 2 所示。



    图 2 调用标准 assert 宏的运行结果


    从上面的示例中不难发现,对标准的 assert 宏来说,自定义的 ASSERT 宏将具有更大的灵活性,可以根据自己的需要打印输出不同的信息,同时也可以对不同类型的错误或者警告信息使用不同的断言,这也是在工程代码中经常使用的做法。当然,如果没有什么特殊需求,还是建议使用标准 assert 宏。

    尽量在函数中使用断言来检查参数的合法性

    在函数中使用断言来检查参数的合法性是断言最主要的应用场景之一,它主要体现在如下 3 个方面:

    1. 在代码执行之前或者在函数的入口处,使用断言来检查参数的合法性,这称为前置条件断言。
    2. 在代码执行之后或者在函数的出口处,使用断言来检查参数是否被正确地执行,这称为后置条件断言。
    3. 在代码执行前后或者在函数的入出口处,使用断言来检查参数是否发生了变化,这称为前后不变断言。


    例如,在上面的 Memcpy 函数中,除了可以通过“assert(dest !=NULL&&src!=NULL);”语句在函数的入口处检查 dest 与 src 参数是否传入 NULL 指针之外,还可以通过“assert(tmp_dest>=tmp_src+len||tmp_src>=tmp_dest+len);”语句检查两个内存块是否发生重叠。如下面的示例代码所示:

     
    1. void *Memcpy(void *dest, const void *src, size_t len)
    2. {
    3. assert(dest!=NULL && src!=NULL);
    4. char *tmp_dest = (char *)dest;
    5. char *tmp_src = (char *)src;
    6. /*检查内存块是否重叠*/
    7. assert(tmp_dest>=tmp_src+len||tmp_src>=tmp_dest+len);
    8. while(len --)
    9. *tmp_dest ++ = *tmp_src ++;
    10. return dest;
    11. }

    除此之外,建议每一个 assert 宏只检验一个条件,这样做的好处就是当断言失败时,便于程序排错。试想一下,如果在一个断言中同时检验多个条件,当断言失败时,我们将很难直观地判断哪个条件失败。因此,下面的断言代码应该更好一些,尽管这样显得有些多此一举:

     
    1. assert(dest!=NULL);
    2. assert(src!=NULL);

    最后,建议 assert 宏后面的语句应该空一行,以形成逻辑和视觉上的一致感,让代码有一种视觉上的美感。同时为复杂的断言添加必要的注释,可澄清断言含义并减少不必要的误用。

    避免在断言表达式中使用改变环境的语句

    默认情况下,因为 assert 宏只有在 Debug 版本中才能起作用,而在 Release 版本中将被忽略。因此,在程序设计中应该避免在断言表达式中使用改变环境的语句。如下面的示例代码所示:

     
    1. int Test(int i)
    2. {
    3. assert(i++);
    4. return i;
    5. }
    6. int main(void)
    7. {
    8. int i=1;
    9. printf("%d\n",Test(i));
    10. return 0;
    11. }

    对于上面的示例代码,由于“assert(i++)”语句的原因,将导致不同的编译版本产生不同的结果。如果是在 Debug 版本中,因为这里向变量 i 所赋的初始值为 1,所以在执行“assert(i++)”语句的时候将通过条件检查,进而继续执行“i++”,最后输出的结果值为 2;如果是在 Release 版本中,函数中的断言语句“assert(i++)”将被忽略掉,这样表达式“i++”将得不到执行,从而导致输出的结果值还是 1。

    因此,应该避免在断言表达式中使用类似“i++”这样改变环境的语句,使用如下代码进行替换:

     
    1. int Test(int i)
    2. {
    3. assert(i);
    4. i++;
    5. return i;
    6. }

    现在,无论是 Debug 版本,还是 Release 版本的输出结果都将为 2。

    避免使用断言去检查程序错误

    在对断言的使用中,一定要遵循这样一条规定:对来自系统内部的可靠的数据使用断言,对于外部不可靠数据不能够使用断言,而应该使用错误处理代码。换句话说,断言是用来处理不应该发生的非法情况,而对于可能会发生且必须处理的情况应该使用错误处理代码,而不是断言。

    在通常情况下,系统外部的数据(如不合法的用户输入)都是不可靠的,需要做严格的检查(如某模块在收到其他模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现)才能放行到系统内部,这相当于一个守卫。而对于系统内部的交互(如子程序调用),如果每次都去处理输入的数据,也就相当于系统没有可信的边界,这样会让代码变得臃肿复杂。事实上,在系统内部,传递给子程序预期的恰当数据应该是调用者的责任,系统内的调用者应该确保传递给子程序的数据是恰当且可以正常工作的。这样一来,就隔离了不可靠的外部环境和可靠的系统内部环境,降低复杂度。

    但是在代码编写与测试阶段,代码很可能包含一些意想不到的缺陷,也许是处理外部数据的程序考虑得不够周全,也许是调用系统内部子程序的代码存在错误,造成子程序调用失败。这个时候,断言就可以发挥作用,用来确诊到底是哪部分出现了问题而导致子程序调用失败。在清理所有缺陷之后,就建立了内外有别的信用体系。等到发行版的时候,这些断言就没有存在的必要了。因此,不能用断言来检查最终产品肯定会出现且必须处理的错误情况。

    看下面一段示例代码:

     
    1. char * Strdup(const char * source)
    2. {
    3. assert(source != NULL);
    4. char * result=NULL;
    5. size_t len = strlen(source) +1;
    6. result = (char *)malloc(len);
    7. assert(result != NULL);
    8. strcpy(result, source);
    9. return result;
    10. }

    对于上面的 Strdup 函数,相信大家都不陌生。其中,第一个断言语句“assert(source!=NULL)”用来检查该程序正常工作时绝对不应该发生的非法情况。换句话说,在调用代码正确的情况下传递给 source 参数的值必然不为 NULL,如果断言失败,说明调用代码中有错误,必须修改。因此,它属于断言的正常使用情况。

    而第二个断言语句“assert(result!=NULL)”的用法则不同,它测试的是错误情况,是在其最终产品中肯定会出现且必须对其进行处理的错误情况。即对 malloc 函数而言,当内存不足导致内存分配失败时就会返回 NULL,因此这里不应该使用 assert 宏进行处理,而应该使用错误处理代码。如下面问题将使用 if 判断语句进行处理:

     
    1. char * Strdup(const char * source)
    2. {
    3. assert(source != NULL);
    4. char * result=NULL;
    5. size_t len = strlen(source)+1;
    6. result = (char *)malloc(len);
    7. if (result != NULL)
    8. {
    9. strcpy(result, source);
    10. }
    11. return result;
    12. }

    总之记住一句话:断言是用来检查非法情况的,而不是测试和处理错误的。因此,不要混淆非法情况与错误情况之间的区别,后者是必然存在且一定要处理的。

    尽量在防错性程序设计中使用断言来进行错误报警

    对于防错性程序设计,相信有经验的程序员并不陌生,大多数教科书也都鼓励程序员进行防错性程序设计。在程序设计过程中,总会或多或少产生一些错误,这些错误有些属于设计阶段隐藏下来的,有些则是在编码中产生的。为了避免和纠正这些错误,可在编码过程中有意识地在程序中加进一些错误检查的措施,这就是防错性程序设计的基本思想。其中,它又可以分为主动式防错程序设计和被动式防错程序设计两种。

    主动式防错程序设计是指周期性地对整个程序或数据库进行搜查或在空闲时搜查异常情况。它既可以在处理输入信息期间使用,也可以在系统空闲时间或等待下一个输入时使用。如下面所列出的检查均适合主动式防错程序设计。

    • 内存检查:如果在内存的某些块中存放了一些具有某种类型和范围的数据,则可对它们做经常性检查。
    • 标志检查:如果系统的状态是由某些标志指示的,可对这些标志做单独检查。
    • 反向检查:对于一些从一种代码翻译成另一种代码或从一种系统翻译成另一种系统的数据或变量值,可以采用反向检查,即利用反向翻译来检查原始值的翻译是否正确。
    • 状态检查:对于某些具有多个操作状态的复杂系统,若用某些特定的存储值来表示这些状态,则可通过单独检查存储值来验证系统的操作状态。
    • 连接检查:当使用链表结构时,可检查链表的连接情况。
    • 时间检查:如果已知道完成某项计算所需的最长时间,则可用定时器来监视这个时间。
    • 其他检查:程序设计人员可经常仔细地对所使用的数据结构、操作序列和定时以及程序的功能加以考虑,从中得到要进行哪些检查的启发。


    被动式防错程序设计则是指必须等到某个输入之后才能进行检查,也就是达到检查点时才能对程序的某些部分进行检查。一般所要进行的检查项目如下:

    • 来自外部设备的输入数据,包括范围、属性是否正确。
    • 由其他程序所提供的数据是否正确。
    • 数据库中的数据,包括数组、文件、结构、记录是否正确。
    • 操作员的输入,包括输入的性质、顺序是否正确。
    • 栈的深度是否正确。
    • 数组界限是否正确。
    • 表达式中是否出现零分母情况。
    • 正在运行的程序版本是否是所期望的(包括最后系统重新组合的日期)。
    • 通过其他程序或外部设备的输出数据是否正确。


    虽然防错性程序设计被誉为有较好的编码风格,一直被业界强烈推荐。但防错性程序设计也是一把双刃剑,从调试错误的角度来看,它把原来简单的、显而易见的缺陷转变成晦涩的、难以检测的缺陷,而且诊断起来非常困难。从某种意义上讲,防错性程序设计隐瞒了程序的潜在错误。

    当然,对于软件产品,希望它越健壮越好。但是调试脆弱的程序更容易帮助我们发现其问题,因为当缺陷出现的时候它就会立即表现出来。因此,在进行防错性程序设计时,如果“不可能发生”的事情的确发生了,则需要使用断言进行报警,这样,才便于程序员在内部调试阶段及时对程序问题进行处理,从而保证发布的软件产品具有良好的健壮性。

    一个很常见的例子就是无处不在的 for 循环,如下面的示例代码所示:

     
    1. for(i=0;i<count;i++)
    2. {
    3. /*处理代码*/
    4. }

    在几乎所有的 for 循环示例中,其行为都是迭代从 0 开始到“count-1”,因此,大家也都自然而然地编写成了上面这种防错性版本。但存在的问题是:如果 for 循环中的索引 i 值确实大于 count,那么极有可能意味着代码中存在着潜在的缺陷问题。

    由于上面的 for 循环示例采用了防错性程序设计方式,因此,就算是在内部测试阶段中出现了这种缺陷也很难发现其问题的所在,更加不可能出现系统报警提示。同时,因为这个潜在的程序缺陷,极有可能会在以后让我们吃尽苦头,而且非常难以诊断。

    那么,不采用防错性程序设计会是什么样子呢?如下面的示例代码所示:

     
    1. for(i=0;i!=count;i++)
    2. {
    3. /*处理代码*/
    4. }

    很显然,这种写法肯定是不行的,当 for 循环中的索引 i 值确实大于 count 时,它还是不会停止循环。

    对于上面的问题,断言为我们提供了一个非常简单的解决方法,如下面的示例代码所示:

     
    1. for(i=0;i<count;i++)
    2. {
    3. /*处理代码*/
    4. }
    5. assert(i==count);

    不难发现,通过断言真正实现了一举两得的目的:健壮的产品软件和脆弱的开发调试程序,即在该程序的交付版本中,相应的程序防错代码可以保证当程序的缺陷问题出现的时候,用户可以不受损失;而在该程序的内部调试版本中,潜在的错误仍然可以通过断言预警报告。

    因此,“无论你在哪里编写防错性代码,都应该尽量确保使用断言来保护这段代码”。当然,也不必过分拘泥于此。例如,如果每次执行 for 循环时索引 i 的值只是简单地增 1,那么要使索引i的值超过 count 从而引起问题几乎是不可能的。在这种情况下,相应的断言也就没有任何存在的意义,应该从程序中删除。但是,如果索引 i 的值有其他处理情况,则必须使用断言进行预警。由此可见,在防错性程序设计中是否需要使用断言进行错误报警要视具体情况而定,在编码之前都要问自己:“在进行防错性程序设计时,程序中隐瞒错误了吗?”如果答案是肯定的,就必须在程序中加上相应的断言,以此来对这些错误进行报警。否则,就不要多此一举了。

    用断言保证没有定义的特性或功能不被使用

    在日常软件设计中,如果原先规定的一部分功能尚未实现,则应该使用断言来保证这些没有被定义的特性或功能不被使用。例如,某通信模块在设计时,准备提供“无连接”和“连接”这两种业务。但当前的版本中仅实现了“无连接”业务,且在此版本的正式发行版中,用户(上层模块)不应产生“连接”业务的请求,那么在测试时可用断言来检查用户是否使用了“连接”业务。如下面的示例代码所示:

     
    1. /*无连接业务*/
    2. #define CONNECTIONLESS 0
    3. /*连接业务*/
    4. #define CONNECTION 1
    5. int MessageProcess(MESSAGE *msg)
    6. {
    7. assert(msg != NULL);
    8. unsigned char service;
    9. service = GetMessageService(msg);
    10. /*使用断言来检查用户是否使用了“连接”业务*/
    11. assert(service != CONNECTION);
    12. /*处理代码*/
    13. }

    谨慎使用断言对程序开发环境中的假设进行检查

    在程序设计中,不能够使用断言来检查程序运行时所需的软硬件环境及配置要求,它们需要由专门的处理代码进行检查处理。而断言仅可对程序开发环境(OS/Compiler/Hardware)中的假设及所配置的某版本软硬件是否具有某种功能的假设进行检查。例如,某网卡是否在系统运行环境中配置了,应由程序中正式代码来检查;而此网卡是否具有某设想的功能,则可以由断言来检查。

    除此之外,对编译器提供的功能及特性的假设也可以使用断言进行检查,如下面的示例代码所示:

     
    1. /*int类型占用的内存空间是否为2*/
    2. assert(sizeof(int)== 2);
    3. /*long类型占用的内存空间是否为4*/
    4. assert(sizeof(long)==4);
    5. /*byte的宽度是否为8*/
    6. assert(CHAR_BIT==8);

    之所以可以这样使用断言,那是因为软件最终发行的 Release 版本与编译器已没有任何直接关系。

    最后,必须保证软件的 Debug 与 Release 两个版本在实现功能上的一致性,同时可以使用调测开关来切换这两个不同的版本,以便统一维护,切记不要同时存在 Debug 版本与 Release 版本两个不同的源文件。

    当然,因为频繁调用 assert 会极大影响程序的性能,增加额外的开销。因此,应该在正式软件产品(即 Release 版本)中将断言及其他调测代码关掉(尤其是针对自定义的断言宏)。

    展开全文
  • 数据结构(C++)有关练习题

    热门讨论 2008-01-02 11:27:18
    在计算机科学发展过程中,早期数据结构教材大都采用PASCAL语言为描述工具,后来出现了采用C语言为描述工具的教材版本、至今又出现了采用C++语言为描述工具的多种教材版本。本教实验指导书是为已经学习过C++语言的...
  • Linux C/C++ 学习路线

    万次阅读 多人点赞 2019-07-04 20:41:56
    对于 C++ 中 boost 八大智能指针的掌握理解,其核心是理解并且剖析过相应的源码, 这三个是最核心的智能指针,理解清楚智能指针的本质是,内存的申请释放全部交给了对象管理,以避免人为疏忽,造成内 存...
  • 3.5.4 字符数据在内存中的存储形式及使用方法 41 3.5.5 字符串常量 41 3.5.6 符号常量 42 3.6 变量赋初值 42 3.7 各类数值型数据之间的混合运算 43 3.8 算术运算符和算术表达式 44 3.8.1 C运算符简介 44 3.8.2 算术...
  • 测试开发笔记

    万次阅读 多人点赞 2019-11-14 17:11:58
    49 第六章 C语言 49 C语言中的存储 50 数据类型 50 常量 53 结构体 54 条件/分支逻辑 54 Switch 54 If 55 循环 55 For 55 while 56 do…while 56 函数 56 第七章 Windows环境搭建 59 一、名词注解定义: 59 C/S ...
  • 最近要考计算机二级c语言,是因为自我感觉 自己的编程能力实在是太差了,课堂上有没有好好的学,所以简单抽时间看看二级的题库,做一些经常出错的知识点的总结!有错的希望大家能够提出来,谢谢大家了! 但是知识点...
  • 后续文章是关于数据结构一些基础实验,本人都已经成功运行,如果有问题,欢迎在评论区留言。 您的支持是罡罡同学前进的最大动力! **我是罡罡同学,一位初入网安的小白。 ☜(ˆ▽ˆ) ** (疯狂暗示 点赞 !关注!...
  • C#基础教程-c#实例教程,适合初学者

    万次阅读 多人点赞 2016-08-22 11:13:24
    本章介绍C#语言的基础知识,希望具有C语言的读者能够基本掌握C#语言,并以此为基础,能够进一步学习用C#语言编写window应用程序和Web应用程序。当然仅靠一章的内容就完全掌握C#语言是不可能的,如需进一步学习C#语言...
  • 软件测试面试题汇总

    万次阅读 多人点赞 2018-09-27 12:31:09
    4、正交表测试用例设计方法的特点是什么? ............................................................................................... 5 5 、描述使用bugzilla 缺陷管理工具对软件缺陷(BUG )跟踪的...
  • 一、五大内存分区: ...里面的变量通常是局部变量、函数参数等。 2、堆区(heap):就是那些由new分配的内存块,它们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没...
  • MATLAB函数速查手册

    千次阅读 多人点赞 2018-03-25 09:06:26
    《MATLAB函数速查手册》较全面地介绍了MATLAB的函数,主要包括MATLAB操作基础、矩阵及其基本运算、数值计算相关的基本函数、符号运算的函数、概率统计函数、绘图图形处理函数、MATLAB程序设计相关函数、Simulink...
  • 华为C语言编程规范(精华总结)

    万次阅读 多人点赞 2020-03-24 09:48:55
    目录 1、代码总体原则 2、头文件 2、函数 3、标识符命名定义 4、变量 5、宏、常量 6、表达式 7、注释 8、排版格式 9、代码编辑编译 “编写程序应该以人为本,计算机第二。” ——Steve McConnell “无缘进华为...
  • 整理了一点计算机组成及基本原理和C语言程序设计的一些基础知识
  • [c,c++,c#,java?这些有什么区别?转] c,c++,c#,java?这些有什么区别? C语言: 目前最著名、最有影响...既然如此庞大复杂的0S都可以用c语言编写,从狭义而言,还有什么系统软件和应用软件不能用c语言编写呢?...
  • 3.5.4 字符数据在内存中的存储形式及使用方法 41 3.5.5 字符串常量 41 3.5.6 符号常量 42 3.6 变量赋初值 42 3.7 各类数值型数据之间的混合运算 43 3.8 算术运算符和算术表达式 44 3.8.1 C运算符简介 44 3.8.2 算术...
  • c语言题库1

    千次阅读 2016-10-09 11:06:34
    在某工程中,要求设置一绝对地址为 0x67a9 的整型变量的值为 0xaa66 。编译器是一个纯粹的 ANSI 编译器。写代码去完成这一任务。 这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换( type...
  • 本书全面介绍了数据结构的基础内容,帮助学生深入了解软件工程的思想和技术。学生还可以通过对一些高级编程概念(如接口、抽象和封装)的了解,为进一步深入学习高级编程知识打下坚实的基础。本书观点清晰明了、语言...
  • 四、VS2015下的FFTW使用 (首先是x86版本) (然后是x64版本) 五、Qt版本下的移植 官网网址:http://www.fftw.org/ 这个已经能正常使用,不会继续研究了,这个FFTW只给预编译版本,没法学习源代码。且商用...
  • C语言经典面试题 C语言面试宝典

    万次阅读 多人点赞 2017-12-22 16:15:26
    毕竟是一种面向对象的语言,为了支持函数的重载,对函数的编译方式 C 的不同。例如,在 C++ 中,对函数 void fun(int,int) 编译后的名称可能是 _fun_int_int ,而 C 中没有重载机制,一般直接利用函数名来指定...
  • 本书的复习题部分通过详细的分析和解答,不仅给出了题目的答案,还强调如何利用C语言的基本原理和基本方法分析、解决问题的过程。在编程练习中,首先按照程序开发的基本流程,通过分析题目要求的基本功能,设计相关...
  • - 通用链表 4.1、链表的存储结构 4.2、操作函数定义 4.3、链表的元素插入 4.4、链表的元素删除 4.5、链表的元素获取 4.6、整体工程代码结构 4.7、调用测试用例 4.7.1、业务节点定义 4.7.2、声明外部底层库函数对象 ...
  • 关于大学计算机相关专业学习路线的见解分析

    万次阅读 多人点赞 2018-03-18 12:25:27
    根据百度百科计算机科学技术专业(以下简称计算机专业)给出的描述,该专业的主干课程有算法、数据结构、操作系统、编译原理、计算机组成原理、计算机体系结构、计算机网络(划重点,这些都是专业基础课,其中的...
  • 15. Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:  #define dPS struct s *  typedef struct s * tPS;  答案是:typedef更好。思考...
  • 编程之法-C语言应用开发与工程实践-C语言概述
  • C语言和内存初步框架了解

    千次阅读 多人点赞 2016-10-09 15:05:12
    ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼...1、C语言对内存地址的封装(用变量名来访问内存、数据类型的含义、函数名的含义) 譬如在C语言中 int a; a = 5; a += 4;//a == 9; 结合内存来解析C语言语句的本质: int a; /...
  • c语言多文件编程,即main文件调用其他.c文件的方法 原创 天泉证道 最后发布于2018-11-14 14:46:32 阅读数 4047 收藏 展开 c语言多文件编程,即main文件调用其他.c文件的方法。 两种方法的区别就是,gcc编译时,是否...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 22,599
精华内容 9,039
关键字:

数据计算方法与c语言工程函数库

c语言 订阅