適合具備 C 語言基礎的 C++ 入門教程(一)
引言
C 語言通常被認為是一種面向過程的語言,因為其本身的特性更容易編寫面向過程的程式碼,當然也不排除使用 C 語言編寫面向過程的程式碼,比如 Linux 的原始碼以及現在很火的國產物聯網作業系統 RT-Thread,其核心的實現方式都是使用 C 語言實現的面向物件的程式碼。相比於 C 語言來說,C++ 更能夠實現面向物件的程式設計,其具有的特性也要比 C 語言要多的多。下面假設有這樣一個需求。
現要描述兩個人的資訊,姓名,職業,年齡,並輸出。
我們首先先使用 C 語言的設計思路實現這個功能。
C語言描述
如果使用 C 語言來描述上面這個問題,大部分都會想到使用結構體來完成這個要求,寫出的程式也就如下所示:
#include <stdio.h>
struct person
{
char *name;
int age;
char *work;
};
int main(int argc, char** aggv)
{
struct person persons[] = {
{"wenzi",24,"programer"},
{"jiao", 22,"teacher"},
};
char i;
for (i = 0; i < 2; i++)
{
printf("name is:%s,age is:%d,work is:%s\n",persons[i].name,persons[i].age,persons[i].work);
}
}
上述這是比較初級的寫法,如果對 C 語言瞭解的更多一點的人在寫這段程式的時候,會使用函式指標的方式將程式碼寫的更加巧妙,程式碼如下所示:
#include <stdio.h>
struct person
{
char *name;
int age;
char *work;
void (* printInfo)(struct person *per);
};
void printInfo(struct person *per)
{
printf("The people's name is:%s,age is:%d,work is:%s\n",per->name,per->age,per->work);
}
int main(int argc, char** argv)
{
struct person per[2];
per[0] = {"wenzi",18,"programer",printInfo};
per[1] = {"jiaojiao",18,"teacher",printInfo};
per[0].printInfo(&per[0]);
per[1].printInfo(&per[1]);
}
使用了函式指標的方式來書寫這個程式,程式也變得更加簡介了,主函式裡也少了 for
迴圈。
C++ 的引入
那除此之外,還有更好的書寫方式麼,這個時候就要引入 C++ 的特性了,上述程式碼中在執行函式時都傳入了引數,那要如何做才能將上述中的引數也省略去呢,且看如下的程式碼:
#include <stdio.h>
struct person
{
char *name;
int age;
char *work;
void prinfInfo(void)
{
printf("The people's name is:%s,age is:%d,work is:%s\n",name,age,work);
}
};
int main(int argc, char** argv)
{
struct person persons[] = {
{"wenzi", 18,"program"},
{"jiao", 18, "teacher"},
};
persons[0].prinfInfo();
persons[1].prinfInfo();
return 0;
}
上述程式碼中使用了 C++ 的特性,在結構體中定義了函式,然後也就可以直接呼叫函數了,更上面 C 語言的程式碼相比較,它沒了實參,而且程式碼看起來也比 C 語言更加簡潔了。
實際在 C++ 中它具有自己獨有的一套機制來實現上述的程式碼,也就是即將說明的 class
,有了 class 之後,我們就可以這樣書寫程式碼:
#include <stdio.h>
class person
{
public:
char * name;
int age;
char * work;
void printInfo(void)
{
printf("The people's name is:%s,age is:%d,work is:%s\n",name,age,work);
}
}
int main(int argc, char** argv)
{
person persons[] = {
{"wenzi", 18,"program"},
{"jiao", 18, "teacher"},
};
persons[0].prinfInfo();
persons[1].prinfInfo();
return 0;
}
上述就是關於 C++ 的一個簡單的引入過程。
C++ 資料訪問控制
但是為了能夠改變類裡地資料,但是又要使得這個改變不要越界,避免胡亂地改變,我們可以這樣來定義這個類:
#include <stdio.h>
#include <iostream>
class Person
{
private:
char *name;
int age;
char *work;
public:
void PrintInfo(void)
{
cout << "name is:" << name << "age = "<< age << "work is:"<< work <<endl;
}
};
這樣定義一個類之後,類裡面的資料成員就變成了私有的,不能夠在外部進行訪問,比如下面這樣子就是錯誤的:
int main(int argc, char ** argv)
{
Person per;
per.age = 10; // error
}
上述這樣進行資料的訪問就是錯誤的,那麼要如何進行訪問呢,我們可以定義這樣一個成員函式進行資料的讀寫,比如下面的程式碼所示:
#include <stdio.h>
#include <iostream>
using namespace std;
class Person
{
private:
char *name;
int age;
char *work;
public:
void PrintInfo(void)
{
cout << "name is:" << name << ",age = "<< age << ",work is:"<< work <<endl;
}
void setName(char *n)
{
name = n;
}
int setAge(int a)
{
if (a < 0 || a > 150)
{
age = 0;
return 0;
}
age = a;
}
};
這樣定義了類之後,就可以訪問私有成員了,比如下面這樣進行:
int main(int argc, char **argv)
{
Person per;
per.setName("wenzi");
per.setAge(24);
per.PrintInfo();
return 0;
}
上述程式碼加入了 private
訪問控制符,通過在類裡面定義成員函式的方式,能夠對私有成員進行讀寫。
this 指標
再來看上述的程式碼,我們可以看到在書寫 setName
和 setAge
這兩個函式的時候,形參寫的是 char *n
和 int a
,這樣子給人的感覺就不是那麼的直觀,如果寫成 char *name
和 char *age
呢,比如成員函式是像下面這樣子編寫的。
void setName(char *name)
{
name = name;
}
int setAge(int age)
{
if (age < 0 || age > 150)
{
age = 0;
return 0;
}
age = age;
}
上述程式碼也很容易看出問題,根據 C 語言的就近原則,name = name
沒有任何意義,這個時候就需要引入 this 指標。引入 this 指標之後的程式碼如下所示:
#include <iostream>
#include <stdio.h>
using namespace std;
class Person {
private:
char *name;
int age;
char *work;
public:
void setName(char *name)
{
this->name = name;
}
int setAge(int age)
{
if (age < 0 || age > 150)
{
this->age = 0;
return -1;
}
this->age = age;
return 0;
}
void printInfo(void)
{
cout << "name =" << name << ", age =" << age << endl;
}
};
int main(int argc, char **argv)
{
Person per;
per.setName("wenzi");
per.setAge(25);
per.printInfo();
}
在上述程式碼中,引入了 this 指標,通過上述程式碼也可以非常清楚它的意思,就是代表當前例項化的物件,能夠指向當前例項化物件的成員。
程式結構
上述程式碼中,成員函式是在類裡面實現的,這樣使得整個類看著十分的臃腫,我們可以按照如下的方式進行書寫:
#include <stdio.h>
class Person
{
private:
char *name;
int age;
char *work;
public:
void SetName(char *name);
int SetAge(int age;)
void PrintInfo(void);
}
void Person::SetName(char *name)
{
this->name = name;
}
void Person::SetAge(int age)
{
this->age = age;
}
void Person::PrintInfo(void)
{
cout << "name = " << name << "age = " << age << endl;
}
通過在類外面實現我們的成員函式,看起來要更為簡潔一些,上述就是程式碼的實現形式。
多檔案
上述程式碼中,我們都是將程式碼寫在一個檔案中,這樣當代碼量很大的時候,如果程式碼都是在一個檔案裡,那麼會使得程式碼難以閱讀,這個時候,我們就會將程式碼分別放在幾個檔案中來進行管理,比如實現上述相同的功能,我們的程式碼結構如下圖所示:
其中 main.cpp
檔案中的內容如下所示:
#include <stdio.h>
#include "person.h"
int main(int argc, char **argv)
{
Person per;
//per.name = "zhangsan";
per.setName("zhangsan");
per.setAge(200);
per.printInfo();
return 0;
}
可以看到在上述 main.cpp
中包含了 #include "person.h"
標頭檔案,實際上是在 person.h
檔案中定義了 person
類,person.h
檔案的內容如下:
#ifndef __PERSON_H__
#define __PERSON_H__
class Person {
private:
char *name;
int age;
char *work;
public:
void setName(char *name);
int setAge(int age);
void printInfo(void);
};
#endif
然後,在 person.cpp
中定義了成員函式:
#include <stdio.h>
#include "person.h"
void Person::setName(char *name)
{
this->name = name;
}
int Person::setAge(int age)
{
if (age < 0 || age > 150)
{
this->age = 0;
return -1;
}
this->age = age;
return 0;
}
void Person::printInfo(void)
{
printf("name = %s, age = %d, work = %s\n", name, age, work);
}
在有了上述三個檔案之後,要如何進行編譯呢,這個時候就需要寫一個 Makefile
檔案,接下來簡單介紹一下 Makefile
語法。
Makefile
總的來說 Makefile
的規則核心就如下所示:
target ... :prerequisites
command
...
...
target也就是一個目標檔案,可以是Object File,也可以是執行檔案。還可以是一個標籤
prerequisites就是要生成那個target所需要的檔案或者是目標
command就是make所要執行的命令(任意的Shell)
說了核心的東西,來看我們當前所編寫的 Makefile
檔案,Makefile
檔案如下所示:
person: main.o person.o
g++ -o [email protected] $^
%.o : %.cpp
g++ -c -o [email protected] $<
clean:
rm -f *.o person
在這裡所要明確的一點是這樣的,就是在 Makefile
中,必須使用 Tab 鍵來進行縮排。然後,需要明確的一個概念是,要使得程式碼能夠執行,需要經過 編譯 -> 連結 -> 執行,這三個過程才能夠執行,編譯是把原始檔編譯成中間程式碼,這個中間程式碼在 UNIX 是 .o 檔案,然後再把大量的 .o 檔案合成可執行檔案,這個過程就是 連結,最後,執行我們連結好的可執行檔案。
我們來看上述這個 Makefile
檔案,person
是最終的可執行檔案,然後,要生成這個可執行檔案,需要 main.o
檔案和 person.o
檔案,然後執行這個操作需要的是第二條命令,g++ -o [email protected] $^
,其中 [email protected]
表示的是目標檔案,$^
表示的是所有依賴檔案。
然後,緊接著看第三條,%.o : %.cpp
,這裡表示的是萬用字元,表示的是所有的 .o 檔案和所有的 .cpp 檔案,意思就是說要生成的所有的 .o 檔案依賴於 .cpp 檔案,然後,執行的命令是 g++ -c -o [email protected] $<
其中表示的是第一個依賴檔案。
最後,我們需要清楚,在編譯過程中,生成了一些中間檔案以及可執行檔案,如果我們想要清除掉當前生成的檔案,那麼只需要執行 make clean
就可以清除掉生成的 .o
檔案以及 person
檔案。
函式過載
C++ 不允許變數重名,但是對於函式來說,可以允許過載,只要函式的引數不同即可,這樣就完成了函式的過載,直接來看一段關於函式過載的程式碼:
#include <iostream>
using namespace std;
int add(int a, int b)
{
cout<<"add int+int"<<endl;
return a+b;
}
int add(int a, int b, int c)
{
cout<<"add int+int+int"<<endl;
return a+b+c;
}
double add(double a, double b)
{
cout<<"add double+double"<<endl;
return a+b;
}
double add(int a, double b)
{
cout<<"add int+double"<<endl;
return (double)a+b;
}
double add(double b, int a)
{
cout<<"add double+int"<<endl;
return (double)a+b;
}
int main(int argc, char **argv)
{
add(1, 2);
add(1, 2, 3);
add(1.0, 2.0);
add(1, 2.0);
add(1.0, 2);
return 0;
}
程式碼很簡單,就是兩數相加的一個運算,但是兩數相加的形參不一樣,有的形參是兩個整型的相加,還有是一個整型和浮點數的相加,因為 C++ 過載的功能,因此,得以定義多個函式名相同但是形參和返回值都不同的函式,從而在主函式實現了不同型別數的相加。
引用和指標
在 C語言中是沒有引用的,在 C++ 中引用的提出也使得之前在 C 語言中必須使用指標的操作,現在可以使用引用完成了,但是引用又不是指標,簡單來說,引用是一個變數的別名,也就是“綽號”,對於這個別名的操作也就完全等同於被引用變數的操作。為了看是否真的是別名,我們來實驗這樣一段程式碼:
#include <iostream>
using namespace std;
int main(int argc,char **argv)
{
int m;
m = 10;
int &n = m;
int *p = &m;
int *p1 = &n;
cout << "n =" << n << endl;
cout << "p =" << p << endl;
cout << "p1 =" << p1 << endl;
return 0;
}
上述這段程式碼中輸出的就是 n 的值,和 m 以及 n 變數的地址,我們來看輸出的內容:
可以看到程式碼中雖然是對 m 進行了賦值,但是在輸出 n 的時候,輸出的是 m 的值,也就是說在這裡對於 n 的操作是完全等同於 m 的,緊接著,我們來證實 n 是否是 m 的別名,那麼我們就來看 n 和 m 的地址,可以看到我們輸出的兩個變數的地址也是完全一致的,這也就證實了我們的說法。
接下來,看一段指標,引用,常規形參的一段程式碼,程式碼如下所示:
#include <iostream>
using namespace std;
int add_one(int a)
{
a = a+1;
return a;
}
int add_one(int *a)
{
*a = *a + 1;
return *a;
}
int add_one_ref(int &b)
{
b = b+1;
return b;
}
int main(int argc, char **argv)
{
int a = 99;
int &c = a;
cout<<add_one(a)<<endl;
cout<<"a = "<<a<<endl;
cout<<add_one(&a)<<endl;
cout<<"a = "<<a<<endl;
cout<<add_one_ref(a)<<endl;
cout<<"a = "<<a<<endl;
c++;
cout<<"a = "<<a<<endl;
cout<<"c = "<<c<<endl;
return 0;
}
根據上述對於引用的闡述,我們直接給出執行結果,執行結果如下所示:
具體的計算過程就不再這裡贅述了。
小結
OK,上述就是關於 C++ 的一個簡單的引入的過程以及其涉及到的一部分有別於C語言的語法,本教程將持續連載,歡迎各位朋友關注~
本小節所涉及的程式碼可以通過百度雲連結的方式獲取:連結:https://pan.baidu.com/s/1RWPXiqiFCVApcfTdaHyDgw
提取碼:j9hd