C和指標學習
C和指標學習
最後更新時間:2012.12.3
原則:儘量短小精悍,實用,需要擴充閱讀都是以連結形式出現
注意:本文主要是針對Unix環境下的C
目 錄
一.編譯與連結
二.特殊字元
三.資料型別
四.修飾符
五.運算子
六.控制語句
七.函式
八.指標
九.陣列
十.字串
十一.結構
十二.union聯合
十三.typedef宣告
十四.前處理器
十五.輸入輸出
十六.檔案
十七.記憶體
十八.異常
十九.連結串列
二十.樹
正 文
一.編譯與連結
1.編譯
#cc program.c
生成a.out檔案
這個名字是編譯器預設的輸出名。如果要修改可執行檔案的名字可以加-o引數:gcc -o myexec main.c
這樣就把main.c編譯連線生成的可執行檔案命名為myexec
gcc編譯器的編譯自定義格式
#cc -o hello hello.c
#gcc -o hello hello.c
使用gcc 編譯器就會為我們生成一個hello的可執行檔案
擴充閱讀:Linux編譯器GCC的使用
http://blog.csdn.net/21aspnet/article/details/1534108
http://blog.csdn.net/21aspnet/article/details/167420
補充說明:如果你實在基礎很差,那麼需要在windows下藉助VS2010這樣的視覺化工具來除錯了,這樣可以很清晰的看出記憶體中的指標
http://blog.csdn.net/21aspnet/article/details/6723758
2.執行
#./a.out
編譯多個原始檔
#cc a.c b.c c.c
3.產生目標檔案
#cc -c program.c
產生program.o的目標檔案以後供連結用
產生多個目標檔案
#cc -c program.c a.c b.c
編譯指定名稱檔案
#cc -o sea a.c
4.連結幾個目標檔案
#cc a.o b.o c.o
二.特殊字元
1.轉義字元
\\單斜線\
\\\\雙斜線\\
注意不是\\\代表\\,因為要2個\\
\'單引號
\"雙引號
\n換行
\r回車
\t製表
\f換頁
\a警告
擴充閱讀:C語言 格式控制符 和 轉義字元
http://blog.csdn.net/21aspnet/article/details/1535459
2.註釋
方法一 /* */
方法二 //
3.保留字
http://blog.csdn.net/21aspnet/article/details/1539252
4. size_t
是為了方便系統之間的移植而定義的
在32位系統上 定義為 unsigned int
在64位系統上 定義為 unsigned long
更準確地說法是 在 32位系統上是32位無符號整形
在 64位系統上是64位無符號整形
size_t一般用來表示一種計數,比如有多少東西被拷貝等
三.資料型別
1.整型
singed 有符號
型別 |
最小範圍 |
char |
0-127 |
signed char |
-127-127 |
unsinged char |
0-255 |
int |
-32767-32767 |
long int |
-2147483647-2147483647 |
unsinged int |
0-65536 |
int a=8;
int a=012;//8進位制是0開頭
int a=0x0a;//16進位制是0x開頭
3種輸出printf("%d",a);都是10
2.浮點
float double long double
3.布林值
C語言中非0值為真,0值為假。
C語言沒有布林型別,任何一個整型的變數都可以充當布林變數,用0表示False,其它數(預設為1)表示True。
a = 1;
if(a) {printf("a is T\n");}else{printf("a is F\n");}//輸出T
a = 2;
if(a) {printf("a is T\n");}else{printf("a is F\n");}//輸出T
a = -1;
if(a) {printf("a is T\n");}else{printf("a is F\n");}//輸出T
a = 0;
if(a) {printf("a is T\n");}else{printf("a is F\n");}//輸出F
if(!a) {printf("a is T\n");}else{printf("a is F\n");}//輸出T
如果你想像Pascal一樣使用true和false,那麼你可以包含標頭檔案stdbool.h。這樣你可以定義變數為bool型別並賦值為true或false。例如:
#include "stdbool.h"bool a = true;
if (a) printf("a is true");
也可以使用
#define False 0
#define True 1
4.型別轉換
float a;
int x=6,y=4;
a=x/y;
printf("%f",a)//輸出1.000000
因為6/4為1;1轉浮點為1.000000
如果不希望截斷需要精確結果要型別轉換
a=(float)x/y;
5.enum列舉
enum A{a,b,c,d};
就是為了定義一組同屬性的值,預設的最前面的是0,後面的元素依次+1;
但是注意,每個列舉都唯一定義一個型別,裡面的元素的值不是唯一的,列舉成員的初始化只能通過同一列舉的成員進行!!
之所以不用整數而用列舉是為了提高程式的可讀性!
enum color
{
red,
green=2,
blue=4
}colorVal;
printf("%d\n",red);
6.位欄位
結構體中欄位冒號後數字表示該欄位分配多少位
#include <stdio.h>
#define MALE 0 ;
#define FEMALE 1 ;
#define SINGLE 0 ;
#define MARRIED 1 ;
#define DIVORCED 2 ;
#define WIDOWED 3 ;
main( )
{
struct employee
{
unsigned gender : 1 ;
unsigned mar_status : 2 ;
unsigned hobby : 3 ;
unsigned scheme : 4 ;
} ;
struct employee e ;
e.gender = MALE ;
e.mar_status = DIVORCED ;
e.hobby = 5 ;
e.scheme = 9 ;
printf ( "\nGender = %d", e.gender ) ;
printf ( "\nMarital status = %d", e.mar_status ) ;
printf ( "\nBytes occupied by e = %d", sizeof ( e ) ) ;
}
7.左值和右值
左值:物件被修改;
右值:使用物件的值。
四.修飾符
1.變數
取變數的值可以直接=變數
給變數賦值一定要&
2.型別限定符const
const是一個C語言的關鍵字,它限定一個變數不允許被改變。
使用const在一定程度上可以提高程式的健壯性,另外,在觀看別人程式碼的時候,清晰理解const所起的作用,對理解對方的程式也有一些幫助。
const int a=15;//必須宣告時賦值
int const a=15;//必須宣告時賦值,const在前在後都可以
宣告好以後再賦值會報錯
const int a;
a=15
------------------------
其次:在函式中宣告為const的形參是因為函式沒有改變所指向的值的內容
int ax(const int *p)
{
int a=*p+1;
return a;
}
試圖改變*p指向的值會報錯
int display(const int * p)
{
return ++*p;
}
const int和int const是一樣的效果
int display(int const * p)
{
return ++*p;
}
可以執行的,因為此刻是*p可以改變,不能改變的是p
int display(int * const p)
{
return ++*p;
}
如果上述3段程式碼中return ++*p;改為return *p++;那麼完全相反的結果
總結:只要const在*前那麼保護的就是指標指向的結果,如果const在*後那麼保護的是指標
擴充閱讀:http://blog.csdn.net/21aspnet/article/details/160197
3.儲存型別
儲存型別 |
儲存位置 |
預設初始值 |
作用域 |
生存週期 |
自動 | 記憶體 | 不可預料 | 限定在變數定義的區域性塊 | 從變數定義處到控制還處在變數定義的塊內 |
暫存器 | cpu暫存器 | 無意義 | 變數定義的區域性塊 | 從變數定義處到控制還處在變數定義的塊內 |
靜態 | 記憶體 | 0 | 變數定義的區域性塊 | 不同函式呼叫間,變數值不變 |
外部 | 記憶體 | 0 | 全域性 | 程式結束前 |
4.static 變數
是C程式編譯器以固定地址存放的變數,只要程式不結束,記憶體不被釋放.
static int a=5;
函式內的static變數只有函式內可以訪問;
函式外的static變數只有該檔案內的函式可以訪問。
1).區域性變數在函式內重新進入時保持原有的值不會初始化
#include<stdio.h>
int fun1()
{
static int v=0;
v++;
return v;
}
int main(int argc, char *argv[])
{
int a1,a2,a3,a4;
a1=fun1();
a2=fun1();
a3=fun1();
a4=fun1();
return 0;
}
輸出:1234
2).與extern一起使用,建立一種私有機制,對函式和變數限制的作用。靜態外部變數作用域從宣告位置開始直到當前檔案結束。對於當前檔案更前位置或者其他檔案函式不可見。
#include<stdio.h>
static int v=0;
int fun1()
{
v++;
return v;
}
int fun2()
{
v++;
return v;
}
int main(int argc, char *argv[])
{
int a1,a2,a3,a4;
a1=fun1();
a2=fun2();
a3=fun1();
a4=fun2();
return 0;
}
輸出:1234
5.static函式
函式如果沒有宣告那麼就是extern,外部連結
如果是static的函式,說明是內部連結,即只有此函式的檔案內部可以呼叫。
好處:易維護;減少名字汙染
擴充閱讀:C語言的一個關鍵字——static
extern int i;
extern儲存型別使幾個原始檔共享同一個變數
外部變數 定義在程式外部,所有的函式和程式段都可以使用.
extern可以置於變數或者函式前,以標示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義。
另外,extern也可用來進行連結指定。
標頭檔案f1.h
int a=5;//定義
主檔案f2.c
#include "f.h" //引用,注意不能<"f.h">
extern int a;//宣告
printf("%d",a);
7.auto 變數
是用堆疊(stack)方式佔用儲存器空間,因此,當執行此區段是,系統會立即為這個變數分配儲存器空間,而程式執行完後,這個堆疊立即被系統收回.在大括號{}內宣告.
8.register 變數
暫存器變數,是由暫存器分配空間,訪問速度比訪問記憶體快,加快執行速度.暫存器大小有限.
注意:register int a=1;只能函式內 而不能函式外,由於暫存器的數量有限(不同的cpu暫存器數目不一),不能定義任意多個暫存器變數
extern 和static可以函式外
register int a=1;
但是如果int *b=&a;這樣會報錯,因為不可以取到暫存器變數的地址。
但是使用VS2010除錯時可以用&a.
擴充閱讀:暫存器register介紹
擴充閱讀:變數屬性
9.volatile變數
編譯器不要對變數優化,每次使用變數,直接從記憶體調入暫存器操作,然後結果返回記憶體。
否則,編譯器可能會對變數程式碼優化。
volatile int i;
10.連結屬性
external外部
internal內部
none無
五.運算子
1.操作符
+-*/%
2.邏輯運算子
&&與
||或
!非
3.條件運算子
a>5?b-6:c/2
a大於5就執行b-6否則執行c/2
4.++
i++和++i作用一樣,就是將i的值加1.
但是如果除了++之外還有別的運算子的話就要考慮先加後加的問題了。
例如:
while(i++<10);
先將i的值與10比較,再將i的值加1.
while(i++<10);
先將i的值與10比較,再將i的值加1.
5.位移和位操作符
<<左移用法:
格式是:a<<m,a和m必須是整型表示式,要求m>=0。
功能:將整型數a按二進位制位向左移動m位,高位移出後,低位補0。
>>右移用法:
格式是:a>>m,a和m必須是整型表示式,要求m>=0。
功能:將整型數a按二進位制位向右移動m位,低位移出後,高位補0
&位與
|位或
^位非
六.控制語句
1.if...else...
如果if後沒有大括號--{,那麼if判定為真只會呼叫之後的第一句程式碼,其餘的走else邏輯。
需要注意的是真假的判斷,參考23條。
int a=0;
int a=NULL;
if(a)
{
//a為非0和NULL才進來
}
int d1=NULL;
char* s1;
struct node* head=createNode("0","0");
if(d1)
{
d1=1;
}int d1=NULL;
char* s1;
struct node* head=createNode("0","0");
if(d1)
{
d1=1;
}int d1=NULL;
char* s1;
struct node* head=createNode("0","0");
if(d1)
{
d1=1;
}
2.while迴圈
i=0;a=0;
while(i<10)
{
a=a+i;
i++;
}
break:退出大迴圈
continue:退出本次迴圈
3.for迴圈
常用寫法
int i;
for(i=0;i<2;i++)
{
}
特殊寫法
for(;;)
4.do....while迴圈
無論如何要先做一次do然後去while
5.switch語句
switch(ch){
case "A":
i+1;
break;
case "B":
i+2;
break;
default:
i=0;
}
6.goto跳轉
goto AA;
AA:if...else
建議不要用goto
七.函式
1.定義
int a=1;
int b=2;
ext1(a,b);
ext1(int x.int y)
{
int temp=x;
x=y;
y=temp;
prinft("x=%d,y=%d",x,y)
}
2.行內函數inline
首先要宣告
inline int 函式名(引數)
後面要有一個真實的函式體
inline int 函式名(引數)
{
}
只有三二行的程式碼,建議使用內聯!這樣執行速度會很快,因為加入了inline 可以減少了,跳入函式和跳出函式的步驟!inline 定義的類的行內函數,函式的程式碼被放入符號表中,在使用時直接進行替換,(像巨集一樣展開),沒有了呼叫的開銷,效率也很高。
另外要注意,行內函數一般只會用在函式內容非常簡單的時候,這是因為,行內函數的程式碼會在任何呼叫它的地方展開,如果函式太複雜,程式碼膨脹帶來的惡果很可能會大於效率的提高帶來的益處。
3.巨集定義
#define SQUARE(x) x*x
程式中寫SQUARE(3) 實際等於3*3
巨集使程式速度更快,但是巨集增加程式的大小。
但是,將引數傳遞給函式,再從函式獲得返回值,時間開銷大。而巨集已經在編譯前儲存到原始碼中。
4.main函式的引數
argc:命令列引數個數;
argc:命令列引數陣列;
env:環境變數陣列;
#include <stdio.h>
int main(int argc,char *argv[],char *env[])
{
int i;
for(i=0; i<argc; i++)
printf("argv[%d]=%s\n",i,argv[i]);
for(i=0; env[i]!=NULL; i++)
printf("env[%d]:%s\n",i,env[i]);
return 5;
}
八.指標
指標只能指向地址!
指標的指標是為了取得“地址”,因為指標只是指向“主體”
指標三檢視
圖1:指標和變數關係
圖2:指向同一個地址的指標
圖3:指向動態分配結構體的指標
typedef struct node
{
int value;
//注意不要在此用Node,不然會有“警告:從不相容的指標型別賦值”
struct node *link;
}Node;
=====================指標總結======================
首先:宣告和使用要分開,不能混用
宣告一個指標出三值:指標變數的地址,指標指向變數的地址,指標指向變數的值(間接)
普通變數只有兩值:變數地址,變數值
宣告:
int * 指標變數=&變數;
使用:
1. (* 指標宣告) 即:&地址對於的記憶體值,也就是變數值;
2. 指標宣告 即:&地址,也就是變數地址
3. &指標宣告 即:指標變數的地址
=====================指標總結======================
int a;
int *b=&a;
int **c=&b;
a=1;
*b=2;
指標要對應級別,一級b對應一級&a,二級c對應二級&b。
如果傳值原始碼塊無星
main()
{
swap(a,b,c)
}
swap(int a,int *b,int **c)
{
a=*b+**c;//這樣不能改變值
*b=a+**c;//可以改變值
}
函式裡要改變原值只有指標
printf("%d",*b);//輸出2
printf("%d",a);//輸出2
//指標和字串
char * ch="abc";
char * cp=ch;
printf("%s",cp);//輸出abc
//指標和字元
char ch=‘a’;
char * cp=&ch;
printf("%c",*cp);//輸出a
//指標和陣列
int a1[]={1,2,3,4,5};
printf("%d",&a1[2]);
int *b=&a1[2];
空指標
void*這不叫空指標,這叫無確切型別指標.這個指標指向一塊記憶體,卻沒有告訴程式該用何種方式來解釋這片記憶體.所以這種型別的指標不能直接進行取內容的操作.必須先轉成別的型別的指標才可以把內容解釋出來.
還有'\0',這也不是空指標所指的內容. '\0'是表示一個字串的結尾而已,並不是NULL的意思.
真正的空指標是說,這個指標沒有指向一塊有意義的記憶體,比如說:
char* k;
這裡這個k就叫空指標.我們並未讓它指向任意地點.
又或者
char* k = NULL;
這裡這個k也叫空指標,因為它指向NULL 也就是0,注意是整數0,不是'\0'
一個空指標我們也無法對它進行取內容操作.
空指標只有在真正指向了一塊有意義的記憶體後,我們才能對它取內容.也就是說要這樣
k = "hello world!";
這時k就不是空指標了.
int i=2;
void *p;
p =&i;
printf("Values of I is=%d\n",p);//輸出地址
printf("Values of I is=%d\n",*p);//報錯
printf("Values of I is=%d\n",(*(int *)p));//輸出i值
void *使用場景:當進行純粹的記憶體操作時,或者傳遞一個指向未定型別的指標時,可以使用void指標,void指標也常常用作函式指標。
用空指標終止對遞迴資料結構的間接引用
用空指標作函式呼叫失敗時的返回值。
用空指標作警戒值
NULL指標
char *q;
q=NULL;
printf("Location 0 contains %d\n",q);
輸出0說明機器允許讀取記憶體地址0;否則說不允許
指標++
int i=1;
int *p;
p =&i;
printf("%d\n",p);
(*p)++;
printf("%d\n",p);
printf("%d\n",*p);
*p++;
printf("%d\n",p);
printf("%d\n",*p);
輸出:
-1078959176
-1078959176
2
-1078959172
-1078959172
可以看出(*p)++改變指標指向的值, *p++改變指標自己,因為int在32位下是4位。
函式指標
#include <stdio.h>
void display();
void main()
{
void( *func_ptr )();
func_ptr = display; /* assign address of function */
printf("\nAddress of function display is %u", func_ptr );
( *func_ptr)( ); /* invokes the function display( ) */
}
void display()
{
puts ("\nLong live viruses!!");
}
輸出:
Address of function display is 134513698
Long live viruses!!
函式指標使用場景
1.Windows中回撥機制
2.C++執行期間動態繫結函式
指標的指標**
主要用在鏈式資料結構中,特別是當函式的實際引數是指標變數時。有時候希望函式通過指標指向別處的方式改變此變數。而這就需要指向指標的指標。
為了從連結串列中刪除一個元素,向函式傳遞一個待改變的指向指標的指標。
擴充套件閱讀:
把指標說透
http://blog.csdn.net/21aspnet/article/details/317866
C指標本質
http://blog.csdn.net/21aspnet/article/details/1539652
九.陣列
1.陣列定義
int a[]={1,2,3,4,5}
a[0]//用序號輸出陣列元素,預設下標從0開始
求陣列長度:printf("%d\n",sizeof(a)/sizeof(a[0]));
int *ap=a+2;
ap[0];//輸出3
int a[]={1,2,3,4,5,6};//自動計算陣列長度
迴圈陣列可以用2種方法
int i;
for(int i;i<6;i++)
{
a[i];這樣輸出
或者*(a+i);因為*(a)指向陣列第一個元素的指標
}
2.指標訪問陣列
int *p=a;注意不是&a;
可以三種方法輸出
p[i];方法一就是陣列元素
*(p+i);方法二
for()
{
printf("%d",*p);//方法三
p++;
}
如果 int const p=a;
用上述程式碼linux下一樣可以編譯
3.陣列的地址:
int num[]={24,25,26}
陣列的首地址有三種方法獲取:
num
&num
&num[0]
int num[3]={11,2,13};
printf("\n%d",num);
printf("\n%d",&num);
printf("\n%d",&num[0]);
輸出都是同一地址
4.陣列的值:
陣列的值有四種方法獲取:
num[i]
*(num+i)
*(i+num)
i[num]
地址:&num[i]
值:num[i]
#include <stdio.h>
int main(void)
{
int marks[3]={11,2,13};
printf("\n%d",marks);
printf("\n%d",&marks[0]);
int i=77,*p;
p=&i;
printf("\n%d",*p);
for(i=0;i<10;i++)
{
p++;
printf("\n%d",*p);
}
}
5.傳遞陣列
#include <stdio.h>
int main(void)
{
int marks[3]={11,2,13};
int i=77,*p;
/*
printf("\n%d",marks);
printf("\n%d",&marks[0]);
p=&i;
printf("\n%d",*p);
for(i=0;i<10;i++)
{
p++;
printf("\n%d",*p);
}
for(i=0;i<10;i++)
{
printf("Please input :\n");
scanf("%d",&marks[i]);
}
*/
for(i=0;i<3;i++)
{
printf("\n%d",&marks[i]);
display(&marks[i]);
}
display(marks);
display(&marks[0]);
printf("\n");
}
display(int *m)
{
printf("\n%d",*m);
}
6.多維陣列
char a[][10]={
"abc",
"cnn",
"bbc",
};
char *names[]={
"abc",
"cnn",
"bbc",
};
printf("\n%s",names[2]);
printf("\n%s",&names[2]);
printf("\n%s",a[2]);
printf("\n%s",&a[2][0]);
printf("\n%s",&a[2][1]);
輸出
bbc
bbc
bbc
bc
記憶體檢視
int a[2][3]={1,2,3,4,5,6};
a[0][0]=1;
a[0][1]=2;
a[0][2]=3;
int a[2][3]={
{1,2,3},
{4,5,6}
};
多維陣列只有第一維可以預設,其餘維都要顯示寫出
int a[][3]={1,2,3,4,5,6};
//宣告指標陣列,每個指標元素都初始化為指向各個不同的字串常量
char const keyword[]={
"do";
"doo";
"doooo";
"do";
"doo";
"do";
"doooooo";
}
//宣告矩陣,每一維長度都是9,不夠用0補齊
char const keyword[][9]={
"do";
"doo";
"doooo";
"do";
"doo";
"do";
"doooooo";
}
擴充套件閱讀:C語言中字元陣列和字串指標分析
http://blog.csdn.net/21aspnet/article/details/1539928
十.字串
1.定義char msg[]={'a','b','c'}
char msg[]={'a','b','c','\0'}
char msg[]={"ABC"}
char *p;或者char *p1="ABC";
p=msg;
一開始不指定字串內容,就需要定義長度:char s[20];
2.輸出字串
方法一
char name[]={'A','B','C'};
int i=0;
while(i<=2)
{
printf("\n%c",name[i]);
i++;
}
方法二
while(name[i]!='\0')
{
printf("\n%c",name[i]);
i++;
}
方法三
while(*p!='\0')
{
printf("\n%c",*p);
p++;
}
方法四
char name[]={"ABC"};
printf("\n%s",name);
陣列賦值
陣列=陣列 [不可以]
指標=字串 [可以]
指標=地址 [可以]
strcpy(陣列,陣列) [可以]
指標字串陣列記憶體示意圖
char day[15] = "abcdefghijklmn";//定義一
char day[] = "abcdefghijklmn";//定義一
char * str="abcdefghijklmn";//定義二
windows下VS2010除錯視窗看區別
char str[20]="0123456789";//str是編譯期大小已經固定的陣列
int a=strlen(str); //a=10;//strlen()在執行起確定
int b=sizeof(str); //而b=20;//sizeof()在編譯期確定
sizeof 運算子是用來求記憶體容量位元組的大小的。而strlen是用來求字串實際長度的。
3.二維字元陣列
char a[][10]={"abc",
"cnn",
"bbc",
};
char *names[]={
"abc",
"cnn",
"bbc",
};
printf("\n%s",names[2]);
printf("\n%s",&a[2][0]);
記憶體示意圖
char *a[4]={"this","is","a","test"};
char **p=a;
擴充套件閱讀:C語言字串處理庫函式
http://blog.csdn.net/21aspnet/article/details/1539970
十一.結構
1.宣告
注意結構一定要};結束
宣告一
struct simple{
int a;
int b;
};
使用
struct simple s1;
s1.a=1;
宣告二
typedef struct {
int a;
int b;
};simple
使用
simple s1;
s1.a=1;
注意:方法二比一少寫一個struct
2.結構初始化
typedef struct {
int a;
int b;
};simple{1,2}
使用
simple.a;//輸出1
3.指向結構的指標
區結構地址必須要用&,但是陣列卻即可以用&也可以不用
struct data {
int a;
int b;
};data1{1,2}
struct data *p;
p=&data1;
(*p).a;//輸出1
p->b;//輸出2
struct book
{
char name[20];
int callno;
};
struct book b1={"abc",100};
struct book *b2;
b2=&b1;
printf("\n%s %d",b1.name,b1.callno);
printf("\n%s %d",b2->name,(*b2).callno);
輸出:
abc 100
abc 100
4.成員運算子點"."和指向結構體成員運算子"->"的區別
兩者都是用來引用結構體變數的成員,但它們的應用環境是完全不一樣,前者是用在一般結構體變數中,而後者是與指向結構體變數的指標連用,例如:有定義
struct student
{
long num;
float score;
};
struct student stud, *ptr=&stud;
則stud.num、stud.score、ptr->num等都是正確的引用方式,但ptr.num、stud->num就是不允許的,其實ptr->num相當於(*ptr).num,只是為了更為直觀而專門提供了這->運算子。
最後指出,這兩者都具有最高優先順序,按自左向右的方向結合。
5.結構陣列引數
#include <stdio.h>
struct book
{
char name[20];
int callno;
};
int main(void)
{
struct book b1={"abc",100};
struct book *b2;
b2=&b1;
printf("\n%s %d",b1.name,b1.callno);
printf("\n%s %d",b2->name,(*b2).callno);
show(b1);
show_(b2);
printf("\n");
}
show(struct book m)
{
printf("\n%d",m.callno);
}
show_(struct book *m)
{
printf("\n%d",(*m).callno);
printf("\n%d",m->callno);
}
輸出:
abc 100
abc 100
100
100
所以:->就相當於*
擴充套件閱讀:
十二.union聯合
1.定義
聯合也叫共同體,和結構struct很像
struct可以定義一個包含多個不同變數的型別,每一個變數在記憶體中佔有自己獨立的記憶體空間,可以同時儲存不同型別的資料。
uniion也可以定義一個包含多個不同變數型別,但這些變數只共有同一個記憶體空間,每次只能使用其中的一種變數儲存資料。
問題是這樣做有什麼意義?
union{
int a;
float b;
char c;
}a;
a.b=10.123; printf("%d",a.a);
雖然沒有初始化a,一樣可以使用!因為a,b,c共用同一塊記憶體。
十三.typedef宣告
typedef為C語言的關鍵字,作用是為一種資料型別定義一個新名字。這裡的資料型別包括內部資料型別(int,char等)和自定義的資料型別(struct等)。
在程式設計中使用typedef目的一般有兩個,一個是給變數一個易記且意義明確的新名字,另一個是簡化一些比較複雜的型別宣告。
typedef與結構結合使用
typedef struct tagMyStruct
{
int iNum;
long lLength;
} MyStruct;
這語句實際上完成兩個操作:
1) 定義一個新的結構型別
struct tagMyStruct
{
int iNum;
long lLength;
};
2) typedef為這個新的結構起了一個名字,叫MyStruct。
typedef struct tagMyStruct MyStruct;
因此,MyStruct實際上相當於struct tagMyStruct,我們可以使用MyStruct varName來定義變數。
typedef int k;
k k1=10;
此宣告定義了一個 int 的同義字,名字為k。注意 typedef 並不建立新的型別。它僅僅為現有型別新增一個同義字。
typedef struct node
{
int value;
struct node *link;
}Node;
注意不要寫成
typedef struct node
{
int value;
Node *link;
}Node;
不然會有警告:警告:從不相容的指標型別賦值
所以在結構中的巢狀結構不要用typedef的宣告!
十四.前處理器
1.define常量
#defined MMX 50
printf("%d",MMX);
使用define更高效,可讀性更好。
2.include檔案包含
#include "filename"
3.條件編譯
形式一
#ifdef
#else
#endif
形式二
#ifndef
#else
#endif
形式三
#if
#else
#endif
非0 為真
選擇功能是否加入,在編譯階段。
作用一:除錯開關。
#define DEBUG//定義可除錯 不定義則不
#ifnedf DEBUG
#ifdef
作用二:便於確定哪些標頭檔案沒有編譯
#ifnedf FILE_H_
#define FILE_H_
總結:
#define 定義巨集
#undef 取消已定義的巨集
#if 如果給定條件為真,則編譯下面程式碼
#ifdef 如果巨集已經定義,則編譯下面程式碼
#ifndef 如果巨集沒有定義,則編譯下面程式碼
#elif 如果前面的#if給定條件不為真,當前條件為真,則編譯下面程式碼
#endif 結束一個#if……#else條件編譯塊
#error 停止編譯並顯示錯誤資訊
擴充套件閱讀:http://blog.csdn.net/21aspnet/article/details/6737612
十五.輸入輸出
1.Printf輸出
格式
%d有符號短整型 %u無符號短整型
%ld有符號長整型 %lu無符號長整型
%x無符號16進位制 %o無符號8進位制
%f浮點型 %lf double浮點型
%c字元 %s字串
printf("putout %d %f",a,b)
注意:printf會輸出中文亂碼,其實是SecureCRT這樣的終端編碼格式問題,設定為預設即可。
控制符
dd 指定欄位寬度的位數
. 區分欄位寬度與精度的小數點
- 在指定的欄位狂度內左對齊\n換行符
printf("%4d",a);//
輸出: 1
2.scanf輸入
int d=0;
注意是scanf("%d",&d)
這裡千萬要注意如果寫為scanf("d%",&d)一樣可以編譯通過,但是會引起隱含的bug!!!
注意是&d而不是d,因為是改變變數的地址對應的內容,而不是直接取其值
#include <stdio.h>
main()
{
int a=1;
char c='z';
char s[]="hello";
scanf("%d %c %s",&a,&c,&s);
printf("\n%d",a);
printf("\n%c",c);
printf("\n%s",s);
printf("\n");
}
注意:scanf會以回車結束,但是讀到空白就認為輸入結束,空白後面的內容會忽視。。。
3.gets與puts
warning: the `gets' function is dangerous and should not be used.
gets從鍵盤獲得一個字串,按回車終止,但是使用gets會提示警告!
puts一次輸出一個字串與printf不同,prints中可以插入字元。所以puts只能用多個變通實現。
char ss[30];
gets(ss);
printf("\n%s",ss);
puts("\noutput:");
puts(ss);
printf("\n");
4.fgets與fputs
fgets可以避免scanf的侷限
fgets()函式的基本用法為: fgets(char * s,int size,FILE * stream);
char name[20];
printf("\n 輸入任意字元 : ");
fgets(name, 20, stdin);//stdin 意思是鍵盤輸入
fputs(name, stdout); //stdout 輸出
printf("\n");
fgets比gets安全!
為了安全,gets少用,因為其沒有指定輸入字元的大小,限制輸入緩衝區得大小,如果輸入的字元大於定義的陣列長度,會發生記憶體越界,堆疊溢位。後果非常嚴重!
fgets會指定大小,如果超出陣列大小,會自動根據定義陣列的長度截斷。
十六.檔案
fopen開啟檔案
FILE結構體已定義在stdio.h中
檔案末尾標誌是ASCII碼26,fgetc讀到該特殊字元返回巨集指令EOF
fclose關閉檔案
1.字元讀寫
fgetc 從當前指標位置讀取一個字元,然後將指標前進一個位置,指向下一個字元,返回讀取的字元。
fputc 將字元寫入檔案
讀檔案套路
FILE * fp;
fp=fopen("檔案地址","r");
char ch;
ch=fgetc(fp)
#include <stdio.h>
#include <stdlib.h>
void main()
{
FILE * fp;
char ch;
fp=fopen("a.c","r");
if(fp==NULL){
//如果檔案不存在的話
puts("can't open file!");
exit(1);
}
while(1)
{
ch=fgetc(fp);
if(ch==EOF)
{
break;
}
printf("%c",ch);
}
fclose(fp);
}
注意:如果不包含標頭檔案stdlib.h,編譯後就出現了警告:隱式宣告與內建函式’exit’不相容。
檔案開啟模式:
無法開啟返回NULL
r:讀。
w:寫。
a:末尾追加。檔案不存在則建立。
r+:開頭讀寫
w+:覆蓋讀寫
a+:末尾追加讀寫
對於二進位制檔案要加b
rb,wb等
2.字串讀寫
puts
gets
fputs 將字串寫入檔案
fgets 一次只讀取一行
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main( )
{
FILE *fp ;
char s[80] ;
fp = fopen ("pr1.txt", "w");
if (fp == NULL)
{
puts ( "Cannot open file") ;
exit(1) ;
}
printf ("\nEnter a few lines of text:\n");
while (strlen (gets (s)) > 0)
{
fputs (s, fp);
fputs ("\n", fp);
}
fclose (fp) ;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
FILE * fp;
char s[30];
fp=fopen("a.txt","r");
if(fp==NULL){
puts("can't open file!");
exit(1);
}
while(fgets(s,20,fp)!=NULL)
{
printf("%s",s);
//fputs(s,fp);
//fputs("\n",fp);
}
fclose(fp);
}
如果是來自鍵盤:fgets(name, 20, stdin);//stdin 意思是鍵盤輸入
3.fprintf和fscanf行
這兩個函式分別是格式化輸出和格式化輸入函式,按照指定的格式輸入資料或者在螢幕上輸出資料,例如“結構”。
fprintf與fscanf和printf與scanf的區別是多了用於儲存和讀取的檔案
在運用fprintf與fscanf時,在向檔案輸出資料及從檔案讀取資料時,分隔符應該相一致。
#include <stdio.h>
#include <stdlib.h>
main( )
{
FILE *fp ;
struct emp
{
char name[40] ;
int age ;
float bs ;
};
struct emp e;
fp = fopen ( "pr1.txt", "w" ) ;
if ( fp == NULL )
{
puts ( "Cannot open file" ) ;
exit(1) ;
}
int i;
for(i=0;i<4;i++)
{
printf ( "\nEnter name, age and basic salary: " );
scanf ( "%s %d %f", e.name, &e.age, &e.bs ) ;
fprintf ( fp, "%s %d %f\n", e.name, e.age, e.bs );
}
fclose ( fp );
fp = fopen ( "pr1.txt", "r" ) ;
if ( fp == NULL )
{
puts ( "Cannot open file" ) ;
exit(1) ;
}
while ( fscanf ( fp, "%s %d %f", e.name, &e.age, &e.bs ) != EOF )
printf ( "\n%s %d %f", e.name, e.age, e.bs ) ;
fclose ( fp ) ;
}
輸出:
Enter name, age and basic salary: 北京 2008 51
Enter name, age and basic salary: 上海 2010 234.7
Enter name, age and basic salary: 廣州 2011 34
Enter name, age and basic salary: 香港 2020 78
北京 2008 51.000000
上海 2010 234.699997
廣州 2011 34.000000
香港 2020 78.000000
4.fwrite和fread塊
fwrite和fread函式讀寫大的資料塊。主要用於二進位制流。
1.函式功能用來讀寫一個數據塊。
2.一般呼叫形式
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
3.說明
(1)buffer:是一個指標,對fread來說,它是讀入資料的存放地址。對fwrite來說,是要輸出資料的地址。
(2)size:要讀寫的位元組數;
(3)count:要進行讀寫多少個size位元組的資料項;
(4)fp:檔案型指標。
注意:
1 完成次寫操(fwrite())作後必須關閉流(fclose());
2 完成一次讀操作(fread())後,如果沒有關閉流(fclose()),則指標(FILE * fp)自動向後移動前一次讀寫的長度,不關閉流繼續下一次讀操作則接著上次的輸出繼續輸出;
3 fprintf() : 按格式輸入到流,其原型是int fprintf(FILE *stream, const char *format[, argument, ...]);其用法和printf()相同,不過不是寫到控制檯,而是寫到流罷了。注意的是返回值為此次操作寫入到檔案的位元組數。
#include <stdio.h>
#include <stdlib.h>
main( )
{
FILE