0.前言
今天,我們進入c++的學習,我在專欄裡提到過,這些課程,來自我在大學自學時候的筆記整理而成,可能有不完善之處,在今天的課程筆記裡,我們忽略了一個有興趣的帶入點,c++的起源,在此引用維基百科的解釋
1.從C語言到C++一些基礎語法的變化
1.1 記憶體的申請和釋放
在C語言當中,我們學習的堆空間申請和釋放:
申請:malloc
釋放:free
在C++當中,推薦使用:
申請:new
釋放:delete
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main()
{
//1. 在C中,申請10個int的空間
int* p1 = (int*)malloc(sizeof(int) * 10);
memset(p1, 0, sizeof(int) * 10);
//2. 在C++中,申請的方式,使用new,並且在申請的同時可以直接
//初始化
//注意:這種寫法,申請了10個int,前5個初始值是1,2,3,4,5
//後面5個就是0
int* p2 = new int[10]{1,2,3,4,5};
//注意:這種寫法,申請了1個int,初始值是10
int* p3 = new int(10);
//3. 釋放malloc申請的空間
free(p1);
//4. 釋放new出來的空間
// 當初申請的時候,申請了1個以上,釋放的時候就需要加[]
delete[]p2;
// 當初申請的時候,只申請了1個,釋放就無需加[]
delete p3;
}
他們有什麼區別?
1.malloc和free 他們是函式,new和delete 他們是C++的運算子
2.malloc返回的是一個 void*型別的指標,需要我們自己轉換的。new申請什麼型別就得到什麼型別的指標,不需要強制轉換的。
3.malloc不會呼叫類的建構函式,free不會呼叫類的解構函式。new會呼叫建構函式,delete會呼叫解構函式。
4.C++推薦使用new和delete
1.2 函式的過載
//實現一個函式,得到兩個整型資料的較大值
int GetMaxInt(int a, int b)
{
if (a>b)
{
return a;
}
else
{
return b;
}
}
//又有新需求,得到兩個浮點型資料的較大值
double GetMaxDouble(double a, double b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
int main()
{
int n = GetMaxInt(10, 20);
double m = GetMaxDouble(10.5, 7.8);
return 0;
}
上面這個程式碼,也是可以的。但是兩個函式的功能實際是一致的,都是獲取較大值。但是函式名卻不一樣,那麼就會增大我們記憶的負擔。需要記住很多的函式名。
C++提供了一個比較好的機制,可以減輕這樣的負擔------- 函式過載
函式過載:在相同的作用域內,函式的名字相同,但是引數不同,這樣可以構成過載,構成過載之後,在呼叫函式的時候,會根據傳遞的引數,自動選擇呼叫哪個函式。
引數不同:
a.型別不同
b.順序不同
c.個數不同
//實現一個函式,得到兩個整型資料的較大值
int GetMax(int a, int b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
//又有新需求,得到兩個浮點型資料的較大值
double GetMax(double a, double b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
int main()
{
GetMax(20, 10);
GetMax(20.8, 10.5);
return 0;
}
只有返回值型別不同,不能構成過載的:
使用函式過載的好處:
我們不需要去維護,記憶很多的函式名,使用起來比較便利。
實際上,過載是一種多型機制,介面複用
名稱粉碎機制,C++的函式名,也要把引數型別算進去,是過載的底層機制。
如果不要名稱粉碎的話,可以在函式的前面 加上
extern "C" 這樣一個宣告,就會以 C的方式編譯函式,不會名稱粉碎了,也就不能過載了。
1.3 預設引數
//獲取自由落體的速度
double GetV(double t, double g = 9.8)
{
return t * g;
}
int main()
{
double v = GetV(5);
GetV(10);
GetV(20);
GetV(20, 9.8 / 6);
return 0;
}
一些需要注意的地方:
1.預設值只能從右往左設定,中間不能間斷
2.當一個函式既有宣告,又有定義的時候,預設引數只能寫在宣告中。
3.當同時出現預設引數和函式過載的時候,容易造成二義性問題
#include <stdio.h>
int GetAdd(int a, int b, int c, int d = 0, int e = 0)
{
return a + b + c + d + e;
}
int GetAdd(int a, int b, int c)
{
return a + b + c;
}
int main()
{
GetAdd(1, 2, 3);
return 0;
}
1.4 引用
#include <stdio.h>
int main()
{
//1. 定義一個變數
int nNum = 100;
//2. 定義一個引用,引用nNum
//這裡& 不是取地址,而是用於定義型別的
int& a = nNum;//a就是nNum的別名
a = 200;
printf("%d %d\n", a, nNum);
nNum = 500;
printf("%d %d\n", a, nNum);
//背後的原理是什麼呢??
//a只是一個名字,沒有自己的空間,和被引用的物件nNum公用記憶體
printf("%p %p", &a, &nNum);
return 0;
}
有什麼用???
可以代替指標的一部分功能:
#include <stdio.h>
void swap(int& a, int& b)
{
int n = a;
a = b;
b = n;
}
int main()
{
int nNum1 = 10;
int nNum2 = 20;
swap(nNum1, nNum2);
printf("%d %d", nNum1, nNum2);
return 0;
}
引用能夠做到的事情,指標也是可以的。為什麼要使用引用呢???
引用和指標有什麼區別???(整體來說,引用比較安全)
1.引用必須初始化,指標可以不初始化。
2.引用一旦初始化,就不能引用其他位置了。
3.指標是一個變數,有自己的記憶體,引用是一個別名,沒有自己的記憶體
#include <stdio.h>
int main()
{
//1. 引用必須要初始化,指標不必
int nNum = 100;
int& a = nNum;
int* p = nullptr;
p = &nNum;
//2. 引用一經初始化,就不能再引用其他位置了
int nNum2 = 200;
a = nNum2;//這個叫複製
p = &nNum2; //p指向了新的位置
//3. 指標的本質是一個變數,有名字,有自己的空間(用來存地址的)
// 引用是沒有自己的空間的,是依賴於被引用的物件存在而存在的
return 0;
}
目前,我們們學習了3種傳參方式:
1.數值傳遞
2.指標傳遞:本質上,還是數值傳遞,只是這個數值是個地址。
3.引用傳遞:形參和實參共享記憶體,這裡就可以說 是形參改變了實參
1.5 C++的輸入和輸出
在C語言種,我們使用的是printf和scanf_s 實現的輸入和輸出。在C++中有了新的方式:
輸出:cout
輸入:cin
配合流運算子: 輸出流 << 輸入流 >>
不能直接使用
1.5.1 名稱空間
需要透過名稱空間去使用名稱空間:是一種防止命名衝突的機制有三種方式:using namespace std; //直接開啟名稱空間中的所有內容,全部都能直接使用了
using std::cout ;//只開啟了 cout 使用誰開啟誰
使用cout的時候,加上名稱空間的名字, 使用 :: 作用域符號
一般使用 方式3或者方式2,方式1 不推薦
#include <iostream>
//using namespace std;
//using std::cout;
int main()
{
std::cout << "helloworld"<<std::endl;
std::cout << 10 << std::endl;
std::cout <<3.32453245+8 << std::endl<<10<<20<<300<<'a';
return 0;
}
1.5.2 cin的使用
#include <iostream>
//using namespace std;
using std::cin;
int main()
{
//1. 輸入一個整數
int nNum = 0;
cin >> nNum;
//2. 輸入一個字元
char cCh = 0;
cin >> cCh;
//3. 輸入一個小數
double fNum = 0;
cin >> fNum;
//4. 輸入一個字串
char buf[50] = {};
char* p = new char[100]{ 0 };
//gets_s(buf); 可以接收空格
cin >> buf;
cin >> p;
return 0;
}
2.類的基本語法
2.1 理解類的語法
定義學生結構體,並且能夠進行結構體的一些使用
#include <iostream>
//using namespace std;
using std::cin;
struct STUDENT {
char szName[20]; //姓名
int nId; //學號
int nScore; //分數
};
void PrintfStu(STUDENT stu)
{
printf("%s ", stu.szName);
printf("%d ", stu.nId);
printf("%d ", stu.nScore);
}
void PrintfStu(STUDENT* pstu)
{
printf("%s ", pstu->szName);
printf("%d ", pstu->nId);
printf("%d ", pstu->nScore);
}
void SetStu(STUDENT* pstu,const char* szName,int nId,int nScore)
{
//pstu->szName = szName
strcpy_s(pstu->szName, szName);
pstu->nId = nId;
pstu->nScore = nScore;
}
int main()
{
STUDENT stu1 = { "xiaoming",20,90 };
PrintfStu(&stu1);
SetStu(&stu1, "xiaohong", 21, 95);
PrintfStu(&stu1);
return 0;
}
在函式傳參的時候,如果引數需要是結構體的話,一般使用指標,優點有兩個:
1.傳遞的資料量比較小的,只有4個位元組
2.能夠修改外部的資料
以上的程式碼,都是學習過的,在C語言程式開發中,也是沒有問題的。
但是,這裡有一個天然的缺點:
需要由程式設計師自己去維護函式和變數之間的使用關係,比如:
我們需要很清楚 PrintfStu,SetStu使用的是STUDENT這個結構體。
在程式規模比較小的時候,這個是比較好維護的。
當程式規模很大之後,再去維護他們的關係就是一個比較大的負擔了。比如有好幾百個結構體,好幾千個函式。
此時有一個新的語法,能解決這個問題,就是類。
2.2 類的語法
#include <iostream>
//關鍵字:class
//類名:一般以C開頭,後面是一個單詞,首字母大寫
//類和結構體一樣,都是自定義的資料型別
//這個資料型別中,可以包含變數,也可以包含函式
class CStudent {
public:
void PrintfStu()
{
printf("%s ", this->szName);
printf("%d ", this->nId);
printf("%d ", this->nScore);
}
void SetStu( const char* szName, int nId, int nScore)
{
//pstu->szName = szName
strcpy_s(this->szName, szName);
this->nId = nId;
this->nScore = nScore;
}
private:
char szName[20]; //姓名
int nId; //學號
int nScore; //分數
};
int main()
{
//定義的這個變數,即包含了資料,又能直接使用函式
CStudent stu1 ;
stu1.SetStu("xiaoming", 20, 90);
stu1.PrintfStu();
stu1.SetStu("xiaohong", 21, 95);
return 0;
}
總結:
1.這麼寫了之後,資料和操作這個資料的函式,在語法上就有了聯絡,他們之間的關係就不需要我們維護了。
2.類:class定義出來的新型別 物件:使用類定義的變數
3.類中的函式,稱之為 成員函式 或者 成員方法 方法 行為
4.類中的變數,稱之為 成員變數 或者 屬性 類中資料
2.3 類中的許可權:
類中的資料和函式,有一個許可權的概念:
訪問物件中的資料,通常有兩種方式:
1.透過物件直接訪問
2.透過物件呼叫物件自己的函式,由物件自己的函式去訪問(自己的函式訪問自己的資料)
public(公有的): 可以透過類的物件 直接去使用的成員
protected(保護的):不可以透過類的物件 直接去使用的成員 (在繼承的時候再詳細講)
private(私有的): 不可以透過類的物件 直接去使用的成員
許可權體現的也是封裝性的思想:
通常來說,將資料定義為私有,這樣的話,使用類的人就不能隨意的修改資料,更為安全。具體怎麼使用這些資料,都需要透過類提供的函式。整個程式就會更為安全。
2.4 this指標
當我們定義一個物件的時候,只定義出來了新的資料成員。函式在記憶體永遠都只有一份
在SetStu這個函式中,如何區分要修改哪一個物件的資料???就是透過this指標。
this指標本質上來說,就是物件的地址。是預設傳遞進去的。
#include <iostream>
//關鍵字:class
//類名:一般以C開頭,後面是一個單詞,首字母大寫
//類和結構體一樣,都是自定義的資料型別
//這個資料型別中,可以包含變數,也可以包含函式
class CStudent {
public:
void PrintfStu()
{
printf("%s ", this->szName);
printf("%d ", this->nId);
printf("%d ", this->nScore);
}
void SetStu( const char* szName, int nId, int nScore)
{
//pstu->szName = szName
strcpy_s(this->szName, szName);
this->nId = nId;
this->nScore = nScore;
}
private:
char szName[20]; //姓名
private:
int nId; //學號
public:
int nScore; //分數
};
int main()
{
//定義的這個變數,即包含了資料,又能直接使用函式
CStudent stu1 ;
CStudent stu2;
CStudent stu3;
stu1.SetStu("xiaoming", 20, 90);//stu1.SetStu(&stu1,"xiaoming", 20, 90);
stu1.PrintfStu();//stu1.PrintfStu(&stu1);
stu1.SetStu("xiaohong", 21, 95);
stu1.nScore = 50;
stu2.SetStu("xiaobai", 15, 88);//stu2.SetStu(&stu2,"xiaobai", 15, 88);
return 0;
}
2.5 類中函式的編寫位置
我們們演示的時候,把函式寫在了類中。
但是實際情況,一般把函式都是寫在類外的