c语言调用同一目录下的函数,从C中同一目录中的另一个文件调用函数
這里有幾件不同的事情。首先,我將介紹多個文件的基本編譯是如何工作的。
如果你有多個文件,重要的是聲明和函數定義之間的區別。定義可能是您在定義函數時習慣的定義:您編寫函數的內容,如
int square(int i) {
return i*i;
}
另一方面,聲明允許您向編譯器聲明您知道函數存在,但是您不告訴編譯器它是什么。例如,你可以寫
int square(int i);
編譯器會期望函數“square”在別處定義。
現在,如果你有兩個不同的文件想要互操作(例如,假設函數“square”在add.c中定義,并且你想在main.c中調用square(10)),你需要同時做一個定義和一個聲明。首先,在add.c中定義square。然后,在main.c的開頭聲明它。這讓編譯器知道它在編譯main.c時有一個函數“square”,它在別處定義。現在,您需要將main.c和add.c編譯為目標文件。你可以通過電話來做到這一點
gcc -c main.c
gcc -c add.c
這將生成文件main.o和add.o.它們包含已編譯的函數,但不完全可執行。這里要理解的重要一點是,main.o在某種意義上是“不完整的”。在編譯main.o時,你告訴它函數“square”存在,但函數“square”沒有在main.o中定義。因此main.o對函數“square”有一種“懸空引用”。除非將它與另一個包含“square”定義的.o(或.so或.a)文件合并,否則它不會編譯成完整的程序。如果您只是嘗試將main.o鏈接到程序中,即
gcc -o executable main.o
您將收到錯誤,因為編譯器將嘗試解析對“square”函數的懸空引用,但不會找到它的任何定義。但是,如果在鏈接時包含add.o(鏈接是將.o文件轉換為可執行文件或.so文件時解析所有這些對未定義函數的引用的過程),那么就不會有任何問題。即
gcc -o executable main.o add.o
這就是如何在C文件中功能性地使用函數,但在風格上,我剛剛向您展示的是“不正確的方式”。我做的唯一原因是因為我認為它會更好地幫助你了解正在發生的事情,而不是依賴于“#include magic”。現在,您可能已經注意到,如果您必須重新聲明要在main.c頂部使用的每個函數,事情會變得有點混亂。這就是為什么C程序經常使用名為“headers”的輔助文件,其擴展名為.h 。標題的概念是它只包含函數的聲明,而沒有它們的定義。這樣,為了使用add.c中定義的函數編譯程序,您不需要手動聲明您正在使用的每個函數,也不需要#include代碼中的整個add.c文件。相反,你可以#include add.h,它只包含add.c的所有函數的聲明。
現在,重新開始#include:#include只是將一個文件的內容直接復制到另一個文件中。所以,例如,代碼
abc
#include "wtf.txt"
def
完全等同于
abc
hello world
def
假設wtf.txt包含文本“hello world”。
那么,如果我們將add.c的所有聲明都放在add.h中(即
int square(int i);
然后在main.c的頂部,我們寫
#include "add.h"
這在功能上與我們剛剛在main.c頂部手動聲明函數“square”一樣。
所以使用標題的一般想法是你可以有一個特殊的文件,通過#including它自動聲明你需要的所有函數。
但是,標題還有一個更常見的用途。我們假設main.c使用50個不同文件的函數。 main.c的頂部看起來像:
#include "add.h"
#include "divide.h"
#include "multiply.h"
#include "eat-pie.h"
...
相反,人們經常將所有#includes移動到main.h頭文件中,只需從main.c中#include main.h.在這種情況下,頭文件有兩個目的。它聲明了main.c中的函數,供其他文件包含時使用,并且包含main.c中包含的main.c的所有依賴項。以這種方式使用它也允許依賴鏈。如果#include add.h,不僅可以獲得add.c中定義的函數,還可以隱式獲取add.c使用的任何函數,以及它們使用的任何函數,等等。
另外,更巧妙的是,#包含來自它自己的.c文件的頭文件會隱式檢查你所犯的錯誤。例如,如果您不小心將square定義為
double square(int i);
在add.h中,你通常可能沒有意識到,直到你鏈接main.o正在尋找square的一個定義,add.o正在提供另一個不兼容的。這會導致鏈接時出錯,因此在構建過程的后期才會意識到錯誤。但是,如果你從add.c #include add.h到編譯器,你的文件看起來像
#include "add.h"
int square(int i) {
return i*i;
}
處理完#include語句之后會是這樣的
double square(int i);
int square(int i) {
return i*i;
}
編譯add.c時編譯器會注意到哪些,并告訴你。實際上,以這種方式包括您自己的標題可以防止您錯誤地向其他文件宣傳您提供的功能類型。
為什么你可以在沒有聲明的情況下使用函數
正如您所注意到的,在某些情況下,您實際上可以使用函數而不是每次聲明它或#including任何聲明它的文件。這是愚蠢的,每個人都同意這是愚蠢的。但是,它是C編程語言(和C編譯器)的遺留特性,如果你在沒有首先聲明它的情況下使用函數,它只是假設它是一個返回類型為“int”的函數。所以實際上,使用函數隱式地將該函數聲明為一個函數,如果它尚未聲明,則返回“int”。如果你考慮它,這是非常奇怪的行為,編譯器應警告你,如果你做這種行為。
頭衛
另一種常見做法是使用“Header Guards”。為了解釋標題保護,讓我們看看可能存在的問題。假設我們有兩個文件:herp.c和derp.c,它們都希望使用彼此包含的函數。遵循上述準則,您可能會有一行herp.h
#include "derp.h"
和一行derp.h
#include "herp.h"
現在,如果你考慮一下,#include“derp.h”將被轉換為derp.h的內容,而derp.h又包含#include“herp.h”行,它將被轉換為herp的內容。 h,包含……等等,所以編譯器將繼續只是擴展包含。類似地,如果main.h#包括herp.h和derp.h,并且herp.h和derp.h都包含add.h,我們看到在main.h中,我們最終得到了兩個add.h副本,一個是#including herp.h的結果,另一個是包含derp.h的結果。那么,解決方案呢?一個“Header guard”,即一段防止任何標題被#included兩次的代碼。例如,對于add.h,執行此操作的常規方法是:
#ifndef ADD_H
#define ADD_H
int sqrt(int i);
...
#endif
這段代碼基本上是告訴預處理器(處理所有“#XXX”語句的編譯器部分)來檢查是否已經定義了“ADD_H”。如果它不是(ifndef)那么它首先定義“ADD_H”(在這個上下文中,ADD_H不必定義為任何東西,它只是一個定義或不定義的布爾值),然后定義其余的標題的內容。但是,如果已經定義了ADD_H,則#including此文件將不執行任何操作,因為#ifndef塊之外沒有任何內容。因此,我們的想法是,只有第一次將它包含在任何給定文件中時,它才會實際向該文件添加任何文本。之后,#include它不會在文件中添加任何其他文本。 ADD_H只是您選擇的任意符號,用于跟蹤add.h是否已包含在內。對于每個標題,您使用不同的符號來跟蹤它是否已被包含。例如,herp.h可能會使用HERP_H而不是ADD_H。使用“標題保護”將解決我上面列出的任何問題,其中包含文件的重復副本,或#includes的無限循環。
總結
以上是生活随笔為你收集整理的c语言调用同一目录下的函数,从C中同一目录中的另一个文件调用函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为IOT设备消息上报和消息下发验证
- 下一篇: 美光科技推最高容量企业级SATA固态硬盘