精华内容
下载资源
问答
  • C语言基础专题 - 头文件引用 本文介绍了C语言中头文件的引用方法 阅读本文前推荐阅读C语言预处理 - 由于博主水平有限,疏忽在所难免。若发现错误请告知博主更正:李俊才 ...如何引用头文件? 这个事情我们

    C语言基础专题 - 头文件引用


    本文介绍了C语言中头文件的引用方法
    阅读本文前推荐阅读C语言预处理

    1.🧐什么是头文件?

    头文件是扩展名为 .h 的文件,这是一个文本文件,内容包含了:

    2.🧐如何引用头文件?

    这个事情我们所有人写的第一个程序Hello World.c中都做过:

    #include <stdio.h>     // 引用头文件
    int main(){
       printf("Hello World")
    }
    

    这里被引用的头文件stdio.h它是编译器自带的,属于系统头文件。
    我们不但可以引用系统头文件,也可以引用自己写的头文件,即用户头文件,但这再语法上由略微差别:

    #include <file>    //  用于引用名为file的系统头文件,默认在系统目录的标准列表中搜索该文件
    #include "file"     //  用于引用名为file的用户头文件 ,默认在包含当前文件的目录中搜索该文件
    

    C语言的编译器有很多,对于以上两种使用头文件的方法一般都提供了相关选项以加入搜索头文件的路径,也常有在名为INCLUDE环境变量中加入搜索路径,以告诉编译器头文件的位置。

    比如,在GCC中(以下引用内容摘录自GCC官方文档3.16目录搜索的选项章节,有外语基础的读者可以自行阅读。)

    这些选项指定目录以搜索头文件,库和编译器的一部分:

    -I dir
    -iquote dir
    -isystem dir
    -idirafter dir
    

    它们在预处理(可以参考C语言预处理部分内容)的时候将目录dir添加到要在预处理过程中搜索头文件的目录列表中。如果dir以'='或>$SYSROOT,然后是'='或被$SYSROOTsysroot前缀替换;看到 --sysroot->isysroot

    指定的目录 -I 引用仅适用于指令的引号形式。指定的目录#include "file" -I-isystem,或者 -idirafter适用于 #include "file"#include <file>指令的查找 。
    您可以在命令行上指定任何数目或这些选项的组合,以在多个目录中搜索头文件。查找顺序如下:

    • (1)对于include指令的引号形式,将首先搜索当前文件的目录。
    • (2)对于include指令的引号形式,目录由 -我引用 选项在命令行中按从左到右的顺序搜索。
    • (3)指定目录 -I 选项以从左到右的顺序扫描。
    • (4)指定目录 -isystem 选项以从左到右的顺序扫描。
    • (5)扫描标准系统目录。
    • (6)指定目录 -idirafter 选项以从左到右的顺序扫描。

    您可以使用 -I覆盖系统头文件,替代您自己的版本,因为这些目录是在标准系统头文件目录之前搜索的。但是,您不应使用此选项来添加包含供应商提供的系统头文件的目录。采用-isystem为了那个原因。
    -isystem-idirafter 选项还将该目录标记为系统目录,以便对它进行与标准系统目录相同的特殊处理。

    如果标准系统包含目录,或使用以下命令指定的目录 -isystem,也指定了 -I-I 选项将被忽略。该目录仍在搜索,但作为系统目录位于系统包含链中的正常位置。这是为了确保#include_next不会错误地更改GCC修正错误的系统头的过程以及该指令的顺序。如果您确实需要更改系统目录的搜索顺序,请使用-nostdinc 和/或 -isystem 选项。

    3.🧐头文件中有一般写了什么?

    如果想自己写一个头文件,那还是有必要了解一下头文件的内容的。我们不妨先打开我们从写HelloWorld.c就开始使用的stdio.h一探究竟。

    /* Checking macros for stdio functions.
       Copyright (C) 2004, 2005, 2009 Free Software Foundation, Inc.
    
    This file is part of GCC.
    
    GCC is free software; you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free
    Software Foundation; either version 3, or (at your option) any later
    version.
    
    In addition to the permissions in the GNU General Public License, the
    Free Software Foundation gives you unlimited permission to link the
    compiled version of this file into combinations with other programs,
    and to distribute those combinations without any restriction coming
    from the use of this file.  (The General Public License restrictions
    do apply in other respects; for example, they cover modification of
    the file, and distribution when not linked into a combine
    executable.)
    
    GCC is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.
    
    Under Section 7 of GPL version 3, you are granted additional
    permissions described in the GCC Runtime Library Exception, version
    3.1, as published by the Free Software Foundation.
    
    You should have received a copy of the GNU General Public License and
    a copy of the GCC Runtime Library Exception along with this program;
    see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
    <http://www.gnu.org/licenses/>.  */
    
    
    #ifndef _SSP_STDIO_H
    #define _SSP_STDIO_H 1
    
    #include <ssp.h>
    #include_next <stdio.h>
    
    #if __SSP_FORTIFY_LEVEL > 0
    
    #include <stdarg.h>
    
    #undef sprintf
    #undef vsprintf
    #undef snprintf
    #undef vsnprintf
    #undef gets
    #undef fgets
    
    extern int __sprintf_chk (char *__restrict__ __s, int __flag, size_t __slen,
    			  __const char *__restrict__ __format, ...);
    extern int __vsprintf_chk (char *__restrict__ __s, int __flag, size_t __slen,
    			   __const char *__restrict__ __format,
    			   va_list __ap);
    
    #define sprintf(str, ...) \
      __builtin___sprintf_chk (str, 0, __ssp_bos (str), \
    			   __VA_ARGS__)
    #define vsprintf(str, fmt, ap) \
      __builtin___vsprintf_chk (str, 0, __ssp_bos (str), fmt, ap)
    
    extern int __snprintf_chk (char *__restrict__ __s, size_t __n, int __flag,
    			   size_t __slen, __const char *__restrict__ __format,
    			   ...);
    extern int __vsnprintf_chk (char *__restrict__ __s, size_t __n, int __flag,
    			    size_t __slen, __const char *__restrict__ __format,
    			    va_list __ap);
    
    #define snprintf(str, len, ...) \
      __builtin___snprintf_chk (str, len, 0, __ssp_bos (str), __VA_ARGS__)
    #define vsnprintf(str, len, fmt, ap) \
      __builtin___vsnprintf_chk (str, len, 0, __ssp_bos (str), fmt, ap)
    
    extern char *__gets_chk (char *__str, size_t);
    extern char *__SSP_REDIRECT (__gets_alias, (char *__str), gets);
    
    extern inline __attribute__((__always_inline__)) char *
    gets (char *__str)
    {
      if (__ssp_bos (__str) != (size_t) -1)
        return __gets_chk (__str, __ssp_bos (__str));
      return __gets_alias (__str);
    }
    
    extern char *__SSP_REDIRECT (__fgets_alias,
    			     (char *__restrict__ __s, int __n,
    			      FILE *__restrict__ __stream), fgets);
    
    extern inline __attribute__((__always_inline__)) char *
    fgets (char *__restrict__ __s, int __n, FILE *__restrict__ __stream)
    {
      if (__ssp_bos (__s) != (size_t) -1 && (size_t) __n > __ssp_bos (__s))
        __chk_fail ();
      return __fgets_alias (__s, __n, __stream);
    }
    
    #endif /* __SSP_FORTIFY_LEVEL > 0 */
    #endif /* _SSP_STDIO_H */
    

    可以看到:

    • 写在最前边的一般是文件内容概述(该文件为检查标准函数的宏(Checking macros for stdio functions.))、版权说明等。
    • 对于程序预处理与之后编译实质内容时后面的c代码,这些代码一般是从以前中写在源文件最前面(头部)的位置,我们将这些代码摘出来,因此叫做头文件。
    • 该文件中还引入了大量其它的头文件。
    • 除此之外我们还看到了大量的extern有关的语句,extern用于调用另一个c文件里的变量或者函数。

    4.👨‍🏫条件引用

    更多相关指令可以参考:C语言-预处理

    这里我们先了解下面这几条预处理指令:

    指令 说明
    #if 如果给定条件为真,则执行预处理的内容
    #else 表示#if后的条件为假时执行预处理的内容
    #elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
    #endif 结束一个 #if……#else 条件编译块

    比如:

    #if CONDITION_1              // 若第1个条件成立
       # include "headfile_1.h"  // 引入头文件"headfile_1.h"
    #elif CONDITION_2            // 若第2个条件成立
       # include "headfile_2.h"  // 引入头文件"headfile_2.h"
       ...
    #endif                       // 条件指令的结束
    

    5.👨‍🏭用宏定义确保单次引用

    在C语言中提供了以下两个指令:

    指令 说明
    #ifdef 如果宏已经定义,则返回真
    #ifndef 如果宏没有定义,则返回真

    为了防止由于多次头文件多次引用导致某些情况下产生错误,我们需要使用上面两个指令。请看代码:

    #ifndef A                        //  当没有宏定义A时
    #define A  包含所有头文件的文件B   // 宏定义A为包含所有头文件的文件B 
    #endif                           // 用于结束一个 #if……#else 条件编译块
    

    为了防止意外,而把头文件的内容都放在#ifndef#endif指令块中。这样作无论头文件会不会被多个文件引用,也就是当再次引用到头文件HeadFileB时,条件为假。

    展开全文
  • VC 创建项目自动创建的预编译头文件,在编译其他文件之前,VC先预编译此文件。 头文件stdafx.h引入了项目中需要的一些通用的头文件,比如window.h等,在自己的头文件中包括stdafx.h就包含了那...

     https://blog.csdn.net/u013057085/article/details/50741114

    将 #include "stdafx.h" 放在所有头文件之前;

    VC 创建项目时自动创建的预编译头文件,在编译其他文件之前,VC先预编译此文件。

    头文件stdafx.h引入了项目中需要的一些通用的头文件,比如window.h等,在自己的头文件中包括stdafx.h就包含了那些通用的头文件。
    所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。

     

     

    展开全文
  • c头文件

    2017-05-19 18:22:32
    最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题. 学习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是比较模糊...

    转自:http://www.cnblogs.com/webcyz/archive/2012/09/16/2688035.html


    C语言头文件的作用
     

    最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.
    学习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是比较模糊的,只是之前的使用大多比较简单,并没有牵涉到太复杂的工程,所以定义和声明还是比较简单而明了了的.但是最近的大工程让我在这方面吃到了一点点苦头,虽然看了别人的代码能够很快的改正,但是这些改正背后的原因却不知道.我想大多数喜欢C语言的程序员应该是和我一样的,总喜欢去追究程序问题背后的底层原因,而这也恰恰是我喜欢C语言的最根本的原因.
    今天看过janders老兄在csdn上的一篇文章后,理解的确加深了很多,而且还学到一些以前不怎么知道的知识.
    现将文章转载过来,并对文章当中的一些拼写错误做了简单的纠正,同时对文字及布局做了少许修改.

    (如果想看原文的,请参考本文底部的链接.)


    --------------------------------------------------------------------------------

     

    C语言中的.h文件和我认识由来已久,其使用方法虽不十分复杂,但我却是经过了几个月的“不懂”时期,几年的“一知半解”时期才逐渐认识清楚他的本来面目。揪其原因,我的驽钝和好学而不求甚解固然是原因之一,但另外还有其他原因。原因一:对于较小的项目,其作用不易被充分开发,换句话说就是即使不知道他的详细使用方法,项目照样进行,程序在计算机上照样跑。原因二:现在的各种C语言书籍都是只对C语言的语法进行详细的不能再详细的说明,但对于整个程序的文件组织构架却只字不提,找了好几本比较著名的C语言著作,却没有一个把.h文件的用法写的比较透彻的。下面我就斗胆提笔,来按照我对.h的认识思路,向大家介绍一下。

     

    让我们的思绪乘着时间机器回到大学一年级。C原来老师正在讲台上讲着我们的第一个C语言程序: Hello world!

     文件名 First.c

    main()

    {

         printf(“Hello world!”);

    }

         例程-1

    看看上面的程序,没有.h文件。是的,就是没有,世界上的万物都是经历从没有到有的过程的,我们对.h的认识,我想也需要从这个步骤开始。这时确实不需要.h文件,因为这个程序太简单了,根本就不需要。那么如何才能需要呢?让我们把这个程序变得稍微复杂些,请看下面这个,

    文件名 First.c

     

     printStr()

    {

         printf(“Hello world!”);

    }

    main()

    {

    printStr();

    }

         例程-2

     

    还是没有, 那就让我们把这个程序再稍微改动一下.

     

    文件名 First.c

    main()

    {

    printStr();

    }

     

     

     printStr()

    {

         printf(“Hello world!”);

    }

         例程-3

     

    等等,不就是改变了个顺序嘛, 但结果确是十分不同的. 让我们编译一下例程-2和例程-3,你会发现例程-3是编译不过的.这时需要我们来认识一下另一个C语言中的概念:作用域.

    我们在这里只讲述与.h文件相关的顶层作用域, 顶层作用域就是从声明点延伸到源程序文本结束, 就printStr()这个函数来说,他没有单独的声明,只有定义,那么就从他定义的行开始,到first.c文件结束, 也就是说,在在例程-2的main()函数的引用点上,已经是他的作用域. 例程-3的main()函数的引用点上,还不是他的作用域,所以会编译出错. 这种情况怎么办呢? 有两种方法 ,一个就是让我们回到例程-2, 顺序对我们来说没什么, 谁先谁后都可以,只要能编译通过,程序能运行, 就让main()文件总是放到最后吧. 那就让我们来看另一个例程,让我们看看这个方法是不是在任何时候都会起作用.

    文件名 First.c
       play2()
     {

     ……………….

               play1();

     ………………..

     
     }


       play1()

    {
        ……………..

               play2();       
        ………………
     }

     

     

    main()

    {

        play1();

    }

    例程-4

     

    也许大部分都会看出来了,这就是经常用到的一种算法, 函数嵌套, 那么让我们看看, play1和play2这两个函数哪个放到前面呢?

     

    这时就需要我们来使用第二种方法,使用声明.

    文件名 First.c

    play1();

    play2();

    play2()

    {

     ……………….

     play1();

     ………………..
    }


        play1()
     {
         …………………….

     play2();

     ……………………
     }

     

    main()

    {

    play1();

    }

    例程-4

     

    经历了我的半天的唠叨, 加上四个例程的说明,我们终于开始了从量变引起的质变, 这篇文章的主题.h文件快要出现了。

    一个大型的软件项目,可能有几千个,上万个play, 而不只是play1,play2这么简单, 这样就可能有N个类似 play1(); play2(); 这样的声明, 这个时候就需要我们想办法把这样的play1(); play2(); 也另行管理, 而不是把他放在.c文件中, 于是.h文件出现了.

     

    文件名 First.h

    play1();

    play2();

     

    文件名 First.C

    #include “first.h”

    play2()

    {

     ……………….

     play1();

     ………………..

    }


        play1();

    {

    ……………………..

         play2();

      ……………………

    }

     

     

    main()

    {

    play1();

    }

    例程-4

     

    各位有可能会说,这位janders大虾也太罗嗦了,上面这些我也知道, 你还讲了这么半天, 请原谅, 如果说上面的内容80%的人都知道的话,那么我保证,下面的内容,80%的人都不完全知道. 而且这也是我讲述一件事的一贯作风,我总是想把一个东西说明白,让那些刚刚接触C的人也一样明白.

    上面是.h文件的最基本的功能, 那么.h文件还有什么别的功能呢? 让我来描述一下我手头的一个项目吧.

     

    这个项目已经做了有10年以上了,具体多少年我们部门的人谁都说不太准确,况且时间并不是最主要的,不再详查了。是一个通讯设备的前台软件, 源文件大小共 51.6M, 大大小小共1601个文件, 编译后大约10M, 其庞大可想而知, 在这里充斥着错综复杂的调用关系,如在second.c中还有一个函数需要调用first.c文件中的play1函数, 如何实现呢?

     

    Second.h 文件

     

    play1();

     

     

    second.c文件

     

    ***()

    {

    …………….

    Play();

    ……………….

    }

    例程-5

     

    在second.h文件内声明play1函数,怎么能调用到first.c文件中的哪个play1函数中呢? 是不是搞错了,没有搞错, 这里涉及到c语言的另一个特性:存储类说明符.

    C语言的存储类说明符有以下几个, 我来列表说明一下

      

     说明符
      用    法
     
     Auto
      只在块内变量声明中被允许, 表示变量具有本地生存期.
     
     Extern
      出现在顶层或块的外部变量函数与变量声明中,表示声明的对象具有静态生存期, 连接程序知道其名字.
     
     Static
      可以放在函数与变量声明中,在函数定义时,只用于指定函数名,而不将函数导出到链接程序,在函数声明中,表示其后边会有定义声明的函数,存储类型static.在数据声明中,总是表示定义的声明不导出到连接程序.
     

     

     

    无疑, 在例程-5中的second.h和first.h中,需要我们用extern标志符来修饰play1函数的声明,这样,play1()函数就可以被导出到连接程序, 也就是实现了无论在first.c文件中调用,还是在second.c文件中调用,连接程序都会很聪明的按照我们的意愿,把他连接到first.c文件中的play1函数的定义上去, 而不必我们在second.c文件中也要再写一个一样的play1函数.

    但随之有一个小问题, 在例程-5中,我们并没有用extern标志符来修饰play1啊, 这里涉及到另一个问题, C语言中有默认的存储类标志符. C99中规定, 所有顶层的默认存储类标志符都是extern . 原来如此啊, 哈哈. 回想一下例程-4, 也是好险, 我们在无知的情况下, 竟然也误打误撞,用到了extern修饰符, 否则在first.h中声明的play1函数如果不被连接程序导出,那么我们在在play2()中调用他时, 是找不到其实际定义位置的 .

     

    那么我们如何来区分哪个头文件中的声明在其对应的.c文件中有定义,而哪个又没有呢?这也许不是必须的,因为无论在哪个文件中定义,聪明的连接程序都会义无返顾的帮我们找到,并导出到连接程序, 但我觉得他确实必要的. 因为我们需要知道这个函数的具体内容是什么,有什么功能, 有了新需求后我也许要修改他,我需要在短时间内能找到这个函数的定义, 那么我来介绍一下在C语言中一个人为的规范:

     

    在.h文件中声明的函数,如果在其对应的.c文件中有定义,那么我们在声明这个函数时,不使用extern修饰符, 如果反之,则必须显示使用extern修饰符.

     

    这样,在C语言的.h文件中,我们会看到两种类型的函数声明. 带extern的,还不带extern的, 简单明了,一个是引用外部函数,一个是自己生命并定义的函数.

    最终如下:

    Second.h 文件

     

    Extern play1();

     

     

    上面洋洋洒洒写了那么多都是针对函数的,而实际上.h文件却不是为函数所御用的. 打开我们项目的一个.h文件我们发现除了函数外,还有其他的东西, 那就是全局变量.

     

    在大型项目中,对全局变量的使用不可避免, 比如,在first.c中需要使用一个全局变量G_test, 那么我们可以在first.h中,定义 TPYE G_test. 与对函数的使用类似, 在second.c中我们的开发人员发现他也需要使用这个全局变量, 而且要与first.c中一样的那个, 如何处理? 对,我们可以仿照函数中的处理方法, 在second.h中再次声明TPYE G_test, 根据extern的用法,以及c语言中默认的存储类型, 在两个头文件中声明的TPYE G_test,其实其存储类型都是extern, 也就是说不必我们操心, 连接程序会帮助我们处理一切. 但我们又如何区分全局变量哪个是定义声明,哪个是引用声明呢?这个比函数要复杂一些, 一般在C语言中有如下几种模型来区分:

     

    1、初始化语句模型

    顶层声明中,存在初始化语句是,表示这个声明是定义声明,其他声明是引用声明。C语言的所有文件之中,只能有一个定义声明。

    按照这个模型,我们可以在first.h中定义如下TPYE G_test=1;那么就确定在first中的是定义声明,在其他的所有声明都是引用声明。

    2、省略存储类型说明

    在这个模型中,所有引用声明要显示的包括存储类extern,而每个外部变量的唯一定义声明中省略存储类说明符。

    这个与我们对函数的处理方法类似,不再举例说明。

     

        这里还有一个需要说明,本来与本文并不十分相关,但前一段有个朋友遇到此问题,相信很多人都会遇到,那就是数组全局变量。

     

    他遇到的问题如下:

    在声明定义时,定义数组如下:

    int G_glob[100];

     

    在另一个文件中引用声明如下:

    int * G_glob;

     

    在vc中,是可以编译通过的,这种情况大家都比较模糊并且需要注意,数组与指针类似,但并不等于说对数组的声明起变量就是指针。上面所说的的程序在运行时发现了问题,在引用声明的那个文件中,使用这个指针时总是提示内存访问错误,原来我们的连接程序并不把指针与数组等同,连接时,也不把他们当做同一个定义,而是认为是不相关的两个定义,当然会出现错误。正确的使用方法是在引用声明中声明如下:

     

    int G_glob[100];

     

    并且最好再加上一个extern,更加明了。

     

    extern int G_glob[100];

     

        另外需要说明的是,在引用声明中由于不需要涉及到内存分配,可以简化如下,这样在需要对全局变量的长度进行修改时,不用把所有的引用声明也全部修改了。

     

    extern int G_glob[];

     

        C语言是现今为止在底层核心编程中,使用最广泛的语言,以前是,以后也不会有太大改变,虽然现在java,.net等语言和工具对c有了一定冲击,但我们看到在计算机最为核心的地方,其他语言是无论如何也代替不了的,而这个领域也正是我们对计算机痴迷的程序员所向往的。

     


    --------------------------------------------------------------------------------

    好了,看完文章,对与C语言头文件的作用应该有了跟多的理解吧,如果这些你原本都知道了,那么仅当是温习一下而已,如果原本不知道,那么恭喜你,现在又学到一些技巧和知识.

    对于全局变量的定义和声明,其实还有另外一个解决的方法,聪明的你可能早已经猜到了:),没错,就是用宏定义的技巧实现.比如a.h文件当中有: 
    #ifdef AAA
     int i=0;
    #else
     int i;
    #endif
    那么,在a.c文件当中,有如下语句:
    ......
    #define AAA
    #include "a.h"
    ......
    而对于其他的任何包含a.h文件的头文件或者.c源文件,只需要直接包含a.h就行了
    ......
    #include "a.h"
    ......
    这样就可以达到在a.c文件当中定义变量一次,而在其他的文件当中声明该变量的目的.
    当然了,你完全可以根据自己的需要来决定在哪个需要包含a.h的文件当中定义宏AAA,但是我要说的是
    在同一个工程的不同的需要包含a.h的文件当中,你只能定义AAA一次,否则在连接这些目标文件时会出现
    重复定义的错误,即使你的单独目标文件编译没有任何的问题.

    当然,这里说的仅仅是对全局变量的声明技巧,强烈的推介大家在头文件中使用宏定义实现对整个头文件的防止重复包含,当然了,这个技巧大多数的c语言程序员都懂.
    #ifndef XXX
    #define XXX

    #endif
    这样做会让你的程序更加稳健,很大程度上减少了不必要的麻烦...

    最后给出一点点全局变量使用需要注意的问题,这也仅仅是个建议,或者说一种编程习惯 ;)
    1) 所有全局变量全部以g_开头,并且尽可能声明成static类型. 
    2) 尽量杜绝跨文件访问全局变量.如果的确需要在多个文件内访问同一变量,应该由该变量定义所在文件内提供GET/PUT函数实现. 
    3) 全局变量必须要有一个初始值,全局变量尽量放在一个专门的函数内初始化. 
    4) 如调用的函数少于三个,请考虑改为局部变量实现. 
     

     

     

     

     

     

     

     

     

    简单办法,先写完整程序,再把一部分抽出去,抽出去的存到 自己的头文件里,在抽出的地方写 #include ...

     

    例如,完整程序(计算平均值):

    #include<stdio.h>

     

    double mean(double *y, int N){

    int i;

    double s=0.0;

    for (i=0;i<N;i++) s=s+y[i];

    s = s / (double) N;

    return s;

    }

    void main()

    {

    double x[10]={1,2,3,4,5,6,7,8,9,10};

    printf("mean = %lf\n", mean(x,10));

    }

    ----------------------------------------------

    抽出部分 存入 a_x.h :

    double mean(double *y, int N){

    int i;

    double s=0.0;

    for (i=0;i<N;i++) s=s+y[i];

    s = s / (double) N;

    return s;

    }

    --------------------------------

     

     在c/c++语言中的头文件其实是为了搜寻对应的类型和函数信息的

    比如在头文件中可以声明一个函数,但这个函数可能定义在任何地方

    比如一个静态库或者动态导入库lib中,或者可以直接以原代码的方式写在cpp/c文件中头文件提供的服务叫做类型映射(phototype)

     

    函数在c/c++语言中也是一种类型函数在声明的时候其实仅仅是说明了对应的函数调用协议,函数名称和参数类型这样就可以明确的指导编译器如何创建这个函数对应的调用代码而寻找这个函数的工作交给了链接器注意 编译器在vc里边叫做cl,链接器在vc里边叫做link。

     

    cl负责生成obj,每一个cpp/c文件会生成一个obj文件

    这个obj里边包含了直接由c/cpp源程序所生成的汇编代码,这个c/cpp文件需要查找的符号(这个后面再说)

     

    link其实和咱们上微机原理汇编课的时候用的Link很像

    他负责将每一个obj中的符号查找表中的东西转换为一个地址

    这个地址就是最后编译完成后的exe文件的函数对应这个函数的入口地址。

     

    符号,就是这个函数或者对象通过编译器后所产生的名称

    在c语言中一个符号由这个函数或者这个对象的名称和这个函数的调用协议组成

    这也是为什么c语言不支持重载的原因

    (还记得重载吧?重载就是参数类型或个数不同,

    参数和个数都相同的叫做重写,重写只能用在类的函数的继承中)

    而c++会在每一个函数的前后添加一堆用于表示这个函数的调用方法所属类型,名字空间和调用参数类型等的大量字符

    用来区分每一个函数

    比如一个函数

    int Test(); 根据不同的命名方法可能被不同的命名

    如果到定义了extern "C" 如下:

    extern "C" int Test();

    这个函数就被vc编译器按c语言的方式命名为 _Test

     其中的前划线 _表示这个函数的调用协议为__cdecl

     Test就是这个函数的名称

    如果使用vc编译器直接编译这个函数int Test();

    他就被当作int __cdecl Test(void); 编译成 ?Test@@YAHXZ

     其中的? 和 @@YAHXZ都是编译器加上去的

     ? 和 @@YAH 是用来表示调用协议的

     其中的H为返回值是int

     X表示没有参数。

     Z是函数名称结束修饰

     

    调用协议

    指函数的参数传递使用的方式

    有__cdecl __fastcall __stdcall __thiscall 等

    __cdecl 是c语言的调用方式

    __fastcall 使用寄存器传递参数

    __stdcall 使用栈传递参数,并且其压栈顺序为从右到左,由被调用函数来清除栈

    __thiscall 是类对象的方法调用方式,这种调用方式不能直接由程序指定

     

    如果一个函数的是被实现在cpp或者c文件中的时候,就必须保证这个cpp或者c文件所产生的符号与这个函数的声明所产生的

    符号相同,否则链接器在链接的时候就会发生无法找到符号的错误

     

    如何保证这个符号是相同的呢?

    只要在cpp或者c文件中包含这个头文件

    或者如果能保证所生成的符号相同则根本就不用h文件也能工作

    比如如下程序

     

    // 这是main.cpp文件的东西

    int Test();

     

    int main()

    {

     Test();

    }

     

    // 这是test.cpp文件的东西

    int Test()

    {

     return 1;

    }

    这里就完全没有用头文件

     

    注意事项:

    1。如果是使用了c文件作为函数的载体而编译器为微软的vc,

    需要在h文件中添加extern "C"通知编译器使用c的命名规则

    最好使用判断

    #ifdef __cplusplus

    extern "C" {

    #endif

     

    ...

    函数声明

    ...

     

    #ifdef __cplusplus

    }

    #endif

    这样在c编译器上也能使用这个头文件了

    2。c/cpp文件与头文件名称无关

    3。如果一个头文件中定义了一个结构或者类,那么在h文件中最好使用防止多次包含的

    #ifndef __XXX__H__

    #define __XXX__H__

     

    ...

    //类或结构定义

    class a

    {

     ...

    };

    ...

     

    #endif

    这里的XXX就是这个头文件的名称,这个名称是可以随便起的,只要不起重。

    但c/cpp文件中不需要添加这些东西

     

    或者如果使用了vc6作为编译器它支持

    #pragma once

    在h文件的最前面写上这句话就可以保证这个头文件只会被处理一遍

     

     

    程序变:

    #include<stdio.h>

    #include "a_x.h"

    void main()

    {

    double x[10]={1,2,3,4,5,6,7,8,9,10};

    printf("mean = %lf\n", mean(x,10));

    }

    =============================================

    你要是愿意随便抽一块也可以,例如抽出(也叫 a_x.h):

    double mean(double *y, int N){

    int i;

    double s=0.0;

    for (i=0;i<N;i++) s=s+y[i];

    s = s / (double) N;

    return s;

    }

    void main()

    {

    ------------------------

    程序变:

    #include<stdio.h>

    #include "a_x.h"

    double x[10]={1,2,3,4,5,6,7,8,9,10};

    printf("mean = %lf\n", mean(x,10));

    }

    ==============================

    语法上,功能上,两种抽法都可以。但第一种方法较好--程序可读性好,不易出错。

     

    一般情况下,头文件里放 函数原型,全局量声明 和 函数定义。

     

     

     

     

    C语言中的头文件可以自己写吗?

    电脑圈圈 发表于 2006-3-17 22:57:00

     

    2

    推荐

    一些初学C语言的人,不知道头文件(*.h文件)原来还可以自己写的。只知道调用系统库函数时,要使用#i nclude语句将某些头文件包含进去。其实,头文件跟.C文件一样,是可以自己写的。头文件是一种文本文件,使用文本编辑器将代码编写好之后,以扩展名.h保存就行了。头文件中一般放一些重复使用的代码,例如函数声明,变量声明,常数定义,宏的定义等等。当使用#i nclude语句将头文件引用时,相当于将头文件中所有内容,复制到#i nclude处。为了避免因为重复引用而导致的编译错误,头文件常具有

    #ifndef   LABEL

    #define   LABEL

       //代码部分

    #endif

    的格式。其中,LABEL为一个唯一的标号,命名规则跟变量的命名规则一样。常根据它所在的头文件名来命名,例如,如果头文件的文件名叫做hardware.h,

    那么可以这样使用:

    #ifndef   __HARDWARE_H__

    #define   __HARDWARE_H__

     //代码部分

    #endif

    这样写的意思就是,如果没有定义__HARDWARE_H__,则定义__HARDWARE_H__,并编译下面的代码部分,直到遇到#endif。这样,当重复引用时,由于__HARDWARE_H__已经被定义,则下面的代码部分就不会被编译了,这样就避免了重复定义。

    另外一个地方就是使用include时,使用引号与尖括号的意思是不一样的。使用引号(“”)时,首先搜索工程文件所在目录,然后再搜索编译器头文件所在目录。而使用尖括号(<>)时,刚好是相反的搜索顺序。假设我们有两个文件名一样的头文件hardware.h,但内容却是不一样的。一个保存在编译器指定的头文件目录下,我们把它叫做文件I;另一个则保存在当前工程的目录下,我们把它叫做文件II。如果我们使用的是#i nclude <hardware.h>,则我们引用到的是文件I。如果我们使用的是#i nclude “hardware.h”,则我们引用的将是文件II。笔者以前就遇到过一个同事问,为什么他修改了那个头文件里面的内容,好象跟没有修改一样?就是因为他有两个一样的头文件(就像我们刚描述的那样),他是使用#i nclude<hardware.h>引用的,而他修改时,却是当前工程所在的目录下的那个文件。

     

     

     

     

    #include 的本质就是把对应的文件直接拷贝到这一行里面

    要理解头文件,主要是要理解“声明”

    C/C++中,所有使用到得变量、函数、类都要是声明过得,就是说,要有一行语句来告诉编译器,我有一个名字叫XXX的???类型的变量(函数、类)。

    然后还有一个因素就是,在编译的时候,程序是按照每个.C或.CPP文件单独编译的。

    也就是说,对于每个C文件中,如果都用到了同一个函数(比如printf),那么,我在每个对应文件中写一遍printf的声明明显是很麻烦的。所以我把这个声明单独写了一个文件,为了区别,我把扩展名记做.h,在需要使用对应的函数(类)的时候,我就不需要去拷贝函数的声明,而只需要#include对应头文件就可以了,系统自动帮你拷贝进来——C语言提供的头文件,按照函数功能分类好了,比如数学函数就都写在了math.h里面,一包含就全包含,不管你用没用到cos()这个函数或者其他什么。

     

    当然,由于.h文件中也可以包含其他.h文件,所以为了不重复声明或定义,需要用宏做相应的处理,这个不是要理解的东西,而是照着写。

     

    看到你的补充,在这里我也补充下答案

    对于自己定义的函数,首先,肯定的是,你至少需要在一个C文件中定义它,否则链接会出错。当你想在任何一个文件中使用的时候,你只需要让这个文件包函数声明所在的头文件即可。

    具体来说:

    a.h中声明了了

    int a( int x);

    a.c中实现这个函数,需要有类似代码

    #include "a.h"

    //.....其他代码

    int a(int x)

    {

    return x*x;

    }

    如果在b.c中想使用这个,则只要在b.c中这样就可以:

    #include "a.h"

    //....其他代码

    x = a(x);

    //...其他代码


    展开全文
  • 预处理过程:头文件

    2021-03-02 19:41:05
    文章目录1 Include语法2 编译器对Include的处理3 头文件的搜索路径4 只加载一次头文件4.1 问题4.2 通过条件编译只加载一次头文件5 总结 1 Include语法 头文件一般包含C语言的声明和宏定义。在C语言源文件中,通常在...

    本文学习编译器在预处理过程中对头文件的处理。Hightec编译器版本是tricore v4.9.1.0。博主在<基于模型的设计>板块就经常提到头文件,本文会从编译器的角度对头文件进行学习。

    1 Include语法

    头文件一般包含C语言的声明和宏定义。在C语言源文件中,通常在最上面用#include指令调用,俗称为包含某个头文件。

    头文件一般分为系统头文件和用户头文件。

    • 系统头文件通常是用来调用系统库,在#include后面要用尖括号。
    #include <file>
    
    • 用户头文件中通常是函数、全局变量的外部声明, 宏定义,结构体定义,类型定义等。用户头文件起到了一个接口的作用,将不同的独立C文件通过头文件联系起来。用户头文件在#include后面要用引号。
    #include "file"
    

    2 编译器对Include的处理

    调用编译器对某个C源文件进行预处理时,如果编译器看到了#include指令,就会先扫描一下#include后的头文件,然后把头文件展开到#include指令的位置。

    为了直观地体会到这个过程,可以自己创建一些文件,然后调用编译器进行预处理。

    1)首先,创建一个 header.h 头文件如下;
    在这里插入图片描述
    在这个头文件中对test函数进行了外部声明。

    2)然后再建立一个program.c文件,如下;
    在这里插入图片描述
    在C文件中显示定义了一个变量x,然后包含了header.h这个头文件,然后定义了test2函数。

    3)通过如下命令行调用编译器,对当前目录下地 program.c 代码进行预处理。

    tricore-gcc -E program.c -o program.i
    

    在这里插入图片描述
    4)在生成的.i文件中,可以看到 header.h 头文件的内容被插入到了 program.c 源文件中;
    在这里插入图片描述
    另外,头文件中也可以嵌套包含头文件,这样的话就会一层一层地展开。

    3 头文件的搜索路径

    上一章是把头文件和源文件放在了一个路径下,通过编译器进行预处理。如果不特别地指出,编译器会在几个安装路径下和当前路径搜索头文件。如果要指定搜索头文件的路径,可以用 -I 参数指明.

    例如,把头文件放在inc文件夹中,如下:
    在这里插入图片描述
    这样的话,命令行就要写成下面的样子。

    tricore-gcc -E program.c -o program.i -I ./inc
    

    如果不加 -I 参数,编译器会报出找不到头文件的错误,如下图。
    在这里插入图片描述
    所以工作中如果看到了这种错误,就要考虑是头文件没加到路径中,还是路径没加到命令行中。

    4 只加载一次头文件

    4.1 问题

    实际项目中会有大量地头文件嵌套,一不小心就会重复包含了头文件。重复包含头文件会有两个问题:一方面如果头文件中有结构体定义,在编译的阶段会报错(预处理过程是不会报错的);另一方面,就算没有重复定义的报错,重复加载也会浪费时间。

    对于结构体重复定义的报错,可以自己复现出来。

    1)首先把第2章中的头文件稍作修改,定义一个结构体。
    在这里插入图片描述
    2)然后在C文件中包含两次该头文件。
    在这里插入图片描述
    3)再通过命令行调用编译器,对源文件进行预处理。注意这里没有 -E 参数,因为结构体重复定义是在编译过程中报错。

    tricore-gcc program.c -o program.i
    

    在这里插入图片描述
    如上图中,指明了struct1这个结构体类型重复定义了。

    4.2 通过条件编译只加载一次头文件

    有一种标准的方法解决上述问题,就是将源文件的整个内容包含在条件编译中,范例如下:

    /* File foo. */
    #ifndef FILE_FOO_SEEN
    #define FILE_FOO_SEEN
    
    the entire file
    
    #endif /* !FILE_FOO_SEEN */
    

    其中 #ifndef 表示如果后面的宏没有被定义,则执行下面到#endif之前的内容。当头文件第一次被包含的时候,还没有定义 FILE_FOO_SEEN 这个宏,就会先定义这个宏,扫描头文件的内容(也就是上述范例的 the entire file)然后展开到源文件中;当头文件第二次被包含的时候,由于第一次定义了这个宏,所以就直接跳到 #endif 了,也就不会再加载一次了。

    对4.1中的源文件做预处理,就可以看到对比结果。

    1)当没有用条件编译的时候,预处理后会把结构体类型的定义加载两次,也就导致了后面编译过程的报错。
    在这里插入图片描述
    2)在头文件中加上条件编译如下。
    在这里插入图片描述
    然后再进行一次预处理,输出的i文件如下图。这样就只有一个结构体定义了。
    在这里插入图片描述

    5 总结

    本文研究了预处理过程中对头文件的处理,有助于理解头文件的作用。

    >>返回个人博客总目录

    展开全文
  • C语言头文件的作用

    千次阅读 多人点赞 2018-08-23 00:54:40
    C语言头文件的作用 最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.学习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深
  • 关于头文件中的理解

    2015-04-18 08:56:35
    头文件的重复包含和编译
  • C++头文件作用

    2013-10-14 12:36:38
    头文件 每个C++/C程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。 C++/C程序的头文件以“.h”为...
  • C++头文件的作用

    2015-07-20 13:34:00
    头文件 每个C++/C程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。 C++/C程序的头文件以“.h”...
  • 例如:要编写头文件test.h  在头文件开头写上两行:  #ifndef _TEST_H  #define _TEST_H//一般是文件名的大写  ············  ············  头文件结尾写上一行:...
  • 头文件中的#ifndef

    千次阅读 2011-11-12 15:07:30
    而编译,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。 还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的: ...
  • ./zlibrary/ui/src/win32/w32widgets/W32VBorderBox.cpp(114) : error C2589: “(”: “::”右边的非法标记 ./zlibrary/ui/src/win32/w32widgets/W32VBorderBox.cpp(114) : error C2059: 语法错误 : “::”2....
  • c++ 头文件的作用

    千次阅读 2012-08-31 15:28:55
    头文件 每个C++/C程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。 C++/C程序的头文件以“.h”为...
  • C头文件组织与包含原则

    千次阅读 2017-09-04 20:33:01
     如非特殊说明,文中“源文件”指*.c文件,“头文件”指*.h文件,“引用”指包含头文件。   一、头文件作用  C语言里,每个源文件是一个模块,头文件为使用该模块的用户提供接口。接口指一个功能模块...
  • C/C++头文件的作用

    2013-03-27 12:40:33
    用C++很长时间了,一直以来弄不明白头文件有啥大用,特别是用第三方库,已经引用了lib文件,为啥还有把头文件引进来。今天学习了一下,才发现C++头文件很重要。 头文件的结构: 头文件由三部分内容组成: ...
  • #ifndef 在头文件中的作用

    千次阅读 2014-08-28 15:34:57
    ,就会出现大量“重定义”的错误。在头文件中实用#ifndef #define #endif能避免头文件的重定义。 方法:例如要编写头文件test.h 在头文件开头写上两行: #ifndef _TEST_H #define _TEST_H//一般是...
  • 如非特殊说明,文中“源文件”指*.c文件,“头文件”指*.h文件,“引用”指包含头文件。 一、头文件作用 C语言里,每个源文件是一个模块,头文件为使用该模块的用户提供接口。接口指一个功能模...
  • 基本类型库头文件

    千次阅读 2009-11-11 17:00:00
    基本类型库头文件由七个部分组成:1. 头部固定正文:由注释、COMDEF.H(定义用在头部的一些标准宏)的#include语句和其它繁杂的安装信息组成。2.前向引用和类型定义:由象struct IMyinterface之类的结构说明和用于一些...
  • C/C++ 头文件作用

    千次阅读 2016-09-03 11:29:27
    最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题. 学习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是比较模糊...
  • 头文件为什么要加#ifndef #define #endif

    千次阅读 2016-11-22 21:07:41
    一 #ifndef 在头文件中的作用一个大的软件工程里有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件,就会出现大量“重定义”的错误。这时在头文件中使用#ifndef #define #endif,就可以防止头文件...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,217
精华内容 5,286
关键字:

引用头文件时大量语法错误