C/C++ 學習筆記:字符串、數組相關

  • C/C++ 中所有字符串字面值都由編譯器自動在末尾添加一個空字符,即默認以 \0 結尾

eg:字面值 "tang"  實際上是 "tang\0",字面值"tang\0" 實際上是"tang\0\0"

  • char 數組

char StrArray[4];// 如果數組 StrArray 不是全局變量,內容是隨機的。
char StrArray[4] = {'B', 'u'};// 這裏會默認給剩餘的 2 個元素補上 \0,若 4 個字符全指定了,就不會在最後加上 \0,因爲超出數組範圍了。
char Str[] = {'t', 'a', 'n', 'g'};// 不會以 \0 結尾
char Str1[] = "tang";// 因爲字符串字面值是以 \0 結尾的,所以數組 Str1 也是以 \0 結尾
char Str2[] = "tang\0";// 儘管字面值有一個 \0,編譯器還是會默認給字符串字面值後面加一個 \0,即這個字符串常量實際上是 "tang\0\0",所以數組 Str2 實際會以這個字符串常量被默認添加的那個 \0 結尾。

所以:sizeof(Str爲 4sizeof(Str1爲 5sizeof(Str2爲 6

  • char指針

不會自動在末尾補 \0

  • string 型變量
1. 以 \0 結尾(不會自動補  \0)

如果把上面不含 \0 的 Str 數組賦值給 string 型變量,則實際會是一直賦值到 \0 結束。相當於調用 string(constchar*s),然後 Str 退化爲指針,然後賦值的時候會一直找到 \0 才結束。

2. 其 size()  和 length() 函數得到的都是不含 \0 的長度

重要的

  1. 用字符串初始化指針時,指針指向的是放在常量區的字符串常量;
  2. 而用字符串來初始化數組時,是用字符串常量的內容初始化數組的內容;
  3. 數組名是一個右值類型( 比如字符型數組的類型爲  char*const ),不能作爲左值來接收另一個數組。
數組名的本質

1. 數組名指代一種數據結構,這種數據結構就是數組;

2. 數組名可以轉換爲指向其指代實體的指針,而且是一個指針常量,不能作自增、自減等操作,不能被修改;   

char str[10];   

str++;// 編譯出錯,提示 str不是左值

3. 數組名作爲函數形參時,淪爲普通指針。

void Func( char str[100] ) {
    cout << sizeof( str ) << endl;// 32位機器下,這裏爲 4
}
// Func( char str[100] ) 函數中數組名作爲函數形參時,在函數體內,數組名失去了本身的內涵,僅僅只是一個指針;
// 在失去其內涵的同時,它還失去了其常量特性,可以作自增、自減等操作,可以被修改。
  • sizeof 與 strlen 的區別
1. sizeof(...) 是運算符,其值在編譯時即計算好了,參數可以是數組、指針、類型、對象、函數等。跟裏存儲的內容沒有關係。
具體而言,當參數分別如下時,sizeof 返回的值表示的含義如下:
a) 數組——編譯時分配的數組空間大小;
b) 指針——存儲該指針所用的空間大小(存儲該指針的地址的長度,是長整型,應該爲 4);
c) 類型——該類型所佔的空間大小;
d) 對象——對象的實際佔用空間大小;

e) 函數——函數的返回類型所佔的空間大小。函數的返回類型不能是 void

2. strlen(...) 是函數,要在運行時才能計算。參數必須是字符型指針(char)。

當數組名作爲參數傳入時,實際上數組就退化成指針了,讀到 \0 爲止返回長度,而且是不把 \0 計入字符串的長度的。


一些例子

void test1() {
    char str[10];
    char* str1 = "0123456789";
    strcpy( str, str1 );// 字符串 str1 需要 11 個字節才能存放下(包括末尾的 '\0'),而 string 只有 10 個字節的空間,strcpy 會導致數組越界
}
void test2() {
    char str[10], str1[10];
    int i;
    for(i=0; i<10; i++)
        str1  = 'a';// 編譯錯誤。因爲數組名 str1 爲 char *const 類型的右值類型,根本不能賦值
                    // 再者,即使想對數組的第一個元素賦值,也要使用 *str1 = 'a'; 
    strcpy( str, str1 );// 對字符數組賦值後,使用庫函數strcpy進行拷貝操作,strcpy會從源地址一直往後拷貝,直到遇到'\0'爲止。所以拷貝的長度是不定的。如果一直沒有遇到'\0'導致越界訪問非法內存,程序就崩了。
}
void test3(char* str1) {
    if(str1 == NULL)
        return ;
    char str[10];
    if( strlen( str1 ) <= 10 )// 應改爲if(strlen(str1) < 10),因爲strlen的結果未統計’\0’所佔用的1個字節。
        strcpy( str, str1 );
}
void Test4() {
    char *str = (char *) malloc( 100 );
    strcpy( str, "hello" );
    free( str );
    ... //省略的其它語句
}
//在執行 char *str = (char *) malloc(100); 後未進行內存是否申請成功的判斷;
//另外,在 free(str) 後未置 str 爲空,導致可能變成一個“野”指針,應加上:str = NULL; 
Swap(int* p1, int* p2) {
    int *p;// p 沒有申請空間就使用,是一個“野”指針,有可能指向系統區,導致程序運行的崩潰
    *p = *p1;
    *p1 = *p2;
    *p2 = *p;
}

在函數中爲指針char* pStr 申請空間

// 傳值調用 OK
void GetMemory( char **p ) {
    *p = (char *) malloc( 100 );
}

// 引用調用 OK
void GetMemory(char *&p) {
    p = (char *) malloc (100);
}

// 使用一級指針,不行
// 因爲作爲形參傳遞後,有p和pStr都指向同一個地方,當malloc後,p就指向了新的地方,而pStr沒變
void GetMemory( char *p ) {
    p = (char *) malloc( 100 );
}

// 在函數裏用數組,不行
// 數組爲函數內的局部自動變量,在函數返回後,內存已經被釋放。
char *GetMemory( void ) {
    char p[] = "hello world";
    return p;
}

寫出完整版的 strcpy 函數:

char * strcpy( char *strDest, const char *strSrc ) {// 將源字符串加 const,表明其爲輸入參數
    assert( (strDest != NULL) && (strSrc != NULL) );// 對源地址和目的地址加非 0 斷言
    char *address = strDest;
    while( (*strDest++ = * strSrc++) != '\0' );
    return address;// 爲了實現鏈式操作,將目的地址返回
}

編寫類 String 的構造函數、析構函數和賦值函數:

class String {
public:
    String(const char *str = NULL); // 普通構造函數
    String(const String &other); // 拷貝構造函數
    ~String(void); // 析構函數
    String& operator=(const String &other); // 賦值函數
private:
    char *m_data; // 用於保存字符串
};
//===============================================================================
// 普通構造函數
String::String(const char *str) {
    if(NULL == str) {
        m_data = new char[1]; // 得分點:對空字符串自動申請存放結束標誌'\0'的空
        assert(NULL != m_data);// 加分點:對m_data加NULL 判斷
        *m_data = '\0';
    } else {
        int length = strlen(str);
        m_data = new char[length+1];
        assert(NULL != m_data);// 若能加 NULL 判斷則更好
        strcpy(m_data, str);
    }
}
// String的析構函數
String::~String(void) {
    delete[] m_data; // 或 delete m_data;
}
// 拷貝構造函數
String::String(const String &other) {// 得分點:輸入參數爲 const 型
    int length = strlen(other.m_data);
    m_data = new char[length+1];
    assert(NULL != m_data);// 加分點:對 m_data 加 NULL 判斷
    strcpy(m_data, other.m_data);
}
// 賦值函數
String& String::operator=(const String &other) { // 得分點:輸入參數爲const型
    if(this == &other) // 得分點:檢查自賦值
        return *this;
    delete [] m_data;// 得分點:釋放原有的內存資源
    int length = strlen( other.m_data );
    m_data = new char[length+1];
    assert(NULL != m_data);// 加分點:對 m_data 加 NULL 判斷
    strcpy( m_data, other.m_data );
    return *this;// 得分點:返回本對象的引用
}








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章