C語言學習之路-由淺入深(快速掌握c基礎)
註明:本部落格只適用於有java基礎的人觀看,因為java是c的升級版,所以下面我們會用java來與c比較
1.第一個C程式:HelloWorld.c
首先我這裡是使用這個軟體編寫的:下載地址
安裝過程一直next就好了
安裝後在你的程式碼目錄建立一個HelloWorld.c,程式碼目錄可以隨意,然後雙擊開啟HelloWorld.c就可以預設進入我們下載的c開發軟體中,如圖:
其中圖中標記為我們java中常用的編譯和執行
下面就可以開始我們c語言的第一個helloWorld,通常說從helloWorld開始可以快速成為大神:
#include <stdio.h> // java import xxx.xx.pack 引用函式的宣告
#include <stdlib.h>
main() // 程式的入口函式
{
//程式碼檔案目錄是本級目錄則會執行成功,否則會找不到該類
printf("Hello world !\n"); // 控制檯列印一個hello world
//如果不是本級目錄必須先指定目錄地址,才能執行成功
system("java -classpath c:\\ HelloWorld");
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
程式碼中我們首先可以看到
1、
這個東西拿java來說就是我們通常的包名
2、main函式是主入口,和java一樣
3、c語言中必須使用system(“pause”);使命令列暫停,方便觀察程式的執行結果,否則結果會在你眼中一閃而過
2.C語言的基本型別與JAVA基本型別對比:
上一節中我們開始我們的第一個helloWorld,下面我們講學習c中的資料型別:
// java資料型別 和長度int 4個位元組 double 8個位元組 float 4個位元組 long 8個位元組
// short 2個位元組 boolean 1個位元組 char 2個位元組 byte 1個位元組
// char, int, float, double, signed, unsigned, long, short and void
// c語言中 資料型別比java少一些 在c語言中沒有 boolean型別的資料 int 1 代表真 0 代表假
// c 語言中沒有String型別的資料 java中表示一個字串 String , c語言中表示字串 通過char型別的陣列來表示字串
// c 語言沒有byte型別 所有用char的型別表示byte型別
#include <stdio.h>
#include <stdlib.h>
// sizeof(); c語言的一個函式 可以把 某種資料型別的長度獲取出來 int
main()
{ // %d 類似sql語句的? 佔位符
printf("char的長度為%d\n", sizeof(char));//1
printf("int的長度為%d\n", sizeof(int));//4
printf("float的長度為%d\n", sizeof(float));//4
printf("double的長度為%d\n", sizeof(double));//8
printf("long的長度為%d\n", sizeof(long));//在不同的情況下可能會有不同的大小,但是long的長度一定比int大 4
printf("short的長度為%d\n", sizeof(short));// 2
//signed, unsigned, 資料型別的修飾符
// signed int ; 代表的是有符號的int的資料
// unsigned int ; 無符號的int資料
printf("signed int的長度為%d\n", sizeof( signed int));//4
printf("unsigned int的長度為%d\n", sizeof( unsigned int));//4
// 符號的修飾符 只能修飾 整數型別的資料 long int
// 不能修飾 浮點型的資料 float double
// printf("signed float的長度為%d\n", sizeof( signed float));
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
從上面我們可以知道,c語言有以下幾種資料型別:
char, int, float, double, long, short
使用char表示java的byte型別資料
使用char資料去表示java中String型別的資料
c的兩種修飾符
signed, unsigned,
3.C語言中的輸入輸出函式:
上一節我們瞭解c語言的基本資料型別,下面我們看看c的輸入輸出函式:
/*%d - int
%ld – long int
%c - char
%f - float
%lf – double
%x – 十六進位制輸出 int 或者long int 或者short int
%o - 八進位制輸出
%s – 字串
Int len;
Scanf(“%d”,&len);*/
#include <stdio.h> // java import xxx.xx.pack 引用函式的宣告
#include <stdlib.h>
main() // 程式的入口函式
{ int i = 3;
float f = 3.1415;
double d = 6.2815;
char c = 'A'; //通過單引號定義字元
short s = 2;
//輸出的時候佔位符和資料型別必須一一對應,否則得不到正確的結果
printf("int i=%d\n",i);
printf("float f=%f\n",f);
printf("char c=%c\n",c);
printf("double d=%lf\n",d);
printf("short s=%d\n",s);
/*char arr[20] ; //定義一個長度為20的陣列
//java中是System.in
scanf("%s",arr); // 從鍵盤接受一個字串,放在c數組裡面
//java中是System.out
printf("s =%s\n",arr);
*/
int j ;
scanf("%d", &j);//&代表的是取地址
//從控制檯得到j地址所代表的址輸出
printf("j=%d\n",j);
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
從程式碼中我們知道c語言的
輸入:scanf();函式 根據地址去輸入&j
輸出:printf();函式
4.指標入門:
上一節我們學到了輸入輸出,下面我們將學習一個新名詞指標
首先java中是沒有指標這個名詞的,
指標是什麼? 指標就是一個地址
地址代表的就是一塊記憶體空間
指標變數是什麼? 用來存放指標
從上面我們就可以知道java中的記憶體控制元件就是c語言中的指標,下面我們看下程式碼:
#include <stdio.h>
#include <stdlib.h>
main()
{
int i =5;// 定義一個int 型別的變數i 值 =5
//%#X表示16進位制的地址佔位符
printf("i的地址 %#X\n",&i);
//獲取i的地址,&i就是一個指標
// &i;
//定義一個指標變數,資料型別*
int* p ; // 指標變數 定義一個int* 型別的變數p
//其他兩種表示方式:int *p, int * p;
p = &i; // 就是把i的指標賦給指標變數p ,現在指標變數p裡面存放的內容(資料) 就是i的地址
printf("p裡面的內容為(i的地址) %#X\n",p);
//*號 操作符
// *號的幾種含義
//1 . *號放在某種資料型別的後面,代表就是這種資料型別的指標 int* float*
//2 . *號 代表一個乘法符號 3*5 = 15;
//3 . *號放在一個指標變數的前面 -> 代表取這個指標變數所存放的地址裡面對應的資料
printf("i=%d\n",i);
printf("*p的值%d\n",*p);
// 改變p的值 會不會影響i的值?
//p = NULL;
// printf("i=%d\n",i);//5
// 改變i的值 會不會影響p的值?
// i = 100;
// printf("p裡面的內容為(i的地址) %#X\n",p);
// 通過上述實驗 p和 i 是兩個不同的變數 ,改變i的值 不會影響 p的值,同理,更改p的值 也不會影響i的值
// 更改*p的值 會不會影響i的值
// *p = 88;
// printf("i=%d\n",i); //88
// 更改i的值 會不會影響 *p的值呢?
// i = 99;
// printf("*p的值%d\n",*p); //99
//*p 和i 其實代表的是同一個變數,代表是同一塊記憶體空間
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
從程式碼中我們可以知道:
指標就是地址,而c語言中我們在變數前面加上&符號就可以到地址也就是指標如:int i=9;那麼i的指標表示方式是&i
指標變數就是用來存放指標的一個變數,如:資料型別* 變數名;如:int * p 這樣就是一個指標變數,將我們上面的&i=p這樣我們就將i的指標放在了指標變數p中
得到指標變數中的值使用符號*,例如我們將上面指標變數p的指標i的值取出來表示就是:*p
*p 和i 其實代表的是同一個變數,代表是同一塊記憶體空間
5.指標介紹:
上一節我們已經知道了指標和指標變數的用法,下面我們將通過一個小程式進一步說明指標
#include <stdio.h>
#include <stdlib.h>
main()
{
// 所有的變數都會分配一塊記憶體空間
// 指標就是用來表示一塊記憶體空間的地址的
// 地址可以用過 &這個符號獲取到某個變數的在記憶體中的地址
// 這個地址如果想把他存放起來 就需要有一個變數 去存放這個地址
// 存放記憶體地址的變數 就是指標變數
// 指標和指標變數
// 指標是用來表示一塊記憶體地址的,
// 指標變數是用來存放一個記憶體地址的 .
//
printf("ready go! 剩餘時間60秒\n");
int time = 60;
printf("time變數對應的記憶體地址為%#X\n", &time);
for(;time>0;time--){
printf("剩餘時間%d\n",time);
sleep(4000);
}
printf("遊戲結束");
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
從程式碼中我們編寫了一個for迴圈,列印time指標值,可以在命令列程式已經在開始走了
從圖中可能大概你們可以看到我使用一個外掛軟體找到了time指標值,外掛地址
怎麼使用外掛軟體:
- 首先點選圖中的箭頭圖示,
- 彈出Process List在裡面找到我們的程式,然後點選,
- 左邊Address欄就會出現我們程式中的time地址
從圖中我們居然看到我們的程式執行到22的時候怎麼又從59開始了,正常情況下我們是21啊,這是怎麼回事呢?
哈哈,這其實就是用到了我們上圖 中的外掛,首先我們使用外掛找到我們的time地址,然後將time 的值改為60之後就發現命令列又從60開始了。
通過這個外掛我們就更加深刻了解指標的作用,下面我們將使用幾個案例去了解指標的一些細節
6.案例:使用指標交換兩個資料:
#include <stdio.h>
#include <stdlib.h>
// 問 java 中有值傳遞和引用傳遞 嗎? 他們的區別是什麼?
// 其實在java中只有值傳遞 , 沒有引用傳遞
// Person p = new Person(); p裡面存放的內容 就是person物件的地址
void swap2(int* p , int* q){ // 傳遞的形參為 i 和j 變數的地址
// *p 代表 i *q 代表就是 j
int temp;
temp = *p;
*p = *q;
*q = temp;
}
void swap1(int i ,int j){ // 形參 i 和j 跟主函式裡面的i和j是兩個不同的變數
printf("子函式 i 地址%#X\n",&i);
printf("子函式 j 地址%#X\n",&j);
int temp;
temp = i;
i = j;
j = temp;
}
main()
{
//利用指標 可以在子函式裡面修改主函式裡面的資料
int i = 3;
int j = 5;
printf("i=%d\n",i);
printf("j=%d\n",j);
printf("主函式 i 地址%#X\n",&i);
printf("主函式 j 地址%#X\n",&j);
/*/交換兩個數字
int temp;
temp = i;
i = j;
j = temp;
*/
//方法一中確實將i,j的值交換了,但是當函式一執行完,函式被回收,i,j就被銷燬了,交換失敗
// swap1(i,j);
//方法二接收的是i,j的地址,i,j在main函式裡,所以資料交換成功
swap2(&i,&j);
printf("交換後\n");
printf("i=%d\n",i);
printf("j=%d\n",j);
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
7.案例:使用指標獲取子函式的資料:
#include <stdio.h>
#include <stdlib.h>
//int** q 表示裡面存放的是一個int* q的指標
f(int** q){
int i = 3;
printf("子函式 i的地址 %#X\n",&i);
// *q 代表的就是p變數
*q = &i;
}
/**
使用指標的時候 不可以訪問已經被系統回收掉的資料
子函式執行完畢後 子函式裡面所有的區域性變數都會別系統回收
*/
main()
{
// 希望在主函式裡面去使用子函式裡面的變數 i
// f();
// 希望在主函式裡面得到子函式 裡面int i變數的地址
int* p ; //存放子函式f中 int i的地址的一個變數
f(&p);
/**在6中我們已經說過函式會被回收,
* 當f()函式一執行完,就會被回收,i就會被回收
* 但是回收是有一定的時間,所以如果我們在正在回收還未回收完全時去取值,
* 會得到正確的值或者地址,但是如果我們比如下面我先列印地址值,在去列印*p
* 此時p是i的地址值,而*p就不是了,因為此時i被回收了,但是我們註釋輸入地址值
* 此時*p就會成功列印i的值
*/
// printf("主函式 i的地址 %#X\n",p);
// printf("i的值為 %d\n",*p);
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
8.案例:使用指標返回一個以上的值
#include <stdio.h>
#include <stdlib.h>
// public List<Person> getPersons() {};
// public byte[] getbytes(){};
/*
如果讓子函式 更改主函式裡面的資料
如何讓子函式 返回一個以上的值
1.子函式的形參 為 主函式中要修改的變數的地址
2. 呼叫子函式的時候 把要修改的變數的地址 傳遞給子函式
3. 在子函式裡面 修改這個地址裡面存放的變數的內容
4. 主函式使用這個變數的時候 裡面的值就發生了變化
*/
int f(int* p, int* q){
*p = 33;
*q = 55;
}
main()
{
int i = 3;
int j = 5;
f(&i,&j);
printf("i=%d\n",i);//33
printf("j=%d\n",j);//55
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
6.7.8案例總結:
1.子函式在main函式中呼叫結束後會被銷燬,隨之傳入的形參也會被銷燬
2.main函式裡的資料要想通過子函式進行互動,子函式的傳入的引數必定是指標變數
3.方法中的返回值不能像java中一樣返回集合之類的資料,通過指標去返回多個數據。
9.指標的常見錯誤:
通過6、7、8節我們細緻的瞭解了指標,下面我們將講解指標的一些錯誤:
#include <stdio.h>
#include <stdlib.h>
main()
{
/* int* p; //定義一個指標變數 垃圾值 -> 野指標
//printf("*p=%d\n",*p);
*p = 1231;
// 立刻藍屏
//
指標變數如果沒有賦值就不能使用
*/
/* int d = 324233;
char* c; ; // 編譯錯誤 不符合的指標型別
c = &d;
printf("*p = %c\n",*c);
型別不相同的指標不可以互相轉換
*/
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
錯誤總結:
1.指標變數中必須得有指標,否則在取指標變數中的指標的值的時候會異常
2.不能給沒有定義指標的指標變數的指標的值賦值
3.指標變數和指標的資料型別必須一一對應
10、指標佔多少個位元組:
之前第二2節中我們知道了c的基本資料型別的位元組數,那麼指標佔多少個位元組呢?
看程式碼:
#include <stdio.h>
#include <stdlib.h>
main()
{
int i =3;
double d = 3.141692;
float f = 3.1423;
char c ='B';
int* ip = &i;
double* dp = &d;
float* fp = &f;
char* cp = &c;
//電腦不同輸入的值可能是4可能是8
printf("int 型別指標變數的長度為 %d\n",sizeof(ip)); //8
printf("double 型別指標變數的長度為 %d\n",sizeof(dp)); //8
printf("float 型別指標變數的長度為 %d\n",sizeof(fp)); //8
printf("char 型別指標變數的長度為 %d\n",sizeof(cp)); //8
// 在32位的作業系統上 因為程式 最大能使用的記憶體空間的地址 就是2的32次方
// 指標只需要4位 就可以表示出來所有的記憶體空間
// 64 並且編譯支援64位 8位
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
首先我們定義了不同的幾個變數,然後將他們的地址放入指標變數中,最後輸出,我們居然發現輸出結果都一樣。
總結:
- 指標變數的記憶體大小是固定的,與資料型別沒有關係
- 指標變數的記憶體大小最終取決於我們的電腦
11、使用char* 指標表示字串
在第二節中我們知道,c語言中是使用char陣列去表示一個java中的String字串,
在我們學習指標之後我們將看看怎麼用指標更加簡單的表示一個字串:
#include <stdio.h>
#include <stdlib.h>
main()
{
char arr[20] ={'h','e','l','l','o','\0'};
// 利用char型別指標 方便的表示一個字串
char* arr1= "hello world";
printf("%s",arr1);
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
從程式碼中我們可以看到使用char陣列的方式是比較複雜的,然後我們使用指標的方式:char* arr1=”“,這樣就可以簡單的直接表示一個字串
注意:在java中我們定義陣列可以int arr[]; int [] arr;但是在c語言中[]只能解除安裝變數名後面,如:int arr[]
12、指標與陣列
指標進階:
#include <stdio.h>
#include <stdlib.h>
// 陣列是一塊連續的記憶體空間 陣列名 就是記憶體空間的首地址
// 陣列名[i] == *(陣列名+i);
main()
{
/* char[] arr = new char[20];
char arr[] ;
*/
// 建立一個長度為5的int型別的陣列
int arr[5] ={1,2,3,4,5};
printf("a[0]=%d\n",arr[0]);
printf("a[4]=%d\n",arr[4]);
// 邏輯上是錯誤的程式碼 陣列下標越界
// printf("a[5]=%d\n",arr[5]);
// windows xp 緩衝區越界補丁
// arr是一個什麼東西呢?
printf("arr = %#X\n",arr);
// 列印 陣列的第一個元素的地址
printf("arr[0]地址 = %#X\n",&arr[0]);
// 列印陣列中的第二個元素
printf("arr[1]=%d\n",arr[1]);
printf("arr[1]=%d\n", *(arr+1));
//問題: arr[i] *(arr+i) 代表的是同一個變數麼?
// 代表的是同一塊記憶體空間 指向的是同一個變數
//通過實驗 : 陣列名錶示的 就是這個陣列第一個元素 的首地址
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
總結:
- 語言語法上沒有陣列越界,在邏輯上存在陣列越界,
- arr是一個指標,arr的指標就是&arr[0]的指標,也就是說陣列的指標就是陣列第一個元素的指標
- 陣列中的資料的指標是一塊連續的記憶體空間
- arr[i]等同於*(arr+i)
13、指標的計算
通過上一節我們大概知道了指標與陣列的關係,下面我們通過一個案例去鞏固一下:
#include <stdio.h>
#include <stdlib.h>
main()
{
int i =3; //天津的某個路上 蓋了一個房子 3
int j =5; // 北京的某個路上 蓋了一個房子 5
int* p = &i; // p 天津的門牌號
int* q = &j; // q 北京的門牌號
// 指標的運算和陣列都是緊密關聯的
char arr[5]={'a','b','c','d','e'}; //一塊連續的記憶體空間
char* p1 = &arr[2];
printf("char = %c\n", *(p1-1));
// char 記憶體中佔用 1個位元組
// int 記憶體 中佔用 4個位元組
int intarr[5]={1,2,3,4,5}; //一塊連續的記憶體空間
int* q1 = &intarr[2];
printf("char = %d\n", *(q1-1));
// 指標的運算 按照 約定好的資料型別 偏移相對應的記憶體空間的大小
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
總結:
- 陣列中的資料的指標是一塊連續的記憶體空間
- 指標的運算按照約定好的資料型別偏移相對應的記憶體空間的大小
14.案例:通過子函式列印陣列
#include <stdio.h>
#include <stdlib.h>
#define pi 3.1415
// 寫一個子函式 列印數組裡面的每一個元素
void printArr(int* arr, int len){ // arr是陣列的首地址 len陣列的長度
int i=0;
for(;i<len;i++){ // 在c99 的語法格式下 for迴圈的初始化條件 不能寫在 for 迴圈的括號裡面
// printf("arr[%d]=%d\n",i,arr[i]); // arr[i] 和 *(arr+i) 代表的含義相同
printf("arr[%d]=%d\n",i, *(arr+i));
}
}
main()
{
// int arr[10]={1,2,3,4,5};
printArr(&arr[0],10);
//1 .定義一個數組 缺陷 陣列的長度 必須事先申請好
//2. 迴圈賦值
//3. 列印數組裡面的內容
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
總結:
- int arr[5]; 這一句程式碼一旦執行 ,就立刻會在記憶體裡面申請 5個記憶體空間 每個記憶體空間的大小可以存放一個int型別的資料
- 留下疑問:沒有辦法動態的增加這一塊空間的大小, 也沒辦法減小這一塊記憶體空間,只能提前寫死,例如我們程式碼中arr[10],直接就將我們的arr陣列大小寫死了
15、realloc()方法介紹
在上一節中我們留下的疑問是不能更改陣列的記憶體控制元件,而realloc()方法則完美解決了這個問題,具體使用方式:
int arr[10];
arr = realloc(arr,sizeof(int)*12); //空間的長度為12了
首先我們可以看到realloc接收兩個引數:
- 第一個引數表示你需要更改大小的變數
- 第二個表示更改之後的長度
我們瞭解了realloc()方法是可以更改變數記憶體大小的,那麼就會存在這樣一個問題,改變之後的記憶體會不會覆蓋之前的資料呢?
對於增加記憶體:realloc是直接在原有的基礎上新增記憶體空間,所以還是會保持原有的資料再進行新增
對於減少記憶體:realloc是會將排列在最後的記憶體空間值給回收掉的,
比如原始資料int arr[4]={1,2,3,4};減少2個記憶體的話,就是int arr[8]={1,2}
16、動態分配記憶體
前面我們看到我們定義的那些函式變數等,都是由系統給我們自動分配記憶體,我們是不用去理會系統到底什麼時候去給我們分配,什麼時候去回收,
而接下我們要學習的是自己去管理記憶體即動態分配記憶體,看程式碼:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
// malloc memory allocate 記憶體申請
main()
{
// 接受一個引數 申請多大(byte)的記憶體空間
int* p = (int*)malloc(sizeof(int)); // 在堆記憶體裡面申請一塊可以存放一個int型別資料的記憶體空間
*p = 4; // 就是往 p 裡面存放的地址 表示的那塊記憶體空間裡面存放一個int型別的資料 4
printf("*p=%d\n",*p);
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
程式碼中我們首先如java匯入類的方式,在程式碼上面添加了一行:
這個就是動態記憶體所要用到的方法了,接下來我們在main函式中
int* p = (int*)malloc(sizeof(int));
這樣我們即給指標變數p分配了int位元組大小的記憶體空間,完成了一個動態分配記憶體的操作
這樣我們就可以使用如上方式去解決上一節中的疑問了
學到的知識點:
- malloc(byte length)方法用於動態申請記憶體。
- 動態申請記憶體其實就是在申請指標
疑問:
- 我們之前看到的,函式使用完成後是會被回收掉的,難道我們動態申請記憶體怎麼被回收呢?不然是會浪費記憶體空間的
17、動態分配記憶體2
在上節中我們已經知道了malloc(byte length)方法是用來動態分配記憶體的,
下面我們將通過一個小案例來進一步講解動態分配記憶體,並且解決上一節中的疑問
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
f(int** address){ //address 存放的是q的地址
// 動態的在堆記憶體裡面申請一塊空間
int* p ;
p = (int*)malloc(sizeof(int)*3);
*p = 3;
*(p+1) = 4;
*(p+2) = 5 ;
printf("子函式裡面 地址%#X\n",p);
*address = p;
// 在子函式裡面把p釋放掉了
//free(p);
}
main()
{
int* q ;
f(&q);
printf("主函式裡面 地址%#X\n",q);
printf("*q = %d\n",*(q+0));
printf("*q = %d\n",*(q+1)); // 殘留的記憶體映像
printf("*q = %d\n",*(q+2));
//動態記憶體分配 程式設計師可以自己手工的決定一個變數的生命週期
//手工的釋放呼叫記憶體空間
//不要使用已經回收掉的記憶體空間裡面的資料
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
首先我們看到是f(int** address) 其中int **表示的是多級指標,下面我們會詳細介紹,在這裡只要知道他是一個存放指標變數的指標變數就行了,
然後定義一個指標變數p並動態分配記憶體和賦值,
最後將p傳遞給f(int** address)的形參
接著我們看到main函式中我們使用了f()函式並得到了f()函式中的值
細心的同學可能可以看到我們在f()方法中可以看到我們註釋的一個函式:free(p)
free(p):將動態釋放的記憶體給回收,使用free(p)函式即可解決我們在上一節留下的疑問,其中p是我們需要釋放記憶體的指標變數
接著我將free(p)開啟,按理說p分配的動態記憶體空間的資料是會被free(p)執行後給回收的,最後居然發現在main函式列印(q+0)為0,(q+1)為4,(q+2)為5,
結果可想而知free(p)難道只將第一個資料給回收了?其實在執行free(p)之後確實將p給回收了,(q+1)為4,(q+2)為5,只是殘留的映像,在回收過程中只是將p的記憶體空間標記為可以再次寫入,其實真正並沒有徹底回收,所以會出現這種情況
18、多級指標
#include <stdio.h>
#include <stdlib.h>
main()
{
int i = 5;
int* p = &i;
int** q = &p;
int*** r = &q;
printf("i=%d\n",***r);
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
一個*表示一級指標,二個*表示二級指標,以此類推
19、函式的指標
#include <stdio.h>
#include <stdlib.h>
int add(int x , int y){
return x+y;
}
main()
{
int (*pf) (int x, int y); //定義一個函式的指標的宣告 名字叫pf 返回值 int 接受引數兩個int
pf = add;
printf("result=%d\n", pf(3,6));
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
程式碼中我們定義了一個返回值int的add函式,在main函式中使用函式指標給add函式賦值,輸出
函式指標使用步驟:
1.定義int (pf)(int x, int y):返回值 (*函式指標別名)(引數列表)
2.賦值 pf = add;
3.引用 pf(3,5);
20、結構體
結構體是c中的新名詞,和java中的類是一樣的
#include <stdio.h>
#include <stdlib.h>
struct Student
{
int age; //4
float score; // 4/
long id; //4
char sex ; //2 vc 6.0 14
};
main()
{
struct Student st={80,55.6f,100010 ,'F'};
printf("age = %d\n",st.age);
printf("score = %f\n",st.score);
printf("id = %ld\n",st.id);
printf("sex = %c\n",st.sex);
// 結構體的長度
printf("長度 = %d\n",sizeof(st));
struct Student* pst;
pst = &st;
//printf("age = %d\n", (*pst).age);
printf("age = %d\n", pst->age);
system("pause"); // 呼叫windows下系統的命令 讓程式暫停執行 方便觀察程式的執行結果
}
從上面的程式碼我們可以看出首先定義除了一個Student的結構體,結構體用struct 修飾
Student定義了四個變數,
給變數賦值:在main中通過 struct Student st={80,55.6f,100010 ,’F’}; 的形式給變數賦值
列印變數1:如同java裡一樣類名.變數名,結構體名.變數名,如:我們這裡是st.age…..
- 列印變數2:首先定義一個Student型別的結構體指標變數pst,將結構體物件的指標&st放在該指標變數pst中,最後使用(*pst).變數名的方式列印,如:(*pst).age,
- 也可以使用->符號來輸出,例如我們上面的:pst->age,表示pst所指向的結構體變數中的age這個成員,在計算機內部會被轉換為 (*pst).age
注意:結構體的長度是根據結構體中資料型別最多的位元組大小*變數的個數,如果位元組數一樣,則按位元組數大的來計算,例如:我們上面有3個變數的位元組數為4,所以導致結構體的大小為16,如果我們的char型別為3個則是8,如果有兩個4個位元組的資料,兩個2個位元組的資料,則結構體的大小為16
結構體的常見幾種表示方式:
//第一種
struct Student
{
int age;
float score;
char sex;
}
//第二種
struct Student2
{
int age;
float score;
char sex;
} st2;
//第三種
struct
{
int age;
float score;
char sex;
} st3
21、聯合體:
首先聯合體使用union 修飾,如:union { long i; int k; char ii; } mix;
聯合體 是定義一塊相同的記憶體空間 存放裡面的資料
聯合體的作用就是用來表示一組資料型別 資料的資料型別為這一組中的某一種資料型別
#include <stdio.h>
#include <stdlib.h>
main( )
{
struct date { int year, month, day; } today;
union { long i; int k; char ii; } mix;
printf("date:%d\n",sizeof(struct date)); //12
printf("mix:%d\n",sizeof(mix));
mix.ii = 'A';
printf("k=%d\n",mix.k);
system("pause");
}
從結果我們可以看出結構體輸出的長度為4,按理說我們裡面有三個資料,光long就為4,
這是因為mix.I mix .k mix.ii共用相同的地址,它會自動取最大的資料型別位元組數為它的大小
程式碼中我們將mix.ii=’A’,輸出mix.k發現也是65,所以我們可以很快得出一個結論:
聯合體裡面的資料內容會相互覆蓋
22、列舉
在java中我們知道也是 有列舉的,用enum修飾,c語言在這一方面是完全一樣的:
#include <stdio.h>
#include <stdlib.h>
enum WeekDay
{
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
};
int main(void)
{
//int day;
enum WeekDay day = Sunday;
printf("%d\n",day);
system("pause");
return 0;
}
輸出6
如果將Monday做以下改動:
#include <stdio.h>
#include <stdlib.h>
enum WeekDay
{
Monday=9,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
};
int main(void)
{
//int day;
enum WeekDay day = Sunday;
printf("%d\n",day);
system("pause");
return 0;
}
將輸出15
使用很簡單的,很java一樣
23、typedef
typedef意義:宣告自定義資料型別,配合各種原有資料型別來達到簡化程式設計的目的的型別定義關鍵字。
例如:
#include <stdio.h>
#include <stdlib.h>
typedef int haha;
int main(void)
{
haha i = 3;
printf("%d\n",i);
}
輸出3,
我們從程式碼中首先我們在main上方定義了一個 typedef int haha:表示用haha去代表int型別的資料,然後下面我們就可以在main函式使用haha定義變數賦值
24、預處理
/**
1.#開頭的都是預處理指令
2.預處理指令分為3種
1>巨集定義
2>條件編譯
3>檔案包含
3.預處理指令在程式碼翻譯成0和1之前執行
4.預處理的位置是隨便寫的。
5.預處理指令的作用於:從編寫指令的那行開始到檔案結束
6.巨集明一般大寫,成員變數名一般小寫。
*/
#define COUNT 4
/**
帶引數的巨集定義效率比函式高
巨集定義只是文字替換,不會幫我們去做任何運算。
*/
#define sum(a,b) ((a)+(b))
#define pf(a) ((a)*(a))
/**
只要寫了#if,在後面就必須加上#endif
條件編譯後面的條件必須是巨集定義,因為預處理指令必須接的是預處理的定義
*/
#if (COUNT==3)
printf("3");
#elif (COUNT==4)
printf("4");
#else (COUNT==5)
printf("5");
#endif
//如果定義了COUNT
//第一種格式
#if defined (COUNT)
printf("5");
#endif
//第二種格式
#ifdef COUNT
printf("5");
#endif
//如果沒有定義COUNT
//第一種格式
#if !defined (COUNT)
printf("5");
#endif
//第二種格式
#ifndef COUNT
printf("5");
#endif
//取消巨集定義之後就不能在使用
#undef COUNT
檔案包含:
/**
//巨集定義也可以不賦值,為空,這個時候一般用檔名作為巨集名,保證引入的唯一性。
第一次是