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
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
6.extern變量
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
所以:->就相當于*
擴展閱讀:
結構的成員訪問?
typedef struct 用法詳解和用法小結
typedef的四個用途和兩大陷阱
十二.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);
}文件打開模式:
無法打開返回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  *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 ); fwrite(&e,sizeof(e),1,fp);}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)while (fread (&e,sizeof(e),1,fp)==1)printf ( "\n%s %d %f", e.name, e.age, e.bs ) ; fclose ( fp ) ;
}5.文件復制
復制文本文件(用r)
#include <stdio.h>
#include <stdlib.h>
main( )
{FILE  *fs, *ft ;char  ch ;fs = fopen ( "pr1.txt", "r" ) ;if ( fs == NULL ){puts ( "Cannot open source file" ) ;exit(1) ;}ft = fopen ( "pr2.txt", "w" ) ;if ( ft == NULL ){puts ( "Cannot open target file" ) ;fclose ( fs ) ;exit(1) ;}while ( 1 ){ch = fgetc ( fs ) ;if ( ch == EOF )break ;elsefputc ( ch, ft ) ;}fclose ( fs ) ;fclose ( ft ) ;
}復制二進制文件 (用rb)
#include <stdio.h>
#include <stdlib.h>
main( )
{FILE  *fs, *ft ;int ch ;fs = fopen ( "pr1.exe", "rb" ) ;if ( fs == NULL ){puts ( "Cannot open source file" ) ;exit(1) ;}ft = fopen ( "newpr1.exe", "wb" ) ;if ( ft == NULL ){puts ( "Cannot open target file" ) ;fclose ( fs ) ;exit(1) ;}while ( 1 ){ch = fgetc ( fs ) ;if ( ch == EOF )break ;elsefputc ( ch, ft ) ;}fclose ( fs ) ;fclose ( ft ) ;
}十七.內存
1.內存分配
程序中局部變量在棧上分配空間,系統自動分配管理;
動態分配內存在堆上,需要用戶釋放。
都在<stdlib.h>
申請內存:malloc
重新申請:realloc
釋放內存:free
#include <stdio.h>
#include <stdlib.h>
main()
{
? ? int i;
? ? char c,*p;
? ? p = (char *)malloc(10);//分配十個字節的緩存區
? ? for(i=0;;i++)
? ? {
? ? ? ? c = getchar();//從鍵盤讀取單個字符數據
? ? ? ? if(i>9)
? ? ? ? {
? ? ? ? ? ? p = (char *)realloc(p,1);//重新增加申請一個字節的空間
? ? ? ? }
? ? ? ? if(c == '\n')
? ? ? ? {
? ? ? ? ? ? p[i] = '\0';
? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? p[i] = c;//將輸入的字符保存到分配的緩存區
? ? ? ? }
? ? }
? ? printf("%s\n",p);
? ? free(p);//釋放內存
}
2.memset
void *memset(void *s, int ch, size_t n);
函數解釋:將s中前n個字節替換為ch并返回s;
memset:作用是在一段內存塊中填充某個給定的值,它是對較大的結構體或數組進行清零操作的一種最快方法。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main(){
char a[5];
memset(a,'1',sizeof(a));
printf("%s\n",a);
char buffer[] = "Hello world\n";
printf("Buffer before memset: %s\n", buffer);
memset(buffer, '*', strlen(buffer) );
printf("Buffer after memset: %s\n", buffer);
}
輸出:
3.動態內存分配
動態內存分配是為了鏈表,否則只能用數組
使用malloc和free分配和釋放
#include <malloc.h>//malloc
#include <stdlib.h>//exit’
int * p;
p=malloc(100);
if(p==NULL)
{
printf("Out of memory!\n");
exit(1);
}
實際中p=malloc(sizeof(int));獲得足夠1個整數的內存
實際中p=malloc(10*sizeof(int));獲得足夠10個整數的內存
擴展閱讀:C語言內存動態分配
http://blog.csdn.net/21aspnet/article/details/146968
?
十八.異常
1.perror
perror("fff");
perror ()用 來 將 上 一 個 函 數 發 生 錯 誤 的 原 因 輸 出 到 標 準 設備 (stderr) 。參數 s 所指的字符串會先打印出,后面再加上錯誤原因字符串。此錯誤原因依照全局變量error 的值來決定要輸出的字符串。
fff: Success
十九.鏈表
定義鏈表
//先定義一個結構體
?typedef struct node{
int data;//數據域
struct node * next;//指針域? ,是struct node的成員,又指向struct node類型的的數據,這里存放下個結點的地址
}Node;
?
建立鏈表
//返回類型是之前定義的結構體
Node * creat()
{?
//需要先定義3個節點
Node * head=NULL;//頭結點
Node *p=NULL;//指向原鏈表的最后一個結點
Node *s=NULL;//指向新節點
head=(Node*)malloc(sizeof(Node));//動態分配內存空間
head->next=NULL;//開始頭結點也就是尾結點
p=head;//p一開始指向頭結點
//用while循環
int c=1;//為0是退出循環的條件
int d=0;
while(c)
{
//使用scanf接受用戶數據數據
scanf("%d",&d);
if?(d!=0)//繼續輸入
{
s==(Node*)malloc(sizeof(Node));//定義新節點s,和定義head一樣
s->data=d;//給新節點賦值
p->next=s;//將第一個結點鏈接在第二個結點后面
p=s;//p繼續指向當前節點
}
else
{
c=0;//用戶輸入0結束 輸入
}
}
p->next=NULL;//結束輸入時將最后結點定義為尾結點
return head;
}
//輸出結點
output(Node * head)
{
//新定義一個指針用于遍歷
//其實如果head->data會是0;單鏈表巧妙的從第二個結點開始,頭結點沒用,但是并不代表沒用值!
Node * p=head->next;
while(p!=NULL)
{
printf("%d",p->data);
p=p->next;//注意while循環里一定要讓P指針不斷往下指向
}
}
//查找制定元素的順序
searchdata(Node *? head)
{
int t=0;
printf("請輸入您要查找的值:\n");
scanf("%d",&t);
Node * p=head->next;
int k=0;
while(p!=NULL)
{
k++;
if(p->data==t)
{
printf("%d是第%d個元素:\n",t,k);
break;
}
p=p->next;
}
if(p==NULL)
{
printf("沒找到");
}
}
//元素之間插入值?
insertdata(Node *? head)
{
Node *s=NULL;//指向新節點
s=(Node*)malloc(sizeof(Node));//動態分配內存空間
int t=0;
int i=0;
printf("請輸入您要查找的值:\n");
scanf("%d",&t);
printf("請輸入您要插在那個元素后面:\n");
scanf("%d",&i);
?
Node * p=head->next;
while(p!=NULL)
{
if(p->data==i)
{
s->data=t;
s->next=p->next;//注意是先指向新節點,再改舊節點
p-next=s;
printf("插入成功:\n");
break;
}
p=p->next;
}
if(p==NULL)
{
printf("沒找到\n");
}
}
擴展閱讀:
鏈表的C語言實現http://blog.csdn.net/21aspnet/article/details/146968
單鏈表功能大全 http://blog.csdn.net/21aspnet/article/details/159053
?
二十.樹
二十一.圖
二十二.算法
1.隨機數
srand():生成隨機數種子
rand():生成隨機數
#include <stdio.h>
#include <stdlib.h>
main()
{
int i,j;
srand((int)time(0));//開始注釋這里
for(i=0;i<10;i++)
{
j=1+(int)(10.0*rand()/(RAND_MAX+1.0));
printf("%d ",j);
}
}
輸出:
說明:沒有調用srand每次產生的都是一樣的!
總結
 
                            
                        