3.Writing Larger Programs
- 2020 年 2 月 18 日
- 筆記
要想寫一個稍微大一點項目,以下知識需要繼續鞏固一下,再次回到基礎知識哈。
1. 頭文件
頭文件或.h文件允許將相關的函數,方法和類聲明收集在一個位置。然後可以將相應的定義放置在.cpp文件中。編譯器認為頭聲明是一個「承諾」,該定義將在後面程式碼找到。因此,如果編譯器尚未找到定義的函數,則可以繼續進行編譯,直到找到定義為止。這允許以任意順序定義(和聲明)函數。
2. 引用
#include <iostream> using std::cout; int main() { int i = 1; // Declare a reference to i. int& j = i; cout << "The value of j is: " << j << "n"; // Change the value of i. i = 5; cout << "The value of i is changed to: " << i << "n"; cout << "The value of j is now: " << j << "n"; // Change the value of the reference. // Since reference is just another name for the variable, // th j = 7; cout << "The value of j is now: " << j << "n"; cout << "The value of i is changed to: " << i << "n"; }

3.指針
C ++指針只是一個變數,用於存儲程式中對象的記憶體地址。
#include <iostream> using std::cout; int main() { int i = 5; int j = 6; // Print the memory addresses of i and j cout << "The address of i is: " << &i << "n"; cout << "The address of j is: " << &j << "n"; }

可能想知道為什麼同一個符號既可以用來訪問記憶體地址,也可以像以前看到的那樣,將引用傳遞到函數中: 1.符號&和*有不同的含義,這取決於它們出現在等式的哪一邊。 2.記住這一點非常重要。對於&,如果它出現在等式的左側(例如,聲明變數時),則表示該變數聲明為引用。如果&出現在等式的右側,或在先前定義的變數之前,則用於返回記憶體地址,如上面的示例所示。
#include <iostream> using std::cout; int main() { int i = 5; // A pointer pointer_to_i is declared and initialized to the address of i. int* pointer_to_i = &i; // Print the memory addresses of i and j cout << "The address of i is: " << &i << "n"; cout << "The variable pointer_to_i is: " << pointer_to_i << "n"; }
從程式碼中可以看到,變數pointer_to_i使用*符號聲明為指向int的指針,pointer_to_i設置為i的地址。從列印輸出中可以看出,pointer_to_i的值與i的地址相同。

一旦有了指針,您可能需要檢索它所指向的對象。在這種情況下,*符號可以再次使用。然而,這一次,它將出現在一個方程的右邊或一個已經定義的變數的前面,所以含義是不同的。在這種情況下,它被稱為「解引用運算符」,並返回被指向的對象。您可以在下面的程式碼中看到這是如何工作的:
#include <iostream> using std::cout; int main() { int i = 5; // A pointer pointer_to_i is declared and initialized to the address of i. int* pointer_to_i = &i; // Print the memory addresses of i and j cout << "The address of i is: " << &i << "n"; cout << "The variable pointer_to_i is: " << pointer_to_i << "n"; cout << "The value of the variable pointed to by pointer_to_i is: " << *pointer_to_i << "n"; }

4.引用與指針
指針和引用可以在C++中使用類似的用例。如前所述,引用和指針都可以在對函數的按次傳遞引用中使用。此外,它們都提供了一種訪問現有變數的可選方法:指針通過變數的地址,以及通過該變數的另一個名稱引用。但是兩者有什麼區別,什麼時候應該使用它們呢?下面的列表總結了指針和引用之間的一些差異,以及應該在何時使用它們:
引用: 引用在聲明時必須初始化。這意味著引用將始終指向有意分配給它的數據。 指針: 指針可以在不初始化的情況下聲明。如果錯誤地發生這種情況,指針可能指向記憶體中的任意地址,與該地址關聯的數據可能毫無意義,從而導致未定義的行為和難以解決的錯誤。
引用: 引用不能為空。這意味著引用應該指向程式中有意義的數據。 指針: 指針可以為空。事實上,如果指針沒有立即初始化,通常最好將其初始化為nullptr,這是一種表示指針為空的特殊類型。
引用: 當在用於按引用傳遞的函數中使用時,該引用可以作為相同類型的變數使用。 指針: 在用於按引用傳遞的函數中使用時,必須取消對指針的引用才能訪問基礎對象。 引用通常比指針更簡單和安全。作為一個很好的經驗法則,在可能的情況下,應該使用引用來代替指針。 但是,有時不能使用引用。一個例子是對象初始化。您可能希望一個對象存儲對另一個對象的引用。 但是,如果在創建第一個對象時另一個對象尚不可用,則第一個對象將需要使用指針,而不是引用,因為引用不能為空,只能在創建另一個對象後初始化引用。
5.
#include <iostream> #include <string> using std::string; using std::cout; int main() { // Variables to hold each car's color. string car_1_color = "green"; string car_2_color = "red"; string car_3_color = "blue"; // Variables to hold each car's initial position. int car_1_distance = 0; int car_2_distance = 0; int car_3_distance = 0; // Increment car_1's position by 1. car_1_distance++; // Print out the position and color of each car. cout << "The distance that the " << car_1_color << " car 1 has traveled is: " << car_1_distance << "n"; cout << "The distance that the " << car_2_color << " car 2 has traveled is: " << car_2_distance << "n"; cout << "The distance that the " << car_3_color << " car 3 has traveled is: " << car_3_distance << "n"; }
這適用於程式中定義的少數汽車,但是如果您希望程式跟蹤許多汽車,這將非常麻煩。你需要為每輛車創建一個新的變數,程式碼會很快變得混亂。解決這個問題的一種方法是定義一個Car類,將這些變數作為屬性,同時使用一些類方法來增加行駛距離並列印出汽車數據。
#include <iostream> #include <string> using std::string; using std::cout; // The Car class class Car { public: // Method to print data. void PrintCarData() { cout << "The distance that the " << color << " car " << number << " has traveled is: " << distance << "n"; } // Method to increment the distance travelled. void IncrementDistance() { distance++; } // Class/object attributes string color; int distance = 0; int number; }; int main() { // Create class instances for each car. Car car_1, car_2, car_3; // Set each instance's color. car_1.color = "green"; car_2.color = "red"; car_3.color = "blue"; // Set each instance's number. car_1.number = 1; car_2.number = 2; car_3.number = 3; // Increment car_1's position by 1. car_1.IncrementDistance(); // Print out the position and color of each car. car_1.PrintCarData(); car_2.PrintCarData(); car_3.PrintCarData(); }

這看起來沒問題,而且已經減少了main中的變數數量,所以可能會看到在未來如何更有條理。然而,現在的程式碼比開始時多得多,而且main似乎沒有組織得多。上面的程式碼仍然在創建汽車之後為每輛汽車設置屬性。
添加一個構造函數
解決這個問題的最好方法是向Car類添加構造函數。構造函數允許您使用所需的數據實例化新對象。在下一個程式碼單元中,我們為Car添加了一個構造函數,它允許傳入數字和顏色。這意味著可以用這些變數創建每個Car對象。
#include <iostream> #include <string> using std::string; using std::cout; class Car { public: void PrintCarData() { cout << "The distance that the " << color << " car " << number << " has traveled is: " << distance << "n"; } void IncrementDistance() { distance++; } // Adding a constructor here: Car(string c, int n) { // Setting the class attributes with // The values passed into the constructor. color = c; number = n; } string color; int distance = 0; int number; }; int main() { // Create class instances for each car. Car car_1 = Car("green", 1); Car car_2 = Car("red", 2); Car car_3 = Car("blue", 3); // Increment car_1's position by 1. car_1.IncrementDistance(); // Print out the position and color of each car. car_1.PrintCarData(); car_2.PrintCarData(); car_3.PrintCarData(); }
如果計劃構建一個更大的程式,那麼此時最好將類定義和函數聲明放在一個單獨的文件中。正如我們之前討論的頭文件一樣,將類定義放在單獨的頭中有助於組織程式碼,並防止在定義類之前嘗試使用類對象出現問題。
- 當類方法在類之外定義時,必須使用scope resolution操作符::來指示該方法屬於哪個類。例如,在PrintCarData方法的定義中可以看到:
void Car::PrintCarData()
如果有兩個類的方法具有相同的名稱,這可以防止任何編譯器問題。
- 改變了構造函數初始化變數的方式。而不是以前的構造函數:
Car(string c, int n) { color = c; number = n; }
構造函數現在使用初始值列表:
Car(string c, int n) : color(c), number(n) {}
類成員在構造函數的主體(現在是空的)之前初始化。初始化器列表是在構造函數中初始化許多類屬性的快速方法。此外,編譯器處理列表中初始化的屬性與在構造函數體中初始化的屬性略有不同。如果類屬性是引用,則必須使用初始值設定項列表對其進行初始化。
- 不需要在類外部可見的變數設置為private。這意味著不能在類之外訪問它們,這可以防止它們被意外更改。
聲明位於.h文件中,定義位於.cpp中
#ifndef CAR_H #define CAR_H #include <string> using std::string; using std::cout; class Car { public: void PrintCarData(); void IncrementDistance(); // Using a constructor list in the constructor: Car(string c, int n) : color(c), number(n) {} // The variables do not need to be accessed outside of // functions from this class, so we can set them to private. private: string color; int distance = 0; int number; }; #endif
#include <iostream> #include "car.h" // Method definitions for the Car class. void Car::PrintCarData() { cout << "The distance that the " << color << " car " << number << " has traveled is: " << distance << "n"; } void Car::IncrementDistance() { distance++; }
car_main.cpp
#include <iostream> #include <string> #include "car.h" using std::string; using std::cout; int main() { // Create class instances for each car. Car car_1 = Car("green", 1); Car car_2 = Car("red", 2); Car car_3 = Car("blue", 3); // Increment car_1's position by 1. car_1.IncrementDistance(); // Print out the position and color of each car. car_1.PrintCarData(); car_2.PrintCarData(); car_3.PrintCarData(); }
6.繼續擴大規模
1.在下面的程式碼中,cp是指向Car對象的指針,下面兩個是等價的:
cp->IncrementDistance(); // Dereference the pointer using *, then // access IncrementDistance() with traditional // dot notation. (*cp).IncrementDistance();
- new在「堆」上為對象分配記憶體。通常,必須手動管理(釋放)此記憶體,以避免程式中出現記憶體泄漏。這很危險,因此通常最好在「stack」上創建對象,而不使用「new」操作符。
#include <iostream> #include <string> #include <vector> #include "car.h" using std::string; using std::cout; using std::vector; int main() { // Create an empty vector of pointers to Cars // and a null pointer to a car. vector<Car*> car_vect; Car* cp = nullptr; // The vector of colors for the cars: vector<string> colors {"red", "blue", "green"}; // Create 100 cars with different colors and // push pointers to each of those cars into the vector. for (int i = 0; i < 100; i++) {; cp = new Car(colors[i % 3], i + 1); car_vect.push_back(cp); } // Move each car forward by 1. for (Car* cp: car_vect) { cp->IncrementDistance(); } // Print data about each car. for (Car* cp: car_vect) { cp->PrintCarData(); } }

ok,接下來將會更加有趣。