【C++複習總結回顧】—— 【五】數組與指針

需要更好的閱讀的體驗請移步 👉 小牛肉的個人博客 👈



一、數組

1. 數組的存儲與初始化

一維數組

二維數組

2. 數組作爲函數參數

3. 對象數組


對象數組——線性迴歸方程實例

class Point{
    public:
        Point(float xx = 0,float yy = 0):x(xx),y(yy){ }
        float getX() const {return x;}
        float getY() const {return y;}
    private:
        float x,y;
};
//直線線性擬合,number爲點數
float lineFit(const Point points[], int number){
    float avgx = 0, avgy = 0;
    float lxx = 0,lyy = 0, lxy = 0;
    for(int i =0;i<number;i++){
        avgx += points[i].getX()/number;
        avgy += points[i].getY()/number;
    }
    for(int i = 0;i<number;i++){
        lxy += (points[i].getX() - avgx) * (points[i].getY() - avgy);
        lyy += (points[i].getY() - avgy) * (points[i].getY() - avgy);
        lxx += (points[i].getX() - avgx) * (points[i].getX() - avgx);
    }
    cout<<"This line can be fitted by y=ax+b."<<endl;
	//輸出迴歸係數a,b
    cout<<"a = "<<lxy/lxx<<" "<<"b = "<<avgy-lxy*avgx/lxx<<endl;
    return (float)(lxy/sqrt(lxx*lyy)); //線性相關的密切程度
}
int main(){
    Point points[10] = {Point(6,10),Point(14,20),Point(26,30),Point(33,40),
                        Point(46,50),Point(54,60),Point(67,70),Point(75,80),
                        Point(84,90),Point(100,100)};
    float r = lineFit(points,10);
    cout<<"Line coefficient r = "<<r<<endl;
}

二、指針

1. 指針運算符 * 和 &

  • * 稱爲指針運算符,也稱解析;
  • & 稱爲取地址運算符;

2. 指針變量的賦值運算

3. 指針的類型

指向常量的指針 const int * p

* 在const的右邊

指針類型的常量 int * const p

* 在const的左邊

4. 指針的算法運算、關係運算

5. 用指針訪問數組元素

6. 指針數組 Point *p[2]

7. 指針與函數

指針作爲函數參數

爲什麼需要用指針做參數?

  • 需要數據雙向傳遞時(引用也可以達到此效果);
    用指針作爲函數的參數,可以使被調函數通過形參指針存取主調函數中實參指針指向的數據,實現數據的雙向傳遞
  • 需要傳遞一組數據,只傳首地址運行效率比較高 ;
    實參是數組名時,形參可以是指針

引用和指針有何區別?何時只能用指針而不能使用引用

  • 引用是一個別名,不能爲空,不能被重新分配;指針是一個存放地址的變量。
  • 當需要對變量重新賦以另外的地址或賦值爲NULL時只能使用指針

指針類型的函數

若函數的返回值是指針,該函數就是指針類型的函數

int * function(){
}

函數類型的指針

即指向函數的指針

定義:數據類型 (* 函數指針名)(形參表)
int (*func)(int, int)
含義:函數指針指向的是程序代碼存儲區。


#include <iostream>
using namespace std;
int compute(int a, int b, int (*func)(int, int))
{
    return func(a, b);
}
int max(int a, int b) // 求最大值
{11
    return ((a > b) ? a : b);
}
int min(int a, int b) // 求最小值
{
    return ((a < b) ? a : b);
}
int sum(int a, int b) // 求和
{
    return a + b;
}
int main()
{
    int a, b, res;
    cout << "請輸入整數a:";
    cin >> a;
    cout << "請輸入整數b:";
    cin >> b;
    res = compute(a, b, &max);
    cout << "Max of " << a << " and " << b << " is " << res << endl;
    res = compute(a, b, &min);
    cout << "Min of " << a << " and " << b << " is " << res << endl;
    res = compute(a, b, &sum);
    cout << "Sum of " << a << " and " << b << " is " << res << endl;
}

8. 對象指針

定義形式:類名 *對象指針名

Point *ptr;
Point a(5,10);
ptr = &a;

對象指針通過 -> 訪問對象成員
ptr->getx() 相當於 (*ptr).getx();

9. this 指針

10. 動態內存分配

首先我們需要分清幾個概念
int *point = new int(5); //用5初始化
int *point = new int(); //用0初始化
int *point = new int; //不分配初值
int *point = new int[5] //表示爲該指針分配包含十個元素的地址空間

同一個程序有可能會處理很大的數據,有時處理很小,若總是以最大的空間內存分配,難免會造成浪費。

  • 在C語言中,用 malloc 進行動態內存分配;
  • 在C++中,用 new 語句

new 動態分配

動態分配一個變量

p = new T; //T代表int,double,float等等
//p類型爲T*的指針
//該語句動態分配了一片大小爲 sizeof(T) 字節的內存空間,並將起始地址賦給了p
int *pn;
pn = new int;
*pn = 5; //往剛開闢的空間中寫入了數據5

動態分配一個數組

p = new T[N];
//動態分配了一片大小爲 sizeof(T)*N 字節的內存空間,並將起始地址賦給了p
int *pn;
int i = 5;
pn = new int[i*20]; //100個元素,最大合法下標爲99
pn[0] = 20;
pn[100] = 30; // 編譯沒問題,運行時數組越界

注:new運算符返回值類型都是 T*

delete 釋放動態分配的內存

delete總是和new成對出現 :即動態分配的內存一定要釋放掉,否則佔用的內存就會越來越多。

delete指針
該指針必須指向new出來的空間

int *p = new int;
*p = 5;
delete p;
delete p; //wrong; 同一片空間不能被delete兩次

delete [ ]指針

int *p = new int[20];
p[0] = 1;
delete []p; //若delete p 則只刪除了一部分,沒有刪乾淨

三、自定義動態數組類 ArrofPoints

將數組的建立和刪除過程封裝起來,數組下標越界檢查

四、Vector類模板(C++標準庫中的)

爲什麼需要vector

  • 封裝任何類型的動態數組,自動創建和刪除
  • 數組下標越界檢查

vector對象的定義
vector<元素類型> 數組對象名(數組長度)

  • vector<int> arr(5) 建立大小爲5的int數組
  • vector<int> arr(5,2) 大小爲5的int類型數組,所有元素用2初始化

vector對象的使用

  • 對數組元素的引用 :
    與普通數組具有相同形式: vector對象名 [ 下標表達式 ]
    vector數組對象名不表示數組首地址 ,因爲數組對象不是數組,而是封裝了數組的對象
  • 獲得數組長度 :用size函數 數組對象名.size()

vector應用舉例:

五、深複製與淺複製


以上面的自定義動態數組類 ArrayOfPoints 爲例

淺複製

ArrayOfPoints pointsArray2(pointsArray1); //創建副本

  • pointsArray1和pointsArray2有相同的值,表面上好像完成了複製,但是指向的是同一個內存空間,並沒有形成真正的副本,當程序中改變pointsArray1時也會改變pointsArray2。
  • 而且,在程序結束之前,pointsArray1和pointsArray2的析構函數會自動調用,動態分配的內存空間被釋放,由於兩個對象給公用了同一塊內存空間,因此該空間被釋放兩次,導致運行錯誤。

深複製(重新編寫複製構造函數)

核心思想:重新new一個存儲空間進行值的複製操作

六、字符串

詳見第一章 【C++複習總結回顧】—— 【一】基礎知識+字符串/string類

七、手擼String類

設計一個字符串類MyString,具有構造函數、析構函數、複製構造函數、並重載運算符 +、=、+=、[ ],儘可能的完善它,使之能滿足各種需要。

#include<iostream>
#include<cstring>
using namespace std;
class Mystring{
    private:
        char* mystring; //字符指針
        int len; //字符串長度
        Mystring(int clen){ //私有構造,防止其他類進行調用創建實例,只使用一次(在+重載)
            mystring = new char[clen + 1];
            for (int i = 0; i < clen; i++)
                mystring[i] = '\0';
            len = clen;
        }
    public:
        Mystring(); //無參構造
        Mystring(const char* const cmystring); //帶參構造
        Mystring(const Mystring& rhs); //複製構造
        ~Mystring(); //析構
        int getLen() const{ //獲取長度
            return this->len;
        }
        const char *GetMyString() const{ //獲取字符串
            return mystring;
        }
		char & operator[](int offset); //重載下標運算符,作爲左值可被修改,需返回引用
        char operator [](int offset) const; //重載下標運算符,作爲右值僅能訪問
        Mystring operator +(const Mystring& rhs); //a+b a的值並不會被修改,不需要返回引用
        void operator +=(const Mystring& rhs); //a+=b 無返回值
        Mystring& operator =(const Mystring& rhs); //a=b a的值會被修改,需要返回引用
};
//無參構造
Mystring::Mystring(){
    mystring = new char[1];
    mystring[0] = '\0';
    len = 0;
}
//帶參構造
Mystring::Mystring(const char* const cmystring){
    len = strlen(cmystring);
    mystring = new char[len+1];
    for(int i = 0; i<len; i++)
        mystring[i] = cmystring[i];
    mystring[len] = '\0';
}
//複製構造
Mystring::Mystring(const Mystring& rhs){
    len = rhs.getLen();
    mystring = new char[len+1];
    for(int i = 0; i<len;i++)
        mystring[i] = rhs[i];
    mystring[len] = '\0';
}
//析構 
Mystring::~Mystring(){
    delete[] mystring;
    len = 0;
}
char& Mystring::operator[](int offset){
    if(offset>len) //若超出最大長度,返回最後一位字符
        return mystring[len-1];
    else
        return mystring[offset];
}
char Mystring::operator[](int offset) const
{
    if (offset > len) //若超出最大長度,返回最後一位字符
        return mystring[len - 1];
    else
        return mystring[offset];
}
//字符串連接+重載
Mystring Mystring :: operator +(const Mystring& rhs){
    int totallen = len + rhs.getLen();
    Mystring str(totallen);
    int i = 0;
    for(;i<len;i++)
        str[i] = mystring[i];
    for(int j = 0;j<rhs.getLen();i++,j++)
        str[i] = rhs[j];
    str[totallen] = '\0';
    return str;
}
void Mystring::operator+=(const Mystring& rhs){
    int totallen = len + rhs.getLen();
    Mystring str(totallen);
    int i = 0;
    for(;i<len;i++)
        str[i] = mystring[i];
    for (int j = 0; j < rhs.getLen(); i++, j++)
        str[i] = rhs[j];
    str[totallen] = '\0';
    *this = str;
}
Mystring &Mystring ::operator=(const Mystring &rhs)
{
    delete[] mystring;
    len = rhs.getLen();
    mystring = new char[len + 1];
    for (int i = 0; i < len; i++)
        mystring[i] = rhs[i];
    mystring[len] = '\0';
    return *this;
}

測試:

int main()
	{
	    Mystring s1("initial test");
	    cout << "S1:\t" << s1.GetMyString() << endl;
	    char *temp = "Hello World";
	    s1 = temp;
	    cout << "Now,S1 is replaced by Hello World" << endl;
	    cout << "S1:\t" << s1.GetMyString() << endl;
	    char s2[20];
	    strcpy(s2, "; I like you!");
	    cout << "S2:\t" << s2 << endl;
	    s1 += s2;
	    cout << "S1+S2:\t" << s1.GetMyString() << endl;
	    cout << "S1[4]:\t" << s1[4] << endl;
	    s1[4] = 'x';
	    cout<<"now s1[4] is repalced by x"<<endl; 
	    cout << "S1:\t" << s1.GetMyString() << endl;
	    cout << "S1[999]:\t" << s1[999] << endl;
	    Mystring s3(" Another myString");
	    cout << "S3:\t" << s3.GetMyString() << endl;
	    Mystring s4;
	    s4 = s1 + s3;
	    cout << "S1+S3:\t" << s4.GetMyString() << endl;
	    return 0;
	}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章