c++ —指針與數組入門
指針數組
數組的元素是指針類型
例:Point *pa[2];
由pa[1]、pa[2]兩個指向Point類的指針構成。
#include <iostream>
using namespace std;
int main()
{
int line1[3]={1,2,3};
int line2[3]={4,5,6};
int line3[3]={7,8,9};
int *pLine[3]={line1,line2,line3}; //指針數組
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
cout<<pLine[i][j]<<" "; //此時的指針數組就可以當二維數組用
cout<<endl;
}
}
注:二維數組中數據都是連續存放的,但指針數組中每個指針地址並不連續。
常指針
const int *p;
指針只能讀取,不能修改指向的值。
int *const p;
指針只能指向一個量,但可讀可寫。
const int *const p;
指針只能讀取,且只能指向一個量
指針函數
存儲類型 數據類型 *函數名()
{
//函數體
}
注:
1、不能講一個非靜態的局部地址返回給主調函數
2、但如果在函數體中通過動態內存分配new操作取得的內存地址返回給主調函數,是合法的;但是內存的分配和釋放不在同一級別,要注意不能忘記釋放,避免內存泄露。
指向函數的指針
存儲類型 數據類型 (*函數名)();
函數指針指向的是程序代碼的儲存區
典型用途:實現函數回調
通過函數指針調用該函數,使得處理相似事件時可以靈活用各種不同的方法。
調用者不關心誰是被調用者,需知道存在一個具有特定原型的限制條件的被調用函數。
例:
#include <iostream>
using namespace std;
int compute(int a,int b,int (*fun)(int,int))
{
return fun(a,b);
}
int max(int a,int b)
{
return a>b?a:b;
}
int min(int a,int b)
{
return a>b?b:a;
}
int sum(int a,int b)
{
return a+b;
}
int main()
{
int a,b,c;
cout<<"請輸入整數a:";
cin>>a;
cout<<"請輸入整數b:";
cin>>b;
c=compute(a,b,&max);
cout<<"a和b的最大值爲:"<<c<<endl;
c=compute(a,b,min); //函數名就代表它的地址,可以不加&
cout<<"a和b的最小值爲:"<<c<<endl;
c=compute(a,b,&sum);
cout<<"a和b的和爲:"<<c<<endl;
}
//這樣就可以用一個compute函數來完成三個相似的功能
對象指針
類名 *對象指針名;
例:
Point a(4,5);
Point *p;
p=& a;
通過指針訪問對象成員:
對象指針名 - >成員名
例:
p - > getX();相當於(*p) . getX();
this指針:
1、隱含於類的每一個非靜態成員中
2、指出成員函數所操作的對象:
當通過一個對象調用成員函數時,系統先將該對象的地址賦給this指針,然後調用成員函數,成員函數對對象的數據成員進行操作時,就隱含使用了this指針。
例:Point類中getX語句:return x;
相當於return this -> x;
動態分配與釋放內存
動態申請內存操作符:new
new 類型名T(初始化參數列表)
功能:程序執行期間,申請用於存放T類型對象的內存空間,並依初值列表賦予初值。
結果值:
成功:T類型的指針,指向新分配的內存;失敗:拋出異常。
釋放內存操作符:delete
delete 指針p;
功能:釋放指針p所指向的內存,且p必須是new操作的返回值。
#include <iostream>
using namespace std;
class Point{
public:
Point():x(0),y(0){
cout<<"默認構造函數"<<endl;
}
Point(int x,int y):x(x),y(y){
cout<<"構造函數"<<endl;
}
~Point(){
cout<<"析構函數"<<endl;
}
int getX(){return x;}
int getY(){return y;}
private:
int x,y;
};
int main()
{
Point *p=new Point;//調用默認構造函數
delete p;//僅僅刪除p所指向的空間,而不刪除p,p仍爲一個指針可以在後續代碼中使用
p=new Point(4,5);//調用構造函數
delete p;
}
結果:
申請和釋放動態數組
分配:new 類型名T[數組長度]
(數組長度可以是任何整數類型表達式,在運行時計算,而普通數組的長度必須是一個已經確定的值)
釋放:delete[] 數組名p
(釋放指針p所指向的數組,p必須是用new分配得到的數組首地址)
多維數組:new 類型名T [第一維長度] [第二維長度]
若爲二維數組,則返回首個一位數組的地址。
三維及以上:三維則用指向二維得到數組首地址訪問
#include <iostream>
using namespace std;
int main()
{
int (*cp)[8][9]=new int[7][8][9];
//三維數組[7][8][9]就是7個二維數組cp[8][9]所組成的,用指向[8][9]的二維數組的指針來接收它動態分配得到的首地址
for(int i=0;i<7;i++)
for(int j=0;j<8;j++)
for(int k=0;k<9;k++)
*(*(*(cp+i)+j)+k)=(i*100+j*10+k);//遍歷三維數組方式一
for(int i=0;i<7;i++){
for(int j=0;j<8;j++){
for(int k=0;k<9;k++)
cout<<cp[i][j][k]<<" ";//遍歷三維數組方式二
cout<<endl;
}
cout<<endl;
}
delete[] cp;//刪除只需要加一個[]就行
return 0;
}
將動態數組封裝成類:
1、更加簡潔,便於管理(在用時不用考慮new分配內存和delete釋放內存,用起來更加方便和安全);
2、可以在訪問數組元素前檢查下標是否越界
#include <iostream>
#include <cassert>
using namespace std;
class Point{
public:
Point():x(0),y(0){
cout<<"默認構造函數"<<endl;
}
Point(int x,int y):x(x),y(y){
cout<<"構造函數"<<endl;
}
~Point(){
cout<<"析構函數"<<endl;
}
int getX(){
return x;
}
int getY(){
return y;
}
void move(int newX,int newY){
x=newX;
y=newY;
}
private:
int x,y;
};
class ArrayOfPoints{
public:
ArrayOfPoints(int size){
points=new Point[size];//構造函數,對points數組初始化
}
~ArrayOfPoints(){
cout<<"刪除數組中"<<endl;
delete[] points;//通過析構函數來釋放動態內存
}
Point& element(int index){ //目的是真正地移動點的位置,而不是僅僅移動它的一個副本,故用引用
assert(index>=0&&index<size);//其作用是如果它的條件返回錯誤,則終止程序執行,從而檢查是否越界
return points[index];//在類外無法訪問類內的points,故需要在類內寫個函數來調用points
}
private:
Point *points;
int size;
};
int main()
{
int count;
cin>>count;
ArrayOfPoints m(count);
m.element(0).move(4,5);
m.element(1).move(1,2);
//先通過ArrayOfPoints中的element函數訪問各個點,再調用各個點的move函數進行移動
return 0;
}
c++11中的智能指針(瞭解)
unique_ptr:
不允許多個指針共享資源,可以用標準庫中的move函數轉移指針
shared_ptr:
多個指針共享資源
weak_ptr:
可複製shared_ptr,但其構造或者釋放對資源不產生影響
vector對象
1、封裝 任何類型 的動態數組,自動創建和刪除;
2、數組下標越界檢查。
vector對象的定義:
vector<元素類型>數組對象名(數組長度);
例:vectorarray(5);
vector對象的使用:
1、與普通數組具有相同形式:
對象名[下標表達式]
(因爲vector中重載了一個下標運算符函數)
注: vector數組對象名不代表首地址。
2、獲得數組長度:用size函數
對象名 . size();
(vector包含於頭文件中)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n;
cin>>n;//賦值必須在定義之前
vector<int>array(n);
for(int i=0;i<array.size();i++)//調用vector中的size函數
array[i]=i+1;
for(int k=0;k<n;k++)
cout<<array[k]<<" ";
return 0;
}
深層複製與淺層複製
淺層複製:
實現對象成員間一一對應的複製(默認複製構造函數)
深層複製:
當被複制的對象數據成員是指針類型時,不是複製該指針成員本身,而是將指針所指對象進行復制。
class ArrayOfPoints{
public:
ArrayOfPoints(int size){
points=new Point[size];
}
~ArrayOfPoints(){
cout<<"刪除數組中"<<endl;
delete[] points;
}
Point& element(int index){
assert(index>=0&&index<size);
return points[index];
}
ArrayOfPoints(const ArrayOfPoints& v);
private:
Point *points;
int size;
};
//深層複製函數
ArrayOfPoints::ArrayOfPoints(const ArrayOfPoints& v){
size=v.size;//將被複制的對象的數組大小傳給新複製的對象大小
points=new Point[size];//按此大小爲新複製的對象另開闢一個數組空間
for(int i;i<size;i++)
points[i]=v.points[i];//再將被複制對象的每個數組元素複製給新對象數組的各個元素
}
因爲若用默認的複製構造函數,則會把原對象的地址複製給新對象,則原對象改變時新對象也隨之改變,並且原對象被析構時將數組空間刪除,此時新對象再被析構是也會刪除同樣的數組空間,便會出錯。
移動構造
1、是c++11標準中新的構造方法;
2、在此之前若想將原對象的狀態轉移到一個新的目標對象則只能通過複製。但在某些情況下,我們只需要移動,而不需要複製。
3、語義:將原對象的控制權全部交給目標對象。
移動構造函數:class_name(class_name &&)
#include <iostream>
using namespace std;
class IntNum{
public:
IntNum(int x=0):xptr(new int(x)){//返回值是指向int的指針
cout<<"構造函數"<<endl;
}
IntNum(const IntNum & n):xptr(new int(*n.xptr)){
//深層複製:將對象n的 xptr指針所指向的值複製給新的int對象,然後再將新對象的指針初始化新的xptr
cout<<"複製構造函數"<<endl;
}
//移動構造函數
IntNum(IntNum && n):xptr(n.xptr){//看似爲淺層複製 ,將原對象的指針複製給新對象
n.xptr=nullptr;//實則將原對象的指針指向空指針,原對象消亡時調用析構函數刪除空指針不會對新對象產生影響
cou<<"移動構造函數"<<endl;
}
~IntNum(){
delete xptr;
cout<<"析構函數"<<endl;
}
int getInt(){
return *xptr;
}
private:
int *xptr;
};
IntNum getNum(){
IntNum a;
return a;
}//先定義一個對象a,再將a返回給主調函數,返回的是一個臨時變量,此時便要調用複製構造函數將a複製給臨時變量
int main()
{
cout<<getNum().getInt()<<endl;
return 0;
}
移動構造其實就是將原對象中指針元素的地址複製給新對象,然後在將原對象的指針指向空指針。
字符串
字符串常量:
1、相當於一個隱含創建的字符常量數組,以‘\0’結尾。
2、表示這一char數組的首地址,可以賦值給char指針。
3、例:const char *p=“program”;
字符數組表示字符串缺點:(c風格的字符串)
1、進行連接、拷貝、比較等操作,都需顯式調用庫函數,很麻煩。
2、字符串長度不確定時,需要用new動態創建字符數組,最後用delete釋放,很繁瑣。
3、有下標越界的風險。
string類:
1、string();默認構造函數,建立一個長度爲0的串
例:string s;
長度可以根據給s的字符串長度進行延展。
2、string(const char *s);用指針s所指向的字符串常量初始化string對象。
例:string s2=“abc”;
3、string(const string & rhs);複製構造函數
例:string s3=s2;
4、string常用操作:
s+t 將s和t連接成一個新串
s=t 用t更新s
s==t 判斷s和t是否相等
s!=t 判斷s和t是否不等
s<t 判斷s是否小於t(逐個字符比較)
s<=t 判斷s是否小於等於t(逐個字符比較)
s>t 判斷s是否大於t(逐個字符比較)
s>=t 判斷s是否大於等於t(逐個字符比較)
s[i] 訪問下標爲i的字符
輸入整行字符串:
1、getline可以輸入整行字符串(包括空格,cin無法讀取空格)
2、輸入時可以使用其他分隔符作爲字符串結束標誌(默認爲換行),將此分隔符作爲第三個參數即可。
例:getline(cin,s2,’,’);