1. 程式人生 > >C和指標學習

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 有符號

unsigned是無符號的意思,也就是說如果你的編譯系統給int分配的儲存單元的長度是2個位元組的話,有符號的int 取值範圍是-32768(即2^15)——32767(即2^15-1),而無符號的unsigned  int就是0-65535(2^16-1)

 

 

型別

最小範圍

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

 

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);
}
注意:如果不包含標頭檔案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