【新手C語言】8.指標、字串
8.指標、字串
基礎C語言的最後一章了,也是比較晦澀的一章
如果您學C只是為了玩玩或者練演算法,那麼差不多已經夠用了
如果您有更大的目標,程式設計嘛,總是無止境的
指標
匯入
我們從sizeof談起:它是一個運算子。給出某個型別或變數在記憶體中佔據的位置,以位元組大小表示(1位元組=4位元)
接著是&,它實質上是一個運算子,它能夠獲得變數的地址(這也意味著它的運算元必須是變數)
順帶一提,用printf輸出地址時,使用%p , 而且取出的地址大小是否和int相同取決於編譯器(32位架構還是64位架構)
int i = 0 ; printf("%p" , & i) ; return 0 ;
&不能對沒有地址的東西取地址,比如a++,++a,a+b,...
指標
我們一直所說的“指標”,才是真正的,能夠完美儲存地址的變數
指標就是儲存地址的變數
我們使用星號* 表示我們所想要的這個變數,是一個指標
星號是“加給”變數的,比如
int *p,q ; 這裡面的p是指標,而q就是普通的整型
指標變數
變數的值是記憶體的地址
普通變數的值是實際的值
指標變數的值是具有實際值的變數的地址
*是一個單目運算子,用來訪問指標的值所表示的地址上的變數
它可以做右值,也可以做左值
int k = *p ;
*p = k+1 ;
由於指標變數的特殊性,我們若在函式中修改了指標變數,那麼也會修改它所指向的值
void f(int *p); void g(int k); int main(void){ int i = 6 ; printf("&i = %p\n",&i); f(&i); g(i); return 0 ; } void f (int *p){ printf(" p = %p\n" , p); printf(" p = %p\n" , *p); *p = 26 ; //就在這裡,把p變數的地址指向的那個變數(就是i)改為26 } void g (int k){ printf("k = %d \n " , k); }
指標與陣列
函式引數表中的陣列,實質上就是個指標(這也是為什麼寫a[]和a[10]之類的是一樣的),也因此在函式中我們不能直接用sizeof得到正確的陣列長度
函式引數表中的陣列實際上是指標,但是可以用陣列的運算子[]來運算
實際上,陣列變數是特殊的指標
1.陣列變數本身表達地址,所以我們取陣列的地址時無需使用&
int a[10] ;
int *p = a ;
2.但是陣列的單元表達的是變數,我們需要用&來取它。陣列a的地址,等於陣列單元a[0]的地址
3.*運算子可以對指標做,也可以對陣列做
4.陣列變數是const的指標,所以不能被賦值
字元型別
CHAR
char是最小的整數型別,同時也是一特殊的型別:字元
原因在於:
1.用單引號表示的字元字面量 'a', '1'
2.''也是字元
3.printf scanf 裡用%c來輸入輸出字元
char c ;
char d ;
c = 1 ;
d = '1' ;
printf("c = %d \n" , c) ; // 結果是1
printf("d = %d \n" , d) ; // 結果是49
以上的兩個1,一個是整型,而另外一個是字元(因此d打印出來是49)
%c 表示以字元的形式輸入/輸出
char c = 'A' ;
printf("%c \n" , c) ;
c++ ;
printf("%c \n" , c) ;
int i = 'Z' - 'A' ;
printf("%d \n" ,i ) ;s
a+'a'-'A' 可以把一個大寫字母變成小寫字母
a+'A'-'a' 可以把一個小寫字母變成大寫字母
逃逸字元
逃逸字元用來表示無法印出來的控制字元或特殊字元,它由一個反斜杆"\" 開頭 , 後面跟上另外一個字元
(如果開啟md看,會看見上面那個反斜槓實際上是兩個)
\b 回退一格
\t 到下一表格位 (也就是製表位上的位置,是每行固定的位置(試著敲一下tab),利用\t 可以使上下行對齊)
\n 換行
\r 回車
\" 雙引號
\' 單引號
\ 反斜槓本身
字串
在C語言中,字串指以0(整數0)結尾的一串字元
0和'\0'是一樣的,但是和'0'是不一樣的
0標誌著字串的結束,但是它不是字串的一部分,計算字串長度的時候也不包含這個0
字串以陣列的形式存在,也已陣列或指標的形式訪問(更多的是以指標的形式)
在string.h中有很多處理字串的函式
字串變數
我們有多種方式表達字串
char *str = "Hello" ;
char word[] = "Hello" ;
char line[10] = "Hello" ;
這裡面 "Hello"被稱為字串常量,"hello"會被編譯器變成一個字元陣列放在某處,這個陣列長度是6,結尾還有表示結束的0
兩個相鄰的字元常量會自動連線
小結
C語言的字串是以字元陣列的形態存在的,不能用運算子對字串做運算,通過陣列的方式可以遍歷字串
唯一特殊的地方是字串字面量可以用來初始化字元陣列
以及標準庫提供了一系列字串函式
字串常量(續)
char* s = "Hello , world!"; //我要指向某個地方的字串
s是一個指標,初始化為指向一個字串常量
由於這個常量所在的地方,實質上是s是const char* s , 不過由於歷史原因,編譯器不接受帶const的寫法
但是當我們試圖對s所指的字串做寫入的時候會導致嚴重後果
當我們編譯過程中有兩個相同的東西(比如s1 s2 兩個字串都是Hello world),它們會指向同一個地方
如果想要製作一個能修改的字串,那麼在一開始就需要用陣列定義
char s[] = "Hello, world!" ; //某個地方的字串就在這裡
區別
int i =0 ;
char *s = "Hello , World";
char *s2 = "Hello,World" ;
char s3[] = "Hello,World";
printf("&i=%p\n", &i) ;
printf("&s =%p\n", &s) ;
printf("&s2=%p\n", &s2) ;
printf("&s3=%p\n", &s3) ;
s3[0] = 'B' ;
printf("Here!s3[0] = %c\n",s3[0]);
return 0 ;
陣列字串:這個字串在這,作為本地變數會被自動回收
指標字串:不知道這個字串在哪,需要處理引數,可以動態分配空間
如果要構造一個字串-->陣列
如果要處理一個字串-->指標
字串可以表達為char*的形式,char*不一定是字串,只有在它所指的字元陣列有結尾0,我們才能說它所指的是字串
字串計算
賦值
char *t = "title" ;
char *s ;
s = t ;
實際上並沒有產生新的字串,只是讓指證s指向了t所指的字串。對s的任何操作就是對t做的
輸入輸出
%s代表輸入輸出的是字串
char string[8];
scanf("%s",string);
printf("%s",string);
scanf讀入一個單詞,到空格、tab、回車為止
想要在空格tab回車之後繼續讀,我們需要再來一個scanf,而且第二個scanf是不會讀到"空格tab回車"的
但是scanf實質上是不安全的,因為不知道要讀入的內容的長度
在百分號和s中間,可以增加一個數字,表示我們希望最多可以讀入多少字元,以此提高安全性。此時就不一定是以空格tab回車來區分了,讀完了,這個scanf就結束了
常見錯誤
char *string ;
scanf("%s",string);
以為char*是字串型別,定義了一個字串變數string就可以直接使用了,但實際上這種做法是十分危險的
char buffer[100] = ""; //空字串,buffer[0] == '\0'
char buffer[] = "" ;//數這個陣列的長度只有1!
string.h
strline : 傳入一個字串,返回它的長度,另外,返回的結果不包括那個0
strcmp:比較兩個字串,int strcmp(const char *s1 ,. const char *s2),s1==s2,返回0,s1>s2返回1,s1<s2返回-1 、
當二者不相等時,返回的是差值。比如比較"abc"和"Abc",返回32
!陣列的比較,永遠是false。想要用if語句判斷,我們需要補充比較結果(0,1,-1)
strcpy: char* strcpy(char* restrict dst,const char restrict src)
把src1的字串拷貝到dst,在這裡restric表示src和dst不重疊(C99)
strcat: char strcat(char*restrict s1 , const char *restrict s2)
把s2拷貝到s1後面,形成一個長的字串。返回s1,意味著s1需要有足夠的空間
strcpy和strcat,都可能出現安全問題,我們還有安全一點的版本使用。那就是strncpy,strncat
它們在結尾還需要額外傳入一個引數n,代表最多可以拷貝多少個字元
另外還有類似的strncmp,最後傳入的n可以控制比較前多少個字元