#
這是編程之美之中的一道題,編程之美中的解法固然好,但自己手寫一遍還是印象深刻。話說,只有想不到,沒有做不到。
C語言版(關鍵在思路,更好的表示,特別是whoIsOk1的表示,可能編程之美上的做法更工業化,這里為了簡化說明問題,用了很多magic number):
/*
* chinese_chess_admiral_marshal.c
*
* Created on: 2008-9-14
* Author: Volnet
* WebSite: http://volnet.cnblogs.com/
* http://www.shnenglu.com/mymsdn/
*/
#include <stdio.h>
#include <stdlib.h>
/*
* admiral
* 1 2 3
* 4 5 6
* 7 8 9
*
* marshal
* 1 2 3
* 4 5 6
* 7 8 9
*
* who can't sit in the same column.
* e.g. if admiral in "1", the marshal can't in the "1"、"4"、"7".
* */
/*
* The five functions do the same thing.
* */
void whoIsOk1();
void whoIsOk2();
void whoIsOk3();
void whoIsOk4();
void whoIsOk5();
int main(void) {
whoIsOk1();
printf("-----------------------------------\n");
whoIsOk2();
printf("-----------------------------------\n");
whoIsOk3();
printf("-----------------------------------\n");
whoIsOk4();
printf("-----------------------------------\n");
whoIsOk5();
//printf("-----------------------------------\n");
return EXIT_SUCCESS;
}
void whoIsOk1() {
/*Normal one*/
unsigned int i, j, total;
total = 0;
for (i = 1; i <= 9; i++)
for (j = 1; j <= 9; j++)
if (i % 3 != j % 3) {
printf("A=%d,B=%d\n", i, j);
total++;
}
printf("total = %d\n", total);
}
/*
* i:iiii0000
* j:0000jjjj
* */
/* g:a variable who can contains 8 bits.
* */
#define iGET(g) ((g&0xf0)>>4)
#define jGET(g) (g&0x0f)
#define iSET(g,i) (g=(g&0x0f)|((i)<<4))
#define jSET(g,j) (g=(g&0xf0)|(j))
void whoIsOk2() {
/*only one variable except “total”*/
unsigned char ij;
/* for testing
ij = 0x53;
iSET(ij,3);
jSET(ij,5);
printf("%x\n%d\n%d\n",ij,iGET(ij),jGET(ij)); */
int total;
total = 0;
for (iSET(ij,1); iGET(ij) <= 9; iSET(ij,iGET(ij)+1))
for (jSET(ij,1); jGET(ij) <= 9; jSET(ij,jGET(ij)+1))
if (iGET(ij) % 3 != jGET(ij) % 3) {
printf("A=%d,B=%d\n", iGET(ij), jGET(ij));
total++;
}
printf("total = %d\n", total);
}
void whoIsOk3() {
/* The principle of the function is :
* get the number 19 from one variable.
* get the number 19 from the same variable.
* we know the variable i/9 would mutex with i%9.
* e.g.
* int i = 20;
* i/7 == 2;
* i%7 == 6;
*
* int j = 14;
* j/7 == 2;
* j%7 == 0;
*
* i - j == 6;
* if(k/7 from 14 to 20)
* k%7 would from 0 to 6 (total = 7);
* so we can get the double circle from one variable.
* */
unsigned int i = 81, total;
total = 0;
do {
if (i / 9 % 3 != i % 9 % 3) {
printf("A=%d,B=%d\n", i / 9 + 1, i % 9 + 1);
total++;
}
} while (i--);
printf("total = %d\n", total);
}
/*whoIsOk3 is nearly equals to whoIsOk4*/
void whoIsOk4() {
unsigned int i = 81, total;
total = 0;
while (i--) {
if (i / 9 % 3 != i % 9 % 3) {
printf("A=%d,B=%d\n", i / 9 + 1, i % 9 + 1);
total++;
}
}
printf("total = %d\n", total);
}
typedef struct {
unsigned x :4;
unsigned y :4;
} complex_counter;
void whoIsOk5() {
/*use an struct scheme to mix two variables in one*/
complex_counter i;
i.x = i.y = 0;
unsigned int total;
total = 0;
for (i.x = 1; i.x <= 9; i.x++)
for (i.y = 1; i.y <= 9; i.y++)
if (i.x % 3 != i.y % 3) {
printf("A=%d,B=%d\n", i.x, i.y);
total++;
}
printf("total = %d\n", total);
}
值得注意的一點是形如#define jSET(g,j) (g=(g&0xf0)|(j))這樣的語句
為了避免二義性,也為了避免被宏替換后的操作符順序依賴,盡量對使用的變量單獨用()括起來,因為它不是一個字母在戰斗……-_-"
執行結果(片段):
A=1,B=2
A=1,B=3
A=1,B=5
A=1,B=6
A=1,B=8
A=1,B=9
A=2,B=1
A=2,B=3
A=2,B=4
A=2,B=6
A=2,B=7
A=2,B=9
A=3,B=1
A=3,B=2
A=3,B=4
A=3,B=5
A=3,B=7
A=3,B=8
A=4,B=2
A=4,B=3
A=4,B=5
A=4,B=6
A=4,B=8
A=4,B=9
A=5,B=1
A=5,B=3
A=5,B=4
A=5,B=6
A=5,B=7
A=5,B=9
A=6,B=1
A=6,B=2
A=6,B=4
A=6,B=5
A=6,B=7
A=6,B=8
A=7,B=2
A=7,B=3
A=7,B=5
A=7,B=6
A=7,B=8
A=7,B=9
A=8,B=1
A=8,B=3
A=8,B=4
A=8,B=6
A=8,B=7
A=8,B=9
A=9,B=1
A=9,B=2
A=9,B=4
A=9,B=5
A=9,B=7
A=9,B=8
total = 54
void changeString(char **t){
*t = "world";
}
void changeString2(char *t[]){
*t = "world2";
}
typedef char *String;
void changeString3(String *s){
*s = "world3";
}
void Inc(int *i){
(*i)++;
}
問題列表:
1、如何改變外部變量?
2、啥時候我們需要使用**?
前言:
先看Inc,我們知道int i是一個值類型,為了能夠達到修改它的目的,我們需要將i的實際地址通過值傳遞的方式從調用函數傳遞給被調用函數,因為對相同的地址的變量進行操作,所以我們的Inc(&i)將如我們所愿,順利地遞增。
以上兩個版本的changeString都是可以達到修改調用函數中的字符串的。如果按照下面的代碼將得到不正確的結果。
錯誤代碼示例:
void errorChangeString(char *t){
t = "change!";
}
int main(void){
char *s = "Hello";
errorChangeString(s);
return EXIT_SUCCESS;
}
在錯誤示例代碼中,假設傳遞的s則為指向字面值"Hello"的首字母'H'所在的地址值,假設這個值為0x1000。在errorChangeString中,假設"change"字面值的首字母'c'所在的地址值為0x2000,s被拷貝給了t,t的任何改動和s沒有任何關聯,因此,s仍然指向0x1000,而不是指向0x2000。
我們應如何看待char **t?
若要讓所謂的t的值能夠跟著s變化,我們則需要指向指針的指針來幫忙。我們知道要讓函數傳遞(C語言只有值傳遞)后,仍然指向相同的值(這里指s始終為0x4000(s指向0x1000,假設它自身地址為0x4000)),則我們需要將這個傳遞的值進行一次包裝,使我們通過形參也能夠控制相同的地址所在的變量(這里指0x4000),因此,我們對形參定義一個指針,形如 char* *t(等價于char **t),這樣*t與s就代表了相同的值而不會因為傳遞而無法操縱s,因此可以在被調用函數內部使用*t來指代要操作的外部變量(通過參數傳遞),因此在內部*t="world"(假設"world"的首地址為0x2000),則s的值就被修改為"world"的首地址。(如下圖所示)
我們應如何看待char *t[]?在我們的changeString2(char *t[])中,我們用char *t[]取代了char **t,我們知道char *t[]代表t是一個數組,數組的每一個成員都是一個char*類型的指針。我們也成為
指針數組。下面讓我們看一個調用:
void changeStrArr(char *t[]){
*t = "World";
}
int main(void){
char *sArr[] = {
"Hello"
};
printf("%s",*sArr);
changeStrArr(sArr);
printf("%s",*sArr); //printf("%s",sArr[0]);
return EXIT_SUCCESS;
}
這是教科書上比較常見的指針數組形式,甚至還會簡單不少(它們的數組通常會有多個元素并用*t++來控制移位)。sArr在這里就是這個數組,因此sArr[0]即為指向該數組第一個元素的指針(因為是指針數組,每一個元素都是一個指針),因此使用printf("%s",*sArr); 或者printf("%s",sArr[0]);都將標準輸出sArr的第一個元素所指向的字符串。
下面我們來看一下下面這段代碼:
void changeString2(char *t[]); //函數體見本文頂部
int main(void){
char *s="Hello";
printf("%s",s);
changeString2(&s);
printf("%s",s);
return EXIT_SUCCESS;
}
從這段代碼中我們主要講s換成了一個字符而不是上一段代碼中的字符指針數組sArr,從上一段代碼我們可以得知s和sArr之間的關系,*s==*sArr[0]==**sArr;(我們可以通過strcmp(q,qArr[0])或者strcmp(q,*qArr);進行判斷,我們知道strcmp(const char *_Str1, const char *_Str2);也就是我們傳遞的q和*qArr均為字符指針也就是它們的定義通常為char *q和char **qArr)。為此我們可以將其進行移項,也可以得到等價表達式(規律:==兩側同時添加相同符號等式依舊不變(在*和&的邏輯里成立),同時出現&*,兩符號起中和作用(先從一個地址中取值,再從值反求它的地址,因此最終結果還是地址本身))也就是&*s==&*sArr[0]==&**sArr <=> s==sArr[0]==*sArr,這樣,再進行一次,&s==&sArr[0]==&*sArr,也就是&s==&sArr[0]==sArr因此changeStrArr(sArr)<=>changeStrArr(&s),因此從上面的代碼段到下面代碼段的演化是成功的(changeString2和changeStrArr本質上沒有差別)。
下面的示例圖則從本質上分別分析了兩者的各自的理由(非上述推理):
用typedef char *String;改良后的程序具有更高的可讀性可以看到第三段代碼中我們在函數聲明前用typedef語句定義了typedef char *String;首先從typedef的本質來講,這種定義將導致使用它的changeString3與changeString函數具有相同的本質,但是從閱讀的習慣上來講,用String而不是用char *的方式,則顯得更加親切。首先我們從眾多起他語言中,比如C#中,C#實現了類型String/string的方式,我們知道String是一個引用類型,但我們同時也知道string類型有個顯著的特征,就是它雖然是引用類型,每次對它的操作總是像值類型一樣被復制,這時候,我們定義的任何(C#):ChangeString(string str);將不起作用,而我們需要增加ref關鍵字來告訴編譯器它是同一實例,而不進行重新申請空間重新分配等一系列復雜操作,于是ChangeString(ref string str);的語句就有類似值類型的一些地方了,同樣,在C語言中,changeString2(String *s)也達到了同樣的效果。這樣的方式也同時對我們更加了解第一種方式起到了輔助作用。(用C#來比喻可能不是太好,因為很多讀者通常都是先接觸C再有機會才接觸C#的,而且也沒有講解到本質)
void changeString3(String *s); //函數體見本文頂部
int main(void){
char *s="Hello";
printf("%s",s);
changeString3(&s);
printf("%s",s);
return EXIT_SUCCESS;
}
本質呢?因為任何一次的"Hello",其中的"Hello"是常量,而不是變量,它的存儲空間在編譯時就已經確定了,它放在了靜態常量區中,因此它的地址不會變也不能加減。因此String,也就是char *指向的是一個不可變的常量,而非變量。(例如我們一直假設char *s = "Hello",的首地址s==0x1000(s的值,不是s的地址),那么它始終是0x1000,但是s是變量,s可以拋棄0x1000指向別的字符串字面值(char literal),但是我們知道C語言中只有按值傳遞,因此我們必須用它的指針假設s的地址0x3000,那么,我們將0x3000進行傳遞,這樣內部就可以對0x3000進行操作了,這樣可以用(0x3000)->value的方式修改value指向0x2000的地址(假設這個地址是"GoodBye"的值),這樣我們的s就被修改了。因為我們的常量在編譯時就已經分配了地址,在程序加載后就長久存在,知道應用程序退出后會跟著宿主一并消失,所以我們同樣不需要free操作)。
下一個問題:
啥時候我們需要用到**?
通過以上的幾個直觀的示例,我們大體了解了一個字符串通過一個函數參數進行修改的具體情況。這是一個很發散性的問題,我也沒有一個很肯定的100%的答案。
從void **v;(//void代表任意類型,可以是int/char/struct node等)定義的本質上來觀察這個問題,我們可以推論void **v;,當我們需要獲取并改變*v的地址值得時候,我們都有這個需要(因為單從void *v的角度講,我們只能夠獲取v的地址改變v的值,但不能改變v的地址)。那我們什么需要獲取并改變*v的值呢?從上面的分析我們不難得出,我們在需要改變v的地址的時候即有這個需要。
下面是一個鏈表的例子:
#include <stdio.h>
#include <stdlib.h>
typedef struct node{
int value;
struct node *next;
} Node;
Node *createList(int firstItem){
Node *head = (Node *)malloc(sizeof(Node));
head ->value = firstItem;
head ->next = NULL;
return head;
}
void addNode(Node *head, Node **pCurrent,int item){
Node *node = (Node *)malloc(sizeof(Node));
node ->value = item;
node ->next = NULL;
(*pCurrent)->next=node;
*pCurrent = node;
}
typedef void (*Handler)(int i);
void foreach(Node *head, Handler Ffront, Handler Flast){
if(head->next!=NULL){
Ffront(head->value);
foreach(head->next,Ffront,Flast);
}
else
{
Flast(head->value);
}
}
void printfFront(int i){
printf("%d -> ",i);
}
void printfLast(int i){
printf("%dn",i);
}
int main(void){
Node *head, *current;
current = head = createList(0);
for(int i=1;i<10;i++)
addNode(head,¤t,i);
foreach(head, printfFront, printfLast);
return EXIT_SUCCESS;
}
//函數輸出
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
這個程序中的關鍵部分就是當前節點值current的確定,部分老師可能會圖方便采用全局變量進行當前值的確定,這個在拋棄型的示例中當然無傷大雅,也很好地描述了鏈表的本質,這本沒什么關系,但是鏈表是一個常用的數據結構,并發怎么辦?操作多個鏈表怎么辦?總之我們要秉承“方法共享,數據不共享的原則”,這樣就不太容易出現問題了。這里我們在main函數中定義了唯一的*head用于標識鏈表的頭,并希望它始終扮演鏈表頭的角色,不然我們最后將無法找到它了。我們用一個同樣類型的節點current指向了當前節點,并始終指向當前節點(隨著鏈表的移動,它將指向最后一個節點)。由于我們的current是主函數中定義的,而它的修改是在被調函數中進行的。因為我們需要改變的*current的值,根據我們的分析,對于要修改值的,我們有使用**的必要,而類似只需要讀取值的head,則沒有任何需要了。
這個程序代表了一種使用**的典型用法,也是大部分需要使用**的用法。
總結:
不論它怎么變化,怎么復雜,我們需要把握幾點:
1、C語言中,函數傳遞永遠是值傳遞,若需要按地址傳遞,則需要用到指針(類似func(void *v){...});
2、在對于需要變化外部值的時候,直接尋址的使用*,間接尋址的使用**;
3、對于復雜的表達式,善于使用嵌套的思路去分析(編譯器亦或如此),注意各符號之間的優先級。
這句是非常經典、簡潔的指針操作語句,但是它在gcc編譯器下居然會出現警告。
warning: suggest parentheses around assignment used as truth value
理由:在C語言中,非0即代表TRUE,反之為FALSE。上面的語句以*s的值用于最后的判斷。但是由于長期的編程實踐告訴我們,人們經常在“=”和“==”的使用上出現手誤,所以gcc編譯器為此要求我們明確地告訴編譯器它是“=”而不是“==”,是故意,而非手誤。
既然我們的語句如上所示,并非要用“==”,因此我們只需修改成下列樣式即可:
參考:
1、http://www.network-theory.co.uk/docs/gccintro/gccintro_94.html
2、http://darkmomo.blogspot.com/2008/05/suggest-parentheses-around-assignment.html
都說是MinGW4.0之后GDB并沒有被包含在安裝包之中,需要另外下載安裝。
話說原理很簡單,就是折騰那么個gdb.exe來,然后放在MinGW的bin目錄中,期間無外乎就是去把它下下來。說是如此簡單,但是到了http://www.gnu.org/software/gdb/download/后才發現,原來只提供源碼,要自己回家編譯。
話說編譯也不難,編譯器啥的都已經有了,按理上個MSYS(也有了)然后用./configure make就可以開始搞了……(一切如README所述)。但是往往好事多磨,最后總是會有Error跳出來攔路。我這個本來就是門外漢,沒有Error都已經很吃力了,有Error顯然吃不消。
話說這里找到一篇文章,圖文并茂,還說的很正點,可惜它所提到的軟件,在sourceForge.net上死活沒找到。
下了那個patch文件回來,看了里面的內容,貌似不像是更新,話說patch文件怎么用都不知道。趕快搜了幾下試了幾下,也沒成功。就直接把圖文并茂里面的圖上的關鍵字拿去“GDB 6.8 for MinGW Setup”搜索,在CSDN上找到了,話說昨天晚上也找到了,可惜昨晚那個網頁不靈光了,死活打不開。
后來上www.xunlei.com隨便搜了個GDB第一條就是了~瀑布汗~
下載地址(gdb-6.3.2)http://prdownloads.sourceforge.net/mingw/gdb-6.3-2.exe?download
改天一定把那個6.8給編譯出來……
在使用Eclipse的時候,一個非常優秀的功能就是能夠使用Spell checker對拼寫進行檢查。如果您沒有用過,或者您不太理解我說的,那你就回顧一下WORD里面那些令人看上去很不舒服的波浪線,雖然看著不舒服,可是它卻減少了很多錯誤的拼寫。
在有些版本中(網上有部分朋友和我一樣在使用CDT的時候出現了這個問題),可能會丟失用戶字典(Missing User Dictionary),如下圖:
在偏好設置中,我們能看到的spelling的選項內,拼寫引擎不存在。
這里介紹一個第三方拼寫檢查插件。在Help->Software Update中選擇Available Software選項卡。
點擊Add State...,添加下面網站的信息http://www.bdaum.de/eclipse/eSpell3鏈接,相關介紹見下面鏈接:http://www.eclipseplugincentral.com/Web_Links-index-req-viewlink-cid-85.html
點擊“Install”即可進行安裝了。(安裝過程需要連接互聯網。)

另人高興和雀躍的并不是這個拼寫檢查插件,它比想象中的要差勁,我似乎看到了滿頁的波浪線,雖然可能可以通過修改參數來使之更人性化,但是我還是覺得不如沒有它更好。默認的spelling居然生效了。剩下的內容可以參看http://www.javalobby.org/java/forums/t17453.html了。

作用域是一個在絕大多數(事實上我沒有見過沒有作用域概念的程序語言)程序語言的必然組成部分。日常良好的使用使用習慣和程序設計風格,有時候讓我們漸漸忘記了這些作用域的準確定義,因為它通常沒給我們帶來什么麻煩。這里我將其寫下來,僅在勾起大家模糊回憶中清晰的輪廓,也許有你所知的,也許有你所惑的,又或者可能我的理解和你的理解甚至格格不入,煩請大家積極指正。
幾個概念:
a.外部變量,許多程序設計的書上用“全局變量”一詞,這里做一個統一,本文中“外部變量”就是所謂的“全局變量”。
b.自動變量,許多程序設計的書上用“局部變量”一詞,這里做一個統一,本文中“自動變量”就是所謂的也可以稱作“動態局部變量”,與此相對的還有“靜態局部變量”,這兩種局部變量的并集就是通常所說的“局部變量”了。
這兩種叫法都是有其特定的道理和闡述的意義的。局部變量就是直言了其作用的范圍,它是局部的可見的(這一點您應該深有體會)。因為在一個函數內聲明一個變量,形如int a;它從它的聲明式開始,直到該函數的末尾(以“}”符號所標識)有效。在函數體的外部,該變量a不存在并且無效。這樣的聲明事實上是auto int a;聲明式的一種省略寫法,因此稱之為“自動變量”,因為它在該函數調用開始的時候初始化內存空間,在調用結束的時候將釋放該函數的內存空間,而這一切不需要人工的干預,因此它是自動的。
c.靜態變量,又稱之為“靜態局部變量”,它通常與auto int a;的形式相反,它采用了static關鍵字進行修飾,static int a;。這樣的聲明導致了它在編譯時就分配了內存空間,并在整個程序的運行過程中持續有效。唯一的限制是它僅在它所在的函數范圍內有效。
1、外部變量的作用域從它聲明的位置開始,到其所在(待編譯)的文件的末尾結束。
參看以下代碼:
#include <stdio.h>
#include <stdlib.h>
void extern1Processor(void);
int main(void){
/*下面的語句因為extern1Variable變量的定義在main函數的后面
* 因此該函數將產生編譯錯誤。
* extern1Variable' undeclared (first use in this function)
printf("main.extern1Variable:%d\n",extern1Variable);*/
extern1Processor();
return EXIT_SUCCESS;
}
int extern1Variable = 2003;
/*下面能夠正確使用extern1Variable變量,因為extern1Variable的
* 定義在extern1Processor(void);方法之上。*/
void extern1Processor(void){
printf("extern1Processor.extern1Variable:%d\n",extern1Variable);
}
2、如果要在外部變量的定義之前使用該變量,則必須在相應的變量聲明中強制地使用關鍵字extern。
參看以下代碼:
#include <stdio.h>
#include <stdlib.h>
void extern2Processor(void);
int main(void) {
extern int extern2Variable;
printf("extern2Processor.extern2Variable:%d\n", extern2Variable);
extern2Processor();
return EXIT_SUCCESS;
}
int extern2Variable = 2004;
void extern2Processor(void) {
printf("extern2Processor.extern2Variable:%d\n", extern2Variable);
}
輸出結果:
extern2Processor.extern2Variable:2004
extern2Processor.extern2Variable:2004
3、如果外部變量的定義與變量的使用不在同一個源文件中,則必須在相應的變量聲明中強制地使用關鍵字extern。
假設在不同的源文件file1.c與file2.c中,我們都需要定義一個變量int aVariable = 2;時,分別編譯二者,它們將都包含一個變量aVariable的聲明和定義。但是當我們將它們一起加載的時候,由于它們都是外部變量,相同的變量名導致編譯器不知道它們的主次關系。因此,這里我們要求程序員一定要用extern將主次分出來。比如file1.c中int aVariable = 2;這句話聲明了aVariable的類型為int,為它分配了sizeof(int)內存大小的一塊內存空間,它的值為2。在file2.c中我們聲明它extern int aVariable;(這里不能使用extern int aVariable=3;但是可以使用int aVariable;)這句話告訴了編譯器aVariable不是我這個源文件中進行聲明的,它來自外部(一個未知的位置)。這樣在單獨編譯該文件gcc -c file2.c的時候就不會因為缺失聲明式而引發編譯錯誤了。
有同學認為這里的int aVariable;是聲明,不是定義,這是一種錯誤的觀點。在外部變量中,形如:
file1.c file2.c
---------------------------------------------------------------
int aVariable = 3; int aVariable;
int main(){
……
}
其中的int aVariable;定義了外部變量aVariable,并為之分配了存儲單元。
這同時也成為了外部變量和靜態變量必須是常量表達式,且只初始化一次的理由。如果我們對兩邊都進行初始化(定義),編譯器將不知道讓誰成為主要初始化的值。事實上,外部變量和靜態變量的值初始化過程是在編譯時進行的,它們的值都被放在靜態存儲區,也就是我們慣常在匯編中的DATA SEGMENT部分,因此它們必須是常量表達式,并且有且只有初始化一次,否則我們將可能寫出類似這樣的語句(而這樣的語句本身就是錯誤的):
DATA SETMENT
INFO1 "INFOMATION1"
"INFOMATION2" ;這樣的定義是不允許的
DATA ENDS
……
按照概念extern通常被看作是外部的,因此通常情況下初始化操作一般是在無extern的聲明式后的,若在extern一邊進行初始化,則有違常理(主次不分了)。但是由于將初始化步驟僅放在extern一邊滿足只初始化一次的原則,因此編譯不會出錯,但是根據不同的編譯器可能會給出警告提示。
參看以下代碼:
包含main的源文件:
#include <stdio.h>
#include <stdlib.h>
void extern3Processor(void);
int main(void) {
extern3Processor();
return EXIT_SUCCESS;
}
extern int extern3Variable;
void extern3Processor(void) {
printf("extern3Processor.extern3Variable:%d\n", extern3Variable);
}
externFile.c文件:(這個文件很簡單,就包含了一個外部變量的定義)
int extern3Variable = 2005;
編譯多個文件如下:(將.c文件編譯為.o文件,再將這幾個.o文件一起加載到.exe文件中(UNIX中通常為.out文件),通常在對個別文件作出修改后,我們只需要重新編譯那個文件,然后將這些新舊.o文件一起加載到.exe文件中即可。)
gcc -O0 -g3 -Wall -c -fmessage-length=0 -osrc\EffectiveArea.o ..\src\EffectiveArea.c
gcc -O0 -g3 -Wall -c -fmessage-length=0 -osrc\externFile.o ..\src\externFile.c
gcc -oEffectiveArea.exe src\externFile.o src\EffectiveArea.o
輸出結果:
extern3Processor.extern3Variable:2005
4、之前提到的文字中包含“聲明”和“定義”,它們其實有著嚴格的區別。
聲明:變量聲明用于說明變量的屬性(主要是變量的類型)
定義:變量定義除了需要聲明之外,還引起了存儲器分配。
5、在步驟3中我們發現在任意文件中定義的外部變量均可在其它文件中進行使用,只要我們使用了extern關鍵字告訴編譯器這個變量的聲明式,我們就可以順利通過編譯。雖然這個方式能夠實現在多個文件中共享數據,但是考慮到文件的管理與項目的不可預測性,這樣的方式未免讓我們有了些許的擔心。要是我定義的變量被別人惡意引用了怎么辦?對于只進行讀操作的行為,可能這種災難是比較小的,但是對于寫操作的行為,就有可能影響到變量的正確性。
用static聲明限定外部變量和函數,可以將其后聲明的對象的作用域限定為被編譯源文件的剩余部分。
參看以下代碼:
包含main的源文件:
#include <stdio.h>
#include <stdlib.h>
void static1Processor(void);
int main(void) {
static1Processor();
return EXIT_SUCCESS;
}
extern int static1Variable;
void static1Processor(void) {
printf("static3Processor.static3Variable:%d\n", static1Variable);
}
staticFile.c文件:(這個文件很簡單,就包含了一個外部變量的定義,與externFile.c所不同的是它的定義增加了static關鍵字修飾):
static int static1Variable = 2006;
輸出結果(編譯錯誤,無任何輸出結果):
由于static int static1Variable = 2006;導致了static1Variable變量只對staticFile.c文件可見。
對于函數,它也是具有類似的限制:
包含main的源文件:
#include <stdio.h>
#include <stdlib.h>
/*兩種定義均無法引用void static2Processor(void);方法的具體實現。*/
/*void static2Processor(void);*/
extern void static2Processor(void);
int main(void) {
static2Processor();
return EXIT_SUCCESS;
}
staticFile.c:
#include <stdio.h>
static int static2Variable = 2007;
static void static2Processor(void) {
printf("static2Processor.static1Variable:%d\n", static2Variable);
}
static不僅可以用于聲明外部變量,它還可以用于聲明內部變量。static類型的內部變量同自動變量一樣,是某個特定局部變量,只能在該函數中使用,但它與自動變量不同的是,不管其所在函數是否被調用,它將一直存在,而不像自動變量那樣,隨著所在函數的被調用和退出而存在和消失。換句話說,static類型的內部變量只是一種只能在某個特定函數中使用但一直占據存儲空間的變量。
6、因為extern是為了防止重復定義,而不是防止重復聲明。因此對于不可能產生重復定義的函數聲明式來說,形如void extern4Processor(void); 這樣的語句可以不用增加extern,因為它是重復的聲明,而不是定義。
因為函數聲明式本身是不允許嵌套的,因此它天生就是外部的,所以默認情況下類似void FunctionName(){……};的形式都有個默認的修飾符extern void FunctionName(){……};只有標識了static的函數不是外部函數。
包含main的源文件:
#include <stdio.h>
#include <stdlib.h>
/*double kinds of declare
* The storage-class specifier, if any,in the declaration specifiers
* shall be either extern or static .
*/
/*extern void extern4Processor(void);*/
void extern4Processor(void);
int main(void) {
extern4Processor();
return EXIT_SUCCESS;
}
externFile.c:
#include <stdio.h>
int extern4Variable = 2008;
void extern4Processor(void) {
printf("extern4Processor.extern4Variable:%d\n", extern4Variable);
}
輸出結果:
extern4Processor.extern4Variable:2008
7、重復聲明并不可怕,可怕的是重復定義。
參看以下代碼:
包含main的源文件:
#include <stdio.h>
#include <stdlib.h>
char repeatVariableWithoutDefinition1; /*declear thrice no hurt*/
char repeatVariableWithoutDefinition1; /*declear thrice no hurt*/
char repeatVariableWithoutDefinition1; /*declear thrice no hurt*/
void repeatVariableWithoutDefinition1Func(void);
int main(void) {
repeatVariableWithoutDefinition1Func();
return EXIT_SUCCESS;
}
void repeatVariableWithoutDefinition1Func(void) {
repeatVariableWithoutDefinition1 = 'v';
printf("repeatVariableWithoutDefinition1:%c\n",
repeatVariableWithoutDefinition1);
}
otherFile.c:
char repeatVariableWithoutDefinition1;
char repeatVariableWithoutDefinition1;
它的無害是因為它們都是在編譯時進行分配的,它們并沒有并存,只是僅存了一個罷了。
8、至此,上面已經生成了許多的聲明/定義。可以看出,我們的函數是可以跨文件調用的,而且每次調用都要寫函數聲明式。為此,C語言支持“頭文件”,也就是我們經常看到的#include "xxxxx.h"或#include <stdio.h>。其中include <>的時候,將根據相應規則查找該文件(通常在編譯器所在Includes文件夾內找),但是""的時候總是先在源文件(*.c)所在文件夾查找,若找不到則使用與#include <>相同規則進行查找。
#include是一個C預處理器,它所指定的文件將在編譯時,將其中內容原封不動地替換到#include語句所在的位置。這樣的話,我們就有能力實現了一個地方定義函數,多個地方調用的功能了。(每次重新寫聲明式難免會造成:1、手誤,導致拼寫錯誤;2、修改維護困難,可能會漏掉,但又機緣巧合不會出錯。)
#include頭文件中可以推薦包含文件聲明、宏替換等(事實上可以包含任何的文本)。
因為可以包含任何的文本,所以我們有可能因為重復定義而導致一些不必要的麻煩,因為畢竟重復定義是沒有任何意義的,還增加編譯時間。因此在頭文件的內部,我們通常采用條件包含來避免重復地包含。
參看以下代碼:
#ifndef EFFECTIVEAREA_H_
#define EFFECTIVEAREA_H_
/*define the content of EffectiveArea.h here!*/
#endif /* EFFECTIVEAREA_H_ */
注意,宏名字是全局有效的,因此我們必須保證它的唯一性,否則,在判斷的時候,就會因為兩個頭文件之間的互相排斥(被認為是同一個文件),但事實上它們之間只是錯誤地定義了名字。為此我們可以用文件名的等價轉換來包含它們,因為文件名是唯一的。(文件名包含它的路徑,通常我們將頭文件放入同一個文件夾下,因此我們可以保證在同級文件夾下的文件名的唯一性。)
這樣我們就可以隨心所欲地包含頭文件了,而不必擔心重復包含頭文件所帶來的壞處了。
9、對于函數簽名聲明返回值類型為int的可以省略(不推薦(引發警告))。
參看以下代碼:
#include <stdio.h>
#include <stdlib.h>
/*We can omit the declaration of function here only when it's returnType is int.
* It only cause compile warning "implicit declaration of function 'nodeclare1Func'"
* Because the default function returnType is 'int'. */
/*But we suggest you explicit declare your function here.*/
/*int nodeclare1Func(int param1); */
/*We can not omit anything here!*/
char nodeclare2Func(void);
int main(void) {
nodeclare1Func(2009);
nodeclare2Func();
return EXIT_SUCCESS;
}
int nodeclare1Func(int param1) {
printf("nodeclare1Func.param1:%d\n", param1);
return EXIT_SUCCESS;
}
char nodeclare2Func(void) {
printf("nodeclare2Func.i:%d\n", 2010);
return 'c';
}
作用域的內容并不難,掌握它們雖然不需要花費太多的時間(當然我還是比較推崇認真掌握了),但是我們應該從使用習慣上做到合理準確地應用這些特性。對于出現的一些錯誤能夠給出合理的解釋。
Shell排序算法是D.L.Shell于1959年發明的,其基本思想是:
下面的這段代碼是Shell算法的C語言實現,其中shellsort為原函數,而traceShellsort則為跟蹤輸出的函數,這里我用了幾個標準輸出的語句,將數據交換的過程進行一個輸出,以更好地理解排序的過程。
#include <stdio.h>
#include <stdlib.h>
#define ARRAY_LENGTH 9
void shellsort(int v[], int n);
void arrayPrintf(int v[], int n);
void traceShellsort(int v[], int n);
int traceOut(int n, int gap, int i, int j, int isnewline);
int traceCount;
int main(void) {
int arr[ARRAY_LENGTH] = { 12, 2, 20, 19, 28, 30, 12, 42, 35 };
printf("Original array:\t\t");
arrayPrintf(arr, ARRAY_LENGTH);
/*sort the array by shell arithmetic*/
//shellsort(arr, ARRAY_LENGTH);
traceShellsort(arr, ARRAY_LENGTH);
putchar('\n');
printf("MinToMax array:\t\t");
arrayPrintf(arr, ARRAY_LENGTH);
return EXIT_SUCCESS;
}
/*shellsort函數:按遞增順序對v[0]…v[n-1]進行排序*/
void shellsort(int v[], int n) {
int gap, i, j, temp;
for (gap = n / 2; gap > 0; gap /= 2)
for (i = gap; i < n; i++)
for (j = i - gap; j >= 0 && v[j] > v[j + gap]; j -= gap) {
temp = v[j];
v[j] = v[j + gap];
v[j + gap] = temp;
}
}
/*shell排序算法的跟蹤版,相同的算法,它將輸出帶有跟蹤過程的數據*/
void traceShellsort(int v[], int n) {
int gap, i, j, temp;
extern int traceCount;
traceCount = 1;
for (gap = n / 2; gap > 0; gap /= 2) {
for (i = gap; i < n; i++) {
for (j = i - gap; traceOut(n, gap, i, j, !(j >= 0 && v[j] > v[j
+ gap])) && j >= 0 && v[j] > v[j + gap]; j -= gap) {
temp = v[j];
v[j] = v[j + gap];
v[j + gap] = temp;
arrayPrintf(v, n);
}
}
}
}
/*用于跟蹤交換過程*/
int traceOut(int n, int gap, int i, int j, int isnewline) {
printf("%2d. n=%d gap=%d i=%d j=%2d %c", traceCount++, n, gap, i, j,
isnewline ? '\n' : ' ');
return 1;
}
/*用于輸出一組數組*/
void arrayPrintf(int v[], int n) {
int i;
for (i = 0; i < n; i++)
printf("%d ", v[i]);
putchar('\n');
}
下面的文字是運行上面一段代碼后產生的結果,其中跟蹤過程中出現的數組輸出,表示該數組步驟中將會產生一次位置交換過程。
Original array: 12 2 20 19 28 30 12 42 35
1. n=9 gap=4 i=4 j=0
2. n=9 gap=4 i=5 j=1
3. n=9 gap=4 i=6 j=2 12 2 12 19 28 30 20 42 35
4. n=9 gap=4 i=6 j=-2
5. n=9 gap=4 i=7 j=3
6. n=9 gap=4 i=8 j=4
7. n=9 gap=2 i=2 j=0
8. n=9 gap=2 i=3 j=1
9. n=9 gap=2 i=4 j=2
10. n=9 gap=2 i=5 j=3
11. n=9 gap=2 i=6 j=4 12 2 12 19 20 30 28 42 35
12. n=9 gap=2 i=6 j=2
13. n=9 gap=2 i=7 j=5
14. n=9 gap=2 i=8 j=6
15. n=9 gap=1 i=1 j=0 2 12 12 19 20 30 28 42 35
16. n=9 gap=1 i=1 j=-1
17. n=9 gap=1 i=2 j=1
18. n=9 gap=1 i=3 j=2
19. n=9 gap=1 i=4 j=3
20. n=9 gap=1 i=5 j=4
21. n=9 gap=1 i=6 j=5 2 12 12 19 20 28 30 42 35
22. n=9 gap=1 i=6 j=4
23. n=9 gap=1 i=7 j=6
24. n=9 gap=1 i=8 j=7 2 12 12 19 20 28 30 35 42
25. n=9 gap=1 i=8 j=6
MinToMax array: 2 12 12 19 20 28 30 35 42
為了更好地查看當前值,我將每一次交換的值用下劃線進行標出。
希爾排序(Shell sort)也稱“縮小增量排序”。它的做法不是每次一個元素挨一個元素的比較。而是先將整個待排記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄基本有序時,再對全體記錄進行一次直接插入排序。這樣大大減少了記錄移動次數,提高了排序效率。
算法思路:先取一個正整數d1(d1<n),把全部記錄分成d1個組,所有距離為dl的倍數的記錄看成是一組,然后在各組內進行插入排序;接著取d2(d2<d1),重復上述分組和排序操作;直到di=1 (i>=1),即所有記錄成為一個組為止。希爾排序對增量序列的選擇沒有嚴格規定,一般選d1約為n/2,d2為d1/2,d3為d2/2,…,di=1。
在C語言的函數定義上,我們通常都是用的函數定義方式為ANSI-C的函數定義方式。但是在C語言之父創立C語言之時,函數的定義形式并非現在我們所見到的形式。下面的代碼顯示了這樣的差別,注意觀察二者在聲明與函數體簽名上的差別。
#include <stdio.h>
#include <stdlib.h>
/*K&R Standard==============start===================*/
const char* originalFunc();
/*K&R Standard---------------end--------------------*/
/*ANSI Standard=============start===================*/
const char* ANSIFunc(char param1, char* param2);
/*You also can define the function like below.
* const char* ANSIFunc(char ,char*); */
/*ANSI Standard--------------end--------------------*/
int main() {
printf("const char* originalFunc(param1,param2):%s\n", originalFunc('a',
"word"));
printf("const char* ANSIFunc(param1,param2):%s\n", ANSIFunc('a', "word"));
return EXIT_SUCCESS;
}
/*K&R Standard==============start===================*/
const char* originalFunc(param1, param2)
char param1;char* param2; {
printf("param1:%c\nparam2:%s\n", param1, param2);
return "originalFunc";
}
/*K&R Standard---------------end--------------------*/
/*ANSI Standard=============start===================*/
const char* ANSIFunc(char param1, char* param2) {
printf("param1:%c\nparam2:%s\n", param1, param2);
return "ANSIFunc";
}
/*ANSI Standard--------------end--------------------*/
K&R的風格與ANSI-C的比,K&R風格又稱為identifier-list,而另一種風格則又稱為declarator,其中originalFunc(param1, param2) 即為這個declarator。事實上兩種風格在ANSI 99 標準中(6.9.1節)都有定義,只不過我們更推崇declarator的方式。因為它將對參數進行強制類型轉換,而標識符列表的方式則沒有進行這項操作。
整理FROM:http://bbs.et8.net/bbs/showthread.php?p=9443319
在VisualStuduo 2008 中文版中,編譯我試用VS2003所寫的一個項目時,提示無法查找到頭文件atlrx.h。
該項目中我使用了ATL的正則表達式類 CAtlRegExp,因此需要對應的頭文件<atlrx.h>。
經過搜索發現,在VS2003中,該文件位于
\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\include
而2008的目錄中確實不存在該文件。
通過2008的在線幫助,查找CAtlRegExp,發現仍然存在,而其附帶的例子中仍然使用的是
#include <atlrx.h>
幫助頁面:
http://msdn2.microsoft.com/zh-cn/lib...xe(VS.80).aspx
按道理2008應該同樣支持該類,但又沒有相應的頭文件?
有沒有別人碰到這個情況?如何處理啊?
如果這個項目無法在VS2008下編譯,俺就沒法遷移到2008下工作了,還得接著用VS2003(VS 2003我都卸載了,嗨!)
http://connect.microsoft.com/VisualS...dbackID=306398
atlrx.h is not part of VS2008 anymore. ATL Server is now an open source project. You can find more details at http://blogs.msdn.com/vcblog/archive...-software.aspx
按照解決方法中的辦法,把VS2003中的頭文件復制過來,解決了。(看來庫中是有的,只是去掉了頭文件)
atlrx.h中包含了模版類的聲明和實現,沒有額外的庫。
下載:http://www.codeplex.com/AtlServer
http://blogs.msdn.com/vcblog/archive/2007/01/19/atl-server-visual-c-shared-source-software.aspx
C++
void CAPPTestApp::RegisterProtocol()
{
CRegKey reg;
reg.Create(HKEY_CLASSES_ROOT,_T("vip"));
reg.SetStringValue(_T(""), _T("URL:vip Protocol"));
reg.SetStringValue(_T("URL Protocol"), _T(""));
DWORD size = MAX_PATH;
CString strPath;
::GetModuleFileName(m_hInstance, strPath.GetBuffer(MAX_PATH), size);
strPath.ReleaseBuffer();
reg.Create(HKEY_CLASSES_ROOT,_T("vip\\DefaultIcon"));
reg.SetStringValue(_T(""), strPath);
strPath += _T(" %1");
reg.Create(HKEY_CLASSES_ROOT,_T("vip\\shell\\open\\command"));
reg.SetStringValue(_T(""), strPath);
::MessageBox(HWND_DESKTOP, _T("The vip protocol has been registered"), _T("APPTest"), MB_OK);
}
用以上代碼可以注冊形如下方的注冊表項:
HKEY_CLASSES_ROOT
vip
(Default) = "URL:vip Protocol"
URL Protocol= ""
DefaultIcon
(Default) = "c:\somepath\APPTest.exe"
shell
open
command
(Default) = "c:\somepath\APPTest.exe" "%1"