目錄
語法. 3
NULL,TRUE,FALSE 3
大小端存儲 4
類型轉換 4
轉義字符 5
運算符的優先級 5
表達式(a=b=c) 7
*pa++=*pb++ 7
值的比較(浮點,指針) 8
循環語句的小技巧 8
常量 const enum define code 8
全局變量定義在.h 10
函數 11
函數聲明與定義 11
函數堆棧(?) 12
函數調用規范(*) 12
函數連接規范(?) 12
參數傳遞 12
返回值 12
存儲類型&作用域 12
遞歸與迭代 13
斷言 assert 13
函數中的const 保護 14
指針 14
指針的初始化 14
指針運算 14
數組 15
函數指針》》》》》》》》》 16
結構,位域,聯合體 16
位域 16
聯合體 union 16
枚舉 16
結構體對齊 17
一些代碼抄抄 17
Printf 17
編譯 18
預處理文件包含 18
宏定義 #與## 18
編譯器常量 20
1
2
3
4
5
uint8_t * pnum ;
uint8_t real_num=0;
uint32_t num =0x12345678;
pnum =(uint8_t*)#
real_num=*pnum;//Keil c51中,real_num=0x12,大端模式
1
2
3
4
int abc =0x11;
if(abc==TRUE)//條件不成立,TRUE=1
{
}
1
2
3
#ifndef NULL
#define NULL ((void *) 0L)
#endif
大端存儲"abcd"
0x12345678
小端存儲"abcd"
0x12345678
地址
char
long
地址
char
long
0x0001
a
0x12
0x0001
d
0x78
0x0002
b
0x34
0x0002
c
0x56
0x0003
c
0x56
0x0003
b
0x34
0x0004
d
0x78
0x0004
a
0x12
1
2
3
4
5
uint8_t * pnum ;
uint8_t real_num=0;
uint32_t num =0x12345678;
pnum =(uint8_t*)#
real_num=*pnum;//Keil c51中,real_num=0x12,大端模式
1
2
3
4
5
6
7
8
union test_array
{
uint32_t a;
uint8_t b[4];
} num_union;
uint8_t real_num=0;
num_union.a =0x12345678;
real_num=num_union.b[0];//Keil C51 real_num=0x12;
標准C語言中,int為默認類型,不明確函數形參或者返回值的時候,則為int 類型。(PC)
區分回車與換行,輸入回車(\r),輸出換行(\n)。換行一般用語文件,把鍵盤輸入的"回車字符轉換為"換行"字符保存而非直接保存"回車字符",換行字符還用於程序的輸出控制,指示終端輸出從新行開始,回車用於鍵盤輸入。有些函數 getchar () 可以把鍵盤輸入的回車轉換為換行字符返回。_@高質量程序設計指南
優先級
運算符
名稱或含義
使用形式
結合方向
說明
1
[]
數組下標
數組名[常量表達式]
左到右
()
圓括號
(表達式)/函數名(形參表)
.
成員選擇(對象)
對象.成員名
->
成員選擇(指針)
對象指針->成員名
2
-
負號運算符
-表達式
右到左
單目運算符
(類型)
強制類型轉換
(數據類型)表達式
++
自增運算符
++變量名/變量名++
單目運算符
--
自減運算符
--變量名/變量名--
單目運算符
*
取值運算符
*指針變量
單目運算符
&
取地址運算符
&變量名
單目運算符
!
邏輯非運算符
!表達式
單目運算符
~
按位取反運算符
~表達式
單目運算符
sizeof
長度運算符
sizeof(表達式)
3
/
除
表達式/表達式
左到右
雙目運算符
*
乘
表達式*表達式
雙目運算符
%
余數(取模)
整型表達式/整型表達式
雙目運算符
4
+
加
表達式+表達式
左到右
雙目運算符
-
減
表達式-表達式
雙目運算符
5
<<
左移
變量<<表達式
左到右
雙目運算符
>>
右移
變量>>表達式
雙目運算符
6
>
大於
表達式>表達式
左到右
雙目運算符
>=
大於等於
表達式>=表達式
雙目運算符
<
小於
表達式<表達式
雙目運算符
<=
小於等於
表達式<=表達式
雙目運算符
7
==
等於
表達式==表達式
左到右
雙目運算符
!=
不等於
表達式!= 表達式
雙目運算符
8
&
按位與
表達式&表達式
左到右
雙目運算符
9
^
按位異或
表達式^表達式
左到右
雙目運算符
10
|
按位或
表達式|表達式
左到右
雙目運算符
11
&&
邏輯與
表達式&&表達式
左到右
雙目運算符
12
||
邏輯或
表達式||表達式
左到右
雙目運算符
13
?:
條件運算符
表達式1? 表達式2: 表達式3
右到左
三目運算符
14
=
賦值運算符
變量=表達式
右到左
/=
除後賦值
變量/=表達式
*=
乘後賦值
變量*=表達式
%=
取模後賦值
變量%=表達式
+=
加後賦值
變量+=表達式
-=
減後賦值
變量-=表達式
<<=
左移後賦值
變量<<=表達式
>>=
右移後賦值
變量>>=表達式
&=
按位與後賦值
變量&=表達式
^=
按位異或後賦值
變量^=表達式
|=
按位或後賦值
變量|=表達式
15
,
逗號運算符
表達式,表達式,…
左到右
從左向右順序運算
容易誤導的
當表達式存在同一個優先級的時候,這個時候需要結合性處理,所有的賦值符(包括復合賦值)都具有右結合性,就是在表達式中最右邊的操作最先執行,然後從右到左依次執行。這樣,c先賦值給b,然後b在賦值給a,最終a的值是c。類似地,具有左結合性的操作符(如位操作符"&"和"|")則是從左至右依次執行。
a=b+c+d
=是右結合的,所以先計算(b+c+d),然後再賦值給a
+是左結合的,所以先計算(b+c),然後再計算(b+c)+d
C語言中具有右結合性的運算符包括所有單目運算符以及賦值運算符(=)和條件運算符。其它都是左結合性。
*pa=*pb; //*高於++高於=,++,=右結合
Pa++;pb++;
for(i=0;i<N;i++)
{
if(condition)
dosomething();
else
doothers();
}
if(condition)
{
for(i=0;i<N;i++)
dosomething();
}
else
{
for(i=0;i<N;i++)
doothers();
}
看*和const誰離右邊的定義指針名最近,*離得近的話表示該指針指向一個常量字符串,不能通過該指針改變字符串的內容;const離得近的話表示這是一個常量指針,指針指向的位置一開始就確定,不能改變。
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main(int argc, char *argv[])
{
const char abc=100;
//abc=12;//編譯器檢查出錯
char * ptr =(char*)&abc;//繞過編譯器檢查
*ptr=12;
printf("abc=%d\r\n",abc);//abc=12;
printf("*ptr=%d\r\n",*ptr);
return 0;
}
1
2
3
4
5
6
7
8
9
void main()
{
char code abc=10;
char * ptr =(char*)&abc;
*ptr=12;//由於CODE 關鍵字,abc=10
printf("abc=%d\r\n",abc);//abc=12;
printf("*ptr=%d\r\n",*ptr);
while(1);
}
全局變量我們一般使用的是在某個.c文件中定義,然後其他c文件要用的,直接extern聲明一下就能用了。C語言中的全局變量的作用域是整個源程序,只能定義一次。但是Ucos 2 的全局變量定義在.h中,與一般做法不一樣。一般情況下,全局變量不可以定義在可被多個.C文件包含的頭文件中,但是ucos利用編譯機制巧妙做到了,將全局變量放在一塊比較容易管理。比如 OS_EXT int g_Var;就是第一次被包含的時候 編譯為 int g_Var,然後第二次被其他文件包含的時侯編譯為 extern int g_Var。
C編譯器在原文件中編譯到include這條語句的時候,是將對應的頭文件內容拷貝到該處的,也就是說在頭文件中定義的全局變量,其實還是定義到了該源文件中。那麼如果另外一個源文件也想引用這個全局變量怎麼辦呢?是不是同理用include包含該頭文件就可以了?如果你這樣做了,編譯器在編譯的時候是不會報錯的,因為你的程序語法沒有任何錯誤,但是在連接(link...)的時候錯誤就來了。在VC++6.0環境中會報出:error LNK2001: unresolved external symbol "int a" (?a@@3HA)這樣的錯誤。這是為什麼呢?每一個源文件編譯之後都會產生一個OBJ或O文件(我們叫它目標文件),之前說過源文件中的include 相當於將該頭文件中的所有語句放在該處,全局變量也就相應的添加進來,問題就在這裡,既然全局變量被相應包含進來,兩個原文件中就都分別定義了該變量,但是前面也說過,全局變量的作用域是整個源程序,只能有定義一次,然而在這個源程序中該變量被定義了兩次,當然要報錯了。怎麼解決呢?參考ucos
/* file 1---------------------------OS_uCOS_II.h */
#ifndef OS_uCOS_II_H
#define OS_uCOS_II_H
#ifdef OS_GLOBALS
#define OS_EXT
#else
#define OS_EXT extern
#endif
OS_EXT int g_Var;
#endif
/* file 2---------------------------OS_uCOS_II.c*/
#define OS_GLOBALS
#include "uCOS_II.h"
/* file3----------------------------------TEST.c*/
#include "uCOS_II.h"
void fun(int x)
{
g_Var=x;
}
/* file4----------------------------------MAIN.c*/
#include "uCOS_II.h"
void main(void)
{
g_Var=10;
}
/* ------------------------------end of program*/
首先,我們可以看到全局變量g_Var在文件TEST.C和MAIN.C中有引用,它的定義是在頭文件OS_uCOS_II.h 中,但是在定義的該變量用到了宏OS_EXT,但是該宏是否被"賦值"取決於是否定義OS_GLOBALS宏,也就是說OS_EXT是否被關鍵字extern替代是有條件的;然後,我們可以看到OS_GLOBALS宏在文件OS_uCOS_II.c中被定義,並且該文件包含了頭文件OS_uCOS_II.h,根據include替代原則,頭文件OS_uCOS_II.h所有信息將被包含進來,OS_GLOBALS被首先定義,那麼OS_EXT就被定義為空,也即全局變量g_Var在該文件中被定義。同理可分析TEST.c文件和MAIN.c文件,由於這兩個源文件只是包含頭文件OS_uCOS_II.h,而沒有#define OS_GLOBALS,因此OS_EXT將被關鍵字extern 替代,全局變量就只是作為聲明出現。因此也就不會出現多次定義的連接錯誤。廢話了這麼多,其實就是條件編譯語句的作用。
在移植到STM32/keil/ucos 2.9中,並沒有包含<ucos_ii.c>直接沒有編譯,直接在os_core.c定義, 在"includes.h"中則包含了"ucos_ii.h、""os_cpu.h"、"os_cfg.h"。
1
2
3
4
#ifndef OS_MASTER_FILE
#define OS_GLOBALS //這裡是重點,只有這個C文件來定義變量
#include "includes.h"
#endif
而其他C文件則是這樣的
1
2
3
#ifndef OS_MASTER_FILE
#include "ucos_ii.h"
#endif
對函數的"定義"和"聲明"不是一回事。"定義"是指對函數功能的確立,包括指定函數名,函數值類型、形參類型、函數體等,它是一個完整的、獨立的函數單位。而"聲明" 的作用則是把函數的名字、函數類型以及形參類型、個數和順序通知編譯系統,以便在調用該函數時系統按此進行對照檢查(例如函數名是否正確,實參與形參的類 型和個數是否一致)。從程序中可以看到對函數的聲明與函數定義中的函數首部基本上是相同的。因此可以簡單地照寫已定義的函數的首部,再加一個分號,就成為 了對函數的"聲明"。在函數聲明中也可以不寫形參名,而只寫形參的類型。 在C語言中,函數聲明稱為函數原型(function prototype)。它的作用主要是利用它在程序的編譯階段對調用函數的合法性進行全面檢查。
說明:
<1> 以前的C版本的函數聲明方式不是采用函數原型,而只是聲明函數名和函數類型。
如:float add(); 不包括參數類型和參數個數。系統不檢查參數類型和參數個數。新版本也兼容這種用法,但不提倡這種用法,因為它未進行全面的檢查。
<2> 實際上,如果在函數調用前,沒有對函數作聲明,則編譯系統會把第一次遇到的該函數形式(函數定義或函數調用)作為函數的聲明,並將函數類型默認為int 型。如一個max函數,調用之前沒有進行函數聲明,編譯時首先遇到的函數形式是函數調用"max(a, b)",由於對原型的處理是不考慮參數名的,因此系統將max()加上int作為函數聲明,即int max(); 因此不少教材說,如果函數類型為整型,可以在函數調用前不必作函數聲明。但是使用這種方法時,系統無法對參數的類型做檢查。或調用函數時參數使用不當,在 編譯時也不會報錯。因此,為了程序清晰和安全,建議都加以聲明為好。
<3> 如果被調用函數的定義出現在主調函數之前,可以不必加以聲明。因為編譯系統已經先知道了已定義的函數類型,會根據函數首部提供的信息對函數的調用作正確性檢查。
<4> 如果已在所有函數定義之前,在函數的外部已做了函數聲明,則在各個主調用函數中不必對所調用的函數再作聲明。http://blog.csdn.net/zhongguoren666/article/details/8477908
進入函數前保存環境變量和返回地址,進入函數是保存實參的拷貝,函數體內保存局部變量。
默認函數輸入參數的入棧順序是函數原型中形參從右至左的原則。
參考文獻:01_C語言的函數調用過程
02_C函數的調用規范
高質量程序設計指南 P101
C對齊不進行嚴格的靜態類型安全檢查(不檢查參數類型)
局部變量默認為 auto類型,除非用static 或者register 限定。
全局變量默認為 static,如果沒有使用extern 只能在本編譯單元內使用。
Auto 、register 只能限定局部變量和常量,搞不懂register這個寄存器變量。
假如這麼定義:register code char a=0x01;
Static 和 全局變量不同的地方在於作用域
遞歸舉例:2分法查詢已經排序的數組
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int Find(int *ary,int index,int len,int value)
{
if(len==1)//最後一個元素
{
if (ary[index]==value)return index;//成功查詢返回索引
return -1;//失敗,返回-1
}
//如果長度大於1,進行折半遞歸查詢
int half=len/2;
//檢查被查值是否大於上半部分最後一個值,如果是則遞歸查詢後半部分
if(value>ary[index+half-1])
return Find(ary,index+half,half,value);
//否則遞歸查詢上半部分
return Find(ary,index,half,value);
}
迭代舉例:1到100 求和
1
2
3
4
5
int v=1;
for(i=2;i<=100;i++)
{
v=v+i;
}
斷言assert是宏定義,在debug 階段使用,用來檢查參數的有效性。一般在函數的入口處,檢查參數是否有效。
斷言是捕捉非法狀況,而非錯誤。例如在 malloc 中可能返回 NULL,這是錯誤,但不非法。
跟蹤語句 tracer 是指示作用,與斷言不同
指針修飾,則賦值時只能也是用 const char *a =getstr();//沒意義個人覺得
值修飾,避免這種 getstr()=100;//基本也沒必要
指針初始化時,"="的右操作數必須為內存中數據的地址,不可以是變量,也不可以直接用整型地址值(但是int*p=0;除外,該語句表示指針為空指針,等價int*p=NULL)。此時,*p只是表示定義的是個指針變量,並沒有間接取值的意思。不確定的指針最好開始定義為NULL,使用的時候先判斷是否為空。
Int *p =(int*)0x12345;
Char * p ="abcde";
1
2
3
4
5
6
7
8
9
10
11
int a=100;
int b=0;
int * ptr_a=&a;
//括號必須加,否則的話=(char*)(ptr_a++)
((char *)ptr_a)++;
printf("&a=%p\n",&a);//&a=0028FF44
//ptr_a=0028FF45
printf("((char *)ptr_a)++=%p \n",ptr_a);
(char *)ptr_a++;//地址+4
//ptr_a=0028FF45+4
printf("((char *)ptr_a)++=%p \n",ptr_a);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, char *argv[])
{
int a=100,b=0;
int * ptr_a=&a;
b=* ptr_a++;
//因為 * 與 ++ 同優先級,並且是右結合
//所以= b=*(ptr_a++);
//++ 是後計算的,所以先b=*a;再ptr_a++
//ptr_a size=4
printf("&a=%p\n",&a);
printf("ptr_a=%p\n",ptr_a);
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char *argv[])
{
int a=100,b=0;
int * ptr_a=&a;
b=(* ptr_a)++;
//1.取ptr_a =100
//2.b=(*ptr_a)
//3.(* ptr_a)++,a+=1;
printf("&a=%p\n",&a);
printf("ptr_a=%p\n",ptr_a);
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0;
}
Int a[10]= int * const a;
Int b[3][4]=int (*const b)[4]
Int c[3][4][5]=int (*const c)[4][5]
同時,下面這四種方式定義等價,n=3
a[i][j]
*(a[i]+j)
(*(a+i))+j
*(*(a+i)+j) =è ((a+i*sizeof(int)*n)+j*sizeof(int))
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
按位存儲,節省空間,配合聯合體使用還可以提取某幾位。 語法: 位域名:長度
1
2
3
4
5
6
7
8
9
10
11
union wy
{
struct
{
unsigned char x1:2;
unsigned char x2:2;
unsigned char x3:2;
unsigned char x4:2;
}cn;
unsigned char s;
} tmp;
共用內存,不產生開銷,只是解釋方式不一樣,可用作大小端解析。
聲明:Enum week{sum,mon,tue,wed,thu,fri=7,sat};// sat=8,枚舉定義 week mytoday=sat;
匿名枚舉可做const , 如 enum {A1 =0x10;,A2=0x20},可替代宏定義也。
1
2
3
4
5
6
7
8
9
10
11
//#define length =sizeofof(int)
//結構大小=length+length*(num/length)//四入五也入
//=4+16=20
struct abc_class
{ enum num_class
{
num=13,
num1=9
}qq;
char mm[num];
}abc_1;
1
2
3
4
5
6
7
8
9
//結構大小=num=13
struct abc_class
{ enum num_class
{
num=13,
num1=9
};//只是聲明,不占空間
char mm[num];
}abc_1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void Uart_Printf(char *fmt,...)
{
va_list ap;
char string[256];
va_start(ap,fmt);
vsprintf(string,fmt,ap);
Uart_SendString(string);
va_end(ap);
}
void Uart_SendString(char *pt)
{
while(*pt)
Uart_SendByte(*pt++);
}
void Uart_SendByte(int data)
{
if(data=='\n')
{
while(!(rUTRSTAT0 & 0x2));
WrUTXH0('\r');
}
while(!(rUTRSTAT0 & 0x2)); //Wait until THR is empty.
WrUTXH0(data);
}
#include用於包含頭文件,有兩種形式:#include <xxx.h>,#include "xxx.h"。
尖括號形式表示被包含的文件在系統目錄中。如果被包含的文件不一定在系統目錄中,應該用雙引號形式。在雙引號形式中可以指出文件路徑和文件名。如果在雙引號中沒有給出絕對路徑,則默認為用戶當前目錄中的文件,此時系統首先在用戶當前目錄中尋找要包含的文件,若找不到再在系統目錄中查找。
對於用戶自己編寫的頭文件,宜用雙引號形式。對於系統提供的頭文件,既可以用尖括號形式,也可以用雙引號形式,都能找到被包含的文件,但顯然用尖括號形式更直截了當,效率更高。
./表示當前目錄,../表示當前目錄的父目錄。
宏定義的作用一般是用一個短的名字代表一個長的代碼序列。宏定義包括無參數宏定義和帶參數宏定義兩類。宏名和宏參數所代表的代碼序列可以是任何意義的內容,如類型、常量、變量、操作符、表達式、語句、函數、代碼塊等。但要尤其注意的是宏名和宏參數必須是合法的標識符,其所代表的內容及意義在宏展開前後必須一直是獨立且保持不變的,不能分開解釋和執行(括號的意義-layty)。
無參數宏定義。用一個用戶指定的稱為宏名的標識符來代表一個代碼序列,這種定義的一般形式為#define 標識符 代碼序列。其中#define之後的標識符稱為宏定義名(簡稱宏名),在宏定義#define之前可以有若干個空格、制表符,但不允許有其它字符,宏名與代碼序列之間用空格符分隔。
帶參數宏定義。帶參數宏定義進一步擴充了無參數宏定義的能力,這時的宏展開既進行宏名的替換又進行宏參數的替換。帶參數的宏定義的一般形式為#define 標識符(參數表) 代碼序列,其中參數表中的參數之間用逗號分隔,在代碼序列中必須要包含參數表中的的參數。在定義帶參數的宏時,宏名與左圓括號之間不允許有空白符,應緊接在一起,否則變成了無參數的宏定義。帶參數宏調用提供的實在參數個數必須與宏定義中的形式參數個數相同。
宏定義的有效范圍稱為宏名的作用域,宏名的作用域從宏定義的結束處開始到其所在的源代碼文件末尾。宏名的作用域不受分程序結構的影響。如果需要終止宏名的作用域,可以用預處理指令#undef加上宏名。
宏名一般用大寫字母,以便與變量名區別。如有必要,宏名可被重復定義,被重復定義後,宏名原先的意義被新意義所代替。
宏定義代碼序列中必須把""配對,不能把字符串""拆開。例如#define NAME "vrmozart不合法,應為#define NAME "vrmozart"。
宏定義代碼序列中可以引用已經定義的宏名,即宏定義可以嵌套。
宏定義在源文件中必須單獨另起一行,換行符是宏定義的結束標志,因此宏定義以換行結束,不需要分號等符號作分隔符。如果一個宏定義中代碼序列太長,一行不夠時,可采用續行的方法。續行是在鍵入回車符之前先鍵入符號\,注意回車要緊接在符號\之後,中間不能插入其它符號,當然代碼序列最後一行結束時不能有\。注意多行宏在調用時只能單獨一行調用,不能用在表達式中或作為函數參數。
預處理器在處理宏定義時,會對宏進行展開(即宏替換)。宏替換首先將源文件中在宏定義隨後所有出現的宏名均用其所代表的代碼序列替換之,如果是帶參數宏則接著將代碼序列中的宏形參名替換為宏實參名。宏替換只作代碼字符序列的替換工作,不作任何語法的檢查,也不作任何的中間計算,一切其它操作都要在替換完後才能進行。如果宏定義不當,錯誤要到預處理之後的編譯階段才能發現。
源代碼中的宏名和宏定義代碼序列中的宏形參名必須是標識符才會被替換,即只替換標識符,不替換別的東西,像注釋、字符串常量以及標識符內出現的宏名或宏形參名則不會被替換。例如:
#define NAME vrmozart,
源代碼//NAME、/*NAME*/、"NAME"、my_NAME_blog中的宏名都不會被替換。
#define BLOG(name) my_name_blog="name",宏定義代碼序列中的宏形參名name也都不會被替換。
如果希望宏定義代碼序列中標識符內出現的宏形參名能夠被替換,可以在宏形參名與標識符之間添加連接符##,在宏替換過程中宏形參名和連接符##一起將被替換為宏實參名。##用於把宏參數名與宏定義代碼序列中的標識符連接在一起,形成一個新的標識符。例如:
#define BLOG(name) my_##name,BLOG(vrmozart)表示my_vrmozart
#define BLOG(name) name##_ blog,BLOG(vrmozart)表示vrmozart_ blog
#define BLOG(name) my_##name##_blog,BLOG(vrmozart)表示my_vrmozart_ blog
如果希望宏定義代碼序列中的宏形參名被替換為宏實參名的字符串形式(即在宏實參名兩端加雙引號"),而不是替換為宏實參名,可以在宏定義代碼序列中的宏形參名前面添加符號#。#用於把宏參數名變為一個字符串形式。例如:
#define STR(name) #vrmozart,STR(vrmozart)表示"vrmozart"
當宏參數是另一個宏的時候,需要注意的是宏定義代碼序列中有用#或##的宏參數是不會再展開。
#define ECHO(s) (get(s), puts(s))
ECHO(str); /* 替換為 (gets(str), puts(str)); */
#define f (x) ((x)-1)
解析成為 f代表了,變成了一個無參數的宏
(x) ((x)-1)
但是在宏定義的調用中,空格不影響,也就是說f(3)與f (3)效果是一樣的都=2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define abs(x) x>0?x:-x
//當處理 abs(a-b),第二個-a-b明顯出錯了,所以參數需要用括號包含起來
abs(a-b)---> a-b>0?a-b:-a-b
//同樣的,返回值也需要括號,如下
abs(a)+1---> a>0? a:-a+1
//當參數和返回值都有括號,還是可能出錯
#define max(a,b) ((a)>(b)):(a):(b))
//當參數被調用兩次的時候考慮表達式是否會被調用兩次或者變化
biggest=x[0];
i=1;
while(i<n)
biggest=max(biggest,x[i++])
//解析成如下,這裡i多加了一次,出錯
biggest=((biggest)>(x[i++]):(biggest),(x[i++]))
//更正為如下:
biggest=x[0];
for(i=1;i<n;i++)
biggest=max(biggest,x[i])
解決max版本,使用局部變量,當前嵌套的話還是不行的
static int max_temp1,max_temp2;
#define max(p,q) (max_temp1=(p),max_temp2=(q),\
max_temp1>max_temp2?max_temp1:max_temp2)
Max(a, Max(b, Max(c)))
當宏放在 語句中,需要考慮其是否真的按照本意執行
//第一次定義
#define assert(e) if(!e) assert_error(__FILE__,__LINE__)
//嘗試如下判斷
if(x>0 && y>0)
assert(x>y);
else
assert(y>x);
//看上去沒啥子問題,展開一下
if(x>0 && y>0)
if(!(x>y)) assert_error(__FILE__,__LINE__);
else
if(!(y>x)) assert_error(__FILE__,__LINE__);
//還是沒問題,仔細配對下 if else
if(x>0 && y>0)
if(!(x>y))
assert_error(__FILE__,__LINE__);
else
if(!(y>x)) assert_error(__FILE__,__LINE__);
//如果給assert 加上{}
#define assert(e) {if(!e) assert_error(__FILE__,__LINE__)}
//編譯會報錯,因為展開後是這樣的,else 前面多了個;
if(x>0 && y>0)
{if(!(x>y)) assert_error(__FILE__,__LINE__);};
else
{if(!(y>x)) assert_error(__FILE__,__LINE__);};
//所以最終運用了 || 的優先級,如果前面的條件成立了,則後面的不執行
#define assert(e) \
((void) ( (e) || assert_error(__FILE__,__LINE__) ))
這裡也可以使用 do { }while(0) 包含語句 參考 <C語言中的宏定義CSDN知識庫>
http://blog.csdn.net/tjxtujingxuan/article/details/40628611
#define T1 struct foo*;
typedef struct foo *T2;
//類型上,T1和T2 完全相同,但是當同時定義多個變量
T1 a,b;
T2 a,b;
//T1
stuuct foo *a ,b; //一個指針,一個結構體
--------------------------------------------以上摘自C陷阱與缺陷
# 替換宏為字符串
## 連接兩個標識符
首先用實參替換形參,將實參代入宏文本中;
然後如果實參也是宏,則展開實參;
最後再繼續處理宏替換後的宏文本,若宏文本也包含宏則繼續展開,否則完成展開。
但是有個例外,那就是第一步後,將實參代入宏文本後,實參之前如果遇到字符"#"或"##",即使實參是宏,也不再展開實參,而是當作文本處理。
#define STRING(x) #x #x #x // STRING(abc)…………>"abcabc"
#defien TEXT(x) "class" #x "info" //TEXT(abc)………..>"classabcinfo"
#define CLASS(name) class##name // CLASS(sys)------->classsys
#defien ME(x,y) x##y##x //ME(me,to)-------->metome
符合ANSI的預定義宏:
__DATE__:表示當前源文件編譯時的日期,格式為:月/天/年(Mmm dd yyyy)。
__FILE__:表示當前正在處理的源文件名稱。
__LINE__:表示當前正在處理的源文件的行,可以用#line指令修改。
__STDC__:表示是ANSI C標准。只有在編譯器選項指定了/Za,並且不是編譯C++程序時,被定義為常整數1;否則未定義。
__TIME__:表示當前源文件的最近編譯時的時間,格式為:小時/分/秒(hh:mm:ss)。
__TIMESTAMP__:表示當前源文件的最近修改日期和時間,格式為:Ddd Mmm dd hh:mm:ss yyyy,其中Ddd是星期的縮寫。
Microsoft相關的宏:
_ATL_VER:定義了ATL的版本。
_CHAR_UNSIGNED:設置默認的char類型是unsigned的。只有在編譯器選項/J指定時才有定義。
__CLR_VER:指定了應用程序編譯時的通用語言運行時(CLR)的版本。格式為:Mmmbbbbb,其中M是CLR的主版本,mm是CLR的次版本,bbbbb是build號。
__cplusplus_cli:只有在用/clr,/clr:pure或/clr:safe編譯時才有定義。__cplusplus_cli的值是200406。
__COUNTER__:為一個整數,從0開始,每出現一次,其值增加1。可以使用__COUNTER__作為前綴來產生唯一的名字。
__cplusplus只有在編譯C++程序時才有定義,一般用於區分C程序和C++程序。
_CPPLIB_VER:在程序中如果包含了任意C++標准庫頭文件,則_CPPLIB_VER有定義。用於顯示正在使用的頭文件的版本。
_CPPRTTI:用於標識編譯器是否指定了RTTI。如果編譯器選項中設定了/GR(打開運行時類型信息機制-RTTI),則_CPPRTTI有定義。
_CPPUNWIND:用於標識編譯器是否打開異常處理。如果編譯器選項中設定了/GX,則_CPPRTTI有定義。
_DEBUG:用於標識是Debug模式。在編譯器指定了/LDd,/MDd或/MTd時才有定義。
_DLL:當編譯器選項指定了/MD或/MDd(Multithread DLL)時才有定義。
__FUNCDNAME__:只有在函數內部才有效。返回該函數經編譯器修飾後的名字。如果編譯器選項中設定了/EP或/P,則__FUNCDNAME__是未定義。
__FUNCSIG__:只有在函數內部才有效,並且返回該函數的簽名。一個函數的簽名由函數名、參數列表、返回類型、內含的命名空間組成。如果它是一個成員函數,它的類名和const/volatile限定符也將是簽名的一部分。在64位操作系統中,__cdecl是默認的函數調用方式。如果編譯器選項中設定了/EP或/P,則__FUNCSIG__是未定義。
__FUNCTION__:只有在函數內部才有效。返回該函數未經修飾的名字。如果編譯器選項中設定了/EP或/P,則__FUNCTION__是未定義。
_INTEGRAL_MAX_BITS:表示整數類型的最大位數(bits)。
_M_ALPHA:為DEC ALPHA平台定義。(現在已不支持)
_M_CEE:當使用/clr的任意形式(/clr:oldSyntax, 例如/clr:safe)編譯時被定義。
_M_CEE_PURE:當使用/clr:pure編譯時被定義。
_M_CEE_SAFE:當使用/clr:safe編譯時被定義。
_M_IX86:為x86處理器架構定義。當值為300時說明是80386,值是400時說明是80486
_M_IA64:為Itanium處理器家族的64位處理器(IA64)架構定義。
_M_IX86_FP:表示編譯器選項/arch的值。0:/arch未指定;1:指定/arch:SSE;2:指定/arch:SSE2
_M_MPPC:為Power Macintosh平台定義。(現在已不支持)
_M_MRX000:為MIPS平台定義。(現在已不支持)
_M_PPC:為PowerPC平台定義。(現在已不支持)
_M_X64:為x64處理器架構定義。
_MANAGED:當編譯器選項指定/clr時定義,其值為1。
_MFC_VER:指定MFC版本。例如:0x0700表示MFC version 7。
_MSC_BUILD:表示編譯器版本號的修訂號部分。修訂號是以時期進行分割的版本號的第四部分。例如:如果VC++編譯器的版本號是15.00.20706.01,則_MSC_BUILD的值為1。
_MSC_EXTENSIONS:當指定編譯器選項/Ze(默認)時有定義,其值為1。
_MSC_FULL_VER:表示編譯器的主,次版本號及build號。主版本號是整個版本號的第一部分,次版本號是整個版本號的第二部分,build號是整個版本號的第三部分。例如: 如果VC++編譯器的版本號是15.00.20706.01,則_MSC_FULL_VER的值為150020706。可以在命令行鍵入cl /?來查看編譯器的版本號。
_MSC_VER:表示編譯器的主,次版本號。例如: 如果VC++編譯器的版本號是15.00.20706.01,則_MSC_VER的值為1500。
__MSVC_RUNTIME_CHECKS:當指定編譯器選項/RTC之一(/RTCs或/RTCu或/RTC1)時有定義。
_MT:當指定編譯器選項/MD或/MDd(Multithreaded DLL)或/MT或/MTd(Multithreaded)時有定義。
_NATIVE_WCHAR_T_DEFINED:當指定編譯器選項/Zc:wchar_t(將wchar_t視為內置類型)時有定義。
_OPENMP:當指定編譯器選項/openmp時有定義,返回一個表示Visual C++中的OpenMP的日期的整數。
_VC_NODEFAULTLIB:當指定編譯器選項/Zl(忽略默認庫名)時有定義。
_WCHAR_T_DEFINED:當指定編譯器選項/Zc:wchar_t或工程中包含的系統頭文件中定義了wchar_t時有定義。
_WIN32:為Win32和Win64應用程序定義。總有定義。
_WIN64:為Win64應用程序定義。
_Wp64:當指定編譯器選項/Wp64時有定義。
① #line
#line指令用於重新設定當前由__FILE__和__LINE__宏指定的源文件名字和行號。
#line一般形式為#line number "filename",其中行號number為任何正整數,文件名filename可選。#line主要用於調試及其它特殊應用,注意在#line後面指定的行號數字是表示從下一行開始的行號。
② #error
#error指令使預處理器發出一條錯誤消息,然後停止執行預處理。
#error 一般形式為#error info,如#error MFC requires C++ compilation。
③ #pragma
#pragma指令可能是最復雜的預處理指令,它的作用是設定編譯器的狀態或指示編譯器完成一些特定的動作。
#pragma一般形式為#pragma para,其中para為參數,下面介紹一些常用的參數。
#pragma once,只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次。
#pragma message("info"),在編譯信息輸出窗口中輸出相應的信息,例如#pragma message("Hello")。
#pragma warning,設置編譯器處理編譯警告信息的方式,例如#pragma warning(disable:4507 34;once : 4385;error:164)等價於#pragma warning(disable:4507 34)(不顯示4507和34號警告信息)、#pragma warning(once:4385)(4385號警告信息僅報告一次)、#pragma warning(error:164)(把164號警告信息作為一個錯誤)。
#pragma comment(…),設置一個注釋記錄到對象文件或者可執行文件中。常用lib注釋類型,用來將一個庫文件鏈接到目標文件中,一般形式為#pragma comment(lib,"*.lib"),其作用與在項目屬性鏈接器"附加依賴項"中輸入庫文件的效果相同。
Sub 設置代碼表格()
' author: code4101
' 設置代碼表格 宏
'
'
' 背景色為morning的配色方案,RGB為(229,229,229)
With Selection.Tables(1)
With .Shading
.Texture = wdTextureNone
.ForegroundPatternColor = wdColorAutomatic
.BackgroundPatternColor = 15066597
End With
.Borders(wdBorderLeft).LineStyle = wdLineStyleNone
.Borders(wdBorderRight).LineStyle = wdLineStyleNone
.Borders(wdBorderTop).LineStyle = wdLineStyleNone
.Borders(wdBorderBottom).LineStyle = wdLineStyleNone
.Borders(wdBorderVertical).LineStyle = wdLineStyleNone
.Borders(wdBorderDiagonalDown).LineStyle = wdLineStyleNone
.Borders(wdBorderDiagonalUp).LineStyle = wdLineStyleNone
.Borders.Shadow = False
.AutoFitBehavior (wdAutoFitContent) '自動調整大小
End With
With Options
.DefaultBorderLineStyle = wdLineStyleSingle
.DefaultBorderLineWidth = wdLineWidth050pt
.DefaultBorderColor = wdColorAutomatic
End With
' 段落無首行縮進,行間距為固定值12磅
With Selection.ParagraphFormat
.LeftIndent = CentimetersToPoints(0)
.RightIndent = CentimetersToPoints(0)
.SpaceBefore = 0
.SpaceBeforeAuto = False
.SpaceAfter = 0
.SpaceAfterAuto = False
.LineSpacingRule = wdLineSpaceExactly
.LineSpacing = 12
.KeepWithNext = False
.KeepTogether = False
.PageBreakBefore = False
.NoLineNumber = False
.Hyphenation = True
.FirstLineIndent = CentimetersToPoints(0)
.OutlineLevel = wdOutlineLevelBodyText
.CharacterUnitLeftIndent = 0
.CharacterUnitRightIndent = 0
.CharacterUnitFirstLineIndent = 0
.LineUnitBefore = 0
.LineUnitAfter = 0
.MirrorIndents = False
.TextboxTightWrap = wdTightNone
.AutoAdjustRightIndent = True
.DisableLineHeightGrid = False
.FarEastLineBreakControl = True
.WordWrap = True
.HangingPunctuation = True
.HalfWidthPunctuationOnTopOfLine = False
.AddSpaceBetweenFarEastAndAlpha = True
.AddSpaceBetweenFarEastAndDigit = True
.BaseLineAlignment = wdBaselineAlignAuto
End With
' 清除原有的段落底紋
Selection.ParagraphFormat.Shading.BackgroundPatternColor = wdColorAutomatic
End Sub
Sub 輸入連續數字()
' author: code4101
行數 = InputBox("請輸入代碼終止行數", "輸入行數", "50")
For i = 1 To 行數 - 1
Selection.TypeText Text:=i
Selection.TypeParagraph
Next
Selection.TypeText Text:=行數
End Sub