1. 程式人生 > 其它 >適合具備 C 語言基礎的 C++ 入門教程(一)

適合具備 C 語言基礎的 C++ 入門教程(一)

技術標籤: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 指標

再來看上述的程式碼,我們可以看到在書寫 setNamesetAge這兩個函式的時候,形參寫的是 char *nint a,這樣子給人的感覺就不是那麼的直觀,如果寫成 char *namechar *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 [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 變數的地址,我們來看輸出的內容:

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