C語言和C++的關係:
C++完全相容C語言,並有自己的特性;
C++是在C語言的基礎上開發的一種物件導向程式語言,應用廣泛。C++支援多種程式設計 正規化 ——物件導向程式設計、泛型程式設計和過程化程式設計。 其程式設計領域眾廣,常用於系統開發,引擎開發等應用領域,是最受廣大程式設計師受用的最強大程式語言之一,支援類:類、封裝、過載等特性!
C++在C的基礎上增添類,C是一個結構化語言,它的重點在於演算法和資料結構。C程式的設計首要考慮的是如何透過一個過程,對輸入(或環境條件)進行運算處理得到輸出(或實現過程(事務)控制),而對於C++,首要考慮的是如何構造一個物件模型,讓這個模型能夠契合與之對應的問題域,這樣就可以透過獲取物件的狀態資訊得到輸出或實現過程(事務)控制。
1. C語言與C++的區別:
1.1 C語言是程序導向程式設計,而C++是物件導向程式設計;
1.1.1物件導向:物件導向是一種對現實世界的理解和抽象的方法、思想,透過將需求要素轉化為物件進行問題處理的一種思想。
1.2 C和C++動態管理記憶體的方法不一樣,C是使用malloc、free函式,而C++不僅有malloc/free,還有new/delete關鍵字。
1.2.1 malloc/free和new/delete差別:
①、malloc/free是C和C++語言的標準庫函式,new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體。
②、由於malloc/free是庫函式不是運算子,不在編譯器範圍之內,不能夠把執行建構函式和解構函式的任務強加入malloc/free。因此C++需要一個能完成動態記憶體分配和初始化工作的運算子new,一個能完成清理與釋放記憶體工作的運算子delete。
③、new可以認為是malloc加建構函式的執行。new出來的指標是直接帶型別資訊的。而malloc返回的都是void指標。
④、malloc是從堆上開闢空間,而new是從自由儲存區開闢(自由儲存區是從C++抽象出來的概念,不僅可以是堆,還可以是靜態儲存區)。
⑤、malloc對開闢的空間大小有嚴格指定,而new只需要物件名。
⑥、malloc開闢的記憶體如果太小,想要換一塊大一點的,可以呼叫relloc實現,但是new沒有直觀的方法來改變。
1.3 C++的類是C中沒有的,C中的struct可以在C++中等同類來使用,struct和類的差別是,struct的成員預設訪問修飾符是public,而類預設是private。
1.4 C++支援過載,而C不支援過載,C++支援過載在於C++名字的修飾符與C不同,例如在C++中函式 int f(int) 經過名字修飾之後變為_f_int,而C是_f,所以C++才會支援不同的引數呼叫不同的函式。
-
5 C++中有引用,而C沒有
1.5.1 指標和引用的區別:
①、指標有自己的一塊空間,而引用只是一個別名。
②、使用sizeof檢視一個指標大小為4(32位),而引用的大小是被引用物件的大小。
③、指標可以是NULL,而引用必須被初始化且必須是對一個以初始化物件的引用。
④、作為引數傳遞時,指標需要被解引用才可以對物件進行操作,而直接對引用的修改都會改變引用所指向的物件。
⑤、指標在使用中可以指向其它物件,但是引用只能是一個物件的引用,不能被修改。
⑥、指標可以有多級指標(**p),而引用只有一級。
⑦、指標和引用使用++運算子的意義不一樣。
1.6 C++全部變數的預設連線屬性是外連線,而C是內連線。
1.7C中用const修飾的變數不可以用在定義陣列時的大小,但是C++用const修飾的變數可以。
1.8C++有很多特有的輸入輸出流。
2. 程序導向與物件導向的區別:
3. C++新語法(儲存結構):
3.1引用:就是某一變數(目標)的一個別名,對引用的操作與對變數直接操作完全一樣。
3.1.1 引用的宣告方法:型別識別符號 &引用名=目標變數名;
如下:定義引用ra,它是變數a的引用,即別名。
int a;
int &ra=a;
(1)&在此不是求地址運算子,而是起標識作用。
(2)型別識別符號是指目標變數的型別。
(3)宣告引用時,必須同時對其進行初始化。
(4)引用宣告完畢後,相當於目標變數有兩個名稱即該目標原名稱和引用名,且不能再把該引用名作為其他變數名的別名。
(5)宣告一個引用,不是新定義了一個變數,它只表示該引用名是目標變數名的一個別名,它本身不是一種資料型別,因此引用本身不佔儲存單元,系統也不給引用分配儲存單元。故:對引用求地址,就是對目標變數求地址。&ra與&a相等。
(6)不能建立陣列的引用。因為陣列是一個由若干個元素所組成的集合,所以無法建立一個陣列的別名。
特點:變數的別名,不佔空間
#include<iostream.h>
void main(){
int a=5;
int &b=a;
b=6;
cout<<"a="<<a<<",b="<<b<<endl;//a=6,b=6
int c=7;
b=c;
cout<<"a="<<a<<",b="<<b<<endl;//a=7,b=7
}
#include<iostream.h>
void main(){
int a[]={1,2,3,4};
int &b=a;
//編譯錯誤:cannot convert from 'int [4]' to 'int &'
}
引用的應用
- 引用作為引數
引用的一個重要作用就是作為函式的引數。以前的C語言中函式引數傳遞是值傳遞,如果有大塊資料作為引數傳遞的時候,採用的方案往往是指標,因為這樣可以避免將整塊資料全部壓棧,可以提高程式的效率。但是現在(C++中)又增加了一種同樣有效率的選擇(在某些特殊情況下又是必須的選擇),就是引用。
#include<iostream.h>
////此處函式的形參p1, p2都是引用
void swap(int &p1,int &p2){
int p=p1;
p1=p2;
p2=p;
} 為在程式中呼叫該函式,則相應的主調函式的呼叫點處,直接以變數作為實參進行呼叫即可,而不需要實參變數有任何的特殊要求。如:對應上面定義的swap函式,相應的主調函式可寫為:
void main(){
int a,b;
cin>>a>>b;//輸入a,b兩個變數的值
swap(a,b);//直接以a和b作為實參呼叫swap函式
cout<<"a="<<a<<",b="<<b<<endl;
}
上述程式執行時,如果輸入資料10 20並回車後,則輸出結果為a=20,b=10。
由上例可以看出:
(1)傳遞引用給函式與傳遞指標的效果是一樣的。這時,被調函式的形參就成為原來主調函式中的實參變數或物件的一個別名來使用,所以在被調函式中對形參變數的操作就是對其相應的目標物件(在主調函式中)的操作。
(2)使用引用傳遞函式的引數,在記憶體中並沒有產生實參的副本,它是直接對實參操作;而使用一般變數傳遞函式的引數,當發生函式呼叫時,需要給形參分配儲存單元,形參變數是實參變數的副本;如果傳遞的是物件,還將呼叫複製建構函式。因此,當引數傳遞的資料較大時,用引用比用一般變數傳遞引數的效率和所佔空間都好。
(3)使用指標作為函式的引數雖然也能達到與使用引用的效果,但是,在被調函式中同樣要給形參分配儲存單元,且需要重複使用"*指標變數名"的形式進行運算,這很容易產生錯誤且程式的閱讀性較差;另一方面,在主調函式的呼叫點處,必須用變數的地址作為實參。而引用更容易使用,更清晰。
如果既要利用引用提高程式的效率,又要保護傳遞給函式的資料不在函式中被改變,就應使用常引用。
- 常引用
常引用宣告方式:const 型別識別符號 &引用名 = 目標變數名;
用這種方式宣告的引用,不能透過引用對目標變數的值進行修改,從而使引用的目標成為const,達到了引用的安全性。
#include<iostream.h>
void main(){
int a=1;
int &b=a;
b=2;
cout<<"a="<<a<<endl;//2
int c=1;
const int &d=c;
// d=2;//編譯錯誤 error C2166: l-value specifies const object
c=2;//正確
}
這不光是讓程式碼更健壯,也有其它方面的需求。
【例4】:假設有如下函式宣告:
string foo();
void bar(string &s);
那麼下面的表示式將是非法的:
bar(foo());
bar("hello world");
原因在於foo( )和"hello world"串都會產生一個臨時物件,而在C++中,臨時物件都是const型別的。因此上面的表示式就是試圖將一個const型別的物件轉換為非const型別,這是非法的。
引用型引數應該在能被定義為const的情況下,儘量定義為const 。
- 引用作為返回值
要以引用返回函式值,則函式定義時要按以下格式:
型別識別符號 &函式名 (形參列表及型別說明)
{ 函式體 }
說明:
(1)以引用返回函式值,定義函式時需要在函式名前加&
(2)用引用返回一個函式值的最大好處是,在記憶體中不產生被返回值的副本。
【例5】以下程式中定義了一個普通的函式fn1(它用返回值的方法返回函式值),另外一個函式fn2,它以引用的方法返回函式值。
#include<iostream.h>
float temp;//定義全域性變數temp
float fn1(float r);//宣告函式fn1
float &fn2(float r);//宣告函式fn2 r
float fn1(float r){//定義函式fn1,它以返回值的方法返回函式值
temp=(float)(r*r*3.14);
return temp;
}
float &fn2(float r){//定義函式fn2,它以引用方式返回函式值
temp=(float)(r*r*3.14);
return temp;
}
void main(){
float a=fn1(10.0);//第1種情況,系統生成要返回值的副本(即臨時變數)
// float &b=fn1(10.0); //第2種情況,可能會出錯(不同 C++系統有不同規定)
/*
編譯錯誤:cannot convert from 'float' to 'float &'
A reference that is not to 'const' cannot be bound to a non-lvalue
*/
//不能從被調函式中返回一個臨時變數或區域性變數的引用
float c=fn2(10.0);//第3種情況,系統不生成返回值的副本
//可以從被調函式中返回一個全域性變數的引用
float &d=fn2(10.0); //第4種情況,系統不生成返回值的副本
cout<<"a="<<a<<",c="<<c<<",d="<<d<<endl;
//a=314,c=314,d=314
}
引用作為返回值,必須遵守以下規則:
(1)不能返回區域性變數的引用。這條可以參照Effective C++[1]的Item 31。主要原因是區域性變數會在函式返回後被銷燬,因此被返回的引用就成為了"無所指"的引用,程式會進入未知狀態。如【例5】中的第2種情況出現編譯錯誤。
(2)不能返回函式內部new分配的記憶體的引用。這條可以參照Effective C++[1]的Item 31。雖然不存在區域性變數的被動銷燬問題,可對於這種情況(返回函式內部new分配記憶體的引用),又面臨其它尷尬局面。例如,被函式返回的引用只是作為一個臨時變數出現,而沒有被賦予一個實際的變數,那麼這個引用所指向的空間(由new分配)就無法釋放,造成memory leak。
(3)可以返回類成員的引用,但最好是const。這條原則可以參照Effective C++[1]的Item 30。主要原因是當物件的屬性是與某種業務規則(business rule)相關聯的時候,其賦值常常與某些其它屬性或者物件的狀態有關,因此有必要將賦值操作封裝在一個業務規則當中。如果其它物件可以獲得該屬性的非常量引用(或指標),那麼對該屬性的單純賦值就會破壞業務規則的完整性。
(4)引用與一些運算子的過載:流運算子<<和>>,這兩個運算子常常希望被連續使用,例如:cout << "hello" << endl; 因此這兩個運算子的返回值應該是一個仍然支援這兩個運算子的流引用。可選的其它方案包括:返回一個流物件和返回一個流物件指標。但是對於返回一個流物件,程式必須重新(複製)構造一個新的流物件,也就是說,連續的兩個<<運算子實際上是針對不同物件的!這無法讓人接受。對於返回一個流指標則不能連續使用<<運算子。因此,返回一個流物件引用是惟一選擇。這個唯一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用這個概念的原因吧。 賦值運算子=。這個運算子象流運算子一樣,是可以連續使用的,例如:x = j = 10;或者(x=10)=100;賦值運算子的返回值必須是一個左值,以便可以被繼續賦值。因此引用成了這個運算子的惟一返回值選擇。
【例6】 測試用返回引用的函式值作為賦值表示式的左值。
#include<iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main(){
put(0)=10;//以put(0)函式值作為左值,等價於vals[0]=10;
put(9)=20;//以put(9)函式值作為左值,等價於vals[9]=20;
cout<<vals[0]<<endl;//10
cout<<vals[9]<<endl;//20
}
int &put(int n){
if(n>=0 && n<=9)
return vals[n];
else{
cout<<"subscript error";
return error;
}
}
- 引用和多型
引用是除指標外另一個可以產生多型效果的手段。這意味著,一個基類的引用可以指向它的派生類例項。
【例7】:
class A;
class B:public A{ ... ... }
B b;
A &Ref = b;//用派生類物件初始化基類物件的引用
Ref 只能用來訪問派生類物件中從基類繼承下來的成員,是基類引用指向派生類。如果A類中定義有虛擬函式,並且在B類中重寫了這個虛擬函式,就可以透過Ref產生多型效果。
引用總結
(1)在引用的使用中,單純給某個變數取個別名是毫無意義的,引用的目的主要用於在函式引數傳遞中,解決大塊資料或物件的傳遞效率和空間不如意的問題。
(2)用引用傳遞函式的引數,能保證引數傳遞中不產生副本,提高傳遞的效率,且透過const的使用,保證了引用傳遞的安全性。
(3)引用與指標的區別是,指標透過某個指標變數指向一個物件後,對它所指向的變數間接操作。程式中使用指標,程式的可讀性差;而引用本身就是目標變數的別名,對引用的操作就是對目標變數的操作。
(4)使用引用的時機。流運算子<<和>>、賦值運算子=的返回值、複製建構函式的引數、賦值運算子=的引數、其它情況都推薦使用引用。
引用型別:(不佔記憶體)
-
概念:不佔用記憶體空間,用於充當變數的別名
-
語法格式: 資料型別& 變數名 = 變數 :(必須初始化)
-
特點:
-
-
引用必須初始化
-
引用只能初始化一次,後續不能再充當別的變數的引用
-
不存在多級引用
-
不存在引用陣列
-
引用的資料型別和變數的資料型別必須完全一致;
-
不能有void型別的引用
-
-
#include <iostream>
using namespace std;
int main()
{
int a = 100;
int b = -233;
int& pa = a;
cout << pa << endl;
pa = 12;
cout << pa << endl;
//取別名只能用一次,pa = a,當pa = b時,就是a = b;修改的是a的值
pa = b;
cout << a << endl;//a = -299
cout << b << endl;//b = -299
cout << pa << endl;//pa = -299
//給pa起別名 == 給a取別名
int& paa = pa;
paa = 101;
cout << a << endl;//a = 101
cout << pa << endl;//pa = 101
cout << paa << endl;//pa = 101
return 0;
}
給指標起別名(引用)
int main()
{
int a = 100;
int* p = &a;//指標
int*(&pp) = p;//引用
int** ppp = &p;//指標
int** (&pap) = ppp;//引用
cout << "p = " << *pp << endl;
cout << "sizep = " << sizeof(p) << endl;
cout << "sizepp = " << sizeof(pp) << endl;
cout << "sizea = " << sizeof(a) << endl;
cout << "sizeppp = " << sizeof(ppp) << endl;
}
int main()
{
int a = 100;
int b = -233;
int* p = &a;//指標
int* & pa = p;
cout << pa << endl;
return 0;
}
給陣列起別名(引用)
int main()
{
int a[3] = { 1,2,3 };
int(&pa)[3] = a;
cout << pa[1] << endl;
cout << sizeof(pa) << endl;
return 0;
}
引用陣列->不存在,因為引用不佔記憶體;
函式的引用:
int main()
{
void(*pFun)(int) = Fun; //函式指標
pFun(100);
//函式的引用
void(&fFun)(int) = Fun;
fFun(98);
return 0;
}
引用的資料型別和變數的資料型別必須完全一致;
int main()
{
unsigned short a = 100;
unsigned short& pa = a;
}
引用不能有void型別,但有void*型別指標;
引用充當函式引數及返回值問題
- 引用充當函式實參(沒區別)
#include<iostream>
using namespace std;
#if 0
void Fun(int x, int y)
{
++x;
++y;
cout << "Fun:" << x << "," << y << endl;
}
int main()
{
int a, b;
a = 88;
b = 78;
int& pa = a;
int& pb = b;
Fun(pa, pb);//引用充當函式的實參,實際上儲存的事變數本身
cout << a << "," << b << endl;
return 0;
}
#endif
引用充當函式的形參:實際上間接修改了實際引數
#include<iostream>
using namespace std;
#if 0
void Fun(int& x, int& y)
{
++x;
++y;
cout << "Fun:" << x << "," << y << endl;
}
int main()
{
int a, b;
a = 88;
b = 78;
//int& pa = a;
//int& pb = b;
Fun(a, b);//引用充當函式的實參,實際上儲存的事變數本身
cout << a << "," << b << endl;
return 0;
}
#endif
函式的返回值是引用,則意味著返回了變數的空間本身,因此函式此時可以充當左值;
#include <iostream>
using namespace std;
#if 0
//函式的返回值是引用,則意味著返回了變數的空間本身,因此函式此時可以充當左值
int& Fun()
{
static int x = 899;
cout << x << endl;
return x;
}
int main()
{
//int res;
//res = Fun();
//cout << res << endl;
Fun() = 900;
Fun();
return 0;
}
#endif
4. 右值引用(C++11特性):
C++11 引入了右值引用(Rvalue references),這是一種特殊的引用型別,它可以用來引用即將被銷燬的物件,也就是右值(rvalues)。右值引用的引入主要是為了支援移動語義(move semantics)和完美轉發(perfect forwarding)。
右值引用的語法
右值引用使用雙&&
來宣告,例如:
int&& rvalueRef = 10; // 10 是一個右值
右值引用的作用
-
移動語義(Move Semantics):允許資源的“移動”,而不是“複製”。這可以減少不必要的資源複製,提高效率。
例如,如果你有一個資源密集型物件,如動態分配的記憶體,移動語義允許你將資源從一個物件轉移到另一個物件,而不是複製資源。
std::vector<int> v1 = {1, 2, 3}; std::vector<int> v2 = std::move(v1); // 移動v1的資源給v2,v1變為空
-
完美轉發(Perfect Forwarding):允許函式模板完美地轉發引數給另一個函式,保持引數的左值/右值特性。
例如,模板函式可以接收任意型別的引數,並將其轉發給另一個函式,同時保持引數的左值或右值特性。
template<typename T> void wrapper(T&& arg) { someFunction(std::forward<T>(arg)); }
右值引用與左值引用的區別
- 左值引用(Lvalue references):可以繫結到左值(lvalues)和右值(rvalues),但繫結到右值時,右值會被轉化為左值。
- 右值引用:只能繫結到右值,不能繫結到左值。
右值引用的規則
- 左值可以繫結到左值引用,但右值引用只能繫結到右值。
- 右值引用可以繫結到左值,但這種情況下左值會被“移動”。
- 右值引用可以繫結到右值引用,但這種情況下會發生引用摺疊(reference collapsing),結果仍然是一個右值引用。
總結
右值引用是C++11中一個強大的特性,它使得資源管理更加高效,同時也支援了模板程式設計中的完美轉發。透過使用右值引用,開發者可以編寫出更加高效和靈活的程式碼。
#include <iostream>
using namespace std;
double add(double x, double y)
{
return x + y;
}
int main()
{
double x = 1.1, y = 1.3;
10;
x + y;
add(1, 2);
//右值引用
int&& r1 = 10;
cout << "r1 = " << r1 << endl;
double&& r2 = x + y;
cout << "r2 =" << r2 << endl;
r2 = 1;
cout <<"r2 =" << r2 << endl;
double&& r3 = add(x, y);
cout <<"r3 = " << r3 << endl;
int&& r4 = add(1, 2);
cout <<"r4 = " << r4 << endl;
int m = r4;
cout << "m = " << m << endl;
return 0;
}
5. 引用與指標的區別:
C++中的引用和指標是兩種不同的機制,它們在記憶體管理和使用上有著本質的區別。以下是引用和指標的主要區別:
-
定義和初始化:
- 引用:引用必須在定義時被初始化,一旦初始化後,它就不能再指向另一個物件。引用的宣告和初始化是同時進行的。
int a = 10; int& ref = a; // 引用必須在宣告時初始化
- 指標:指標可以在定義時不初始化,可以在任何時候指向任何物件。
int* ptr; // 指標可以在宣告時不初始化 int b = 20; ptr = &b; // 指標可以在任何時候指向另一個物件
- 引用:引用必須在定義時被初始化,一旦初始化後,它就不能再指向另一個物件。引用的宣告和初始化是同時進行的。
-
記憶體佔用:
- 引用:引用本身不佔用記憶體,它只是目標物件的一個別名。
- 指標:指標本身需要佔用記憶體來儲存它所指向的物件的地址。
-
型別轉換:
- 引用:引用一旦被初始化,其型別就固定了,不能改變。
- 指標:指標可以很容易地進行型別轉換,例如,可以將一個
int*
轉換為void*
。
-
多重間接:
- 引用:引用不能被多重間接,即不存在引用的引用(
int&&
是右值引用,不是引用的引用)。 - 指標:指標可以被多重間接,例如
int** ptr
。
- 引用:引用不能被多重間接,即不存在引用的引用(
-
運算子:
- 引用:對引用的操作和對原始物件的操作完全一樣,不需要使用解引用運算子
*
。int& ref = a; ref = 30; // 直接賦值,不需要解引用
- 指標:對指標的操作需要使用解引用運算子
*
。int* ptr = &a; *ptr = 30; // 需要解引用來賦值
- 引用:對引用的操作和對原始物件的操作完全一樣,不需要使用解引用運算子
-
陣列和函式引數:
- 引用:引用可以作為函式引數,以避免複製大型物件,並且可以返回函式內部的區域性變數的引用(儘管這通常不是一個好習慣)。
- 指標:指標也可以作為函式引數,但它們通常用於處理動態分配的記憶體或陣列。
-
安全性:
- 引用:引用更安全,因為它們不能被重新賦值為另一個物件,也不能被設定為
nullptr
。 - 指標:指標可以被設定為
nullptr
,也可以指向任何物件,包括無效的記憶體地址,這可能導致程式崩潰。
- 引用:引用更安全,因為它們不能被重新賦值為另一個物件,也不能被設定為
-
nullptr和空引用:
- 引用:引用不能被設定為
nullptr
,也不能指向空。 - 指標:指標可以被設定為
nullptr
,表示它不指向任何物件。
總的來說,引用提供了一種安全、簡潔的方式來訪問物件,而指標則提供了更多的靈活性和控制,但同時也帶來了更多的複雜性和潛在的錯誤。在現代C++程式設計中,引用通常被優先使用,特別是在函式引數和返回值中,以避免不必要的複製和提高程式碼的可讀性。指標仍然在處理動態記憶體分配、陣列和某些底層操作中發揮著重要作用。
- 引用:引用不能被設定為
6. 引用陣列:
不存在引用陣列
7. bool型別:
1、C++中的布林型別
(1)C++在C語言的基礎型別系統之上增加了bool;
1)C語言中,沒有bool型別存在,往往都是用整型代替bool型別,常用0表示假,1表示真;
2)bool本來就有這樣的型別,但是在C語言中卻沒有這樣的基本型別,所以只有使用整型代替bool型別,但是不嚴謹。
3)這也是C++中的“+”的體現;
(2)C++中的bool可能的值只有true和false;
1)true代表真值,編譯器內部用1來表示(但是會將非0值轉換為1儲存);
2)false代表非真值,編譯器內部用0來表示;
(3)理論上bool之佔用1個位元組
布林型別是C++中的基本資料型別
1)可以定義bool型別的全域性變數;
2)可以定義bool型別的常量;
3)可以定義bool型別的指標;
4)可以定義bool型別的陣列;
5)...;
bool型別:用於儲存真假值;
-
取值:true (1) false(0);
-
該變數的大小佔1byte空間
#include <iostream>
using namespace std;
int main()
{
bool ok;
cout << sizeof(ok) << endl;
ok = 0;
cout << ok << endl;
return 0;
}