1. 程式人生 > >C語言中,為什麼字串可以賦值給字元指標變數

C語言中,為什麼字串可以賦值給字元指標變數

1.以字串形式出現的,編譯器都會為該字串自動新增一個0作為結束符,如在程式碼中寫
  "abc",那麼編譯器幫你儲存的是"abc\0"

2."abc"是常量嗎?答案是有時是,有時不是。

不是常量的情況:"abc"作為字元陣列初始值的時候就不是,如
                  char str[] = "abc";
    因為定義的是一個字元陣列,所以就相當於定義了一些空間來存放"abc",而又因為
    字元陣列就是把字元一個一個地存放的,所以編譯器把這個語句解析為
    char str[3] = {'a','b','c'};
                  又根據上面的總結1,所以char str[] = "abc";的最終結果是


    char str[4] = {'a','b','c','\0'};
    做一下擴充套件,如果char str[] = "abc";是在函式內部寫的話,那麼這裡
    的"abc\0"因為不是常量,所以應該被放在棧上。

是常量的情況:  把"abc"賦給一個字元指標變數時,如
                  char* ptr = "abc";
    因為定義的是一個普通字元指標,並沒有定義空間來存放"abc",所以編譯器得幫我們
    找地方來放"abc",顯然,把這裡的"abc"當成常量並把它放到程式的常量區是編譯器
    最合適的選擇。所以儘管ptr的型別不是const char*,並且ptr[0] = 'x';也能編譯

    通過,但是執行ptr[0] = 'x';就會發生執行時異常,因為這個語句試圖去修改程式
    常量區中的東西。
    記得哪本書中曾經說過char* ptr = "abc";這種寫法原來在c++標準中是不允許的,
    但是因為這種寫法在c中實在是太多了,為了相容c,不允許也得允許。雖然允許,
    但是建議的寫法應該是const char* ptr = "abc";這樣如果後面寫ptr[0] = 'x'的
    話編譯器就不會讓它編譯通過,也就避免了上面說的執行時異常。
    又擴充套件一下,如果char* ptr = "abc";寫在函式體內,那麼雖然這裡的"abc\0"被
    放在常量區中,但是ptr本身只是一個普通的指標變數,所以ptr是被放在棧上的,

    只不過是它所指向的東西被放在常量區罷了。

3.陣列的型別是由該陣列所存放的東西的型別以及陣列本身的大小決定的。
  如char s1[3]和char s2[4],s1的型別就是char[3],s2的型別就是char[4],
  也就是說儘管s1和s2都是字元陣列,但兩者的型別卻是不同的

4.字串常量的型別可以理解為相應字元常量陣列的型別
  如"abcdef"的型別就可以看成是const char[7]

5.sizeof是用來求型別的位元組數的。如int a;那麼無論sizeof(int)或者是sizeof(a)都
  是等於4,因為sizeof(a)其實就是sizeof(type of a)

6.對於函式引數列表中的以陣列型別書寫的形式引數,編譯器把其解釋為普通
  的指標型別,如對於void func(char sa[100],int ia[20],char *p)
  則sa的型別為char*,ia的型別為int*,p的型別為char*


7.根據上面的總結,來實戰一下:
  對於char str[] = "abcdef";就有sizeof(str) == 7,因為str的型別是char[7],
  也有sizeof("abcdef") == 7,因為"abcdef"的型別是const char[7]。
  對於char *ptr = "abcdef";就有sizeof(ptr) == 4,因為ptr的型別是char*。
  對於char str2[10] = "abcdef";就有sizeof(str2) == 10,因為str2的型別是char[10]。
  對於void func(char sa[100],int ia[20],char *p);
  就有sizeof(sa) == sizeof(ia) == sizeof(p) == 4,
  因為sa的型別是char*, ia的型別是int*,p的型別是char*。

四、

這幾天搞Unix上的C程式,裡面用到了很多字元陣列和字串指標,我記得在學完C語言後相當一段時間裡,對指標這個東西還是模模糊糊,後來工作也沒怎麼用到過C,雖然網上這類的文章也有很多,還是決定自己在這做個小總結,也算加深下自己的印象,寫了下面的測試程式:

#include <stdio.h>

int main(int argc, char *argv[])
{

  char day[15] = "abcdefghijklmn";
  char* strTmp = "opqrstuvwxyz";

  printf("&day is %x\n",&day);
  printf("&day[0] is %x\n",&day[0]);
  printf("day is %x\n",day);
 
  printf("\n&strTmp is %x\n",&strTmp);
  printf("&strTmp[0] is %x\n",&strTmp[0]);
  printf("strTmp is %x\n",strTmp);
 
  getchar(); 
  return 0;
}

執行後螢幕上得到如下結果:


其實看到結果估計很多東西就好明白了,

    先看看前三個輸出也就是關於變數day的,在 char day[15] = "abcdefghijklmn"; 這個語句執行的時候,系統就分配了一段長15的記憶體,並把這段記憶體起名為day,裡面的值為"abcdefghijklmn",如下圖所示:

        再看程式,第一個輸出,&day,&號是地址運算子,也就是day這個變數的記憶體地址,很明顯,在最前面,也就是a字元所在位元組的地址;
        對於第二個輸出也就好理解了,&day[0],就是day陣列中第一個變數(也就是a)的地址,因此他們兩個是一樣的;
        第三個輸出是day,對於陣列變數,可以使用變數名來索引變數中的內容,其實這裡的day可以理解成陣列變數退化的指標,並且指向陣列的開頭,既然把它理解成指標,那麼它的值肯定是地址了,所以他的值和上面兩個也一樣。

    再看看後面三個輸出,關於字串指標strTmp,在執行char* strTmp = "opqrstuvwxyz";後,記憶體的圖示如下:


如圖所示,記憶體分配了兩段記憶體,一個名為strTmp,型別是一個字元指標,另外一段是一個字串常量,且strTmp裡面存放著字元常量的首地址,注意這裡無法通過strTmp修改這段字串,因為是常量;於是程式中的後面三個輸出就好理解了;
    
  &strTmp:strTmp這個字元指標的地址
  &strTmp[0]:strTmp所指字元常量第一個字元的地址
  strTmp:strTmp這個字元指標的值,即字元常量的首地址

因此,最後兩個的值是一樣的。
      指標可以這樣理解,指標這種型別,和int,char,double等等是一樣的,只是它用來儲存地址值的,而int變數儲存整數,char變數儲存字元,僅此而已,就char型指標或者int指標,本質是一樣的,都是存放的地址,只不過那個地址所裡面的變數型別不同而已,還有一種void型指標,就是可以放任何型別變數的地址。

五、個人程式碼以及註釋,純屬個人理解,定有不妥之處,望批評指正:

#include <stdio.h>

int main(int argc, char *argv[])
{
 char* strTmp = "abcd";
 printf("strTmp is %s\n",strTmp);//將字串常量"abcd"的地址所隱含的內容轉換成“string型別”
 printf("strTmp is %d\n",strTmp);//將字串常量"abcd"的地址轉換成int型別,這裡不同的機子不同的時間的執行結果可能會不一樣,因為地址可能會發生變化
 printf("strTmp is %c\n",strTmp);//將字串常量"abcd"的地址轉換成字元型,這裡不同的機子不同的時間的執行結果可能會不一樣,因為地址可能會發生變化
 printf("*strTmp is %c\n",*strTmp);//將字串常量"abcd"的地址所隱含的內容轉換成字元型,由下面註釋的這句會丟擲異常可知,這裡並無擷取字串,*strTmp長度本身就是1
 //printf("*strTmp is %s\n",*strTmp);//不能將字元轉換成字串型
 getchar();
 return 0;
}

 六、後來又有看到下面這樣的說法可供讀者參考:

1. C語言中沒有字串型別,只有用字元陣列來表示。這和c++中string是有區別的,C++中string是可以直接賦值如string s;s="Hello world";但是C語言中的字元陣列卻不能這樣。所以,這裡的strTmp可以理解為字元陣列的首地址,也可以用它代表整個字元陣列,所以能輸出所有字元陣列中的內容。

 2.字串就是字元陣列或者是指標。 記憶體實現都一樣的。 陣列名字就是一個指標。

char ch[100] ;
char *p;
p =ch;

3.定義的字串方式舉例:

字串定義其實很簡單在c/c++語言中定義一個字串可以使用如下的語法:

char *s1=“string1”;//定義字串常量,指標形式

char s2[]=“string2”;//定義字串常量,陣列形式

char *s3=new char[10];//定義字串變數並分配記憶體 指標形式

strcpy(s3,"string3");//為s3賦值

char s4[10];//定義字串變數,陣列形式

strcpy(s4,"string4");//為s4賦值

以上四種方法都能定義一個字串,同時通過字串在記憶體中的分佈可以清楚地知道是什麼情況

4. C語言中字串賦值方法strcpy(char*d,char*s)其中s代表是源字串,d代表目標字串,也就是你要賦值的字串。

5.c語言中的字串跟java或c++中的字串不同。如char *p;其中p是一個指標,p中儲存一個記憶體緩衝區的首地址。所謂的記憶體緩衝區就是一段連續的記憶體地址,裡面存放了一系列的字元。那系統又是如何判斷在哪裡結束呢。那就是根據符號‘\0’。這個字元佔一個位元組,8位,每位的值都是0。