一、typedef
為現有類型創建一個新的名字, 使用最多的地方是創建易於記憶的類型名
typedef int size;此聲明定義了一個 int 的同義字,名字為 size
想看http://baike.baidu.com/view/1283800.htm
第12題:考查typedef類型定義,函數指針
例1:typedef int (*test) ( float * , float*)
test tmp;
tmp 的類型是
(a) 函數的指針,該函數以 兩個指向浮點數(float)的指針(pointer)作為參數(arguments)
Pointer to function of having two arguments that is pointer to float
(b) 整型
(c) 函數的指針,該函數以 兩個指向浮點數(float)的指針(pointer)作為參數(arguments),並且函數的返回值類型是整型
Pointer to function having two argument that is pointer to float and return int
(d) 以上都不是
另外一例:
typedef void(*ptr_tp_func)(int);/*它表示ptr_tp_func是函數指針,該函數接受一個int參數,返回值為void*/
ptr_tp_func signal(int, ptr_tp_func);/*表示signal是一個函數,接受兩個參數,一個int和一個ptr_tp_func,返回值是ptr_tp_func類型*/
二、按位運算等小技巧
1、統計i有多少位為1:for( ; i ; i&=i-1) k++;
2、宏寫出swap(x,y):
#define swap(x,y)
x=x+y; y=x-y; x=x-y;
3、一句實現判斷x是不是2的若干次冪:x&(x-1)?false:true;
4、實現左移循環移n位:a=(a<<n)|(a>>(8*sizeof(int)-n));
5、快速求一個數的7倍:X<<3-X
三、編譯宏
#define 定義宏
#undef 取消已定義的宏
#if 如果給定條件為真,則編譯下面代碼
#ifdef 如果宏已經定義,則編譯下面代碼
#ifndef 如果宏沒有定義,則編譯下面代碼
#elif 如果前面的#if給定條件不為真,當前條件為真,則編譯下面代碼
#endif 結束一個#if……#else條件編譯塊
#error 停止編譯並顯示錯誤信息
四、指針
1、 有10個指針的數組 int *p[10];
int (*p)[10]中p是一個指針。它的類型是:指向int x[10]這樣的一維數組的指針。
指向函數的指針,函數有一個整型參數並返回一個整數型 int (*p)(int);
一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數並返回一個整型數 int (*p[10])(int);
2、char a[9]="abcdefg"; char *p="abcdefg";
其它區別:1.指針保存數據的地址,如p保存的是常量字符串“abcdefg”地址,對p內容的修改是非法的 ;數組保存的是數據,對數組的內容可以直接修改。
五、限定符的用法
1、const
1)作用:a.告知參數的應用目的
b.使優化器產生緊湊的代碼
c.保護不希望被改變的參數:防止參數意外改變
2)例子:a. const int a; a是一整型常量
b. int const a; 同上
c. const int *a; 指針a指向的內容不可變
d. int * const a;指針a指向的內容可變,但指針不可變
e. int const * a const; 都不可變
面試的問題:
a.一個參數既可以是const又是volatile嗎?
b.一個指針可以是volatile嗎?
答:a.可以,例子:只讀的狀態寄存器,它是volatile因為可能被意想不到的改變,是const因為不希望程序試圖去改變它。
b.可以。一個例子是中斷服務子程序修改一個指向一個buffer的指針時。
此類考題記住volatile型變量可能隨時改變即可。
2、restrict
restrict關鍵字允許編譯器優化某部分代碼以更好地支持計算。它只能用於指針,表明該指針是訪問該對象唯一且初始的方式。
int ar[10];
int * restrict restar= (int *) malloc(10 * sizeof(int));
int * par= ar;
for (n=0; n<10; n++) {
par[n]+=5;
restar[n] +=5;
ar[n] *=2;
par[n] +=3;
restar[n] +=3;
}
restar是訪問它所指向的數據塊的唯一且初始的方式,編譯器可以把涉及restar的兩條語句替換成下面的語句,效果相同:
restar[n] +=8;/*可以進行替換*/
3、register
register關鍵字請求讓編譯器將變量a直接放入寄存器里面,以提高讀取速度,因為register變量可能不存放在內存中,在C語言中register關鍵字修飾的變量不可以被取地址,但是c++中進行了優化。
六、精度和優先級
1、類型不同的操作數運算,精度向低級自動轉換。無符號+有符號=無符號
2、.設 int a=12,則執行完語句 a+=a-=a*a后,a的值是-264.
所有的賦值符(包括復合賦值)都具有右結合性,就是在表達式中最右邊的操作最先執行,然后從右到左依次執行。
3、c中函數參數默認是從右向左壓棧的,printf計算參數時也是
int arr[]={6,7,8,9,10}; int *ptr=arr; *(ptr++)+=123;
printf("%d,%d\n",*ptr,*(++ptr));結果為8
即printf內的參數從右向左運算。
4、位運算要考慮機器字長,算術運算符優先級高於移位運算符
unsigned char = 0xA5; unsigned char b = ~a>>4+1; 則b為250.
5、三個float:a,b,c .問值 (a+b)+c==(b+a)+c 和(a+b)+c==(a+c)+b
不一定相等,float存在大數淹沒小數的情況,如
float a=100000000000,b=-100000000000,c=0.00000000001;
cout < <a+b+c < <endl;
cout < <a+c+b < <endl;
結果是1e-011和0;
盡量把數量級相近的數先相加
七、大小端問題
大端模式:是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中。arm、powerpc
小端模式:是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中。intelx86
大小端測試程序1:
int checkSystem( )
{union check
{int i; char ch;
}c; c.i=1; return (c.ch==1);
}
測試大小端的程序2:
在使用little endian的系統中 這些函數會把字節序進行轉換:
define HTONS(n) ((((u16_t)((n) & 0xff)) << 8) | (((n) & 0xff00) >> 8))
ltons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序
在使用big endian類型的系統中 這些函數會定義成空宏。
利用linux中自帶的宏進行判斷:
#if __BYTE_ORDER == __LITTLE_ENDIAN
// 小頭字節序
#elif __BYTE_ORDER == __BIG_ENDIAN
// 大字節序
八、內存
1、申請內存的函數
void *calloc ( size_t num_elements, size_t element_size );
void *realloc (void *ptr, size_t new_size );
realloc函數用於修改一個原先已經分配的內存塊的大小,可以使一塊內存的擴大或縮小。當起始空間的地址為空,即*ptr = NULL,則同malloc。calloc與malloc相比:calloc分配的內存被初始化為0,calloc兩個參數:元素個數,元素大小。
2、內存分布
BSS段:未初始化的全局變量和靜態變量;數據段:初始化的全局變量和靜態變量;代碼段(或文本段):可執行文件的指令;局部變量運行時創建並存儲於棧,函數結束即釋放;靜態變量和全局變量則在程序結束后釋放;可執行文件中包含BSS段所需要的大小,但不是BSS段,堆棧等在程序運行時創建
九、中斷
1、 找出下面一段ISR的問題。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
1)ISR不能傳遞參數。
2)ISR不能有返回值。
3)ISR應該短且有效率,在ISR中做浮點運算不明智。
4)ISR中不應該有重入和性能上的問題,因此不應該使用pintf()函數。
不可重入函數不可以在它還沒有返回就再次被調用。例如printf,malloc,free等都是不可重入函數。因為中斷可能在任何時候發生,例如在printf執行過程中,因此不能在中斷處理函數里調用printf,否則printf將會被重入。 函數不可重入大多數是因為在函數中引用了全局變量。例如,printf會引用全局變量stdout,malloc,free會引用全局的內存分配表。
十、鏈表
一個鏈表不知道頭結點,有一個指針指向其中一個結點,請問如何刪除這個指針指向的結點:將這個節點復制成下一個節點的值,然后刪除下一個節點
typedef struct LinkList {
int Element;
LinkList * next;
}LinkList;
初始化:
linklist * List_init(){
linklist *HeadNode= (linklist*)malloc(sizeof(linklist));
if(HeadNode == NULL) {
printf("空間緩存不足");
return HeadNode;
}
HeadNode->Element= 0;
HeadNode->next= NULL;
Return HeadNode;
}
創建鏈表:
void CreatList(linklist *HeadNode,int *InData,int DataNum)
{
int i = 0;
linklist *CurrentNode = (linklist*) HeadNode;
for(i = 0;i<DataNum;i++)
{
CurrentNode->Element = InData[i];
if(i< DataNum-1)// 由於每次賦值后需要新建結點,為了保證沒有多余的廢結點
{
CurrentNode->next =(linklist *)malloc(sizeof(linklist));
CurrentNode= CurrentNode->next;
}
}
CurrentNode->next= NULL;
}
插入節點:
bool InsertList(linklist *HeadNode,int LocateIndex,int InData){
int i=1;// 由於起始結點HeadNode是頭結點,所以計數從1開始
linklist *CurrentNode= (linklist *) HeadNode;
//將CurrentNode指向待插入位置的前一個結點(index -1)
while(CurrentNode&& i<LocateIndex-1){
CurrentNode= CurrentNode->next;
i++;
}
linklist *NodeToInsert=(linklist*)malloc(sizeof(linklist));
if(NodeToInsert == NULL){
printf("空間緩存不足");
return ERROR;
}
NodeToInsert->Element= InData;
NodeToInsert->next = CurrentNode->next;
CurrentNode->next = NodeToInsert;
return OK;
}
具體參考:https://blog.csdn.net/u012531536/article/details/80170893
十一、內聯函數
內聯函數(inline) |
帶參數的宏 |
編譯(匯編)時展開 |
預編譯時替換 |
嚴格的參數類型檢查 |
簡單的替換 |
函數體不能太大,不能包含大循環和遞歸 |
沒要求 |
當編譯器發現某段代碼在調用一個內聯函數時,它不是去調用該函數,而是將該函數的代碼,整段插入到當前位置。這樣做的好處是省去了調用的過程,加快程序運行速度。內聯函數適用於函數體較小且頻繁使用的函數,調用時進行代碼的復制,無需跳轉、入棧等操作,以空間換時間而且因為函數有嚴格的參數類型檢查,比宏要安全;內聯函數在編譯時不單獨產生代碼 。