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;
}
浙公網安備 33010602011771號