適合具備 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;
- }
通過在類外面實現我們的成員函數,看起來要更為簡潔一些,上述就是代碼的實現形式。
多文件
上述代碼中,我們都是將代碼寫在一個文件中,這樣當代碼量很大的時候,如果代碼都是在一個文件里,那么會使得代碼難以閱讀,這個時候,我們就會將代碼分別放在幾個文件中來進行管理,比如實現上述相同的功能,我們的代碼結構如下圖所示:
image-20210110120503456
其中 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 $@ $^
- %.o : %.cpp
- g++ -c -o $@ $<
- clean:
- rm -f *.o person
在這里所要明確的一點是這樣的,就是在 Makefile中,必須使用 Tab 鍵來進行縮進。然后,需要明確的一個概念是,要使得代碼能夠執行,需要經過 編譯 -> 鏈接 -> 執行,這三個過程才能夠運行,編譯是把源文件編譯成中間代碼,這個中間代碼在 UNIX 是 .o 文件,然后再把大量的 .o 文件合成可執行文件,這個過程就是 鏈接,最后,執行我們鏈接好的可執行文件。
我們來看上述這個 Makefile文件,person是最終的可執行文件,然后,要生成這個可執行文件,需要 main.o文件和 person.o文件,然后執行這個操作需要的是第二條命令,g++ -o $@ $^,其中 $@ 表示的是目標文件,$^表示的是所有依賴文件。
然后,緊接著看第三條,%.o : %.cpp,這里表示的是通配符,表示的是所有的 .o 文件和所有的 .cpp 文件,意思就是說要生成的所有的 .o 文件依賴于 .cpp 文件,然后,執行的命令是 g++ -c -o $@ $<其中表示的是第一個依賴文件。
最后,我們需要清楚,在編譯過程中,生成了一些中間文件以及可執行文件,如果我們想要清除掉當前生成的文件,那么只需要執行 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 變量的地址,我們來看輸出的內容:
image-20210112235421638
可以看到代碼中雖然是對 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;
根據上述對于引用的闡述,我們直接給出運行結果,運行結果如下所示:
image-20210113000240223
具體的計算過程就不再這里贅述了。
小結
OK,上述就是關于 C++ 的一個簡單的引入的過程以及其涉及到的一部分有別于C語言的語法,本教程將持續連載,歡迎各位朋友關注~
本小節所涉及的代碼可以通過百度云鏈接的方式獲取:鏈接:https://pan.baidu.com/s/1RWPXiqiFCVApcfTdaHyDgw
提取碼:j9hd
本文轉載自微信公眾號「wenzi嵌入式軟件」,可以通過以下二維碼關注。轉載本文請聯系wenzi嵌入式軟件公眾號。