1. 程式人生 > 其它 >【新手C語言】8.指標、字串

【新手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可以控制比較前多少個字元