中企動力做過的網(wǎng)站女教師遭網(wǎng)課入侵視頻
1.預(yù)定義符號
__FILE__ //進行編譯的源文件
__LINE__ //文件當(dāng)前的行號
__DATE__ //文件被編譯的日期
__TIME__ //文件被編譯的時間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
這些預(yù)定義符號都是C語言內(nèi)置的。
舉個例子:
#include <stdio.h>int main() {printf("%s\n", __FILE__);//如:1.cprintf("%d\n", __LINE__);// 5printf("%s\n", __DATE__);// Jul 30 2023printf("%s\n", __TIME__);// 10:13:20 記錄的時間是編譯的時間printf("%d\n", __STDC__); //1 也可能是未定義 不遵循ANSI Creturn 0;
}
2 #define
2.1 #define定義標(biāo)識符
#define
定義標(biāo)識符形式:
#define 標(biāo)識符 值
其中,標(biāo)識符
是你希望定義的名稱,而 值
可以是一個數(shù)值、一個字符串或一個表達式。
例子:
#include <stdio.h>
#define MAX 100
#define STR "Hello Wrold"
#define do_forever for (;;)
int main() {printf("%d\n", MAX);//100printf(STR); //Hello Worlddo_forever; //死循環(huán)return 0;
}
#define
只是進行簡單的文本替換,沒有類型檢查和錯誤檢查。
建議
#define
后面不要加分號
#include <stdio.h>
#define MAX 1000;
int main() {int max = 0;if (3 > 5) {//max = MAX; //報錯 因為MAX ==1000; 出現(xiàn)了兩個分號max = MAX//正確} else {max = 0;}return 0;
}
2.2 #define定義宏
#define機制包括了一個規(guī)定,允許把參數(shù)替換到文本中,這種實現(xiàn)通常稱為宏或定義宏。
下面是宏的聲明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現(xiàn)在stuff中。
例子:
#include <stdio.h>//函數(shù)解決
int Max_hanshu(int x, int y) {return x > y ? x : y;
}
//宏解決
#define MAX(x, y) (x > y ? x : y)
int main() {int a = 10;int b = 20;int max = Max_hanshu(a, b);int m = MAX(a, b);printf("%d\n", max); //20printf("%d\n", m); //20return 0;
}
注意:
參數(shù)列表的左括號必須與name緊鄰。
如果兩者之間有任何空白存在,參數(shù)列表就會被解釋為stuff的一部分。
例如:
#define SQUARE( x ) x * x
這個宏接收一個參數(shù) x . 如果在上述聲明之后,你把
SQUARE( 5 );
置于程序中,預(yù)處理器就會用下面這個表達式替換上面的表達式:
5 * 5
警告:
這個宏存在一個問題:
觀察下面的代碼段:
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
乍一看,你可能覺得這段代碼將打印36這個值。 事實上,它將打印11。
為什么?
替換文本時,參數(shù)x被替換成a + 1,所以這條語句實際上變成了:
printf ("%d\n",a + 1 * a + 1 );
這樣就比較清晰了,由替換產(chǎn)生的表達式并沒有按照預(yù)想的次序進行求值。
在宏定義上加上兩個括號,這個問題便輕松的解決了:
#define SQUARE(x) (x) * (x)
這樣預(yù)處理之后就產(chǎn)生了預(yù)期的效果:
printf ("%d\n",(a + 1) * (a + 1) );
這里還有一個宏定義:
#define DOUBLE(x) (x) + (x)
定義中我們使用了括號,想避免之前的問題,但是這個宏可能會出現(xiàn)新的錯誤。
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));
這將打印什么值呢?
warning:
看上去,好像打印100,但事實上打印的是55.
我們發(fā)現(xiàn)替換之后:
printf ("%d\n",10 * (5) + (5));
乘法運算先于宏定義的加法,所以出現(xiàn)了55。
這個問題,的解決辦法是在宏定義表達式兩邊加上一對括號就可以了。
#define DOUBLE( x) ( ( x ) + ( x ) )
#include <stdio.h>#define SQUARE(X) X *X
#define SQUARE1(X) (X) * (X)
#define DOUBLE(X) (X) + (X)
#define DOUBLE1(X) ((X) + (X))
int main() {printf("%d\n", SQUARE(5)); // 25printf("%d\n", SQUARE(5 + 1)); // 5+1*5+1 == 11printf("%d\n", SQUARE1(5 + 1));// 36printf("%d\n", DOUBLE(6)); // 12printf("%d\n", DOUBLE(6 + 1)); // 14printf("%d\n", 10 * DOUBLE(6)); // 66 10*(6)+(6) ==66printf("%d\n", 10 * DOUBLE1(6));//120return 0;
}
總結(jié):
所以用于對數(shù)值表達式進行求值的宏定義都應(yīng)該用這種方式加上括號,避免在使用宏時由于參數(shù)中的操作符或鄰近操作符之間不可預(yù)料的相互作用。
2.3 #define的替換規(guī)則
在程序中擴展#define定義符號和宏時,需要涉及幾個步驟。
- 在調(diào)用宏時,首先對參數(shù)進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先被替換。
- 替換文本隨后被插入到程序中原來文本的位置。對于宏,參數(shù)名被他們的值所替換。
- 最后,再次對結(jié)果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復(fù)上述處理過程。
注意:
- 宏參數(shù)和#define定義中可以出現(xiàn)其他#define定義的符號。但是對于宏,不能出現(xiàn)遞歸。
- 當(dāng)預(yù)處理器搜索#define定義的符號的時候,字符串常量的內(nèi)容并不被搜索。
示例 1 - 合法的宏定義:
#define PI 3.14159
#define CIRCLE_AREA(radius) (PI * (radius) * (radius))double area = CIRCLE_AREA(2.5); // 宏 CIRCLE_AREA 使用了已定義的宏 PI
示例 2 - 非法的宏定義(遞歸):
// 這是一個非法的宏定義,宏 AREA 使用了它自身
#define AREA(x) (x > 0 ? x * x : AREA(x))int result = AREA(5); // 這將導(dǎo)致宏展開的無限循環(huán),造成編譯錯誤
2.4 #和##
#
運算符可以將宏參數(shù)轉(zhuǎn)換為字符串常量。它允許你在宏定義中將參數(shù)轉(zhuǎn)換為字符串字面值。
實例:
#define STRINGIFY(x) #xint main() {int num = 42;const char* str = STRINGIFY(num);// 在宏展開時,num 被轉(zhuǎn)換為字符串 "42"printf("num as a string: %s\n", str); // Output: "num as a string: 42"return 0;
}
##
運算符用于在宏定義中將兩個標(biāo)記粘貼在一起。它允許你將多個標(biāo)識符組合成一個新的標(biāo)識符。
實例:
#define CONCAT(x, y) x ## yint main() {int num1 = 10;int num2 = 20;int result = CONCAT(num, 1) + CONCAT(num, 2);// 在宏展開時,CONCAT(num, 1) 變成 num1,CONCAT(num, 2) 變成 num2// 所以,result 的值就是 num1 + num2,即 10 + 20printf("result: %d\n", result); // Output: "result: 30"return 0;
}
2.5 帶副作用的宏參數(shù)
當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過一次的時候,如果參數(shù)帶有副作用,那么你在使用這個宏的時候就可能出現(xiàn)危險,導(dǎo)致不可預(yù)測的后果。副作用就是表達式求值的時候出現(xiàn)的永久性效果。
有副作用的代碼:
int main() {int a = 1;int b = a + 1;// b=2,a=1a = 1;b = ++a;// b=2,a=2 帶有副作用的代碼,a的值發(fā)生了改變int ch = getchar();//讀一個字符,緩沖區(qū)少一個字符return 0;
}
x+1;//不帶副作用
x++;//帶有副作用
MAX宏可以證明具有副作用的參數(shù)所引起的問題。
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//輸出的結(jié)果是什么?
這里我們得知道預(yù)處理器處理之后的結(jié)果是什么:
z = ( (x++) > (y++) ? (x++) : (y++));
所以輸出的結(jié)果是:
x=6 y=10 z=9
2.6 宏和函數(shù)對比
宏通常被應(yīng)用于執(zhí)行簡單的運算。 比如在兩個數(shù)中找出較大的一個。
那為什么不用函數(shù)來完成這個任務(wù)?
原因有二:
1.用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實際執(zhí)行這個小型計算工作所需要的時間更多。
所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。2.更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。 所以函數(shù)只能在類型合適的表達式上使用。反之這個宏怎可以適用于整形、長整型、浮點型等可以用于>來比較的類型。 宏是類型無關(guān)的。
實例:
#include <stdio.h>int Max(int x, int y) {return x > y ? x : y;
}#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main() {int a = 10;int b = 20;//函數(shù)的方式int m1 = Max(a, b);printf("%d\n", m1);//宏的方式int m2 = MAX(a, b);printf("%d\n", m2);return 0;
}
宏的缺點:當(dāng)然和函數(shù)相比宏也有劣勢的地方:
每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序 的長度。
宏是沒法調(diào)試的。
宏由于類型無關(guān),也就不夠嚴(yán)謹(jǐn)。
宏可能會帶來運算符優(yōu)先級的問題,導(dǎo)致程容易出現(xiàn)錯。
宏有時候可以做函數(shù)做不到的事情。比如:宏的參數(shù)可以出現(xiàn)類型,但是函數(shù)做不到。
#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
int main(){int *p = malloc(10, sizeof(int));MALLOC(10, int); //類型作為參數(shù)return 0;
}
宏和函數(shù)的一個對比
屬性 | #define宏 | 函數(shù) |
---|---|---|
代碼長度 | 每次使用時,宏代碼都會被插入到程序中。除了非常 小的宏之外,程序的長度會大幅度增長 | 函數(shù)代碼只出現(xiàn)于一個地方;每 次使用這個函數(shù)時,都調(diào)用那個 地方的同一份代碼 |
執(zhí)行速 度 | 更快 | 存在函數(shù)的調(diào)用和返回的額外開 銷,所以相對慢一些 |
操作符 優(yōu)先級 | 宏參數(shù)的求值是在所有周圍表達式的上下文環(huán)境里, 除非加上括號,否則鄰近操作符的優(yōu)先級可能會產(chǎn)生不可預(yù)料的后果,所以建議宏在書寫的時候多些括號。 | 函數(shù)參數(shù)只在函數(shù)調(diào)用的時候求 值一次,它的結(jié)果值傳遞給函數(shù)。表達式的求值結(jié)果更容易預(yù)測。 |
帶有副 作用的 參數(shù) | 參數(shù)可能被替換到宏體中的多個位置,所以帶有副作用的參數(shù)求值可能會產(chǎn)生不可預(yù)料的結(jié)果。 | 函數(shù)參數(shù)只在傳參的時候求值一 次,結(jié)果更容易控制。 |
參數(shù)類 型 | 宏的參數(shù)與類型無關(guān),只要對參數(shù)的操作是合法的, 它就可以使用于任何參數(shù)類型。 | 函數(shù)的參數(shù)是與類型有關(guān)的,如果參數(shù)的類型不同,就需要不同的函數(shù),即使他們執(zhí)行的任務(wù)是相同的。 |
調(diào)試 | 宏是不方便調(diào)試的 | 函數(shù)是可以逐語句調(diào)試的 |
遞歸 | 宏是不能遞歸的 | 函數(shù)是可以遞歸的 |
2.7 命名約定
一般來講函數(shù)的宏的使用語法很相似。所以語言本身沒法幫我們區(qū)分二者。 那我們平時的一個習(xí)慣是:
把宏名全部大寫
函數(shù)名不要全部大寫
3.#undef
這條指令用于移除一個宏定義。
#include <stdio.h>#define M 100
int main() {printf("%d\n");
#undef Mprintf("%d\n", M);//報錯,M的宏定義已經(jīng)被移除return 0;
}
4.命令行定義
C語言是一種通用的編程語言,它允許開發(fā)者通過編寫命令行程序與計算機進行交互。命令行程序是指在命令行界面(也稱為終端或命令提示符)中運行的程序。用戶可以通過輸入命令和參數(shù)來調(diào)用這些程序,并從程序的輸出中獲取結(jié)果。
在C語言中,命令行參數(shù)是通過main函數(shù)的參數(shù)傳遞給程序的。main函數(shù)是C程序的入口點,它有兩個參數(shù):argc
和argv
。
- argc:表示命令行參數(shù)的數(shù)量,包括程序本身。它是一個整數(shù)類型的變量。
- argv:是一個指向字符指針數(shù)組的指針,用于存儲命令行參數(shù)的字符串。每個字符串代表一個命令行參數(shù)。其中,argv[0]存儲的是程序的名稱(執(zhí)行文件的名稱),argv[1]存儲的是第一個命令行參數(shù),以此類推。
int main(int argc, char *argv[]) {// Your code herereturn 0;
}
示例說明:
假設(shè)我們有一個程序叫做"my_program",編譯后生成可執(zhí)行文件"my_program.exe"(在Windows上),然后我們在命令行中運行該程序,輸入如下:
my_program hello world
在這個例子中,argc的值將是4,因為有四個參數(shù):程序名稱"my_program"、“hello”、“world”,以及一個隱含的表示字符串結(jié)束的null指針。
argv數(shù)組將包含以下內(nèi)容:
argv[0] -> "my_program"
argv[1] -> "hello"
argv[2] -> "world"
argv[3] -> NULL
#include <stdio.h>int main(int argc, char *argv[]) {for (int i = 0; i < argc; i++) {printf("Argument %d: %s\n", i, argv[i]);}return 0;
}
輸出結(jié)果:
Argument 0: my_program
Argument 1: hello
Argument 2: world
5.條件編譯
條件編譯是一種預(yù)處理指令,它允許在編譯階段根據(jù)不同的條件選擇性地包含或排除代碼片段。條件編譯可以用于根據(jù)不同的編譯條件來控制程序的行為,比如在不同平臺上使用不同的代碼或啟用/禁用特定功能。
條件編譯使用預(yù)處理指令#ifdef
、#ifndef
、#else
、#endif
、#if
、#elif
和#define
等來實現(xiàn)。這些指令都以井號(#)開頭,并且在編譯前被預(yù)處理器處理。
下面是C語言條件編譯的基本指令:
1.#ifdef
和 #ifndef
:
#ifdef 宏名// 如果宏已定義,則編譯這里的代碼
#else// 如果宏未定義,則編譯這里的代碼
#endif
#ifdef
用于檢查一個宏是否已經(jīng)定義,如果已定義,則編譯 #ifdef
和 #endif
之間的代碼,否則跳過這部分代碼。
#ifndef
則與 #ifdef
相反,它用于檢查一個宏是否未定義,如果未定義,則編譯 #ifndef
和 #endif
之間的代碼。
2.#else
:
#ifdef 宏名// 如果宏已定義,則編譯這里的代碼
#else// 如果宏未定義,則編譯這里的代碼
#endif
#else
用于在 #ifdef
或 #ifndef
條件不滿足時,編譯 #else
和 #endif
之間的代碼。
3.#if
、#elif
和 #endif
:
#if 表達式// 如果表達式為真,則編譯這里的代碼
#elif 其他表達式// 如果其他表達式為真,則編譯這里的代碼
#else// 如果前面的條件都不滿足,則編譯這里的代碼
#endif
#if
允許根據(jù)一個表達式的結(jié)果來決定是否編譯其后的代碼。#elif
用于檢查前面的條件不滿足時,繼續(xù)檢查其他條件。#else
則用于處理前面的條件都不滿足的情況。
嵌套條件編譯
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
6.文件包含
6.1 頭文件被包含的方式
1.本地文件包含" "
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯誤。
#include"add.h"
int main(){printf("hehe\n");return 0;
}
2.庫文件包含 < >
查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯誤。
這樣是不是可以說,對于庫文件也可以使用 " " 的形式包含?
答案是肯定的,可以。但是這樣做查找的效率就低些,當(dāng)然這樣也不容易區(qū)分是庫文件還是本地文件了。
6.2 嵌套文件包含
comm.h和comm.c是公共模塊。
test1.h和test1.c使用了公共模塊。
test2.h和test2.c使用了公共模塊。
test.h和test.c使用了test1模塊和test2模塊。
這樣最終程序中就會出現(xiàn)兩份comm.h的內(nèi)容。這樣就造成了文件內(nèi)容的重復(fù)。
如何解決這個問題?
答案:條件編譯。
每個頭文件的開頭寫:
#ifndef __TEST_H__
#define __TEST_H__
//頭文件的內(nèi)容
#endif //__TEST_H__
或者:
#pragma once