CPP類與對象(1)

08類與對象

類的定義

注意一些符號。

class Student
{
private:
    int age;
    void setAge(int);
protected:
    double height;
    void setHeight(double);
public:
    char* name;
    void setName(char*);
    char* getName();
    double getHeight();
    int getAge();
};

注意 :修飾符缺省,爲private
成員函數與成員數據的定義不分先後。
在這裏插入圖片描述

類與結構體

結構體類型與類的唯一的區別在於可見性:
類成員的缺省的存取權限是私有的;
結構體類型成員的缺省的存取權限是公有的。

結構體存在的主要原因:與C語言保持兼容
什麼時候用結構體而不用類:定義主要用來保存數據、沒有什麼操作的類型,這時用結構體更方便。

類與對象

  • 在定義類時,只是定義了一種數據類型,並不爲類分配存儲空間。
  • 只有在定義了類的實例(對象)後,系統纔會爲類的變量分配空間。
  • 在建立對象時,只爲對象分配用於保存數據成員的內存空間,而成員函數的代碼爲該類的每一個對象所共享。

類的成員函數

①在類裏面聲明,外面實現,注意ClassName::主要考慮到
::是域運算符,就如std::是一樣,要指明是哪裏的函數,因爲函數可能有重名)

class Student
{
    double height;
public:
    void setHeight(double);
    double getHeight();
};


void Student::setHeight(double h)
{
    height = h;
}

double Student::getHeight(){
    return height;
}

②直接在類裏面聲明並實現

class Student
{
    double height;
public:
    void setHeight(double h){
        height = h;
    }
    double getHeight(){
        return height;
    }
};
  • 內聯成員函數的實現方式
    ①如上面的情況,在類內聲明,在外面實現時,加上inline
class Student
{
    double height;
public:
    double getHeight();
};

 
inline double Student::getHeight(){
    return height;
}

②在類內實現
這時簡單的成員函數在編譯時自動作爲內聯函數來實現的。

  • 對象成員的使用
    對象名+.運算符
A a;
a.成員名

指針+->運算符

A a;
A *pa = &a;
pa->成員名

③引用+.運算符;

A &b = a;
b.成員名

也就是說;

class Student
{
public:
    double height;
    void setHeight(double);
};
inline void Student::setHeight(double h){
    height = h ;
}

int main(){
    Student s,*ps;
    s.setHeight(1.75);
    ps = &s;
    cout<<s.height<<endl;
    cout<<(*ps).height<<endl;
    cout<<ps->height<<endl;
}

/*
1.75
1.75
1.75
*/

注意這三種是等價的,但一般不用(*ps).height

對象做參數

下面的小demo揭示了c++和java中對象作參數的差異性
(1)

#include<iostream>
using namespace std;

class Clock
{
public:
    int time = 1;
};

int main(){
    void setTime(Clock&);
    void showTime(Clock&);
    Clock t1;
    setTime(t1);
    showTime(t1);
    return 0;
}

void setTime(Clock& t)
{
    cin>>t.time;
}

void showTime(Clock& t)
{
    cout<<t.time<<endl;
}
輸出結果
10(來自鍵盤)
10

(2)

#include<iostream>
using namespace std;

class Clock
{
public: 
    int time = 1;
};

int main(){
    void setTime(Clock);
    void showTime(Clock);
    Clock t1;
    setTime(t1);
    showTime(t1);
    return 0;
}

void setTime(Clock t)
{
    cin>>t.time;
}

void showTime(Clock t)
{
    cout<<t.time<<endl;
}
輸出結果
10(來自鍵盤)
1 (默認初始化值)

注意:
在java裏面,對象都是傳引用,就是控制那個對象;
但在c++裏面,如果沒有&,相當於重新創建一個對象。

這裏在給出一個小栗子,供初學者思考
(i)

#include<iostream>
using namespace std;

class Clock
{
public:
    int time = 1;
};

int main(){
    void setTime(Clock,int time = 0);
    void showTime(Clock);
    Clock t1;
    setTime(t1);
    showTime(t1);
    return 0;
}

void setTime(Clock t,int time)
{
    t.time = time;
}

void showTime(Clock t)
{
    cout<<t.time<<endl;
}
輸出結果
1

(ii)

#include<iostream>
using namespace std;

class Clock
{
public:
    int time = 1;
};

int main(){
    void setTime(Clock&,int time = 0);
    void showTime(Clock&);
    Clock t1;
    setTime(t1);
    showTime(t1);
    return 0;
}

void setTime(Clock &t,int time)
{
    t.time = time;
}

void showTime(Clock &t)
{
    cout<<t.time<<endl;
}
輸出結果
0

(iii)

#include<iostream>
using namespace std;

class Clock
{
public:
    int time = 1;
};

int main(){
    void setTime(Clock&,int time = 0);
    void showTime(Clock&);
    Clock t1;
    setTime(t1,10);
    showTime(t1);
    return 0;
}

void setTime(Clock &t,int time)
{
    t.time = time;
}

void showTime(Clock &t)
{
    cout<<t.time<<endl;
}
輸出結果
10

09類與對象的使用

構造函數和析構函數是類的兩種特殊的成員函數。
構造函數:在創建對象時,使用給定的值將對象的數據成員初始化,將對象初始化爲一個特定的初始狀態。
析構函數:在系統釋放對象前,做一些清理工作。

構造函數

格式:

ClassName:: ClassName(){}
  • 構造函數的函數名必須與類名相同。
  • 不能指定函數返回值的類型,也不能指定爲void類型,不能有return語句
  • 一個類可以定義若干個構造函數,可以帶參數,也可以沒有參數。當定義多個構造函數時,必須滿足函數重載的原則。
  • 構造函數可以指定參數的缺省值。
  • 可以是內聯函數。
  • 構造函數屬於類的成員函數,對私有數據成員、保護的數據成員和公有的數據成員均能進行初始化。
  • 在建立類對象時自動調用構造函數。構造函數不需要用戶調用,也不能被用戶主動調用。 要注意這一點和java的區別!

比如:A a;,就已經創建了一個類A的實例a,
不允許向java一樣A a = new A();

  • demo
    注意: A a1(); 是錯的,這相當於在聲明一個返回值類型爲A的函數。
#include<iostream>
using namespace std;

class A{
    int x,y;
public:
    A(int a = 1,int b = 2){
        x = a ;
        y = b ;
    }
    void print(){
        cout<<"x "<<x<<"  y "<<y<<endl;
    }
};

int main(){
    A a1;
    a1.print();
//    A a1(); 是錯的,這相當於在聲明一個返回值類型爲A的函數
    A a2(3);
    a2.print();
    A a3(3,4);
    a3.print();
    return 0;
}
輸出結果
x 1  y 2
x 3  y 2
x 3  y 4
  • 對象的分類
    • 局部對象
      函數內定義的auto型對象(auto一般省略)。
      局部對象的生命週期就是那個代碼塊,或者是函數體。
      在函數內每次定義對象時,都要調用構造函數。
    • 靜態對象
      static修飾的局部對象。首次定義對象時,只調用一次構造函數,對象一直存在。
    • 全局對象
      在還沒有調用main函數時,就創建好了。
      全局對象: 在函數之外定義對象時,調用構造函數。

一張圖區分這個過程:
在這裏插入圖片描述

  • 缺省的構造函數
    參數列表爲空,不爲數據成員設置初始值;
    如果類內定義了成員的初始值,則使用類內定義的初始值;
    如果沒有定義類內的初始值,則以默認方式初始化:基本類型的數據默認初始化的值是不確定的。

    在定義類時,只要顯式定義了一個類的構造函數,則編譯器就不產生缺省的構造函數。如果此時依然希望編譯器隱含生成默認構造函數,
    可以使用A() = default;。否則仍要使用缺省的就報錯。

要注意下面這種情況:
在這裏插入圖片描述
Case 1.參數表爲空的構造函數
Case 2.全部參數都有默認值的構造函數
編譯器無法區分,報錯!

  • 用參數初始化表(member initializer list )對數據成員初始化
    注意語法格式。
    當然放在類聲明外面實現也是可以的。
class A{
    int x,y;
public:
    A(){}
    A(int a,int b):x(a),y(b){}
};
  • 委託構造函數
    爲了避免代碼重複,可以使用委託構造函數。
    委託構造函數使用類的其他構造函數執行初始化過程。

(一種方式)

class A{
    int x,y,z;
public:
    A():x(1),y(1),z(1){}
    A(int a,int b,int c):x(a),y(b),z(c){}
};

(使用委託構造函數)

class A{
    int x,y,z;
public:
    A(int a,int b,int c):x(a),y(b),z(c){}
    A():A(0,0,0){}
    //實際上A()的構造委託給了A(int,int,int)
};

析構函數

析構函數的作用與構造函數正好相反,是在對象的生命期結束時釋放系統爲對象所分配的空間
在程序的執行過程中,當某一對象的生存期結束時,系統自動調用析構函數,收回爲對象分配的存儲空間。
格式:

ClassName:: ~ClassName(){}
  • 析構函數的幾點說明:
    • 析構函數是成員函數,函數體可寫在類體內,也可寫在類體外。
    • 析構函數名必須是在類名前面加上字符“~”。
    • 析構函數不能帶有任何參數,沒有返回值,不指定函數類型。
    • 一個類只能定義一個析構函數,不允許重載
    • 析構函數是在撤消對象時由系統自動調用的。
    • 若在類的定義中沒有顯式定義析構函數,則編譯器自動產生一個缺省的析構函數,其函數體爲空。ClassName::~ClassName() { };

一般情況下,調用析構函數的次序與調用構造函數的次序相反:
最先創建的對象,其對應的析構函數最後被調用
最後創建的對象,其對應的析構函數最先被調用。

demo

#include<iostream>
#include <string>
using namespace std;

class A{
    int x,y;
    string name;
public:
    A(int a,int b):x(a),y(b){}
    A():A(0,0){}
    ~A(){
        cout<<name<<"調用析構函數"<<endl;
    }
    
    void setName(string s){
        name = s;
    }
    void showName(){
        cout<<name<<endl;
    }
};

int main(){
    A a1;
    a1.setName("1");
    a1.showName();
    A a2;
    a2.setName("2");
    a2.showName();
    cout<<"退出主函數"<<endl;
    return 0;
}
輸出結果
1
2
退出主函數
2調用析構函數
1調用析構函數
  • 構造函數與new、析構函數與delete

    • 可以使用new運算符來動態地建立對象。建立時自動調用構造函數,以便完成初始化對象的數據成員, 最後返回這個動態對象的起始地址。(用指針保存)

    • 用new建立類的對象時,可以使用帶參數的構造函數,也可以使用無參構造函數。

    • 用new運算符產生的動態對象,當不再使用這個對象時,必須用delete運算符來釋放對象所佔用的存儲空間,有多少new,就有多少delete(不同於之前的對象創建,之前的對象生命週期結束,自動調用析構函數)。(這可沒有java的gc自動清理)

  • new運算符
    在程序執行期間,申請用於存放T類型對象的內存空間,並依初值列表賦以初值。
    返回值:成功時,T類型的指針,指向新分配的內存; 失敗時,拋出異常。

    (i)有參數列表

new A(1,2,3);

(ii)無參數列表

new A;
注意,這裏和java的區別!!!
寫成`new A();`可就錯了!
  • delete運算符
    功能:釋放指針p所指向的內存。p必須是new操作的返回值。
A *pa;
pa = new A;
delete pa//調用了析構函數;
  • 注意
    如果在構造函數中用new爲對象的數據成員分配了動態存儲空間,則在類中 應該定義一個析構函數,並在析構函數中使用delete,收回由new分配的動態存儲空間。

demo

class String{
    char *str;
    int length;
    
public:
    String(char* pstr)
    {
        if(pstr) //判斷是否爲空指針,注意和 if(*str) 的區別
        {
            length = (int)strlen(pstr);
            str = new char[length+1]; //+1是爲了給'\0'留出空間
            strcpy(str, pstr);
        }
        else{
            str = 0;
            length = 0;
        }
    }
    void show(){cout<<str<<endl;}
    //str的空間是new出來的,析構函數裏面必須delete掉
    ~String(){
        if(str){
            cout<<"調用析構函數"<<endl;
            delete []str;  //刪除連續的空間
        }
    }
    
};


int main()
{
    String s("hello world");
    s.show();
    cout<<"主函數結束R"<<endl;
    return 0;
}
hello world
主函數結束R
調用析構函數
  • 不同存儲類型的對象調用構造函數及析構函數
    1、對於全局對象(在函數外定義的對象),在程序開始執行時,調用構造函數;到程序結束時,調用析構函數。
    2、對於局部對象(在函數內定義的對象),當程序執行到定義對象的地方時,調用構造函數;在退出對象的作用域時,調用析構函數。
    3、用static定義的局部對象,在首次到達對象的定義時調用構造函數;程序結束時,調用析構函數
    4、對於用new運算符動態生成的對象,在產生對象時調用構造函數,只有使用delete運算符釋放對象時,才調用析構函數。若不使用delete來撤消動態生成的對象,程序結束時,對象仍存在並佔用相應的存儲空間系統不能自動撤消動態生成的對象。

注意:

在這裏插入圖片描述

對象數組

demo:

class Box
{
private:
    int h,w,l;
public:
    Box(int h,int w,int l):h(h),w(w),l(l){}
    int getV(){
        return h*w*l;
    }
};

int main()
{
    Box boxes[3] = {Box(1,2,3),Box(2,3,4),Box(3,4,5)};
    for(Box box:boxes)
    {
        cout<<box.getV()<<endl;
    }
    return 0;
}
  • 動態創建對象數組
    用new運算符來動態生成對象數組時,自動調用構造函數,
    用delete運算符來釋放指針所指向的對象數組佔用的存儲空間時,在指針變量的前面必須加上[ ], 才能將數組元素所佔用的空間全部釋放。否則,只釋放第0個元素所佔用的空間。

    • 分配:
      new 類型名T[數組長度]
      數組長度可以是任何表達式,在運行時計算
  • 釋放 delete[] 數組名p
    釋放指針p所指向的數組。p必須是用new分配得到的數組首地址。

A *pa;
pa = new A[3];
……
delete []pa;
  • 當使用delete時,僅僅調用了對象數組中第一個對象的析構函數
    當使用delete [ ]時,將會逐個調用析構函數。
    對象數組的內存釋放,需要做兩件事情:一是釋放最初申請的那部分空間,二是調用析構函數完成清理工作。
    在這裏插入圖片描述

  • 使用帶參數的構造函數初始化對象數組
    語法

A *pa;
pa = new A[3]{{},{},{}};
#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
using namespace std;


class A{
    int x,y;
public:
    A(int nx,int ny=1)
    {
        x = nx;
        y = ny;
    }
    A():A(0,0){}  //委託構造函數
    void show(){
        cout<<"x "<<x<<"   y "<<y<<endl;
    }
};

int main()
{
    A *p = new A[2];  //必須在類聲明裏補上無參構造函數,否則找不到
    for(int i=0;i<2;i++)
    {
        (p+i)->show();
    }
    A *pa = new A[3]{{1,20},{29,3},{2}};
    for(int i=0;i<3;i++)
    {
        (pa+i)->show();
    }
    return 0;
}

x 0   y 0
x 0   y 0
x 1   y 20
x 29   y 3
x 2   y 1

對象指針

  • 指向對象的指針
class A{
public:
	void show(){cout<<"ok"<<endl;}
};


A *pa;
A a;
pa = &a;
//調用
a.show();
(*pa).show();
pa->show();

  • 指向對象成員的指針
    對象中的成員也有地址,存放對象成員地址的指針變量就是指向對象成員的指針變量。
class A{
public:
	int x;
	……
};

A a;
int *p = &a.x;
//調用
*p

this指針

  • 在每一個成員函數中都包含一個特殊的指針,這個指針的名字是固定的,稱爲this。它是指向本類對象的指針,它的值是當前被調用的成員函數所屬對象的起始地址。
    例如,當調用成員函數a.volume()時,編譯系統就把對象a的起始地址賦給this指針,在成員函數引用數據成員時,就按照this的指向找到對象a的數據成員。a.volume()函數要計算heightwidthlength的值,實際上是執行:(this->height)*(this->width)*(this->length)當前this指向a, 相當於執行:(a.height)*(a.width)*(a.length)

在這裏插入圖片描述

const數據保護

要使數據能在一定範圍內共享,又要防止它不被修改,可以使用關鍵字const,把相關的數據定義爲常量。

措施:

  • 對於既需要共享、又需要防止改變的數據應該聲明爲常類型(用const修飾)。
  • 對於不改變對象狀態的成員函數應該聲明爲常函數

常對象

通過常對象只能調用它的常成員函數,(哪怕這個函數實際上並沒有去修改對象的數據)。常成員函數可以訪問常對象中的數據成員,但仍然不允許修改常對象中數據成員的值。
在這裏插入圖片描述

必須在成員函數加上consconst的位置。
在這裏插入圖片描述

  • 可變的數據成員
    有時編程時一定要修改常對象中的某個數據成員的值,ANSI C++考慮到實際編程時的需要,對此作了特殊的處理,對該數據成員聲明爲mutable, 如:mutable int count;把count聲明爲可變的數據成員後,在聲明爲const的成員函數中可以修改它的值。在這裏插入圖片描述

  • mutable關鍵字

class A{
    mutable int x,y;
public:
……
};

常對象只保證其數據成員是常數據成員,其值不被修改。如果在常對象中的成員函數沒有加const聲明,編譯系統把它作爲普通成員函數處理。

不要誤認爲常對象中的成員函數都是常成員函數。

常成員

+ 1常數據成員
用關鍵字const來聲明常數據成員。常數據成員的值是不能改變的。

只能通過構造函數的參數初始化表對常數據成員進行初始化,不能採用在構造函數中對常數據成員賦初值的方法。如在類體中定義了常數據成員hour:const int hour; //聲明hour爲常數據成員

demo

class A{
    const int x,y;
public:
    A(int x,int y=1):x(x),y(y){}
};

在這裏插入圖片描述

  • 2常成員函數
    普通對象也能調用const函數。
    一般的成員函數可以引用本類中的非const數據成員,也可以修改它們。
    常成員函數只能引用本類中的數據成員,而不能修改它們。
    常成員函數可以引用const數據成員,也可以引用非const的數據成員。
    const數據成員可以被const成員函數引用,也可以被非const的成員函數引用。
數據成員 普通函數 const 函數
普通成員 可以引用,可以改變 可以引用,不能改變
const成員 可以引用,不能改變 可以引用,不能改變
const 對象 不能調用 可以調用,不能改變

如果要求所有的數據成員的值都不允許改變,則可以將所有的數據成員聲明爲const, 或將對象聲明爲const(常對象),然後用const成員函數引用數據成員,這樣起到“雙保險”的作用,切實保證了數據成員不被修改。

在這裏插入圖片描述

  • const與函數重載
class A{
public:
    int f()const{return 1;}
    int f(){return 0;}
};

int main()
{
    const A a;
    A b;
    cout<<a.f()<<endl;
    cout<<b.f()<<endl;
    return 0;
}
1
0

指向對象的常指針

  • 指向對象的常指針變量的值不能改變,即始終指向同一個對象,但可以改變其所指向對象的值。
  • 往往用常指針作爲函數的形參,目的是不允許在函數執行過程中改變指針變量的值,使其始終指向原來的對象。
  • 如果想將一個指針變量固定地與一個對象相聯繫(即該指針變量始終指向一個對象),可以將它指定爲const型指針變量。

語法

類名 * const 指針變量名
注意const的位置!

A a1,a2;
A * const pa;
pa = &a1;
//違法
//pa = &a2; 

指向常對象的指針變量

  • 指向常變量的指針變量
    注意const的位置
const 類型 * 指針變量名
int x = 1;
const int* px = &x;
*px = 2//違法,不能通過px改變x的值,但x可變
x = 3;

在這裏插入圖片描述
如果一個變量已被聲明爲常變量,只能用指向常變量的指針變量指向它,而不能用一般的(指向非const型變量的)指針變量去指向它。
在這裏插入圖片描述

  • 如果函數的形參是指向非const型變量的指針,實參只能用指向非const變量的指針,而不能用指向const變量的指針。這樣,在執行函數的過程中可以改變形參指針變量所指向的變量(也就是實參指針所指向的變量)的值。
    因爲如果傳入指向const型變量的指針,可能會報錯

  • 如果函數的形參是指向const變量的指針,在執行函數過程中不能改變指針變量所指向的變量的值,因此允許實參是指向const變量的指針,或指向非const變量的指針。
    無風險,函數正常工作。在這裏插入圖片描述

  • 指向常對象的指針變量
    指向常對象的指針變量的概念和使用與指向常變量的指針變量類似

    • 如果一個對象被聲明爲常對象,只能用指向常對象的指針變量指向它,而不能用一般的(指向非const型對象的)指針變量去指向它。
    • 如果定義了一個指向常對象的指針變量,不能通過它改變所指向的對象的值,但指針變量本身的值是可以改變的
    • 如果定義了一個指向常對象的指針變量,不能通過它改變所指向的對象的值,但指針變量本身的值是可以改變的。

對象的常引用

  • 概念:
    常引用:不能修改被引用的對象。

在這裏插入圖片描述

  • const數據小結
形式 含義
A const a a 是常對象,在任何情況下不允許修改
void A::f() const f()是A類中常成員函數,可以引用,但不允許修改類的成員數據
A * const p p是指向A的實例的常指針,不能改變指向
const A * p p 是指向A類常對象的指針,不能通過p修改此對象(此對象本身也許可以修改)
const A &a1 = a a1是對象a的別名,但不可以通過a1改變a(a自己也許可以改變)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章