C到C++ 快速過度 C 結構體到類
“類”這個概念對C++來說意義非凡,與面向對象密不可分。 從這個概念開始,C++比C復雜的一面越加凸顯。
還記得C語言中的結構體么?
struct Point{
double x;
double y;
};
上面的代碼是一個“結構體模版”,我們利用它創建了一個叫做Point的類型。
在這之后,我們就可以像聲明基本類型的變量一樣聲明Point類型:
Point ob;
ob叫做結構體Point的一個“實例”。
而當我們 int n; 的時候,會講n是int類型的一個“變量”。
結構體是一種復合類型,但是它和同樣身為復合類型的數組不同:
<!--數組操作技巧-->
char name[20];
我們這樣就聲明了一個數組,并且每個name[i](0 <=i < 20)都是一個獨立的char類型的變量。它們被稱為數組的“元素”。
值得一提的是,同一個數組的各個元素所占用的內存空間是連續的。
這使得我們在遍歷(檢索)整個數組的時候,速度非常的快:
int line[] = {2, 5, 6, 7, 8, 12, -5, -32}; // 1
int len = sizeof(line) / sizeof(int); // 2
for(int i = 0; i < len; i++)
line[i] = -line[i];
上述語句遍歷這個數組,并且將每個元素的值都取反。
第 1 個語句說明,數組可以根據你聲明時填寫的數據自動分配大小,但是一旦分配,數組的大小就定了。
第 2 個語句可以直接確定數組中元素的個數,這樣就無需一個個數了。
sizeof是一個操作符,用法類似于函數。當參數是數組名時,將得到整個數組所占內存的字節數;而當參數是類型時,得到的是該類型變量的字節數。
而對于字符串數組,初始化有更方便的方法:
char name[] = "Always Laugh";
這樣name就會被自動分配成長度13的數組(別忘了'\0')。
除了之前介紹的使用sizeof操作符取元素個數的方法以外,頭文件<string.h>(C++中shi<cstring>)中的strlen函數可以更方便地取字符串長度:
char name[] = "Always Laugh";
int len = strlen(name);
for(int i = 0; i < len; i++)
name[i]++;
這個代碼會把name數組的的每個元素在字符表中“后移”一位。
需要注意的是,strlen只能處理char的字符串數組,對int等不適用。
并且它以字符串末尾的'\0'字符為結束標志,因此取到的往往不是數組大小。
<!--類-->
現在介紹C++中的“類”這個概念,它和結構體極其相似。
我們先寫一個使用結構體的程序:
#include <iostream>
using namespace std;
struct Point{ // 1 結構體的模版
double x; // 成員
double y;
};
int main()
{
Point p; // 2 結構體的聲明
p.x = 3.2; // 3 結構體的使用
p.y = 8.9;
cout << "(" << p.x << "," << p.y << ")" << endl; // 結構體的使用
return 0;
}
結構體的模版和函數的定義性質相同(之后介紹的“類”也同樣),只是規定了這個結構體/類的內部結構和工作原理,并不分配內存。
我們在使用結構體對象的時候,總是使用它的成員。“.”這個操作符用來連接結構體的實例和它的成員。
實例的名稱可以自由去定義,就像變量名一樣,但是相同類型的實例總是擁有的成員確是完全相同的,都和模版中的一致。
即利用相同模版初始化的實例都具有相同的成員。
而當將類引入時,會發生較大的變化:
#include <iostream>
using namespace std;
class Point{
private:
double x;
double y;
public:
setPoint(double m, double n){
x = m; y = n;
}
printPoint(){
cout << "(" << x << "," << y << ")" << endl;
}
};
int main()
{
Point p;
p.setPoint(3.2, 8.9);
p.printPoint();
return 0;
}
這個程序和用結構體的效果是相同的,但是復雜的地方集中在了類的模版定義部分。
出現了很多陌生的東西,private,public,甚至還有函數。
這是因為,對于類而言,不僅變量可以作為成員,函數也可以。
而當你想要在主函數中使用如下語句時:
p.x = 2.2; p.y = 1.1;
你會發現這個不允許的。
原因就在于關鍵字private(私有的),它將x和y成員保護了起來,使它們在“模版區”之外不能出現。
注意是“不能出現”,而不僅是“不能賦值”。這意味著 cout << p.x; 也是不允許的。
關鍵字public(公有的)定義的成員可以暴露給外界,正如主函數中的p.setPoint(3.2, 8.9);和p.printPoint();一樣。
把x和y設置為私有成員是為了數據的安全性:
int main()
{
Point p;
p.setPoint(3.2, 8.9);
p.printPoint();
return 0;
}
這是剛剛的主函數,我們從中根本看不出p這個類含有哪種或者多少個私有成員,更不知道成員叫做x/y。
它們確實可以在類模版中看到,但是在工程中我們使用的類,大多數是見不到類模版的,他們存放在不同的文件中。
這樣,我們只需要知道類的公有成員函數接受哪些變量,起到哪些作用,學會調用即可。至于具體細節,內部邏輯,我們不需要知道。
這就是“封裝”,C++的三大特性之一。
正如你看到的,私有成員變量可以在成員函數中直接出現,因為它們處于同一個作用域中(類模版的花括號)。
有私有成員函數嗎?當然有。它可以供其他私有成員函數或者公有成員函數去調用。
總之,只有公有成員(可以出現在“模版區”之外)才會和“.”連用。因為一個“實例”的聲明和使用都是在外界的。
簡而言之:
成員包括私有成員和公有成員。
成員分為成員變量和成員函數。
<!--構造函數-->
class Point{
private:
double x;
double y;
public:
Point(){
}
Point(double m, double n) {
x = m; y = n;
}
setPoint(double m, double n){
x = m; y = n;
}
printPoint(){
cout << "(" << x << "," << y << ")" << endl;
}
};
仍然是那個類,多了兩個比較怪異的函數。沒錯,函數的名稱和類名稱一樣,并且他們不含返回值。
有了它們,我們便可以在主函數中這樣子聲明p。
int main()
{
Point p(3.2, 8.9);
p.printPoint();
return 0;
}
構造函數是在初始化實例的過程中調用的,這樣我們可以在初始化的同時給成員賦值,而setPoint用來改變成員的值。
為什么Point函數聲明了兩次?還記得函數重載么?我們重載了它。
這樣我們既可以在聲明實例的時候初始化它,也可以不這么做。
因為構造函數總是會執行的。Point p;和Point p();絲毫沒有區別。
<!--些許復雜的示例程序-->
#include <iostream>
#include <cstring>
using namespace std;
struct Friend{
char name[20];
Friend* nest;
Friend(char* pn){
strcpy(name, pn);
nest = NULL;
}
};
struct FriendList{
Friend* head;
Friend* mov;
int number;
bool none;
FriendList(Friend* pri){
head = mov = pri;
number = 1;
none = 0;
}
void ReTop(){
mov = head;
none = 0;
}
void BeFriend(Friend* someone){
Friend* temp = mov->nest;
mov->nest = someone;
someone->nest = temp;
number++;
}
bool Que(Friend *f){
if(none) {
cout << "You got no friends.\n\n";
ReTop();
return 0;
}
if(!mov->nest) none = 1;
char ch;
while(getchar() != '\n') continue;
cout << "Do you wang to make friends with "
<< mov->name << "?" << endl
<< "Y to YES and N to NO" << endl;
cin >> ch;
if(ch == 'Y') {
cout << "You've been friend with " << mov->name << "!\n\n";
BeFriend(f);
ReTop();
return 0;
}else{
mov = mov->nest;
return 1;
}
}
void End()
{
Friend* temp;
while(number--) {
temp = head->nest;
delete(head);
head = temp;
}
}
};
int main()
{
char name[20] = "Always Laugh";
Friend *Me = new Friend(name);
FriendList myFriend(Me);
int T = 4;
while(T--) {
cout << "Enter your name: ";
cin >> name;
Friend* you = new Friend(name);
cout << "Hello " << you->name << "!" << endl;
while(myFriend.Que(you));
}
return 0;
}
這個示例程序對剛接觸C++的人來說難度很大,接近一個課設。因為類的內部聯系復雜。
并且還用到了動態內存分配(比如new和delete操作符),和鏈表的內容(這些內容對大家來說是陌生的)。
這是一個交朋友的程序,大家不妨編譯運行一下,看看效果,再盡力把代碼搞懂(為了避免越界訪問,請不要輸入得太古怪)。