天天看點

C++ Primer Plus學習筆記之複合類型(上)

作者:好先生FX

前言

個人覺得學習程式設計最有效的方法是閱讀專業的書籍,通過閱讀專業書籍可以建構更加系統化的知識體系。

一直以來都很想深入學習一下C++,将其作為自己的主力開發語言。現在為了完成自己這一直以來的心願,準備認真學習《C++ Primer Plus》。

為了提高學習效率,在學習的過程中将通過釋出學習筆記的方式,持續記錄自己學習C++的過程。

本篇前言

本章首先将介紹除類以外的所有複合類型,以及将介紹new和delete及如何使用它們來管理資料。另外,還将簡要地介紹string類,它提供了另一種處理字元串的途徑。

一、數組

數組(array)是一種資料格式,能夠存儲多個同類型的值。

要建立數組,可使用聲明語句。數組聲明應指出以下三點:

  • 存儲在每個元素中的值的類型
  • 數組名
  • 數組中的元素數

聲明數組的通用格式如下:

typeName arrayName[arraySize]           

表達式arraySize指定元素數目,它必須是整型常數(如10)或const值,也可以是常量表達式(如8*sizeof(int)),即其中所有的值在編譯時都是已知的。

具體示例如下代碼:

short months[12];           

通過以上代碼,聲明了一個名為months的擁有12個short類型值的數組。

通過使用下标或索引可以單獨通路數組元素。C++規定數組從0開始編号。C++使用帶索引的方括号表示法來指定數組元素。例如:months[0]是months數組的第一個元素,months[11]是months數組的最後一個元素。因為開始數字為0,加上元素數量12個,最後一個元素比總數量少1。

在編寫代碼的過程中要注意下标值是否有效,例如:months[-1]、months[12]就是無效的下标值,這可能會導緻程式出現異常。

接下來我們根據程式清單4.1了解數組的一些屬性,包括聲明數組、給數組元素指派以及初始化數組:

// arrayone.cpp -- 小型整數數組
#include <iostream>
int main()
{
    using namespace std;
    int orange[2];  // 建立有2個元素的數組
    orange[0] = 15; // 指派給第1個元素
    orange[1] = 28;
    int orangeCosts[2] = {3, 2}; // 建立并初始化數組
    cout << "橙子的總數量 = ";
    cout << orange[0] + orange[1] << endl;
    cout << "第一個包裝裡有" << orange[0] << "個橙子,";
    cout << "其中每個橙子的成本為" << orangeCosts[0] << "元。\n";
    cout << "第二個包裝裡有" << orange[1] << "個橙子,";
    cout << "其中每個橙子的成本為" << orangeCosts[1] << "元。\n";
    int total = orange[0] * orangeCosts[0] + orange[1] * orangeCosts[1];
    cout << "全部橙子的總成本為" << total << "元。\n";
    cout << "\norange數組的長度 = " << sizeof orange;
    cout << " bytes.\n";
    cout << "第一個數組的長度 = " << sizeof orange[0];
    cout << " bytes.\n";
    return 0;
}           

運作結果如下:

橙子的總數量 = 43
第一個包裝裡有15個橙子,其中每個橙子的成本為3元。
第二個包裝裡有28個橙子,其中每個橙子的成本為2元。
全部橙子的總成本為101元。
orange數組的長度 = 8 bytes.
第一個數組的長度 = 4 bytes.           

1、程式說明

該程式首先建立一個名為orange包含2個int類型元素的數組。然後我們根據下标值分别對2個元素進行了指派。因為orange的每個元素都是int類型,是以能夠将數值指派給元素、并将它們相加和相乘。

程式在給數組中元素指派時,采用了兩種方式,分别是通過下标值對每個元素進行指派和提供一個用逗号分隔的值清單(初始化清單),并将它們用花括号括起即可。

接下來,程式通過下标值對數組中元素進行通路并進行一些計算。

sizeof運算符傳回類型或資料對象的長度(機關為位元組)。将sizeof運算符用于數組名,得到的将是整個數組中的位元組數,如果将sizeof用于數組元素,則得到的将是元素的長度(機關為位元組)。

2、數組的初始化規則

C++隻有在定義數組時才能使用初始化,此後隻能通過下标值分别對單個元素進行指派:

int orangeCosts[2] = {3, 2}; // 有效
int bananaCosts[2]; // 有效
bananaCosts[2] = {7,10}; // 無效
bananaCosts = orangeCosts; // 無效
bananaCosts[0] = 7; // 有效
bananaCosts[1] = 10; // 有效           

初始化數組時,提供的值可以少于數組的元素數目。例如,下面的語句隻初始化orangeCosts數組的第一個元素:

int orangeCosts[2] = {3}; // 有效           

以上代碼,将第一個元素指派為3,第二個元素編譯器預設指派為0。

如果初始化數組時方括号内([]) 為空,C++編譯器将計算元素個數。例如:

int appleCosts = {2 , 5};            

以上代碼,編譯器将使appleCosts數組包含2個元素。

3、C++11數組初始化方法

C++11将使用大括号的初始化(清單初始化)作為一種通用初始化方式,可用于所有類型。C++11在以前清單初始化的基礎上增加了一些新的功能:

  • 初始化數組時,可省略等号 (=):
int appleCosts[2] {2 , 5};            
  • 可不在大括号内包含任何東西,這将把所有元素都設定為零
int appleCosts[2] {};            
  • 清單初始化禁止縮窄轉換:
int appleCosts[2] {2.0 , 5};            

在上述代碼不能通過編譯,因為将浮點數轉換為整型是縮窄操作,即使浮點數的小數點後面為零。

二、字元串

字元串是存儲在記憶體的連續位元組中的一系列字元。C++處理字元串的方式有兩種:1、繼承自C語言的C風格字元串;2、基于string類庫的方法。

C風格字元串具有一種特殊的性質:以空字元(null character)結尾,空字元被寫作\0,其ASCII碼為0,用來标記字元串的結尾,如下代碼:

char hi[2]={'h', 'i' };//不是字元串
char hello[6]={'h',  'e',  'l',  'l',  'o', '\0'};//是字元串           

以上方法初始化字元串的方法較為複雜,可以使用引号括起字元串即可。這種字元串被稱為字元串常量(string constant)或字元串字面值(string literal),如下代碼:

char hello[6]="hello";
char good[]="good";//讓編譯器計算長度           

用引号括起的字元串隐式地包括結尾的空字元,是以不用顯式地包括它。

C++對字元串的長度沒有限制,處理字元串的函數根據空字元的位置判斷字元串是否結束。在确定存儲字元串所需的最短數組時,需要将結尾的空字元計算在内。

注意:字元串常量(使用雙引号)不能與字元常量(使用單引号)互換。

1、拼接字元串常量

有時候,字元串很長,無法放到一行中。C++允許拼接字元串字面值,即将兩個用引号括起的字元串合并為一個。事實上,任何兩個由空白(空格、制表符和換行符)分隔的字元串常量都将自動拼接成一個。

是以,,下面所有的輸出語句都是等效的:

cout << "hi i am " "kangkang\n";
cout << "hi i am "  "kangkang\n";
cout << "hi i am "
    "kangkang\n";           

注意,拼接時不會在被連接配接的字元串之間添加空格,第二個字元串的第一個字元将緊跟在第一個字元串的最後一個字元 (不考慮\0)後面。第一個字元串中的\0字元将被第二個字元串的第一個字元取代。

2、在數組中使用字元串

要将字元串存儲到數組中,最常用的方法有兩種将數組初始化為字元串常量、将鍵盤或檔案輸入讀人到數組中。代碼如下:

// strings.cpp -- 在數組中存儲字元串
#include <iostream>
#include <cstring>  // strlen()函數所在頭檔案
int main()
{
    using namespace std;
    const int Size = 15;
    char name1[Size];           // 空數組
    char name2[Size] = "Jane";  // 初始化數組
    cout << "你好!我是" << name2;
    cout << "!請問你叫什麼名字?\n";
    cin >> name1;
    cout << "我叫" << name1 << ",我的名字占";
    cout << strlen(name1) << "個字元";
    cout << ",在存儲數組中占" << sizeof(name1) << "位元組。\n";
    cout << "第一個字元是" << name1[0] << "。\n";
    name2[1] = '\0';                // 設定空字元串
    cout << "很高興認識你,我的第一個字元是";
    cout <<  name2  << endl;
    return 0;
}           

運作結果如下:

你好!我是Jane!請問你叫什麼名字?
Kang
我叫Kang,我的名字占4個字元,在存儲數組中占15位元組。
第一個字元是K。
很高興認識你,我的第一個字元是J           

通過使用sizeof運算符可以計算出數組的長度,strlen()函數隻計算存儲在數組中可見的字元的長度。

3、字元串輸入

由于不能通過鍵盤輸入空字元,cin使用空白(空格、制表符和換行符)來确定字元串的結束位置,這意味着cin在讀取字元數組輸入時如果存在空白則會導緻,隻讀取空白之前的内容放到數組中,并自動在結尾添加空字元。例如:

// instr1.cpp -- 讀取多個字元串
#include <iostream>
int main()
{
    using namespace std;
    const int ArSize = 20;
    char name[ArSize];
    char city[ArSize];
    cout << "請輸入你的姓名(姓和名空格分開):\n";
    cin >> name;
    cout << "輸入你的城市:\n";
    cin >> city;
    cout << "請确定輸入内容:姓名為" << name << ",所在城市為" << city << endl;
    return 0;
}           

運作結果如下:

請輸入你的姓名(姓和名空格分開):
張 三
輸入你的城市:
請确定輸入内容:姓名為張,所在城市為三           

出現以上情況,就是因為張和三字之間有空白。cin在讀取字元數組輸入時遇到空白時,将讀取的空白之前的内容放到數組中,并自動在結尾添加空字元,而空白之後的内容在遇到下一個cin時又會重複上面的規則,這才導緻三字被寫入city數組中。

要解決以上問題有兩種方法:getline()函數和get()函數,這兩個函數都讀取一行輸入,直到到達換行符。然而,随後getline()将丢棄換行符,而get()将換行符保留在輸入序列中。

将上述代碼分别用getline()函數和get()函數替換如下:

// instr1.cpp -- 讀取多個字元串
#include <iostream>
int main()
{
    using namespace std;
    const int ArSize = 20;
    char name[ArSize];
    char city[ArSize];
    cout << "請輸入你的姓名(姓和名空格分開):\n";
    cin.get(name,20).get() ;
    cout << "輸入你的城市:\n";
    cin.getline(city,20);
    cout << "請确定輸入内容:姓名為" << name << ",所在城市為" << city << endl;
    return 0;
}           

運作結果如下:

請輸入你的姓名(姓和名空格分開):
張 三
輸入你的城市:
北京
請确定輸入内容:姓名為張 三,所在城市為北京           

因為get()将換行符保留在輸入序列中,需要調用get()将尾部的換行符讀取掉。否則還是會出現上面的情況,無法輸入城市的内容。

三、string類簡介

string類定義隐藏了字元串的數組性質,可以像處理普通變量那樣處理字元串。

使用string對象更友善,也更安全。從理論上說,可以将char數組視為一組這使得與使用數組相比,用于存儲一個字元串的char存儲單元,而string類變量是一個表示字元串的實體

1、C++11字元串初始化

C++11也允許将清單初始化用于C風格字元串和string對象,代碼如下:

char hi[] = { "hi" };
string hello = { "hello" };           

2、指派、拼接和附加

使用string類時,可以将一個string對象賦給另一個string對象,代碼如下:

string str1 = "test";
string str2 = str1;           

string類簡化了字元串的合并操作。可以使用運算符+或者+=将兩個string對象合并起來,代碼如下:

string str3 = str1 + str2;
str1 += str2;           

3、string類的其他操作

接下來我們對比一下string類和字元數組的不同,代碼如下:

// strtype3.cpp -- 更多字元串類特性
#include <iostream>
#include <string>               // string類的頭檔案
#include <cstring>              // C風格字元串頭檔案
int main()
{
    using namespace std;
    char charr1[20]; 
    char charr2[20] = "jaguar"; 
    string str1;  
    string str2 = "panther";
    // string對象和字元數組的指派
    str1 = str2;                // 将 str2 複制給 str1
    strcpy(charr1, charr2);     // 将 charr2 複制給 charr1
 
    // 用于string對象和字元數組的追加
    str1 += " paste";           // 将“ paste”追加到 str1 結尾
    strcat(charr1, " juice");   // 将“ juice”追加到 charr1 結尾
    // 查找string對象和C風格字元串的長度
    int len1 = str1.size();     // 擷取 str1 長度
    int len2 = strlen(charr1);  // 擷取 charr1 長度
 
    cout << "字元串 " << str1 << " 包含 "
         << len1 << " 個字元。\n";
    cout << "字元串 " << charr1 << " 包含 "
         << len2 << " 個字元。\n";
    return 0; 
}           

運作結果如下:

字元串 panther paste 包含 13 個字元。
字元串 jaguar juice 包含 12 個字元。           

從上述代碼,我們可以看出string對象的文法通常比使用C字元串函數簡單,同時因為string對象會自動調整大小,相對于字元數組一不小心就超出目标數組大小,顯得更為安全。

其中還有一點差别,就是在計算字元串的長度時,str1不是被用作函數的參數,而是通過句點.連接配接了size()方法。這是因為str1作為string類對象,可以使用對象名和句點.運算符來指出方法要使用哪個字元串,換一種說法就是string類對象通過句點.運算符可以調用string類中的方法。

4、string類 I/O

未初始化的數組的内容是未定義的;其次,函數strlen()從數組的第一個元素開始計算位元組數,直到遇到空字元。

将輸入讀取到string對象,還可以通過如下方式:

getline(cin, str1);           

5、其他形式的字元串字面值

C++有很多類型的字元串字面值,具體如下:

char str[] = "hello";
wchar_t str1[] = L"hello";
char16_t str2[] = u"hello";
char32_t str3[] = U"hello";

           

C++11新增的另一種類型是原始(raw)字元串。将"(和)"作為定界符,并使用字首R來辨別原始字元串:

cout << R"("Apple" 的意思是蘋果。)" << endl;           

運作結果如下

"Apple" 的意思是蘋果。           

根據運作結果我們可以看到,"(和)"定界符之間的内容被原封不動的進行了輸出。但當我們需要在"(和)"定界符之間輸入)"時,可以在原始字元串文法的"和(之間添加其他字元,同時結尾"和)之間也必須包含這些字元。例如在"和(之間添加+*,需要使用R"+*(和)+*",示例代碼如下:

cout << R"+*("(Apple)" 的意思是蘋果。)+*" << endl;           

運作結果如下

"(Apple)" 的意思是蘋果。           

四、結構簡介

當我們有一組不同類型的資料需要存儲時,可以使用C++中的結構類型。

結構是使用者定義的類型,而結構聲明定義了這種類型的資料屬性。定義了類型後,便可以建立這種類型的變量。是以建立結構包括兩步。首先,定義結構描述——它描評并标記了能夠存儲在結構中的各種資料類型。然後按描述建立結構變量(結構資料對象)。最後可以定義結構類型後,通過成員運算符(.)來通路各個成員。

結構定義文法:

struct 結構名
{
    //資料類型
}           

1、在程式中使用結構

// structur.cpp -- 一個簡單的結構
#include <iostream>
struct Person // 聲明結構
{
    char name[20];
    int age;
};
int main()
{
    using namespace std;
    Person bjPerson =
    {
            "張三", // name 值
            18      // age 值
    };
    Person shPerson =
    {
            "李四",
            32
    };
    cout << "請北京來的同學介紹一下自己:" << "\n";
    cout << "大叫好,我是" << bjPerson.name << ",今年" << bjPerson.age << "歲\n";
    cout << "接下來請上海來的同學介紹一下自己:" << "\n";
    cout << "大叫好,我是" << shPerson.name << ",今年" << shPerson.age << "歲\n";
    return 0;
}           

運作結果如下:

請北京來的同學介紹一下自己:
大叫好,我是張三,今年18歲
接下來請上海來的同學介紹一下自己:
大叫好,我是李四,今年32歲           

在使用結構之前,首先聲明結構,然後如聲明數組變量一樣,使用結構類型名加上變量名即可表明一個結構類型,接着和數組一樣,使用由逗号分隔值清單,并将這些值用花括号括起。

要調用結構類型中的成員,使用成員運算符(.)即可。

2、C++11結構初始化

與數組一樣,C++11也支援将清單初始化用于結構,且等号(=)是可選的:

Person bjPerson{"張三", 18};           

當大括号内未包含任何東西,各個成員都将被設定為零。

Person bjPerson{};           

bjPerson.name的每個位元組都被設定為零,bjPerson.age被設定為零。

同數組一樣,不允許縮窄轉換。

3、結構可以将string類作為成員嗎

隻要編譯器支援對以string對象作為成員的結構進行初始化:

#include <string>
struct Person // 聲明結構
{
    std::string name;
    int age;
};           

4、其他結構屬性

C++使使用者定義的類到與内置類型盡可能相似。例如,可以将結構作為參數傳遞給函數,也可以讓函數傳回一個結構。另外,還可以使用指派運算行(=)将結構賦給另一個同類型的結構,這樣結構中每個成員都将被設定為另一個結構中相應成員的值,即使成員是數組。這種指派被稱為成員指派(assignment))。

5、結構數組

結構是使用者定義的類型,也可以如其他基本類型一樣,建立對應的數組,代碼如下:

Person persons[2] = 
{
    {
            "張三", 
            18 
    },
    {
            "李四",
            32
    }
};
    cout << "請第一位同學介紹一下自己:" << "\n";
    cout << "大叫好,我是" << persons[0].name << ",今年" << persons[0].age << "歲\n";           

6、結構中的位字段

字段的類型應為整型或枚舉,接下來是冒号,冒号後面是指定了使用位數的數字。可以使用沒有名稱的字段提供間距。每個成員都被稱為位字段(bit field)。示例代碼:

struct ComputerInfo
{
    unsigned int SN : 4;//序列号占4bit
    unsigned int : 4;//空白間距占4bit
    unsigned int Version : 1;//版本号占1bit
}           

可以像通常那樣初始化這些字段:

ComputerInfo computerInfo = {14, 3 };           

五、 共用體

共用體 (union)是一種資料格式,它能夠存儲不同的資料類型,但隻能同時存儲其中的一種類型。也就是說,結構可以同時存儲 int、long和double,共用體隻能存儲int、long或double。共用體的句法與結構相似,但含義不同。例如,請看下面的聲明:

#include <iostream>
using namespace std;
struct Person // 聲明結構
{
    union
    {
        unsigned short insideId;
        long globalId;
    } PersonId;
    string name;
    int age;
};
int main()
{
    Person person;
    person.PersonId.insideId = 101;
    person.name = "張三";
    person.age = 18;
    cout << "人員内部Id是" << person.PersonId.insideId << endl;
    person.PersonId.globalId = 122834;
    cout << "人員全局Id是" << person.PersonId.globalId << endl;
}           

共用體常用于作業系統資料結構或硬體資料結構。

六、枚舉

C++的enum工具提供了另一種建立符号常量的方式,這種方式可以代替const。它還允許定義新類型,但必須按嚴格的限制進行。使用enum的句法與使用結構相似。示例代碼如下:

enum myColor
{
    red,
    blue,
    green
};           

以上代碼讓myColor成為新類型的名稱; myColor被稱為枚舉(enumeration)。就像struct變量被稱為結構一樣。将red、blue、green作為符号常量,它們對應整數值0~2。這些常量叫作枚舉量(enumerator)。

在預設情況下,将整數值賦給枚舉量,第一個枚舉量的值為0,第二個枚舉量的值為1,依次類推。

枚舉通常被用來定義相關的符号變量,而不是新類型。

示範代碼:

enum myColor
{
    red,
    blue,
    green
};
int main()
{
    myColor color1;
    color1 = red;
    color1 = myColor(1);
}           

在使用整型轉換為枚舉時,需要使用枚舉括号包含整型值進行轉換才行。

1、設定枚舉量的值

可以使用指派運算符來顯式地設定枚舉量的值:

enum myColor
{
    red = 2,
    blue = 4,
    green = 7
};           

指定的值必須是整數。也可以隻顯式地定義其中一些校舉量的值,甚至可以将多個枚舉值設定為同一整數。

2、枚舉的取值範圍

取值範圍的定義如下:

  • 找出上限,需要知道枚舉量的最大值。找到大于這個最大值的、最小值的2的幂,将它減去1,得到的便是取值範圍的上限。
  • 計算下限,需要知道枚舉量的最小值。如果它不小于0,則取值範圍的下限為0;否則,采用與尋找上限方式相同的方式,但加上負号。

選擇用多少空間來存儲枚舉由編評器決定。

繼續閱讀