程式=算法+資料結構+程式設計方法+語言工具和壞境文章目錄
- 告知:使用C語言的7個步驟
- 最簡單的C代碼
- 判斷浮點數變量x的值是否為零
- 逗号表達式
- 求位
- 循環語句
- 分支語句
- if代碼規範
- 函數
- 實際參數(實參)
- 形式參數(形參)
- 函數的調用
- 傳值調用:
- 傳址調用✅✅
- 函數嵌套
- 函數遞歸(遞去歸來)
- 回調函數
- 數組
- 下标
- 指針
- 指針,間接通路和變量
- 函數指針
- 函數指針數組
- 函數指針數組的使用
- 回調函數_qsort函數的使用
告知:使用C語言的7個步驟
-
第1步:定義程式的目标
在動手寫程式之前,要在腦中有清晰的思路。想要程式去做什麼首先自己要明确自己想做什麼,思考你的程式需要哪些資訊,要進行哪些計算和控制,以及程式應該要報告什麼資訊。在這一步驟中,不涉及具體的計算機語言,應該用一般術語來描述問題。
-
第2步:設計程式
對程式應該完成什麼任務有概念性的認識後,就應該考慮如何用程式來完成它。例如,使用者界面應該是怎樣的?如何組織程式?目标使用者是誰?準備花多長時間來完成這個程式?
除此之外,還要決定在程式(還可能是輔助檔案)中如何表示資料,以及用什麼方法處理資料。學習C語言之初,遇到的問題都很簡單,沒什麼可選的。但是,随着要處理的情況越來越複雜,需要決策和考慮的方面也越來越多。通常,選擇一個合适的方式表示資訊可以更容易地設計程式和處理資料。
再次強調,應該用一般術語來描述問題,而不是用具體的代碼。但是,你的某些決策可能取決于語言的特性。例如,在資料表示方面,C的程式員就比Pascal的程式員有更多選擇。
-
第3步:編寫代碼
設計好程式後,就可以編寫代碼來實作它。也就是說,把你設計的程式翻譯成 C語言。這裡是真正需要使用C語言的地方。可以把思路寫在紙上,但是最終還是要把代碼輸入計算機。這個過程的機制取決于程式設計環境,我們稍後會詳細介紹一些常見的環境。一般而言,使用文本編輯器建立源代碼檔案。該檔案中内容就是你翻譯的C語言代碼。程式清單1.1是一個C源代碼的示例。
程式清單1.1 C源代碼示例
#include
int main(void)
{
int dogs;
printf("How many dogs do you have?\n");
scanf("%d", &dogs);
printf("So you have %d dog(s)!\n", dogs);
return 0;
}
在這一步驟中,應該給自己編寫的程式添加文字注釋。最簡單的方式是使用 C的注釋工具在源代碼中加入對代碼的解釋。
第4步:編譯
接下來的這一步是編譯源代碼。再次注意,編譯的細節取決于程式設計的環境,前面介紹過,編譯器是把源代碼轉換成可執行代碼的程式。可執行代碼是用計算機的機器語言表示的代碼。這種語言由數字碼表示的指令組成。如前所述,不同的計算機使用不同的機器語言方案。C 編譯器負責把C代碼翻譯成特定的機器語言。此外,C編譯器還将源代碼與C庫(庫中包含大量的标準函數供使用者使用,如printf()和scanf())的代碼合并成最終的程式(更精确地說,應該是由一個被稱為連結器的程式來連結庫函數,但是在大多數系統中,編譯器運作連結器)。其結果是,生成一個使用者可以運作的可執行檔案,其中包含着計算機能了解的代碼。
編譯器還會檢查C語言程式是否有效。如果C編譯器發現錯誤,就不生成可執行檔案并報錯。了解特定編譯器報告的錯誤或警告資訊是程式員要掌握的另一項技能。
第5步:運作程式
傳統上,可執行檔案是可運作的程式。在(常端模式和Linux終端模式)中運作程式要輸入可執行檔案的檔案名,而其他環境可能要運作指令(如,在VAX中的VMS[2])或一些其他機制。例如,在Windows和Macintosh提供的內建開發環境(IDE)中,使用者可以在IDE中通過選擇菜單中的選項或按下特殊鍵來編輯和執行C程式。最終生成的程式可通過單擊或輕按兩下檔案名或圖示直接在作業系統中運作。
第6步:測試和調試程式
程式能運作是個好迹象,但有時也可能會出現運作錯誤。接下來,應該檢查程式是否按照你所設計的思路運作。你會發現你的程式中有一些錯誤,計算機行話叫作bug。查找并修複程式錯誤的過程叫調試。學習的過程中不可避免會犯錯,學習程式設計也是如此。是以,當你把所學的知識應用于程式設計時,最好為自己會犯錯做好心理準備。
第7步:維護和修改代碼
建立完程式後,你發現程式有錯,或者想擴充程式的用途,這時就要修改程式。例如,使用者輸入以Zz開頭的姓名時程式出現錯誤、你想到了一個更好的解決方案、想添加一個更好的新特性,或者要修改程式使其能在不同的計算機系統中運作,等等。如果在編寫程式時清楚地做了注釋并采用了合理的設計方案,這些事情都很簡單。
許多初學者經常忽略第1步和第2步(定義程式目标和設計程式),直接跳到第3步(編寫代碼)。剛開始學習時,編寫的程式非常簡單,完全可以在腦中構思好整個過程。即使寫錯了,也很容易發現。但是,随着編寫的程式越來越龐大、越來越複雜,動腦不動手可不行,而且程式中隐藏的錯誤也越來越難找。最終,那些跳過前兩個步驟的人往往浪費了更多的時間,因為他們寫出的程式難看、缺乏條理、讓人難以了解。要編寫的程式越大越複雜,事先定義和設計程式環節的工作量就越大。
磨刀不誤砍柴工,應該養成先規劃再動手編寫代碼的好習慣,用紙和筆記錄下程式的目标和設計架構。這樣在編寫代碼的過程中會更加得心應手、條理清晰。
最簡單的C代碼
int main(void){
return 0;
}
判斷浮點數變量x的值是否為零
if(|x-0.000001|<=0.000001)
printf("零");
else
printf("不是零")
逗号表達式
第一個輸出:是因為逗号表達式,隻會把逗号的最後一個值賦給i
第二個:i指派為1,接着執行常量2的運算,計算結果丢棄。最終,i的結果是1而不是2.
求位
百分位:n/00;
十位:n/10%100 或 (n-a*100)/10
個位:n%10
控制行号:if(++n%5==0)//五個為一行
printf("\n");
循環語句
看下面代碼,你認為會列印多少句”hehe“?
- for變種1
#include
int main()
{
int i = 0;
int j = 0;
for(;i<10;i++)
{
for(;j<10;j++)
printf("hehe\n");
}
return 0;
}
答案是隻輸出10句;
//j++ =0,1,2,3,4,5,6,7,8,9,10;j=10;
//執行外層for時,i等于1了,進入内循環,j沒有被重新初始化,導緻j的值還是10;i++等于2時,j依然還是10開始。…不滿足内循環直接退出,列印10次hehe
- for變種2
直接上代碼
for執行了0次,因為k指派了0,為假,是以不執行
分支語句
int day = 0;
int n = 1;
scanf("%d",&day);
switch(day)
{
case 1+0://ok
printf("星期一");
case 1.0://error ,隻能是整形常量
case n://error
break;//在最後的case語句加一個break,為以後添加case分支而好維護
}
if代碼規範
int num = 4;
if(5 == num)//常量和變量比較時,常量因放在變量左邊
//if(5 = num);//編譯失敗,直覺錯誤
printf("hehe");
函數
實際參數(實參)
真實傳給函數的參數,叫實參,實參可以是:常量,變量,表達式,函數等。無論實參是何種類型的量,在進行函調用時,它們都必須有确定的值,以便把這些值傳遞給形參。
形式參數(形參)
形式參數是指函數名後括号中的變量,因為形式參數隻有在函數被調用的過程中才執行個體化(配置設定記憶體單元),是以叫形式參數,形式參數當函數調用完成之後就自動銷毀,是以形式參數隻在函數中有效
時間函數
#include
#include
int main ()
{
time_t rawtime;
struct tm * timeinfo;
time ( &rawtime );
timeinfo = localtime ( &rawtime );
printf ( "目前本地時間為: %s", asctime (timeinfo) );
return 0;
}
函數的調用
#include
void Swap2(int* pa,int* pb)
{
int tmp = 0;
tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//void Swap1(int x,int y)
//{
//
// int tmp = 0;
// tmp = x;
// x = y;
// y = x;
//}
int main()
{
int a = 10;
int b = 20;
printf("交換前:a = %d b = %d\n",a,b);
//Swap1(a, b);//傳值調用
Swap2(&a, &b);//傳址調用
printf("交換後:a = %d,b = %d\n",a,b);
return 0;
}
傳值調用:
函數的形參和實參分别占有不同的記憶體塊,對形參的修改不會影響實參。
傳址調用✅✅
- 傳址調用是把函數外部建立變量的記憶體位址傳遞給函數參數的一種調用函數的方式。
- 這種傳參方式可以讓函數和函數外邊的變量建立起真正的聯系,也就是函數内部可以直接操作函數外部的變量。
傳址調用可以節省記憶體,因為傳值的話,我們需要拷貝,參數壓棧的系統開銷比較大,導緻性能下降
函數嵌套
#include
int main()
{
printf("%d",printf("%d",printf("%d",printf("%d",4))));//列印4111
//因為最後一個printf列印4,倒數第二個printf列印調用printf輸出的個數為1(包括空格),依次...詳細看printf函數文檔
printf("%d",printf("%d",printf("%d",43))//4321
return 0;
}
函數遞歸(遞去歸來)
遞歸的兩個必要條件
- 存在限制條件,當滿足這個限制條件的時候,遞歸便不再繼續
- 每次遞歸調用之後越來越接近這個限制
錯誤使用 遞歸
#include
int main()
{
main();//err
return 0;
}
正确使用遞歸
#include
void print(int n)//依次列印1 2 3 4
{
if(n>9)
print(n/10);
printf("%d ",n%10);
}
int main()
{
int num = 0;
scanf("%d",&num);//1234
print(num);
}
回調函數
#include
#include
#include
//void qsort(void *base, //base中存放的是待排序資料中的第一個對象的位址
// size_t num, //排序資料元素的個數
// size_t size,//排序資料中一個元素的大小,機關是位元組
// int (*cmp)(const void*,const void*)//是用來比較待排序資料中的2個元素的(一個)函數
// );
int cmp_int(const void* e1,const void* e2)
{
return *(int *)e1-*(int *)e2;//因為參數類型是void,我們用(*int)強制類型轉換
/*傳回值 意義
<0 所指向的元素在所指向的元素之前p1p2
0 指向的元素等效于 指向的元素p1p2
>0 指向的元素位于 指向的元素之後p1p2
*/
}
void print(int arr[],int sz)
{
int i= 0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void test1()
{
//整形資料的排序
int arr[]={9,8,7,6,5,4,3,2,1,0};
int sz=sizeof(arr)/sizeof(arr[0]);
//排序
qsort(arr,sz,sizeof(arr[0]),cmp_int);
//列印
print(arr,sz);
}
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1,const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//轉換結構體類型指針,e1指向age成員
}
void test2()
{
//排序結構體資料
struct Stu s[3]={ {"zhangsang",30},{"lisi",34},{"wangwu",20}};
int sz=sizeof(s)/sizeof(s[0]);
//按照年齡來排序
qsort(s,sz,sizeof(s[0]),sort_by_age);
}
int sort_by_name(const void* e1,const void* e2)
{
return strcmp(((struct Stu*)e1)->name ,((struct Stu*)e2)->name);
}
void test3()
{
//排序結構體資料
struct Stu s[3]={ {"zhangsang",30},{"lisi",34},{"wangwu",20}};
int sz=sizeof(s)/sizeof(s[0]);
//按照名字來排序
qsort(s,sz,sizeof(s[0]),sort_by_name);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
數組
- 數組的長度機關是常量表達式表示
- 數組在記憶體中連續存放的,
int n =10;
int arr[n]={1,2,3,4,4,};//error
下标
如果下标是從那些已知正确的值計算得來,那麼就無需檢查它的值。如果一個用作下标的值是根據某種方法從使用者輸入的資料産生而來的,那麼在使用它之前必須進行檢查,確定它們位于有效的範圍之内。
指針
指針,間接通路和變量
#include
int main()
{
int a=7;
int *d = &a;
*d = 10-*d;//*d==a -- a=10-a(7)
//d= 10- *d;//err,因為d是個指針變量
//整形變量不能存儲一個指針變量
*&a=*d;//說明變量a==*d,不建議這種表達式
printf("a==%p \n",a);
printf("*a==%p \n",*d);
printf("d==%p\n",d);
printf("&a==%p\n",&a);
return 0;
}
函數指針
void test(int **p2)
{
**p2=20;
}
int test_p(int x,int y)
{
return x+y;
}
#include
int main()
{
int a =10;
int * pa=&a;//pa是一級指針
int **ppa=&pa;//ppa是二級指針
//二級指針傳參
test(ppa);
test(&pa);//傳一級指針的位址
int* arr[10]={0};
test(arr);
printf("%d ",a);
//pf函數指針變量
int (*pf)(int ,int )=&test_p;
return 0;
}
int Add(int x,int y)
{
return x+y;
}
#include
int main()
{
//int (*pf)(int,int )=&Add;
int (*pf)(int,int )=Add;//Add == pf
//int ret=(Add)(3,5);//3
//int ret=(*pf)(3,5)//1 ;*是擺設,可以int ret=(*****pf)(3,5)結果依然正确
//int ret=*pf(3,5)//err;優先級 pf會先和(3,5)結合,任何*對函數的傳回值8解引用肯定會錯
int ret = pf(3,5);//2
printf("%d ",ret);//
return 0;
}
函數指針數組
int add(int x,int y)
{
return x+y;
}
int sub(int x,int y)
{
return x-y;
}
#include
int main()
{
int (*pf1)(int ,int)=add;
int (*pf2)(int ,int)=sub;
int (*pfarr[2])(int ,int )={add,sub};//pfarr就是函數指針數組,{}裡可以是pf1,pf2,*注意是花括号
//add函數和sub函數的形參因為一樣,記憶體存放也是一樣,而數組前提就是記憶體類型一樣;
//是以可以同數組存放到函數指針,故叫做函數指針數組。
return 0;
}
函數指針數組的使用
#include
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int main()
{
int input = 0;
//電腦-計算整型變量的加、減、乘、除
//a&b a^b a|b a>>b a< b do { menu(); int x = 0; int y = 0; int ret = 0; printf("請選擇:>"); scanf("%d", &input); switch (input) { case 1: printf("請輸入2個操作數>:"); scanf("%d %d", &x, &y); ret = Add(x, y); printf("ret = %d\n", ret); break; case 2: printf("請輸入2個操作數>:"); scanf("%d %d", &x, &y); ret = Sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("請輸入2個操作數>:"); scanf("%d %d", &x, &y); ret = Mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("請輸入2個操作數>:"); scanf("%d %d", &x, &y); ret = Div(x, y); printf("ret = %d\n", ret); break; case 0: printf("退出程式\n"); break; default: printf("選擇錯誤,重新選擇!\n"); break; } } while (input); return 0; }
#include
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int main()
{
int input = 0;
//電腦-計算整型變量的加、減、乘、除
//a&b a^b a|b a>>b a< b do { menu(); //pfArr就是函數指針數組 //轉移表 - 《C和指針》 int (*pfArr[5])(int, int) = { NULL, Add, Sub, Mul, Div }; int x = 0; int y = 0; int ret = 0; printf("請選擇:>"); scanf("%d", &input);//2 if (input >= 1 && input <= 4) { printf("請輸入2個操作數>:"); scanf("%d %d", &x, &y); ret = (pfArr[input])(x, y); printf("ret = %d\n", ret); } else if(input == 0) { printf("退出程式\n"); break; } else { printf("選擇錯誤\n"); } } while (input); return 0; }
回調函數_qsort函數的使用
#include
#include
#include
//void qsort(void *base, //base中存放的是待排序資料中的第一個對象的位址
// size_t num, //排序資料元素的個數
// size_t size,//排序資料中一個元素的大小,機關是位元組
// int (*cmp)(const void*,const void*)//是用來比較待排序資料中的2個元素的(一個)函數
// );
int cmp_int(const void* e1,const void* e2)
{
return *(int *)e1-*(int *)e2;//因為參數類型是void,我們用(*int)強制類型轉換
/*傳回值 意義
<0 所指向的元素在所指向的元素之前p1p2
0 指向的元素等效于 指向的元素p1p2
>0 指向的元素位于 指向的元素之後p1p2
*/
}
void print(int arr[],int sz)
{
int i= 0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void test1()
{
//整形資料的排序
int arr[]={9,8,7,6,5,4,3,2,1,0};
int sz=sizeof(arr)/sizeof(arr[0]);
//排序
qsort(arr,sz,sizeof(arr[0]),cmp_int);
//列印
print(arr,sz);
}
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1,const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//轉換結構體類型指針,e1指向age成員
}
void test2()
{
//排序結構體資料
struct Stu s[3]={ {"zhangsang",30},{"lisi",34},{"wangwu",20}};
int sz=sizeof(s)/sizeof(s[0]);
//按照年齡來排序
qsort(s,sz,sizeof(s[0]),sort_by_age);
}
int sort_by_name(const void* e1,const void* e2)
{
return strcmp(((struct Stu*)e1)->name ,((struct Stu*)e2)->name);
}
void test3()
{
//排序結構體資料
struct Stu s[3]={ {"zhangsang",30},{"lisi",34},{"wangwu",20}};
int sz=sizeof(s)/sizeof(s[0]);
//按照名字來排序
qsort(s,sz,sizeof(s[0]),sort_by_name);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
void Swap(char*buf1,char*buf2,int width)
{
int i = 0;
for(i=0;i<width;i++)
{
char tmp = *buf1;
*buf1=*buf2;
*buf2=tmp;
buf1++;
buf2++;
}
}
//模仿qsort實作一個冒泡排序的通用算法
void bubbble_sort(void *base,
int sz,
int width,
int (*cmp)(const void*e1,const void*e2)
)
{
int i =0;
for(i=0;i<sz-1;i++){
int j = 0;
for(j=0;j<sz-1-i;j++)
{
if(cmp((char*)base+j*width,(char *)base+(j+1)*width)>0)//(兼并)适合int類型和字元類型,如果是用int的話,那我用字元排序那位元組+幾就是跳過4個位元組,單一
{
Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}