數組和指針
Int powers[8] = {1,2,4,6,8,16,32,64};初始化僅asic C,
常量數組:const int days[months]={31,28,31,30,31,30,31,31,30,31,30,31};
在函數的內部聲明,并且沒有使用關鍵字static,不同的存儲類具有不同的屬性,因此不能把本章的只是推廣到其它的存儲類。例如:沒有進行初始化,一些存儲類的變量和數組會把它們的存儲單元設置為0;數組則直接讀取上面的內容。
如果不初始化數組,數組元素和未初始化的普通變量一樣,其中存儲的是無用的數值;但是如果部分初始化數組,未初始化的元素則被設置為0;
如果初始化列表中的項目的個數大于數組大小,編譯器會毫不留情地認為這是一個錯誤。但是用另外一種形式可以避免受到編譯器的此類奚落:您可以省略括號中的數字,從而讓編譯器自動匹配數組的大小,和初始化列表中的項目數目。
Sizeof days / sizeof days[0];
C99可以對單個數組元素賦值,
Int arr[6] = {[5] = 212};其它的都為0;
又如:
Int days[MONTHS] = {31,28,[4] = 31,30,31,[1] = 29};
結果為:31,29,0,0,31,30,31,0,0,0,0,0;

聲明數組時在方括號內只能使用整數常量表達式,整數常量表達式是由整數常量組成的表達式,sizeof表達式被認為是一個整數常量。而和c++不一樣,一個const值卻不是整數常量。并且該表達式的值必須大于0;

在形參的形式中
Fun(Int * char para1)等價于 fun(int para1[]);
Int * p1,*p2,*p3;這說明int *不是一個整體,int只是修飾指針的類型,* 是指針類型(猜測),可能定義一個變量要2部分,int是第一部分,*是第二部分,平常只要第一部分。而且第二部分可以和第一部分分離。(純粹猜測)
表達式 ar[i] 和*(ar+i)的意義是等價的。但是只有是指針的時候才可以 ar++;
指針的8種操作
1.賦值可以把地址賦給指針。
2.求值 *char;
3.取指針地址 &char
4.將一個整數加給指針 ptr+4
5.增加指針的值
6.從指針中減去一個整數。
7.減小指針的值。
8.求差值可以求出兩個指針間的差值。
比較比較兩個指針的大小。

變長數組 VLA
2維數組的初始化:

一維數組時一行,2維是一個面,3維是一個立方體。
指針和數組
對指針+1的結果就是對該指針增加一個尺存儲單元,對于數組而言,地址會增加到下一個元素的地址。而不是下一個字節。
1.指針的數值就是它所指向的對象的地址,指向float的指針,地址的內部表示方式是由硬件來決定的,很多計算機包含多個字節的數據類型,比如double類型的變量,對象的地址通常指的是其首字節的地址。
2.指針前運用運算符* 就可以得到該指針所指向的對象的數值。
3.對指針加1,等價于對指針的值加上它所指向的對象的字節大小。
在函數原型或函數定義頭的場合中,可以用int * ar代替int ar[],無論在任何情況下,形式int * ar都表示ar是指向int的指針。形式int ar[]也可以表示ar是指向int的指針,但只是在聲明形式參量時才可以這樣使用,使用第二中形式可以提醒讀者ar不僅指向一個Int數值,而且它指向的這個int是一個數組中的元素。
由于原型允許省略名稱因此下面的聲明是等價的:
Int sum (int *ar,int n); Int sum (int *,int); Int sum (in tar[],int n); Int sum (int [], int);
定義函數時,名稱是不可以省略的,因此,在定義時下面兩種形式是等價的:
Int sum(int *ar,int n) { } Int sum (in tar[],int n) { } Int marbles[size];
Sizeof marbles 是整個數組的大小,
Sizeof ar或者sizeof tar 就是單個數組元素的大小。
也可以傳遞首指針和尾指針
Int sump(int * start,int * end) { Int total = 0; While(start < end ) { Total += *start; Start++; } }


在c中,兩個表達式 ar[i] 和*(ar+i) 的意義是等價的,而且不管ar是一個數組名還是一個指針變量,這兩個表達式都可以工作,然而只有當ar是一個指針變量時,才可以使用ar++這樣的表達式。
指針操作
指針之間的減法結果等于相對于他們的類型的值,比如
Pi+3 –pi=3,這個3相對于他們的類型int而言就是3個4字節的地址長度。
指針可以直接賦值給以個指針。
計算機并不檢查指針是否依然指向某個數組元素,C保證指向數組的指針和指向數組后的第一個地址的指針都是有效的,但是如果指針在進行了增量或減量運算后超出了這個范圍,后果將是未知的,另外,可以對指向數組元素的指針進行取值運算,但不能對指向數組后的第一個地址的指針進行取值運算,盡管這個指針式合法的。
Int *pt;
*pt = 5;
這是不可行的,因為pt的值是隨機的,所以隨機地址賦值為5,可能是重要的位置。
指針不能加指針。
由于參數傳遞數組的時候是傳遞地址的,所以可以改變原數組的值,但是如果不想改變要保護原數組的話,可以在函數原型和定義的形式參量聲明中使用關鍵字const;
使用const來創建符號常量(const double PI = 3.14159),數組常量(const mars [3] ={1,2,3}),指針常量int * const p=&a;,以及指向常量的指針const int * p=&a;(const int a=3;)。
1)指針變量 int * p = &a;
2)指針常量 int * const a = &b; 這里的a 是個指針常量它指向變量b的地址,不能再修改它的值,a = &c;是錯誤的。
3)指向常量的指針它是變量指向的是一個常量
它是一個變量,但它指向的內容是常量。
Const int b = 1,c = 2;
Const int * a;
A = &b;a = &c,由于它本身是變量,所以可以改變它的值,再用它指向常量c.
但是它指向的內存是常量,是不能被修改的。如: *a = 20;是錯誤的。
還有另外一種寫法 int const * a;const的位置,還是在*前面;
4)指向常量的指針常量它是常量,但是它指向的內容是常量
Cons int b = 1, c = 2;
Int const * const a = &b;
看關鍵字const,const后面的不可修改。
Int * const a =&b;//則a不能被修改。
Int const * a=&b; //const后面是*a,則說明*a不能被修改。
把常量或非常量數據的地址賦給指向常量的指針式合法的。

然而只有非常量的數據才可以賦給普通的指針:

所以要分三個點來看指向的是常量還是非常量,還有就是常量指針還是指針常量。
多維數組與指針

Int zippo[4][2]:
數組名zippo同事也是數組首元素的地址,本例中,zippo的首元素本省又是包含兩個int的數組,因此zippo也是包含兩個int數組的地址,
1)Zippo是數組首元素的地址,所以zippo的值和&zippo[0]相同,另一方面,zippo[0]本身是包含兩個整數的數組,因此zippo[0]的值同其首元素(一個整數)的地址&zippo[0][0]相同。簡單的說zippo[0]是一個整數大小對象的地址,zippo是兩個整數大小對象的地址,因為整數和兩個整數組成的數組開始同一個地址,因此zippo和zipp[0]具有相同的數值。
2)對一個指針加1,會對原來的數值加上一個對應類型大小的數值。在這方面,zippo和zippo[0]是不一樣的,zippo所指向的對象的大小事兩個int,而zippo【0】所指向的大小事一個int,因此zippo+1和zippo[0]+1的結果不同。
3)對一個指針取值(使用運算符*或者帶有索引的[]運算符)得到的是該指針所指向對象的數值,因為zipp[0]是其首元素zippo[0][0]的地址,所以 *(zippo[0])代表存儲在zippo[0][0]中的數值,即是一個int數值,同樣zippo代表其首元素的地址,但是zippo[0]本身就是int數的地址,即&zippo[0][0],因此*zippo是&zippo[0][0]。對這兩個表達式同時應用取值運算符將得到 **zippo等價于*&zippo[0][0],后者簡化為一個int數 zippo[0][0].簡而言之,zippo是地址的地址,需要兩次取值才可以得到通常的數值。地址的地址或指針的指針式雙重間接地典型例子。
總結:數組名是它本身元素的首地址,所以一維數組中的數組名就是數組單個元素的第一個元素的首地址,而二維數組就是一維數組的數組,所以它的名稱就是一維數組的第一組數組的地址,所以他們相加結果是不一樣的。相加就相當于他們元素加1,一維數組的元素是一個元素,而二維數組加1就相當于一維數組的一組數組。三維一樣是二維數組的數組。它加1相當于加一個二維數組的量。對于取值來說二維是一維的地址,所以要經過兩次取值才能取到數。

Int (* pz)[2] // 首先是一個指針,指向一個包含2個int值得數組。
pz指向一個包含2個int值得數組。加()是因為[]優先級高于*
Int說明數組和指針都int型的。
Int * pz[] //是指2個指向int值的指針構成的數組。
看這個名稱與誰先結合,與【】它就是一個數組,與*就是一個指針。

最后一行說明指針的指針不能賦給2維數組名。
Int * p1;
Const int *p2;
Const int ** pp2;
P1=p2; // 非法,把const賦值給非const指針
P2=p1;// 合法 把非const指針賦值給const指針
Pp2= &p1; //非法 為什么呢不也是非const賦值給const么
原來是2層間接賦值的時候不可以。1層間接是可以的。
二維數組的形參聲明:
Int fun(Const int (* pt) [4])
或者int fun(const pt [][4]);
Typedef int arr4[4]; Typedef arr4 arr3x4[3];
Int sum2(arr3x4 ar,int rows);等價于 Int sum2(in tar[3][4],int rows); Int sum2(in tar[][4],int rows);
一般聲明多維數組的形參時可以省略最左邊的數目。
傳統c向函數傳遞多維數組的方法是把數組名傳遞給相應的類型的指針參量。指針生命需要制定各維的大小(除了最左面的不需要明確指出大小);第一個參量的維數大小通常為第二個參量來傳遞;
Void display(double ar[][12],int rows)… Display(sales,5);
變長數組提供了另一種方法,兩個維大小都可以作為參數被傳遞給函數。因此函數原型和函數調用就可以如下:;
Void display(int rows,int cols,double ar[rows][cols]); …. …. Display(5,12,sales);
變長數組必須是自動存儲類,而且聲明時不可以進行初始化。
變說的是創建前維數可以是變量,創建后是不可以變的。
省略名稱的話
Int sum2d(int,int,ar[*][*]);
二維數組,
復合文字
對參量傳值,可以穿變量和常量,但是數組沒有數組常量可供傳遞,c99新增了復合文字,文字是非符號常量,5是int類型的文字,81.3是double類型的文字,’Y’是char類型的文字,”elephant”是字符串文字。如果有能夠表示數組和結構內容的復合文字,那么在編寫程序時更為方便。相當于java中的匿名數組等等
(int[2]){10,20}
相當于一個數組,可以傳遞給參量,或者給指針賦值。
字符串和字符串函數
字符串的定義
字符串常量define “XXXXXX”,
char數組 char[]={‘c’,’d’,’\0’},char[] = “cd”;
char指針 char * s=”just a test!”;
和字符串數組char *s[]={”just a test!!”,”just a test too”}。
1)字符串常量:又成字符串文字,是指位于一對雙引號中的任何字符。雙引號里的文字加上編譯器自動提供的結束標志\0字符,作為一個字符串被存儲在內存里。
字符串文字中間沒有間隔或者間隔是空格符,ansic會將其串連起來。
字符串常量是靜態存儲類,靜態存是指如果在一個函數中使用的字符串常量,即使是多次調用了這個函數,該字符串在程序的整個運行中只存儲一份。整個引號中的內容作為指向該字符串存儲位置的指針。與數組名作為數組存儲位置的指針類似。

1)字符串數組及其初始化
Const char ml[40] = “Limit yourself to one line’s worth.”;
注意標志結束的空字符,如果沒有它,得到的就只是一個字符數組而不是一個字符串。指定數組大小時,一定要確保數組元素數比字符串長度至少多1.未被使用的元素均被自動初始化為0,這里的0是char形式的空字符,而不是數字字符0.

Const char *m3[] = “\nEnough about me what’s your name?”;
這個聲明和下列聲明的作用幾乎相同:
Char m3[] = “\nEnough about me what’s your name?”;
都聲明了m3是一個指向給定字符串的指針。都是被引用的字符串本身決定了為字符串預留的存儲空間大小。
總之:數組初始化時從靜態存儲區把一個字符串賦值給數組,而指針初始化只是復制字符串的地址。
如:
Char heart[]=”I love Tillie!”;
Char *head =”I love Millie!”;
主要區別是:heart是個常量,而指針head則是個變量。
可以
Putchar(heart[i]); Putchar(head[i]); *(Heart+i) *(Head+i)
只有指針可以
While(*(head)!=’\0’)
Putchar(*(head++));

數組的元素師變量(除非聲明數組時帶有關鍵字const)
建議初始化一個指向字符串文字的指針時使用const修飾符:
Const char * p1=”Klingon”;
用一個字符串文字來初始化一個非const得數組,則不會導致此類問題,因為數組從最初的字符串得到一個拷貝。

這個示例說明字符串常量是在靜態存儲區存儲的。
字符指針只是把這個地址賦值過來,
而數組則把內容復制過來。
由于字符指針指向的是字符串常量,所以不能用字符指針來修改字符串常量。
建議指向字符串的字符指針用const修飾。Const char * p2=”Klingon”;
字符串數組
Const char * mytal[5] = {“Adding numbers swiftly”,”Multiolying accurately”,
”Stashing dta”,”Following instructions to
the letter”,”Understanding the c language”};
所以mytal是一個由5個指向char的指針組成的數組。也就是說,mytal是一個數組,每個元素都是一個char類型值得地址。
也可以用字符的二維數組來聲明字符串數組。Char mytal[5][20]
兩者的不同之處是數組在內存中連續存放,而指針數組指向的字符串不一定是連續存放的。
指針和字符串
字符指針賦值只是把指針的值也就是字符串的地址賦給了另一個指針。也就是2個指針指向了同一個地址。
字符串輸入:如果想把一個字符串讀到程序中,必須首先預留存儲字符串的空間,然后使用輸入函數來獲取這個字符串。
Char *name;
Scanf(“%s”,name);
這可能會通過編譯器,但是在讀入name的時候,name會覆蓋程序中的數據和代碼,并可能導致程序異常終止。
最簡單的方法就是在聲明中明確指出數組大小。Char name[8];
獲取字符串的函數,
1)scanf(),如果是%s字符串讀到(但是不包括下一個空白字符比如空格、制表符、換行符)

它遇到換行符,制表符和空格符就結束,不包括但是也不丟棄。
1)gets(),丟棄末尾的換行符,并在末尾添加\0.

說明丟棄了回車符
返回一個字符指針,與參數數組時內容是一樣的。如果無措就返回一個地址,如果有誤或者遇到結束符,就返回一個NULL
如果是getchar()就要用 getchar()!=EOF,
1)fgets()函數。
Char name[50];//這時候已經為數組分配了內存空間。
Gets(name)!=NULL;
它返回一個字符指針與傳入的字符指針是同一個。
Fgets()函數與gets()函數有三點不一樣的地方:
1)它需要第二個參數來說明最大讀入字符數。
2)Fgets()讀取到換行符,就會把它存到字符串里,而不是像gets()那樣丟棄它。
3)需要第三個參數來說明讀取哪一個文件,鍵盤上讀取,可以使用stdin。
根據特點:gets()從鍵盤讀取文本可能要更好,因為它更容易被使用,更快,而且更簡潔。

Scanf主要用于某種標準形式輸入的混合類型數據的讀取和轉換。
字符串輸出函數:
1)Puts顯示字符串自動在后面加上換行符。Puts遇到空格符就會停下來(、0).
2)fputs()的第二個參數來說明要寫的文件。
3)與puts()不同,fputs()并不為輸出自動添加換行符。
注意:gets()丟掉輸入里的換行符,但是puts()為輸出添加換行符。Fgets()存儲輸入中的換行符,fputs()也不為輸出添加換行符。
Printf()不添加換行符。
Gets() puts(); Fgets() fputs();
自定義字符串輸出函數
Int i = 0; While(*string) Putchar(*string++);
或記錄字符數。
字符串函數
Strlen()不考慮最后的\0字符的字符串長度。
Strcat 將第二個字符串的一份拷貝添加到第一個字符串的結尾,從而使第一個字符串成為一個新的組合字符串,第二個字符串并沒有給帶便。
Strncat()添加了一個字數,直到加到字數或者遇到空字符為止,由二者中先符合的那一個來終止添加過程。基本上是第一的長度減去字符串的長度再減一就是可以添加的最大值。
Strcmp();比較二個字符串,比較的是二個字符串的內容,小于為負數等于為零,大于為正數。
字符數組可以這樣初始化,但是不能這樣賦值:char tmp[40] = ”just a test”;
Char tmp[40];
Tmp = ”just a test”;
賦值可以使用strcpy函數來,也可以不在第一位。
如果第一個不是數組而是指針,那么指針指向的位置可以是任何地方。
Strcpy 兩個屬性:它是char *類型,它返回的是第一個參數的值,即一個字符的地址;其次第一個參數不需要指向數組的開始,這樣就可以只復制數組的一部分。

第一箭頭說明復制后把第二個的結束符也帶過去了,第二個說明函數返回的是第一個參數的地址。

箭頭后面部存在說明這個函數不能用來拷貝字符數組,只能用來拷貝字符串。

上面說明如果復制的位置位于目標字符長度之后,它不會去掉源字符串的結束符,并且也不會考慮復制后是不是超出了大小范圍。充分說明C語言設計時要求速度和充分相信程序員。
Strncpy函數:要比strcpy多注意由于源字符串被截斷復制,可能不帶有結束符。所以復制的時候大小要小一個。

字符的比較用關系運算符。
Strncmp(list,”astro”,5);用“astro”與list的前5個字符相比較。
小結:
Char *strcpy(char * s1,const char *s2):
該函數把s2指向字符串復制到s1指向的位置,返回值是s1.
Char *strncpy(char *s1,const char * s2,size_t n);
該函數把s2指向的字符復制到s1指向的位置,復制字符數不能超過n個,返回值是s1,空字符后的字符不被復制。如果源字符串的字符數少于n個,在目標字符串中就以空字符填充,如果源字符串的字符串的字符數大于或等于n個。空字符就不被復制,返回值是s1.

說明只看大小不管是不是字符串的結束。
Sprint()函數是向字符串中打印與printf函數一樣。
Char * strcat(char * s1,const char * s2);
S2指向的字符串被復制到S1指向字符串的結尾,復制過來的s2所指字符串的第一個字符覆蓋了s1所指字符串結尾的空字符。返回值是s1.程序員自己處理數組的大小。S1如果是數組必須足夠大。如果是字符串賦值的指針也是不可以的。

Char * strncat(char * s1,const char * s2,size_t n);
S2字符中的空字符及其后的任何字符都不會被復制,并且追加一個空字符到所得結果后面,返回值是s1.不要求S1的大小大于或等于復制后大小。
要牢記char * ptr;的聲明并沒有給指針分配空間,所以不能用于strcate的鏈接。
Int strcmp(const char * s1,const char * s2);
Int strncmp(const char * s1,const char * s2,size_t n);
Char * strchr(const char *s,int c);該函數返回一個指向字符串S中存放字符c的第一個位置的指針(標志結束的空字符時字符串的一部分,因此也可以搜索到它),如果沒有找到該字符,函數就返回空指針。
Char *strpbrk(const char *s1,const char *s2);
該函數返回一個指針,指向字符串S1中存放S2字符串中的任何字符的第一個位置,如果沒有找到任何字符,函數返回空指針。
排序字符串數組的時候可以用指針指向每條字符串,排序這個指針的數組;

Gets(char[n])!=NULL,遇到EOF字符,
Char[n][0]!=’\0’ 或者第一個是空行。
字符函數和字符串
Ctype.h
Toupper() 轉換為大寫
Ispunct() 是否為符號
命令行參數
Int main(int argc,char *argv[]) { //argc參數的個數,argv參數的數組 return 0; }
C編譯器允許main()沒有參數,或者兩個參數(有些實現允許更多的參數,但這將是對標準的擴展),兩個參數時,第一個參數是命令行中的字符串數,但不是必須的,第二個參數是一個指向字符串指針數組。Argv[0]一般是程序名,argv[1]是第一個參數,argv[2]是第二個參數。很多環境允許使用把多個單詞集中在一個參數里。例如:repeat “I am hungry” now
把字符串轉換為數字
轉換函數 atoi();atof();atol();strtol();strtoul()把一個字符串轉換為unsigned long型值,strtod()把一個字符串轉換為duble型
如果字符串只是以一個整數作為開頭,atoi函數依然可以工作,在這種情況下函數在遇到非整數部分之前一直轉換字符。
