20160129.CCPP體系詳解(0008天)
程式片段(01):函式.c+call.c+測試.cpp
內容概要:函式
///函式.c
#include <stdio.h>
#include <stdlib.h>
//01.函式:
// 1.函式的作用:重用程式碼,重用功能
// 表象:程式碼的封裝,程式碼的重用
// 實質:功能的封裝,功能的重用
int main01(void)
{
system("tasklist");
system("pause");
}
//02.函式的宣告與定義淺析:
// 1.函式的宣告的定義的使用時機不同:
// 函式的宣告用於編譯時期-->編譯檢查
// 函式的定義用於連結時期-->連結檢查
// 函式的真正使用:宣告和定義缺一不可
// 2.由於編譯器的不同所導致的差異:
// C語言編譯器:寬泛(VS2015自動配置了編譯器所需要的靜態庫Lib的目錄)
// 可以既沒有宣告也沒有定義(只是沒有顯式的在程式碼中標註,但是
// 編譯器能夠自動識別到),編譯器當中有兩個配置選項(庫目錄+附加依賴項)
// 但是,如果編譯器當中沒有配置這兩項(庫目錄和附加依賴項)就會編譯報錯
// 注意:編譯時期需要宣告,連結時期需要實體
// C++語言編譯器:嚴格
// 必須既有宣告也有定義,必須顯式的在程式碼中進行標註
// 編譯時期需要宣告,連結時期需要定義
// 3.在程式碼當中函式宣告和定義出現的時機:
// 標準做法:函式宣告必須出現在函式呼叫之前
// 函式宣告的位置既可以獨立形式出現,也可以出現於函式體內部,但必須出現
// 在呼叫之前(CCPP同時支援的規則)
// 4.關於形參是否存在形參名稱的問題:
// 函式宣告的時候可以沒有形參名稱,
// 但是,函式實現的時候必須有函式的形參名稱
int getres(int a, int b, int c);//函式的宣告
int main01(void)
{
//程式碼重用
int x = 11, y = 12, z = 13;
x = x*x*x;
y = y*y*y;
z = z*z*z;
int res = x + y + z;
res = getres(x, y, z);
printf("%d \n", res);
int a = 10, b = 12, c = 13;
a = a*a*a;
b = b*b*b;
c = c*c*c;
int res1 = a + b + c;
res1 = getres(a, b, c);//函式通過程式碼的重用實現了功能的重用
printf("res1 = %d \n", res1);
system("pause");
}
//03.為了讓程式能夠連線成功,在函式進行宣告之後就必須進行函式的定義
// 函式的宣告:只是表明函式存在,你可以使用這個函式的名稱(表明有,可以形式用)
// 函式的定義:確切表明函式存在,你可以使用這個函式的本身(確實有,可以實際用)
int getres(int a, int b, int c)
{
return a*a*a + b*b*b + c*c*c;
}
///call.c
//01.編譯器的不同特點測試:
// 1.VS2015的編譯器,預設進行了編譯器所需的靜態庫(LIb)的配置:
// 因此,雖然沒有函式的具體宣告,但是C語言程式卻可以靜態庫(Lib)
// 的配置選項進行函式的定位,最終找到函式實體本身
// 注:C語言由於預設對靜態庫(Lib)的配置,因此C語言的編譯比較寬泛
// 有靜態庫(LIb)的路徑,可以自動定義,不以來與函式的確切宣告
// 2.C++的編譯器:嚴格控制
// 要求必須有函式的宣告和定義,才能夠打包成為應用程式
// 編譯的時候需要檢測函式的宣告是否存在?
// 連結的時候需要檢測函式的實現是否存在?
int main02(void)
{
system("calc");//系統庫函式,標準庫函式
system("pause");
}
///測試.cpp
#include <stdio.h>
#include <stdlib.h>
int add(int a, int b);//函式宣告
//01.自定義函式的宣告和定義特點:
// 1.剛剛那個是針對於系統函式的宣告和定義的區別
// 自定義函式相對於系統函式而言,就是沒有標準庫(Lib)而已
// 2.由於編譯器所導致的不同:
// C語言編譯器特點:
// 沒有函式宣告,但是有靜態庫(Lib)配置-->編譯通過
// 沒有函式宣告,有定義,且實體在任意位置-->可能編譯通過,可能編譯不通過
// 放在呼叫之前的定義,編譯通過
// 放在呼叫之後的定義,可能通過,可能不通過
// 由於C語言編譯器的特點,所以可能檢測的出來,也可能檢測不出來
// 函式的宣告和定義都可以缺掉,只要靜態庫(Lib)中包含,模糊匹配
// C++語言編譯器:
// 沒有函式宣告,但是又靜態庫(Lib)配置-->編譯不通過
// 沒有函式宣告,有定義,且實體在呼叫之前可以呼叫
// 要求:函式的宣告和定義缺一不可,準確匹配
int main03(void)
{
add(1, 2);
printf("%d \n", add(10, 20));
system("pause");
}
int add(int a, int b)
{
return a + b;
}
程式片段(02):C語言函式呼叫例項.c+函式.c+run.c
內容概要:函式的分割+函式的劃分
///C語言函式呼叫例項.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
//01.函式的使用特點:
// 1.C語言當中的異常處理函式abort();
// (1).用於表明某個位置出現了錯誤
// (提示方式:以windows彈窗作為異常提醒)
// (2).函式特點:
// 只是一個提示,點選彈窗之後,程式依然可以繼續執行
// 不會直接中斷整個應用程式(直接表明異常函式abort()的呼叫)
// 2.C語言當中的函式特點:
// 不可以進行函式的巢狀定義!
// C++語言當中不允許函式的直接巢狀,但是允許間接的通過
// Lambda表示式實現函式的巢狀形式
int main01(void)
{
int a;
int b;
scanf("%d,%d", &a, &b);
if (b == 0)
{
abort();//處理程式的異常
}
printf("%d \n", a / b);
//void go()
//{
//}
system("pause");
}
///函式.c
#include <stdio.h>
#include <stdlib.h>//std:表明標準靜態庫-->跨平臺靜態庫-->C語言標準跨平臺靜態函式庫(Lib)
#include <Windows.h>//第三方靜態庫:僅僅適用於Windows的靜態函式庫
void run(char *path)//外部函式,C語言當中的程式碼重用(功能重用),主要依賴於函式的使用特點
{//被調函式
ShellExecuteA(0, "open", path, 0, 0, 1);//預設視窗開啟方式
}
//01.區分主調函式和被掉函式的概念:
// 在那個函式程式碼塊兒中寫其他函式的呼叫語句,那麼:
// 那個函式就是所謂的主調函式
// 其他函式就是所謂的被調函式
int main02(void)
{//主調函式
// run("\"C:\\Program Files\\Tencent\\QQ\\QQProtect\\Bin\\QQProtect.exe\"");
// run("C:\\Users\\yincheng01\\AppData\\Roaming\\baidu\\BaiduYun\\baiduyun.exe");
system("pause");//庫函式,不加標頭檔案,C語言可以,但是為了程式碼規範,還是要新增上標頭檔案的
}
///run.c
#include <stdio.h>
#include <stdlib.h>
//01.函式的組成元素分析:
// 函式的宣告:int getmax(int a, int b);-->末尾的函式宣告結束符(";")分號不允許省略
// 函式的實現:int getmax(int a, int b){return a > b ? a : b;}-->程式碼塊兒("{}")當中的語句就是函式實現語句
// 返回值型別:int-->限制函式的返回值最終型別
// 函式名稱:getmax-->實質即使函式指標-->函式存放函式宣告的地址-->另外還有函式定義的地址(兩個地址不用)
// C語言當中goto語句的實現原理就如同組合語言當中jump原理-->通過反彙編可以區分(函式宣告地址和函式實現地址的不同)
// C語言當中的應用程式在應用程式被載入進記憶體之後,就會新建一張函式表(類似於變數表)-->裡面記錄了函式定義的地址
// 於是我們就可以通過函式宣告的地址找到具體函式定義的地址(這是實現劫持的原理:函式指標)
// 改變函式指標的指向,可以讓其具備不同的行為,以至於沒有行為也是通過這個進行控制的
// 函式宣告變數-->儲存函式定義(實體變數<==>普通變數)的地址-->所以函式宣告變數叫做函式指標(存放地址的變數叫做指標變數)
// 所以:函式宣告變數叫做函式指標
// 形式參數列:(int a, int b)-->int a,int b代表的就是實際的引數本身
// 函式執行體:{return a > b ? a : b;}-->函式實體的程式碼塊兒內容
// 函式返回值:return a > b ? a : b;-->return語句表明函式的具體反回值
int getmax(int a, int b);//函式的語句塊兒不允許宣告,所以通過空語句分號(";")進行表示
int getmax(int a, int b)
{
//int a;//函式體內部定義變數不可以和形式引數名稱重名
return a > b ? a : b;
}
int main03(void)
{
printf("%p \n", getmax);
system("pause");
}
程式片段(03):void.c+fun.c
內容概要:函式的使用和引數
///void.c
#include <stdio.h>
#include <stdlib.h>
int add(int a, int b);//遵循軟體工程規範,在函式呼叫之前必須明確函式的宣告
//01.void型別的使用特點:
// 1.出現位置的不同,意義不同
// 返回值型別位置:
// 表明函式不需要返回值,不用通過return關鍵字顯式的將返回值帶出
// 函式形式引數位:
// 表明該函式無需引數值,明確函式不需要傳入實際引數
// 2.void的使用注意事項:
// 可以用來定義指標型別-->void *-->俗稱乾地址-->沒有明確解析方式的地址
// -->但是由於地址的大小已經確定(要麼4位元組|要麼8位元組)-->編譯器決定
// 所以知曉儲存一個地址需要開闢多少個位元組
// -->因此,指標變數的記憶體地址開闢成功
// 不能用來定義變數型別-->void---->因為沒有明確型別,沒有明確的解析方式
// -->不能描述變數所需要開闢的儲存空間究竟需要多大?
// -->導致開闢普通變數的記憶體空間失敗
//02.返回值和返回值型別的使用注意事項:
// 1.返回值的型別要求與返回值的型別保持一致!
// 如果不一致將會發生資料型別的轉換(自動型別轉換+自動型別轉換)
// 2.如果返回值的型別採用void描述:
// C語言採用其他型別的返回值進行返回,那麼編譯器不會報錯,但是返回的值卻可能是不正確的
// C++語言採用其他型別的返回值進行返回,那麼編譯器直接進行報錯,說型別的不匹配
//03.return關鍵字的作用:
// 1.返回值:將值從被調函式當中帶出
// 2.中斷多層巢狀迴圈的執行(區分於goto語句的實現特點)
// 3.中斷函式的執行
//04.所有的函式,預設的返回值型別都是int型別
// 包括特殊的main函式的預設返回值型別也是int型別
// 只是函式若是沒有明確的宣告返回值型別,而進行返回異常的整數
int main01(void)
{
printf("%d \n", add(10, 20));
//void a;//"a":非法使用"void"型別,代表任何型別
return 1;//返回值應該與返回值型別一致
//如果函式申明為void,卻用return返回一個其他型別的值,那麼C++編譯器報錯,由於型別不匹配
//但是C語言的編譯器不會進行報錯
system("pause");
}
add(int a, int b)
{
return a + b;
}
int main02(void)
{
getchar();//根據函式呼叫找到找到函式實體-->函式宣告-->函式實體
getchar();//引數即使為空,函式的呼叫依然需要新增上小括號("")
system("pause");
}
///fun.c
#include <stdio.h>
#include <stdlib.h>
void change(int a)//函式的副本機制:int a-->形式引數的宣告
{//讀取暫存器當中的整型值,構建當前函式所需使用的記憶體變數值
a = 3;
printf("&change = %p, change = %d \n", &a, a);
}
int main03(void)
{
//主調函式當中傳遞給被調函式的引數叫做實際引數,簡稱實參
change(10);//副本,開闢記憶體容納暫存器的的值-->暫存器當中的值可以直接進行讀取使用
system("pause");
}
//01.函式引數的特點:
// 1.主調函式和被調函式當中的引數是不同的概念:
// (1).所處的位置不同:
// 棧記憶體不同,不同的函式處於不同的執行時堆疊
// 所以即使名稱相同,也是不同的變數
// (2).不可以跨函式訪問區域性變數
// 執行時堆疊的不可見特點
// 上下層(執行時堆疊)當中的區域性變數不可以誇堆疊訪問
// 2.主調函式傳遞給被調函式的實際引數的副本可能的儲存位置:
// 未接收-->暫存器-->快取器-->未經使用的常量資料
// 接收了-->棧記憶體-->儲存普通的副本資料,棧記憶體容得下
// 接收了-->堆記憶體-->如果副本資料很大,就必須採用堆記憶體空間進行儲存
int main04(void)
{
int a = 10;
printf("&main = %p, main = %d \n", &a ,a);
change(a);
printf("%d \n", a);
system("pause");
}
程式片段(04):輸入輸出.c+return.c
內容概要:return與引數
///輸入輸出.c
#include <stdio.h>
#include <stdlib.h>
int add(int a, int b)//int a, int b這兩個形式引數,只有在被呼叫的時候,才會涉及到自動分配和自動釋放
{
printf("add1 a = %d, b = %d \n", a, b);
a = 19;
b = 29;//修改的是當前被調函式當中的區域性變數,也就是主調函式傳遞進來的實際引數的副本資料
printf("add2 a = %d, b = %d \n", a, b);
return a + b;
}
//01.在我看來,傳值和傳址都是一樣的:
// 只不過一個被賦予了普通變數的解析特點->其他
// 一個唄賦予了指標變數的解析特點而已-->陣列
int main01(void)
{
int a = 10;
int b = 20;
printf("%d \n", add(a, b));
printf("%d \n", add(11, 12));//函式的引數除了陣列以外,都是副本(區別於指標變數接收,還是普通變數接收)
printf("main a = %d, b = %d \n", a, b);
system("pause");
}
//02.引數傳遞特點:
// add_debug(1, 2, 3);-->實參太多
// C語言引數過多隻會發出警告,結果不保證絕對正確,引數剛好合適,能夠保證結果正確
// add_debug(1);------->實參太少
// 直接發生變異報錯
// 注:函式引數進棧的順序是從右往左,提取函式引數的資料是從上往下進行提取的
// 例如:(int a, int b);
// 進棧順序:b--->a
// 區分:函式形式引數的進棧順序和函式區域性變數的進棧順序
//03.函式引數進棧的順序嚴格區分:Release環境下進行的測試
// 函式形式引數的進棧順序:
// 資料進棧:從右往左,依次進棧,
// 資料對映:從左往右
// 舉例:傳遞資料1, 2, 3
// (int a, int b)
// 資料進棧: 資料對映:
// 棧底: 3 -> 丟掉|編譯器預置資料
// 2 -> b
// 棧頂:1 -> a
// 函式區域性變數的進棧順序:
// 從下往上-->程式碼進棧-->掃描區域性變數的時候,變數由下往上進行宣告的
int add_debug(int a, int b)
{
printf("a = %p, b = %p \n", &a, &b);
printf("a = %d, b = %d \n", a, b);
a = 1;
b = 2;
int x = 3;
int y = 4;
printf("x = %p, y = %p \n", &x, &y);
printf("x = %d, y = %d \n", x, y);
printf("\n");
}
int main02(void)
{
//printf("%d \n", add_debug(1));
//printf("%d \n", add_debug(1, 2));
//printf("%d \n", add_debug(3, 12));
printf("error = %d \n",add_debug(1, 2, 3, 4, 5) );
system("pause");
}
//04.引數傳遞的注意事項:
// C語言編譯器中,主調函式傳遞給被調函式的實際引數如果過多:
// 多得資料會被忽略掉,
// 引數個數如果一致,型別一致,書序一致能夠保證結果正確
// C語言編譯器中,實參和形參的型別要儘量一致,個數也要一致
// 由於C語言編譯器過於寬泛,所以不怎麼嚴格
int add_test(int a, int b)//int a = 11.0賦值的操作,賦值回自動完成型別轉換
{
return a + b;
}
int main03(void)
{
printf("%d", add_test(11.9, 2, 3, 5, 10, 12));
system("pause");
}
//05.小數型別在進行整數的過程當中:
// 只會進行取整運算,不涉及到四捨五入的情景
int add_test1(int a, int b)//return也會完成資料型別的轉換
{
return 13.9;
}
int main04(void)
{
printf("%d \n", add_test1(1, 2));
//int a = 10;
//a + 1 = 9;
system("pause");
}
///return.c
#include <stdio.h>
#include <stdlib.h>
//01.C語言編譯器當中的函式特點:
// 如果函式表明了需要返回值型別,需要返回值
// 你如果不通過return關鍵字正確的返回值,那麼編譯器不會進行報錯
// 但是程式最終的結果不正確結果自負
int addx()
{
return 1;
}
int main05(void)
{
printf("%d \n", addx());
system("pause");
}
//02.函式形式引數和返回值詳解:
// (1).都存在有副本機制:
// 副本資料可能的儲存位置(暫存器-->快取器-->棧記憶體-->堆記憶體)
// (2).都存在資料型別轉換:
// 自動型別轉換(小-->大)+強制型別轉換(大-->小)
int getnum()
{
int num;
printf("%p \n", &num);
num = 10;
return 10.9;//return有副本機制,在暫存器,快取,記憶體,堆記憶體(編譯器根據資料特點決定)
system("notepad");//當前的函式塊兒語句,由於處於return關鍵字之後,所以不會有被執行到的機會
//int data = num;//副本機制模擬
//正規化副本機制,都會通過賦值,賦值就會發生自動型別轉換|強制型別轉換特點
}
void show()
{
system("notepad");
return;
}
//03.結束多層迴圈的方式特點:
// goto:結束多層迴圈,但是函式並未彈棧(還未出棧)
// return:結束多層迴圈,函式發生彈棧(直接出棧)
void showx()
{
for (int i = 1; i < 100; i++)
{
if (i % 13 == 0)
{
printf("%d ", i);
return;//迴圈內部,結束迴圈-->區別於goto迴圈的跳轉特點
}
}
}
int main06(void)
{
//return;//main函式意味著退出
printf("%d \n", getnum());
showx();
system("pause");
}
//04.&getnum();所涉及到的問題分析:
// 1.函式返回的副本資料的原本可能存在於:
// 暫存器-->快取器-->棧記憶體-->堆記憶體
// 1.getnum();這個函式返回的是一個資料
// 具體的一個資料,不涉及到變數概念,不涉及到記憶體概念
// 所以不能通過取地址符進行操作(資料:是右值,不是左值)
int main07(void)
{
//printf("%d \n", &getnum());//不是左值,右值,右值存在於暫存器內部
system("pause");
}
int add(int a, int b)//add(int a, int b)-->int
{
return a + b;
}
int main08(void)
{
printf("%d \n", add(add(add(1, 2), 3), 4));
system("pause");
}
內容概要(05):過程.c
內容概要:函式執行過程
#include <stdio.h>
#include <stdlib.h>
int main01(void)
{
printf("main 上 \n");
void print1();//C語言建議新增宣告,新增了宣告只有一定不會出錯,沒有宣告可能會出錯
print1();
printf("%d \n", add(-1, 0));//引數多了可以編譯,但是不能保證結果正確,引數少了不可以編譯正確
printf("main 下 \n");
system("pause");
}
int add(int a, int b)
{
return a + b;
}
void print1()
{
printf("print1 上 \n");
printf("print1 下 \n");
}
程式片段(06):go.c
內容概要:函式引數的運算順序
#include <stdio.h>
#include <stdlib.h>
void show(int a, int b)
{
printf("a = %d, b = %d \n", a, b);
}
int main01(void)
{
int a = 5;
show(a, a++);//6,5
system("pause");
}
//01.函式形式引數和區域性變數特點詳解:
// 1.所以測試環境均為Release環境(標準)
// 2.分特點詳解:
// 形式引數:
// 資料進棧順序:決定實參執行順序
// 從右往左
// 資料對映順序:
// 從左往右
// 資料進棧: 資料對映:
// 3 丟失(多餘)|垃圾(少了)
// 2 -> b
// 1 -> a
// 資料地址順序:
// 先進棧的形式引數位於高地址
// 後進棧的形式引數位於地地址
// 區域性變數:
// 由於程式碼是從下往上進行進棧的
// 所以變數也是從下往上進行壓棧的
// 先壓棧的變數位於高地址
// 後壓棧的變數位於地地址
// 壓棧過程當中只是第一次初始化資料
// 高地址到低地址的壓棧過程
// 壓棧之後的執行過程決定最終的資料特點
// 低地址往高地址的執行特點(正好適應程式的從上往下執行特點)
int add(int a, int b)
{
printf("&a = %d, &b = %d \n", &a, &b);
printf("a = %d, b = %d \n", a, b);
int x = 1;
int y = 2;
printf("&x = %d, &y =%d \n", &x, &y);
printf("x = %d, b = %d \n", x, y);
return a + b;
}
int main02(void)
{
//printf("%d \n", add(1, 2));
printf("%d \n", add(1, 2, 3));
system("pause");
}
程式片段(07):main.c
內容概要:CodeBlocks測試
#include <stdio.h>
#include <stdlib.h>
void show(int a, int b)
{
printf("a=%d,b=%d", a, b);
}
int main()
{//效果一致
int a = 5;
show(a, a++);
getchar();
}
程式片段(08):可變引數.c
內容概要:可變引數
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>//標準引數:模擬可變引數必需的標頭檔案
int add(int num, ...)//...代表可變引數
{
int res = 0;//結果
va_list argp;//儲存引數開始的地址
va_start(argp, num);//從首地址開始,讀取num後面的資料
for (int i = 0; i < num; i++)
{
res += va_arg(argp, int);//讀取一個資料並且按照int型別進行二進位制資料的解析
}
va_end(argp);//結束讀取
return res;
}
int main01(void)
{
printf("%d \n", add(3, 1, 2, 3));
printf("%d \n", add(4, 1, 2, 3, 4));
printf("%d \n", add(5, 1, 2, 3, 4, 5));
system("pause");
}
int main02(void)
{
printf("%d, %d, %d \n", 1, 2, 3);
printf("%d, %s, %c, %d \n", 1, "123", 'A', 4);
system("pause");
}
//01.可變引數使用方式一:
// 1.將第一個引數作為確定可變引數列表當中所儲存的引數總個數
// 2.可變引數使用流程總結:
// (1).包含標頭檔案:
// #include <stdarg.h>
// (2).確定函式宣告:
// void vatest(int count, ...);
// (3).進行引數使用:
// va_list argp;//儲存可變引數列表的首地址(類似於陣列的特點)
// va_start(argp, count);//從首地址開始,讀取count個引數
// va_arg(argp, type);//按照type型別讀取當前可變引數列表當中讀取到的位置所在的資料
// va_end(argp);//結束可變引數列表的讀取狀態
void go(int num, ...)
{
va_list argp;//儲存可變引數列表開始的首地址
va_start(argp, num);//從首地址開始,讀取num個的資料
for (int i = 0; i < num; i++)
{
char str[50];
//sprintf(str, "%s", va_arg(argp, char *));
//system(str);//只要呼叫一次va_arg就從可變引數列表當中讀取一個引數
system(va_arg(argp,char *));
//讀取一個二進位制資料並且按照char *型別解析
}
va_end(argp);//結束讀取
}
int main03(void)
{
go(3, "notepad", "calc", "tasklist & pause");
system("pause");
}
//02.可變引數的使用方式二:
// 1.不採用可變引數前置引數作為讀取結束條件,而是按照可變引數列表的結束特點進行讀取
// 2.不定長可變引數列表的使用特點:
// (1).引入標頭檔案:
// #include <stdarg.h>//支援可變引數列表的使用
// (2).函式宣告特點:
// void vatest(int start, ...);//可變引數列表的函式宣告
// va_list argp;//儲存可變引數列表的首地址(類似於陣列原理)
// vastart(argp, start);//暫定可變引數列表當中引數的讀取個數
// int argvalue = start;//確定可變引數列表的首個引數
// do
// {
// int value=argvalue;//使用可變引數列表當中的資料
// argvalue = va_arg(argp, int);//不斷的按照指定型別進行讀取
// }while(argvaue != -1);
// va_end(argp);結束讀取
void showint(int start, ...)
{
va_list argp;//儲存引數開始的地址
va_start(argp, start);//從首地址開始讀取資料,暫定為讀取start個資料
int argvalue = start;//第一步初始化
do
{
printf("\n %d", argvalue);
argvalue = va_arg(argp, int);//不斷讀取
} while (argvalue != -1);
va_end(argp);//結束讀取
}
int main04(void)
{
//showint(1, 2, 3, 4, -1);
showint(1, 2, 3, 4, 5, -1);
system("pause");
}
程式片段(09):C宣告.c+函式宣告.c+int.c+全域性與區域性衝突
內容概要:C語言函式宣告+全域性變數與區域性變數
///C宣告.c
#include <stdio.h>
#include <stdlib.h>
//01.使用函式的特點:
// 1.()用於對函式進行標識
// 2.進行函式呼叫必須明確呼叫的型別:
// 區分變數訪問和函式呼叫("()")
//02.C語言的編譯器特點:
// 1.由於VC2015這個編譯器當中自動包含了庫目錄和附加依賴項
// 所以使用C語言函式的時候,可以沒有宣告語句,因為C語言編譯器
// VC2015會自動到靜態庫目錄和附加依賴項當中去進行查詢,
// 自動查詢所需呼叫的函式,引數多了,少了都可以進行呼叫(前期,可以;後期,不可以)
// 2.函式呼叫觸發了C語言編譯器VC2015的自動定位功能
//03.庫函式的查詢:
// 分為系統庫函式和定義庫函式的查詢
// C語言編譯器VC2015支援自動查詢
// C++編譯器不支援自動查詢
int main01(void)
{
printf("Hello China! \n");//()是個函式
printf;//引用函式必須要進行宣告
system("pause");
}
int main02(void)
{
add(2, 3);
print();
//add;
system("pause");
}
int print(){}
int add(int a, int b)
{
return a + b;
}
///函式宣告.c
#include <stdio.h>
#include <stdlib.h>
//01.C語言當中的宣告和定義特點:
// 1.宣告可以有多個,但是定義只能有一個
// 2.函式宣告的時候可以不用指明形參的名稱
// 但是定義的時候必須指定形參的名稱
// 並且函式宣告的形參名稱和函式實現的形參名稱可以不同
// 3.函式的宣告和定義與變數的宣告和定義類似
int add(int a, int b);//宣告
int add(int x, int y);//宣告
int add(int h, int j);//宣告
int add(int k, int l);//宣告
int main03(void)
{
printf("%d", add(1, 2));
system("pause");
}
int add(int a, int b)
{
return a + b;//函式的定義
}
//int add(int a, int b)
//{//宣告可以有多個,但是定義只能有一個
// return a + b;//函式的定義
//}
///int.c
#include <stdio.h>
#include <stdlib.h>
//全域性變數:int a = 10;
//int a;//全域性變數當做宣告看待,如果沒有初始化,將會被編譯器預設賦予0
int a;
//int a = 9;//int a;//全域性變數宣告,int a = 10;//全域性變數定義,宣告可以有多個,定義只能有一個
int main04(void)
{
printf("%d \n", a);
system("pause");
}
//01.區域性變數和全域性變數的使用總結:
// 1.是否具備宣告和定義之間的區別:
// 函式和全域性變數都有區別
// 區域性變數沒有區別(都當做定義來對待)
// 2.全域性變數的生命週期:
// 程式程式碼一旦載入進程式碼區就已經存在了
// 全域性變數優先於main函式的存在
// 3.全域性變數的作用域:
// 從當前檔案的定義位置開始,到跨檔案的範圍
// 內都可以進行訪問的到
int main05(void)
{
//區域性變數沒有宣告和定義的區別
//int a = 10;//變數重名,區域性變數
//int a;
////int a = 10;//區域性變數
//int a;
//int a;
//int a;
a = 9;
system("pause");
}
void go()
{
a = 11;
}
///全域性與區域性衝突.c
#include <stdio.h>
#include <stdlib.h>
//01.全域性變數和區域性變數內容總結:
// 1.全域性變數很容易被區域性變數覆蓋
// 2.全域性變數可以被多個函式所共享,方便於讀寫操作
// 3.全域性變數在如果只是進行宣告瞭,但是沒有被定義
// 那麼系統會為其定製一個預設的初始化值0
// 4.全域性變數可以在跨檔案的情況下進行呼叫:
// 容易出現全域性變數重合(型別相同,名稱相同)
// 5.區域性變數和全域性變數重名的情況之下,會覆蓋掉
// 全域性變數
// 6.當區域性程式碼塊兒當中存在和全域性變數相同的變數
// 那麼區域性程式碼塊兒的操作將遮蔽對全域性變數的操作
// 相當於對全域性變數的操作無效
int a;
int a;
int a = 3;
int main06(void)
{
printf("%d \n", a);
system("pause");
}
int main07(void)
{
int a = 10;
printf("%d \n", a);//區域性變數覆蓋全域性變數,重名
{
printf("%d \n", a);
int a = 13;
printf("%d \n", a);//內部塊兒語句會遮蔽外部變數
}
printf("%d \n", a);//區域性變數覆蓋全域性變數,重名
system("pause");
}
程式片段(10):test.cpp
內容概要:宣告與定義差別
#include <stdio.h>
#include <stdlib.h>
//01.函式的宣告和定義詳解:
// 1.函式的宣告可以有多個,定義只能有一個
// 2.函式的宣告可以沒有引數名稱,但是必須有引數型別
// 3.函式宣告的引數名稱可以和函式的定義的引數名稱不一致
// 但是要求型別必須一一對應
int add(int a, int b);//宣告要與定義相匹配
int add(int x, int y);//宣告的變數名可以省略,可以和定義的變數名不一致,但是要求型別必須一致
int add(int a, int b)
{
return a + b;
}
int main01(void)
{
add(1, 2);
system("pause");
}
程式片段(11):baidu.c+stack.c
內容概要:函式呼叫流程簡單遞迴
///baidu.c
#include <Windows.h>
//01.動態庫(Dll)知識+遞迴呼叫知識:
// (1).Dll注入技術可以讓任何程式掛掉
// (2).針對於像360這樣的安全軟體
// 需要採用sys層面的技術進行破壞
// 因為360安全衛士是基於驅動層面開發
// (3).如何匯出動態庫(Dll)?
// 1).在原始函式宣告之前新增
// _declspec(dllexport)
// 2).配置專案屬性(配置型別)
// 動態庫(.dll)
_declspec(dllexport) void go()
{
Sleep(1);
go();
}
///stack.c
#include <stdio.h>
#include <stdlib.h>
//線性遞迴001:將一個整數進行逆序輸出
// 遞迴函式的規律總結:
// 是否需要返回值?
// 如果有累變(加,減,乘,除)效果,就需要返回值型別,否則一般情況之下是不需要返回值型別的
// 是否需要形式引數?
// 如果涉及到遞迴函式當中每層遞迴函式呼叫的資料使用,只是數值意義上的使用,就需形式引數
// 資料使用等同於資料關聯,等同於遞迴函式呼叫層當中的資料傳遞,形參變數資料傳遞
// 是否需要類似於for迴圈結構的迴圈初始化條件?如果有,就需要形式引數,如果沒有,則無需
// 是否逐漸逼近類似於for迴圈的迴圈終止條件?
// 遞迴入口+遞迴出口
// 是否涉及到資料的列印顯示順序?
// 列印語句如果需要順序,就寫於遞迴呼叫之前;
// 列印語句如果需要逆序,就謝宇遞迴呼叫之後.
void revInt(unsigned int value)//類似於for迴圈的迴圈初始化條件
{
unsigned int quotient = value / 10;//空間複雜度1+時間複雜度1
if (quotient != 0)//類似於for迴圈的迴圈判斷條件
revInt(quotient);//類似於重複一次for迴圈的迴圈執行體
putchar(value % 10 + '0');//列印順序為逆序(由於執行時堆疊的即時列印特點決定)-->這樣列印的原因是因為跨平臺性可移植性比較好!
}
//線性遞迴002:輸入9,就順序|逆序列印從1~9之間的整數
void printInt1(int value)
{
//putchar(value + '0');//逆序列印
if (value - 1 > 0)//時間複雜度2
printInt1(value - 1);
putchar(value + '0');//順序列印
}
//線性遞迴003:列印任意一個區間[value1,value2]之間的整數
// 要求一:(順序|逆序)
// 要求二:從value1-->value2|value2-->value1
void printInt2(int value1, int value2)
{
printf("%d \n", value1);
if ((value1 + 1) <= value2)
printInt2(value1 + 1, value2);
}
//線性遞迴004:列印字串當中的每一個字元(反轉效果)
// 1.嚴格區分字元陣列和字元指標陣列之間的區別
// 2.putchar();和printf();函式之間的區別
// putchar();不具備處理字元指標所指向的實體的作用
// printf();具備處理字串指標所指向的實體的作用
// 3.putchar();每次只會列印單個字元,所有的字元拼裝在
// 一起之後,就是一個字串
// 4.putchar();遇到字元就直接列印字元本身,不會出現變故
// 放在括號內與括號外是有區別的(是否具備判斷效果)
// 決定最後一次列印的特點
void printStr(char *str)//類似於for迴圈的迴圈初始化條件
{
if (*str)//類似於for迴圈的迴圈判斷條件
{
//printStr(str + 1);//類似於for迴圈的迴圈趨於結束的條件
printStr(++str);//簡化形式
putchar(*str);//類似於for迴圈的迴圈執行體-->putchar();不具備處理字元指標所指向的實體的作用
}//如果不將putchar(*str);放在括號的內部,那麼最後一層遞迴函式在進行列印的時候會將NUT|('\0')|0給列印出來
//也就是最終多列印了一個不可見字元
}
//線性遞迴005:列印任意一個整數的階乘結果
unsigned int calFact1(unsigned int value)//int表明遞迴函式的一層函式呼叫就能返回該階乘結果,unsigned int num表明類似於for迴圈的迴圈初始化條件,或者說要做一件事情,直接所需的引數
{//如同:我要求取某個數的階乘,你就得給我這個資料,我就根據這個資料算出一個階乘結果反饋給你
if (0 == value || 1 == value)//類似於無限迴圈的結束條件,也就是遞迴函式的出口,結束最後一層遞迴函式的呼叫,不用再進行遞迴呼叫,而且不用再執行最後一層遞迴函式剩餘的語句(直接出結果)
return 1;//由於return關鍵字的特殊性,所以最後一層遞迴函式的執行依賴於它-->return直接終止函式,所以不會在執行一層遞迴呼叫以及一層遞迴呼叫之後的語句
calFact1(value - 1);//讓無限迴圈不斷的執行下去,至於迴圈的終止條件我們無需關注,因為上面一段兒已經決定了
return value * calFact1(value - 1);//對於該行語句,不用關注其執行流程,只需關注,value=value*value!,只是用於一次遞迴函式的呼叫就能完成意向功能,剩餘遞推關係讓計算機去做,我們不關注
}
unsigned int calFact2(unsigned int value)
{
if (0 == value || 1 == value)
return 1;
else//原理:一次求解,絕對有結果,至於結果的遞推關係我們無需去關注,只需要關注的是一次遞迴函式的呼叫到底能夠完成什麼樣兒的功能,至於如何遞推,如果計算,那都是計算機的事情
return value * calFact2(value - 1);
}
//線性遞迴006:將一個正整數轉化為其的二進位制表現形式列印出來
void printIntToBin1(unsigned long value)//使用long型別意味著更好的程式跨平臺性(可移植性)-->不像int型別(16位佔用2個位元組(short),32位以上佔用4個位元組(long))-->long始終佔用4個位元組
{
unsigned long quotient = value / 2;//空間複雜度1+時間複雜度1
if (0 != quotient)
printIntToBin1(quotient);//不斷的執行列印除以2之後的餘數(二進位制位)
putchar((value % 2) + '0');//餘數逆置,一次遞迴呼叫意味著逆序列印一個二進位制位,即使商為0,也需要列印出這個商為0情況之下的餘數0
}
void printIntToBin2(unsigned long value)
{
unsigned long remainder = value % 2;
if (value / 2 > 0)//時間複雜度2
printIntToBin2(value / 2);
//putchar(0 + i);
putchar(remainder ? '1' : '0');
}
//線性遞迴007:迴圈轉遞迴剖析
// 1.任何一個迴圈都可以轉換為遞迴
// 2.任何一個遞迴都可以轉化為迴圈+棧
void loopToRecursion(long value)
{
printf("%d, %p \n", value, &value);
if (value < 9)
loopToRecursion(value + 1);
}
//01.遞迴的分類:
// 1.函式呼叫方式:
// 直接呼叫自己-->直接遞迴-->簡單遞迴
// 間接呼叫自己-->間接遞迴-->複雜遞迴
// 2資料結構模型:
// 線性遞迴:f(n)=f(n-1)+n;
// 樹狀遞迴:f(n)=f(n-1)+f(n-2);
// 圖狀遞迴:
//02.遞迴的要點:
// 1.遞迴的滿足要點:
// 遞迴入口+遞迴出口
// 2.遞迴的函式要點:
// 執行時堆疊
//03.所涉及到的知識點:
// 任何一個(0~1)之間的整數加上一個('0');
// 那麼該表示式所獲得的最終結果就是該
// 整數所對應的ASCII碼值
int main01(void)
{
//revInt(1234);
//printInt1(9);
//printInt2(-55, 55);
//printStr("123456789");
//char str[10] = "123456789";//區分字元陣列和字元指標陣列
//printStr(str);
//printf("%d \n", calFact1(10));
//printf("%d \n", calFact2(10));
//printIntToBin1(106);
//printIntToBin2(106);
//loopToRecursion(0);
system("pause");
}
//04.採用無線迴圈列印一段兒字串
int main02(void)
{
//system("notepad");//同步函式,一次只開啟一個記事本,需要等待使用者結束這個記事本在往下執行
printf("12345");
main02();
}
//05.輸入一個整數,就列印整數個字串
void intPrintStr(char *str, unsigned long value)
{
if (0 == value)
return;
if (value - 1 > 0)
intPrintStr(str, --value);
printf("%s \n", str);
}
//06.控制Notepad的執行次數
void printNontepad(unsigned long value)//void:只列印資料,不需要返回 value:for迴圈的初始化條件
{
if (0 == value)//value:判斷for迴圈是否啟用迴圈執行體
{
return;
}
else
{//5->4->3->2->1:五個對映對-->執行五次
system("notepad");//這句話放在前面還是後面都是一樣的
printNontepad(value - 1);//重複執行一次回退的遞迴迴圈層
}
}
//07.輸入任意一個正整數N,用遞迴實現從1~N之間的累加
unsigned long addNum(unsigned long value)
{
if (1 == value)
return 1;
return addNum(value - 1) + value;
}
int main03(void)
{
//intPrintStr("notepad", 5);
//printNontepad(5);
//printf("%d \n", addNum(100));
system("pause");
}
程式片段(12):線性遞迴.c+樹狀遞迴.c+漢諾塔.c
內容概要:遞迴
///線性遞迴.c
#include <stdio.h>
#include <stdlib.h>
//01.遞迴運算常用解析思想:
// 1.數學歸納法
// 2.例如:對等差數列的描述
// f(0)=0;
// f(n)=f(n-1)+x;
// 注:關於關係式的遞迴推導我們不用關心,因為計算機內部自己會去進行推導
// 記住!計算機最大的用處不是思考,而是計算
//02.main函式的特點:
// 如果為main函式定義一個int型別的變數,
// 那麼編譯器會自動為該int型別的變數預設初始化一個1(預設初始化)
// 至於手動初始化需要通過命令列對程式進行啟動
void main01(void)
{
main01();//通過遞迴實現的死迴圈
}
//03.通過遞迴模擬迴圈實現迴圈次數的控制:
void loopToRecursion(unsigned int value)
{
if (value >= 5)
return;
else
loopToRecursion(value + 1);
system("notepad");
}
//04.求取0到任何一個正整數的之間的所有正整數的和?
// f(100)=100+f(99);
// f(100)=100+99+f(98);
// f(100)=100+99+98+f(97);
// f(n)=n+f(n-1);
unsigned long countInt(unsigned long value)
{
if (value == 1)
return 1;
return value + countInt(value - 1);
}
void uLongToBin(unsigned long value)
{
unsigned long quotient = value / 2;
if (quotient != 0)
uLongToBin(quotient);
putchar(value % 2 ? '1' : '0');
}
int main02(void)
{
//loopToRecursion(0);
//printf("%lu \n", countInt(100));
uLongToBin(100);
system("pause");
}
///樹狀遞迴.c
#include <stdio.h>
#include <stdlib.h>
//斐波那契數列:
// 實際問題:
// 1對兔子,2個月以後可以生育,從可以開始生育之後,每個月都能生育一對兔子
// 數學描述:
// 1------->1
// 2------->1
// 3------->2
// 4------->3
// 5------->5
// 6------->8
// f(n)=f(n-2)+f(n-1);
// 數學描述的特點:
// f(n-2):
// 描述的是這個月較上個月能夠多增加的兔子數目
// 相差2的原因是因為一對兔子只有相隔兩個月才具備生育一對兔子的能力;
// 也就才會生育一對兔子
// f(n-1):
// 描述的是上個月所有的兔子總計數目
// f(n):
// 本月總共的兔子數目
unsigned long countRabbit1(unsigned long month)
{
if (1 == month || 2 == month)
return 1;
return countRabbit1(month - 2) + countRabbit1(month - 1);
}
void countRabbit2(unsigned long month)
{
if (1 == month || 2 == month)
printf("1 \n");
else
{
int f1 = 1;
int f2 = 2;
int f3 = f1 + f2;
for (int i = 3; i < month; i++)
{//通過迴圈輪替的方式向前進行推進計算(計算機處理計算問題)
f3 = f1 + f2;
f1 = f2;
f2 = f3;
}
printf("f3= %lu \n", f3);
}
}
//02.樹狀遞迴內容總結:
// 1.樹狀遞迴速度很慢,遞迴很慢,函式的呼叫和返回都需要時間
// 2.任何遞迴都可以轉換為迴圈加棧
// 遞迴=迴圈+棧
int main01(void)
{
printf("%lu \n", countRabbit1(40));
countRabbit2(40);
system("pause");
}
///漢諾塔.c
#include <stdio.h>
#include <stdlib.h>
//01.遞迴解決問題的思想:
// 1.明確需要解決的問題是什麼?
// 確定遞迴函式的宣告
// 2.明確解決問題的重複步驟是什麼?
// 確定遞迴函式的實現
// 3.明確遞迴的入口和出口條件?
// 什麼時候開始遞迴;
// 什麼時候結束遞迴
void hanoiTower(unsigned long num, char x, char y, char z)//類似於for迴圈的迴圈初始化條件
{//只需要列印結果,不需要累變特點-->void;函式的實際意義-->hannoTower(unsigned long num, char x, char y, char z);
if (1 == num)
{//類似於for迴圈的迴圈判斷條件-->遞迴終止繼續執行的條件
//printf("%c-->%c \n", 'A', 'C');//直接搬動
printf("%c-->%c \n", x, z);
return;
}//遞迴狀態時刻被保留與堆疊當中-->當前函式在執行時所能訪問的內容只有執行時堆疊當中的內容
//類似於for迴圈的迴圈執行體內容-->通用問題化解方式
hanoiTower(num - 1, x, z, y);//A-->B
printf("%c-->%c \n", x, z);//A-->C
hanoiTower(num - 1, y, x, z);//B-->C
}
int main04(void)
{
hanoiTower(4, 'A', 'B', 'C');
system("pause");
}
相關文章
- 20160217.CCPP體系詳解(0027天)
- 20160124.CCPP詳解體系(0003天)
- 20160125.CCPP詳解體系(0004天)
- 20160126.CCPP體系詳解(0005天)
- 20160127.CCPP體系詳解(0006天)
- 20160130.CCPP體系詳解(0009天)
- 20160203.CCPP體系詳解(0013天)
- 20160211.CCPP體系詳解(0021天)
- 20160213.CCPP體系詳解(0023天)
- 20160214.CCPP體系詳解(0024天)
- 20160215.CCPP體系詳解(0025天)
- 20160224.CCPP體系詳解(0034天)
- 20160218.CCPP體系詳解(0028天)
- 20160219.CCPP體系詳解(0029天)
- 手遊《天地劫》的三天體驗——深度系統剖析及玩法詳解
- 20160122.CCPP詳解體系(0001天)
- 20160123.CCPP詳解體系(0002天)
- 20160128.CCPP體系詳解(0007天)
- 20160131.CCPP體系詳解(0010天)
- 20160204.CCPP體系詳解(0014天)
- 20160205.CCPP體系詳解(0015天)
- 20160210.CCPP體系詳解(0020天)
- 20160212.CCPP體系詳解(0022天)
- 20160207.CCPP體系詳解(0017天)
- 20160225.CCPP體系詳解(0035天)
- 20160226.CCPP體系詳解(0036天)
- 20160227.CCPP體系詳解(0037天)
- 20160222.CCPP體系詳解(0032天)
- 20160221.CCPP體系詳解(0031天)
- 20160201.CCPP體系詳解(0011天)
- 20160202.CCPP體系詳解(0012天)
- 20160209.CCPP體系詳解(0019天)
- 20160216.CCPP體系詳解(0026天)
- 20160206.CCPP體系詳解(0016天)
- 20160208.CCPP體系詳解(0018天)
- 20160223.CCPP體系詳解(0033天)
- 20160220.CCPP體系詳解(0030天)
- MySQL體系結構詳解MySql